The usual way to save a Skeleton Definition XML file is from “Character Controls>Definition>Save Skeleton Definition…” But, what if you’re looking for a programmatic way of doing it using Python – or someone left a comment asking about how to do it and you can’t resist a python challenge? Well, then you end up with something like this…
Create a list of Character Defintion Slot Property names
To start, we need to create a list of character mapping slot property names from MotionBuilder’s Character Definition tab. To do this, I reworked the XML parse code from the Python Characterization Tool to create a list of slot property names we need to find in FBCharacter – basically the reverse of populating the Mapping List (I also tried FBCharacter.GetModel(FBBodyNodeId), but the problem with that is I’d still need a dictionary to map the nodeId’s to slot names when it comes to creating the xml, so I opted for a list).
CHARACTER_SLOT_PROPERTY_NAME_LIST = (
'ReferenceLink',
'HipsLink',
'LeftUpLegLink',
'LeftLegLink',
'LeftFootLink',
'RightUpLegLink',
'RightLegLink',
'RightFootLink',
'SpineLink',
'LeftArmLink',
'LeftForeArmLink',
'LeftHandLink',
'RightArmLink',
'RightForeArmLink',
'RightHandLink',
'HeadLink',
'LeftToeBaseLink',
'RightToeBaseLink',
'LeftShoulderLink',
'RightShoulderLink',
'NeckLink',
'LeftFingerBaseLink',
'RightFingerBaseLink',
'Spine1Link',
'Spine2Link',
'Spine3Link',
'Spine4Link',
'Spine5Link',
'Spine6Link',
'Spine7Link',
'Spine8Link',
'Spine9Link',
'Neck1Link',
'Neck2Link',
'Neck3Link',
'Neck4Link',
'Neck5Link',
'Neck6Link',
'Neck7Link',
'Neck8Link',
'Neck9Link',
'LeftUpLegRollLink',
'LeftLegRollLink',
'RightUpLegRollLink',
'RightLegRollLink',
'LeftArmRollLink',
'LeftForeArmRollLink',
'RightArmRollLink',
'RightForeArmRollLink',
'HipsTranslationLink',
'LeftHandThumb1Link',
'LeftHandThumb2Link',
'LeftHandThumb3Link',
'LeftHandThumb4Link',
'LeftHandIndex1Link',
'LeftHandIndex2Link',
'LeftHandIndex3Link',
'LeftHandIndex4Link',
'LeftHandMiddle1Link',
'LeftHandMiddle2Link',
'LeftHandMiddle3Link',
'LeftHandMiddle4Link',
'LeftHandRing1Link',
'LeftHandRing2Link',
'LeftHandRing3Link',
'LeftHandRing4Link',
'LeftHandPinky1Link',
'LeftHandPinky2Link',
'LeftHandPinky3Link',
'LeftHandPinky4Link',
'LeftHandExtraFinger1Link',
'LeftHandExtraFinger2Link',
'LeftHandExtraFinger3Link',
'LeftHandExtraFinger4Link',
'RightHandThumb1Link',
'RightHandThumb2Link',
'RightHandThumb3Link',
'RightHandThumb4Link',
'RightHandIndex1Link',
'RightHandIndex2Link',
'RightHandIndex3Link',
'RightHandIndex4Link',
'RightHandMiddle1Link',
'RightHandMiddle2Link',
'RightHandMiddle3Link',
'RightHandMiddle4Link',
'RightHandRing1Link',
'RightHandRing2Link',
'RightHandRing3Link',
'RightHandRing4Link',
'RightHandPinky1Link',
'RightHandPinky2Link',
'RightHandPinky3Link',
'RightHandPinky4Link',
'RightHandExtraFinger1Link',
'RightHandExtraFinger2Link',
'RightHandExtraFinger3Link',
'RightHandExtraFinger4Link',
'LeftFootThumb1Link',
'LeftFootThumb2Link',
'LeftFootThumb3Link',
'LeftFootThumb4Link',
'LeftFootIndex1Link',
'LeftFootIndex2Link',
'LeftFootIndex3Link',
'LeftFootIndex4Link',
'LeftFootMiddle1Link',
'LeftFootMiddle2Link',
'LeftFootMiddle3Link',
'LeftFootMiddle4Link',
'LeftFootRing1Link',
'LeftFootRing2Link',
'LeftFootRing3Link',
'LeftFootRing4Link',
'LeftFootPinky1Link',
'LeftFootPinky2Link',
'LeftFootPinky3Link',
'LeftFootPinky4Link',
'LeftFootExtraFinger1Link',
'LeftFootExtraFinger2Link',
'LeftFootExtraFinger3Link',
'LeftFootExtraFinger4Link',
'RightFootThumb1Link',
'RightFootThumb2Link',
'RightFootThumb3Link',
'RightFootThumb4Link',
'RightFootIndex1Link',
'RightFootIndex2Link',
'RightFootIndex3Link',
'RightFootIndex4Link',
'RightFootMiddle1Link',
'RightFootMiddle2Link',
'RightFootMiddle3Link',
'RightFootMiddle4Link',
'RightFootRing1Link',
'RightFootRing2Link',
'RightFootRing3Link',
'RightFootRing4Link',
'RightFootPinky1Link',
'RightFootPinky2Link',
'RightFootPinky3Link',
'RightFootPinky4Link',
'RightFootExtraFinger1Link',
'RightFootExtraFinger2Link',
'RightFootExtraFinger3Link',
'RightFootExtraFinger4Link',
'LeftInHandThumbLink',
'LeftInHandIndexLink',
'LeftInHandMiddleLink',
'LeftInHandRingLink',
'LeftInHandPinkyLink',
'LeftInHandExtraFingerLink',
'RightInHandThumbLink',
'RightInHandIndexLink',
'RightInHandMiddleLink',
'RightInHandRingLink',
'RightInHandPinkyLink',
'RightInHandExtraFingerLink',
'LeftInFootThumbLink',
'LeftInFootIndexLink',
'LeftInFootMiddleLink',
'LeftInFootRingLink',
'LeftInFootPinkyLink',
'LeftInFootExtraFingerLink',
'RightInFootThumbLink',
'RightInFootIndexLink',
'RightInFootMiddleLink',
'RightInFootRingLink',
'RightInFootPinkyLink',
'RightInFootExtraFingerLink',
'LeftShoulderExtraLink',
'RightShoulderExtraLink',
'LeafLeftUpLegRoll1Link',
'LeafLeftLegRoll1Link',
'LeafRightUpLegRoll1Link',
'LeafRightLegRoll1Link',
'LeafLeftArmRoll1Link',
'LeafLeftForeArmRoll1Link',
'LeafRightArmRoll1Link',
'LeafRightForeArmRoll1Link',
'LeafLeftUpLegRoll2Link',
'LeafLeftLegRoll2Link',
'LeafRightUpLegRoll2Link',
'LeafRightLegRoll2Link',
'LeafLeftArmRoll2Link',
'LeafLeftForeArmRoll2Link',
'LeafRightArmRoll2Link',
'LeafRightForeArmRoll2Link',
'LeafLeftUpLegRoll3Link',
'LeafLeftLegRoll3Link',
'LeafRightUpLegRoll3Link',
'LeafRightLegRoll3Link',
'LeafLeftArmRoll3Link',
'LeafLeftForeArmRoll3Link',
'LeafRightArmRoll3Link',
'LeafRightForeArmRoll3Link',
'LeafLeftUpLegRoll4Link',
'LeafLeftLegRoll4Link',
'LeafRightUpLegRoll4Link',
'LeafRightLegRoll4Link',
'LeafLeftArmRoll4Link',
'LeafLeftForeArmRoll4Link',
'LeafRightArmRoll4Link',
'LeafRightForeArmRoll4Link',
'LeafLeftUpLegRoll5Link',
'LeafLeftLegRoll5Link',
'LeafRightUpLegRoll5Link',
'LeafRightLegRoll5Link',
'LeafLeftArmRoll5Link',
'LeafLeftForeArmRoll5Link',
'LeafRightArmRoll5Link',
'LeafRightForeArmRoll5Link',
)
Import Functions
Next we need to import a couple of functions to help us do what we need. We’ll need os to create our directory paths, etree to create our xml, dom to make it pretty and OrderedDict to build the xml from.
from pyfbsdk import FBApplication
import os
import xml.etree.ElementTree as etree
import xml.dom.minidom as minidom
from collections import OrderedDict
Create an Ordered Dictionary
Now we can go through the Character Definition slot property name list, find the property slot in FBCharacter, get the joint name from the “Mapping List” and create an Ordered Dictionary. Couple of things about this…
- First, the “Mapping List” in the Character Definition tab is a python list even though it only ever contains one object, so we need to get the first [0] object in the list.
- Second, we need to use an Ordered Dictionary to create the XML. When I tried this with a standard python dictionary, the order is randomised. The problem with this is, if you load the XML file using Character Controls>Definition>Load Skeleton Definition… it fails spectacularly at assigning the correct joints in the correct slots – I’m guessing it’s using the order of items, not their names – if you’re going to use python to populate the “Mapping List”, this won’t be a problem.
def _get_character_slotName_jointName_orderedDict(character_object):
"""
Create character mapping slot name, joint name ordered dictionary
Must be ordered dictionary to work with Character>Definition>Load Skeleton Definition...
Returns slot name, joint name ordered dictionary
"""
# Create empty list for slot name and joint name
slot_name_joint_name_list = []
# Add slot name, joint name tuples to list
for slot_prop_name in CHARACTER_SLOT_PROPERTY_NAME_LIST:
# Remove 'Link' to get character definition slot name
slot_name = slot_prop_name.replace('Link', '')
# Find character definition slot property
slot_prop_obj = character_object.PropertyList.Find(slot_prop_name)
if slot_prop_obj:
# Get joint name in slot property list
joint_name = slot_prop_obj[0].Name
# Add slot_name and joint_name to list
slot_name_joint_name_list.append((slot_name,joint_name))
else:
# Add empty value if not joint
slot_name_joint_name_list.append((slot_name,''))
# Create ordered dict from list
slot_name_joint_name_ordrd_dict = OrderedDict(slot_name_joint_name_list)
return slot_name_joint_name_ordrd_dict
Convert Ordered Dictionary to ElementTree
Once we have an Ordered Dictionary, we can convert it into an ElementTree ready to create the XML.
def _convert_dic_to_etree(ordered_dict):
"""
Convert ordered_dict to ElementTree
Use default template naming
Returns ElementTree object
"""
# Create root
config_root = etree.Element('config_root')
# Add match list subelement to root
match_list = etree.SubElement(config_root, 'match_list')
# Add each item in dictionary to mach_list as Subelement
for dslot_name, djoint_name in ordered_dict.iteritems():
etree.SubElement(match_list, "item", key=dslot_name, value=djoint_name)
return config_root
Make the ElementTree Pretty (XML)
Then, we can convert the ElementTree to “Pretty XML” using dom so it looks nice is human readable and has the same formatting as a Skeleton Definition XML created from the Character Controls window. We also need to remove the XML header to make it compatible with “Load Skeleton Definition…”.
def _convert_etree_to_pretty_xml(etree_obj):
"""
Convert ElementTree to pretty xml to match default skelteton template xml format
returns pretty xml with no header
"""
# Convert etree to string
xml_string = minidom.parseString(etree.tostring(etree_obj))
# Convert string to pretty xml
xml_pretty = xml_string.toprettyxml()
# Remove header and whitespace from xml
xml_with_no_header = xml_pretty.split('?>')[1].strip()
return xml_with_no_header
Write Skeleton Defintion XML
Finally, we can put everything together and write the XML to the default MotionBuilder “template” folder.
def save_skeleton_definition_file(character_obj, skel_def_file_name):
"""
Save Skeleton Definition File for character
Save to Default Template folder
"""
# Set default template path
skel_def_template_dir = os.path.join(os.path.expanduser("~"),
"AppData", "Roaming",
"Autodesk",
"HIKCharacterizationTool6",
"template")
# Create full XML file path
skel_def_file_path = os.path.join(skel_def_template_dir, skel_def_file_name)
# Get Character slotName jointName Ordered Dictionary
skel_def_dict = _get_character_slotName_jointName_orderedDict(character_obj)
# Convert Dict to ElementTree
etree_skel_def = _convert_dic_to_etree(skel_def_dict)
# Convert ElementTree to pretty XML
pretty_xml = _convert_etree_to_pretty_xml(etree_skel_def)
# Write Skeleton Definition XML
with open(skel_def_file_path, 'w') as xfile:
xfile.write(pretty_xml)
xfile.close()
Test with Current Character
All that’s left to do now is get the CurrentCharacter and write a Skeleton Definition XML we’ll use the character’s name as a prefix to help find it later.
current_character = FBApplication().CurrentCharacter
save_skeleton_definition_file(current_character, current_character.Name + "_template.xml")
Like I said at the start, I do enjoy a bit of python challenge. It’s not the first time I’ve wondered how to do something, and then disappeared down a rabbit hole for hours or days….
Anyway, hope this has answered the question in the comments on the previous post or given you a few ideas for a new tool in MotionBuilder.
Leave a Reply