Joint Chains

This topic builds on the concepts presented in Rigid Body Physics and uses a Rigid Body Physical Property in conjunction with an instance of Joint Physical Property.

Creating Joints

To create a joint in the scene, we first require at least three models, as depicted in the image below. The parent and child models are used to identify the extremities of a joint. The null model in the middle represents the point around which the models will rotate. The parent and child models are typically connected to a Rigid Body Physical Property to allow them to react to physical forces and collisions (see Rigid Body Physics).

The Joint Physical Property object is used to constrain these models to act as a physical joint. An instance of Joint Physical Property is created via FBCreateObject() using the Asset Browser path 'Browsing/Templates/Physical Properties' and the 'Joint' entry name. A null model is first connected to the Joint Physical Property via FBConnect() to define a new point of rotation. This generates an FBComponent within the Joint Physical Property whose name corresponds to '<null_model_name> Joint'. The parent and child models are thereafter connected to this new FBComponent's 'Connections' property via FBConnect(). This process is outlined in the following code sample, taken from the example below.

# Create the necessary relationships within a Joint Physical Property object.
def createJoints(pNullDict):
    # The 'joint' object maintains the physical property relations between model pairs and
    # their respective points of rotation.
    joint = FBCreateObject( 'Browsing/Templates/Physical Properties', 'Joint', 'Joint' )

    # Bind each '(parent, child)' key and its associated 'nullModel' to the joint object.
    for (parent, child), nullModel in pNullDict.iteritems():

        # Connect the joint to the nullModel to define the point of rotation for this (parent, child) pair
        FBConnect(joint, nullModel)        

        # Connect the parent and child models to the joint to have them use the nullModel
        # as a point of rotation.
        for component in joint.Components:
            connectionProp = None
            if component.Name == nullModel.Name + ' Joint':
                connectionProp = component.PropertyList.Find('Connections')
                if connectionProp != None:
                    # (!!!) Note: The order of the parameters in FBConnect() is 
                    #       important!
                    FBConnect(parent, connectionProp)
                    FBConnect(child, connectionProp)
Note: A single Joint Physical Property object can be used to create a chain of joints.

Example: Rooted Joint Chain with Inverted Gravity

Sample Viewport Output:

Program Summary: The program below begins by creating a chain of cube primitives, arranged in a sequential parent-child hierarchy. The base cube of this chain is passively rooted on the ground plane and every other cube is assigned an active Rigid Body Physical Property.

An instance of FBModelNull is inserted between each parent-child pair in the cube chain to identify each joint's point of rotation. A single Joint Physical Property object is then used to constrain the chain's movement. Lastly, the Physics Solver's 'Gravity' property is set to (0, 9.81, 0) to invert the force of gravity in the scene.

from pyfbsdk import *

# This tutorial loosely follows the steps outlined in the MotionBuilder User Guide topic:
# "User Guide > Animating with Physical Properties > Joint Physical property > Creating a Joint chain reaction"

# Global variables used to create the scene.
numModels = 7
modelSpacing = FBVector3d(0, 21, 0)
chainStart = FBVector3d(-50, 0, 0)
gravity = FBVector3d(0, 9.81, 0)


###############################################################
# Helper Function(s).                                         #
###############################################################

# Create a primitive from the asset browser.
def createModel(pIndex, pTranslation):    
    #model = FBCreateObject( 'Browsing/Templates/Elements/Primitives', 'polySphere', 'polySphere' )
    model = FBCreateObject( 'Browsing/Templates/Elements/Primitives', 'Cube', 'Cube' )
    model.Name = 'myModel' + str(pIndex)
    model.Translation = pTranslation
    model.Scaling = FBVector3d(1, 1, 1)
    model.Show = True

    return model


# Create a null model at the specific position. A null model is used 
# to identify the rotation point between two models to form a joint.
def createNull(pIndex, pTranslation):
    nullModel = FBModelNull('myNull' + str(pIndex))
    nullModel.Translation = pTranslation
    nullModel.Show = True

    return nullModel


# Create a chain of models distanced 'pModelSpacing' apart starting at 
# the position 'pChainStart'.
def createModelChain(pChainStart, pNumModels, pModelSpacing):
    models = []
    for i in range(0, pNumModels):
        modelOffset = pModelSpacing * i
        modelPosition = pChainStart + modelOffset        
        newModel = createModel(i, modelPosition)
        models.append(newModel)

    return models


# Create a null between each model in the chain.
def createNullsFromModels(pModels):
    # The 'nulls' dictionary maps a pair of models (parent, child) to a
    # null model. This null model denotes the point of rotation between the parent/child models.
    # The resulting dictionary will look like this for 7 models:
    #
    # nulls = {
    #     (myModel0, myModel1) : myNull1,
    #     (myModel1, myModel2) : myNull2,
    #          ...
    #     (myModel5, myModel6) : myNull6
    # }
    #
    nulls = {}

    for i in range(1, len(pModels)):
        parent = pModels[i-1]
        child = pModels[i]

        # Create the null at the halfway point between two models.
        position0 = parent.Translation.Data
        position1 = child.Translation.Data
        nullPosition = position0 + (position1 - position0) / 2

        # Create the null and associate it to the (parent, child) pair
        # in the 'nulls' dictionary.
        nullModel = createNull(i, nullPosition)
        nulls[(parent, child)] = nullModel

    return nulls


# Apply a Rigid Body physical property to the models in the list.
# The pActivation parameter defines the Activation value.
# 0: Active | 1: Active at Collision | 2: Passive
def applyRigidBody(pModelList, pActivation):
    # Create the rigid body object from the Asset Browser.
    rigidBody = FBCreateObject( 'Browsing/Templates/Physical Properties', 'Rigid Body', 'Rigid Body' )

    # Set the Activation property.
    activationProp = rigidBody.PropertyList.Find('Activation')
    activationProp.Data = pActivation

    # If we are using Active activation, set the Correct Mass Center to false.
    if(pActivation == 0) :    
        correctMassCenter = rigidBody.PropertyList.Find('Correct Mass Center')
        correctMassCenter.Data = False

    # Connect the rigid body to the model.
    for model in pModelList:
        FBConnect(rigidBody, model)


# Create the necessary relationships within a Joint Physical Property object.
def createJoints(pNullDict):
    # The 'joint' object maintains the physical property relations between model pairs and
    # their respective points of rotation.
    joint = FBCreateObject( 'Browsing/Templates/Physical Properties', 'Joint', 'Joint' )

    # Bind each '(parent, child)' key and its associated 'nullModel' to the joint object.
    for (parent, child), nullModel in pNullDict.iteritems():

        # Connect the joint to the nullModel to define the point of rotation for this (parent, child) pair
        FBConnect(joint, nullModel)        

        # Connect the parent and child models to the joint to have them use the nullModel
        # as a point of rotation.
        for component in joint.Components:
            connectionProp = None
            if component.Name == nullModel.Name + ' Joint':
                connectionProp = component.PropertyList.Find('Connections')
                if connectionProp != None:
                    # (!!!) Note: The order of the parameters in FBConnect() is 
                    #       important!
                    FBConnect(parent, connectionProp)
                    FBConnect(child, connectionProp)


# Create a physics solver.
def createPhysicsSolver(pGravity):
    solver = FBCreateObject( 'Browsing/Templates/Solvers', 'Physics Solver', 'Physics Solver' )

    # Set the gravity property.
    solver.PropertyList.Find('Gravity').Data = pGravity

    # Activate the Physics solver.
    solver.Active = True
    solver.Live = True


###############################################################
# Main.                                                       #
###############################################################
# Clear the scene.
FBApplication().FileNew()

# Create a chain of models.
models = createModelChain(chainStart, numModels, modelSpacing)
applyRigidBody([models[0]], 2) # Rigid Body - Passive Activation (2).
applyRigidBody(models[1:], 0)  # Rigid Body - Active Activation (0).

# Create nulls between each model.
nullDict = createNullsFromModels(models)

# Create the joints between the models and the nulls.
createJoints(nullDict)

# Create and activate a physics solver in the scene.
createPhysicsSolver(gravity)

# Move the base to make the chain wiggle.
models[0].Translation.Data += FBVector3d(50, 0, 0)