Skip to content

Commit 4b63469

Browse files
authored
Merge pull request #1385 from johnhaddon/usdShaderFix
USDScene : Don't treat unconnected material outputs as attributes
2 parents 0c0e768 + ac1651b commit 4b63469

File tree

6 files changed

+66
-12
lines changed

6 files changed

+66
-12
lines changed

Changes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
10.4.x.x (relative to 10.4.10.2)
22
========
33

4+
Fixes
5+
-----
6+
7+
- USDScene : Fixed treatment of unconnected material outputs during reading. If they were "authored" but not connected to a source, they were incorrectly being treated as valid attributes, and loading as empty ShaderNetworks which caused problems elsewhere.
8+
49
10.4.10.2 (relative to 10.4.10.1)
510
========
611

contrib/IECoreUSD/include/IECoreUSD/ShaderAlgo.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,11 @@ namespace ShaderAlgo
5757
IECOREUSD_API pxr::UsdShadeOutput writeShaderNetwork( const IECoreScene::ShaderNetwork *shaderNetwork, pxr::UsdPrim shaderContainer );
5858

5959
/// Reads a ShaderNetwork from a material output, typically obtained from `UsdShadeMaterial::GetOutput()`.
60+
/// Returns `nullptr` if `canReadShaderNetwork() == false`, usually because the output has no connected source.
6061
IECoreScene::ShaderNetworkPtr readShaderNetwork( const pxr::UsdShadeOutput &output );
62+
/// Returns true if `readShaderNetwork()` will return `nullptr`, usually because the output has no
63+
/// connected source.
64+
bool canReadShaderNetwork( const pxr::UsdShadeOutput &output );
6165

6266
#if PXR_VERSION >= 2111
6367
/// Reads a ShaderNetwork from a light.

contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,22 @@ pxr::UsdShadeOutput IECoreUSD::ShaderAlgo::writeShaderNetwork( const IECoreScene
312312
return networkOutUsd;
313313
}
314314

315+
bool IECoreUSD::ShaderAlgo::canReadShaderNetwork( const pxr::UsdShadeOutput &output )
316+
{
317+
pxr::UsdShadeConnectableAPI usdSource;
318+
pxr::TfToken usdSourceName;
319+
pxr::UsdShadeAttributeType usdSourceType;
320+
if(
321+
!output.GetConnectedSource( &usdSource, &usdSourceName, &usdSourceType ) ||
322+
usdSourceType != pxr::UsdShadeAttributeType::Output
323+
)
324+
{
325+
return false;
326+
}
327+
328+
return true;
329+
}
330+
315331
IECoreScene::ShaderNetworkPtr IECoreUSD::ShaderAlgo::readShaderNetwork( const pxr::UsdShadeOutput &output )
316332
{
317333
pxr::UsdShadeConnectableAPI usdSource;
@@ -322,7 +338,7 @@ IECoreScene::ShaderNetworkPtr IECoreUSD::ShaderAlgo::readShaderNetwork( const px
322338
usdSourceType != pxr::UsdShadeAttributeType::Output
323339
)
324340
{
325-
return new IECoreScene::ShaderNetwork();
341+
return nullptr;
326342
}
327343

328344
IECoreScene::ShaderNetworkPtr result = new IECoreScene::ShaderNetwork();

contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ class ShaderNetworkCache : public LRUCache<pxr::SdfPath, IECoreScene::ConstShade
469469
static IECoreScene::ConstShaderNetworkPtr getter( const ShaderNetworkCacheGetterKey &key, size_t &cost )
470470
{
471471
IECoreScene::ConstShaderNetworkPtr result = ShaderAlgo::readShaderNetwork( key );
472-
cost = result->Object::memoryUsage();
472+
cost = result ? result ->Object::memoryUsage() : 0;
473473
return result;
474474
}
475475

@@ -516,7 +516,7 @@ Imath::M44d localTransform( const pxr::UsdPrim &prim, pxr::UsdTimeCode time )
516516
return result;
517517
}
518518

519-
// Used to assign a unique hash to each USD file. Using a global counter rather than the file name
519+
// Used to assign a unique hash to each USD file. Using a global counter rather than the file name
520520
// means that we treat the same file as separate if it is closed and reopened. This means it's not
521521
// a problem if USD changes things when a file is reopened. USD appears to not in general guarantee
522522
// that anything is the same when reopening an unchanged file - things we're aware of that could
@@ -950,7 +950,7 @@ bool USDScene::hasAttribute( const SceneInterface::Name &name ) const
950950
{
951951
if( pxr::UsdShadeOutput o = mat.GetOutput( output ) )
952952
{
953-
return o.GetAttr().IsAuthored();
953+
return ShaderAlgo::canReadShaderNetwork( o );
954954
}
955955
}
956956
return false;
@@ -1014,6 +1014,10 @@ void USDScene::attributeNames( SceneInterface::NameList &attrs ) const
10141014
{
10151015
for( pxr::UsdShadeOutput &o : mat.GetOutputs( /* onlyAuthored = */ true ) )
10161016
{
1017+
if( !ShaderAlgo::canReadShaderNetwork( o ) )
1018+
{
1019+
continue;
1020+
}
10171021
InternedString attrName = AttributeAlgo::nameFromUSD( { o.GetBaseName() , false } );
10181022
if( !purpose.IsEmpty() )
10191023
{
@@ -1107,8 +1111,7 @@ ConstObjectPtr USDScene::readAttribute( const SceneInterface::Name &name, double
11071111
const auto &[output, purpose] = materialOutputAndPurpose( name.string() );
11081112
if( pxr::UsdShadeMaterial mat = m_root->computeBoundMaterial( m_location->prim, purpose ) )
11091113
{
1110-
pxr::UsdShadeOutput o = mat.GetOutput( output );
1111-
if( o && o.GetAttr().IsAuthored() )
1114+
if( pxr::UsdShadeOutput o = mat.GetOutput( output ) )
11121115
{
11131116
return m_root->readShaderNetwork( o );
11141117
}

contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ def testInstancePrototypeHashesNotReused( self ) :
482482
# The solution we've ended up with is instead of assigning consistent hashes, each instance of the
483483
# file gets it's own unique hashes, and is basically treated separately. This allows us to just
484484
# use the prototype names in the hash, since we force the hash to be unique anyway.
485-
485+
486486
usedHashes = set()
487487

488488
for i in range( 100 ):
@@ -506,7 +506,7 @@ def testInstancePrototypeHashesNotReused( self ) :
506506
instanceJ = scene.child( "instance%i" % j )
507507
self.assertEqual( h1, instanceJ.child( "world" ).hash( scene.HashType.TransformHash, 1 ) )
508508
del instanceJ
509-
509+
510510
self.assertNotIn( h2, usedHashes )
511511
for j in range( 11, 20 ):
512512
instanceJ = scene.child( "instance%i" % j )
@@ -2681,7 +2681,8 @@ def testShaders( self ) :
26812681
oneShaderNetwork.addShader( "foo", surface )
26822682
oneShaderNetwork.setOutput( IECoreScene.ShaderNetwork.Parameter( "foo", "" ) )
26832683

2684-
# A network with no output can be written out, but it will read back in as empty
2684+
# A network with no output can be written out, but not read back in, because
2685+
# it will not have been connected to a material output.
26852686
noOutputNetwork = IECoreScene.ShaderNetwork()
26862687
noOutputNetwork.addShader( "foo", surface )
26872688

@@ -2868,14 +2869,14 @@ def testShaders( self ) :
28682869

28692870
root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read )
28702871

2871-
self.assertEqual( set( root.child( "shaderLocation" ).attributeNames() ), set( ['ai:disp_map', 'ai:surface', 'complex:surface', 'testBad:surface', 'volume', 'componentConnection:surface', 'manualComponent:surface' ] ) )
2872+
self.assertEqual( set( root.child( "shaderLocation" ).attributeNames() ), set( ['ai:disp_map', 'ai:surface', 'complex:surface', 'volume', 'componentConnection:surface', 'manualComponent:surface' ] ) )
28722873

28732874
self.assertEqual( root.child( "shaderLocation" ).readAttribute( "ai:surface", 0 ).outputShader().parameters, oneShaderNetwork.outputShader().parameters )
28742875
self.assertEqual( root.child( "shaderLocation" ).readAttribute( "ai:surface", 0 ).outputShader(), oneShaderNetwork.outputShader() )
28752876
self.assertEqual( root.child( "shaderLocation" ).readAttribute( "ai:surface", 0 ), oneShaderNetwork )
2876-
self.assertTrue( root.child( "shaderLocation" ).hasAttribute( "testBad:surface" ) )
2877+
self.assertFalse( root.child( "shaderLocation" ).hasAttribute( "testBad:surface" ) )
28772878

2878-
self.assertEqual( root.child( "shaderLocation" ).readAttribute( "testBad:surface", 0 ), IECoreScene.ShaderNetwork() )
2879+
self.assertEqual( root.child( "shaderLocation" ).readAttribute( "testBad:surface", 0 ), None )
28792880
self.assertEqual( root.child( "shaderLocation" ).readAttribute( "ai:disp_map", 0 ), pickOutputNetwork )
28802881
self.assertEqual( root.child( "shaderLocation" ).hasAttribute( "ai:volume" ), False )
28812882
self.assertEqual( root.child( "shaderLocation" ).readAttribute( "volume", 0 ), oneShaderNetwork )
@@ -3667,5 +3668,17 @@ def testMaterialBindingInsideInstance( self ) :
36673668
shader2 = instance2.readAttribute( "surface", 0.0, _copy = False )
36683669
self.assertTrue( shader1.isSame( shader2 ) )
36693670

3671+
def testUnconnectedMaterialOutput( self ) :
3672+
3673+
root = IECoreScene.SceneInterface.create(
3674+
os.path.join( os.path.dirname( __file__ ), "data", "unconnectedMaterialOutput.usda" ),
3675+
IECore.IndexedIO.OpenMode.Read
3676+
)
3677+
3678+
sphere = root.child( "sphere" )
3679+
self.assertFalse( sphere.hasAttribute( "cycles:surface" ) )
3680+
self.assertNotIn( "cycles:surface", sphere.attributeNames() )
3681+
self.assertIsNone( sphere.readAttribute( "cycles:surface", 0 ) )
3682+
36703683
if __name__ == "__main__":
36713684
unittest.main()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#usda 1.0
2+
3+
def Sphere "sphere" (
4+
prepend apiSchemas = ["MaterialBindingAPI"]
5+
)
6+
{
7+
rel material:binding = </myMaterial>
8+
}
9+
10+
def Material "myMaterial"
11+
{
12+
token outputs:cycles:surface
13+
}

0 commit comments

Comments
 (0)