Destiny Rigging/Animation Slides and Videos Up
The Destiny talks at SIGGRAPH were really interesting, the material just went up and you should definitely check it out:
A Technical Art Blog
The Destiny talks at SIGGRAPH were really interesting, the material just went up and you should definitely check it out:
As technically-inclined artists, we often like to create polished UIs, but we have to balance this with not wanting to complicate the user experience (fewer files the better). I tend to not use too many icons with my tools, and my Maya tools often just steal existing icons from Maya: because I know they will be there.
However, you can embed bitmap icons into your PySide/PyQt apps by using the XPM file format, and storing it as a string array. Often I will just place images at the bottom of the file, this keeps icons inside your actual tool, and you don’t need to distribute multiple files or link to external resources.
Here’s an example XPM file:
/* XPM */ static char *heart_xpm[] = { /* width height num_colors chars_per_pixel */ "7 6 2 1", /* colors */ "` c None", ". c #e2385a", /* pixels */ "`..`..`", ".......", ".......", "`.....`", "``...``", "```.```" }; |
This is a small heart, you can actually see it, in the header you se the ‘.’ maps to pink, you can see the ‘.’ pattern of a heart. The XPM format is like C, the comments tell you what each block does.
Here’s an example in PySide that generates the above button with heart icon:
import sys from PySide import QtGui, QtCore def main(): app = QtGui.QApplication(sys.argv) heartXPM = ['7 6 2 1','N c None','. c #e2385a','N..N..N',\ '.......','.......','N.....N','NN...NN','NNN.NNN'] w = QtGui.QWidget() w.setWindowTitle('XPM Test') w.button = QtGui.QPushButton('XPM Bitmaps', w) w.button.setIcon(QtGui.QIcon(QtGui.QPixmap(heartXPM))) w.button.setIconSize(QtCore.QSize(24,24)) w.show() sys.exit(app.exec_()) if __name__ == '__main__': main() |
You need to feed QT a string array, and strip everything out. Gimp can save XPM, but you can also load an image into xnView and save as XPM.
Addendum: Robert pointed out in the comments that pyrcc4, a tool that ships with PyQt4, can compile .qrc files into python files that can be imported. I haven’t tried, but if they can be imported, and are .py files, I guess they can be embedded as well. He also mentioned base64 encoding bitmap images into strings and parsing them. Both these solutions could really make your files much larger than XPM though.
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)
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)
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:
print len(cmds.ls(type=("animCurveUL","animCurveUU","animCurveUA","animCurveUT"))) |
So what are these nodes? I mentioned above that animCurveUA puts out an angle:
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:
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:
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)[0] break except: cmds.warning('blendWeighted node with no output: ' + node) break elif cmds.nodeType(node) == 'blendWeighted': try: drivenAttr = cmds.listConnections(node + '.output', p=1)[0] break except: cmds.warning('blendWeighted node with no output: ' + node) break if not drivenAttr: drivenAttr = cmds.listConnections(c + '.output', p=1)[0] 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.
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)[0] #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('.')[0]) == 'hyperLayout': conns.pop(conns.index(conn)) if len(conns) == 1: drivenInput = conns[0] 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.
This is just a reminder that for some time now Maya has been saving all ScriptEditor tabs on crash. I frequently bump into people who don’t know or don’t remember this. If your Maya says that it’s attempting to save in your /Temp folder, it’s also saving all your ScriptEditor tabs.
Powered by WordPress