Curiosity Rigged the Cat, Part IV

A few weeks ago I “finished” the MechaCat rig for the first time. It was an entirely manual build process. Any automation was done with hacky, one-off scripts. Detaching the guides from the rig was a destructive, permanent process. Connections between components had to be done by selecting two transforms and running one of the aforementioned scripts. This would be fine if I had to build the rig once, if I built it right the first time, and if I did not have to make changes.


In order to make the rigging workflow less “hand crafted” and more “assembly line”, I needed to write some tools. Maya offers six different options for writing tools – MEL, Python, PyMEL, and the three flavors of the Maya API.

Let’s review our choices.

MEL is a scripting language that is useful for saving a scene in plain text, and nothing else.

Python is an interpreted, dynamically typed programming language widely used in the animation industry, with a fairly clean syntax. The maya.cmds Python module is, unfortunately, just a wrapper around MEL.

PyMEL is an object oriented wrapper around the Maya API. I don’t use it; to me it feels like overkill, and takes too long to import.

The Maya API is an interface to internal core of Maya. The C++ API pre-dates the C++ standard library, so it is like a Volvo from the 1980’s; it runs. Soon after Python was added to Maya, the Maya Python API was released. Unfortunately, it is a straight port of the C++ API, which means it can be awkward to use.

In Maya 2012, the Maya Python API 2.0 was released, which aimed to be more “Pythonic” and allow you to write code that looked like more like Python, rather than Python trying to cosplay as C++. At time of writing it is not feature complete, but you can work around most of the missing classes.

I chose to do my tooling in the Maya Python API 2.0 for three reasons. First, I haven’t done much with it so it was an opportunity to learn. Second, it allowed for faster development. When writing a C++ plugin, you have to re-compile your plugin after every change. On Windows, you have to close Maya to recompile. (EDIT: I have since been told that you can recompile on Windows, but first you have to flush the undo queue, open a new scene, and unload the plugin). When writing a Python plugin, you only have to flush the undo queue and unload the plugin. And third, I get tired of writing CHECK_MSTATUS_AND_RETURN_IT(status) every few lines.

One gotcha with writing your own MPxCommand in the Maya Python API 2.0 – you need to have maya_useNewAPI in your code so Maya knows which version of the API you’re using. While this requirement is documented, it’s easy to miss.

from maya.api import OpenMaya

def maya_useNewAPI():
    """When writing a plugin which uses the new API,
    the plugin must define a function called maya_useNewAPI
    so that Maya will know to pass it objects from the new
    API rather than the old one.

Some people like to alias their modules when they import them, like
import maya.cmds as mc or import maya.api.OpenMaya as OM2. You can do that if you wish (assuming your supervisor condones it).

The boilerplate surrounding a Maya Python API 2.0 MPxCommand is the same as in C++, just without curly braces or type declarations.

The advantages of writing a custom MPxCommand is that they are almost always faster (especially in C++) and add only one action to the undo queue. The main disadvantage is that you have to implement the undo behavior.

I wrote a dozen commands for working with rig components. One to rename a component (and all of its nodes), five for working with guides (attach, detach, load, save, and mirror), and three each for working with inputs/outputs. This post will focus on the non I/O guides command, since the I/O commands aren’t as interesting.

In part three I talked about my three multi-layered guide setup. In review, a duplicate of the input hierarchy was constrained to the guides, and each guided input was driven by direct connections from its duplicate. This meant that, while manually building the rig, I could detach the guides by deleting the duplicate hierarchy.

A non-destructive workflow would require a little more finesse.

First, I needed a way for a component to inform the commands what their guided inputs were. This is were consistent naming conventions came in. As a rule, every transform under the input root with an srfOffset suffix is a guided input.

Next, I needed a way to tell what attributes were guided. As a rule, an attribute had to be guided by another attribute of the same name. To that end, I created a compound attribute called guideData on each input, with a multi-index message child attribute named guide_attrs. Each input’s duplicate’s guided attributes were connected to this attribute.

Thus, the Attach Guides and Detach Guides commands could iterate of the hierarchy below input and treat any node with a guideData attribute as a guided input. The command then becomes a simple loop that dis/connects attributes.

The Mirror Guides command is also simple. As a rule, all transforms under the guides root are guides. At this time, only the translate attributes of a guide are used. Controlling the orientation of a guided input is done with an aimConstraint, with a guide as the aim target and a guide as the up object. For simplicity, the aim vector is locked to positive X, and the up vector is locked to positive Y.

Thus, mirroring a component’s guides is done getting the position of a guide, mirroring it across the specified axis, and setting it on the opposite guide. Mirroring positive can be achieved by negating the X, Y, or Z component of a point, or by multiplying a point by an identity matrix with a negative axis vector.

However, not all guides are position handles. Guides that control the orientation of a control need to mirror the control’s orientation in the same way that the Mirror Joints tool works. This can be achieved by negating two or the three axes, or multiplying a point by an identity matrix with two negative axis vectors.

Finally, in the piston component, there is a guide that controls the internal aim vector of the piston. As a direction vector, it needs to be fully negated, either by negating the X, Y, and Z component, or by multiplying it by an identity matrix will three negative axis vectors.

Faced with three different behaviors when mirroring guides, I created a rule wherein all guides had a guide_type attribute that indicated if it was a position, orientation, or direction guide. Since position guides are the most common, I use it as the default and exempt those guides from having to have a tag.

Thus, the guides tell the Mirror Guides command how they should be mirrored, and the command’s -ma/-mirrorAcross flag argument controls across which axis the guides will be mirrored. The command then becomes a simple loop that gets a guide’s position, mirrors it, and sets it in the mirrored guide, which may be the same guide.

To quote the last line of every job in the Haynes Volvo repair manual, “installation is the reverse of removal.” Implementing undo for these commands required recording the change, and doing it backwards. For the Attach/Detach Guides command, this was pairs of attributes. For the Mirror Guides command, this was guide/position tuples.

After I was done tooling, I was able to write a short build script that would load all the components in the rig, rename them, reload the component to component connections, attach the guides, reload the guide positions, detach the guides, and reload the component to geometry connections. In under ten seconds, I could go from an empty scene to a fully rigged character.

With a little more tooling, I could conceivably write a script to update a module. Suppose an error in the leg component was fixed, and it need to be applied to the rig. Rather than replicate the fix four times, you could run a batch process that would open the rig, save the guides and input/output connections, disconnect the component, remove it, load the new one, and reconnect it.

This tooling, while extensive, is not production ready. A production ready workflow would require a tool for creating the guideData attribute(s) that wasn’t a hacky, one-off script. It would also require scripts to validate a component’s boundaries and enforce the rules. But, this tooling is for a personal project, and doesn’t need to be made artist proof.

You can view the commands I wrote on my GitHub page for the Mecha Cat project.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s