First Month at Boulder Media
So a month ago I moved to Dublin Ireland to start my new job as a Junior Rigger at Boulder Media. I've been busy with a lot of relocation related stuff but now I can finally start posting regularly again.
A lot of the stuff I've been working on this past month is really cool, but unfortunately I can't talk about it yet because of my NDA. I’m looking forward to being able to share it with you in the future but in the meantime here are some tidbits that I’ve learned in the past month. All of this stuff is pretty simple to understand but it's also all very interesting.
Animation Controls:
Thicker curves: Do you find it annoying that nurbs curves are so thin? Well lucky for you we can make them thicker. Each nurbsCurve shapes node has a lineWidth attribute that is hidden, but we can find it by going to the attribute editor and scrolling down to extra attributes. By default this attribute is set to -1 which makes the curve as thin as possible but you can change that to whatever you want, it might even be cool to give animators the ability to choose the thickness of the controls (A good idea for a basic script).
Naming Conventions:
So in the past I have used a naming convention like this L_arm_FK_elbow_ctrl' for controls and 'L_arm_elbow_rotBlend' for nodes. Which is an "ok" naming convention, it's readable, it tells you what each node and control does but we can do better.
It's better to start the name of your nodes with their nodeType so instead of 'L_arm_elbow_rotBlend' we would have 'blendColors_L_arm_elbow'. This is better because if you're searching for an object or a node in the outliner you can just type in blendColors* and find it lickety-split.
It's also better to go from the most important part of the name to the least important so 'blendColors_L_arm_elbow' should be 'blendColors_arm_L_elbow' because there is more of a difference between an arm and a leg than a left and a right arm, so 'arm' is more important than 'L'
I can't talk about the exact naming convention we use at Boulder Media but hopefully these tips will help you to pin down a more useful naming convention.
Protected Colors:
Coloring your controls is a great way to add visual clarity to your rig. I colored all the primary controls on the right side red and all the primary controls on the left side blue, light blue and pink for the secondary controls, and light green and orange for the tertiary controls (like the bendy controls).
I thought this was a pretty good color scheme until my supervisor used the phrase 'protected colors'. So Maya uses different colors to tell the user about an object, is it selected, is it being influenced, etc. It can be confusing if your controls are colored using any of these 'protected colors'. If you want to see what those colors are just go to Windows > Setting and Preferences > Color Settings, and then go to the active tab, and the general dropdown.
Some of these colors can still be used because the animator should never encounter them (like the highlight color which only appears in component mode). So the protected colors are pink,light blue, white, purple, and light green. Pink can still be okay if you don't have any template objects in the scene.
Well-Designed Shapes:
I'll admit, the HERMES auto rigger has some very lazy control designs. They're almost all circles. Circles are easy to create but they tell you very little about what the control does or how it is oriented. If I twisted a circle you wouldn't be able to tell.
Compare that to something like Josh Sobel's Kayla rig. He has arrows on some controls. He used squares and prisms. This makes his controls more user-friendly because you can guess what they do just by looking at them. He's opted for a single color scheme rather than one for the right side and one for the left side and all the colors in that scheme are easily distinguishable. He doesn't use any 'protected colors'. When he does use circles they are usually for controls that move but don't rotate (like with the bendy controls) or where it is clear what they do (like on the fingers).
None of this stuff is going to make or break your rig, I didn't know this stuff before I graduated, but it's icing on the cake, and recruiters like icing.
Matrix Attributes: Adding attributes to objects is easy. Just go to Modify > Add Attributes.
But if you only do it this way you'll miss out on an extra attribute type, a matrix. That’s right, even though Maya doesn't give you the option in the menu, you can add matrix attributes to any node.
To do this just use a command like this: cmds.addAttr(obj, ln = “attributeName”, at = “matrix”).
Just like message attributes matrices don’t show up in the channel box, you’ll need to go to the extra attributes section of the attribute editor.
This can help with keeping rig components as separate as possible. Previously I would have empty transform nodes (groups) in the hierarchy and have parent constraints connect them to connect the components, but this creates nodes that exactly part of either component.
Using matrices we can give each component input and output matrices and use them for matrix-based parent constraints inside of the component. This is a little bit cleaner than using parent constraints but does take some getting used to.
Python Stuff:
String formatting: So if you wanted to combine two strings in MEL or Python you could just concatenate them like this: stringC = stringA + stringB But as it turns out that is slow and it can get difficult to read as you concatenate more and more strings.
Instead we can achieve the same result like this:
stringC = “{}_{}”.format(stringA,stringB) This cuts down on the number of characters you have to type and it groups all the variables together at the end of the line for easy editing (instead if having + “_” + between them all). The speed increase isn't noticeable all the time so if using '+' is more readable then go for it, but try to using 'format' when you can. If you're interested in learning more about this here's a good resource (https://www.learnpython.org/en/String_Formatting). Unpacking, Enumeration: Say I had a list of objects and I wanted to print them out alongside their index number like this:
'cat' = 0
you might write something like this:
If we do it that way need to declare a variable that we're only going to use once and if we have several for loops like this we need to manually reset count to 0 every time, so not the most elegant solution. But we can do this in two lines, by doing something called unpacking. You might have already used unpacking without even knowing it. When you write code like this:
circle_transform_node, circle_history_node = cmds.circle(ch = True)
You are unpacking the return value and splitting it up among multiple variables. We can do the same thing in a for loop as well.
This would print out:
The cat eats the fish The dog eats the bone The mouse eats the cheese
and we didn't need to worry about indexes. But let's go the our first question how can we get it to print out 'cat = 1','dog = 2','mouse = 3'? Well we don't need to add those numbers into the list, we can actually make python do it for us. Try out this:
This should print out:
[(0, 'cat'), (1, 'dog'), (2, 'mouse')]
The reason it gave us 0 and not 1 is because the last input for the enumerate function is the starting value so we want that to be 1 not 0. The final code would look this this:
So we were able to get rid of the two lines that declared and increased the value of count, and we even cleaned up the print line using string formatting.
Note: We don't need two write list before enumerate in the final example. The reason for this gets into the difference between the value of a variable and that variable location in memory so just roll with it, or write list every time if you want.
One more example of how unpacking can make your code cleaner. Have you ever tried to do this?
cmds.setAttr('locator1.t', cmds.getAttr('locator2.t'))
it doesn't work because setAttr needs individual values, and getAttr returns a list containing a single tuple (annoying right). We could use pymels setAttr and getAttr but jumping into pymel is a big step. Instead we can do this:
cmds.setAttr('locator1.t', *cmds.getAttr('locator2.t')[0])
First we add a [0] to get the tuple inside the list instead of the whole list, and then we add a * which unpacks the tuple into individual values. Go ahead and try it out, it's a lot more elegant than this:
cmds.setAttr('locator1.tx', cmds.getAttr('locator2.tx')) cmds.setAttr('locator1.ty', cmds.getAttr('locator2.ty')) cmds.setAttr('locator1.tz', cmds.getAttr('locator2.tz'))
or even this
cmds.setAttr('locator1.tx', cmds.getAttr('locator2.tx'), cmds.getAttr('locator2.ty'), cmds.getAttr('locator2.tz'))
So that was a lot of information and I probably got more into the weeds than I should have but it's always good to share this information because you can't just assume it's common knowledge.