Skip to content

Commit f44e57e

Browse files
Merge pull request #1482 from johnhaddon/usdTimeFix
USDScene : Work around imprecision converting time to UsdTimeCode
2 parents 63a5328 + b37ad2a commit f44e57e

File tree

3 files changed

+64
-1
lines changed

3 files changed

+64
-1
lines changed

Changes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
10.5.x.x (relative to 10.5.15.2)
22
========
33

4+
Fixes
5+
-----
46

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

69
10.5.15.2 (relative to 10.5.15.1)
710
=========

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
@@ -4595,5 +4595,44 @@ def testUsdVolVolumeWithEmptyField( self ) :
45954595
root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read )
45964596
self.assertIsNone( root.child( "volume" ).readObject( 0 ) )
45974597

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

0 commit comments

Comments
 (0)