[OpenMaya] :: Iteration

As a tools developoer, it is in my best interest to make the code run fast, and if there is anything in Maya that makes things go fast, it’s OpenMaya:

Below is an example of how OpenMaya iterates a scene to find all the AnimCurve nodes connected to an object in Maya.:

# import maya modules
from maya import OpenMaya as om

def get_connected_nodes(object_name="", find_node_type=om.MFn.kAnimCurve): """ get connected nodes from node provided. :param object_name: <str> string object to use for searching from. :param find_node_type: <om.MFn> kObjectName type to find. """ node = get_m_obj(object_name) dag_iter = om.MItDependencyGraph( node, om.MItDependencyGraph.kUpstream, om.MItDependencyGraph.kPlugLevel) dag_iter.reset() found_nodes = [] while not dag_iter.isDone(): cur_item = dag_iter.currentItem() if cur_item.hasFn(find_node_type): found_nodes.append(cur_item) dag_iter.next() return found_nodes

Another way to go about doing this business is creating a generator object by introducing yield, in the same code, we just remove the return statement:

# import maya modules
from maya import OpenMaya as om

def get_connected_nodes_gen(object_name="", find_node_type=om.MFn.kAnimCurve): """ nodes generator. :param object_name: <str> string object to use for searching from. :param find_node_type: <om.MFn> kObjectName type to find. """ node = get_m_obj(object_name) dag_iter = om.MItDependencyGraph( node, om.MItDependencyGraph.kUpstream, om.MItDependencyGraph.kPlugLevel) dag_iter.reset() while not dag_iter.isDone(): cur_item = dag_iter.currentItem() if cur_item.hasFn(find_node_type): yield cur_item dag_iter.next()

We can test the speed of the code by utilizing the cProfile module:

# import maya modules
from maya import OpenMaya as om

# import local modules
import cProfile

# define variables
anim_key_nodes = object_utils.get_connected_nodes_gen('pCube1')

# run profiler
cProfile.run("for n in anim_key_nodes: print n")

<...>
<...>
<maya.OpenMaya.MObject; proxy of <Swig Object of type 'MObject *' at 0x000002776FB30B40> >
<maya.OpenMaya.MObject; proxy of <Swig Object of type 'MObject *' at 0x000002776FB306C0> >

213 function calls in 0.021 seconds

Ordered by: standard name

ncalls tottime percall cumtime percall filename:lineno(function)
1 0.020 0.020 0.021 0.021 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 OpenMaya.py:1539(__init__)
1 0.000 0.000 0.000 0.000 OpenMaya.py:7370(__init__)
14 0.000 0.000 0.000 0.000 OpenMaya.py:84(_swig_repr)
1 0.000 0.000 0.000 0.000 OpenMaya.py:9666(__init__)
15 0.000 0.000 0.001 0.000 object_utils.py:60(get_connected_nodes_gen)
1 0.000 0.000 0.000 0.000 object_utils.py:80(get_m_obj)
42 0.000 0.000 0.000 0.000 {maya._OpenMaya.MItDependencyGraph_currentItem}
43 0.000 0.000 0.000 0.000 {maya._OpenMaya.MItDependencyGraph_isDone}
42 0.000 0.000 0.000 0.000 {maya._OpenMaya.MItDependencyGraph_next}
1 0.000 0.000 0.000 0.000 {maya._OpenMaya.MItDependencyGraph_reset}
1 0.000 0.000 0.000 0.000 {maya._OpenMaya.MItDependencyGraph_swiginit}
42 0.000 0.000 0.000 0.000 {maya._OpenMaya.MObject_hasFn}
1 0.000 0.000 0.000 0.000 {maya._OpenMaya.MObject_swiginit}
1 0.000 0.000 0.000 0.000 {maya._OpenMaya.MSelectionList_add}
1 0.000 0.000 0.000 0.000 {maya._OpenMaya.MSelectionList_getDependNode}
1 0.000 0.000 0.000 0.000 {maya._OpenMaya.MSelectionList_swiginit}
1 0.000 0.000 0.000 0.000 {maya._OpenMaya.new_MItDependencyGraph}
1 0.000 0.000 0.000 0.000 {maya._OpenMaya.new_MObject}
1 0.000 0.000 0.000 0.000 {maya._OpenMaya.new_MSelectionList}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}

And there we have it, we have covered OpenMaya iteration by traversing the node connections and finding the corresponding node (in this case the AnimCurve node), and a python generator to show the similarities between a loop and a generator. And yes, you can do the similar basic maya command, but it’s not as fun:

# import maya modules
from maya import cmds

def get_connected_anim(object_name=""):
    """
    get connected nodes from node provided.
    :param object_name: <str> string object to use for searching from.
    :param find_node_type: <om.MFn> kObjectName type to find.
    """
    anim_c = cmds.listConnections(object_name, s=1, d=0, type='animCurve')
    anim_b = cmds.listConnections(object_name, s=1, d=0, type='blendWeighted')
    anim_curves = []
    if not anim_c and anim_b:
        for blend_node in anim_b:
            anim_curves.extend(cmds.listConnections(blend_node, s=1, d=0, type='animCurve'))
        return anim_curves
    else:
        return anim_c

 ...
animCurveUL4
animCurveUL5
animCurveUL8
animCurveUL10
animCurveUL12

         2 function calls in 0.013 seconds
Ordered by: standard name
 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
         1    0.013    0.013    0.013    0.013 :1()
         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects} 

Yes this seems like regular maya commands are faster, but you’ve got to remember, this is just a couple of nodes. What if you had to loop through a large set of vertices on a piece of geometry? I’ve created two functions: One loops through vertices by using standard maya cmds, the other using OpenMaya MItMeshVertex:

def get_mesh_points(object_name):
    """
    Mesh points iterator.
    :param object_name: <str> object name.
    :return: <list> vertex positions
    """
    mesh_fn, mesh_ob, mesh_dag = get_mesh_fn(object_name)
    mesh_it = om.MItMeshVertex(mesh_ob)
    mesh_vertexes = []
    print("[Number of Vertices] :: {}".format(mesh_fn.numVertices()))
    while not mesh_it.isDone():
        mesh_vertexes.append(mesh_it.position())
        mesh_it.next()
    return mesh_vertexes


def get_mesh_points_cmds(object_name):
    """
    Mesh points iterator.
    :param object_name:  <str> object name. 
    :return: <list> vertex positions 
    """
    mesh_vertices = cmds.ls(object_name + '.vtx[*]', flatten=1)
    print("[Number of Vertices] :: {}".format(len(mesh_vertices)))
    nums = []
    for i in mesh_vertices:
        nums.append(i)
    return nums

Now let’s see how cProfiler works on them both by iterating through 429510 vertices mesh:

 # run cmdsiterator 
cProfile.run("object_utils.get_mesh_points_cmds('Emmanuel_Guevarra_Ian_McKellen_medres:Group2')")
 [Number of Vertices] :: 429510
          429516 function calls in 1.629 seconds
 Ordered by: standard name
 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
         1    0.019    0.019    1.629    1.629 :1()
         1    0.038    0.038    1.609    1.609 object_utils.py:128(get_mesh_points_cmds)
         1    1.548    1.548    1.548    1.548 {built-in method ls}
         1    0.000    0.000    0.000    0.000 {len}
    429510    0.023    0.000    0.023    0.000 {method 'append' of 'list' objects}
         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
         1    0.000    0.000    0.000    0.000 {method 'format' of 'str' objects}
# run OpenMaya iterator
cProfile.run("object_utils.get_mesh_points('Emmanuel_Guevarra_Ian_McKellen_medres:Group2')")
 [Number of Vertices] :: 429510
          1718065 function calls in 0.860 seconds
 Ordered by: standard name
 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
         1    0.059    0.059    0.860    0.860 :1()
         1    0.000    0.000    0.000    0.000 OpenMaya.py:2790(init)
         1    0.000    0.000    0.000    0.000 OpenMaya.py:5304(init)
         1    0.000    0.000    0.000    0.000 OpenMaya.py:7701(init)
         1    0.000    0.000    0.000    0.000 OpenMaya.py:9666(init)
         1    0.313    0.313    0.801    0.801 object_utils.py:112(get_mesh_points)
         1    0.000    0.000    0.000    0.000 object_utils.py:142(get_mesh_fn)
         1    0.000    0.000    0.000    0.000 {isinstance}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.MDagPath_extendToShapeDirectlyBelow}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.MDagPath_node}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.MDagPath_swiginit}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.MFnMesh_numVertices}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.MFnMesh_swiginit}
    429511    0.030    0.000    0.030    0.000 {maya._OpenMaya.MItMeshVertex_isDone}
    429510    0.035    0.000    0.035    0.000 {maya._OpenMaya.MItMeshVertex_next}
    429510    0.393    0.000    0.393    0.000 {maya._OpenMaya.MItMeshVertex_position}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.MItMeshVertex_swiginit}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.MObject_hasFn}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.MSelectionList_add}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.MSelectionList_getDagPath}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.MSelectionList_swiginit}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.new_MDagPath}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.new_MFnMesh}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.new_MItMeshVertex}
         1    0.000    0.000    0.000    0.000 {maya._OpenMaya.new_MSelectionList}
    429510    0.030    0.000    0.030    0.000 {method 'append' of 'list' objects}
         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
         1    0.000    0.000    0.000    0.000 {method 'format' of 'str' objects}

for cmds and OpenMaya, 1.629 seconds and 0.860 seconds respectively.