Skip to content

Commit 8215cd5

Browse files
Merge branch 'RB-10.5' into RB-10.6
2 parents 69ffbde + 643f16a commit 8215cd5

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

Changes

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
10.6.x.x (relative to 10.6.0.0)
22
========
33

4+
Fixes
5+
-----
46

7+
- USDScene : Worked around numerical imprecision when converting between time and UsdTimeCode.
58

69
10.6.0.0 (relative to 10.5.15.2)
710
========
@@ -35,11 +38,19 @@ Breaking Changes
3538
- Removed support for `IECORE_RTLD_GLOBAL` environment variable.
3639
- SmoothSkinningData : Removed, along with all associated Ops and Parameters.
3740

38-
10.5.x.x (relative to 10.5.15.2)
41+
10.5.x.x (relative to 10.5.15.3)
3942
========
4043

4144

4245

46+
10.5.15.3 (relative to 10.5.15.2)
47+
=========
48+
49+
Fixes
50+
-----
51+
52+
- USDScene : Worked around numerical imprecision when converting between time and UsdTimeCode.
53+
4354
10.5.15.2 (relative to 10.5.15.1)
4455
=========
4556

contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,28 @@ class USDScene::IO : public RefCounted
715715

716716
pxr::UsdTimeCode timeCode( double timeSeconds ) const
717717
{
718-
return timeSeconds * m_timeCodesPerSecond;
718+
const double timeCode = timeSeconds * m_timeCodesPerSecond;
719+
720+
// It's common for `timeSeconds` to have been converted from a
721+
// `frame` value (by Gaffer's SceneReader for example), and it's
722+
// also common for USD's `timeCodesPerSecond` to match the FPS used
723+
// in the conversion, meaning that integer timecodes correspond to
724+
// integer frames.
725+
//
726+
// But numerical imprecision means that `timeCode` may no longer be
727+
// the exact same integer `frame` we started with. Compute the
728+
// integer version of the timecode, and if it is an equally
729+
// plausible conversion of `timeSeconds`, then prefer it.
730+
//
731+
// This is important because timesamples and value clips are commonly
732+
// placed on integer timecodes, and we want to hit them exactly.
733+
const double integerTimeCode = std::round( timeCode );
734+
if( integerTimeCode / m_timeCodesPerSecond == timeSeconds )
735+
{
736+
return integerTimeCode;
737+
}
738+
739+
return timeCode;
719740
}
720741

721742
// Tags

contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4607,5 +4607,44 @@ def testUsdVolVolumeWithEmptyField( self ) :
46074607
root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read )
46084608
self.assertIsNone( root.child( "volume" ).readObject( 0 ) )
46094609

4610+
def testTimeCodeClamping( self ) :
4611+
4612+
fileName = os.path.join( self.temporaryDirectory(), "test.usda" )
4613+
fileName = "test.usda"
4614+
4615+
# Create a stage with a fairly common timesampling setup.
4616+
# TimeCodesPerSecond and FramesPerSecond are equal, so that integer
4617+
# timecodes correspond to whole frames.
4618+
4619+
framesPerSecond = 30.0
4620+
4621+
stage = pxr.Usd.Stage.CreateNew( fileName )
4622+
stage.SetTimeCodesPerSecond( framesPerSecond )
4623+
stage.SetFramesPerSecond( framesPerSecond )
4624+
4625+
# Keyframe a boolean value, alternating on and off each frame.
4626+
4627+
prim = pxr.UsdGeom.Xform.Define( stage, "/child" )
4628+
primVar = pxr.UsdGeom.PrimvarsAPI( prim ).CreatePrimvar( "test", pxr.Sdf.ValueTypeNames.Bool )
4629+
4630+
frameRange = range( 1, 50000 )
4631+
for frame in frameRange :
4632+
primVar.Set( bool( frame % 2 ), frame )
4633+
4634+
stage.GetRootLayer().Save()
4635+
del stage
4636+
4637+
# Read back the values for each frame, asserting they are as expected.
4638+
# Because boolean values can't be interpolated, we have to hit the
4639+
# _exact_ timecode for the frame - if we're under, then we'll get the
4640+
# held value from the previous frame.
4641+
4642+
scene = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read )
4643+
child = scene.child( "child" )
4644+
4645+
for frame in frameRange :
4646+
timeInSeconds = frame / framesPerSecond
4647+
self.assertEqual( child.readAttribute( "render:test", timeInSeconds ), IECore.BoolData( frame % 2 ) )
4648+
46104649
if __name__ == "__main__":
46114650
unittest.main()

0 commit comments

Comments
 (0)