Before we can start applying all that lovely motion capture we got from our Xsens MVN mocap system to our characters, we’re going to need to put them in a T-Pose and Characterize them…I know, I know…ANOTHER tutorial on how to T-Pose and Characterize a character in MotionBuilder…
BUT WAIT, this one’s different…
What if you’re working on a project with multiple characters and tight deadlines (and let’s be honest, on those kinds of projects, you’re probably going to be doing this more than once) then you’re going to want a way to do this automatically…
…which is exactly where Python scriping in MotionBuilder comes in!
Now, I’ve already made a video showing how to t-pose and characterize your character manually, so in this video I’ll show you how to write a script to do everything automatically:
- Centre your character in the scene
- Create IK constraints for the arms and legs
- Use those constraints to T-pose the character
- Create a Character asset
- Complete the Character Mapping using the Skeleton Definition XML file
- And Characterize it
And even if you don’t use python scripting in MotionBuilder, you can still learn a new way to T-Pose your character using Chain IK Constraints.
Why use MotionBuilder Python?
The idea for this came from the guys over at Rigging Dojo who made a video showing how to t-pose a character in Maya using IK. I thought this was a great alternative to manually rotating the limbs into a T-Pose, and, as this is the kind of thing you’ll do again and again, it’s also the perfect job for a python script.
The great thing about scripts is, you can get them to do all the boring, repetitive stuff, so you can spend more time doing the interesting, creative stuff. Also, because the script won’t miss a step or forget to do something, your characters will always work in the same way, making them easier for everyone to work with.
AND not only all of that, but you’ll also be able to give the script to someone who’s never used MotionBuilder before and they’ll be able to use it to set up a character with no training. Just a couple of button clicks and away they go…
Writing the Code
Import Python Modules
The first thing we’ll need to do is import a couple of modules we’ll use later
from pyfbsdk import *
import xml.etree.ElementTree as etree
import os
import pprint
Convert XML to Python Dictionary
Next, we’ll need a fuction to convert the Skeleton Definition XML file to a python dictionary. These are the XML files stored in your windows “user\AppData\Roaming\Autodesk\HIKCharacterizationTool6\template” folder. To use you’re own XML template file, just pass it the name of your XML file.
def convert_skeleton_definition_xml_to_dict(xmlFileName):
"""
Convert Skeleton Definition XML file to Python Dictionary
Uses Default MotionBuilder HIKCharacterizationTool6/template filepath
"""
# Get path to XML template file
xmlFilePath = os.path.join(os.path.expanduser("~"), "AppData", "Roaming", "Autodesk", "HIKCharacterizationTool6", "template", xmlFileName)
# Parse XML file
parsedXmlFile = etree.parse(xmlFilePath)
# Create empty dictionary
skelDefDict = {}
# Add parsed XML to dcitionary
for line in parsedXmlFile.iter("item"):
jointName = line.attrib.get("value")
if jointName:
slotName = line.attrib.get("key")
skelDefDict[slotName] = jointName
return skelDefDict
Get Character Joint Name from XML Template Dictionary
Once we have the Skeleton Definition file as a dictionary, we can use the character asset “slot name” (which is always the same) to find the joint name (which could be different for each character).
def get_char_joint_from_slot_name(templateDict,
slotName
):
"""
Get joint in character using MotionBuilder character slot name
"""
# Get name of joint from MotionBuilder character slot
charJointName = templateDict.get(slotName)
# Find joint object using joint name
charJointObj = FBFindModelByLabelName(charJointName)
return charJointObj
Create IK Effector
For this example, we’re going to create a Null object to use as the IK Effector. Then, we’ll align the null with the end joint in the limb, for example, the wrist.
def create_ik_effector(endJointObj):
"""
Create IK Effector using Null Object
Set Object Name
Align IK Effector to wrist/ankle
"""
# Create name for IK effector including namespace and suffix
effectorName = ikHelperNamesapce + endJointObj.Name + "_ikEffector"
# Create Null object
effector = FBModelNull(effectorName)
# Show Null
effector.Show = True
# Set Null Size
effector.Size = 300
# Get Gloabl Traslation of End Joint
endJointObjVecotr3d = FBVector3d()
endJointObj.GetVector(endJointObjVecotr3d, FBModelTransformationType.kModelTranslation, True)
# Align IK Effector Null to End Joint
effector.SetVector(endJointObjVecotr3d, FBModelTransformationType.kModelTranslation, True)
return effector
Create Pole Vector Object
We’ll use another Null object to create our Pole Vector object and align that to the elbow joint using “SetMatrix”. This will make sure the translation and rotation of the null matches the elbow, so it will straighten the elbow correctly when we translate it later.
def create_pole_vector(endJointObj):
"""
Create Pole Vector using Null Object
Set Object Name
Align Pole Vector to elbow/knee
"""
# Create name for Pole Vector including namespace and suffix
poleVectorName = ikHelperNamesapce + endJointObj.Name + "_poleVector"
# Create Null object
pv = FBModelNull(poleVectorName)
# Show Null
pv.Show = True
# Set Null Size
pv.Size = 300
# Get Matrix for middle joint
jointMatrix = FBMatrix()
endJointObj.Parent.GetMatrix(jointMatrix)
# Align Pole Vector Null to Middle Joint
pv.SetMatrix(jointMatrix)
return pv
Create IK Constraint
Now we’ve got an IK Effector and Pole Vector, we can create a “Chain IK” constraint and add them to it.
def create_ik_constraint(endJointObj,
effector,
pv
):
"""
Create Chain IK Constraint
Set Constraint Name
Populate Constraint
Activate Constraint
"""
# Get constraint name
constraintName = ikHelperNamesapce + endJointObj.Name + "_poleVector"
# Get first joint in joint chain
firstJoint = endJointObj.Parent.Parent
# Find Chain IK in FBConstraintManager
constraintManager = FBConstraintManager()
for constTypeIndex in range(constraintManager.TypeGetCount()):
if constraintManager.TypeGetName(constTypeIndex) == "Chain IK":
# Create Chain IK Constraint
chainIk = constraintManager.TypeCreateConstraint(constTypeIndex)
# Set constraint name including namespace
chainIk.LongName = constraintName
# Populate Constraint
for refGroupIndex in range(chainIk.ReferenceGroupGetCount()):
if chainIk.ReferenceGroupGetName(refGroupIndex) == "First Joint":
chainIk.ReferenceAdd(refGroupIndex, firstJoint)
elif chainIk.ReferenceGroupGetName(refGroupIndex) == "End Joint":
chainIk.ReferenceAdd(refGroupIndex, endJointObj)
elif chainIk.ReferenceGroupGetName(refGroupIndex) == "Effector":
chainIk.ReferenceAdd(refGroupIndex, effector)
elif chainIk.ReferenceGroupGetName(refGroupIndex) == "Pole Vector Object":
chainIk.ReferenceAdd(refGroupIndex, pv)
# Activate (Snap) Constrtaint
chainIk.Snap()
return chainIk
T-Pose Character Limb
Now we’ve created a Chain IK Constraint, we can use it to T-Pose our characters arms and legs.
def t_pose_limb(endJoint,
isArm
):
"""
T-Pose Character Limb using Chain IK
Create IK Effector
Create Pole Vector
Create Chain IK
Calculate Limb length using Joint Roll/Twist Axis
Offset in X for arms
Offset in Y for legs
"""
# Get first joint in chain
firstJoint = endJoint.Parent.Parent
# Create IK Effector
leftArmEffector = create_ik_effector(endJoint)
# Create Pole Vector
leftArmPv = create_pole_vector(endJoint)
# Create Chain IK
leftChainIk = create_ik_constraint(endJoint, leftArmEffector, leftArmPv)
# Calulate Length of Limb
leftLimbLength = endJoint.Parent.Translation[1] + endJoint.Translation[1]
# Get Gloabl Translation of First Joint
firstJointVector3d = FBVector3d()
firstJoint.GetVector(firstJointVector3d, FBModelTransformationType.kModelTranslation, True)
# Get Global offset Poistion for IK Effector and Pole Vector
if isArm:
# Check if Left/Right arm
if firstJointVector3d[0]<0:
xOffset = firstJointVector3d[0] + leftLimbLength*-1
else:
xOffset = firstJointVector3d[0] + leftLimbLength
ikOffsetVector3d = FBVector3d(xOffset, firstJointVector3d[1], firstJointVector3d[2])
pvOffsetVector3d = FBVector3d(firstJointVector3d[0], firstJointVector3d[1], -50)
else:
yOffset = firstJointVector3d[1] + leftLimbLength*-1
ikOffsetVector3d = FBVector3d(firstJointVector3d[0], yOffset, firstJointVector3d[2])
pvOffsetVector3d = FBVector3d(firstJointVector3d[0], firstJointVector3d[1], 50)
# Offset IK Effector and Pole Vector tranlation in Gloabl Space
leftArmEffector.SetVector(ikOffsetVector3d, FBModelTransformationType.kModelTranslation, True)
leftArmPv.SetVector(pvOffsetVector3d, FBModelTransformationType.kModelTranslation, True)
# Zero Pole Vector roation values in Gloabl space
leftArmPv.SetVector(FBVector3d(), FBModelTransformationType.kModelRotation, True)
T-Pose Character
The last thing we need to do is combine all of the above to T-Pose our character and centre it in the scene.
def t_pose_character(skeletonDefinitionDictionary):
"""
Centre Character in Scene
Zero Joint Rotations Locally
Put Charcter in T-Pose using Chain IK
"""
# Get Character Hips Object
charHipsObj = get_char_joint_from_slot_name(skeletonDefinitionDictionary, "Hips")
charHipsObj.Selected = True
if not charHipsObj:
print "ERROR"
else:
# Centre Character in Scene
charHipsVector3d = FBVector3d()
charHipsObj.GetVector(charHipsVector3d, FBModelTransformationType.kModelTranslation, True)
charZeroHipVector3d = FBVector3d(0.0, charHipsVector3d[1], 0.0)
charHipsObj.SetVector(charZeroHipVector3d, FBModelTransformationType.kModelTranslation, True)
# Zero Joint Rotations Locally
for skeleton in FBSystem().Scene.ModelSkeletons:
skeleton.Rotation = FBVector3d(0, 0, 0)
# Evaluate scene to register new joint rotations
FBSystem().Scene.Evaluate()
# T-Pose Character Limbs
charLeftHand = get_char_joint_from_slot_name(skeletonDefinitionDictionary, "LeftHand")
t_pose_limb(charLeftHand, True)
charLeftHand = get_char_joint_from_slot_name(skeletonDefinitionDictionary, "RightHand")
t_pose_limb(charLeftHand, True)
charLeftHand = get_char_joint_from_slot_name(skeletonDefinitionDictionary, "LeftFoot")
t_pose_limb(charLeftHand, False)
charLeftHand = get_char_joint_from_slot_name(skeletonDefinitionDictionary, "RightFoot")
t_pose_limb(charLeftHand, False)
Characterize Character
Once we have our character in a T-Pose, this script will add a character to the scene, rename it, complete the character mapping using the Skeleton Definition template dictionary we created earlier, activate the Characterization and make it the current character in the scene. It will also return any errors if things go wrong.
def characterize_character(skeletonDefinitionDictionary,
characterName
):
"""
Create Character
Complete Character Mapping using XML Template file
Characterize Character
Set Character to Current Character
"""
# Create New Character
newCharacter = FBCharacter(characterName)
# Complete Character Mapping
charSlotNameJointNameDict = convert_skeleton_definition_xml_to_dict("HIK.xml")
for slotName, jointName in charSlotNameJointNameDict.items():
mappingSlot = newCharacter.PropertyList.Find(slotName + "Link")
jointObj = FBFindModelByLabelName(jointName)
if jointObj:
mappingSlot.append(jointObj)
# Characterize
characterized = newCharacter.SetCharacterizeOn(True)
if not characterized:
# Get Characterization Error
print newCharacter.GetCharacterizeError()
else:
# Set Character to Current Character
FBApplication().CurrentCharacter = newCharacter
delete_ik_helpers(ikHelperNamesapce[:-1])
return newCharacter
Delete IK Helpers using Namespace
Last thing to do is delete the “IK Helpers” we created to T-Pose the character. As we added them into a namespace, we can use that namespace to find and delete them.
def delete_ik_helpers(ikHelperNamespace):
FBSystem().Scene.NamespaceDelete(ikHelperNamespace)
Run the code
The only thing left to do is to run the code.
ikHelperNamesapce = "ikHelper:"
charSkelDefDict = convert_skeleton_definition_xml_to_dict("HIK.xml")
##t_pose_character(charSkelDefDict)
##characterize_character(charSkelDefDict, "nigel")
Next Step…
If you wanted to take this one step further, you could add a script to create a UI with a couple of buttons and use the FBMessageBoxGetUserValue to get the character name…but where’s the fun if I give you all the code here 🙂
And don’t foget to let me know in the comments below if you’d you like to see more python scripting tutorials for MotionBuilder, or if you have any questions or suggestions on the above.
Till next time…
Dave says
Thank you for publishing this, I’ve been looking for a good starter tutorial on MB scripting for a while, this made it all a breeze.
Mocappy says
Excellent! Good to hear it’s helped get you get started. There’s a lot you can do with scripting, and a LOT of the less creative, repetitive work you can save your self!
I was thinking of creating a course to expand on this a little – basically the entire character set up process from beginning to end, but using python. It’s a good way to learn how to find and work on objects in the scene, plus do some simple file IO…
If you’re interested, keep an eye on the academy
LL says
Excellent blog and very helpful that I learned a lot. Thanks for sharing it.
Would you like to do tutorial about how to save skeleton definition Template and save with XML file in python?
Mocappy says
Thank you. And yes, yes I would…
https://mocappys.com/how-to-create-a-skeleton-definition-file-in-motionbuilder-using-python/
Aaron Low says
Thanks for the tutorial. I am attempting to repurpose the Characterization function to automate Vicon skeleton set ups.
I am currently debugging a solution by exploring scrubbing namespaces as I suspect the longName are usually the issues in these factors.
What factors should I be looking for in debugging characterization. I print the slot and joint names. So I know the xml file is read and working properly(Even made a custom XML for the same skeleton)
I suspect this relates to
jointObj = FBFindModelByLabelName(jointName)
As this is finding all of this type, and if there are three people with that name it can cause issues.
So I need to search through the children more than just whole scene.
Thanks again for the video!
Mocappy says
Hi Aaron,
First, I would keep namespaces if you can, especially if you have multiple charcter rigs in your scene. It means you can keep the joint names the same for all your characters (handy for scripting, etc) and use the namespace to avoid naming conflicts, deleting after the fact, etc.
For Vicon skeletons, I think they usually come namespaced with the performer name, apart from the top node, so you’ll need to find that and add it to the same namespace.
This characterization script isn’t set up for namespaces, but if you wanted to use them (and I’d highly recommend you do), you could pass the performer name as the character name. This will name your Character to match the performer. Then change the newCharacter LongName to characterName+”:”+characterName for consitency and help with other scripts later 😉
Last thing to do is, when you FBFindModelByLabelName, you’ll need to use the LongName to include the namespace (characterName+”:”+jointName) to make sure you’re getting the joints for that character only – if you wanted, you could also add some more code here to catch “if not jointObj: print “Can’t Find”, jointName
Hopefully this will help