t = FBTime(0, 0, 1, 0, 0)
print t
# Multiply our time by 3.5
ticks = t.Get()
t.Set(long(ticks * 3.5))
print t.GetSecondDouble() # 3.5 sec
print t.GetMilliSeconds() # 3500 ms
print t.GetFrame(True) # 105 at 30 fps, 210 at 60 fps, etc.
# In MotionBuilder 2013, the first parameter is omitted (see note below)
print t.GetFrame(True, FBTimeMode.kFBTimeMode30Frames) # 105
print t.GetFrame(True, FBTimeMode.kFBTimeModeNTSC_Drop) # 104
print t.GetFrame(True, FBTimeMode.kFBTimeMode60Frames) # 210
print t.GetFrame(True, FBTimeMode.kFBTimeModeCustom, 48.0) # 168
t = FBTime(0, 0, 0, 30, 0, FBTimeMode.kFBTimeMode30Frames)
print t.GetSecondDouble() # 30 frames at 30 fps: 1.0 sec
t = FBTime(0, 0, 0, 30, 0, FBTimeMode.kFBTimeMode60Frames)
print t.GetSecondDouble() # 30 frames at 60 fps: 0.5 sec
t = FBTime(0, 0, 0, 30, 0, FBTimeMode.kFBTimeModeCustom, 48.0)
print t.GetSecondDouble() # 30 frames at 48 fps: 0.625 sec
localTime = FBSystem().LocalTime
print '%s\n (%f sec)\n (%d frames)' % (
localTime,
localTime.GetSecondDouble(),
localTime.GetFrame(True)
)
# Scrub to frame 44
t = FBTime(0, 0, 0, 44, 0)
FBPlayerControl().Goto(t)
FBPlayerControl().GotoStart()
FBPlayerControl().GotoEnd()
playback = FBPlayerControl()
playback.Stop() if playback.IsPlaying else playback.Play()
print FBPlayerControl().GetTransportFps() # Returns the FBTimeMode value
print FBPlayerControl().GetTransportFpsValue() # Returns the actual FPS value
# Set the playback framerate to 60 fps
FBPlayerControl().SetTransportFps(FBTimeMode.kFBTimeMode60Frames)
print '%d -- %d' % (
FBSystem().CurrentTake.LocalTimeSpan.GetStart().GetFrame(True),
FBSystem().CurrentTake.LocalTimeSpan.GetStop().GetFrame(True)
)
# Change the current take to go from frame 2000 to frame 5000
FBSystem().CurrentTake.LocalTimeSpan = FBTimeSpan(
FBTime(0, 0, 0, 2000, 0),
FBTime(0, 0, 0, 5000, 0)
)
At 1:15, I demonstrate how to use the GetFrame method. In MotionBuilder 2012, it has three arguments:
Here's how those arguments work, assuming that our scene framerate is set to 30 fps:
t = FBTime(0, 0, 2, 15, 0) # Two and a half seconds
print t.GetFrame(False) # 15
print t.GetFrame(True) # 75
print t.GetFrame(True, FBTimeMode.kFBTimeMode60Frames) # 150
print t.GetFrame(True, FBTimeMode.kFBTimeModeCustom, 15.0) # 37
In the 2013 version of the SDK, however, the first parameter (pCummul) has been removed outright, and GetFrame always returns a cummulative frame number. So we'd have to modify our code like so to work with MotionBuilder 2013:
t = FBTime(0, 0, 2, 15, 0) # Two and a half seconds
# t.GetFrame(False) is no longer supported
print t.GetFrame() # 75
print t.GetFrame(FBTimeMode.kFBTimeMode60Frames) # 150
print t.GetFrame(FBTimeMode.kFBTimeModeCustom, 15.0) # 37
Of course, this means that FBTime.GetFrame is mutually incompatible between MotionBuilder 2012 and 2013. If you were really interested in maintaining compatibility between both versions, you'd have to use some ugly piece of glue code like this:
def IsVersion2013():
import pyfbsdk
return hasattr(pyfbsdk, 'FBHUD')
def GetFrame(time, timeMode = FBTimeMode.kFBTimeModeDefault,
customFramerate = 0.0):
if IsVersion2013():
return time.GetFrame(timeMode, customFramerate)
return time.GetFrame(True, timeMode, customFramerate)
t = FBTime(0, 0, 2, 15, 0)
print GetFrame(t) # 75
print GetFrame(t, FBTimeMode.kFBTimeMode60Frames) # 150
print GetFrame(t, FBTimeMode.kFBTimeModeCustom, 15.0) # 37
There are three kinds of time value that MotionBuilder refers to. Local time (FBSystem().LocalTime) is the playback position within the current take. System time (FBSystem().SystemTime) is the wall-clock time given by the system, which steadily advances regardless of what MotionBuilder does.
The third kind of time, reference time, is used for independent timeframes that are synchronized to the take. For example, if you had an LTC device writing timecode data into the take, then takes with recorded data would have a reference timecode associated with them. To get the current reference time, then, you could use the FBReferenceTime class:
refTime = FBReferenceTime()
t = refTime.GetTime(refTime.ItemIndex, FBSystem().SystemTime)
print t.GetSecondDouble()
When you change the local time in a script, it's important to realize that the scene is not immediately reevaluated at the new time. If you access an animated property after setting the playback time, you'll still get the value at the old time. The solution is to call FBScene.Evaluate after setting the time, causing an explicit update of the scene.
To help understand this issue, try running the following code in an empty scene. It simply creates a cube and animates its translation over the course of the take.
# Create a cube and get the FCurve for its translation in X
cube = FBModelCube('Cube')
cube.Show = True
cube.Translation.SetAnimated(True)
fcurve = cube.Translation.GetAnimationNode().Nodes[0].FCurve
# Get the start and end time of the current take
startTime = FBSystem().CurrentTake.LocalTimeSpan.GetStart()
stopTime = FBSystem().CurrentTake.LocalTimeSpan.GetStop()
# Key the cube at x translation 0 at the start of the take
# Key the cube at x translation 100 at the end of the take
fcurve.KeyAdd(startTime, 0.0)
fcurve.KeyAdd(stopTime, 100.0)
Now, if we manually scrub the timeline and then print cube.Translation, we get the translation of the cube at that point in time. However, try setting the playback time and accessing the translation from the same script:
FBPlayerControl().GotoStart()
print cube.Translation
FBPlayerControl().GotoEnd()
print cube.Translation
You'll notice that the values you get vary, and that they're generally incorrect. The problem is that MotionBuilder doesn't respond to the change in local time until the next frame. Because Python scripts are executed in a single, blocking thread, this doesn't occur until after the entire script is finished running.
Therefore, if we modify the current time in a script, and we then want to access the current value of any animated property, we need to explicitly reevaluate the scene. In this case, that means adding calls to FBScene.Evaluate:
FBPlayerControl().GotoStart()
FBSystem().Scene.Evaluate()
print cube.Translation
FBPlayerControl().GotoEnd()
FBSystem().Scene.Evaluate()
print cube.Translation
Note for future reference that there are other ways of accessing animated properties at arbitrary times without having the change the current time and reevaluate the entire scene. We'll get into that in the next couple of chapters.
If you see any errors that you'd like to point out, feel free to email me at awforsythe@gmail.com.