diff --git a/include/IECoreNuke/Convert.h b/include/IECoreNuke/Convert.h index 8d375c262d..851a9d2e93 100644 --- a/include/IECoreNuke/Convert.h +++ b/include/IECoreNuke/Convert.h @@ -41,6 +41,7 @@ #include "DDImage/Box.h" #include "DDImage/Box3.h" +#include "DDImage/Matrix3.h" #include "DDImage/Matrix4.h" #include "DDImage/Vector2.h" #include "DDImage/Vector3.h" @@ -119,6 +120,12 @@ IECORENUKE_API Imath::Color4f convert( const DD::Image::Vector4 &from ); template<> IECORENUKE_API Imath::V3d convert( const DD::Image::Vector4 &from ); +template<> +IECORENUKE_API Imath::M33f convert( const DD::Image::Matrix3 &from ); + +template<> +IECORENUKE_API Imath::M33d convert( const DD::Image::Matrix3 &from ); + template<> IECORENUKE_API Imath::M44f convert( const DD::Image::Matrix4 &from ); diff --git a/include/IECoreNuke/ToNukeGeometryConverter.h b/include/IECoreNuke/ToNukeGeometryConverter.h index 576b825e54..517859782d 100644 --- a/include/IECoreNuke/ToNukeGeometryConverter.h +++ b/include/IECoreNuke/ToNukeGeometryConverter.h @@ -37,9 +37,11 @@ #include "IECoreNuke/ToNukeConverter.h" +#include "IECore/CompoundObject.h" #include "IECore/NumericParameter.h" #include "IECore/Object.h" #include "IECore/SimpleTypedParameter.h" +#include "IECore/TypedObjectParameter.h" #include "DDImage/GeometryList.h" @@ -99,6 +101,7 @@ class IECORENUKE_API ToNukeGeometryConverter : public ToNukeConverter IECore::IntParameterPtr m_objIndexParameter; IECore::StringParameterPtr m_pathParameter; + IECore::CompoundObjectParameterPtr m_attributesParameter; }; diff --git a/src/IECoreNuke/Convert.cpp b/src/IECoreNuke/Convert.cpp index 7dbc4696e4..e6d3bef4e2 100644 --- a/src/IECoreNuke/Convert.cpp +++ b/src/IECoreNuke/Convert.cpp @@ -127,6 +127,34 @@ Imath::Color4f convert( const DD::Image::Vector4 &from ) return Imath::Color4f( from.x, from.y, from.z, from.w ); } +template<> +Imath::M33f convert( const DD::Image::Matrix3 &from ) +{ + Imath::M33f result; + for( unsigned int i=0; i<3; i++ ) + { + for( unsigned int j=0; j<3; j++ ) + { + result[j][i] = from[j][i]; + } + } + return result; +} + +template<> +Imath::M33d convert( const DD::Image::Matrix3 &from ) +{ + Imath::M33d result; + for( unsigned int i=0; i<3; i++ ) + { + for( unsigned int j=0; j<3; j++ ) + { + result[j][i] = from[j][i]; + } + } + return result; +} + template<> Imath::M44f convert( const DD::Image::Matrix4 &from ) { diff --git a/src/IECoreNuke/LiveScene.cpp b/src/IECoreNuke/LiveScene.cpp index b15c26cd31..cdb2d6f123 100644 --- a/src/IECoreNuke/LiveScene.cpp +++ b/src/IECoreNuke/LiveScene.cpp @@ -44,6 +44,7 @@ #include "IECore/Exception.h" #include "IECore/NullObject.h" +#include "IECore/SimpleTypedData.h" #include "IECore/TransformationMatrixData.h" #include "OpenEXR/OpenEXRConfig.h" @@ -81,6 +82,38 @@ IECore::TransformationMatrixd convertTransformMatrix( DD::Image::Matrix4& from ) tbb::recursive_mutex g_mutex; +IECore::ConstObjectPtr convertAttribute( const DD::Image::Attribute *attribute ) +{ + switch( attribute->type() ) + { + case DD::Image::FLOAT_ATTRIB : + return new IECore::FloatData( attribute->flt( 0 ) ); + case DD::Image::INT_ATTRIB : + return new IECore::IntData( attribute->integer( 0 ) ); + case DD::Image::STD_STRING_ATTRIB : + return new IECore::StringData( attribute->stdstring( 0 ) ); + case DD::Image::STRING_ATTRIB : + { + const char *s = attribute->string( 0 ); + return new IECore::StringData( s ? s : "" ); + } + case DD::Image::VECTOR2_ATTRIB : + return new IECore::V2fData( IECore::convert( attribute->vector2( 0 ) ) ); + case DD::Image::VECTOR3_ATTRIB : + return new IECore::V3fData( IECore::convert( attribute->vector3( 0 ) ) ); + case DD::Image::NORMAL_ATTRIB : + return new IECore::V3fData( IECore::convert( attribute->normal( 0 ) ) ); + case DD::Image::VECTOR4_ATTRIB : + return new IECore::Color4fData( IECore::convert( attribute->vector4( 0 ) ) ); + case DD::Image::MATRIX3_ATTRIB : + return new IECore::M33fData( IECore::convert( attribute->matrix3( 0 ) ) ); + case DD::Image::MATRIX4_ATTRIB : + return new IECore::M44fData( IECore::convert( attribute->matrix4( 0 ) ) ); + default : + return nullptr; + } +} + LiveScene::LiveSceneGeometryCache &cachedGeometryListMap() { static LiveScene::LiveSceneGeometryCache *cache = new LiveScene::LiveSceneGeometryCache(); @@ -424,15 +457,85 @@ void LiveScene::writeTransform( const Data *transform, double time ) bool LiveScene::hasAttribute( const Name &name ) const { + const unsigned numObjects = objectNum(); + for( unsigned i = 0; i < numObjects; ++i ) + { + if( m_pathMatcher.match( geoInfoPath( i ) ) != IECore::PathMatcher::ExactMatch ) + { + continue; + } + auto geoInfo = object( i ); + if( !geoInfo ) + { + return false; + } + // we only support Group_Object because they are only one that makes sense to match to IECoreScene Attributes + // other type of attributes are more akin to PrimitiveVariables + // so we could consider adding support for Group_Primitive/Vertex and Point as such. + auto attr = geoInfo->get_group_attribute( GroupType::Group_Object, name.c_str() ); + return attr && attr->size() > 0; + } return false; } void LiveScene::attributeNames( NameList &attrs ) const { + attrs.clear(); + const unsigned numObjects = objectNum(); + for( unsigned i = 0; i < numObjects; ++i ) + { + if( m_pathMatcher.match( geoInfoPath( i ) ) != IECore::PathMatcher::ExactMatch ) + { + continue; + } + auto geoInfo = object( i ); + if( !geoInfo ) + { + return; + } + const int count = geoInfo->get_attribcontext_count(); + for( int j = 0; j < count; ++j ) + { + auto context = geoInfo->get_attribcontext( j ); + // we only support Group_Object because they are only one that makes sense to match to IECoreScene Attributes + // other type of attributes are more akin to PrimitiveVariables + // so we could consider adding support for Group_Primitive/Vertex and Point as such. + if( context && !context->empty() && context->group == GroupType::Group_Object && context->name ) + { + attrs.push_back( context->name ); + } + } + return; + } } ConstObjectPtr LiveScene::readAttribute( const Name &name, double time ) const { + const unsigned numObjects = objectNum( &time ); + for( unsigned i = 0; i < numObjects; ++i ) + { + if( m_pathMatcher.match( geoInfoPath( i ) ) != IECore::PathMatcher::ExactMatch ) + { + continue; + } + auto geoInfo = object( i, &time ); + if( !geoInfo ) + { + return IECore::NullObject::defaultNullObject(); + } + // we only support Group_Object because they are only one that makes sense to match to IECoreScene Attributes + // other type of attributes are more akin to PrimitiveVariables + // so we could consider adding support for Group_Primitive/Vertex and Point as such. + auto attr = geoInfo->get_group_attribute( GroupType::Group_Object, name.c_str() ); + if( attr && attr->size() > 0 ) + { + if( auto converted = convertAttribute( attr ) ) + { + return converted; + } + } + return IECore::NullObject::defaultNullObject(); + } return IECore::NullObject::defaultNullObject(); } diff --git a/src/IECoreNuke/SceneCacheReader.cpp b/src/IECoreNuke/SceneCacheReader.cpp index f368b52190..23737b8009 100644 --- a/src/IECoreNuke/SceneCacheReader.cpp +++ b/src/IECoreNuke/SceneCacheReader.cpp @@ -38,6 +38,8 @@ #include "IECoreNuke/Hash.h" #include "IECoreNuke/ToNukeGeometryConverter.h" +#include "IECore/CompoundObject.h" + #include "IECoreGL/BoxPrimitive.h" #include "IECoreGL/Camera.h" #include "IECoreGL/Exception.h" @@ -1094,6 +1096,18 @@ void SceneCacheReader::loadPrimitive( DD::Image::GeometryList &out, const std::s if (converter) { converter->parameters()->parameter( "path" )->setValue( new IECore::StringData( itemPath ) ); + + IECore::CompoundObjectPtr sceneAttributes = new IECore::CompoundObject(); + IECoreScene::SceneInterface::NameList attrNames; + sceneInterface->attributeNames( attrNames ); + for( const auto &attrName : attrNames ) + { + // Safe to cast away const as the CompoundObject is only used to pass + // attribute values through to the converter, which only reads them. + sceneAttributes->members()[attrName] = boost::const_pointer_cast( sceneInterface->readAttribute( attrName, time ) ); + } + converter->parameters()->parameter( "attributes" )->setValue( sceneAttributes ); + converter->convert( out ); // store the world matrix to apply in geometry_engine because // somewhere after the create_geometry nuke reset the matrix in the SourceGeo base class. diff --git a/src/IECoreNuke/ToNukeGeometryConverter.cpp b/src/IECoreNuke/ToNukeGeometryConverter.cpp index fc5d3ac415..00b7369bdd 100644 --- a/src/IECoreNuke/ToNukeGeometryConverter.cpp +++ b/src/IECoreNuke/ToNukeGeometryConverter.cpp @@ -33,17 +33,124 @@ ////////////////////////////////////////////////////////////////////////// #include "IECoreNuke/ToNukeGeometryConverter.h" +#include "IECoreNuke/Convert.h" #include "IECoreNuke/LiveScene.h" #include "IECore/CompoundData.h" +#include "IECore/MessageHandler.h" +#include "IECore/CompoundObject.h" #include "IECore/CompoundParameter.h" +#include "IECore/SimpleTypedData.h" #include +#include "boost/format.hpp" + using namespace IECoreNuke; using namespace IECore; using namespace DD::Image; +namespace +{ + +void writeAttribute( DD::Image::GeometryList &geoList, int objIndex, const char *name, const IECore::Object *value ) +{ + switch( value->typeId() ) + { + case IECore::FloatDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::FLOAT_ATTRIB ); + attr->flt() = static_cast( value )->readable(); + break; + } + case IECore::IntDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::INT_ATTRIB ); + attr->integer() = static_cast( value )->readable(); + break; + } + case IECore::BoolDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::INT_ATTRIB ); + attr->integer() = static_cast( value )->readable() ? 1 : 0; + break; + } + case IECore::StringDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::STD_STRING_ATTRIB ); + attr->stdstring() = static_cast( value )->readable(); + break; + } + case IECore::V2fDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::VECTOR2_ATTRIB ); + const auto &v = static_cast( value )->readable(); + attr->vector2() = DD::Image::Vector2( v.x, v.y ); + break; + } + case IECore::V3fDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::VECTOR3_ATTRIB ); + attr->vector3() = IECore::convert( static_cast( value )->readable() ); + break; + } + case IECore::Color4fDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::VECTOR4_ATTRIB ); + const auto &c = static_cast( value )->readable(); + attr->vector4() = DD::Image::Vector4( c.r, c.g, c.b, c.a ); + break; + } + case IECore::M33fDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::MATRIX3_ATTRIB ); + const auto &m = static_cast( value )->readable(); + DD::Image::Matrix3 result; + for( int i = 0; i < 3; i++ ) + { + for( int j = 0; j < 3; j++ ) + { + result[j][i] = m[j][i]; + } + } + attr->matrix3() = result; + break; + } + case IECore::M44fDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::MATRIX4_ATTRIB ); + attr->matrix4() = IECore::convert( Imath::M44d( static_cast( value )->readable() ) ); + break; + } + case IECore::M44dDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::MATRIX4_ATTRIB ); + attr->matrix4() = IECore::convert( static_cast( value )->readable() ); + break; + } + case IECore::M33dDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::MATRIX3_ATTRIB ); + const auto &m = static_cast( value )->readable(); + DD::Image::Matrix3 result; + for( int i = 0; i < 3; i++ ) + { + for( int j = 0; j < 3; j++ ) + { + result[j][i] = m[j][i]; + } + } + attr->matrix3() = result; + break; + } + default : + IECore::msg( IECore::Msg::Warning, "ToNukeGeometryConverter", boost::format( "Unsupported attribute type \"%s\" for \"%s\"" ) % value->typeName() % name ); + break; + } +} + +} // namespace + IE_CORE_DEFINERUNTIMETYPED( ToNukeGeometryConverter ); ToNukeGeometryConverter::ToNukeGeometryConverter( const std::string &description, IECore::TypeId fromType, ConstObjectPtr object ) @@ -57,6 +164,9 @@ ToNukeGeometryConverter::ToNukeGeometryConverter( const std::string &description m_pathParameter = new StringParameter( "path", "The object path in the hierarchy.", new StringData() ); parameters()->addParameter( m_pathParameter ); + m_attributesParameter = new CompoundObjectParameter( "attributes", "Scene attributes to write as Group_Object attributes.", new CompoundObject() ); + parameters()->addParameter( m_attributesParameter ); + } void ToNukeGeometryConverter::convert( GeometryList &geoList ) const @@ -72,6 +182,13 @@ void ToNukeGeometryConverter::convert( GeometryList &geoList ) const auto nameAttribute = geoList.writable_attribute( objIndex, GroupType::Group_Object, IECoreNuke::LiveScene::nameAttribute.data(), AttribType::STD_STRING_ATTRIB); nameAttribute->stdstring() = m_pathParameter->getTypedValue(); + // add scene attributes as Group_Object attributes + ConstCompoundObjectPtr attributes = m_attributesParameter->getTypedValue(); + for( const auto &attr : attributes->members() ) + { + writeAttribute( geoList, objIndex, attr.first.c_str(), attr.second.get() ); + } + ConstCompoundObjectPtr operands = parameters()->getTypedValidatedValue(); doConversion( srcParameter()->getValidatedValue(), geoList, objIndex, operands.get() ); } diff --git a/test/IECoreNuke/LiveSceneKnobTest.py b/test/IECoreNuke/LiveSceneKnobTest.py index d31dbe009c..f529f428a5 100644 --- a/test/IECoreNuke/LiveSceneKnobTest.py +++ b/test/IECoreNuke/LiveSceneKnobTest.py @@ -445,6 +445,72 @@ def testReadObjectMesh(self): obj = child.readObject(0) self.assertIsInstance(obj, IECoreScene.MeshPrimitive) + def testEmptySceneAttributes( self ) : + + n = nuke.createNode( "ieLiveScene" ) + liveScene = n.knob( "scene" ).getValue() + + self.assertEqual( liveScene.attributeNames(), [] ) + self.assertFalse( liveScene.hasAttribute( "ieName" ) ) + self.assertFalse( liveScene.hasAttribute( "nonExistent" ) ) + self.assertTrue( isinstance( liveScene.readAttribute( "nonExistent", 0 ), IECore.NullObject ) ) + + def testAttributes( self ) : + import imath + import IECoreScene + + sceneFile = "test/IECoreNuke/scripts/data/liveSceneData.scc" + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ['/root/A/a', '/root/B/b'] ) + + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sceneReader ) + + liveScene = n.knob( "scene" ).getValue() + + # Root and parent locations have no matching GeoInfo, so no attributes. + self.assertEqual( liveScene.attributeNames(), [] ) + self.assertFalse( liveScene.hasAttribute( "ieName" ) ) + + for subPath in ( ["A"], ["B"] ) : + subScene = liveScene.scene( subPath ) + self.assertEqual( subScene.attributeNames(), [] ) + self.assertFalse( subScene.hasAttribute( "ieName" ) ) + + # Leaf locations have Group_Object attributes set by ToNukeGeometryConverter. + leafA = liveScene.scene( ["A", "a"] ) + self.assertIn( "ieName", leafA.attributeNames() ) + self.assertTrue( leafA.hasAttribute( "ieName" ) ) + self.assertFalse( leafA.hasAttribute( "nonExistent" ) ) + self.assertIsInstance( leafA.readAttribute( "nonExistent", 0 ), IECore.NullObject ) + + nameAttr = leafA.readAttribute( "ieName", 0 ) + self.assertIsInstance( nameAttr, IECore.StringData ) + self.assertEqual( nameAttr.value, "/A/a" ) + + leafB = liveScene.scene( ["B", "b"] ) + nameAttr = leafB.readAttribute( "ieName", 0 ) + self.assertIsInstance( nameAttr, IECore.StringData ) + self.assertEqual( nameAttr.value, "/B/b" ) + + # Scene attributes from the SCC round-trip through Nuke as Group_Object attributes. + self.assertIn( "user:Mref", leafA.attributeNames() ) + self.assertTrue( leafA.hasAttribute( "user:Mref" ) ) + + # The SCC stores M44dData but Nuke round-trips it as M44fData. + expectedScene = IECoreScene.SharedSceneInterfaces.get( sceneFile ) + expectedLeaf = expectedScene.scene( ["A", "a"] ) + expectedAttr = expectedLeaf.readAttribute( "user:Mref", 0.5 ) + + attr = leafA.readAttribute( "user:Mref", 0 ) + self.assertIsInstance( attr, IECore.M44fData ) + self.assertEqual( attr.value, imath.M44f( expectedAttr.value ) ) + + if __name__ == "__main__": unittest.main()