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 on a previous post asking 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 Definition Slot Property names
To start, 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 all the properties in FBCharacter and find the ones that have “Link” in the name and ignore any that also contain “Game” and “Leaf” as these aren’t used in the Skeleton Definition XML. Then we can use the same property to get the joint name from the “Mapping List” and combine all that information to 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 = []
for character_property in character_object.PropertyList:
if "Link" in character_property.Name\
and not "Game" in character_property.Name\
and not "Leaf" in character_property.Name:
character_slot_obj = character_property
# Remove 'Link' to get character definition slot name
slot_name = character_slot_obj.Name.replace('Link', '')
# Check slot has "Mapping List" object assigned
if character_slot_obj:
# slot returns a list of objects, so get first
joint_name = character_slot_obj[0].Name
else:
joint_name = ""
# Add slot_name and joint_name to list
slot_name_joint_name_list.append((slot_name, joint_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()
print "File Saved as:{}".format(skel_def_file_path)
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.
Jaisurya Chandrasekar says
What if I already have a skeleton definition in XML, and loading the xml in the current skeleton which I have in the scene. There is a button in motion builder to ‘load skeleton definition’ How can I do it with python?
Thanks!
Mocappy says
I don’t think this button is exposed in python (most aren’t), so you have to use the FBCharacter, etc. to get the same effect. Sorry.
If you already have the XML, you can convert it to a dictionary and use that, like it do in this tutorial