Stumbling Toward 'Awesomeness'

A Technical Art Blog

Tuesday, September 30, 2014

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’)

posted by Chris at 3:28 PM  

Wednesday, September 24, 2014

Maya DAG nodes in QT tools (don’t use string paths)

fragile

 

String paths to Maya objects are _fragile_. This data is stale as soon as you receive it, long paths are better than short, and taking into account namespaces is even better –BUT when it comes down to it, the data is just old as soon as you get it, and there is a better way.

A Full Path that is Never Stale

If you store a node in a Maya Python API MDagPath object, you can ask at any time and get its full path. Because it’s basically a pointer to the object in memory. Here’s an example:

import maya.OpenMaya as om
 
#create and populate an MSelectionList
sel = om.MSelectionList()
sel.add(cmds.ls(sl=1)[0])
 
#create and fill an MDagPath
mdag = om.MDagPath()
sel.getDagPath(0, mdag)
 
#At any time request the current fullpath to the node
print mdag.fullPathName()

CHALLENGE: Can you write a renaming tool that doesn’t use string path node representation? That works with all DG nodes? Without using PyMel? 😉

Embedding Maya Python Objects in PyQT (UserRole)

So.. if we can store a pointer to an object, how do we track that with QT UIs, pass it around, etc? It can be really difficult to find a way to be responsible here, you have all these lists and node representations, and in the past I would try to sync lists of python MDagPath objects, or make a dictionary that mirrors the listView… but there is a way to store arbitrary python objects on list and tree widgetitems!

In this code snipet, I traverse a selection and add all selected nodes to a list, I add ‘UserRole’ data to each and set that data to be an MDagPath:

    listWids = []
    for item in nodes:
        sel = om.MSelectionList()
        sel.add(item)
        mdag = om.MDagPath()
        sel.getDagPath(0, mdag)
        name = mdag.fullPathName()
        if not self.longNamesCHK.isChecked():
            name = name.split('|')[-1]
        wid = QtGui.QListWidgetItem(listWid)
        wid.setText(name)
        wid.setData(QtCore.Qt.UserRole, mdag)
        listWids.append(wid)

Now, later when we want to get the full path name of this widgetItem, we just ask it for this data. That returns the MDagPath object, and we can ask the object for the current full path to the node:

print self.drivenNodeLST.currentItem().data(QtCore.Qt.UserRole).fullPathName()

So this is a good way to have arbitrary data that travels wit the QT node description, which is usually some kind of widget.

posted by Chris at 12:40 PM  

Thursday, September 18, 2014

Ryse SIGGRAPH 2014 Reel

I mentioned that we won “best Realtime Graphics” at this year’s SIGGRAPH conference, but I never linked the video:
https://www.youtube.com/watch?v=mLvUQNjgY7E

posted by Chris at 4:24 PM  

Powered by WordPress