Before I get into the collosal mess that is setting driven keys in Maya, let me start off by saying, when I first made an ‘SDK’ in college, back in 1999, never did I think I would still be rigging like this 15 years later. (Nor did I know that I was setting a ‘driven key’, or a ‘DK’ which sounds less glamorous)
How The Mess is Made
Grab this sample scene [driven_test]. In the file, a single locator with two attributes drives the translation and rotation of two other locators. Can’t get much ismpler than that, but look at this spaghetti mess above! This simple driven relationship created 24 curves, 12 blendWeighted nodes, and 18 unitConversion nodes. So let’s start to take apart this mess. When you set a driven-key relationship, it uses an input and a curve to drive another attribute:
When you have multiple attributes driving a node, maya creates ‘blendWeighted’ nodes, this blends the driven inputs to one scalar output, as you see with the translateX below:
Blending scalars is fairly straight forward, but for rotations, get ready for this craziness: A blendWeighted node cannot take the output of an animCurveUA (angle output), the value must first be converted to radians, then blended. But the final node cannot take radians, so the result must be converted back to an euler angle. This happens for each channel.
If you think this is retarded; welcome to the club. It is a very cool usage of general purpose nodes in Maya, but you wouldn’t think so much of rigging was built on that, would you? That when you create a driven-key it basically makes a bunch of nodes and doesn’t track an actual relationship, because of this, you can’t even reload a relationship into the SDK tool later to edit! (unless you traverse the spaghetti or takes notes or something)
I am in love with Node Edtor, but by default hypergraph did hide some of the spaghetti, it used to hide unitConversions as ‘auxiliary nodes’:
Node Editor shows unitConversions regardless of whether aux nodes are enabled, I would rather see too much and know what’s going on than have things hidden, but maybe that’s just me. You can actually define what nodes are considered aux nodes and whether ‘editors’ show them, but I am way off on a tangent here.
So just go in there and delete the unit conversion yourself and wire the euler angle output, which you would think is a float.. into the blendWeighted input, which takes floats. Maya won’t let you, it creates the unitConversion for you because animCurveUA outputs angles, not floats.
This is why our very simple example file generated over 50 nodes. On Ryse, Maruis’ face has about 73,000 nodes of driven-key spaghetti. (47,382 curves, 1,420 blendWeighted, 24,074 unitConversion)
Finding and traversing
So how do we find this stuff and maybe query and deal with it? Does Maya have some built in way of querying a driven relationship? No. Not that I have ever found. You must become one with the spaghetti! setDrivenKeyframe has a query mode, but that only tells you what ‘driver’ or ‘driven’ nodes/attrs are in the SDK window if it’s open, they don’t query driver or driven relationships!
We know that these intermediate nodes that store the keys are curves, but they’re a special kind of curve, one that doesn’t take time as an input. Here’s how to search for those:
So what are these nodes? I mentioned above that animCurveUA puts out an angle:
- animCurveUU – curve that takes a double precision float and has a double output
- animCurveUA – takes a double and outputs an angle
- animCurveUL – takes a double and outputs a distance (length)
- animCurveUT – takes a double and outputs a time
When working with lots of driven-key relationships you frequently want to know what is driving what, and this can be very difficult because of all the intermediate node-spaghetti. Here’s what you often want to know:
- What attribute is driving what – for instance, select all nodes an attr drives, so that you can add them back to the SDK tool. ugh.
- What is being driven by an attribute
I wrote a small script/hack to query these relationships, you can grab it here [drivenKeyVisualizer]. Seriously, this is just a UI front end to a 100 line script snippet, don’t let the awesomeness of QT fool you.
The way I decided to go about it was:
- Find the driven-key curves
- Create a tiny curve class to store little ‘sdk’ objects
- List incoming connections (listConnections) to get the driving attr
- Get the future DG history as a list and reverse it (listHistory(future=1).reverse())
- Walk the reversed history until I hit a unitConversion or blendWeighted node
- Get it’s outgoing connection (listConnections) to find the plug that it’s driving
- Store all this as my sdk objects
- Loop across all my objects and generate the QTreeWidget
Here’s how I traversed that future history (in the file above):
#search down the dag for all future nodes futureNodes = [node for node in cmds.listHistory(c, future=1, ac=1)] #reverse the list order so that you get farthest first futureNodes.reverse() drivenAttr = None #walk the list until you find either a unitConversion, blendWeighted, or nothing for node in futureNodes: if cmds.nodeType(node) == 'unitConversion': try: drivenAttr = cmds.listConnections(node + '.output', p=1) break except: cmds.warning('blendWeighted node with no output: ' + node) break elif cmds.nodeType(node) == 'blendWeighted': try: drivenAttr = cmds.listConnections(node + '.output', p=1) break except: cmds.warning('blendWeighted node with no output: ' + node) break if not drivenAttr: drivenAttr = cmds.listConnections(c + '.output', p=1) if drivenAttr: dk.drivenAttr = drivenAttr else: cmds.warning('No driven attr found for ' + c)
This of course won’t work if you have anything like ‘contextual rigging’ that checks the value of an attr and then uses it to blend between two banks of driven-keys, but because this is all general DG nodes, as soon as you enter nodes by hand, it’s no longer really a vanilla driven-key relationship that’s been set.
If you have a better idea, let me know, this above is just a way I have done it that has been robust, but again, I mainly drive transforms.
What can you do?
Prune Unused Pasta
By definition, when rigging something with many driven transforms like a face, you are creating driven-key relationships based on what you MIGHT need. This goes for when making the initial relationship, or in the future when you maybe want to add detail. WILL I NEED to maybe translate an eyelid xform to get a driven pose I want.. MAYBE.. so you find yourself keying rot/trans on *EVERYTHING*. That’s what I did in my example, and the way the Maya SDK tool works, you can’t really choose which attrs per driven node you want to drive, so best to ‘go hunting with a shotgun’ as we say. (shoot all the trees and hope something falls out)
Ok so let’s write some code to identify and prune driven relationships we didn’t use.
CAUTION: I would only ever do this in a ‘publish’ step, where you take your final rig and delete crap to make it faster (or break it) for animators. Also, I have never used this exact code below in production, I just created it while making this blog post as an example. I have run it on some of our production rigs and haven’t noticed anything terrible, but I also haven’t really looked. 😀
def deleteUnusedDriverCurves(): for driverCurve in cmds.ls(type=("animCurveUL","animCurveUU","animCurveUA","animCurveUT")): #delete unused driven curves if not [item for item in cmds.keyframe(driverCurve, valueChange=1, q=1) if abs(item) > 0.0]: cmds.delete(driverCurve) deleteUnusedDriverCurves()
This deletes any curves that do not have a change in value. You could have multiple keys, but if there’s no curve, let’s get rid of it. Now that we have deleted some curves, we have some blendWeighted nodes that now aren’t blending anything and unitConversions that are worthless creatures. Let’s take care of them:
def deleteUnusedBlendNodes(): #rewire blend nodes that aren't blending anything for blendNode in cmds.ls(type='blendWeighted'): if len(cmds.listConnections(blendNode, destination=0)) == 1: curveOutput = None drivenInput = None #leap over the unitConversion if it exists and find the driving curve curveOutput = cmds.listConnections(blendNode, destination=0, plugs=1, skipConversionNodes=1) #leap over the downstream unitConversion if it exists conns = cmds.listConnections(blendNode, source=0, plugs=1, skipConversionNodes=1) for conn in conns: if cmds.nodeType(conn.split('.')) == 'hyperLayout': conns.pop(conns.index(conn)) if len(conns) == 1: drivenInput = conns else: cmds.warning('BlendWeighted had more than two outputs? Node: ' + blendNode) #connect the values, delete the blendWeighted cmds.connectAttr(curveOutput, drivenInput, force=1) cmds.delete(blendNode) print 'Removed', blendNode, 'and wired', curveOutput, 'to', drivenInput deleteUnusedBlendNodes()
We find all blendWeighted nodes with only one input, then traverse out from them and directly connect whatever it was the node was blending, then we delete it. This is a bit tricky and I still think I may have missed something because I wrote this example at 2am, but I ran it on some rigs and haven’t seen an issue.
Here are the results running this code on an example rig:
Create a Tool To Track / Mirror / Select Driven Relationships
This is a prototype I was working on at home but shelved, I would like to pick it up again when I have some time, or would be open to tossing it on github if people would like to help. It’s not rocket science, it’s besically what Maya should have by default. You just want to track the relationships you make, and also select all nodes driven by an attr. Also mirror their transforms across an axis (more geared toward driven transforms).
Write Your Own Driver Node
Many places create their own ‘driven node’ that just stores driven relationships. Judd Simantov showed a Maya node that was used on Uncharted2 to store all facial poses/driven relationships:
The benefits of making your own node are not just in DAG readability, check out the time spent evaluating all these unitConversion and blendWeighted nodes in a complex facial rig (using the new Maya 2015 EXT 1 Profiler tool) –that’s over 760ms! (click to enlarge)
Though it’s not enough to make a node like this, you need to make a front end to manage it, here’s the front end for this node:
Give Autodesk Feedback!
As the PSD request is ‘under review’, maybe when they make the driver, they can make a more general framework to drive things in Maya.
As you can see, there are many ways to try to manage and contain the mess generated by creating driven-key relationships. I would like to update this based on feedback, it’s definitely not an overview, and speaks more to people who have been creating driven-key relationships for quite some time. Also, if you would find a tool like my Maya SDK replacement useful, let me know, especially if you would like to help iron it out and/or test it.