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)
# ...
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)
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
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)