Skip to content

Commit 81339b8

Browse files
authored
Merge pull request #1346 from johnhaddon/usdNormalsPrimVar
USD PrimitiveAlgo : Fix bugs handling normals
2 parents 3e85a27 + 4847e8d commit 81339b8

File tree

5 files changed

+121
-9
lines changed

5 files changed

+121
-9
lines changed

Changes

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
10.4.x.x (relative to 10.4.6.0)
2+
========
3+
4+
Fixes
5+
-----
6+
7+
- USD::PrimitiveAlgo :
8+
- Fixed reading of primitives containing `primvars:normals`. These are now correctly loaded as a primitive variable called `N`, taking precedence over the UsdGeomPointBased `normals` attribute.
9+
- Fixed writing of indexed normals so that the indexing is retained on load. Note that this means that normals are now _always_ written as `primvars:normals` and never via the UsdGeomPointBased `normals` attribute.
10+
111
10.4.6.0 (relative to 10.4.5.0)
212
========
313

contrib/IECoreUSD/src/IECoreUSD/PrimitiveAlgo.cpp

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ void IECoreUSD::PrimitiveAlgo::writePrimitiveVariable( const std::string &name,
9797
writePrimitiveVariable( "st", primitiveVariable, primvarsAPI, time );
9898
return;
9999
}
100+
else if( name == "N" && runTimeCast<const V3fVectorData>( primitiveVariable.data ) )
101+
{
102+
writePrimitiveVariable( "normals", primitiveVariable, primvarsAPI, time );
103+
return;
104+
}
100105

101106
const pxr::SdfValueTypeName valueTypeName = DataAlgo::valueTypeName( primitiveVariable.data.get() );
102107
pxr::UsdGeomPrimvar usdPrimVar = primvarsAPI.CreatePrimvar( pxr::TfToken( name ), valueTypeName );
@@ -121,11 +126,6 @@ void IECoreUSD::PrimitiveAlgo::writePrimitiveVariable( const std::string &name,
121126
{
122127
pointBased.CreatePointsAttr().Set( PrimitiveAlgo::toUSDExpanded( value ), time );
123128
}
124-
else if( name == "N" )
125-
{
126-
pointBased.CreateNormalsAttr().Set( PrimitiveAlgo::toUSDExpanded( value ), time );
127-
pointBased.SetNormalsInterpolation( PrimitiveAlgo::toUSD( value.interpolation ) );
128-
}
129129
else if( name == "velocity" )
130130
{
131131
pointBased.CreateVelocitiesAttr().Set( PrimitiveAlgo::toUSDExpanded( value ), time );
@@ -469,6 +469,23 @@ void IECoreUSD::PrimitiveAlgo::readPrimitiveVariables( const pxr::UsdGeomPrimvar
469469
primitive->variables.erase( it );
470470
}
471471
}
472+
473+
// USD uses "normals" for normals and we use "N".
474+
475+
it = primitive->variables.find( "normals" );
476+
if( it != primitive->variables.end() )
477+
{
478+
if( auto d = runTimeCast<V3fVectorData>( it->second.data ) )
479+
{
480+
// Force the interpretation, since some USD files
481+
// use `vector3f` rather than `normal3f`. I'm looking
482+
// at you, `arnold-usd`.
483+
d->setInterpretation( GeometricData::Normal );
484+
primitive->variables["N"] = it->second;
485+
primitive->variables.erase( it );
486+
}
487+
}
488+
472489
}
473490

474491
void IECoreUSD::PrimitiveAlgo::readPrimitiveVariables( const pxr::UsdGeomPointBased &pointBased, pxr::UsdTimeCode time, IECoreScene::Primitive *primitive, const Canceller *canceller )
@@ -485,9 +502,14 @@ void IECoreUSD::PrimitiveAlgo::readPrimitiveVariables( const pxr::UsdGeomPointBa
485502
}
486503

487504
Canceller::check( canceller );
488-
if( auto n = boost::static_pointer_cast<V3fVectorData>( DataAlgo::fromUSD( pointBased.GetNormalsAttr(), time ) ) )
505+
if( !primitive->variables.count( "N" ) )
489506
{
490-
primitive->variables["N"] = IECoreScene::PrimitiveVariable( PrimitiveAlgo::fromUSD( pointBased.GetNormalsInterpolation() ), n );
507+
// Only load `PointBased::GetNormalsAttr()` if we didn't already load `primvars:normals`.
508+
// From the USD API docs : "If normals and primvars:normals are both specified, the latter has precedence."
509+
if( auto n = boost::static_pointer_cast<V3fVectorData>( DataAlgo::fromUSD( pointBased.GetNormalsAttr(), time ) ) )
510+
{
511+
primitive->variables["N"] = IECoreScene::PrimitiveVariable( PrimitiveAlgo::fromUSD( pointBased.GetNormalsInterpolation() ), n );
512+
}
491513
}
492514
}
493515

contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,7 +1370,9 @@ def testPointBasedPrimvars( self ) :
13701370

13711371
# Make sure we redirect Cortex primitive variables to the correct
13721372
# attributes of UsdGeomPointBased instead of writing them to
1373-
# arbitrary primvars.
1373+
# arbitrary primvars. We don't use the `normals` attribute though,
1374+
# since USD documents `primvars:normals` to take precedence, and
1375+
# it is the only way we can preserve indexed normals.
13741376

13751377
stage = pxr.Usd.Stage.Open( fileName )
13761378
primvarsAPI = pxr.UsdGeom.PrimvarsAPI( stage.GetPrimAtPath( "/test" ) )
@@ -1382,7 +1384,8 @@ def testPointBasedPrimvars( self ) :
13821384

13831385
usdMesh = pxr.UsdGeom.Mesh( stage.GetPrimAtPath( "/test" ) )
13841386
self.assertTrue( usdMesh.GetPointsAttr().HasAuthoredValue() )
1385-
self.assertTrue( usdMesh.GetNormalsAttr().HasAuthoredValue() )
1387+
self.assertFalse( usdMesh.GetNormalsAttr().HasAuthoredValue() )
1388+
self.assertTrue( primvarsAPI.GetPrimvar( "normals" ) )
13861389
self.assertTrue( usdMesh.GetVelocitiesAttr().HasAuthoredValue() )
13871390
if pxr.Usd.GetVersion() >= ( 0, 19, 11 ) :
13881391
self.assertTrue( usdMesh.GetAccelerationsAttr().HasAuthoredValue() )
@@ -3357,5 +3360,51 @@ def testInvalidPrimitiveVariables( self ) :
33573360
del goodCube["uv"]
33583361
self.assertEqual( goodCube, badCube )
33593362

3363+
def testNormalsPrimVar( self ) :
3364+
3365+
root = IECoreScene.SceneInterface.create( os.path.dirname( __file__ ) + "/data/normalsPrimVar.usda", IECore.IndexedIO.OpenMode.Read )
3366+
mesh = root.child( "mesh" ).readObject( 0 )
3367+
3368+
self.assertNotIn( "normals", mesh )
3369+
self.assertIn( "N", mesh )
3370+
self.assertEqual( mesh["N"].interpolation, IECoreScene.PrimitiveVariable.Interpolation.FaceVarying )
3371+
self.assertIsNotNone( mesh["N"].indices )
3372+
3373+
def testNormalsPrimVarBeatsNormalsAttribute( self ) :
3374+
3375+
root = IECoreScene.SceneInterface.create( os.path.dirname( __file__ ) + "/data/normalsAttributeAndPrimVar.usda", IECore.IndexedIO.OpenMode.Read )
3376+
mesh = root.child( "mesh" ).readObject( 0 )
3377+
3378+
self.assertNotIn( "normals", mesh )
3379+
self.assertIn( "N", mesh )
3380+
self.assertEqual( mesh["N"].data, IECore.V3fVectorData( [ imath.V3f( 0, 0, 1 ) ] * 3, IECore.GeometricData.Interpretation.Normal ) )
3381+
self.assertEqual( mesh["N"].interpolation, IECoreScene.PrimitiveVariable.Interpolation.FaceVarying )
3382+
self.assertIsNone( mesh["N"].indices )
3383+
3384+
def testRoundTripIndexedNormals( self ) :
3385+
3386+
mesh = IECoreScene.MeshPrimitive(
3387+
IECore.IntVectorData( [ 3, 3 ] ),
3388+
IECore.IntVectorData( [ 0, 2, 1, 2, 3, 1 ] ),
3389+
"linear",
3390+
IECore.V3fVectorData( [
3391+
imath.V3f( 0, 1, 0 ), imath.V3f( 1, 1, 0 ), imath.V3f( 0, 0, 0 ), imath.V3f( 1, 0, 0 )
3392+
] )
3393+
)
3394+
3395+
mesh["N"] = IECoreScene.PrimitiveVariable(
3396+
IECoreScene.PrimitiveVariable.Interpolation.FaceVarying,
3397+
IECore.V3fVectorData( [ imath.V3f( 0, 0, 1 ) ], IECore.GeometricData.Interpretation.Normal ),
3398+
IECore.IntVectorData( [ 0, 0, 0, 0, 0, 0 ] )
3399+
)
3400+
3401+
fileName = os.path.join( self.temporaryDirectory(), "indexedNormals.usda" )
3402+
root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Write )
3403+
root.createChild( "mesh" ).writeObject( mesh, 0.0 )
3404+
del root
3405+
3406+
root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read )
3407+
self.assertEqual( root.child( "mesh").readObject( 0.0 ), mesh )
3408+
33603409
if __name__ == "__main__":
33613410
unittest.main()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#usda 1.0
2+
3+
def Mesh "mesh"
4+
{
5+
int[] faceVertexCounts = [3]
6+
int[] faceVertexIndices = [0, 1, 3]
7+
point3f[] points = [(0, 0, 0), (0, 1, 0), (1, 0, 0)]
8+
normal3f[] primvars:normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1)] (
9+
elementSize = 3
10+
interpolation = "faceVarying"
11+
)
12+
normal3f[] normals = [(0, 0, -1), (0, 0, -1), (0, 0, -1)] (
13+
interpolation = "vertex"
14+
)
15+
uniform token subdivisionScheme = "none"
16+
}

0 commit comments

Comments
 (0)