Batch Retime Takes in Motionbuilder (Python Script)

Today I wanted to share a python tool script for Motionbuilder which allows you to batch retime takes in Motionbuilder, super handy for speeding up game clips by a multiplying factor (i.e x0.5 speed (half-speed) or x2 speed (double-speed)).

Some notes before we get started
You need to save your scene! - Save it twice in fact in case you mess up the speed script, this way you can just open the previous save and try again (remember to keep resaving before you try again) … learn from my experience. :stuck_out_tongue:

Make sure you select all the takes you want to speed up (they’ll be highlighted blue.)

This script is especially handy if you incorrectly plotted your animation at a wrong rate causing it playback in slow motion. In this case, you can work how much of a factor you need to times the clips again to achieve a real-time movement.

Make sure you have data baked on the SKELETON - otherwise, you’ll just see a default pose.

Okay, so how to get the script working?

We drag the python script into the viewport and click execute - nothing will happen, this is normal.
We need to go to the Python Editor and type a command to start the conversion.

With the Python Editor open type Retime( [ speed you want to multiply by ] )
The speed you want to multiply by should be like this:

  • 1.0 is just the speed it is now.
  • 0.5 is half speed
  • 2.0 is double speed and so on.

Great, now hit enter and the code will start executing and retime the clips.

What happens behind the scenes is:

  • We get all the takes and save them to an array and go through them one at a time.
  • We plot the take to the story editor and scale the time attribute of the clip to match what you entered. We also calculate the new end time as to not cut off any frames.
  • Now we plot to skeleton rather than the control rig as this gives better results.
  • We move on to the next take and refresh the scene (We repeat until we finish)

Note: After this is plotted to the skeleton, you can replot it to the control-rig

Done! Here is the script code now:

import pyfbsdk as fb
import pyfbsdk_additions as fba
import decimal
 
##Refresh the scene
def SceneRefresh(): 
    fb.FBPlayerControl().GotoNextKey()
    fb.FBSystem().Scene.Evaluate()
    fb.FBPlayerControl().GotoPreviousKey()
    fb.FBSystem().Scene.Evaluate()
 
##Get Length Of Time Line / Get Time Line Frame Count            
def GetTimeSpan():
    return ( fb.FBSystem().CurrentTake.LocalTimeSpan.GetStop().GetFrame() - fb.FBSystem().CurrentTake.LocalTimeSpan.GetStart().GetFrame() )
 
##Set Time Line Length ie. SetTimeSpan(150, 200) Will Set The Time Line To Start At Frame 150 And End At Frame 200
def SetTimeSpan(start, end):
    fb.FBSystem().CurrentTake.LocalTimeSpan = fb.FBTimeSpan(fb.FBTime(0, 0, 0, start, 0), fb.FBTime(0, 0, 0, end, 0))


 
##Plot Clip
def PlotStoryClip(clip):
    ##Deal With The User's Sory Mode Activity
    fb.FBStory().Mute = False
    SceneRefresh()
    print clip.Speed
    ##Plot Options
    lPlotClipOptions = fb.FBPlotOptions()
    lPlotClipOptions.ConstantKeyReducerKeepOneKey = False
    lPlotClipOptions.PlotAllTakes = False
    lPlotClipOptions.PlotOnFrame = True
    lPlotClipOptions.PlotPeriod = fb.FBTime( 0, 0, 0, 1 )
    lPlotClipOptions.PlotTranslationOnRootOnly = True
    lPlotClipOptions.PreciseTimeDiscontinuities = False
    lPlotClipOptions.RotationFilterToApply = fb.FBRotationFilter.kFBRotationFilterUnroll
    lPlotClipOptions.UseConstantKeyReducer = False
    ##Plot Story Clip On Current Character
    lChar = fb.FBApplication().CurrentCharacter    
    print lChar.Name           
    ##lChar.PlotAnimation(fb.FBCharacterPlotWhere.kFBCharacterPlotOnControlRig,lPlotClipOptions )
    lChar.PlotAnimation(fb.FBCharacterPlotWhere.kFBCharacterPlotOnSkeleton,lPlotClipOptions )

 
##In the Story, Delete The Track Created By This Script        
def CleanStoryTrack():       
    for eachTrack in fb.FBStory().RootFolder.Tracks:
        if "--Del_Me-PythonStoryClip" in eachTrack.Name:
            eachTrack.FBDelete()
            ##print "Finished Take Processing"
        else:
            pass
 
 
def Retime(scale):
    lTakeLst = []
    ##Add Every Selected Take Within Our Scene To Our Take List
    for i in range( len(fb.FBSystem().Scene.Takes) ):
        if fb.FBSystem().Scene.Takes[i].Selected == True:
            lTakeLst.extend([fb.FBSystem().Scene.Takes[i]])
        else: pass
    ogTake = fb.FBSystem().CurrentTake
    ##Delete Every Take Within Our Take List    
    for take in lTakeLst:
        fb.FBSystem().CurrentTake = take
        fb.FBSystem().CurrentTake.MergeLayers(fb.FBAnimationLayerMergeOptions.kFBAnimLayerMerge_AllLayers_CompleteScene, True, fb.FBMergeLayerMode.kFBMergeLayerModeAutomatic)
        lTrack = fb.FBStoryTrack(fb.FBStoryTrackType.kFBStoryTrackCharacter, fb.FBStory().RootFolder)
        lTrack.Label = fb.FBSystem().CurrentTake.Name + "--Del_Me-PythonStoryClip"
        lTrack.Details.append(fb.FBApplication().CurrentCharacter)
        lTrack.CopyTakeIntoTrack( fb.FBSystem().CurrentTake.LocalTimeSpan, fb.FBSystem().CurrentTake )
        for clip in lTrack.Clips:
            ##Get Clips New End Frame
            oClipStopFrame = GetTimeSpan()
            ##Half oClipStopFrame for double speed
            half = oClipStopFrame / scale
            ##Create Our Adjustment Speed (Clips Current Legnth / Our Desired Length)
            adjSpeed = round( ( decimal.Decimal(oClipStopFrame) / scale ) , 4 )
            ##Set StopFrame So Anim Does Not Get Cut Off
            clip.Stop = fb.FBTime(0,0,0,(half+1))
            ##Set Our Clips New Speed To Be Our Adjustment Speed
            clip.Speed = scale
            
            
            
            ##Set Our Time Span To Match Our Desired TimeLine Length
            SetTimeSpan( 0, half )
         
        PlotStoryClip(clip)
        CleanStoryTrack()
             
    fb.FBSystem().CurrentTake = ogTake