Inside Sabertooth
Learn how Sabertooth uses 3ds Max to create 3D interactive projects, including HBO Go’s Game of Thrones interactive experience
  • 1/3
You are here: Forum Home / Autodesk® MotionBuilder® / Python / Iterating through per-frame functions without StepForward()?
  RSS 2.0 ATOM  

Iterating through per-frame functions without StepForward()?
Rate this thread
 
11589
 
Permlink of this thread  
avatar
  • Location: Frankfurt am Main
  • Total Posts: 65
  • Joined: 27 October 2006 01:37 PM

Is there a way to iterate through a frame range without using FBPlayerControl().StepForward().

I ported over some scripted tools from 3dsmax. After profiling them for a while, on avg, mbuilder python iterates through frames 13 to 40 times slower than maxscript.

Is there a way to loop through time and do something every frame other than using the player control? I think this is so slow because even though MBuilder is locked up and not redrawing it’s UI, it is moving a little invisible timeslider with playercontrol. There must be a way to just request a value at a certain time or run an expression at a certain time without moving the playercontrol to this time.

An example would be:

fStart int(FBPlayerControl().ZoomWindowStart.GetFrame(True))
fStop int(FBPlayerControl().ZoomWindowStop.GetFrame(True))

for 
i in range(fStart,fStop):
    
at time i:
        print 
model.Translation


Replies: 0
avatar
  • LoCK
  • Posted: 05 May 2008 05:21 AM

I guess the only other way would be to find an object that has Keys for the time range you are interested in. You could, then, generate a List of Keys for an object and iterate through that while performing your action.

This might be faster, however, the StepForward is useful for scene-wide actions.



Replies: 0
avatar

http://www.chrisevans3d.com/pub_blog/?p=6

I actually made a post about this in my blog in hopes someone would see it through google or something.

Anyway, I have talked to a few people, and they just told me ‘yeah, mbuilder is slow’, but I think there surely has to be another way. Most 3d programs differentiate between moving the time slider and getting a value at a time.

An example from another Autodesk product:

--Maxscript
for animationrange.start to animationrange.end do
(
    
at time i print obj.pos
)

versus:

--Maxscript
for animationrange.start to animationrange.end do
(
    
slidertime i
    
print obj.pos
)

CE



Replies: 0
avatar
  • KxL
  • Posted: 06 May 2008 04:31 PM

Pangloss,

You shocked me with this blog post ;) There is propably at least few things that you are missing. First, and most important - MB is different from other application. You have to chose right before you start to write for it. You have to note, that MB is real-time application, and what you want to do is to use it’s real-time evaluation. And this is not the way of working with MB. If you want to make real-time operations you have to plug into MB evaluation. This can be unfortunately done only by C++ by chosing correct extension type (constraint, device, and in some cases tool).

What you are doing is calling very costly scene Evaluation, which I always try to omit. Also, please take note, that you are forcing entire scene evaluation, not only per object evaluation. I can agree that having per object evaluation for a given time would be nice, but again it is not the way of working with MB.

Last thing is your sentence: “...This is 13 times faster than MotionBuilder, a program that prides itself on dealing with keyframe data!” - you should not mix working with animation data and getting data from evaluation system. If think Animators could tell more about speed of working with keyframe data. BTW. if you want to test getting AnimationNodes keys use this script (run at least two times)

from pyfbsdk import FBSystemFBModelListFBGetSelectedModelsFBTimeFBVector3dFBMessageBox
            
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
(lFrameTimelAnimationNodelPositionV)

lSystem FBSystem()
lStartTime 
lSystem.SystemTime.GetSecondDouble()

lModelList FBModelList()
FBGetSelectedModels(lModelList ,lSystem.RootModel);

for 
lFrame in range(0,1500):
    for 
lModel in lModelList:
        
PrintPosition(lModellFrame)
        
del(lModel)
    
del(lFrame)


lDuration lSystem.SystemTime.GetSecondDouble() - lStartTime

FBMessageBox
('Duration:',('Process took ' str(lDuration) + ' seconds'),'OK')

del(lSystem,lStartTime,lModelList,lDuration)
del(FBSystemFBModelListFBGetSelectedModelsFBTimeFBVector3dFBMessageBox)

Cheers



Replies: 0
avatar

Thanks for your reply! you should work for autodesk, I am currently waiting to hear back from someone.

So, I really have my work cut out for me. :(

So here’s a fn that I run every frame across 3 sets of markers:

def avgPos(models):
    
mLen len(models)
    if 
mLen == 1:
        return 
models[0].Translation
    total 
vec3(models[0].Translation)
    for 
i in range (1mLen):
        
total += vec3(models[i].Translation)
    
avgTranslation total/mLen
    
return fbv(avgTranslation)

Things like this just didn’t appear to work unless I eval’d the scene. So, this fn should go into the fcurves of every model and take out their individual values then make vectors out of those and then add/divide them?

Then there’s something like this, for some reason it just would not worrk correctly until I put those scene evals there:

def faceOrient():
        
lScene.Evaluate()
        
        
Rpos vec3(avgPos(right))
        
Lpos vec3(avgPos(left))
        
Cpos vec3(avgPos(center))
        
        
faceAttach.GetMatrix(pMatrix)
        
xVec = (Cpos Rpos)
        
xVec xVec.normalize()
        
zVec = ((Cpos vec3(faceAttach.Translation)).normalize()).cross(xVec)
        
zVec zVec.normalize()
        
yVec xVec.cross(zVec)
        
yVec yVec.normalize()
        
facePos = (Rpos Lpos)/2
        
        pMatrix[0] 
xVec.x
        pMatrix[1] 
xVec.y
        pMatrix[2] 
xVec.z
        
        pMatrix[4] 
yVec.x
        pMatrix[5] 
yVec.y
        pMatrix[6] 
yVec.z
        
        pMatrix[8] 
zVec.x
        pMatrix[9] 
zVec.y
        pMatrix[10] 
zVec.z
        
        pMatrix[12] 
facePos.x
        pMatrix[13] 
facePos.y
        pMatrix[14] 
facePos.z
        
        faceAttach
.SetMatrix(pMatrix,FBModelTransformationMatrix.kModelTransformation,True)
        
lScene.Evaluate()

Same with using setvector as in this example:

for frame in range(FStart,FStop):
        
        
faceOrient()
        
        for 
m in range (0,len(newMarkers)):
            
markerAnimNodes newMarkers[m].AnimationNode.Nodes
            newMarkers[m]
.SetVector(markers[m].Translation.Data)
            
lScene.Evaluate()
            
keyTransRot(markerAnimNodes)
        
        
keyTransRot(animNodes)

        
lPlayerControl.StepForward()
        
lVal = (frame/FStop)*10
        lFbp
.Percent lVal

So I will go through my code, and any time I see that I needed to use a scene eval to get the desired output, i will look into a way of going to the fcurves to get or set the data at a frame?

CE



Replies: 0
avatar
  • KxL
  • Posted: 07 May 2008 02:03 AM

It’ all about how objects are driven:
- if they are only driven by keys, and possible in hierarchies, you could count it’s global position from FCurves, and this will speed up your work
- if they are also driven by constraints etc, you have to use scene evaluation, BUT

what I see, those function could be constraints. The only problem is propably that you do not work with c++.

Let me know if you are working with only keys driven models.

Hope this will help you.



Replies: 0
avatar

So I am writing my own GetMatrix that does not require scene evaluations, but the only way to do this is with the following, right?

aPosNode FBFindModelByName('C3D:L_TMP').Translation.GetAnimationNode()
aRotNode FBFindModelByName('C3D:L_TMP').Rotation.GetAnimationNode()
whatever lPosNode.Nodes[0].FCurve.Evaluate(lFrameTime)

So the only way I can get the matrix without evaling the scene is to construct a matrix from the euler data, figuring out the rotation order and all that? Is there an existing way to query this stuff from the keyframe data? Shouldn’t there be?

CE



Replies: 0
avatar
  • KxL
  • Posted: 10 May 2008 07:17 AM

OK, I spend some of my free time to write code for you, and others from this forum that will find it useful.

The code alow to create global transformation matrix from animation nodes. So if object is in hierarchy it will get you global values, not local.

How to test it:
- save as py file
- open/create scene with animated model (can be in heirarchy)
- select model that you want get matrix
- zoom transport to time rage you want to get data
- run script

Result are printed in PythonConsole.

You can also uncomment the visualization code to have live view of what was counted.

Here is the code. I placed also profiling, so you can see how fast is it working (run at least two times!).

from pyfbsdk import *
import math

def FBMatrixFromAnimationNode
pModelpTime ):
    
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

def FBMatrixMult
pResultpParentpChild ):      
    
sum =0
    aa 
0
    bb 
0
    
for i in range(0,4):
        for 
j in range(0,4):
            
j
            b 
bb
            sum 
0
            
for k in range(0,4): 
                
sum += pChild [b]*pParent[c]
                c 
+= 4
                b 
+= 1
            pResult[aa] 
sum
            aa
+=1
        bb 
+= 4

def CopyFBMatrix
(pDestinationpSource):
    for 
i in range(0,16):
        
pDestination[i] pSource[i]

def GetGlobalFBMatrix
pModelpTime):
    
lGlobalMatrix FBMatrixFromAnimationNode(pModelpTime )

    
tmpMatrix FBMatrix()
    while 
pModel.Parent:
        
CopyFBMatrix(tmpMatrix lGlobalMatrix)
        
lParentMatrix FBMatrixFromAnimationNode(pModel.ParentpTime )
        
FBMatrixMult(lGlobalMatrixlParentMatrixtmpMatrix 
        
pModel pModel.Parent
    
return lGlobalMatrix

#Profiling
lSystem FBSystem()
lStartTime 
lSystem.SystemTime.GetSecondDouble()

#Usage part
lModelList FBModelList()
FBGetSelectedModels
lModelList )

lStartFrame FBPlayerControl().ZoomWindowStart.GetFrame(True)
lStopFrame FBPlayerControl().ZoomWindowStop.GetFrame(True)

lNumberOfSteps lStopFrame
lUpdateStep 
= (lStopFrame lStartFrame )/lNumberOfSteps 

lLastStepTime 
0
while lNumberOfSteps >= 0:
    for 
lModel in lModelList:
        
lGlobalMatrix GetGlobalFBMatrixlModelFBTime(0,0,0,lLastStepTime) )
        
        
#simple print
        
print lGlobalMatrix
        
        
#This will make BIG affect on duration time, but it's nice for displaying the results
        #lNullName = lModel.Name + '_at frame:' + str(lLastStepTime)
        #lNull = FBModelNull( lNullName )
        #lNull.Show = True
        #lNull.SetMatrix(lGlobalMatrix)
    
lLastStepTime += lUpdateStep
    lNumberOfSteps 
-= 
        
#Display end of profiling
lDuration lSystem.SystemTime.GetSecondDouble() - lStartTime
FBMessageBox
('Duration:',('Process took ' str(lDuration) + ' seconds'),'OK')

BTW. I didn’t make any cleaning, because I do not have time for this. This is fast implementation. If someone want to correct it, please do it.

Hope someone will find it useful.



Replies: 0
avatar

Hey man, you are awesome. Please let me know your name, because I will update another add-on to my last post, showing these ways to get this stuff per-frame without scene evals. I will also integrate these functions into my facial stabilization tool which i will release for free, and I want to add your name in the comments.

I will try this stuff this weekend sometime and get back to you,

Thanks again,

CE



Replies: 0
avatar
  • KxL
  • Posted: 10 May 2008 01:51 PM

I hope it will work for your case.

As for your update to “last post”, I hope you talk about blog post? It would be great to correct any misleading that it could cause. Maybe post link to this thread?

Now, my name..hmm..I think that my nick name is more recognizable than my real name ;-)

Cheers!



Replies: 0
avatar

Well, as for misinformation, almost everywhere i found information online about per frame operations, they did complete scene evals, which is why your information is so great! I always research things a lot before making such statements, as I told you, I even contacted autodesk directly and spoke with a ‘Motionbuilder Application Engineer’, and they did not give me the answer you did.

Not to mention, it takes a of knowledge about a specific 3d app to know how to generate a transformation matrix from euler values. It would have taken much longer to do this without your help!

Thanks a lot, I will post a follow explaining the things you have posted here and linking to this thread,

CE



Replies: 0
avatar
  • KxL
  • Posted: 11 May 2008 05:41 AM

You are too kind. This is simple implementation for generating matrix with only XYZ rotation order. Hope you will not need others.

Cheers.



Replies: 0
avatar

I spent a lot of time today messing with this stuff.

So, using things like SetMatrix, SetVector, etc.. I was told by the Autodesk Application Engineer to use KeyCandidate after setting these, but key candidate only works on a current time, is that correct? And StepForward is very slow.

Here is how I am doing it:

def keyTransRot(animNodeList):
        for 
lNode in animNodeList:
            if (
lNode.Name == 'Lcl Translation'):
                
lNode.KeyCandidate()
            if (
lNode.Name == 'Lcl Rotation'):
                
lNode.KeyCandidate()

Do I need to write my own versions that go in and do something like this:

def mySetVector(nodeframeval):
        
lFrameTime FBTime(0,0,0,frame)
        
lAnimationNode node.Translation.GetAnimationNode()
        
lAnimationNode.Nodes[0].FCurve.KeyAdd(lFrameTime,val[0])
        
lAnimationNode.Nodes[1].FCurve.KeyAdd(lFrameTime,val[1])
        
lAnimationNode.Nodes[2].FCurve.KeyAdd(lFrameTime,val[2])

Then I would need to write one for rotation as well, or just one for both.

I am so thankful for your help, a lot of this seems very obtuse. I really wanted to release this script to the public, but not done in a way that is inefficient or bad, as I want people to learn from it. However doing things ‘correctly’ seems pretty difficult, and there is little information available. I got the KeyAdd usage above from the auto-generated docs and jason park’s masterclass.

For example, there are 35 example scripts that come with motionbuilder, and not one shows how to set a keyframe on a model :(



Replies: 0
avatar
  • KxL
  • Posted: 14 May 2008 04:08 PM

I personally write directly to FCurves, but I can understand why AAE sugested you to use SetMatrix/SetVector. When you are setting keys on FCurves you have to set local data, so you have to count them yourself. With SetMatrix/SetVector you can set global data, and local will be count by MB.

KeyCandidate will set key on current time (if I remeber good, because I do not use it).

One thing that I see that you are doing

lNode.Name == 'Lcl Translation'

comparing strings are not the fastest operation. Try to avoid it if you are really want to make fast executing scripts. Why you do not like this ?

pModel.Translation.GetAnimationNode()

OK, I hope I answered all you questions, because I am a little tired today and I could miss something.



Replies: 0