Python Scripting in MotionBuilder

01 – pyfbsdk and Hello, Cubes

 

Part One introduces the scripting environment within MotionBuilder and demonstrates how to write and execute a simple script using pyfbsdk, MotionBuilder's Python API module.

Watch on YouTube | Watch in a full window

Key Points


Code

CreateCubeWall.py

from pyfbsdk import *

import random

def CreateCube(xPos, yPos, scaleFactor):
    '''
    Creates a test cube at the given position on the XY plane, scaling it
    uniformly by the given scale factor. The name of the cube will be
    based on its initial position. Returns the new cube.
    '''
    name = 'testCube_%d_%d' % (xPos, yPos)
    cube = FBModelCube(name)
    cube.Show = True

    cube.Translation = FBVector3d(xPos, yPos, 0.0)
    cube.Scaling = FBVector3d(scaleFactor, scaleFactor, scaleFactor)

    return cube

def main():
    ''' Creates a wall of cubes. '''
    GAP_INTERVAL = 50
    for xPos in range(-500, 500, GAP_INTERVAL):
        for yPos in range(0, 1000, GAP_INTERVAL):
            CreateCube(xPos, yPos, random.random() * 5.0 + 5.0)

if __name__ in ('__main__', '__builtin__'):
    main()

Notes & Errata

The Asterisk Import

Around 2:15, I explain the effects of blanket-importing everything from pyfbsdk with the asterisk syntax, and I mention that using this sort of import is ordinarily bad practice.

Python is very keen to organize code into namespaces, and one of the great benefits of this practice is that you don't have to worry about name clashes and other inane problems. For example, I can have a local variable called path, and at the same time I can search through a list of module directories called sys.path and use functions in another module called os.path. I don't have to worry about any of these things overwriting the other, so long as I don't do something stupid like this:

path = 'C:\\foo.txt'          # path is now a string
from sys import *             # path is now a list
from os import *              # path is now a module
path = path.normpath(path[0]) # what you talkin bout, willis?

Namespaces aren't as strictly used in C++, and things like named symbols and relationships between source files don't matter after compile time. A common mechanism for avoiding clashes in C++ libraries is to give everything a prefix, hence the redundant-seeming FB prepended to every class in pyfbsdk. It would seem kind of pointless to do something like this:

import pyfbsdk as fb
cube = fb.FBFindModelByLabelName('Cube')
null = fb.FBModelNull('Null')

Because this more primitive method protects us from name clashes, and because MotionBuilder ordinarily has all of these classes imported already, it's not a huge deal for us to use an asterisk import here. I just wanted to point out this peculiarity so you know that it's still totally not cool to import other Python modules this way.


Argument Error Message

At 2:50, I call the constructor of FBModelCube with no arguments to demonstrate an error message. If you get a Boost.Python.ArgumentError, you haven't provided the correct number of arguments or the correct types of arguments.

Boost.Python is the library that generates the Python bindings for the C++ API, and this error is its way of complaining that it couldn't find a sensible way to translate from the Python arguments it's been given to one of the function prototypes expected in the underlying C++ code. There can be many (overloaded) prototypes, but in this case it's just one:

__init__(struct object * __ptr64, char * __ptr64)

The message lists the name of the function that Boost.Python is trying to call (__init__), followed by its arguments, which are both unhelpfully named __ptr64 (64-bit pointer). The important details here are the number of arguments and their types.

The first argument is of type struct object * — this just means it's a pointer to a Python object. In all method calls, this first argument is the object on which we're calling the method (i.e., self or this). We can see from the "Python argument types" line that this object is being passed in automatically on our behalf: Python just happens to know more specifically that it's an FBModelCube.

The second argument in the C++ signature is of type char * (character pointer), which is C-speak for "string." So that's how we know, from deciphering the error message, that we need to pass in a string.


Corrections?

If you see any errors that you'd like to point out, feel free to email me at awforsythe@gmail.com.