The FBPose
class encapsulates MotionBuilder's ability to take a snapshot of characters (FBCharacterPose
), and objects (FBObjectPose
).
A character pose is created by invoking the FBCharacterPose()
constructor. This adds an empty pose item in the scene's list of poses, as well as in MotionBuilder's Pose Controls
UI element.
Given a reference to an FBCharacter
in the scene (for example: FBApplication.CurrentCharacter
), the FBCharacterPose.CopyPose()
function stores the given character's pose into that FBCharacterPose
object. Pose creation is illustrated in the code snippet below.
# Create poses of the character.
def createPoses(pCharacter):
poseList = []
# Create our first pose.
pose1 = FBCharacterPose('myPose1')
pose1.CopyPose(pCharacter)
poseList.append(pose1)
# Obtain a reference to the character's effectors.
hipsEffector = pCharacter.GetEffectorModel(FBEffectorId.kFBHipsEffectorId)
# Create our second pose.
hipsEffector.Translation = FBVector3d(0, 50, 0)
# (!!!) Note: It is very important to invoke FBSystem().Scene.Evaluate()
# at this step to ensure the hipsEffector has effectively been translated.
# Otherwise, it is not guaranteed that MotionBuilder's evaluation thread
# will have translated the hips effector before pose2.CopyPose() is called.
FBSystem().Scene.Evaluate()
pose2 = FBCharacterPose('myPose2')
pose2.CopyPose(pCharacter)
poseList.append(pose2)
# Repeat pose1 in our poseList to enable looping later.
poseList.append(pose1)
return (poseList)
The FBCharacterPose.PastePose()
function applies the data contained in FBCharacterPose
to the specified character. An instance of FBCharacterPoseOptions
can also be specified to ensure the pose is mirrored, or to enable full-body keying mode. The code snippet below illustrates how to apply a list of poses to a character in the scene, and how to use these poses as animation key frames.
# Create keys from the pose list.
def createKeys(pCharacter, pPoseList):
# Ensure we take a pose of the entire character's body.
poseOptions = FBCharacterPoseOptions()
poseOptions.mCharacterPoseKeyingMode = FBCharacterPoseKeyingMode.kFBCharacterPoseKeyingModeFullBody
poseOptions.SetFlag(FBCharacterPoseFlag.kFBCharacterPoseMirror, True)
# Select the character's control rig (1st and 3rd parameter = True). This
# allows FBPlayerControl.Key() to key the selected models.
pCharacter.SelectModels(True, False, True, False)
# Create a pose every 15 frames using our pose list.
lPlayer = FBPlayerControl()
poseCount = 0
frameInterval = 15
for pose in pPoseList:
# Go to the next frame.
lPlayer.Goto(FBTime(0, 0, 0, frameInterval * poseCount))
FBSystem().Scene.Evaluate()
# Apply the pose to the character using our pose options.
pose.PastePose(pCharacter, poseOptions)
FBSystem().Scene.Evaluate()
# Create a key in the player.
lPlayer.Key()
FBSystem().Scene.Evaluate()
# Increment our counter.
poseCount += 1
# De-select the character's control rig (1st parameter = False, 3rd parameter = True).
pCharacter.SelectModels(False, False, True, False)
# Return the frame number of the last frame keyed.
return frameInterval * (poseCount - 1)
Sample Viewport Output:
Program Summary: The following program builds on the program presented in FBCharacter - Characters. We define two new functions to make the character perform a looping "crouching" animation:
createPoses()
: This function creates two poses, the first of which is the T-Pose of the character. The second pose has the character's hips effector translated to (0, 50, 0)
. Character poses are taken via FBCharacterPose.CopyPose()
.createKeys()
: This function keys the character every 15 frames, according to the specified list of poses. Poses are applied to the character via FBCharacterPose.PastePose()
.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
# Create poses of the character.
def createPoses(pCharacter):
poseList = []
# Create our first pose.
pose1 = FBCharacterPose('myPose1')
pose1.CopyPose(pCharacter)
poseList.append(pose1)
# Obtain a reference to the character's effectors.
hipsEffector = pCharacter.GetEffectorModel(FBEffectorId.kFBHipsEffectorId)
# Create our second pose.
hipsEffector.Translation = FBVector3d(0, 50, 0)
# (!!!) Note: It is very important to invoke FBSystem().Scene.Evaluate()
# at this step to ensure the hipsEffector has effectively been translated.
# Otherwise, it is not guaranteed that MotionBuilder's evaluation thread
# will have translated the hips effector before pose2.CopyPose() is called.
FBSystem().Scene.Evaluate()
pose2 = FBCharacterPose('myPose2')
pose2.CopyPose(pCharacter)
poseList.append(pose2)
# Repeat pose1 in our poseList to enable looping later.
poseList.append(pose1)
return (poseList)
# Create keys from the pose list.
def createKeys(pCharacter, pPoseList):
# Ensure we take a pose of the entire character's body.
poseOptions = FBCharacterPoseOptions()
poseOptions.mCharacterPoseKeyingMode = FBCharacterPoseKeyingMode.kFBCharacterPoseKeyingModeFullBody
poseOptions.SetFlag(FBCharacterPoseFlag.kFBCharacterPoseMirror, True)
# Select the character's control rig (3rd parameter = True). This
# allows FBPlayerControl.Key() to key the selected models.
pCharacter.SelectModels(True, False, True, False)
# Create a pose every 15 frames using our pose list.
lPlayer = FBPlayerControl()
poseCount = 0
frameInterval = 15
for pose in pPoseList:
# Go to the next frame.
lPlayer.Goto(FBTime(0, 0, 0, frameInterval * poseCount))
FBSystem().Scene.Evaluate()
# Apply the pose to the character using our pose options.
pose.PastePose(pCharacter, poseOptions)
FBSystem().Scene.Evaluate()
# Create a key in the player.
lPlayer.Key()
FBSystem().Scene.Evaluate()
# Increment our counter.
poseCount += 1
# De-select the character's control rig (1st parameter = False).
pCharacter.SelectModels(False, False, True, False)
# Return the frame number of the last frame keyed.
return frameInterval * (poseCount - 1)
###############################################################
# 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)
# Create the poses
poseList = createPoses(character)
# Create keys in the player control.
lastFrame = createKeys(character, poseList)
# Play the animation.
lPlayer = FBPlayerControl()
lPlayer.LoopStart = FBTime(0, 0, 0, 0) # Set the start time in the player control.
lPlayer.LoopStop = FBTime(0, 0, 0, lastFrame) # Set the end time in the player control.
lPlayer.LoopActive = True # Enable playback looping.
lPlayer.GotoStart() # Play the animation from the beginning.
lPlayer.Play()