Here’s a small tip on where to quickly put files (ini or xml for example) that you can easily fetch from both Max:
import os os.getenv('LOCALAPPDATA')
Both will return “C:\Users\USER\AppData\Local”. And if you ever wondered what’s the difference between /AppData/Local and AppData/Roaming this might shed some light on it: http://superuser.com/questions/150012/what-is-the-difference-between-local-and-roaming-folders
If this was explained somewhere in Motionbuilder’s help it would have made my day yesterday so much easier. Anyway:
FBMenuManager(): the manager
FBGenericMenu(): a menu with FBGenericMenuItem()s inside
FBGenericMenuItem(): an item inside a menu, where something happens when you click on it
You can think of FBGenericMenu as a branch with leafs inside. The FBGenericMenuItems are leafs. And with FBMenuManager() you can construct, edit and destruct more branches and leafs.
You can create leafs with FBGenericMenu, but that’s all you should use it for (when creating menus).
All main menus are FBGenericMenu instances, the ones that are besides File, Edit, Help, etc.
But here’s the important trick. If you want a submenu inside a menu you need to use FBMenuManager and specify the full path (FBGenericMenu/FBGenericMenuItem) for the new subMenu. It will then create a FBGenericMenu for your FBGenericMenuItem. Confusing? Yes.
One practical example. Say you have a menu called MyTool, and you want a submenu Import with items Animation and Character, something like:
You could use this code:
oMenuMngr = FBMenuManager() oMenuMngr.InsertLast( None, "MyTool" ) oMenuMngr.InsertLast( "MyTool", "Import" ) oMenu = oMenuMngr.GetMenu( "MyTool" ) oMenuItem = oMenu.GetFirstItem() print oMenuItem.Caption # Import oMenuMngr.InsertLast( "MyTool/Import", "Animation" ) # now Import is also a FBGenericMenu as well as a FBGenericMenuItem temp = oMenu.GetFirstItem() print temp # Import FBGenericMenuItem temp = oMenuMngr.GetMenu( "MyTool/Import") print temp # Import FBGenericMenu
Apparently, to get a FBGenericMenu object you need the full path. The FBGenericMenuItems you can get only from the parent FBGenericMenu where it belongs.
FBGenericMenu also holds two important things:
1) a list of Ids, one integer for each FBGenereicMenuItem inside, which doesn’t need to be in order
2) OnMenuActivate, which binds all items to only one function that in turn will run each time the user click on any of FBGenericMenuItems inside this FBGenericMenu.
But I will get more to that on another post about creating dynamic menus based on tools you have on disc.
It’s quite easy. Basically you need to:
- plot to the skeleton;
- save one animation;
- turn on Mirror Animation for the character;
- plot back to the control rig, which then mirrors the animation;
- rotate the Character Reference model 180 degrees;
- plot back to the skeleton;
- save mirrored animation.
The tricky part is just number 5 where you need to do some matrix rotation. Python for this would be something like:
from pyfbsdk import * app = FBApplication() char = app.CurrentCharacter savePath = r"C:\" filename = app.FBXFileName skeleton = FBCharacterPlotWhere.kFBCharacterPlotOnSkeleton ctrlrig = FBCharacterPlotWhere.kFBCharacterPlotOnControlRig # plot to skeleton, see bellow plotAnim(char, skeleton) # save left animation sOptions = FBFbxOptions(False) # false = save options sOptions.SaveCharacter = True sOptions.SaveControlSet = False sOptions.SaveCharacterExtension = False sOptions.ShowFileDialog = False sOptions.ShowOptionsDialog = False app.SaveCharacterRigAndAnimation(savePath + "\\" + filename + "_L", char, sOptions) # activate mirror and plot char.MirrorMode = True plotAnim(char, ctrlrig) # get reference model refModel = FBFindModelByName("Character_Ctrl:Reference") # rotating 180, the tricky part # http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q28 rotateY180 = FBMatrix() rotateY180 = math.cos((180*0.017453292519943295769236907684886)) rotateY180 = math.sin((180*0.017453292519943295769236907684886)) rotateY180 = -math.sin((180*0.017453292519943295769236907684886)) rotateY180 = math.cos((180*0.017453292519943295769236907684886)) refMT = FBMatrix() refModel.GetMatrix(refMT) refModel.SetMatrix( MatrixMult(rotateY180, refMT) ) scene.Evaluate() # plot back to skeleton plotAnim(char, skeleton) # save again app.SaveCharacterRigAndAnimation(savePath + "\\" + filename + "_R", char, sOptions)
The plot and multiplication functions are:
# This is from Neil3d: http://neill3d.com/mobi-skript-raschet-additivnoj-animacii?langswitch_lang=en def MatrixMult(Ma, Mb): res = FBMatrix() for i in range(0,4): for j in range(0,4): sum=0 for k in range(0,4): sum += Ma[i*4+k] * Mb[k*4+j] res[i*4+j] = sum return res def plotAnim(char, where): if char.GetCharacterize: switchOn = char.SetCharacterizeOn(True) plotoBla = FBPlotOptions() plotoBla.ConstantKeyReducerKeepOneKey = True plotoBla.PlotAllTakes = True plotoBla.PlotOnFrame = True plotoBla.PlotPeriod = FBTime( 0, 0, 0, 1 ) #plotoBla.PlotTranslationOnRootOnly = True plotoBla.PreciseTimeDiscontinuities = True #plotoBla.RotationFilterToApply = FBRotationFilter.kFBRotationFilterGimbleKiller plotoBla.UseConstantKeyReducer = False plotoBla.ConstantKeyReducerKeepOneKey = True if (not char.PlotAnimation(where, plotoBla)): FBMessageBox( "Something went wrong", "Plot animation returned false, cannot continue", "OK", None, None ) return False return char
If you are exporting the character to a game, pay attention to the root node rotation. If you used it in the characterization, chances are it might also be rotated, and you might not want that to happen. You can also establish a convention on the name of the files, and easily detect if the animation currently open ends with _L or _R, and adapt the filename correctly.
FBApplication().SaveCharacterRigAndAnimation() is the equivalent of the Save Character Animation on the Character Controls window, which saves only the animation, without mesh. I prefer to use this when exporting only the animation from Motionbuilder to some other software, since it’s cleaner, faster and file sizes are smaller, but you could use any other function also.
It’s quite straight forward, once you have the correct package. Here is two places worth looking for 64 bits modules:
If you install the module it rests under C:\Python26\Lib\site-packages. You just need to copy it to a sys.path from Motionbuilder’s python. A good choice is: C:\Program Files\Autodesk\Autodesk MotionBuilder 2011 64-bit\bin\x64\python\lib\plat-win
Made some improvements to the script. After almost blowing my brains out trying to debug again all the matrix math, it seems the additive results were not being added correctly to the control rig’s bind pose.
Anyway, I switched from getting the bind pose from the control rig for the actual “stance pose” for the bone skeleton. This is the pose stored when you “add the character”. I get it’s matrix and paste it on a frame on another layer at take03. It shouldn’t be needed if the bones have zeroed out rotations though.
From all that debugging I’ve learned a couple of things:
- How to install numpy and any other modules/libraries to Mobu’s python;
- That Mobu crashes if I try to extend a list of FBMatrix() to another list (but append works);
- That to get the stance pose I need to necessarily do a .SetMatrix then .AnimationNode.KeyCandidate(). And obviously a .GetMatrix before that to find the stance matrix.
I will post more about the modules thingy on the next days.
Finally added some new features, some polish on the code and updated the script on github. You can use it to retarget any amount of animations over your characters without much hassle. You can download it here.
You just need a scene with an already characterized character and a folder with animations in .fbx. The animations need to be either already characterized or not. If they are not, they need to be on a tpose on frame 0 and have either Motionbuilder (with or without a prefix) or 3dsMax Biped nomenclature. They need to be characterized to be retargeted from one control rig to the other, so that’s why it needs a tpose. For a custom bone mapping you can easilly add a new one or edit the mobu one.
I might add bvh support in the future (maybe with FBFileBatch()?) when I find more free time again. Or feel free to add it yourself.
* edit: Ok, added support for bvh, namespace, and custom skeletons. You still need to edit the bone mapping if you have custom skeleton though.
Ok, done! Tested with EMotionFX at work and it worked great! It will probably work with both Unity, UDK, or any other animation system, but I’d love to hear from anyone that tries it out.
You can download the .py over at github, or all ziped files here. The readme files explains it all. You just need a characterized character, an animation ploted on take01, a pose ploted on take02 to be subtracted and an empty take03.
If you find any bugs or have any question, ideas or feature requests just send them my way.
Edit: added an example file using the Gremlin sample scene. Just open it and run the script. You can get a good idea of how it works by studying the content on the takes, it’s quite simple and straight forward.
Uploaded a tentative version to github. I got it working with a full character, but will have to try it out on EMotionFX on monday at work.
I need to add the pasting of the tpose automatically also.