Stumbling Toward 'Awesomeness'

A Technical Art Blog

Monday, May 12, 2008

Speeding Up Per-Frame Functions in MB: FBSystem().Scene.Evaluate()

KxL on Autodesk’s ‘The Area‘ forum has really shed some light on to why MotionBuilder was so slow when iterating through frames. It all boils down to the usage of FBSystem().Scene.Evaluate(). This evaluates the entire scene and is very, very slow. Motionbuilder does not allow for getting a parameter at time i, or do partial scene evaluation.

So this basically means that any time you need to get the position, rotation, or matrix of a model, you should go to the keyfame data directly. (unless it’s a constraint!) Here is an example function that prints position every frame. It does so by getting the information from the fcurves themselves, and *not* querying the nodes.

#prints position given a model and a frame
def PrintPosition(pModel,pFrame):
    lFrameTime = FBTime(0,0,0,pFrame)
    lAnimationNode = pModel.Translation.GetAnimationNode()
    lPositionV = FBVector3d(
        lAnimationNode.Nodes[0].FCurve.Evaluate(lFrameTime),
        lAnimationNode.Nodes[1].FCurve.Evaluate(lFrameTime),
        lAnimationNode.Nodes[2].FCurve.Evaluate(lFrameTime))
    print lPositionV
    del(lFrameTime, lAnimationNode, lPositionV)

Here is another example, showing how to build a transformation matrix from animation data (nodes). This is required because the normal way of requesting the matrix of a model in MBuilder (model.GetMatrix(pMatrix)), requires an Evaluate(). The following requires importing the following libraries: [math].

def FBMatrixFromAnimationNode( pModel, pTime ):
    lResult = FBMatrix()
    lTranslationNode = pModel.Translation.GetAnimationNode()
    lRotationNode = pModel.Rotation.GetAnimationNode()
    lScaleNode = pModel.Scaling.GetAnimationNode()
 
    lRotationV = FBVector3d(
        lRotationNode.Nodes[0].FCurve.Evaluate(pTime) * 0.017453292519943295769236907684886,
        lRotationNode.Nodes[1].FCurve.Evaluate(pTime) * 0.017453292519943295769236907684886,
        lRotationNode.Nodes[2].FCurve.Evaluate(pTime) * 0.017453292519943295769236907684886)
 
    lScaleV = FBVector3d(
        lScaleNode.Nodes[0].FCurve.Evaluate(pTime),
        lScaleNode.Nodes[1].FCurve.Evaluate(pTime),
        lScaleNode.Nodes[2].FCurve.Evaluate(pTime))
 
    sphi = math.sin(lRotationV[0])
    cphi = math.cos(lRotationV[0])
    stheta = math.sin(lRotationV[1])
    ctheta = math.cos(lRotationV[1])
    spsi = math.sin(lRotationV[2])
    cpsi = math.cos(lRotationV[2])
 
    lResult[0] = (cpsi*ctheta)*lScaleV[0]
    lResult[1] = (spsi*ctheta)*lScaleV[0]
    lResult[2] = (-stheta)*lScaleV[0]
 
    lResult[4] = (cpsi*stheta*sphi - spsi*cphi)*lScaleV[1]
    lResult[5] = (spsi*stheta*sphi + cpsi*cphi)*lScaleV[1]
    lResult[6] = (ctheta*sphi)*lScaleV[1]
 
    lResult[8] = (cpsi*stheta*cphi + spsi*sphi)*lScaleV[2]
    lResult[9] = (spsi*stheta*cphi - cpsi*sphi)*lScaleV[2]
    lResult[10] = (ctheta*cphi)*lScaleV[2]
 
    lResult[12] = lTranslationNode.Nodes[0].FCurve.Evaluate(pTime)
    lResult[13] = lTranslationNode.Nodes[1].FCurve.Evaluate(pTime)
    lResult[14] = lTranslationNode.Nodes[2].FCurve.Evaluate(pTime)
 
    return lResult

Soon, I hope to build an example tool showing how to iterate through frames and do facial motioncapture stabilization. That was sort of how all this came up, I wrote the tool, but it was ~20 times slower than it should have been.

posted by Chris at 12:05 PM  

Friday, May 2, 2008

Code Execution Time on Per-Frame Functions in MotionBuilder

This is a follow-up to my post about per-frame operations in MotionBuilder. For some reason they take forever. I will show you an example, and compare the MB Python implementation to other scripting implementations by executing the same code in another Autodesk product (3dsMax).

Time Stamping

First, lets find a way to see how long it takes to execute a block of code. In the example below, the script will

from pyfbsdk import *
import time
 
#get the time
t1 = time.time()
 
#get selected items
selectedModels = FBModelList()
FBGetSelectedModels(selectedModels,None,True)
 
#print each items position
for item in selectedModels: print item.Translation
 
#tell us how long that took (time start - time finish)
print ('Process took ' + str((time.time() - t1)) + ' seconds')

I can run this on some selected models and it reports: Process took 0.0 seconds. Now if you run this on, say, 60 markers, the output window has no scrollbar, and unless you have a large screen, you will not see the last output telling you how long the process took. So we can replace the print with a messagebox:

duration = str((time.time() - t1))
FBMessageBox('Process Info:',('Process took ' + duration + ' seconds'),'ok')

Testing a Function That Loops Through Keys

Let’s take our example from before, where we go through every frame and print the position of an object in MB. Alter that code by inserting the code from above. Add the ‘import time‘ at the top, and then set t1 before you run posEveryFrame(selectedModels[0]). Now after that function, add your line that generates a messagebox.

When I run this on a single object across 1369 frames I get the messagebox above: it takes almost 10 seconds! Remember, we are just printing a position of an object here. I wrote the same code in Maxscript and it took 0.703 seconds. This is 13 times faster than MotionBuilder, a program that prides itself on dealing with keyframe data!

I think this is because MotionBuilder is actually moving the timeslider itself, even though the UI is not updating! This must be the case, because it is just unbelievably slow. So next we will research whether or not we can grab scene info at a certain time.

posted by Chris at 12:42 AM  

Thursday, May 1, 2008

Iterating and Per-frame Functions in MotionBuilder

In any animation package, there are many times technical artists/animators will need to iterate through all the frames of animation and do something. This is a given.

Accessing The Timeline

To access timeline functionality in MBuilder you can either get the current take and then request it’s start/stop or use the following, which grabs the start and stop in the current take/timeline:

FBPlayerControl().ZoomWindowStart.GetFrame()
FBPlayerControl().ZoomWindowStop.GetFrame()

However, this only returns a frame number in the range of your FPS, for instance, if your end frame is 1369, it will return the value 19L. To better understand this, enter 1369%30 into your Python Console, it will return 19. More info on modulo operations [here]

So how do we get time from MotionBuilder in a usable format? Here are two examples:

#this retuns the real frame number
FBPlayerControl().ZoomWindowStop.GetFrame(True)
#this returns in seconds</span></code>
FBPlayerControl().ZoomWindowStop.GetSecondDouble()

Iterating Through the Timeline

Now that we can get the range of the frames in the timeline, let’s do stuff! Here is an example you can execute in MB showing how to iterate through time and do something for every frame (print frame number):

from pyfbsdk import *
 
lSystem=FBSystem()
lScene = lSystem.Scene
playaCtrl = FBPlayerControl()
 
#this returns the start/stop as integers<code>
fStart = int(playaCtrl.ZoomWindowStart.GetFrame(True))
fStop = int(playaCtrl.ZoomWindowStop.GetFrame(True))
for frame in range(fStart,fStop): print frame

Now, this is not to be confused with doing something on every frame. Above, we are just iterating through the frame numbers themselves. If there are 1000 frames, you are just going from number 1 to 1000 printing the current number.

So now let’s iterate through frames and do something every frame.

from pyfbsdk import *
 
def posEveryFrame(model):
	lSystem=FBSystem()
	lScene = lSystem.Scene
 
	#we instance the player control to read from it faster
	playaCtrl = FBPlayerControl()
 
	#set our start and stop (as integers)
	fStart = int(playaCtrl.ZoomWindowStart.GetFrame(True))
	fStop = int(playaCtrl.ZoomWindowStop.GetFrame(True))
 
	#tell player control to go to the start of the scene
	playaCtrl.GotoStart()
 
	#now iterate through the frames
	for frame in range(fStart,fStop):
		#evaluate the scene to make sure the data is updated
		lScene.Evaluate()
		#output the models position
		print model.Translation
		#tell the player to go to the next frame
		playaCtrl.StepForward()
 
selectedModels = FBModelList()
FBGetSelectedModels(selectedModels,None,True)
 
#run our function on the first model in the selection
posEveryFrame(selectedModels[0])

So, executing this with a model selected should print the models translation (position) every frame for all frames in your timeslider (transport control).

Now you can start messing around with iterating through frames, and you will immediately notice two things:

1) Iterating through frames in MotionBuilder is slow. Almost slower than my grandmother entering all the values into a calculator (not even one with large numbered buttons)

2) The Python Console that you print to does not have a scroll bar, and does not have selectable text. (complete and utter fail)

posted by Chris at 3:56 AM  
« Previous Page

Powered by WordPress