diff --git a/src/IECoreNuke/ToNukeGeometryConverter.cpp b/src/IECoreNuke/ToNukeGeometryConverter.cpp index 00b7369bdd..b8ea64d4c3 100644 --- a/src/IECoreNuke/ToNukeGeometryConverter.cpp +++ b/src/IECoreNuke/ToNukeGeometryConverter.cpp @@ -63,6 +63,13 @@ void writeAttribute( DD::Image::GeometryList &geoList, int objIndex, const char attr->flt() = static_cast( value )->readable(); break; } + case IECore::DoubleDataTypeId : + { + // Nuke only supports float attributes, so we narrow double to float. + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::FLOAT_ATTRIB ); + attr->flt() = static_cast( static_cast( value )->readable() ); + break; + } case IECore::IntDataTypeId : { auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::INT_ATTRIB ); @@ -94,6 +101,13 @@ void writeAttribute( DD::Image::GeometryList &geoList, int objIndex, const char attr->vector3() = IECore::convert( static_cast( value )->readable() ); break; } + case IECore::Color3fDataTypeId : + { + auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::VECTOR3_ATTRIB ); + const auto &c = static_cast( value )->readable(); + attr->vector3() = DD::Image::Vector3( c[0], c[1], c[2] ); + break; + } case IECore::Color4fDataTypeId : { auto attr = geoList.writable_attribute( objIndex, GroupType::Group_Object, name, AttribType::VECTOR4_ATTRIB ); @@ -143,6 +157,13 @@ void writeAttribute( DD::Image::GeometryList &geoList, int objIndex, const char attr->matrix3() = result; break; } + // Nuke has no equivalent for InternedString types, so we silently skip them. + // We could convert InternedStringVectorData to a delimited string, but + // round-tripping wouldn't be transparent — LiveScene would need special + // handling to split the string back into a vector. + case IECore::InternedStringDataTypeId : + case IECore::InternedStringVectorDataTypeId : + break; default : IECore::msg( IECore::Msg::Warning, "ToNukeGeometryConverter", boost::format( "Unsupported attribute type \"%s\" for \"%s\"" ) % value->typeName() % name ); break; diff --git a/test/IECoreNuke/LiveSceneKnobTest.py b/test/IECoreNuke/LiveSceneKnobTest.py index f529f428a5..f72799cf63 100644 --- a/test/IECoreNuke/LiveSceneKnobTest.py +++ b/test/IECoreNuke/LiveSceneKnobTest.py @@ -511,6 +511,88 @@ def testAttributes( self ) : self.assertEqual( attr.value, imath.M44f( expectedAttr.value ) ) + def testAttributeTypeRoundTrip( self ) : + import imath + import IECoreScene + import tempfile + import os + + # Create a temporary SCC with various attribute types on a leaf location. + tmpDir = tempfile.mkdtemp() + sceneFile = os.path.join( tmpDir, "attrTypes.scc" ) + + scene = IECoreScene.SceneCache( sceneFile, IECore.IndexedIO.OpenMode.Write ) + child = scene.createChild( "obj" ) + child.writeObject( IECoreScene.MeshPrimitive.createBox( imath.Box3f( imath.V3f( -1 ), imath.V3f( 1 ) ) ), 0.0 ) + child.writeTransform( IECore.M44dData( imath.M44d() ), 0.0 ) + + child.writeAttribute( "user:testFloat", IECore.FloatData( 1.5 ), 0.0 ) + child.writeAttribute( "user:testDouble", IECore.DoubleData( 2.5 ), 0.0 ) + child.writeAttribute( "user:testInt", IECore.IntData( 42 ), 0.0 ) + child.writeAttribute( "user:testBool", IECore.BoolData( True ), 0.0 ) + child.writeAttribute( "user:testString", IECore.StringData( "hello" ), 0.0 ) + child.writeAttribute( "user:testInternedString", IECore.InternedStringData( "skipped" ), 0.0 ) + child.writeAttribute( "user:testV2f", IECore.V2fData( imath.V2f( 1, 2 ) ), 0.0 ) + child.writeAttribute( "user:testV3f", IECore.V3fData( imath.V3f( 1, 2, 3 ) ), 0.0 ) + child.writeAttribute( "user:testColor3f", IECore.Color3fData( imath.Color3f( 0.1, 0.2, 0.3 ) ), 0.0 ) + child.writeAttribute( "user:testColor4f", IECore.Color4fData( imath.Color4f( 0.1, 0.2, 0.3, 0.4 ) ), 0.0 ) + child.writeAttribute( "user:testM33f", IECore.M33fData( imath.M33f() ), 0.0 ) + child.writeAttribute( "user:testM44f", IECore.M44fData( imath.M44f() ), 0.0 ) + child.writeAttribute( "user:testM44d", IECore.M44dData( imath.M44d() ), 0.0 ) + + del child, scene + + mh = IECore.CapturingMessageHandler() + with mh : + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ["/root/obj"] ) + + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sceneReader ) + + liveScene = n.knob( "scene" ).getValue() + leaf = liveScene.scene( ["obj"] ) + + # Each attribute type round-trips through Nuke. Some types change + # (e.g. DoubleData -> FloatData, Color3fData -> V3fData) because + # Nuke only has float-precision attribute types. + cases = [ + ( "user:testFloat", IECore.FloatData, 1.5 ), + ( "user:testDouble", IECore.FloatData, 2.5 ), + ( "user:testInt", IECore.IntData, 42 ), + ( "user:testBool", IECore.IntData, 1 ), + ( "user:testString", IECore.StringData, "hello" ), + ( "user:testV2f", IECore.V2fData, imath.V2f( 1, 2 ) ), + ( "user:testV3f", IECore.V3fData, imath.V3f( 1, 2, 3 ) ), + ( "user:testColor3f", IECore.V3fData, imath.V3f( 0.1, 0.2, 0.3 ) ), + ( "user:testColor4f", IECore.Color4fData, imath.Color4f( 0.1, 0.2, 0.3, 0.4 ) ), + ( "user:testM33f", IECore.M33fData, imath.M33f() ), + ( "user:testM44f", IECore.M44fData, imath.M44f() ), + ( "user:testM44d", IECore.M44fData, imath.M44f() ), + ] + + for name, expectedType, expectedValue in cases : + self.assertIn( name, leaf.attributeNames() ) + self.assertTrue( leaf.hasAttribute( name ) ) + attr = leaf.readAttribute( name, 0 ) + self.assertIsInstance( attr, expectedType, f"Wrong type for {name}: {type( attr )}" ) + self.assertEqual( attr.value, expectedValue, f"Wrong value for {name}" ) + + # InternedStringData has no Nuke equivalent and should be silently skipped. + self.assertNotIn( "user:testInternedString", leaf.attributeNames() ) + self.assertFalse( leaf.hasAttribute( "user:testInternedString" ) ) + + # No warnings should have been emitted for the skipped type. + warnings = [m for m in mh.messages if m.level == IECore.Msg.Level.Warning] + self.assertEqual( warnings, [] ) + + os.remove( sceneFile ) + os.rmdir( tmpDir ) + + if __name__ == "__main__": unittest.main()