From ba11e404fdb7d4272ab1b7c1488cedf62447877c Mon Sep 17 00:00:00 2001 From: Lucien Fostier Date: Mon, 29 Jun 2026 10:59:25 -0700 Subject: [PATCH] ToNukeGeometryConverter: Add new attribute type conversion and silently skip internedStringVectorData while warning for other types We still want to warn about unsupported type but we purposely skip the one that can't be transparently round tripped like InternedStringVectorData to cut down the noise. --- src/IECoreNuke/ToNukeGeometryConverter.cpp | 21 ++++++ test/IECoreNuke/LiveSceneKnobTest.py | 82 ++++++++++++++++++++++ 2 files changed, 103 insertions(+) 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()