Scripting Your Rig: Creating a Three-Joint Chain, Part 1

In this series, we’re going to be scripting out the components for a character rig using Python. While the end result will be structured for a biped, we’re going to write these scripts in a generic way so that they can be used in various creatures. For this post, let’s take a look at a “Three-Joint Chain” setup which we could implement for an arm or a leg.

Creating The File

Before setting up a system like this, it’s generally a good idea to define some variables that you’ll reuse. I’m going to make this setup an for an arm and start my python file as such:

from maya import cmds, mel
import maya.api.OpenMaya as om


class Arm():
    """
    This will create an arm setup.
    """

    def __init__(self):

        self.PART = "Arm"
        self.JNTNAMES = ['Arm', 'Elbow', 'Wrist', 'Twist']
        self.xyz = [[3, 18, 0],[3, 14, -0.25],[3, 10, 0]]

It’s important to note a few things here. On the import, we’re going to be using the Maya Python API. This won’t be scary, I promise! We just need to grab some vectors for polevectors (get it?) and the API is much easier to work with for that particular task. We’re also defining a few variables up front to help with naming consistency later on. Our “xyz” variable is going to determine the default position for now.

Creating The Locators

Let’s setup some locators to use as a base point. You can honestly use anything you want (nulls, joints, primitives) and we’ll be enhancing this later on. For now, a simple spaceLocator will work.

def placers(self):
    """
    This will create locators for positioning.
    """

    # Create Locators
    self.FirstLoc = cmds.spaceLocator(n=self.JNTNAMES[0])
    cmds.xform(self.FirstLoc, ws=1, t=self.xyz[0])
    self.SecondLoc = cmds.spaceLocator(n=self.JNTNAMES[1])
    cmds.xform(self.SecondLoc, ws=1, t=self.xyz[1])
    self.ThirdLoc = cmds.spaceLocator(n=self.JNTNAMES[2])
    cmds.xform(self.ThirdLoc, ws=1, t=self.xyz[2])

Our function starts off crafting three locators on a single plane, something that will haunt your ikSolver later on if offset. Using those handy pre-defined variables, position and naming are taken care of for us. Given that this is an arm, and we need to keep that single plane, let’s scale down these massive locators and parent them together:

# Scale for ease of visibility
for segment in [self.FirstLoc, self.SecondLoc, self.ThirdLoc]:
    cmds.setAttr(segment[0] + ".localScaleX", .2)
    cmds.setAttr(segment[0] + ".localScaleY", .2)
    cmds.setAttr(segment[0] + ".localScaleZ", .2)

# Parent to maintain angle and move easier
cmds.parent(self.ThirdLoc, self.SecondLoc)
cmds.parent(self.SecondLoc, self.FirstLoc)

Now we have locators to properly move around… and that kinda sucks. We’re crafting bones, so it would be nice to visualize them! I want to explain that we could have used joints in the beginning instead of locators. I’m choosing locators because I want to keep my “types” separated. My setup uses locators, which are easy to find and remove by type, and the joints generated later on won’t get confusing. It’s a personal preference, so feel free to modify this to your own needs and preferences.

# Create pseudo-bones to make it easier to visualize
cmds.select(d=1)
tempA = cmds.joint(n="%s_Temp"%self.JNTNAMES[0])

tempC = cmds.joint(n="%s_Temp"%self.JNTNAMES[1])
tempD = cmds.joint(n="%s_Temp"%self.JNTNAMES[2])
cmds.setAttr(tempA + ".overrideEnabled", 1)
cmds.setAttr(tempA + ".overrideDisplayType", 1)

cmds.setAttr(tempC + ".overrideEnabled", 1)
cmds.setAttr(tempC + ".overrideDisplayType", 1)

cmds.parentConstraint(self.FirstLoc, tempA)
cmds.parentConstraint(self.SecondLoc, tempC)
cmds.parentConstraint(self.ThirdLoc, tempD)

cmds.parent(tempA, self.FirstLoc)

self.locatorList = [self.FirstLoc, self.SecondLoc, self.ThirdLoc]
self.locatorJointsList = [tempA, tempC, tempD]
cmds.select(d=1)
return self.locatorList, self.locatorJointsList

Let’s tear down this code. We’re creating some temporary joints, enabling overrides so they are just displayable, using contraints to put them into the correct location,  and parenting these temporary bones to our top Locator. We are making a list of the locators and joints as well, so we can return them for use later on. Nothing super fancy here, but now we’ve got out locator setup ready with pseudo-bones. If you put it all together, our final code should look like this:


from maya import cmds
import maya.api.OpenMaya as om

class Arm():
    """
    This will create an arm setup.
    """

    def __init__(self):

        self.PART = "Arm"
        self.JNTNAMES = ['Arm', 'Elbow', 'Wrist', 'Twist']
        self.xyz = [[3, 18, 0],[3, 14, -0.25],[3, 10, 0]]

    def placers(self):
        """
        This will create locators for positioning.
        """

        # Create Locators
        self.FirstLoc = cmds.spaceLocator(n=self.JNTNAMES[0])
        cmds.xform(self.FirstLoc, ws=1, t=self.xyz[0])
        self.SecondLoc = cmds.spaceLocator(n=self.JNTNAMES[1])
        cmds.xform(self.SecondLoc, ws=1, t=self.xyz[1])
        self.ThirdLoc = cmds.spaceLocator(n=self.JNTNAMES[2])
        cmds.xform(self.ThirdLoc, ws=1, t=self.xyz[2])

        # Scale for ease of visibility
        for segment in [self.FirstLoc, self.SecondLoc, self.ThirdLoc]:
            cmds.setAttr(segment[0] + ".localScaleX", .2)
            cmds.setAttr(segment[0] + ".localScaleY", .2)
            cmds.setAttr(segment[0] + ".localScaleZ", .2)

        # Parent to maintain angle and move easier
        cmds.parent(self.ThirdLoc, self.SecondLoc)
        cmds.parent(self.SecondLoc, self.FirstLoc)

        # Create pseudo-bones to make it easier to visualize
        cmds.select(d=1)
        tempA = cmds.joint(n="%s_Temp"%self.JNTNAMES[0])

        tempC = cmds.joint(n="%s_Temp"%self.JNTNAMES[1])
        tempD = cmds.joint(n="%s_Temp"%self.JNTNAMES[2])
        cmds.setAttr(tempA + ".overrideEnabled", 1)
        cmds.setAttr(tempA + ".overrideDisplayType", 1)

        cmds.setAttr(tempC + ".overrideEnabled", 1)
        cmds.setAttr(tempC + ".overrideDisplayType", 1)

        cmds.parentConstraint(self.FirstLoc, tempA)
        cmds.parentConstraint(self.SecondLoc, tempC)
        cmds.parentConstraint(self.ThirdLoc, tempD)

        cmds.parent(tempA, self.FirstLoc)

        self.locatorList = [self.FirstLoc, self.SecondLoc, self.ThirdLoc]
        self.locatorJointsList = [tempA, tempC, tempD]
        cmds.select(d=1)
        return self.locatorList, self.locatorJointsList

To run it, instantiate the class, then run the function like this:


myNewArm = Arm()

myNewArm.placers()

Now move around and resize that as needed, then we can pick up on creating the joints in the next post!

One thought on “Scripting Your Rig: Creating a Three-Joint Chain, Part 1

Leave a Reply

Your email address will not be published. Required fields are marked *