Archive for December, 2014

littlewitch (autorig theory part 2)

A good friend and I have been talking about releasing a product lately. Not a specific product, but the release process: how there has been a shift to releasing early and patching often until you get something people want to use.

I was going to delay the release of the code I promised in my last post until I had more of it done, but I could always say that. Instead, here’s the first release:

https://github.com/kattkieru/littlewitch

I’d consider it pre-alpha. Also, I (unapologetically) use tabs, PyMEL, and **kwargs. You’ve been warned.

The good stuff: despite being written from the ground up on the bus over the last three weeks, it’s shaping up well. Without a prior codebase to worry about for compatibility I’ve been able to throw out a bunch of old ideas and replace them with shiny new ones. Untested, shiny new ones. But one of my goals with auto rigs is to both reduce the need for postbuild scripts and to reduce the need for boilerplate code. So far, this new system is doing well for the latter. Here’s the build function for the SimpleFK module (a module that adds one FK control to every joint in a chain, and optionally one to its tip):


def build(self, **kwargs):
  self.pushState()

  self.createModule()
  self.conChain = self.createRigChain('CON', 2.0)

  self.connectChains( self.conChain, self.chain )

  ## controls
  targetControls = self.conChain[:]
  if self.chainLength > 1:
    if not self['addControlToTip']:
      targetControls.pop(-1)

  for index, item in enumerate( targetControls ):
    name = item.partition("_")[0]
    con = self.createControl('fk', name, item, constrain='parent')
    utils.lock(con, s=True, v=True)
    if self.numControls('fk') > 1:
      pm.parent(self.getZero('fk', index), self.getControl('fk', index-1))

  self.popState()

Params mentioned in the last article can be accessed through the __getitem__ method, so there’s no need to have a long list of them being cached at method start (or to pass them between methods in a dictionary). Controls now pull their Parameters directly from the module based on registered categories, and are created with zeros by default. Controls can constrain a target joint at creation time. ¬†Little things.

Anyway, code is up, with more on the way (although not likely before the new year). There are a lot of functions stubbed out that do nothing, and many bugs to be squashed.  Be gentle, and send suggestions if you have them.

autorig theory

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.

Tags: , , ,