diff --git a/Changes b/Changes index 6b4860b1e6..e83f25cd6b 100644 --- a/Changes +++ b/Changes @@ -3,7 +3,15 @@ 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. + - 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. +- 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/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/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/DataAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/DataAlgo.cpp index 436d534ae8..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 } }; @@ -323,12 +341,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; @@ -420,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(); @@ -433,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/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/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 ) 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 + } + } + } + } +} + 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 578da65f2c..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 @@ -54,11 +57,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 +92,10 @@ ShaderNetwork::Parameter ShaderNetworkAlgo::addShaders( ShaderNetwork *network, ); } +////////////////////////////////////////////////////////////////////////// +// `removeUnusedShaders()` +////////////////////////////////////////////////////////////////////////// + namespace { @@ -126,55 +131,199 @@ void ShaderNetworkAlgo::removeUnusedShaders( ShaderNetwork *network ) } } +////////////////////////////////////////////////////////////////////////// +// Component connection adapters +////////////////////////////////////////////////////////////////////////// + namespace { -const InternedString g_swizzleHandle( "swizzle" ); -const InternedString g_packHandle( "pack" ); +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])$" ); +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" ); -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" }; +array g_packInParameterNames = { "in1", "in2", "in3", "in4" }; -ShaderNetwork::Parameter convertComponentSuffix( const ShaderNetwork::Parameter ¶meter, const std::string &suffix ) +struct SplitAdapter { - int index; - auto it = find( begin( g_vectorComponents ), end( g_vectorComponents ), suffix ); - if( it != end( g_vectorComponents ) ) + 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( "*" ) } ) { - index = it - begin( g_vectorComponents ); + auto it = map.find( key ); + if( it != map.end() ) + { + auto cIt = it->second.find( component ); + if( cIt != it->second.end() ) + { + return cIt->second; + } + } } - else + + 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( "*" ) } ) { - it = find( begin( g_colorComponents ), end( g_colorComponents ), suffix ); - assert( it != end( g_colorComponents ) ); - index = it - begin( g_colorComponents ); + auto it = map.find( key ); + if( it != map.end() ) + { + auto tIt = it->second.find( destinationParameterType ); + if( tIt != it->second.end() ) + { + return tIt->second; + } + } } - return ShaderNetwork::Parameter( - parameter.shader, - boost::replace_last_copy( parameter.name.string(), "." + suffix, "[" + to_string( index ) + "]" ) + throw IECore::Exception( + "No component join adapter registered" ); } +const bool g_defaultAdapterRegistrations = [] () { -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" -}; + 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 @@ -202,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 } ); @@ -246,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 ) { @@ -259,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. - // Make connections + 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] + ); + } + } + } + } + ); + + // 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 ) { @@ -300,7 +476,7 @@ void ShaderNetworkAlgo::addComponentConnectionAdapters( ShaderNetwork *network, } if( source ) { - network->addConnection( { source, { packHandle, g_packInParameterNames[i] } } ); + network->addConnection( { source, { adapterHandle, adapter.inParameters[i] } } ); } } @@ -314,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 ); } } @@ -428,12 +568,61 @@ 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" ); 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 + { + auto cIt = find( begin( g_colorComponents ), end( g_colorComponents ), suffix ); + assert( cIt != end( g_colorComponents ) ); + index = cIt - 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 +679,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 +778,10 @@ ShaderNetworkPtr ShaderNetworkAlgo::convertObjectVector( const ObjectVector *net return result; } +////////////////////////////////////////////////////////////////////////// +// Spline handling +////////////////////////////////////////////////////////////////////////// + namespace { @@ -725,6 +933,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 +1017,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; @@ -968,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 0000000000..62d2b5a212 Binary files /dev/null and b/test/IECoreScene/data/legacyComponentConnectionAdaptors.cob differ