Python Scripting in MotionBuilder

04 – Accessing Components: FBScene

Part Four describes how to obtain references to objects in the scene, first using the global functions of pyfbsdk, and then with the help of the FBScene class.

Watch on YouTube | Watch in a full window

Key Points


Code

Find a single model by name

print FBFindModelByLabelName('ENV:Root')
print FBFindObjectByFullName('Model::ENV:Root') # <-- equivalent

Print the name of every free function defined in pyfbsdk

import pyfbsdk

for attr in dir(pyfbsdk):

    if attr.endswith('_TypeInfo'):
        continue

    attrType = type(eval('pyfbsdk.%s' % attr))
    if str(attrType).find('Boost.Python.function') >= 0:
        print attr

Find all components matching a specific name pattern

foundComponents = FBComponentList()

includeNamespace = True
modelsOnly = False
FBFindObjectsByName('ENV:GEO:*', foundComponents, includeNamespace, modelsOnly)

for comp in foundComponents:
    print comp, comp.LongName

Get a list of all selected models, sorted by selection order

selectedModels = FBModelList()

topModel = None # Search all models, not just a particular branch
selectionState = True # Return models that are selected, not deselected
sortBySelectOrder = True # The last model in the list was selected most recently
FBGetSelectedModels(selectedModels, topModel, selectionState, sortBySelectOrder)

for model in selectedModels:
    print model, model.LongName

A utility function to return the last model that the user selected

def GetLastSelectedModel():
    '''
    Returns the most recently selected model in the scene, or
    None if no models are selected.
    '''
    selectedModels = FBModelList()
    FBGetSelectedModels(selectedModels, None, True, True)
    return selectedModels[-1] if selectedModels else None

Clear the selection by deselecting all components in the scene

for comp in FBSystem().Scene.Components:
    comp.Selected = False

Iterate over all the cameras in the scene

for camera in FBSystem().Scene.Cameras:
    print camera.LongName, camera.SystemCamera

Iterate over all top-level models

for model in FBSystem().Scene.RootModel.Children:
    print '%s <%s>' % (model.LongName, model.__class__.__name__)
    assert model.Parent == None # Top-level models have no Parent

Select a model and all of its descendants (recursively)

def SelectBranch(topModel):
    '''
    Selects the given model and all of its descendants. Note that this
    function does not clear the current selection -- that's the caller's
    responsibility, if desired.
    '''
    for childModel in topModel.Children:
        SelectBranch(childModel)

    topModel.Selected = True

Delete a model and all of its descendants (recursively)

def DeleteBranch(topModel):
    '''
    Deletes the given model and all of its descendants.
    '''
    while len(topModel.Children) > 0:
        DeleteBranch(topModel.Children[-1])

    topModel.FBDelete()

Notes & Errata

Deprecated Functions

You may have noticed that I failed to mention the FBFindModelByName function, which searches for models by short name only. This function is deprecated as of MotionBuilder 2012. You should use FBFindModelByLabelName instead, as it's faster and it avoids ambiguity.

Additionally, FBFindObjectsByNamespace is deprecated in MotionBuilder 2013. It's best to use FBFindObjectsByName instead, as its functions more-or-less identically with the proper arguments.


Group Names

You won't typically need to use FBFindObjectByFullName, which is the only public function that requires you to deal directly with group names. Still, the set of group names doesn't appear to be publicly documented, so here's an incomplete list:

Group NameAssociated Navigator CategoriesBase Class
ModelScene, Cameras, LightsFBModel
ConstraintConstraints, Actors, CharactersFBConstraint
MarkerSetMarker SetsFBMarkerSet
AudioAudioFBAudioClip
DeviceDevicesFBDevice
MaterialMaterialsFBMaterial
PosePosesFBPose
ShaderShadersFBShader
TakeTakesFBTake
TextureTexturesFBTexture
VideoVideosFBVideo


Component Idiosyncrasies

A few types of components have some quirks that you should watch out for. Some of these are minor points that relate to nomenclature or inheritance. For example:

More dangerously, some of the lists in FBScene contain special values alongside ordinary components. Some of these objects can crash MotionBuilder if you try to delete them, or they're simply undeletable, which might get you trapped in a while loop if you try to delete them all. To give a few examples:

These objects should have their deletable flag unset, if you feel like checking it. Here's how you might use it:

# The default material can't be deleted, so we'll be stuck
# in an infinite loop and crash if we try to delete everything.
while len(FBSystem().Scene.Materials) > 0:
    FBSystem().Scene.Materials[0].FBDelete()

# Here's a quick fix that works in the case of materials.
while len(FBSystem().Scene.Materials) > 1:
    FBSystem().Scene.Materials[1].FBDelete()

# But more generally, we could check the each object's flags.
i = 0
while i < len(FBSystem().Scene.Materials):
    
    material = FBSystem().Scene.Materials[i]
    
    if material.GetObjectFlags() & FBObjectFlag.kFBFlagDeletable:
        material.FBDelete()
    else:
        i += 1

Corrections?

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