From 4845d7d3eeb8e7554edc39bea8c835c77a1a77d3 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Mon, 2 Feb 2026 15:59:44 +0000 Subject: [PATCH] USD ShaderAlgo : Resolve NodeGraph interface connections correctly As documented here : https://openusd.org/dev/api/usd_shade_page_front.html#UsdShadeResolvingInterface --- Changes | 3 + .../IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp | 51 +++++++++------- .../IECoreUSD/test/IECoreUSD/USDSceneTest.py | 34 +++++++++++ .../test/IECoreUSD/data/materialSubgraph.usda | 59 +++++++++++++++++++ .../data/materialTerminalFromSubgraph.usda | 26 ++++++++ 5 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 contrib/IECoreUSD/test/IECoreUSD/data/materialSubgraph.usda create mode 100644 contrib/IECoreUSD/test/IECoreUSD/data/materialTerminalFromSubgraph.usda diff --git a/Changes b/Changes index a38ac0faa8..def6ab2aa2 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,10 @@ 10.6.x.x (relative to 10.6.3.0) ======== +Fixes +----- +- USDScene : Fixed handling of connections involving UsdShadeNodeGraph interface parameters. 10.6.3.0 (relative to 10.6.2.2) ======== diff --git a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp index 07962fc3b8..3cd1cd67fc 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp @@ -50,6 +50,7 @@ #endif #include "pxr/usd/usdGeom/primvarsAPI.h" +#include "pxr/usd/usdShade/utils.h" #include "boost/algorithm/string/predicate.hpp" #include "boost/algorithm/string/replace.hpp" @@ -255,34 +256,40 @@ IECore::InternedString readShaderNetworkWalk( const pxr::SdfPath &anchorPath, co std::vector connections; for( pxr::UsdShadeInput &i : usdShader.GetInputs() ) { - pxr::UsdShadeConnectableAPI usdSource; - pxr::TfToken usdSourceName; - pxr::UsdShadeAttributeType usdSourceType; + auto sourceAttributes = i.GetValueProducingAttributes(); + if( sourceAttributes.size() == 0 ) + { + continue; + } + else if( sourceAttributes.size() > 1 ) + { + IECore::msg( + IECore::Msg::Warning, "ShaderAlgo", + boost::format( "Input `%1%` has multiple inputs" ) + % i.GetAttr().GetPath() + ); + } - pxr::UsdAttribute valueAttribute = i; - if( i.GetConnectedSource( &usdSource, &usdSourceName, &usdSourceType ) ) + const pxr::UsdShadeAttributeType type = pxr::UsdShadeUtils::GetType( sourceAttributes[0].GetName() ); + if( type == pxr::UsdShadeAttributeType::Output ) { - if( usdSourceType == pxr::UsdShadeAttributeType::Output ) + const IECoreScene::ShaderNetwork::Parameter sourceHandle = readShaderNetworkWalk( + anchorPath, pxr::UsdShadeOutput( sourceAttributes[0] ), timeCode, shaderNetwork + ); + connections.push_back( { + sourceHandle, { handle, fromUSDParameterName( i.GetBaseName() ) } + } ); + if( IECore::DataPtr d = IECoreUSD::DataAlgo::fromUSD( pxr::UsdAttribute( i ), timeCode ) ) { - const IECoreScene::ShaderNetwork::Parameter sourceHandle = readShaderNetworkWalk( - anchorPath, usdSource.GetOutput( usdSourceName ), timeCode, shaderNetwork - ); - connections.push_back( { - sourceHandle, { handle, fromUSDParameterName( i.GetBaseName() ) } - } ); - } - else - { - // Connected to an exposed input on the material container. We don't - // have an equivalent in IECoreScene::ShaderNetwork yet, so just take - // the parameter value from the exposed input. - valueAttribute = usdSource.GetInput( usdSourceName ); + parameters[fromUSDParameterName( i.GetBaseName() )] = d; } } - - if( IECore::DataPtr d = IECoreUSD::DataAlgo::fromUSD( pxr::UsdAttribute( valueAttribute ), timeCode ) ) + else if( type == pxr::UsdShadeAttributeType::Input ) { - parameters[fromUSDParameterName( i.GetBaseName() )] = d; + if( IECore::DataPtr d = IECoreUSD::DataAlgo::fromUSD( sourceAttributes[0], timeCode ) ) + { + parameters[fromUSDParameterName( i.GetBaseName() )] = d; + } } } diff --git a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py index 04ca35f9ad..af7902d4a1 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py @@ -4733,5 +4733,39 @@ def testAnimatedLight( self ) : self.assertNotEqual( shaderNetworks[frame], shaderNetworks[0] ) self.assertEqual( shaderNetworks[frame].outputShader().parameters["exposure"].value, frame / 2.0 ) + def testMaterialSubgraph( self ) : + + scene = IECoreScene.SceneInterface.create( os.path.dirname( __file__ ) + "/data/materialSubgraph.usda", IECore.IndexedIO.OpenMode.Read ) + sphere = scene.child( "sphere" ) + shaderNetwork = sphere.readAttribute( "ri:surface", 0.0 ) + + # We don't currently have the concept of a subgraph in a ShaderNetwork, + # so we only load the leaf nodes, and we "flatten" the connections and + # values. + + self.assertEqual( set( shaderNetwork.shaders().keys() ), { "lamaSurface", "pxrVary", "nodeGraph/lamaAdd", "nodeGraph/lamaDiffuse1", "nodeGraph/lamaDiffuse2" } ) + + self.assertEqual( shaderNetwork.getOutput(), ( "lamaSurface", "bxdf_out" ) ) + self.assertEqual( shaderNetwork.shaders()["lamaSurface"].parameters, IECore.CompoundData() ) + self.assertEqual( shaderNetwork.input( ( "lamaSurface", "materialFront" ) ), ( "nodeGraph/lamaAdd", "bxdf_out" ) ) + + self.assertEqual( shaderNetwork.shaders()["nodeGraph/lamaAdd"].parameters, IECore.CompoundData() ) + self.assertEqual( shaderNetwork.input( ( "nodeGraph/lamaAdd", "material1" ) ), ( "nodeGraph/lamaDiffuse1", "bxdf_out" ) ) + self.assertEqual( shaderNetwork.input( ( "nodeGraph/lamaAdd", "material2" ) ), ( "nodeGraph/lamaDiffuse2", "bxdf_out" ) ) + + self.assertEqual( shaderNetwork.shaders()["nodeGraph/lamaDiffuse1"].parameters, IECore.CompoundData( { "diffuseColor" : imath.Color3f( 2 ) } ) ) + self.assertEqual( shaderNetwork.inputConnections( "nodeGraph/lamaDiffuse1" ), [] ) + + self.assertEqual( shaderNetwork.shaders()["nodeGraph/lamaDiffuse2"].parameters, IECore.CompoundData() ) + self.assertEqual( shaderNetwork.input( ( "nodeGraph/lamaDiffuse2", "diffuseColor" ) ), ( "pxrVary", "resultRGB" ) ) + + def testMaterialTerminalFromSubgraph( self ) : + + scene = IECoreScene.SceneInterface.create( os.path.dirname( __file__ ) + "/data/materialSubgraph.usda", IECore.IndexedIO.OpenMode.Read ) + sphere = scene.child( "sphere" ) + shaderNetwork = sphere.readAttribute( "ri:surface", 0.0 ) + + self.assertEqual( shaderNetwork.outputShader().name, "LamaSurface" ) + if __name__ == "__main__": unittest.main() diff --git a/contrib/IECoreUSD/test/IECoreUSD/data/materialSubgraph.usda b/contrib/IECoreUSD/test/IECoreUSD/data/materialSubgraph.usda new file mode 100644 index 0000000000..fc090abd56 --- /dev/null +++ b/contrib/IECoreUSD/test/IECoreUSD/data/materialSubgraph.usda @@ -0,0 +1,59 @@ +#usda 1.0 + +def Sphere "sphere" ( + prepend apiSchemas = ["MaterialBindingAPI"] +) +{ + rel material:binding = +} + +def Material "material" +{ + token outputs:ri:surface.connect = + + def Shader "lamaSurface" + { + uniform token info:id = "LamaSurface" + token inputs:materialFront.connect = + token outputs:bxdf_out + } + + def NodeGraph "nodeGraph" + { + color3f inputs:diffuseColorA = ( 2, 2, 2 ) + + color3f inputs:diffuseColorB = (1, 1, 1) + color3f inputs:diffuseColorB.connect = + + token outputs:bxdf_out.connect = + + def Shader "lamaAdd" + { + uniform token info:id = "LamaAdd" + token inputs:material1.connect = + token inputs:material2.connect = + token outputs:bxdf_out + } + + def Shader "lamaDiffuse1" + { + uniform token info:id = "LamaDiffuse" + color3f inputs:diffuseColor.connect = + token outputs:bxdf_out + } + + def Shader "lamaDiffuse2" + { + uniform token info:id = "LamaDiffuse" + color3f inputs:diffuseColor.connect = + token outputs:bxdf_out + } + + } + + def Shader "pxrVary" + { + uniform token info:id = "PxrVary" + color3f outputs:resultRGB + } +} diff --git a/contrib/IECoreUSD/test/IECoreUSD/data/materialTerminalFromSubgraph.usda b/contrib/IECoreUSD/test/IECoreUSD/data/materialTerminalFromSubgraph.usda new file mode 100644 index 0000000000..3cebe037c6 --- /dev/null +++ b/contrib/IECoreUSD/test/IECoreUSD/data/materialTerminalFromSubgraph.usda @@ -0,0 +1,26 @@ +#usda 1.0 + +def Sphere "sphere" ( + prepend apiSchemas = ["MaterialBindingAPI"] +) +{ + rel material:binding = +} + +def Material "material" +{ + token outputs:ri:surface.connect = + + def NodeGraph "nodeGraph" + { + + token outputs:bxdf_out.connect = + + def Shader "lamaSurface" + { + uniform token info:id = "LamaSurface" + token outputs:bxdf_out + } + + } +}