Skip to content

Custom attach


The Record Simulation command consists of 4 steps.

# Step Description
1 Extract Extract Simulation onto a new joint hierarchy
2 Attach Constrain rig controls to joint hierarchy
3 Bake Call cmds.bakeResults
4 Cleanup Delete joint hierarchy and constraints

The final result is keyframes on the rig controls, in a new Animation Layer.

Version 2.0 - Up to date

Written for Ragdoll 2022.05.04 and above.

Manual Record

Here's what it looks like to do manually, for 1 control.

Repeat the Attach step for each control, and take locked channels into account, and you've entirely replicated what Record Simulation does.

Custom Attach

The Attach step can be overridden to accommodate for custom constraints, for when your studio rigs cannot use the native cmds.parentConstraint and cmds.orientConstraint nodes that ship with Maya.

Here's how it works.

# 1 - Import the Record Simulation command
from ragdoll import recording

# 2 - Write your own command to attach
def custom_attach(...):

# 3 - Replace the default attach
recording._Recorder._attach = custom_attach

Here's an example of the default implementation, pay special attention to the calls to cmds.parentConstraint and cmds.orientConstraint; these are the ones you want to replace with your own commands.

from ragdoll.vendor import cmdx
from ragdoll import recording

def custom_attach(self, marker_to_dagnode):
    """Constrain rig controls to simulation

        marker_to_dagnode (dict): Dictionary with marker: dagnode, the dagnode
            is the animation control, e.g. L_hand_ctl

        new_constraints (list): Newly created constraints as cmdx.Node instances


    # Constraints created by this function
    # This list is used to delete the constraints after recording is complete
    new_constraints = []

    # Attach animation controls at the start time,
    # that's where the simulation and animation overlap
    initial_time = cmdx.current_time()

    for dst, marker in self._dst_to_marker.items():
        src = marker_to_dagnode.get(marker, None)

        if not src:

        # dst == your rig control
        # src == the extracted transform

        skip_rotate = set()
        skip_translate = set()

        for chan, plug in zip("xyz", dst["rotate"]):
            if plug.locked:

        for chan, plug in zip("xyz", dst["translate"]):
            if plug.locked:

        if skip_translate != {"x", "y", "z"}:

            # REPLACE ME
            pcon = cmds.parentConstraint(
                skipTranslate=list(skip_translate) or "none",

            # Store reference to this node
            pcon = cmdx.encode(pcon[0])

        if skip_rotate != {"x", "y", "z"}:

            # REPLACE ME
            ocon = cmds.orientConstraint(
                skip=list(skip_rotate) or "none",

            ocon = cmdx.encode(ocon[0])


    return new_constraints

# Store original attach, just in case
except NameError:
    old_attach = recording._Recorder._attach

# Replace with custom implementation
recording._Recorder._attach = custom_attach


Copy/paste the above snippet into your Script Editor and call Record Simulation. Nothing should have changed, because the above is a near-exact copy of the original.

From here, start editing the snippet. Keep executing the script in your Script Editor as you make changes, and call Record Simulation from the Ragdoll menu to try it out.

To restore the original implementation, either use old_attach or reload the plug-in.


The src variable is the extracted simulation, the joint. The dst variable is your rig control, what you want to constrain to src. The skip_translate and skip_rotate variables are two sets containing the channels on your rig controls were locked (if any). Use these if your constraint commands needs them (like cmds.parentConstraint does).

Default Constraint Commands

The default implementation uses cmds.parentConstraint for the Translate channels, and cmds.orientConstraint for the Rotate channels to accommodate for when one or more Rotate channels are locked. As it happens, the cmds.parentConstraint produces gimbal locks in situations like that.

You may use a single constraint command for all channels, if available, to try and see whether those also run into gimbal issues - such as randomly flipping 180 degrees. If they do, consider using a different constraint for the rotate channel.

Return Value

The command MUST return new_constraint which MUST be of list type and contain any and all nodes created by this function. In the default implementation, we call on maya.cmds to create the constraint, but convert the resulting string types to cmdx.Node via cmdx.encode(). You MUST do the same.


See the original command, along with available members of self on GitHub.

Last update: 2022-04-04