Mocappys

Your Guide to Capturing and Editing Motion

  • MotionBuilder Tutorials
  • Motion Capture Guides
  • Academy
  • Contact
  • About
You are here: Home / MotionBuilder Tutorials / How to Write a Skeleton Definition File Using Python

How to Write a Skeleton Definition File Using Python

By Mocappy Leave a Comment

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…

Table of Contents

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…

  1. 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.
  2. 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.

Tweet

Filed Under: MotionBuilder Tutorials

« How to Use MotionBuilder Story Tool to Create a Cut-Scene
How to Animate Props in MotionBuilder | Multi-Referential Constraints »

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Search

Want to Animate Your Character with Motion Capture?

how_to_animate_character_with_mocap

Join my Complete Step-By-Step Course to learn everything you need to know to Animate Your Character With Motion Capture. From exporting your character into MotionBuilder, to importing the finished animation back onto your character – exactly as you would in a professional studio.

LEARN MORE>>


Hi, I’m Simon Kay

My aim for this site is to share everything I’ve learned over the last 20 years using MotionBuilder at some of the leading video game, motion capture and visual effects studios.

Top Posts:

  • Creating a Looping Animation In MotionBuilder
  • How to Characterize your character in MotionBuilder
  • How to Map Optical Mocap using MotionBuilder Actor
  • How to Rig a Character for MotionBuilder
  • How to Plan a Motion Capture Shoot | Creating a Move List
  • How to Mocap a Performer using a Vicon System
  • How to Merge Characters in MotionBuilder
  • Retargeting Animation In MotionBuilder
  • How to plan a Motion Capture Shoot – Mocap Shot List
  • How to Write a Skeleton Definition File Using Python

Follow:

  • E-mail
  • LinkedIn
  • RSS
  • Twitter
  • YouTube

Copyright © 2023 Mocappys.com