From 143e05b208cd6de13dd5d8645eaa3006c09e4e7f Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 18 Oct 2024 16:20:50 +0100 Subject: [PATCH 1/5] DataAlgo : Allow conversion of VtValue without SdfValueTypeName This is needed when values aren't sourced from attributes - for instance, from metadata instead. --- Changes | 2 ++ contrib/IECoreUSD/include/IECoreUSD/DataAlgo.h | 13 ++++++------- contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp | 18 +++++++++++++++--- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Changes b/Changes index 6b4860b1e6..7acb2ff56e 100644 --- a/Changes +++ b/Changes @@ -3,7 +3,9 @@ Improvements ------------ + - USDScene : PointInstancers are now loaded with invisibleIds and inactiveIds as primitive variables. +- IECoreUSD::DataAlgo : Made `valueTypeName` argument to `fromUSD( const VtValue & )` optional. This allows VtValue to be converted without having additional type information available. 10.5.9.5 (relative to 10.5.9.4) ======== diff --git a/contrib/IECoreUSD/include/IECoreUSD/DataAlgo.h b/contrib/IECoreUSD/include/IECoreUSD/DataAlgo.h index 9f8d172188..1e6c8bd611 100644 --- a/contrib/IECoreUSD/include/IECoreUSD/DataAlgo.h +++ b/contrib/IECoreUSD/include/IECoreUSD/DataAlgo.h @@ -66,13 +66,12 @@ typename USDTypeTraits::CortexType fromUSD( const T &value ); template boost::intrusive_ptr< typename USDTypeTraits::CortexVectorDataType > fromUSD( const pxr::VtArray &array ); -/// Converts USD `value` to Cortex Data, applying any additional -/// geometric interpretation implied by `valueTypeName`. If -/// `arrayAccepted` is false, then converts single element arrays -/// to simple data and emits a warning and returns nullptr for -/// all other arrays. Returns nullptr if no appropriate conversion -/// exists. -IECOREUSD_API IECore::DataPtr fromUSD( const pxr::VtValue &value, const pxr::SdfValueTypeName &valueTypeName, bool arrayAccepted = true ); +/// Converts USD `value` to Cortex Data, applying any additional geometric +/// interpretation implied by `valueTypeName` if it is provided. If +/// `arrayAccepted` is false, then converts single element arrays to simple data +/// and emits a warning and returns nullptr for all other arrays. Returns +/// nullptr if no appropriate conversion exists. +IECOREUSD_API IECore::DataPtr fromUSD( const pxr::VtValue &value, const pxr::SdfValueTypeName &valueTypeName = pxr::SdfValueTypeName(), bool arrayAccepted = true ); /// Converts the value of `attribute` at the specified time, using the attribute's /// type name to apply geometric interpretation. The meaning of `arrayAccepted` is diff --git a/contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp index 436d534ae8..99e2756643 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp @@ -323,12 +323,24 @@ static const std::mapsecond( value, arrayAccepted ); } - const auto it = g_fromVtValueConverters.find( valueTypeName.GetType() ); + const auto it = g_fromVtValueConverters.find( type ); if( it == g_fromVtValueConverters.end() ) { return nullptr; From 2f375d2bf3efbc9fbb3f61d37b779b184f8a8ee1 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 23 Oct 2024 12:56:19 +0100 Subject: [PATCH 2/5] DataAlgo : Add conversions between `VtDictionary` and `CompoundData` --- Changes | 4 +- contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp | 48 ++++++++++++++++++- .../IECoreUSD/test/IECoreUSD/DataAlgoTest.py | 2 +- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index 7acb2ff56e..56f3486184 100644 --- a/Changes +++ b/Changes @@ -5,7 +5,9 @@ Improvements ------------ - USDScene : PointInstancers are now loaded with invisibleIds and inactiveIds as primitive variables. -- IECoreUSD::DataAlgo : Made `valueTypeName` argument to `fromUSD( const VtValue & )` optional. This allows VtValue to be converted without having additional type information available. +- IECoreUSD::DataAlgo : + - Made `valueTypeName` argument to `fromUSD( const VtValue & )` optional. This allows VtValue to be converted without having additional type information available. + - Added conversions between `VtDictionary` and `CompoundData`. 10.5.9.5 (relative to 10.5.9.4) ======== diff --git a/contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp index 99e2756643..0703d7b799 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp @@ -34,6 +34,7 @@ #include "IECoreUSD/DataAlgo.h" +#include "IECore/CompoundData.h" #include "IECore/DataAlgo.h" #include "IECore/MessageHandler.h" @@ -213,6 +214,19 @@ IECore::DataPtr dataFromSdfAssetPath( const pxr::VtValue &value, GeometricData:: return dataFromSdfAssetPath( value.UncheckedGet() ); } +IECore::DataPtr dataFromDictionary( const pxr::VtValue &value, GeometricData::Interpretation interpretation, bool arrayAccepted ) +{ + CompoundDataPtr result = new CompoundData; + for( const auto &[name, v] : value.Get() ) + { + if( IECore::DataPtr d = IECoreUSD::DataAlgo::fromUSD( v, pxr::SdfValueTypeName() ) ) + { + result->writable()[name] = d; + } + } + return result; +} + static const std::map g_fromVtValueConverters = { // Numeric types @@ -279,7 +293,11 @@ static const std::map>(), &dataFromArray }, { TfType::Find(), &dataFromValue }, { TfType::Find>(), &dataFromArray }, - { TfType::Find(), &dataFromSdfAssetPath } + { TfType::Find(), &dataFromSdfAssetPath }, + + // Dictionary + + { TfType::Find(), &dataFromDictionary } }; @@ -432,6 +450,26 @@ struct VtValueFromData return VtValue( DataAlgo::toUSD( data->readable() ) ); } + VtValue operator()( const IECore::CompoundData *data, bool arrayRequired ) + { + if( arrayRequired ) + { + return VtValue(); + } + + VtDictionary result; + for( const auto &[name, value] : data->readable() ) + { + VtValue v = DataAlgo::toUSD( value.get() ); + if( !v.IsEmpty() ) + { + result[name] = v; + } + } + + return VtValue( result ); + } + VtValue operator()( const IECore::Data *data, bool arrayRequired ) const { return VtValue(); @@ -445,7 +483,13 @@ pxr::VtValue IECoreUSD::DataAlgo::toUSD( const IECore::Data *data, bool arrayReq { try { - return IECore::dispatch( data, VtValueFromData(), arrayRequired ); + VtValueFromData valueFromData; + if( auto cd = runTimeCast( data ) ) + { + // Manual dispatch since CompoundData not handled by `dispatch()`. + return valueFromData( cd, arrayRequired ); + } + return IECore::dispatch( data, valueFromData, arrayRequired ); } catch( const IECore::Exception & ) { diff --git a/contrib/IECoreUSD/test/IECoreUSD/DataAlgoTest.py b/contrib/IECoreUSD/test/IECoreUSD/DataAlgoTest.py index f594cc9086..d425851567 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/DataAlgoTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/DataAlgoTest.py @@ -83,7 +83,7 @@ def testToUSDBinding( self ) : ( IECore.FloatData( 2.5 ), 2.5 ), ( IECore.IntVectorData( [ 1, 2, 3 ] ), [ 1, 2, 3 ] ), ( IECore.PathMatcherData(), None ), - ( IECore.CompoundData(), None ), + ( IECore.CompoundData(), {} ), ] : self.assertEqual( IECoreUSD.DataAlgo.toUSD( data ), value ) From d4f8106ad76d9a138a27b10e4119931dc7691d3a Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 18 Oct 2024 16:33:30 +0100 Subject: [PATCH 3/5] USD ShaderAlgo : Replace fixed metadata with generic blind data support I think we should be moving away from pre-registered metadata entirely, since the definitions will not be available anywhere outside of Gaffer. --- Changes | 3 ++ contrib/IECoreUSD/resources/plugInfo.json | 8 +-- .../IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp | 28 ++++++++-- .../IECoreUSD/test/IECoreUSD/USDSceneTest.py | 49 +++++++++++++++++ .../data/legacyComponentConnections.usda | 54 +++++++++++++++++++ 5 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 contrib/IECoreUSD/test/IECoreUSD/data/legacyComponentConnections.usda diff --git a/Changes b/Changes index 56f3486184..310d0c061a 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,9 @@ Improvements - IECoreUSD::DataAlgo : - Made `valueTypeName` argument to `fromUSD( const VtValue & )` optional. This allows VtValue to be converted without having additional type information available. - Added conversions between `VtDictionary` and `CompoundData`. +- IECoreUSD::ShaderAlgo : + - Stopped writing `cortex_autoAdaptor` metadata, which would cause errors in DCCs without the definition registered. + - Added round-tripping of all blind data stored on Shaders. 10.5.9.5 (relative to 10.5.9.4) ======== diff --git a/contrib/IECoreUSD/resources/plugInfo.json b/contrib/IECoreUSD/resources/plugInfo.json index fc186e3f82..ae59cfc19d 100644 --- a/contrib/IECoreUSD/resources/plugInfo.json +++ b/contrib/IECoreUSD/resources/plugInfo.json @@ -17,6 +17,10 @@ "target": "usd" } }, + ## \todo This metadata causes problems because other DCCs are + # unlikely to have our `plugInfo.json` available. Replace it + # with custom data instead (`UsdObject::SetCustomData()`), since + # that doesn't require central registration. "SdfMetadata": { "cortex_isConstantPrimitiveVariable": { "type": "bool", @@ -37,9 +41,7 @@ "type": "bool", "appliesTo": "prims" }, - # Label a shader that was created automatically to hold - # a connection that USD can't support directly - if we - # want to round trip, we have to remove these on input + # Legacy metadata once used by ShaderAlgo. "cortex_autoAdapter": { "type": "bool", "appliesTo": "prims" diff --git a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp index e80fa58ab8..9f2891ead9 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp @@ -64,7 +64,8 @@ namespace { -pxr::TfToken g_adapterLabelToken( IECoreScene::ShaderNetworkAlgo::componentConnectionAdapterLabel().string() ); +const pxr::TfToken g_blindDataToken( "cortex:blindData" ); +pxr::TfToken g_legacyAdapterLabelToken( IECoreScene::ShaderNetworkAlgo::componentConnectionAdapterLabel().string() ); std::pair shaderIdAndType( const pxr::UsdShadeConnectableAPI &connectable ) { @@ -249,11 +250,26 @@ IECore::InternedString readShaderNetworkWalk( const pxr::SdfPath &anchorPath, co readNonStandardLightParameters( usdShader.GetPrim(), parameters ); IECoreScene::ShaderPtr newShader = new IECoreScene::Shader( shaderName, shaderType, parametersData ); + + // General purpose support for any Cortex blind data. + + const pxr::VtValue blindDataValue = usdShader.GetPrim().GetCustomDataByKey( g_blindDataToken ); + if( !blindDataValue.IsEmpty() ) + { + if( auto blindData = IECore::runTimeCast( IECoreUSD::DataAlgo::fromUSD( blindDataValue ) ) ) + { + newShader->blindData()->writable() = blindData->readable(); + } + } + + // Legacy support for `cortex_autoAdaptor` metadata. + pxr::VtValue metadataValue; - if( usdShader.GetPrim().GetMetadata( g_adapterLabelToken, &metadataValue ) && metadataValue.Get() ) + if( usdShader.GetPrim().GetMetadata( g_legacyAdapterLabelToken, &metadataValue ) && metadataValue.Get() ) { newShader->blindData()->writable()[ IECoreScene::ShaderNetworkAlgo::componentConnectionAdapterLabel() ] = new IECore::BoolData( true ); } + shaderNetwork.addShader( handle, std::move( newShader ) ); // Can only add connections after we've added the shader. @@ -350,10 +366,12 @@ void writeShaderParameterValues( const IECoreScene::Shader *shader, pxr::UsdShad input.Set( IECoreUSD::DataAlgo::toUSD( p.second.get() ) ); } - const IECore::BoolData *adapterMeta = shader->blindData()->member( IECoreScene::ShaderNetworkAlgo::componentConnectionAdapterLabel() ); - if( adapterMeta && adapterMeta->readable() ) + if( shader->blindData()->readable().size() ) { - usdShader.GetPrim().SetMetadata( g_adapterLabelToken, true ); + usdShader.GetPrim().SetCustomDataByKey( + g_blindDataToken, + IECoreUSD::DataAlgo::toUSD( shader->blindData() ) + ); } } diff --git a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py index 21dde18f48..1ddaaeec38 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py @@ -3534,6 +3534,55 @@ def testColor4fShaderParameterComponentConnections( self ) : root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read ) self.assertEqual( root.child( "object" ).readAttribute( "ai:surface", 0 ), network ) + def testLegacyComponentConnections( self ) : + + expectedNetwork = IECoreScene.ShaderNetwork( + shaders = { + "source" : IECoreScene.Shader( "noise" ), + "output" : IECoreScene.Shader( + "color_correct", + parameters = { + "input" : imath.Color4f( 1 ), + } + ), + }, + connections = [ + ( ( "source", "r" ), ( "output", "input.g" ) ), + ( ( "source", "g" ), ( "output", "input.b" ) ), + ( ( "source", "b" ), ( "output", "input.r" ) ), + ( ( "source", "r" ), ( "output", "input.a" ) ), + ], + output = "output", + ) + + root = IECoreScene.SceneInterface.create( os.path.join( os.path.dirname( __file__ ), "data", "legacyComponentConnections.usda" ), IECore.IndexedIO.OpenMode.Read ) + self.assertEqual( root.child( "object" ).readAttribute( "ai:surface", 0 ), expectedNetwork ) + + def testShaderBlindData( self ) : + + shader = IECoreScene.Shader( "test" ) + shader.blindData()["testInt"] = IECore.IntData( 10 ) + shader.blindData()["testFloatVector"] = IECore.FloatVectorData( [ 1, 2, 3, ] ) + shader.blindData()["test:colon"] = IECore.BoolData( True ) + shader.blindData()["testCompound"] = IECore.CompoundData( { + "testString" : "test", + "testStringVector" : IECore.StringVectorData( [ "one", "two" ] ) + } ) + + network = IECoreScene.ShaderNetwork( + shaders = { "test" : shader }, + output = ( "test", "out" ) + ) + + fileName = os.path.join( self.temporaryDirectory(), "testShaderBlindData.usda" ) + root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Write ) + object = root.createChild( "object" ) + object.writeAttribute( "surface", network, 0.0 ) + del object, root + + root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read ) + self.assertEqual( root.child( "object" ).readAttribute( "surface", 0 ), network ) + def testMaterialPurpose( self ) : def assertExpected( root ) : diff --git a/contrib/IECoreUSD/test/IECoreUSD/data/legacyComponentConnections.usda b/contrib/IECoreUSD/test/IECoreUSD/data/legacyComponentConnections.usda new file mode 100644 index 0000000000..2d2674594c --- /dev/null +++ b/contrib/IECoreUSD/test/IECoreUSD/data/legacyComponentConnections.usda @@ -0,0 +1,54 @@ +#usda 1.0 + +def Xform "object" ( + prepend apiSchemas = ["MaterialBindingAPI"] +) +{ + rel material:binding = + + def Scope "materials" ( + cortex_autoMaterials = true + ) + { + def Material "material_aaad0a40dfc4f67f90ca42cbc732dec4" + { + token outputs:arnold:surface.connect = + + def Scope "arnold_surface_shaders" + { + def Shader "output" + { + uniform token info:id = "color_correct" + color4f inputs:input = (1, 1, 1, 1) + color4f inputs:input.connect = + token outputs:DEFAULT_OUTPUT + } + + def Shader "pack" ( + cortex_autoAdapter = true + ) + { + uniform token info:id = "osl:MaterialX/mx_pack_color" + float inputs:in1 = 1 + float inputs:in1.connect = + float inputs:in2 = 1 + float inputs:in2.connect = + float inputs:in3 = 1 + float inputs:in3.connect = + float inputs:in4 = 1 + float inputs:in4.connect = + color4f outputs:out + } + + def Shader "source" + { + uniform token info:id = "noise" + float outputs:b + float outputs:g + float outputs:r + } + } + } + } +} + From 2ba41a7d2ea3218bb5bf51fac02b605210bd0e6a Mon Sep 17 00:00:00 2001 From: John Haddon Date: Mon, 21 Oct 2024 15:19:33 +0100 Subject: [PATCH 4/5] ShaderNetworkAlgo : Shuffle into related sections Nothing in the code is changing here - we're just putting related stuff in the same part of the file. --- src/IECoreScene/ShaderNetworkAlgo.cpp | 131 +++++++++++++++----------- 1 file changed, 77 insertions(+), 54 deletions(-) diff --git a/src/IECoreScene/ShaderNetworkAlgo.cpp b/src/IECoreScene/ShaderNetworkAlgo.cpp index 578da65f2c..81afb1ea16 100644 --- a/src/IECoreScene/ShaderNetworkAlgo.cpp +++ b/src/IECoreScene/ShaderNetworkAlgo.cpp @@ -54,11 +54,9 @@ using namespace Imath; using namespace IECore; using namespace IECoreScene; -namespace { - -BoolDataPtr g_trueData( new BoolData( true ) ); - -} +////////////////////////////////////////////////////////////////////////// +// `addShaders()` +////////////////////////////////////////////////////////////////////////// ShaderNetwork::Parameter ShaderNetworkAlgo::addShaders( ShaderNetwork *network, const ShaderNetwork *sourceNetwork, bool connections ) { @@ -91,6 +89,10 @@ ShaderNetwork::Parameter ShaderNetworkAlgo::addShaders( ShaderNetwork *network, ); } +////////////////////////////////////////////////////////////////////////// +// `removeUnusedShaders()` +////////////////////////////////////////////////////////////////////////// + namespace { @@ -126,6 +128,10 @@ void ShaderNetworkAlgo::removeUnusedShaders( ShaderNetwork *network ) } } +////////////////////////////////////////////////////////////////////////// +// Component connection adaptors +////////////////////////////////////////////////////////////////////////// + namespace { @@ -135,46 +141,9 @@ const InternedString g_inParameterName( "in" ); const InternedString g_outParameterName( "out" ); const InternedString g_packInParameterNames[4] = { "in1", "in2", "in3", "in4" }; const boost::regex g_componentRegex( "^(.*)\\.([rgbaxyz])$" ); -const boost::regex g_splineElementRegex( "^(.*)\\[(.*)\\]\\.y(.*)$" ); -const boost::regex g_splineAdapterInRegex( "^in([0-9]+)(\\..*)?$" ); const char *g_vectorComponents[3] = { "x", "y", "z" }; const char *g_colorComponents[4] = { "r", "g", "b", "a" }; - -ShaderNetwork::Parameter convertComponentSuffix( const ShaderNetwork::Parameter ¶meter, const std::string &suffix ) -{ - int index; - auto it = find( begin( g_vectorComponents ), end( g_vectorComponents ), suffix ); - if( it != end( g_vectorComponents ) ) - { - index = it - begin( g_vectorComponents ); - } - else - { - it = find( begin( g_colorComponents ), end( g_colorComponents ), suffix ); - assert( it != end( g_colorComponents ) ); - index = it - begin( g_colorComponents ); - } - - return ShaderNetwork::Parameter( - parameter.shader, - boost::replace_last_copy( parameter.name.string(), "." + suffix, "[" + to_string( index ) + "]" ) - ); -} - - -const int maxArrayInputAdapterSize = 32; -const InternedString g_arrayInputNames[maxArrayInputAdapterSize] = { - "in0", "in1", "in2", "in3", "in4", "in5", "in6", "in7", "in8", "in9", - "in10", "in11", "in12", "in13", "in14", "in15", "in16", "in17", "in18", "in19", - "in20", "in21", "in22", "in23", "in24", "in25", "in26", "in27", "in28", "in29", - "in30", "in31" -}; -const InternedString g_arrayOutputNames[maxArrayInputAdapterSize + 1] = { - "unused", "out1", "out2", "out3", "out4", "out5", "out6", "out7", "out8", "out9", - "out10", "out11", "out12", "out13", "out14", "out15", "out16", "out17", "out18", "out19", - "out20", "out21", "out22", "out23", "out24", "out25", "out26", "out27", "out28", "out29", - "out30", "out31", "out32" -}; +BoolDataPtr g_trueData( new BoolData( true ) ); } // namespace @@ -434,6 +403,35 @@ const InternedString &ShaderNetworkAlgo::componentConnectionAdapterLabel() return ret; } +////////////////////////////////////////////////////////////////////////// +// OSL Utilities +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +ShaderNetwork::Parameter convertComponentSuffix( const ShaderNetwork::Parameter ¶meter, const std::string &suffix ) +{ + int index; + auto it = find( begin( g_vectorComponents ), end( g_vectorComponents ), suffix ); + if( it != end( g_vectorComponents ) ) + { + index = it - begin( g_vectorComponents ); + } + else + { + it = find( begin( g_colorComponents ), end( g_colorComponents ), suffix ); + assert( it != end( g_colorComponents ) ); + index = it - begin( g_colorComponents ); + } + + return ShaderNetwork::Parameter( + parameter.shader, + boost::replace_last_copy( parameter.name.string(), "." + suffix, "[" + to_string( index ) + "]" ) + ); +} + +} // namespace void ShaderNetworkAlgo::convertOSLComponentConnections( ShaderNetwork *network ) { @@ -490,6 +488,21 @@ void ShaderNetworkAlgo::convertOSLComponentConnections( ShaderNetwork *network, } } +void ShaderNetworkAlgo::convertToOSLConventions( ShaderNetwork *network, int oslVersion ) +{ + expandSplines( network, "osl:" ); + + // \todo - it would be a bit more efficient to integrate this, and only traverse the network once, + // but I don't think it's worth duplicated the code - fix this up once this call is standard and we + // deprecate and remove convertOSLComponentConnections + convertOSLComponentConnections( network, oslVersion); + +} + +////////////////////////////////////////////////////////////////////////// +// `convertObjectVector()` +////////////////////////////////////////////////////////////////////////// + namespace { @@ -574,6 +587,10 @@ ShaderNetworkPtr ShaderNetworkAlgo::convertObjectVector( const ObjectVector *net return result; } +////////////////////////////////////////////////////////////////////////// +// Spline handling +////////////////////////////////////////////////////////////////////////// + namespace { @@ -725,6 +742,23 @@ const std::string g_oslShader( "osl:shader" ); const std::string g_colorToArrayAdapter( "Utility/__ColorToArray" ); const std::string g_floatToArrayAdapter( "Utility/__FloatToArray" ); +const int maxArrayInputAdapterSize = 32; +const InternedString g_arrayInputNames[maxArrayInputAdapterSize] = { + "in0", "in1", "in2", "in3", "in4", "in5", "in6", "in7", "in8", "in9", + "in10", "in11", "in12", "in13", "in14", "in15", "in16", "in17", "in18", "in19", + "in20", "in21", "in22", "in23", "in24", "in25", "in26", "in27", "in28", "in29", + "in30", "in31" +}; +const InternedString g_arrayOutputNames[maxArrayInputAdapterSize + 1] = { + "unused", "out1", "out2", "out3", "out4", "out5", "out6", "out7", "out8", "out9", + "out10", "out11", "out12", "out13", "out14", "out15", "out16", "out17", "out18", "out19", + "out20", "out21", "out22", "out23", "out24", "out25", "out26", "out27", "out28", "out29", + "out30", "out31", "out32" +}; + +const boost::regex g_splineElementRegex( "^(.*)\\[(.*)\\]\\.y(.*)$" ); +const boost::regex g_splineAdapterInRegex( "^in([0-9]+)(\\..*)?$" ); + template< typename TypedSpline > std::pair< InternedString, int > createSplineInputAdapter( ShaderNetwork *network, const TypedData *splineData, @@ -792,17 +826,6 @@ void ensureParametersCopy( } // namespace -void ShaderNetworkAlgo::convertToOSLConventions( ShaderNetwork *network, int oslVersion ) -{ - expandSplines( network, "osl:" ); - - // \todo - it would be a bit more efficient to integrate this, and only traverse the network once, - // but I don't think it's worth duplicated the code - fix this up once this call is standard and we - // deprecate and remove convertOSLComponentConnections - convertOSLComponentConnections( network, oslVersion); - -} - void ShaderNetworkAlgo::collapseSplines( ShaderNetwork *network, std::string targetPrefix ) { std::vector< IECore::InternedString > adapters; From 38c7fdb6fa6db6b496f6d4b217a11b72c567be0e Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 23 Oct 2024 15:53:37 +0100 Subject: [PATCH 5/5] ShaderNetworkAlgo : Allow customisation of component adaptors The main motivation here is that the MaterialX shaders we were using as adaptors don't really make sense - they're not actually available as OSL shaders any more, so they have zero chance of working in other DCCs when we export shading to USD from Gaffer. Instead we can now register renderer-specific adaptors that are available everywhere the renderer is. This also somewhat changes our approach to removing adaptors. Instead of just tagging them as "an adaptor" and figuring the rest out based on the shader name, we now register all the necessary information as blind data. This will allow us to reliably remove an adaptor even if adaptor registrations change in the future. It should also be useful at a certain studio which rewrites the `info:id` attribute on export to USD, in such a way that it broke our old adaptor removal code. --- Changes | 1 + include/IECoreScene/ShaderNetworkAlgo.h | 36 +- src/IECoreScene/ShaderNetworkAlgo.cpp | 463 +++++++++++++----- .../bindings/ShaderNetworkAlgoBinding.cpp | 25 +- test/IECoreScene/ShaderNetworkAlgoTest.py | 95 +++- .../legacyComponentConnectionAdaptors.cob | Bin 0 -> 4276 bytes 6 files changed, 467 insertions(+), 153 deletions(-) create mode 100644 test/IECoreScene/data/legacyComponentConnectionAdaptors.cob diff --git a/Changes b/Changes index 310d0c061a..e83f25cd6b 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,7 @@ Improvements - IECoreUSD::ShaderAlgo : - Stopped writing `cortex_autoAdaptor` metadata, which would cause errors in DCCs without the definition registered. - Added round-tripping of all blind data stored on Shaders. +- IECoreScene::ShaderNetworkAlgo : Added a mechanism for customising the adapter shaders used by `addComponentConnectionAdapters()`. 10.5.9.5 (relative to 10.5.9.4) ======== diff --git a/include/IECoreScene/ShaderNetworkAlgo.h b/include/IECoreScene/ShaderNetworkAlgo.h index 9f39280cd3..e7e7ef445c 100644 --- a/include/IECoreScene/ShaderNetworkAlgo.h +++ b/include/IECoreScene/ShaderNetworkAlgo.h @@ -63,17 +63,39 @@ IECORESCENE_API void removeUnusedShaders( ShaderNetwork *network ); template void depthFirstTraverse( const ShaderNetwork *network, Visitor &&visitor, IECore::InternedString shader = "" ); -/// Replace connections between sub components of colors or vectors with connections to whole parameters -/// on adapter shaders. Currently uses the OSL shaders mx_pack_color and mx_swizzle_color_float as adapters. -/// The newly created shaders will be labelled with a blind data so they can be identified. -/// If `targetPrefix` is given, only translates connections to shaders with a type starting with this string +/// Replaces connections between sub components of colors or vectors with +/// connections to whole parameters on adapter shaders. If `targetPrefix` is +/// given, only translates connections to shaders with a type starting with this +/// string. IECORESCENE_API void addComponentConnectionAdapters( ShaderNetwork *network, std::string targetPrefix = "" ); -/// Find adapters that were created by addComponentConnectionAdapters ( based on the blind data label ), -/// and remove them, replacing them with the original component connections +/// Finds adapters that were created by addComponentConnectionAdapters, and +/// removes them, replacing them with the original component connections. IECORESCENE_API void removeComponentConnectionAdapters( ShaderNetwork *network ); -/// The name of the boolean blindData label used by add/removeComponentConnectionAdapters +/// Registers an adapter to split a component from a color or vector output, ready for connection into +/// a scalar input. Used by `addComponentConnectionAdapters()`. +/// +/// - `destinationShaderType` : The type prefix for the shader receiving the connection - e.g. "ai", "osl". +/// - `component` : "r", "g", "b", "a", "x", "y", or "z". +/// - `adapter` : The shader to be used as the adapter. +/// - `inParameter` : The parameter that receives the color or vector input. +/// - `outParameter` : The parameter that outputs the component. +IECORESCENE_API void registerSplitAdapter( const std::string &destinationShaderType, IECore::InternedString component, const IECoreScene::Shader *adapter, IECore::InternedString inParameter, IECore::InternedString outParameter ); +/// Removes an adapter registration. +IECORESCENE_API void deregisterSplitAdapter( const std::string &destinationShaderType, IECore::InternedString component ); + +/// Registers an adapter to join multiple scalar components into a color or vector output. Used by `addComponentConnectionAdapters()`. +/// +/// - `destinationShaderType` : The type prefix for the shader receiving the connection - e.g. "ai", "osl". +/// - `destinationParameterType` : `(V2i|V3i|V2f|V3f|Color3f|Color4f)DataTypeId`. +/// - `inParameters` : The parameters that receives the individual components of the vector or color. +/// - `outParameter` : The parameter that outputs the vector or color. +IECORESCENE_API void registerJoinAdapter( const std::string &destinationShaderType, IECore::TypeId destinationParameterType, const IECoreScene::Shader *adapter, const std::array &inParameters, IECore::InternedString outParameter ); +/// Removes an adapter registration. +IECORESCENE_API void deregisterJoinAdapter( const std::string &destinationShaderType, IECore::TypeId destinationParameterType ); + +/// \deprecated IECORESCENE_API const IECore::InternedString &componentConnectionAdapterLabel(); /// Converts various aspects of how shaders are stored to be ready to pass directly to OSL. diff --git a/src/IECoreScene/ShaderNetworkAlgo.cpp b/src/IECoreScene/ShaderNetworkAlgo.cpp index 81afb1ea16..51230dc994 100644 --- a/src/IECoreScene/ShaderNetworkAlgo.cpp +++ b/src/IECoreScene/ShaderNetworkAlgo.cpp @@ -36,14 +36,17 @@ #include "IECoreScene/ShaderNetworkAlgo.h" +#include "IECore/DataAlgo.h" #include "IECore/SimpleTypedData.h" #include "IECore/StringAlgo.h" #include "IECore/SplineData.h" +#include "IECore/TypeTraits.h" #include "IECore/VectorTypedData.h" #include "IECore/MessageHandler.h" #include "boost/algorithm/string/predicate.hpp" #include "boost/algorithm/string/replace.hpp" +#include "boost/container/flat_map.hpp" #include "boost/regex.hpp" #include @@ -129,22 +132,199 @@ void ShaderNetworkAlgo::removeUnusedShaders( ShaderNetwork *network ) } ////////////////////////////////////////////////////////////////////////// -// Component connection adaptors +// Component connection adapters ////////////////////////////////////////////////////////////////////////// namespace { -const InternedString g_swizzleHandle( "swizzle" ); -const InternedString g_packHandle( "pack" ); -const InternedString g_inParameterName( "in" ); -const InternedString g_outParameterName( "out" ); -const InternedString g_packInParameterNames[4] = { "in1", "in2", "in3", "in4" }; +const InternedString g_splitAdapterHandle( "splitAdapter" ); +const InternedString g_splitAdapterComponent( "splitAdapter:component" ); +const InternedString g_splitAdapterInParameter( "splitAdapter:inParameter" ); +const InternedString g_splitAdapterOutParameter( "splitAdapter:outParameter" ); + +const InternedString g_joinAdapterHandle( "joinAdapter" ); +const InternedString g_joinAdapterInParameters( "joinAdapter:inParameters" ); +const InternedString g_joinAdapterOutParameter( "joinAdapter:outParameter" ); + const boost::regex g_componentRegex( "^(.*)\\.([rgbaxyz])$" ); -const char *g_vectorComponents[3] = { "x", "y", "z" }; -const char *g_colorComponents[4] = { "r", "g", "b", "a" }; +array g_vectorComponents = { "x", "y", "z" }; +array g_colorComponents = { "r", "g", "b", "a" }; BoolDataPtr g_trueData( new BoolData( true ) ); +const InternedString g_inParameterName( "in" ); +const InternedString g_outParameterName( "out" ); +array g_packInParameterNames = { "in1", "in2", "in3", "in4" }; + +struct SplitAdapter +{ + InternedString component; + ConstShaderPtr shader; + InternedString inParameter; + InternedString outParameter; +}; + +// One adapter for each output component. +using ComponentsToSplitAdapters = boost::container::flat_map; +using SplitAdapterMap = std::unordered_map; + +SplitAdapterMap &splitAdapters() +{ + static SplitAdapterMap g_map; + return g_map; +} + +const SplitAdapter &findSplitAdapter( const std::string &destinationShaderType, InternedString component ) +{ + const auto &map = splitAdapters(); + const string typePrefix = destinationShaderType.substr( 0, destinationShaderType.find_first_of( ':' ) ); + + for( const auto &key : { typePrefix, string( "*" ) } ) + { + auto it = map.find( key ); + if( it != map.end() ) + { + auto cIt = it->second.find( component ); + if( cIt != it->second.end() ) + { + return cIt->second; + } + } + } + + throw IECore::Exception( + "No component split adapter registered" + ); +} + +struct JoinAdapter +{ + ConstShaderPtr shader; + std::array inParameters; + InternedString outParameter; +}; + +using TypesToJoinAdapters = boost::container::flat_map; +using JoinAdapterMap = std::unordered_map; + +JoinAdapterMap &joinAdapters() +{ + static JoinAdapterMap g_map; + return g_map; +} + +const JoinAdapter &findJoinAdapter( const std::string &destinationShaderType, IECore::TypeId destinationParameterType ) +{ + const auto &map = joinAdapters(); + const string typePrefix = destinationShaderType.substr( 0, destinationShaderType.find_first_of( ':' ) ); + + for( const auto &key : { typePrefix, string( "*" ) } ) + { + auto it = map.find( key ); + if( it != map.end() ) + { + auto tIt = it->second.find( destinationParameterType ); + if( tIt != it->second.end() ) + { + return tIt->second; + } + } + } + + throw IECore::Exception( + "No component join adapter registered" + ); +} + +const bool g_defaultAdapterRegistrations = [] () { + + ShaderPtr splitter = new Shader( + "MaterialX/mx_swizzle_color_float", "osl:shader" + ); + + for( auto c : string( "rgbaxyz" ) ) + { + splitter->parameters()["channels"] = new StringData( { c } ); + ShaderNetworkAlgo::registerSplitAdapter( + "*", string( { c } ), + splitter.get(), + g_inParameterName, + g_outParameterName + ); + } + + ShaderPtr joiner = new Shader( "MaterialX/mx_pack_color", "osl:shader" ); + for( auto t : { V2iDataTypeId, V3iDataTypeId, V2fDataTypeId, V3fDataTypeId, Color3fDataTypeId, Color4fDataTypeId } ) + { + ShaderNetworkAlgo::registerJoinAdapter( + "*", t, + joiner.get(), + g_packInParameterNames, + g_outParameterName + ); + } + + return true; +} (); + +bool isSplitAdapter( const Shader *shader, InternedString &component, InternedString &inParameter, InternedString &outParameter ) +{ + if( auto *d = shader->blindData()->member( g_splitAdapterComponent ) ) + { + auto inP = shader->blindData()->member( g_splitAdapterInParameter ); + auto outP = shader->blindData()->member( g_splitAdapterOutParameter ); + if( inP && outP ) + { + component = d->readable(); + inParameter = inP->readable(); + outParameter = outP->readable(); + } + return true; + } + else if( auto *b = shader->blindData()->member( ShaderNetworkAlgo::componentConnectionAdapterLabel() ) ) + { + // Legacy format. + if( b->readable() && shader->getName() == "MaterialX/mx_swizzle_color_float" ) + { + component = shader->parametersData()->member( "channels" )->readable(); + inParameter = g_inParameterName; + outParameter = g_outParameterName; + return true; + } + } + + return false; +} + +bool isJoinAdapter( const Shader *shader, std::array &inParameters, InternedString &outParameter ) +{ + if( auto d = shader->blindData()->member( g_joinAdapterInParameters ) ) + { + auto o = shader->blindData()->member( g_joinAdapterOutParameter ); + if( o ) + { + for( size_t i = 0; i < inParameters.size(); ++i ) + { + inParameters[i] = i < d->readable().size() ? d->readable()[i] : InternedString(); + } + outParameter = o->readable(); + return true; + } + } + else if( auto *b = shader->blindData()->member( ShaderNetworkAlgo::componentConnectionAdapterLabel() ) ) + { + // Legacy format. + if( b->readable() && shader->getName() == "MaterialX/mx_pack_color" ) + { + inParameters = g_packInParameterNames; + outParameter = g_outParameterName; + return true; + } + } + + return false; +} + } // namespace void ShaderNetworkAlgo::addComponentConnectionAdapters( ShaderNetwork *network, std::string targetPrefix ) @@ -171,21 +351,24 @@ void ShaderNetworkAlgo::addComponentConnectionAdapters( ShaderNetwork *network, boost::cmatch match; if( boost::regex_match( connection.source.name.c_str(), match, g_componentRegex ) ) { - // Insert a conversion shader to handle connection to component + // Insert a conversion shader to handle connection from component. auto inserted = outputConversions.insert( { connection.source, ShaderNetwork::Parameter() } ); if( inserted.second ) { - ShaderPtr swizzle = new Shader( "MaterialX/mx_swizzle_color_float", "osl:shader" ); + InternedString component = match[2].str(); + const SplitAdapter &adapter = findSplitAdapter( sourceShader->getType(), component ); - swizzle->blindData()->writable()[ componentConnectionAdapterLabel() ] = g_trueData; + ShaderPtr adapterShader = adapter.shader->copy(); + adapterShader->blindData()->writable()[g_splitAdapterComponent] = new InternedStringData( component ); + adapterShader->blindData()->writable()[g_splitAdapterInParameter] = new InternedStringData( adapter.inParameter ); + adapterShader->blindData()->writable()[g_splitAdapterOutParameter] = new InternedStringData( adapter.outParameter ); - swizzle->parameters()["channels"] = new StringData( match[2] ); - const InternedString swizzleHandle = network->addShader( g_swizzleHandle, std::move( swizzle ) ); + const InternedString adapterHandle = network->addShader( g_splitAdapterHandle, std::move( adapterShader ) ); network->addConnection( ShaderNetwork::Connection( ShaderNetwork::Parameter{ connection.source.shader, InternedString( match[1] ) }, - ShaderNetwork::Parameter{ swizzleHandle, g_inParameterName } + ShaderNetwork::Parameter{ adapterHandle, adapter.inParameter } ) ); - inserted.first->second = { swizzleHandle, g_outParameterName }; + inserted.first->second = { adapterHandle, adapter.outParameter }; } network->removeConnection( connection ); network->addConnection( { inserted.first->second, connection.destination } ); @@ -215,10 +398,8 @@ void ShaderNetworkAlgo::addComponentConnectionAdapters( ShaderNetwork *network, if( boost::regex_match( connection.destination.name.c_str(), match, g_componentRegex ) ) { // Connection into a color/vector component - - // Insert a conversion shader to handle connection from component - const InternedString parameterName = match[1].str(); + auto inserted = convertedParameters.insert( parameterName ); if( !inserted.second ) { @@ -228,40 +409,66 @@ void ShaderNetworkAlgo::addComponentConnectionAdapters( ShaderNetwork *network, continue; } - // All components won't necessarily have connections, so get - // the values to fall back on for those that don't. - Color4f value( 0 ); - const Data *d = shader.second->parametersData()->member( parameterName ); - if( const V3fData *vd = runTimeCast( d ) ) - { - value = Color4f( vd->readable()[0], vd->readable()[1], vd->readable()[2], 0.0f ); - } - else if( const Color3fData *cd = runTimeCast( d ) ) - { - value = Color4f( cd->readable()[0], cd->readable()[1], cd->readable()[2], 0.0f ); - } - else if( auto c4d = runTimeCast( d ) ) - { - value = c4d->readable(); - } - - // Make shader and set fallback values + // Insert a conversion shader to handle connection from component - ShaderPtr packShader = new Shader( "MaterialX/mx_pack_color", "osl:shader" ); - packShader->blindData()->writable()[ componentConnectionAdapterLabel() ] = g_trueData; - for( int i = 0; i < 4; ++i ) + const Data *parameterValue = shader.second->parametersData()->member( parameterName ); + if( !parameterValue ) { - packShader->parameters()[g_packInParameterNames[i]] = new FloatData( value[i] ); + throw IECore::Exception( + boost::str( boost::format( + "No value found for parameter `%1%.%2%`" + ) % shader.first % parameterName ) + ); } - const InternedString packHandle = network->addShader( g_packHandle, std::move( packShader ) ); + // Make adapter shader. + + const JoinAdapter &adapter = findJoinAdapter( shader.second->getType(), parameterValue->typeId() ); + ShaderPtr adapterShader = adapter.shader->copy(); + adapterShader->blindData()->writable()[g_joinAdapterInParameters] = new InternedStringVectorData( + vector( adapter.inParameters.begin(), adapter.inParameters.end() ) + ); + adapterShader->blindData()->writable()[g_joinAdapterOutParameter] = new InternedStringData( adapter.outParameter ); + + // Set fallback values for adapter input parameters (since all may not receive connections). + + dispatch( + parameterValue, + [&] ( auto *d ) { + using DataType = typename std::remove_const_t>; + if constexpr( + TypeTraits::IsVecTypedData::value || + std::is_same_v || + std::is_same_v + ) + { + using ValueType = typename DataType::ValueType; + using BaseType = typename ValueType::BaseType; + for( size_t i = 0; i < ValueType::dimensions(); ++i ) + { + if( !adapter.inParameters[i].string().empty() ) + { + adapterShader->parameters()[adapter.inParameters[i]] = new TypedData( + d->readable()[i] + ); + } + } + } + } + ); - // Make connections + // Add shader to network and make connections. - network->addConnection( { { packHandle, g_outParameterName }, { shader.first, parameterName } } ); + const InternedString adapterHandle = network->addShader( g_joinAdapterHandle, std::move( adapterShader ) ); + network->addConnection( { { adapterHandle, adapter.outParameter }, { shader.first, parameterName } } ); for( int i = 0; i < 4; ++i ) { + if( adapter.inParameters[i].string().empty() ) + { + continue; + } + ShaderNetwork::Parameter source = network->input( { shader.first, parameterName.string() + "." + g_colorComponents[i] } ); if( !source && i < 3 ) { @@ -269,7 +476,7 @@ void ShaderNetworkAlgo::addComponentConnectionAdapters( ShaderNetwork *network, } if( source ) { - network->addConnection( { source, { packHandle, g_packInParameterNames[i] } } ); + network->addConnection( { source, { adapterHandle, adapter.inParameters[i] } } ); } } @@ -283,111 +490,75 @@ void ShaderNetworkAlgo::removeComponentConnectionAdapters( ShaderNetwork *networ { std::vector< IECore::InternedString > toRemove; + InternedString component; + InternedString inParameter; + std::array inParameters; + InternedString outParameter; + for( const auto &s : network->shaders() ) { - ConstBoolDataPtr labelValue = s.second->blindData()->member( componentConnectionAdapterLabel() ); - if( !labelValue || !labelValue->readable() ) + if( isSplitAdapter( s.second.get(), component, inParameter, outParameter ) ) { - continue; - } + ShaderNetwork::Parameter source = network->input( ShaderNetwork::Parameter( s.first, inParameter ) ); + if( !source ) + { + throw IECore::Exception( boost::str( + boost::format( + "removeComponentConnectionAdapters : \"%1%.%2%\" has no input" + ) % s.first.string() % inParameter.string() + ) ); + } + source.name = source.name.string() + "." + component.string(); - bool isPack = s.second->getName() == "MaterialX/mx_pack_color"; - bool isSwizzle = s.second->getName() == "MaterialX/mx_swizzle_color_float"; + const ShaderNetwork::ConnectionRange outputConnections = network->outputConnections( s.first ); + for( auto connectionIt = outputConnections.begin(); connectionIt != outputConnections.end(); ) + { + // Copy and increment now so we still have a valid iterator when we + // remove the connection. + const ShaderNetwork::Connection connection = *connectionIt++; + network->removeConnection( connection ); + network->addConnection( { source, connection.destination } ); + } - if( !( s.second->getType() == "osl:shader" && ( isSwizzle || isPack ) ) ) - { - throw IECore::Exception( boost::str( - boost::format( "removeComponentConnectionAdapters : adapter is not of supported type and name: '%s' %s : %s" ) % - s.first % s.second->getType() % s.second->getName() - ) ); + toRemove.push_back( s.first ); } - - toRemove.push_back( s.first ); - - ShaderNetwork::ConnectionRange outputConnections = network->outputConnections( s.first ); - - for( ShaderNetwork::ConnectionIterator it = outputConnections.begin(); it != outputConnections.end(); ) + else if( isJoinAdapter( s.second.get(), inParameters, outParameter ) ) { - // Copy and increment now so we still have a valid iterator - // if we remove the connection. - const ShaderNetwork::Connection connection = *it++; - network->removeConnection( connection ); - - if( isPack ) + std::array componentInputs; + for( size_t i = 0; i < inParameters.size(); ++i ) { - const Shader *targetShader = network->getShader( connection.destination.shader ); - - ShaderNetwork::ConnectionRange inputConnections = network->inputConnections( s.first ); - for( ShaderNetwork::ConnectionIterator inputIt = inputConnections.begin(); inputIt != inputConnections.end(); inputIt++ ) + if( !inParameters[i].string().empty() ) { - const IECore::InternedString &inputName = inputIt->destination.name; - int inputIndex = -1; - for( int i = 0; i < 4; i++ ) - { - if( inputName == g_packInParameterNames[i] ) - { - inputIndex = i; - } - } + componentInputs[i] = network->input( { s.first, inParameters[i] } ); + } + } - if( inputIndex == -1 ) - { - throw IECore::Exception( boost::str( - boost::format( - "removeComponentConnectionAdapters : Unrecognized input for mx_pack_color \"%1%\"" - ) % inputName - ) ); - } + const ShaderNetwork::ConnectionRange outputConnections = network->outputConnections( s.first ); + for( auto connectionIt = outputConnections.begin(); connectionIt != outputConnections.end(); ) + { + // Copy and increment now so we still have a valid iterator when we + // remove the connection. + const ShaderNetwork::Connection connection = *connectionIt++; + network->removeConnection( connection ); - ShaderNetwork::Parameter componentDest; - if( - targetShader->parametersData()->member( connection.destination.name ) || - targetShader->parametersData()->member( connection.destination.name ) - ) - { - componentDest = { connection.destination.shader, IECore::InternedString( connection.destination.name.string() + "." + g_colorComponents[inputIndex] ) }; - } - else if( targetShader->parametersData()->member( connection.destination.name ) ) - { - componentDest = { connection.destination.shader, IECore::InternedString( connection.destination.name.string() + "." + g_vectorComponents[inputIndex] ) }; - } - else + const Data *destinationValue = network->getShader( connection.destination.shader )->parametersData()->member( connection.destination.name ); + const bool isColor = runTimeCast( destinationValue ) || runTimeCast( destinationValue ); + + for( size_t i = 0; i < componentInputs.size(); ++i ) + { + if( !componentInputs[i] ) { - throw IECore::Exception( boost::str( - boost::format( - "removeComponentConnectionAdapters : Unrecognized type for target parameter \"%1%.%2%\"" - ) % connection.destination.shader.string() % connection.destination.name.string() - ) ); + continue; } - network->addConnection( { inputIt->source, componentDest } ); + InternedString component = isColor ? g_colorComponents.at( i ) : g_vectorComponents.at( i ); + network->addConnection( + { componentInputs[i], { connection.destination.shader, connection.destination.name.string() + "." + component.string() } } + ); } } - else - { - const StringData *channelsData = s.second->parametersData()->member( "channels" ); - if( !channelsData ) - { - throw IECore::Exception( boost::str( - boost::format( - "removeComponentConnectionAdapters : mx_swizzle_color_float \"%1%\"should have \"channels\" parameter" - ) % s.first.string() - ) ); - } - - ShaderNetwork::Parameter componentSource = network->input( ShaderNetwork::Parameter( s.first, "in" ) ); - if( !componentSource ) - { - throw IECore::Exception( boost::str( - boost::format( - "removeComponentConnectionAdapters : mx_swizzle_color_float \"%1%\" must have an input" - ) % s.first.string() - ) ); - } - componentSource.name = componentSource.name.string() + "." + channelsData->readable(); - network->addConnection( { componentSource, connection.destination } ); - } + toRemove.push_back( s.first ); } } @@ -397,6 +568,26 @@ void ShaderNetworkAlgo::removeComponentConnectionAdapters( ShaderNetwork *networ } } +void ShaderNetworkAlgo::registerSplitAdapter( const std::string &destinationShaderType, IECore::InternedString component, const IECoreScene::Shader *adapter, IECore::InternedString inParameter, IECore::InternedString outParameter ) +{ + splitAdapters()[destinationShaderType][component] = { component, adapter->copy(), inParameter, outParameter }; +} + +void ShaderNetworkAlgo::deregisterSplitAdapter( const std::string &destinationShaderType, IECore::InternedString component ) +{ + splitAdapters()[destinationShaderType].erase( component ); +} + +void ShaderNetworkAlgo::registerJoinAdapter( const std::string &destinationShaderType, IECore::TypeId destinationParameterType, const IECoreScene::Shader *adapter, const std::array &inParameters, IECore::InternedString outParameter ) +{ + joinAdapters()[destinationShaderType][destinationParameterType] = { adapter->copy(), inParameters, outParameter }; +} + +void ShaderNetworkAlgo::deregisterJoinAdapter( const std::string &destinationShaderType, IECore::TypeId destinationParameterType ) +{ + joinAdapters()[destinationShaderType].erase( destinationParameterType ); +} + const InternedString &ShaderNetworkAlgo::componentConnectionAdapterLabel() { static InternedString ret( "cortex_autoAdapter" ); @@ -420,9 +611,9 @@ ShaderNetwork::Parameter convertComponentSuffix( const ShaderNetwork::Parameter } else { - it = find( begin( g_colorComponents ), end( g_colorComponents ), suffix ); - assert( it != end( g_colorComponents ) ); - index = it - begin( g_colorComponents ); + auto cIt = find( begin( g_colorComponents ), end( g_colorComponents ), suffix ); + assert( cIt != end( g_colorComponents ) ); + index = cIt - begin( g_colorComponents ); } return ShaderNetwork::Parameter( @@ -991,7 +1182,7 @@ void ShaderNetworkAlgo::expandSplines( ShaderNetwork *network, std::string targe continue; } - // currentSplineArrayAdapters holds array adaptors that we need to use to hook up inputs to + // currentSplineArrayAdapters holds array adapters that we need to use to hook up inputs to // spline plugs. It is indexed by the name of a spline parameter for the shader, and holds // the name of the adapter shader, and the offset we need to use when accessing the knot // vector. diff --git a/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp b/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp index af9c2097e0..c886c98a7d 100644 --- a/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp +++ b/src/IECoreScene/bindings/ShaderNetworkAlgoBinding.cpp @@ -33,12 +33,14 @@ ////////////////////////////////////////////////////////////////////////// #include "boost/python.hpp" -#include "boost/pointer_cast.hpp" #include "ShaderNetworkAlgoBinding.h" #include "IECoreScene/ShaderNetworkAlgo.h" +#include "boost/pointer_cast.hpp" +#include "boost/python/stl_iterator.hpp" + using namespace boost::python; using namespace IECore; using namespace IECoreScene; @@ -46,6 +48,23 @@ using namespace IECoreScene; namespace { +void registerJoinAdapterWrapper( const std::string &destinationShaderType, IECore::TypeId destinationParameterType, const Shader *adapter, object pythonInParameters, InternedString outParameter ) +{ + std::array inParameters; + size_t i = 0; + for( auto it = stl_input_iterator( pythonInParameters ), eIt = stl_input_iterator(); it != eIt; ++it, ++i ) + { + if( i >= inParameters.size() ) + { + PyErr_SetString( PyExc_IndexError, "Too many input parameters" ); + throw_error_already_set(); + } + inParameters[i] = extract( *it ); + } + + ShaderNetworkAlgo::registerJoinAdapter( destinationShaderType, destinationParameterType, adapter, inParameters, outParameter ); +} + void convertOSLComponentConnectionsWrapper( ShaderNetwork *network, int oslVersion ) { ShaderNetworkAlgo::convertOSLComponentConnections( network, oslVersion ); @@ -78,6 +97,10 @@ void IECoreSceneModule::bindShaderNetworkAlgo() def( "removeUnusedShaders", &ShaderNetworkAlgo::removeUnusedShaders ); def( "addComponentConnectionAdapters", &ShaderNetworkAlgo::addComponentConnectionAdapters, ( arg( "network" ), arg( "targetPrefix" ) = "" ) ); def( "removeComponentConnectionAdapters", &ShaderNetworkAlgo::removeComponentConnectionAdapters, ( arg( "network" ) ) ); + def( "registerSplitAdapter", &ShaderNetworkAlgo::registerSplitAdapter, ( arg( "destinationShaderType" ), arg( "component" ), arg( "adapter" ), arg( "inParameter" ), arg( "outParameter" ) ) ); + def( "deregisterSplitAdapter", &ShaderNetworkAlgo::deregisterSplitAdapter, ( arg( "destinationShaderType" ), arg( "component" ) ) ); + def( "registerJoinAdapter", ®isterJoinAdapterWrapper, ( arg( "destinationShaderType" ), arg( "destinationParameterType" ), arg( "adapter" ), arg( "inParameters" ), arg( "outParameter" ) ) ); + def( "deregisterJoinAdapter", &ShaderNetworkAlgo::deregisterJoinAdapter, ( arg( "destinationShaderType" ), arg( "destinationParameterType" ) ) ); def( "componentConnectionAdapterLabel", &componentConnectionAdapterLabelWrapper ); def( "convertToOSLConventions", &ShaderNetworkAlgo::convertToOSLConventions ); def( "convertOSLComponentConnections", &convertOSLComponentConnectionsWrapper, ( arg( "network" ), arg( "oslVersion" ) = 10900 ) ); diff --git a/test/IECoreScene/ShaderNetworkAlgoTest.py b/test/IECoreScene/ShaderNetworkAlgoTest.py index 92ac2bfae7..0ae5268a0f 100644 --- a/test/IECoreScene/ShaderNetworkAlgoTest.py +++ b/test/IECoreScene/ShaderNetworkAlgoTest.py @@ -34,6 +34,7 @@ # ########################################################################## +import pathlib import unittest import imath @@ -271,6 +272,7 @@ def testArnoldComponentConnectionsNotConverted( self ) : self.assertEqual( n, n2 ) def testAddRemoveComponentConnectionAdapters( self ) : + source = IECoreScene.Shader( "source", "ai:shader" ) dest = IECoreScene.Shader( "dest", "ai:surface" ) @@ -350,9 +352,6 @@ def testAddRemoveComponentConnectionAdapters( self ) : self.assertEqual( converted.getShader( connectionDict["b"].source.shader ).name, "MaterialX/mx_pack_color" ) self.assertEqual( converted.getShader( connectionDict["c"].source.shader ).name, "MaterialX/mx_swizzle_color_float" ) - self.assertEqual( IECoreScene.ShaderNetworkAlgo.componentConnectionAdapterLabel(), IECore.InternedString( "cortex_autoAdapter" ) ) - self.assertEqual( converted.getShader( connectionDict["c"].source.shader ).blindData(), IECore.CompoundData( { "cortex_autoAdapter" : True } ) ) - # With a prefix that doesn't match, nothing happens self.assertEqual( prefixMismatch, network ) # With a prefix that matches, everything gets adapted @@ -365,12 +364,12 @@ def testAddRemoveComponentConnectionAdapters( self ) : self.assertTrue( network.getShader( "source1", _copy = False ).isSame( converted.getShader( "source1", _copy = False ) ) ) self.assertTrue( network.getShader( "dest", _copy = False ).isSame( converted.getShader( "dest", _copy = False ) ) ) - dest.blindData()["cortex_autoAdapter"] = IECore.BoolData( True ) - badNetwork = IECoreScene.ShaderNetwork() - badNetwork.addShader( "badHandle", dest ) - badNetwork.setOutput( IECoreScene.ShaderNetwork.Parameter( "badHandle", "" ) ) - with self.assertRaisesRegex( RuntimeError, "removeComponentConnectionAdapters : adapter is not of supported type and name: 'badHandle' ai:surface : dest" ) : - IECoreScene.ShaderNetworkAlgo.removeComponentConnectionAdapters( badNetwork ) + # Check that we can unconvert a network converted using legacy adaptor blind data. + + legacyConverted = IECore.ObjectReader( str( pathlib.Path( __file__ ).parent / "data" / "legacyComponentConnectionAdaptors.cob" ) ).read() + legacyConvertedBack = legacyConverted.copy() + IECoreScene.ShaderNetworkAlgo.removeComponentConnectionAdapters( legacyConvertedBack ) + self.assertEqual( legacyConvertedBack, network ) def testConvertObjectVector( self ) : @@ -691,5 +690,83 @@ def testColor4ComponentConnections( self ) : IECoreScene.ShaderNetworkAlgo.removeComponentConnectionAdapters( unconverted ) self.assertEqual( unconverted, original ) + def testCustomComponentConnectionAdaptors( self ) : + + original = IECoreScene.ShaderNetwork( + shaders = { + "noise1" : IECoreScene.Shader( "noise", "ai:shader" ), + "image" : IECoreScene.Shader( "image", "ai:shader", { "missing_texture_color" : imath.Color4f( 1, 2, 3, 4 ) } ), + "noise2" : IECoreScene.Shader( "noise", "ai:shader", { "color1" : imath.Color3f( 1, 2, 3 ) } ), + }, + connections = [ + ( ( "noise1", "out.r" ), ( "image", "missing_texture_color.a" ) ), + ( ( "image", "out.r" ), ( "noise2", "color1.b" ) ), + ( ( "image", "out.g" ), ( "noise2", "color1.g" ) ), + ( ( "image", "out.b" ), ( "noise2", "color1.r" ) ), + ], + output = ( "noise2", "out" ) + ) + + for c in "rgbaxyz" : + IECoreScene.ShaderNetworkAlgo.registerSplitAdapter( + "ai", c, IECoreScene.Shader( "rgba_to_float", "ai:shader", { "mode" : c } ), "input", "out" + ) + + IECoreScene.ShaderNetworkAlgo.registerJoinAdapter( + "ai", IECore.Color3fData.staticTypeId(), IECoreScene.Shader( "float_to_rgb", "ai:shader" ), ( "r", "g", "b" ), "out" + ) + + IECoreScene.ShaderNetworkAlgo.registerJoinAdapter( + "ai", IECore.Color4fData.staticTypeId(), IECoreScene.Shader( "float_to_rgba", "ai:shader" ), ( "r", "g", "b", "a" ), "out" + ) + + def deregisterAdaptors() : + + for c in "rgbaxyz" : + IECoreScene.ShaderNetworkAlgo.deregisterSplitAdapter( "ai", c ) + + IECoreScene.ShaderNetworkAlgo.deregisterJoinAdapter( "ai", IECore.Color3fData.staticTypeId() ) + IECoreScene.ShaderNetworkAlgo.deregisterJoinAdapter( "ai", IECore.Color4fData.staticTypeId() ) + + self.addCleanup( deregisterAdaptors ) + + converted = original.copy() + IECoreScene.ShaderNetworkAlgo.addComponentConnectionAdapters( converted ) + + noise2Input = converted.input( ( "noise2", "color1" ) ) + noise2InputShader = converted.getShader( noise2Input.shader ) + self.assertEqual( noise2InputShader.name, "float_to_rgb" ) + self.assertEqual( noise2InputShader.type, "ai:shader" ) + for c in "rgb" : + cInput = converted.input( ( noise2Input.shader, c ) ) + cInputShader = converted.getShader( cInput.shader ) + self.assertEqual( cInputShader.name, "rgba_to_float" ) + self.assertEqual( cInputShader.type, "ai:shader" ) + self.assertEqual( cInputShader.parameters["mode"], IECore.StringData( { "r" : "b", "g" : "g", "b" : "r" }[c] ) ) + self.assertEqual( converted.input( ( cInput.shader, "input" ) ), ( "image", "out" ) ) + + imageInput = converted.input( ( "image", "missing_texture_color" ) ) + self.assertEqual( imageInput.name, "out" ) + imageInputShader = converted.getShader( imageInput.shader ) + self.assertEqual( imageInputShader.name, "float_to_rgba" ) + self.assertEqual( imageInputShader.type, "ai:shader" ) + + noise1Split = converted.input( ( imageInput.shader, "a" ) ) + noise1SplitShader = converted.getShader( noise1Split.shader ) + self.assertEqual( noise1SplitShader.name, "rgba_to_float" ) + self.assertEqual( noise1SplitShader.type, "ai:shader" ) + self.assertEqual( converted.input( ( noise1Split.shader, "input" ) ), ( "noise1", "out" ) ) + self.assertEqual( noise1SplitShader.parameters["mode"], IECore.StringData( "r" ) ) + + # Check that removing the adaptors gets us back to the original network. + # We deregister the adaptors first because we want the removal process to + # be completely independent of the current registrations. + + deregisterAdaptors() + + unconverted = converted.copy() + IECoreScene.ShaderNetworkAlgo.removeComponentConnectionAdapters( unconverted ) + self.assertEqual( unconverted, original ) + if __name__ == "__main__": unittest.main() diff --git a/test/IECoreScene/data/legacyComponentConnectionAdaptors.cob b/test/IECoreScene/data/legacyComponentConnectionAdaptors.cob new file mode 100644 index 0000000000000000000000000000000000000000..62d2b5a2120d20c136bdfe04f5be7091aaaa55fb GIT binary patch literal 4276 zcmZ`+TWlN06+N@NT=6M#^{`g2Xl2KyW!VxbQj#S*mSjn;liDgB*>P$&lGh?5(I&Yx zAN3<`f&@*PrY@Wp+K;ww5Tu_$o=t&%6ams8FwhqLsZ$iqR}dgD(AF-{q-c<)XO?6h zGQ7ZWc4qF*+_~qTGs9UYUv#R4^qgI>A)m!cDO*@|n~Q69I#XK6RL(l3(}>50Mu#Sb zMsXlpsGdWvnszWc6i*Jtb|go}hZ5sM@u5s^VmUcx8cAC5@yTRzGM2FB=MGyVv5_R|pPq8o3OJfcTi;lzSR)B5 z9-EAhO(v2xqsIiGh<@!CD)2KF**59=L#)5fI;jWqxZ)PsLi{?c_W-NMkkuM8;pgZg)l9b zYKKrT-h<{Y%y25vA;-N@4$^@(C^Qa2iMlS!4J@FqS$ZAePiIS%EBHA3F_sptu$NP) zegs0;C%xsIe)_*j&KdK;*929;h7i4W9f(4WVmtx8tFetJruLwh&QD!JQxuUDeu_@I z={~g#K{`{ez|KyVtEKzxl?-N`oKs5N|9MW;jDdYQ$F8`&R=umgUUQG`(^IeMpZlyI z@2>Z6zQuTNy+3-3@!RVC1GgCOtM?zYE16Q(&fPnlKet@8S57al$UVU+=O)W8EwFvv z6pbYZ>-|hd>~)-+yI-;1+NxVMtXu1CtzNgSYq!_i*1G%vG(%|Xd<3`#vx`bal^UT+T>}+R3RM;U z%UD!lBlr$NgYdqC@BlXWufRJCe-~mgbpJ@MsZWdWsGd;#BaBnY1iO3mXh188Z;py@4a!aDH0*Dya%7cL=e z2(Q`w9t_`AaCf6e5Iie$sH=An`VxWz=<6GR$9Em=$F9pmBK$r)N5Xg<=N<=og}y2# z7_u(t$AmBB#>;hj{4&g;5X|8a0w0MD;-Bzm#nc(}XGM#7$9-t?wdZ?$d4{h%Dai~`Kfz`tOHAgoOwfQ=G9)EgA$Wu{V$7s{ zq&|<&8m`B`f@`E{354k;T*ry)8}&4-fvS^1$KiVm!{Fg5la!3B@rZIYE9oqrLSVP> z=Om%b7UjCjm@D$$p-aB3*7&k2eEJrF`=FC@|AJ3d!o5n<49GiGFogQ2=+VX`uUaRr zLRl3==$B0ab;O5ouMnEh$_%RT6(yxw^%;b|uXx>CM;o!(L2Mq9P`{`V>hoHg?>TLr zp*ixQs;mezNCk{I#4$2>dnf#;5n@`V927QWUhP>HE-uxRqTll(>IFK=z zN9{DeNF2U&(;y$()ez<>UX;3;dY!4O*RV{ATdLO~7bm}jtcKI>FL0uQUHV+XT$s{? z={@+b!E+pAcOmc=C?J#xNgHx71+(WAsnqIHDG&c3rhwZ9&~8o;j|Ly!N4vO!z^coK z)dnAqiJ+>qA5#^XG9L-gQI}+&!kZO+!$d+#kxQ*nmoYE+M%1ZQ2ulq5mlSiv5*@x9 zLS82mQFsVLnj%XiCiTa(ZjF-G1I5tD6My1qD9;Q0S?$tJs^OP3eVlZAP{{2MWAF*_ z9lxWFZ$LPuksoWYuHpbs42z&V%kmeNk6C_gpzExG6ACF5Y23}^4LQWKC&=;}#$TRA zzha#rT-TL(=B#_-6)L$QNuHpLQ)-g2kMqQs_MvUB50OL%QX?Eb>2rr~<7*~_Vre_U zEw2P%I0ho+04zJFaxFmX>Vm=BY-O>y1MyJ~d@6v6YIFj@s6_@L> z?&h%V4xi|A^}E2_2D&2Uel1BVMs*IB+)C45J!^abbyNki|yibT7o$V4PpZ zbD2!p&$~f6lkbF)*3n2{i}WVXUuEAh^#;7qB)g92->!8+CYJ47yYKnLjZc*vS1_}Mn|Kvs6h`3+ zjQsl$WAJR1L-s~OlP%o|F~ms8rxkbcw}+8xBRO;+4c8O{L)Xe5Fgp3-i{KHb4ISSj2=iCM{1(|rU@$HSF*H_OJEKKxsD zXfw~xVukUVfmO^iIJ5Uu(0@KZMkIB_Dw%ROT3Gt+!T>}odzrF%yAN@vYJ2hUTIpgl*EFx2dqpfC~sRtC;;&;#6^rGpnOaOO-YJZw$jT&Cu4Ckonihp z{R4+I`t$95@G5x0A;cOgD%1_sB&nNbBR*N%DRD{#hjwzg2|s2y8rR*588XdQfr*mz z$Yl;Q6_5@wM@GoMU2ZXxIQ3&c2$sjP?Ew^LbGe;FV4|XUu Zf^oO9)%aok*MMw+-`AehZ2>pW{tq2iB!mC} literal 0 HcmV?d00001