Friday, July 27, 2012

Stick a fork in it

... because I finally finished my modular autorig. Developed by following along with 3DBuzz's monstrous 50+ hour 'Developing Modular Rigging Systems with Python' DVD, this autorig is by far the most elaborate and involved scripting project I've ever taken on. This all started because I wanted to build my own autorig, but didn't really know exactly where to start, so I picked up the learning DVD to get a better idea of the process. I definitely learned a ton doing this project, so I wanted to share some of those insights here. Below is a quick demo of the system's functionality.


The process:

The autorig itself spans over 30 python files, written from scratch, and it took a little over 2 months working off and on to finish. The process was pretty simple at first; watch the videos and write the code as they write it, testing frequently. However, it quickly became apparent that the process wouldn't just be a walk in the park, copying code the whole way. Discrepancies in the way UI code is handled by the pre-QT interface of Maya 210 (which was the version the system was developed in) and Maya 2012 meant that subtle changes had to be made in order for all of the elements of the user interface to be displayed correctly. Also, fixing any bugs that I discovered while testing (most due to my own typos, I'll admit) was totally up to me.

I coded the entire system in Notepad++, rather than relying on the benefits of code completion and error checking in an IDE such as Eclipse. The result is that I've grown extremely adept at catching my own syntax errors, and quickly fixing those that do manage to slip through the cracks. Even some of the trickier and more insidious bugs didn't last much longer than 30 minutes or so of solid debugging.

The entire system is scripted in the maya.cmds implementation of python rather than pyMel. This has the effect of making the code much more verbose and quite a bit harder to read... but overall it wasn't that bad.

Insights:

Since I took this on as a learning experience, I wanted to really break down what I learned, the good, the bad, and the ugly, from this project.
The main thing that I learned was that, in a project of this magnitude, construction is king. I was reading Code Complete by Steve McConnell while working on the system, and it was a great combination. It really helped drive home the importance of well designed, well documented, well constructed coding. I'll get more into that in a second.

Since I had planned to take what I learned and apply it to a new autorigger of my own design, I noted a few things that I would change in my system. The first was the reliance on naming convention and namespaces to store and recall information about where a particular rig part came from and what it could do. This led to namespaces nested three layers deep, and an outliner that quickly became a solid wall of text. It worked, but storing information inside of metaNodes connected directly to rig parts would have been a more elegant, more powerful, and a cleaner solution. Second, once you 'lock' the rig and turn the blueprint modules into actual joints, that's it. the process is pretty much irreversible. It would have been nice to be able to see the joint locations, and then be able to go back and make changes. The other big design choice that I would avoid was the use of locked containers. Containers in Maya may be powerful, and theoretically awesome from a rigger's point of view (animators can't rename or delete important nodes, everything is bundled together), but in practice they're pretty annoying. Having to constantly unlock nested containers in order to debug things is slow and painful. In addition, attributes published to containers behave strangely in the channel box, disallowing middle-mouse scrubbing to change attribute values, and turning the clean list of attributes into an organizational nightmare.

Seen here: the arm controls? I'm not even sure...

The main thing I took away from this project though, was the importance of construction. I want to be clear: I mean no disrespect or insult to the guys who developed this system. It was a rapidly developed prototype, meant as a proof of concept and a learning tool -- not a production ready rigging pipeline. What the guys at 3DBuzz managed to pull off in this DVD was nothing short of awesome. That being said, the code itself is pretty rough.
There is almost no documentation whatsoever to the code. This was addressed directly in the videos a few times, as the addition of comments and documentation would have made the videos even longer, but even so, it makes the system a nightmare to read through. I considered adding documentation myself, but I would have never been able to keep up with the videos, and I wouldn't have truly understood the code well enough as I was writing it to comment it well anyways. Since the system was developed bit by bit, there were pieces of the code that weren't actually implemented until many hours later. They were only added when they were because the architect of the system knew that those functions would be needed eventually.
I would have liked to see more wrappers to Maya functions. There are arguments against this sort of thing (see the "Wrapping Commands in Maya" post on Sune Kempf's rigging page), but after you've written this for the umpteenth time:

cmds.xform(obj, worldSpace=True, absolute=True, translation=cmds.xform(target, q=True, worldSpace=True, translation=True))

You'll find yourself wishing you had declared a utility function:

utils.matchWorldSpacePosition(obj, target)

Stuff like this would have also made the routines smaller, since they tend to run on (and on and on).

The biggest issue I had with the code though, and the one thing that would really make this system solid, was the problem of information hiding, or more specifically, the lack thereof. Throughout the system, routines and functions made assumptions about the data that was being returned to them. This may have worked, since the assumptions were essentially hard-coded by the system's architect himself, but it made the code nigh impossible to read and follow in places. It also cause random bugs to pop up all over the place.
For instance, in one function, there was a check to see if a spine joint chain had 5 or more joints, and if it did, it could have a certain control rig installed on it. The code to check that condition was:

blueprintJoints = utils.findJointChain(blueprintJointsGrp)
if len(blueprintJoints) >= 6:
    # do stuff

This seems like a typo. It seemed like a typo to me as I wrote it, and it took me a second to remember that the 'findJointChain' function returned the parent group of the joints in index one of the returned array. So instead of checking to see if there were 5 joints in the returned array, you had to add one, in order to account for that group. You'd never guess that was the case from the title of the routine, but this is one of many examples of this sort of thing. A better implementation of this check would be to write a routine that really did only return the joints (the existing routine could continue to return the group as well), and use a named constant instead of the magic number 6:

MIN_JOINTS_IN_SPINE_RIG = 5
blueprintJoints = utils.findOnlyJointChain(blueprintJointsGrp)
if len(blueprintJoints) >= MIN_JOINTS_IN_SPINE_RIG:
    # do stuff

This makes the code much more readable, almost self-documenting.

ALL the learning:

This post is getting crazy long, so I'll try and wrap it up. What did I learn from this project? First, I got an INCREDIBLE amount of experience, writing the code, testing it, and debugging it. Second, I got to see how the whole system fits together. How multiple files and classes interrelate and work together to create an incredible amount of functionality. I learned some cool python tricks and techniques as well, such as using __import__ instead of the usual import when you need to bring in a module that is being stored as a variable, and you don't explicitly know which module it is. I also got to use a bit of vector math, which got me thinking of solutions to problems that stumped me a week ago (matching pole vector locations with a no-flip leg setup? I'll probably write a post about it). I also saw how the complexity of a system grows exponentially, and how the design is so import when it comes to the scope of a project, and determining which features get implemented or not.
Finally, I'd just like to say again, what an undertaking the creators of this system have pulled off. Not just building the autorig, but deconstructing it and rebuilding it in a tutorial format, constructing the system piece by piece, adding functionality and keeping it running and testable at every stage. Kudos to those guys at 3DBuzz. Would I recommend the DVD to others? If you've got the basics of python and scripting for maya down, and you're not intimidated by a challenge -- definitely. You'll learn a ton, I know I did.

2 comments:

  1. I went through all that about a year ago and got side tracked, and yes it was a beast to come back to and try and debug, I'm in the process of simplifying it all, I have it based on pymel and Qt but combined all the UI's into one tool as well. Kudos for getting through it all.

    ReplyDelete
  2. Hi Kyle,

    I bought the DVD a while back and finally got around to doing it.

    I'm about half way through and the amount of error checking I have to do is insane... Currently at the mirror expression stage video 91 and I keep getting strange snapping...

    tried it on Maya 2016,2017 and 2018.

    any ideas what it could be...

    it works fine if I don't add everything into the summetryMove_container.... any ideas?

    ReplyDelete