::capturing nodes created by function::

July 11th, 2011 by hamish download the zooToolBox

I’ve written a few tools where being able to capture the nodes created by a particular function has come in handy. So I figured I’d blog about it in the hopes that it makes someone else’s life easier.

So do what exactly? Well, the idea is to run some function, and be able to capture all the nodes created while that function executes. I’ll talk a bit about my motivations for this at the end of the post – for now, lets dig into the code.

Maya provides quite a few event based callbacks that you can hook up using the C++ API. This hasn’t been easy to do before python came along without writing C++ code. This particular problem you could mostly solve without this API access, but not any way that would be performant.

from maya.OpenMaya import MObjectHandle, MDGMessage, MMessage
import apiExtensions  #this module makes the maya api bindings a little easier to work with

def getNodesCreatedBy( function, *args, **kwargs ):
        '''
        returns a 2-tuple containing all the nodes created by the passed function, and
        the return value of said function
        '''

        #construct the node created callback
        newNodeHandles = []
        def newNodeCB( newNode, data ):
                newNodeHandles.append( MObjectHandle( newNode ) )

        def remNodeCB( remNode, data ):
                remNodeHandle = MObjectHandle( remNode )
                if remNodeHandle in newNodeHandles:
                        newNodeHandles.remove( remNodeHandle )

        newNodeCBMsgId = MDGMessage.addNodeAddedCallback( newNodeCB )
        remNodeCBMsgId = MDGMessage.addNodeRemovedCallback( remNodeCB )

        ret = function( *args, **kwargs )
        MMessage.removeCallback( newNodeCBMsgId )
        MMessage.removeCallback( remNodeCBMsgId )

        #NOTE: the newNodes is a list of MObjects.  If you want node names do something like this:
        #newNodes = [ str( h.object() ) for h in newNodeHandles ]
        #this solution relies on using the apiExtensions module imported above
        newNodes = [ h.object() for h in newNodeHandles ]

        return newNodes, ret

So thats the code that does the magic. Its pretty simple, even if a bit awkward to use. Basically you pass it the function you want to run and all its corresponding args, and it will return the list of nodes created by that function, and the return value of that function.

Just a quick note – as it says in the comments if you don’t want MObjects returned you can easily return node names instead. Although if you’re using the apiExtensions module you can use MObject instances as if they were node names. You can grab the apiExtensions module from here, or just grab it out of the latest release.  Or if you’re a pymel kinda guy/gal, I expect you can instantiate the PyNode class with an MObject.

Moving on, here is a quick example:

from maya.cmds import *
def someFunc():
        spaceLocator()
        group( em=True )
        joint()
        polySphere()

        return 'some interesting return value'

nodesCreated, returnValue = getNodesCreatedBy( someFunc )
print nodesCreated, returnValue

Thats it. Doesn’t matter how the nodes are created, this function will capture them all.  You may also notice that the shape nodes for the locator have been included as well.  The nodesCreated list will quite literally contain ALL nodes created by the function passed in.

So whats it used for?

Just say I want to write a tool that will build a dynamics network on a given control chain.  I want to be able to turn this functionality on and off easily and I want to make sure when it gets deleted there is no cruft left behind.  First I write the code to build the network.  Then I write another function that will execute this code and capture the created nodes and put them in an object set or a container or something.

Skeleton Builder works in this way.  When a rig part is built the build method gets run inside this node capturing decorator and all created nodes are put into an object set.  So Skeleton Builder can easily delete a rig cleanly.  It can also more easily understand the relationship between a given node and a given part of the rig.

Another tool that uses this is the Dynamics Tool.  This tool will create a hair follicle setup and constrain a given list of objects to it.  It will then give you an interface to delete and create this setup on the fly, or bake the results down to keys.  The deletion of the hair setup is clean because ALL nodes are captured when the rig is created.

One minor thing to note – if you’re running maya 2009 there is a bug with removing node creation callbacks…  This bug is fairly harmless although it may result in noticeable slowdowns if you end up with lots of improperly removed callbacks.

Share

This post is public domain

This entry was posted on Monday, July 11th, 2011 at 15:20 and is filed under main. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.