FBCharacter - Characters

What is an FBCharacter?

The FBCharacter class encapsulates the set of constraints on a hierarchy of FBModel. This hierarchy is usually composed of instances of FBModelSkeleton, though instances of FBModelRoot are also used to define the character's reference point and hips, as seen in the

!!CONVERSION WARNING!! broken xref detectd, likely a cross-book reference. The target GUID file is GUID-46F873C8-32C7-4834-8264-1735F20A10FE.htm#SECTION_7AC4D12B1A3A449CB84668318C45C139 and the target link text is Example: Creating a Character

below.

When an instance of FBCharacter is created, it does not contain any joints. You must explicitly assign instances of FBModelSkeleton and FBModelRoot to their appropriate slots in the FBCharacter. Slots are identified as instances of FBProperty in FBCharacter.PropertyList whose names end with "Link". For example, the FBProperty corresponding to the character's hip joint is named "HipsLink".

The script below creates an FBCharacter, and prints all its slot names. It then illustrates how to assign a hierarchy of FBModelSkeleton to the character's "HipsLink" and "SpineLink" slots, via FBProperty.append().

from pyfbsdk import *

FBApplication().FileNew()

# Create a character.
character = FBCharacter('myCharacter')

# Print all the slot names.
for item in character.PropertyList:
    if item.Name.endswith('Link'):
        print item.Name

# Create a T-Pose hierarchy...
hips = FBModelSkeleton('myHips')
hips.Show = True
hips.Translation = FBVector3d(0, 60, 0)

spine = FBModelSkeleton('mySpine')
spine.Show = True
spine.Parent = hips
spine.Translation = FBVector3d(0, 20, 0)
# ...

# Assign the models to their appropriate slots in the character...
hipslot = character.PropertyList.Find('HipsLink')
hipslot.append(hips)

spineslot = character.PropertyList.Find('SpineLink')
spineslot.append(spine)
# ...

Creating a Skeleton

A character's skeleton is defined by a hierarchy of FBModel. The subclass(es) of FBModel used to define a skeleton only affect how the skeleton is rendered in the viewport. In the case of FBModelSkeleton, the joints are linked with a bone, denoted by a triangular prism. If a joint is an instance of FBModelRoot, it is not linked with a triangular prism from its parent joint.

It is recommended that each skeleton begin with a "reference" joint (FBModelRoot), to help position the character along the X-Z plane. The hips of the skeleton should also be an FBModelRoot, so that a bone is not drawn between the reference joint and the hips.

If you are creating a biped, the skeleton you initially define must be in a T-Pose. If you are creating a quadruped, its four legs must be below its body, and its toes must be pointing downwards.

The function createSkeleton() in the script below uses a custom "jointMap" data structure as a template to create a hierarchy of FBModelRoot and FBModelSkeleton. Two skeletons are added to the scene, but we do not bind their joints to instances of FBCharacter. As such, the joints of these skeletons are not constrained to move like a biped.

from pyfbsdk import *

# In the following skeleton template, the "LeftUpLeg" 
# has "Hips" as its parent, and its local (x,y,z) translation is (15, -10, 0).
jointMap = {
    #jointName,     (parentName,       translation  )
    'Reference':    (None,           (  0,   0,  0)),
    'Hips':         ('Reference',    (  0,  75,  0)),    
    'LeftUpLeg':    ('Hips',         ( 15, -10,  0)),
    'LeftLeg':      ('LeftUpLeg',    (  0, -30,  0)),
    'LeftFoot':     ('LeftLeg',      (  0, -30,  0)),     
    'RightUpLeg':   ('Hips',         (-15, -10,  0)),
    'RightLeg':     ('RightUpLeg',   (  0, -30,  0)),
    'RightFoot':    ('RightLeg',     (  0, -30,  0)),     
    'Spine':        ('Hips',         (  0,  40,  0)),
    'LeftArm':      ('Spine',        ( 20,  10,  0)),
    'LeftForeArm':  ('LeftArm',      ( 30,   0,  0)),
    'LeftHand':     ('LeftForeArm',  ( 30,   0,  0)),    
    'RightArm':     ('Spine',        (-20,  10,  0)),
    'RightForeArm': ('RightArm',     (-30,   0,  0)),
    'RightHand':    ('RightForeArm', (-30,   0,  0)),
    'Head':         ('Spine',        (  0,  30,  0)),
}

###############################################################
# Helper Function(s).                                         #
###############################################################

# Create a skeleton in a T-pose facing along the positive Z axis
def createSkeleton(pNamespace, pColor):
    global jointMap
    skeleton = {}

    # Populate the skeleton with joints.
    for jointName, (parentName, translation) in jointMap.iteritems():
        if jointName == 'Reference' or jointName == 'Hips':
            # If it is the reference node, create an FBModelRoot.
            joint = FBModelRoot(jointName)

        else:
            # Otherwise, create an FBModelSkeleton.
            joint = FBModelSkeleton(jointName)

        joint.LongName = pNamespace + ':' + joint.Name # Apply the specified namespace to each joint.
        joint.Color = pColor
        joint.Size = 250                               # Arbitrary size: big enough to see in viewport 
        joint.Show = True                              # Make the joint visible in the scene.

        # Add the joint to our skeleton.
        skeleton[jointName] = joint

    # Once all the joints have been created, apply the parent/child 
    # relationships to each of the skeleton's joints.
    for jointName, (parentName, translation) in jointMap.iteritems():
        # Only assign a parent if it exists.
        if parentName != None and parentName in jointMap.keys():
            skeleton[jointName].Parent = skeleton[parentName] 

        # The translation should be set after the parent has been assigned.
        skeleton[jointName].Translation = FBVector3d(translation)

    return skeleton


###############################################################
# Main.                                                       #
###############################################################

FBApplication().FileNew()

red = FBColor(1, 0, 0)
skeleton1 = createSkeleton('mySkeleton1', red)
skeleton1['Reference'].Translation = FBVector3d(-100, 0, 0)

green = FBColor(0, 1, 0)
skeleton2 = createSkeleton('mySkeleton2', green)
skeleton2['Reference'].Translation = FBVector3d(100, 0, 0)

Characterizing a Skeleton

An FBCharacter is said to be "characterized" if a minimal hierarchy of FBModel has been assigned to the "Base" (required) character slots. The following list presents the names of these required character slots: HipsLink, LeftUpLegLink, LeftLegLink, LeftFootLink, RightUpLegLink, RightLegLink, RightFootLink, SpineLink, LeftArmLink, LeftForeArmLink, LeftHandLink, RightArmLink, RightForeArmLink, RightHandLink, HeadLink. The image below shows a valid characterization of the required character slots.

When the required slots have been associated with a joint in a skeleton, the FBCharacter.SetCharacterizeOn() function may be called with the True parameter. When a character is successfully characterized, an inverse and/or forward kinematic control rig may be created using FBCharacter.CreateControlRig(). To display and use this control rig, set the FBCharacter.ActiveInput property to True.

The code below is taken from Example: Creating a Character, and illustrates how to characterize a skeleton.

# Characterize the skeleton and create a control rig.
def characterizeSkeleton(pCharacterName, pSkeleton):
    # Create a new character.
    character = FBCharacter(pCharacterName)
    FBApplication().CurrentCharacter = character

    # Add each joint in our skeleton to the character.
    for jointName, joint in pSkeleton.iteritems():
        slot = character.PropertyList.Find(jointName + 'Link')
        slot.append(joint)

    # Flag that the character has been characterized.
    character.SetCharacterizeOn(True)

    # Create a control rig using Forward and Inverse Kinematics,
    # as specified by the "True" parameter.
    character.CreateControlRig(True)

    # Set the control rig to active.
    character.ActiveInput = True

    return character

Example: Creating a Character.

Sample Viewport Output:

Program Summary: The program below first defines a template which will be used to create a hierarchy of FBModelSkeleton joints. The joint names are important here because they correspond to MotionBuilder's most basic biped characterization naming template.

Once the skeleton is created, we apply cartoon-shaded spheres to each joint to make our character more visually appealing. In practice however, we would assign a mesh to the skeleton's 'Reference' model. The vertices of this mesh would be weighted according to the character's bones. This lengthy process is omitted for clarity.

The program concludes by characterizing the skeleton, and creating a forward- and inverse-kinematic control rig.

from pyfbsdk import *

# In the following skeleton template, the "LeftUpLeg" 
# has "Hips" as its parent, and its local (x,y,z) translation is (15, -10, 0).
jointMap = {
    #jointName,     (parentName,       translation  )
    'Reference':    (None,           (  0,   0,  0)),
    'Hips':         ('Reference',    (  0,  75,  0)),    
    'LeftUpLeg':    ('Hips',         ( 15, -10,  0)),
    'LeftLeg':      ('LeftUpLeg',    (  0, -30,  0)),
    'LeftFoot':     ('LeftLeg',      (  0, -30,  0)),     
    'RightUpLeg':   ('Hips',         (-15, -10,  0)),
    'RightLeg':     ('RightUpLeg',   (  0, -30,  0)),
    'RightFoot':    ('RightLeg',     (  0, -30,  0)),     
    'Spine':        ('Hips',         (  0,  40,  0)),
    'LeftArm':      ('Spine',        ( 20,  10,  0)),
    'LeftForeArm':  ('LeftArm',      ( 30,   0,  0)),
    'LeftHand':     ('LeftForeArm',  ( 30,   0,  0)),    
    'RightArm':     ('Spine',        (-20,  10,  0)),
    'RightForeArm': ('RightArm',     (-30,   0,  0)),
    'RightHand':    ('RightForeArm', (-30,   0,  0)),
    'Head':         ('Spine',        (  0,  30,  0)),
}

###############################################################
# Helper Function(s).                                         #
###############################################################

# Create a skeleton in a T-pose facing along the positive Z axis
def createSkeleton(pNamespace):
    global jointMap
    skeleton = {}

    # Populate the skeleton with joints.
    for jointName, (parentName, translation) in jointMap.iteritems():
        if jointName == 'Reference' or jointName == 'Hips':
            # If it is the reference node, create an FBModelRoot.
            joint = FBModelRoot(jointName)

        else:
            # Otherwise, create an FBModelSkeleton.
            joint = FBModelSkeleton(jointName)

        joint.LongName = pNamespace + ':' + joint.Name # Apply the specified namespace to each joint.
        joint.Color = FBColor(0.3, 0.8, 1)             # Cyan
        joint.Size = 250                               # Arbitrary size: big enough to see in viewport 
        joint.Show = True                              # Make the joint visible in the scene.

        # Add the joint to our skeleton.
        skeleton[jointName] = joint

    # Once all the joints have been created, apply the parent/child 
    # relationships to each of the skeleton's joints.
    for jointName, (parentName, translation) in jointMap.iteritems():
        # Only assign a parent if it exists.
        if parentName != None and parentName in jointMap.keys():
            skeleton[jointName].Parent = skeleton[parentName] 

        # The translation should be set after the parent has been assigned.
        skeleton[jointName].Translation = FBVector3d(translation)

    ''' The following is a much longer way to write the above function:

    reference = FBModelRoot('Reference')

    hips = FBModelRoot('Hips')
    hips.Color = FBColor(0.3, 0.8, 1)
    hips.Size = 250
    hips.Translation = FBVector3d(0, 75, 0)
    hips.Parent = reference
    skeleton['Hips'] = hips

    spine = createSkeletonNode('Spine')
    spine.Parent = hips
    spine.Color = FBColor(0.3, 0.8, 1)
    spine.Size = 250
    spine.Translation = FBVector3d(0, 25, 0)
    skeleton['Spine'] = spine

    # ...
    '''
    return skeleton

# Create a model which will be applied to each joint in the skeleton.
def createModel():
    # Create a sphere.
    model = FBCreateObject('Browsing/Templates/Elements/Primitives', 'Sphere', 'Sphere')
    model.Scaling = FBVector3d(0.9, 0.9, 0.9)

    # Define a slightly reflective dark material.
    material = FBMaterial('MyMaterial')
    material.Ambient = FBColor(0, 0, 0)
    material.Diffuse = FBColor(0, 0.04, 0.08)
    material.Specular = FBColor(0, 0.7, 0.86)
    material.Shininess = 100
    model.Materials.append(material)

    # Create a cartoon-like shader.
    shader = FBCreateObject('Browsing/Templates/Shading Elements/Shaders', 'Edge Cartoon', 'MyShader')

    # For a list of all the shader's properties do:
    #for item in shader.PropertyList:
    #    print item.Name
    aliasProp = shader.PropertyList.Find('Antialiasing')
    aliasProp.Data = True
    colorProp = shader.PropertyList.Find('EdgeColor')
    colorProp.Data = FBColor(0, 0.83, 1)
    widthProp = shader.PropertyList.Find('EdgeWidth')
    widthProp.Data = 8

    # Append the cartoon shader to the model.
    model.Shaders.append(shader)        

    # The default shader must also be applied to the model.
    defaultShader = FBSystem().Scene.Shaders[0]
    model.Shaders.append(defaultShader) 

    # Use the default shading mode.
    model.ShadingMode = FBModelShadingMode.kFBModelShadingDefault

    return model

# Apply a copy of pModel to each joint in the skeleton. 
def applyModelToSkeleton(pSkeleton, pModel):   
    # Create a copy of the model for each joint in the skeleton.
    for jointName, joint in pSkeleton.iteritems():
        if jointName == 'Reference':
            # Do not apply the model to the Reference node.
            continue

        # Parent the copied model to the joint.
        model = pModel.Clone()
        model.Parent = joint        
        model.Show = True

        # Use the joint name as a prefix.
        model.Name = jointName + pModel.Name

        # Reset the model's translation to place it at the same 
        # location as its parent joint.
        model.Translation = FBVector3d(0, 0, 0)

# Characterize the skeleton and create a control rig.
def characterizeSkeleton(pCharacterName, pSkeleton):
    # Create a new character.
    character = FBCharacter(pCharacterName)
    FBApplication().CurrentCharacter = character

    # Add each joint in our skeleton to the character.
    for jointName, joint in pSkeleton.iteritems():
        slot = character.PropertyList.Find(jointName + 'Link')
        slot.append(joint)

    # Flag that the character has been characterized.
    character.SetCharacterizeOn(True)

    # Create a control rig using Forward and Inverse Kinematics,
    # as specified by the "True" parameter.
    character.CreateControlRig(True)

    # Set the control rig to active.
    character.ActiveInput = True

    return character


###############################################################
# Main.                                                       #
###############################################################

# Clear the scene.
FBApplication().FileNew()

# Create a new skeleton.
characterName = 'MyStickman'
skeleton = createSkeleton(characterName)

# Apply a model to each limb of the skeleton.
templateModel = createModel()
applyModelToSkeleton(skeleton, templateModel)
templateModel.FBDelete() # We do not need the template model anymore.

# Characterize the skeleton and create a control rig.
character = characterizeSkeleton(characterName, skeleton)