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.
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