this is your rig on sugarcubes

Posts Tagged rigging

A friend has been pushing me to write about how my Maya autorig works, and my thought process on how to write modular autorigs as a whole. Been putting it off for far too long, so here we go!

Problems with Autorigs

I look at modules in an autorigs as simple, replicable pieces that serve a specific purpose without overlapping feature-wise with another module. They’re Lego pieces, and when connected together you get something predictable that can be rebuilt and repurposed across multiple characters.

However, most autorigs I’ve seen or used split their workflow into three stages:

The Guide (using Gear parlance): usually a simpler rig setup that allows the user to position the rig without worrying about joint orientation, joint counts, or how modules might need to be connected under the hood to expose certain features.

The Bind Joints: generated by the guide, these are used for skinning the character.

The Rig: also generated from the guide, these are the controls that move the bind joints.

Usually the rig and bind joints are generated at the same time from the guide. This leads you to the first problem that I’ve tried to solve: when you have to modify the rig in any way, you end up having to delete the rig and the bind joints, modify the guide, regenerate the entire rig from scratch, and then reload the skin weights. We had a character at work two years back that was hella complex, and when the client asked for changes the build alone took a half hour, with weight re-loading taking even longer.

Having to regenerate the rig whenever changes are made at the guide stage is also, in my opinion, quite limiting. You can’t fix a single module in rigs that work like this, and you can’t do iterations as quickly when you’re running through every module’s build and reattaching skins to change a control type.

My Process

I wanted to overcome these specific limitations, so my goal initially was to be able to build, unbuild, rebuild, or upgrade any module in the rig without affecting other modules, within reason (which is a caveat we’ll get to). I also wanted to not delete bind joints unless it was absolutely necessary, so that rig development could continue with skinned characters without having to reload skins every five minutes.

To do this, one thing I’ve done in my system is to remove guides. Instead, modules are built off of the bind joints, each module receiving its own chain. None of the chains are parented to each other, but are instead constrained to each other after module build. Also, as much as possible I try to set up parenting relationships that have control spaces / follows driven by bind joints in other modules. These are special message connections.

Unbuilding, or removing a single module, requires that the module and all nodes related to the rig are deleted from the scene, and the original chain is reset to its default position. This means that a mechanism is needed for saving and restoring those default positions, as well as for keeping track of all nodes added to the scene by a module’s build.

Because constraints between modules are largely between bind joints, one nice feature of the system is that unbuilding a module and resetting its joints to their default position leaves the rigs of other modules in tact. You can change a IK Limb into an FK chain, for example, and modules that rely on its bind joints will follow the new setup without needing to be rebuilt.

Tagging and Upgrading

To identify joint chains in the scene which can be turned into rigs, the system can be passed a selection of joints or can search through all joints; in both cases, the roots of the chains are tagged with Params, which are special user attributes applied before the build. Params could be things like the token used to identify the module and name controls uniquely, control scale, number of controls (for things like Spine rigs), and so on. Params don’t get removed during normal usage, so settings used to build a module persist in the Maya file on the root joints and don’t require a separate offline file. A template for a rig can be just the bind joints and their connections, saved as a .ma file.

A benefit of this is that when the code for the module changes and Params are added or removed, they can be upgraded on joint roots easily. I’ve used this trick to update parts of older rigs while maintaining animation compatibility in scenes with references.

Controllers and Orientation

Controllers pull their setup information from the Params as well. This includes type, size, color, and orientation. As all bones in the system are oriented X-down, Y-up (to provide a predictable set of inputs as well as to match Maya’s defaults), orientations allow the controllers to be turned in their default position with axes oriented in a different manner. More simply, if the animators want fingers to be X down and Z up, or Z down and -Y up, this preference can be set in Params on the root joint of the chain and will take effect at build time. Different control types and their Parameters are registered in the __init__() of a rig module.

Postbuilds and Seaming

One of the hardest things to reconcile when making modules work together is when dependencies from one module must exist before another can be built. An example: a reverse-foot setup requires that the leg module it’s going to drive and be driven by (depending on whether the combination is in IK or FK mode) exists prior to connection.

I solved this by having builds run in stages: build, postbuild, and seam.

The build is what you expect: it sets up the control structure for the module, constraining the bind hierarchy to it and returning the module group. If you’ve ever scripted a rig setup you’ll find the build() function of a module easy to follow.

Postbuild happens as the name suggests: after the build. In fact, the postbuild functions of the modules are run after all module build() functions have returned. This guarantees that the pre-conditions that might be required for the postbuild() will be complete without having to explicitly track the order in which modules are built. That aforementioned Foot module, for example, can depend on the Leg it’s attached to existing by the time the master build function runs its postbuild(), and it can use that knowledge to ask the built Leg module for controllers and other information.

Seaming is the constraining together of modules. Each module has one or more targets for constraints. The root input moves the entire rig, so you can be sure that if it is constrained your module’s controls will move (and scale) properly with it. There might also be an IK goal input that gets constrained to another module to move the IK goal: the tip of a limb, or a Spine.

My previous design had seaming as a function outside the module that’s run by the master build process, but I’ve moved it into the modules themselves so that custom seaming overrides can be created. Each module also handles unseaming, or the removal of input constraints and the resetting of the input objects to their original positions. Also, with seaming moving into its own module-level function, while I may be keeping the postbuild() method in the base class I’m not sure currently how much it will be needed moving forwards.

Try It Out For Yourself

In writing this I’ve also gone and written an entirely new system from the ground up in an effort to showcase my thoughts in working code. It requires Maya 2014 or greater and is Python-only. The first version only has two modules and no UI, but there are example files to test the builds. If there’s enough interest I’ll fill out the module library a bit more and possibly throw a UI on it. I’ll update this post soon with a link to the Github repo.

I’m constantly attempting new setups in rigging. Usually this is out of necessity, such as when current scripted setups don’t behave reliably or deliver the desired result with new models. Other attempts are me exploring things about my setups with which I’m not one hundred percent satisfied. I’ll keep doing research until I find a setup that works as I feel it should in the majority of situations, stably and predictably.

Stretchy Spline IK is something I’ve never liked because of the way its chains overshoot the end of the curve when the spline’s curvature is too great. I’ve never worked in a studio that had its own custom Spline IK solver, where this problem is non-existent, so I research solutions whenever I hit the issues that stretchy splines always bring.

Recently I had a thought about using live sub-curves of a spline (using the Maya subCurve node). I’ve always figured that the overshoots are due to floating point rounding and the fact that measuring a nurbs curve is an inexact science; curves are sub-samples a few thousand times, and the distances between points are summed.

(Before you ask, I use splines because they’re easy and light when you need to be able to lock a chain to a minimum or maximum length, but still allow stretching. I have not found a fast way of doing the same using a nurbs ribbon.)

I wrote a custom node to extract sub-curves by length along the original curve (using MFnNurbsCurve::findParamFromLength), then attached joints to each subCurve with Spline IK. I figured that sub-curves made by length would lock each joint in place, and that somehow this would work around the overshoot issue.

Boy was I wrong.

Turns out the assumption I’ve had for a while– the same assumption I’ve heard from other riggers– was itself incorrect. It’s not that the lengths aren’t being measured accurately enough, but that as the spline curves in on itself, the effective lengths of bones on it should shrink because the bones can’t bend to match. Obvious in hindsight, but it caught me by surprise. It’s like Manhattan Distance: in Euclidean space you may only be two kilometers from that pizzeria you love, but you end up walking three kilometers to get there because Euclidean distance doesn’t take into account the fact that we can’t pass through buildings. (Or: that sewers are winding and not always as easy to traverse as the city streets for your average turtle.)

On the up side I can think of a few good uses for the sub-curve node I made, so the experiment wasn’t a total loss. I also have a few ideas of how to use the curvature of the driving spline to come up with a scale value, meaning I have new experiments to carry out.

The test proved to me again that it never hurts to pull apart an established method in an attempt to do it better. “White belt mind,” a teacher of mine used to say– try to never lose that initial state we all have when we begin something new and are constantly trying to learn.

The second half of the work I did was to automate the process of moving data around between Motion Builder and Maya, and to make tools for the animators that lightened their loads when it came to exporting and retargeting animations. I was also responsible for a batch conversion pipeline.

On the project there were animations in Motion Builder FBX files that were intended for reuse, which meant loading and retargeting multiple animations across multiple characters. This is a case (if only minimal edits are required) where the HIK system in Maya can be used to blast through conversions and free up animators for other work. Also, as many of the original animations were in single FBX files as Takes and the new decision was to have one file per animation (allowing multiple animators to work on the same character without file collisions in the version repository), the multi-take Motion Builder FBX files would need to be split in batch.

The Maya Human IK system as of Maya 2012 is usable and has a lot of good benefits. Much of what they say works out of the box really does, provided you stick within the system: the ability to retarget animations between characters of differing sizes and joint placements works very well, and the fidelity of those animations stays high. If you rename or reorient bones but still have a Characterization in place for both characters, the transfer will still work out. However, there were also significant downsides:

The Motion Builder to Maya one-click scene send did not work as expected 100% of the time. When transferring from Motion Builder to Maya, I often found that while the Characterization would transfer over the rig itself would not be properly connected, and many times did not even come in at the right position in the Maya scene. Baking the keys down to the skeleton, transferring, and regenerating the rig on the Maya side does work. You lose the editability of having less keys, but you get a one-to-one transfer between the two programs this way and the Characterization makes rebuilding and rebaking the keys to the rig a one-click process.

On the Maya side you lose a lot of the features you’d expect. For example, the animators complained about not having foot roll controls. Regular Maya constraints don’t behave the same way you’d expect, and adding onto an HIK rig can be trickier than building on top of a regular Maya rig. The strangest thing was that you can’t zero controls. If you want to return to the “stance” pose, you have to change the input to the skeleton, then key the rig at the frame you want to have zero’d, and finally go back to having the rig control the skeleton. Editing curves on the HIK rig can be frustrating, as both the FK and IK objects are used to control the final position of joints and the different Human IK modes for posing and interaction pull at the body parts in different ways; often animators were baffled about which controls were causing jitters or other issues, and it was usually a control for a body part much higher up the hierarchy. Lastly, the HIK controls and skeleton don’t have the same orientations as the bones they’re based upon. If you’ve set up your skeleton in Maya properly with behaviour-mirrored arms and legs, you’ll find that you have to pose HIK-rigged characters’ limbs separately anyway. (I only had time to look into these issues for a moment, as I had a lot on my plate; if there are easy solutions that were overlooked I’d love to know what they are.)

I had a look at the system and the commands it used when I walked through the Characterization and retargeting processes, and determined at the time that Python was not the way to go for the retargeting pipeline itself. I found in tests that it was more work to get the MEL functions behind the HIK system working from Python than it was to write MEL wrapper functions and call out to them from Python when necessary. It was also more efficient to use the MEL functions as they were, as opposed to pulling them apart to find the necessary node connections to set up the system on my own.

There’re a few lists of functions available online already (as I discovered on [insert blog link]). Here’re the ones I ended up using.

HIKCharacterizationTool, HIKCharacterControlsTool — These bring up their respective windows / docked panels. I found that not having the relevant window open made the functions that depended on the window being open fail, so keep that in mind when running HIK commands in batch.

getCurrentCharacter() — Returns the name of the current character as a string.

hikBakeToSkeleton — Bakes the keys from the rig or from another character being used as a retargeting source to the skeleton. I used this function when exporting from Maya to the game engine.

characterizationControlRigDelete() — Completely removes the control rig from the scene and sets the source for the character back to its skeleton.

setRigLookAndFeel(name, look number) — There are a few different looks for HIK rigs. In batch I found it nice to set the one I preferred before saving files out for animation fixes.

mayaHIKgetInputType — Returns 0 if input type is Stance Pose, 1 if input type is skeleton or control rig (I guess this means “self”), and 2 if input is another character.

mayaHIKsetCharacterInput(character, input character) — For retargeting, allows you to set the input of one character to be another in the scene.

characterizationCreate() — Creates a new Characterization node. You can rename the node and then make the UI recognize the new name with the following command.

characterizationToolUICmd — Useful for setting the current character name: characterizationToolUICmd -edit -setcurrentcharname [name]

setCharacterObject(object name, character name, characterization number, 0) — I don’t think I’ve seen this documented elsewhere, but this does the connecting during the Characterization phase from your joints into the character node. It appears the connections are specific and need to be fit into particular indices in a message attribute array, so the “characterization number” is something you need to figure out ahead of time if you’re doing a large number of these in batch. Some important numbers:

Ground

0

Left Thigh

2

Left Calf

3

Left Foot

4

Left Toe

118

Right Foot

7

Right Toe

142

Right Calf

6

Right Thigh

5

Pelvis / Center of Motion

1

Left Clavicle

18

Right Clavicle

19

Left UpperArm

9

Left Forearm

10

Left Hand

11

Right UpperArm

12

Right Forearm

13

Right Hand

14

Neck Base

20

Head

15

The nice thing about this is that once you know all the numbers, you can slide them into attributes on the joints in your skeleton and use that data to apply characterizations later on.

Going forwards, if I were to use the same tools again in another production (and in cases where animation needs to be transferred between two differing skeletal hierarchies, it would make sense), I think I’d pull the code apart a bit more and have a look at how the connections are made at the lowest level, then rewrite a chunk of this code in Python.

One more thing: using the standard functions, sometimes the UI will take a bit to catch up. Unfortunately, this means that functions which take inputs from the relevant UI will fail in situations where running them manually will work fine. EvalDeferred didn’t fix this issue for me every time, possibly because of how Qt and Maya are connected together and how they both do lazy updates at different times. I haven’t delved much into PyQt and Maya’s Qt underpinnings just yet, but the updating behavior is something for further study. In the interim, I found that using the maya.utils.processIdleEvents function to make sure all events were taken care of after doing major steps in the characterization or baking processes helped the UI catch up.