Your First Python Program
 
 
 

This topic serves as an introduction to using Python with MotionBuilder. Several important classes and concepts are presented here to give you a basic understanding of how to use Python with MotionBuilder.

Your First Python Program

The Dancing Man program described in this topic constrains a character's hip effector to the mouse's (x,y) screen coordinates. At the end of this step-by-step tutorial, a character will appear to dance in the scene as the user moves the mouse. The image below shows the end result of The Dancing Man program.

"Those who danced were thought to be quite insane by those who could not hear the music." - Angela Monet

Opening the Python Editor

From the MotionBuilder application menu, select Window > Python Editor. This opens the Python Editor window, in which you can create and run Python Programs.

Importing the MotionBuilder Python Module

The MotionBuilder Python API is contained in the pyfbsdk module. This Python module is generated from the MotionBuilder C++ API using Boost Python. The pyfbsdk module contains the vast majority of the Python classes included in the MotionBuilder SDK, as well as many useful functions, including matrix and vector manipulation functions (FBMatrixInverse(), FBDot(), FBLength(), etc). For more information, consult Python Reference > Modules > pyfbsdk in the left navigation tree.

The following code sample imports the pyfbsdk module, which should be the first line of your MotionBuilder Python program. We also import the os module, which we will use later to interface with the operating system.

from pyfbsdk import *
import os

Clearing the Scene

Related topics:

We first obtain a reference to MotionBuilder's instance of FBApplication. This class provides much of the functionality present in the File application menu. In the following code sample, we invoke FBApplication.FileNew(), which is equivalent to File > New, but does not prompt the user to save before clearing the scene. For more information, see FBApplication - File I/O and Application Utilities.

# Get the instance of FBApplication, and clear the scene with FileNew().
app = FBApplication()
app.FileNew()

We then obtain a reference to MotionBuilder's scene, which will be used later to add a mouse device. The FBSystem class is used to access the scene (FBScene), as well as track various system properties including memory usage, the application path, and MotionBuilder's version information. For more information, see FBSystem - The System Class.

# Get a reference to the underlying system properties of MotionBuilder.
system = FBSystem()

# Get a reference to the current MotionBuilder scene.
scene = system.Scene

Opening a File

Related topics:

The character file we want to open is in the MotionBuilder installation directory, under OpenRealitySDK\scenes\PlasticMan.fbx. We define the helper function GetMotionBuilderInstallationDirectory() to obtain MotionBuilder's installation directory, which makes use of the FBSystem.ApplicationPath property.

# Get the MotionBuilder installation directory.
#
# Note: FBSystem().ApplicationPath returns a string similar to:
#       'C:\Program Files\Autodesk\MotionBuilder 2012 (64-bit)\bin\x64'.
#       We only want to return the substring:
#       'C:\Program Files\Autodesk\MotionBuilder 2012 (64-bit)\
#
def GetMotionBuilderInstallationDirectory() :
    applicationPath = FBSystem().ApplicationPath
    return applicationPath[0:applicationPath.index('bin')]

Invoking FBApplication.FileOpen() with the second argument set to False will open the "PlasticMan.fbx" file without prompting the user with a dialogue box. For more information on opening, merging or appending the contents of a file to the scene, see Loading from a File. Upon completion of FBapplication.FileOpen(), the PlasticMan character will appear in the MotionBuilder application.

# Use the sample PlasticMan.fbx file in the default installation directory.
filename = GetMotionBuilderInstallationDirectory() + 'OpenRealitySDK\scenes\PlasticMan.fbx'
app.FileOpen(filename, False)
NoteSpecific scene elements can be saved or loaded from a file, while other elements can be discarded. For more information, see File Options.

Getting the Character's Hips Effector

Related topics:

To make the PlasticMan character dance, we will need to manipulate his hips effector. Most objects in the scene can be uniquely identified according to their own namespace and name (see Names and Namespaces). As such, we will obtain the hips effector by searching for the FBModel whose name corresponds to "Plasticman_Ctrl:HipsEffector" via FBFindModelByLabelName().

# Get a reference to the Plastic Man's hips effector.
hipEffector = FBFindModelByLabelName("Plasticman_Ctrl:HipsEffector")

The hipEffector variable now points to the FBModelMarker corresponding to the PlasticMan's hips effector. The FBModel base class is used to define the Translation, Rotation and Scaling (TRS) of scene elements. As we will see shortly, our goal here is to constrain the translation and rotation of the PlasticMan's hips effector to the mouse's input.

Adding a Mouse Device to the Scene

To use the mouse as an input device, we will need to add a mouse FBDevice to the scene. The FBCreateObject() function can be used to obtain a mouse device. Once the mouse object is created, we must activate it (FBDevice.OnLine), allow the application to read its data (FBBox.Live) , and append it to the scene's list of devices (FBScene.Devices).

# Create a mouse device and append it to the scene.
mouseDevice = FBCreateObject("Browsing/Templates/Devices", "Mouse", "MyMouse")
mouseDevice.OnLine = True
mouseDevice.Live = True
scene.Devices.append(mouseDevice)

The FBCreateObject() function is used to create objects from within the Asset Browser UI window. We describe its arguments below.

Constraining the Character's Hips Effector to the Mouse

We can now constrain the character's hips effector to the mouse's input. To do this, we create a new FBConstraintRelation, and name it "DanceConstraint".

# Create a constraint relation.
constraintRelation = FBConstraintRelation("DanceConstraint")
constraintRelation.Active = True

The following image illustrates how the hips effector will be constrained by the mouse device's output. Instances of FBBox and FBAnimationNode are used in an FBConstraintRelation to define the behavior of the constraint.

Animatable Elements - The FBBox Base Class

FBBox is the base class of any animatable element in the scene, including models (FBModel), lights (FBLight), materials (FBMaterial), textures (FBTexture), shaders (FBShader), constraints (FBConstraint), and so on. Our FBConstraintRelation will contain a set of FBBox objects. In the image above, the boxes "MyMouse", "Number to Vector", "Number to Vector 1", and "Plasticman_Ctrl:HipsEffector" are all instances of FBBox. Each instance of FBBox can fulfill a specific role in the FBConstraintRelation:

  • The MyMouse box acts as a data source.
  • The Plasticman_Ctrl:HipsEffector box is being constrained.
  • The Number to Vector and Number to Vector 1 boxes convert the mouse's X and Y data into 3D vectors.

In the following code sample, we create these boxes and set their position in the "Constraint Settings" UI window accordingly.

# 1. Create a box for the mouse's output.
mouseBox = constraintRelation.SetAsSource(mouseDevice)
constraintRelation.SetBoxPosition(mouseBox, 30, 30)

# 2. Create a box for the hip effector's input.
hipEffectorBox = constraintRelation.ConstrainObject(hipEffector)
constraintRelation.SetBoxPosition(hipEffectorBox, 700, 30)

# 3. Create a number to vector box to map the 'Y' output of the mouse
# to the 'Y' translation of the PlasticMan's hip effector.
translationConversionBox = constraintRelation.CreateFunctionBox('Converters', 'Number to Vector')
constraintRelation.SetBoxPosition(translationConversionBox, 300, 100)

# 4. Create a number to vector box to map the 'X' output of the mouse
# to the 'X' and 'Z' rotation of the PlasticMan's hip effector.
rotationConversionBox = constraintRelation.CreateFunctionBox('Converters', 'Number to Vector')
constraintRelation.SetBoxPosition(rotationConversionBox, 300, 0)

Animatable Properties - The FBAnimationNode Class

Instances of FBAnimationNode are contained within an FBBox. In the image above, the animation nodes are denoted by arrows. An FBAnimationNode exists for any animatable property of an FBBox. For example, the "Plasticman_Ctrl:HipsEffector" box provides one "input" animation node for each of its animatable properties: translation, rotation, and scaling.

  • Arrows on the left side of a box correspond to input animation nodes.
  • Arrows on the right side of a box correspond to output animation nodes.

The utility function FindAnimationNode() defined at the beginning of our program returns the child animation node whose name corresponds to the one specified by pName.

# Define a utility function to look up a named
# animation node under a given parent node.
def FindAnimationNode( pParent, pName ):
    lResult = None
    for lNode in pParent.Nodes:
        if lNode.Name == pName:
            lResult = lNode
            break
    return lResult

In the following code sample, we extract the animation nodes of interest, and connect them together via FBConnect().

# Get a reference to all the animation nodes we need to connect.
# Mouse output:
xMouseOut = FindAnimationNode(mouseBox.AnimationNodeOutGet(), 'X')
yMouseOut = FindAnimationNode(mouseBox.AnimationNodeOutGet(), 'Y')

# Translation conversion:
yTranslationConversionIn = FindAnimationNode(translationConversionBox.AnimationNodeInGet(), 'Y')
translationConversionOut = FindAnimationNode(translationConversionBox.AnimationNodeOutGet(), 'Result')

# Rotation conversion:
xRotationConversionIn = FindAnimationNode(rotationConversionBox.AnimationNodeInGet(), 'X')
zRotationConversionIn = FindAnimationNode(rotationConversionBox.AnimationNodeInGet(), 'Z')
rotationConversionOut = FindAnimationNode(rotationConversionBox.AnimationNodeOutGet(), 'Result')

# Hip effector input. Can be obtained in the following alternative ways:
# hipTranslationIn = FindAnimationNode(hipEffectorBox.AnimationNodeInGet(), 'Translation')
# hipRotationIn = FindAnimationNode(hipEffectorBox.AnimationNodeInGet(), 'Rotation')
hipTranslationIn = hipEffector.Translation.GetAnimationNode()
hipRotationIn = hipEffector.Rotation.GetAnimationNode()



# Connect the animation nodes in the constraint.
FBConnect(yMouseOut, yTranslationConversionIn)
FBConnect(xMouseOut, xRotationConversionIn)
FBConnect(xMouseOut, zRotationConversionIn)
FBConnect(translationConversionOut, hipTranslationIn)
FBConnect(rotationConversionOut, hipRotationIn)

Saving to a File with a Dialog

Related topics:

We finally ask the user where to save the scene. To do this, we create a dialog box using FBFilePopup(), and configure it for saving.

# Save the file using a dialog box.
saveDialog = FBFilePopup()
saveDialog.Style = FBFilePopupStyle.kFBFilePopupSave
saveDialog.Filter = '*'

saveDialog.Caption = 'My Save Dialog Box'
# Set the path to the current user's My Documents\MB folder.
saveDialog.Path = os.path.expanduser('~') + '\Documents\MB'
saveDialog.FileName = 'DancingMan.fbx'

if saveDialog.Execute():
    app.FileSave(saveDialog.FullFilename)

NoteA current issue in the MotionBuilder SDK requires that the FBFilePopup.Filter property be set to "*". Failure to do so will likely result in a crash.

The Dancing Man

The following code sample is the complete Dancing Man program which you can copy into the Python Editor and run. Change the filename value to a suitable path if you are not using the default 64-bit installation of MotionBuilder. You will also have to change the string "Plasticman_Ctrl:HipsEffector" if your file contains a different character.

from pyfbsdk import *
import os

# Define a utility function to look up a named
# animation node under a given parent node.
def FindAnimationNode( pParent, pName ):
    lResult = None
    for lNode in pParent.Nodes:
        if lNode.Name == pName:
            lResult = lNode
            break
    return lResult

# Get the MotionBuilder installation directory.
#
# Note: FBSystem().ApplicationPath returns a string similar to:
#       'C:\Program Files\Autodesk\MotionBuilder 2012 (64-bit)\bin\x64'.
#       We only want to return the path:
#       'C:\Program Files\Autodesk\MotionBuilder 2012 (64-bit)\
def GetMotionBuilderInstallationDirectory() :
    applicationPath = FBSystem().ApplicationPath
    return applicationPath[0:applicationPath.index('bin')]



# Get the instance of FBApplication, and clear the scene with FileNew().
app = FBApplication()
app.FileNew()



# Get a reference to the underlying system properties of MotionBuilder.
system = FBSystem()



# Get a reference to the current MotionBuilder scene.
scene = system.Scene



# Use the sample PlasticMan.fbx file in the default installation directory.
filename = GetMotionBuilderInstallationDirectory() + 'OpenRealitySDK\scenes\PlasticMan.fbx'
app.FileOpen(filename, False)



# Get a reference to the Plastic Man's hips effector.
hipEffector = FBFindModelByLabelName('Plasticman_Ctrl:HipsEffector')



# Create a mouse device and append it to the scene.
mouseDevice = FBCreateObject('Browsing/Templates/Devices', 'Mouse', 'MyMouse')
mouseDevice.Online = True
mouseDevice.Live = True
scene.Devices.append(mouseDevice)



# Create a constraint relation.
constraintRelation = FBConstraintRelation('DanceConstraint')
constraintRelation.Active = True



# 1. Create a box for the mouse's output.
mouseBox = constraintRelation.SetAsSource(mouseDevice)
constraintRelation.SetBoxPosition(mouseBox, 30, 30)

# 2. Create a box for the hip effector's input.
hipEffectorBox = constraintRelation.ConstrainObject(hipEffector)
constraintRelation.SetBoxPosition(hipEffectorBox, 700, 30)

# 3. Create a number to vector box to map the 'Y' output of the mouse
# to the 'Y' translation of the PlasticMan's hip effector.
translationConversionBox = constraintRelation.CreateFunctionBox('Converters', 'Number to Vector')
constraintRelation.SetBoxPosition(translationConversionBox, 300, 100)

# 4. Create a number to vector box to map the 'X' output of the mouse
# to the 'X' and 'Z' rotation of the PlasticMan's hip effector.
rotationConversionBox = constraintRelation.CreateFunctionBox('Converters', 'Number to Vector')
constraintRelation.SetBoxPosition(rotationConversionBox, 300, 0)



# Get a reference to all the animation nodes we need to connect.
# Mouse output:
xMouseOut = FindAnimationNode(mouseBox.AnimationNodeOutGet(), 'X')
yMouseOut = FindAnimationNode(mouseBox.AnimationNodeOutGet(), 'Y')

# Translation conversion:
yTranslationConversionIn = FindAnimationNode(translationConversionBox.AnimationNodeInGet(), 'Y')
translationConversionOut = FindAnimationNode(translationConversionBox.AnimationNodeOutGet(), 'Result')

# Rotation conversion:
xRotationConversionIn = FindAnimationNode(rotationConversionBox.AnimationNodeInGet(), 'X')
zRotationConversionIn = FindAnimationNode(rotationConversionBox.AnimationNodeInGet(), 'Z')
rotationConversionOut = FindAnimationNode(rotationConversionBox.AnimationNodeOutGet(), 'Result')

# Hip effector input. Can be obtained in the following alternative ways:
# hipTranslationIn = FindAnimationNode(hipEffectorBox.AnimationNodeInGet(), 'Translation')
# hipRotationIn = FindAnimationNode(hipEffectorBox.AnimationNodeInGet(), 'Rotation')
hipTranslationIn = hipEffector.Translation.GetAnimationNode()
hipRotationIn = hipEffector.Rotation.GetAnimationNode()



# Connect the animation nodes in the constraint.
FBConnect(yMouseOut, yTranslationConversionIn)
FBConnect(xMouseOut, xRotationConversionIn)
FBConnect(xMouseOut, zRotationConversionIn)
FBConnect(translationConversionOut, hipTranslationIn)
FBConnect(rotationConversionOut, hipRotationIn)



# Save the file using a dialog box.
saveDialog = FBFilePopup()
saveDialog.Style = FBFilePopupStyle.kFBFilePopupSave
saveDialog.Filter = '*'

saveDialog.Caption = 'My Save Dialog Box'
# Set the path to the current user's My Documents\MB folder.
saveDialog.Path = os.path.expanduser('~') + '\Documents\MB'
saveDialog.FileName = 'DancingMan.fbx'

if saveDialog.Execute():
    app.FileSave(saveDialog.FullFilename)