Why Storing String Refs is Dangerous and Irresponsible
Yes! Definitely a clickbait title if there ever was one; but this is important! Some of you contacted me regarding my earlier post (Don’t Use String Paths), where I said you should never use string names to keep track of anything even vaguely important in Maya.
This problem is so fundamental in Maya that the initial Python code test I came up with for the Crytek Technical Art Dept used to be a simple maya node renamer. No joke.
THE PROBLEM
From time to time I see code that attempts reach out into the DAG and grab nodes by concatenating tokens like:
character_side + '_' + character_name + '_arm_' + UID + '_' + type |
This is SUPER dangerous. You are basically guessing that an object exists, and betting your whole tool or codebase on this, it’s super fragile.
Let’s use the example of making a turtle, let’s call him ‘Red’, he’s red, and his shell is soft:
cmds.joint(name = 'red' + '_' + shell_hardness + '_' + animal_type + '_hand') cmds.select('red' + '_' + shell_hardness + '_' + animal_type + '_hand') # Error: No object matches name: red_soft_turtle_hand # Traceback (most recent call last): # File "", line 1, in # ValueError: No object matches name: red_soft_turtle_hand # |
First up, you should *never* do this, when you are creating a node with a name you are making from scratch, you *must* first check if it exists. If it exists, Maya will alter the name and you will not be able to find it, your code will immediately break either with the above error, or this one:
cmds.select('red_soft_turtle_hand') # Error: More than one object matches name: red_soft_turtle_hand # Traceback (most recent call last): # File "", line 1, in # ValueError: More than one object matches name: red_soft_turtle_hand # |
SLIGHTLY BETTER
So here’s something a bit more safe, but still not recommended in situations where this is important:
hand_jnt =Â cmds.joint(name = 'red' + '_' + shell_hardness + '_' + animal_type + '_hand') print hand_jnt # >>: red_soft_turtle_hand1 |
Here, we’re storing the node created in a variable, if the name is not the name we expected, we still know how to find the node. This is a lot safer, your code will continue to run, but it’s also not a great way of working. Maya created our joint, but called it ‘red_soft_turtle_hand1’, but, because we stored the node returned from the command creating it into a variable, we can print hand_jnt, and it will return ‘red_soft_turtle_hand1’.
To be super clear, this is more safe because:
- Maya is resolving any name clashes for you automatically
- You get the *real* name returned to you from Maya upon node creation
- If you create a node name that exists elsewhere, it returns you a full path automatically
This is somewhat acceptable in code that’s building a rig. Wham! Bam! You created a node and then did something with it seconds later and never looked back!
STILL A PROBLEM
So let’s say you’re doing the slightly safer way, and you’re storing long paths of nodes which Maya gives you the name of. As soon as you store a long path, it’s stale. This is what I meant above, the longer you store this information, the less reliable it is.
Let’s look at our joint, its long path is:
|root|pelvis|shell|some_other_joint|arm1|arm2|red_soft_turtle_hand |
If *any* of the six parent nodes names change, just a single character! You’re dead in the water. If the hierarchy changes: you’re dead in the water.
If you’re working with someone who defends the above by saying the following
- “Oh, the hierarchy will never change”
- “Oh, there will never be a node with the same name”
- “Oh, no one will ever rename any of these”
The Maya DAG is the wild west.
You WILL have duplicate node names.
You WILL have hierarchy changes.
Professional tools don’t only work with a bunch of caveats,
if it’s a valid Maya scene, your code should work with it:
BE PREPARED.
THE SOLUTIONS
There are clear ways to overcome the challenge above, they’re not secrets, they are actually the professional way Autodesk tells you to do it in the docs, code examples, and their own implementations.
You don’t *have* to work 100% in the API or PyMel, but like we said before, as soon as possible get a pointer to your object. Think of any string path as having a short half-life.
Using The Maya Python API
In C++ when you pass around an object, you have a pointer to it’s location in memory. The name and DAG path can change, but you can at any time get the current name, and path. Fresh, not stale!
import maya.OpenMaya as om m_dg = om.MFnDagNode() m_obj = m_dg.create("joint") m_dagPath = om.MDagPath() if m_obj.hasFn(om.MFn.kDagNode): om.MDagPath.getAPathTo( m_obj, m_dagPath ) print m_dagPath.fullPathName() # >>: |joint1 |
Using PyMel
PyMel wraps the API, a PyMel object is a *real* python object that stores the actual MObject, but you don’t have to use the API.
myJoint = joint(name='doesnt_even_matter') print myJoint.fullPath() # >>: |root|pelvis|shell|some_other_joint|arm1|arm2|red_soft_turtle_hand|doesnt_even_matter #In case you never used PyMel, here's some examples of convenience functions: myJoint.transformationMatrix() myJoint.inputs() |
Notice that PyMel is handing you back python objects, you can ask the object for it’s full path at any time, and it will be fresh data.
Using Message Attrs
Message attributes are how Alias decided users will serialize relationships between nodes. I have removed this section because future-me wrote an entire post dedicated to this (The Mighty Message Attribute).
MAKING A PLAN
The important thing here is that you decide a consistent way of using the above. At Crytek we tried to have all core functions that accepted or passed DG nodes, do the handoff as MObjects. We serialized all important relationships with message attrs, and when there was too much data for that to be efficient, we stamped nodes with metadata.
RETROFITTING
If you have an existing pipeline where you build a lot of string references by concatenating tokens like the example above, you can make a convenience function that will validate your assumptions and deal gracefully with issues. Something like my_utils.get_node(‘my’ + ‘_’ + ‘thing’)