Maya: List nodes by attribute value

I haven’t found a good way to do this, at least not a fast one. I need to list nodes by an attribute value. Other than getting all nodes of the type i want and then iterating through them checking their node value, is there a faster way?

On a couple thousand nodes, it’s taking a little too long.

Is there something in the API that might help?

Thanks

Hack way - regex the .ma file, if the scene isn’t live. Or export all, then regex. Maaaay be faster?

How are you selecting your objects to iterate over? And how long is ‘too long’?

If I create 10,000 transforms with the attribute “.myattr” and give it a numeric value, then select by the wildcard ls("*.myattr", o=True) and return only those whose myattr attribute == 10, it takes .3 seconds. Is that too long, or is yours much longer? Are you using a more complex wildcard to select the objects?

That being said, you can get improved performance using the API:

import time
import maya.api.OpenMaya as om2
import maya.cmds as cmds

def setup():
    grp = cmds.createNode('transform', n='topgrp')
    nodes = []
    for i in range(5000):
        t = cmds.createNode('transform')
        cmds.addAttr(t, ln='myattr', dv=i%200)
        nodes.append(t)
    cmds.parent(nodes, grp)

def test():
    ## Using maya.cmds
    #
    start = time.time()
    
    res = []
    for i in cmds.ls('*.myattr', o=True):
        if cmds.getAttr('%s.myattr' % i) == 10:
            res.append(i)
    
    end = time.time()
    print 'maya.cmds: %s' % (end - start)
    
    ## Using API 2.0
    #
    start = time.time()
    
    search = "*.myattr"
    sel = om2.MGlobal.getSelectionListByName(search)
    depFn = om2.MFnDependencyNode()
    
    res = []
    for i in xrange(sel.length()):
        mObj = sel.getDependNode(i)
        depNode = depFn.setObject(mObj)
        if depNode.findPlug("myattr", True).asInt() == 10:
            res.append(depNode.name())
    
    end = time.time()
    print 'api: %s' % (end - start)

# maya.cmds: 0.15700006485
# api: 0.0460000038147

If you’re not on 2012 or later (or maybe 2011?) you’ll have to use the 1.0 API, which will be slightly different. I don’t know how comfortable you are with it but if you need some help piecing that together I’m happy to help.
If your attribute is not an integer attribute, you’ll need to change the call to asInt() to one that suits your needs. Check the MPlug doc page for info on other available methods. I don’t know how to link to maya api docs (frames yay) but there are a number of methods (asInt, asBool, asFloat, …)

1 Like

not to get the objects with the attribute, but to get the objects where the attribute is equal to a value.

say you wanted to get all transforms that have a scaleX > 10.0, or equal to 10.0 or whatever.

I’m guessing theres no quick way to get this?

filtering to objects with the attrib is the right first step either way

I think if you select them in a determinate order and call getAttr (".yourAttribHere"), you’ll get back all of the results in selection order.

oh awesome, i didn’t know you could specify nodes and a common attribute. the docs don’t mention any of that syntax :slight_smile:

This will probably speed things up a bit. Before I had to iterate over every object, get the attribute and compare it.

Thanks everyone

I always forget that getAttr can be called on multiple selections.

I just ran a test using 4 methods:

  • Getting objects with a wildcard and looping over them and calling getAttr
  • Getting objects with a wildcard, selecting them, and calling getAttr on the full selection
  • Calling getAttr on the current selection
  • The API, using a wildcard

The API is still the fastest method (unless some of methodology isn’t optimized). Calling getAttr(’.myAttr’) on a full selection is the slowest method if you don’t already have the objects selected and have to select them using a wildcard. If you already have the objects selected it’s about 10x faster, but still slower than the API. Granted they are all pretty damn close, and I’m getting less than .2 seconds variance with 10,000 objects, but I got curious and wanted to compare.

def setup(nodecount=10000):
    """ Creates `nodecount` number of transforms and sets their scale between 1 and 11 """

    grp = cmds.createNode('transform', n='topgrp')
    nodes = []
    for i in range(nodecount):
        t = cmds.createNode('transform', n='testobj#')
        cmds.xform(t, s=(i%12, 1, 1))
        nodes.append(t)
    cmds.parent(nodes, grp)

def using_mayacmds(limit=10):
    ## Using maya.cmds
    #
    start = time.time()
    
    res = []
    for i in cmds.ls('testobj*'):
        if cmds.getAttr('%s.scaleX' % i) >= limit:
            res.append(i)
    
    end = time.time()
    print 'maya.cmds: %s' % (end - start)

    return res

def using_getattr_wildcard(limit=10):
    start = time.time()

    objs = cmds.ls('testobj*')
    cmds.select(objs, r=True)
    values = cmds.getAttr('.scaleX')
    res = []
    for i, v in enumerate(reversed(values)):
        if v >= limit:
            res.append(objs[i])

    end = time.time()
    print 'getattr with wildcard: %s' % (end - start)

    return res
    
def using_getattr_currentsel(limit=10):
    start = time.time()

    objs = cmds.ls(sl=True)
    values = cmds.getAttr('.scaleX')
    res = []
    for i, v in enumerate(reversed(values)):
        if v >= limit:
            res.append(objs[i])

    end = time.time()
    print 'getattr selection: %s' % (end - start)

    return res

def using_api(limit=10):
    ## Using API 2.0
    #
    start = time.time()
    
    search = "testobj*"
    # gets all nodes that match the "testobj*" search pattern
    sel = om2.MGlobal.getSelectionListByName(search)
    depFn = om2.MFnDependencyNode()
    
    res = []
    # Loop through each node
    for i in xrange(sel.length()):
        mObj = sel.getDependNode(i)
        depNode = depFn.setObject(mObj)
        # Get the scaleX attribute as a float, if >= 10 append it
        if depNode.findPlug("scaleX", True).asFloat() >= limit:
            res.append(depNode.name())
    
    end = time.time()
    print 'api: %s' % (end - start)

    return res

setup(10000)
mc = using_mayacmds()
gas = using_getattr_currentsel()
gaw = using_getattr_wildcard()
api = using_api()

print "all equal: %s" % (mc == gas == gaw == api)

# maya.cmds: 0.31200003624
# getattr selection: 0.141000032425
# getattr with wildcard: 2.23399996758
# api: 0.0940001010895
# all equal: True

EDIT: Apparently getAttr on a selection returns starting with the most recently selected object, which is the opposite of ls(sl=True) does, so I reversed the getAttr values.

for the using_getattr_wildcard test you can just pass the objs list to the gatAttr call directly, no need to select:


def using_getattr_wildcard(limit=10):
    start = time.time()

    objs = cmds.ls('testobj*')
    values = cmds.getAttr(objs, '.scaleX')
    res = []
    for i, v in enumerate(reversed(values)):
        if v >= limit:
            res.append(objs[i])

    end = time.time()
    print 'getattr with wildcard: %s' % (end - start)

    return res

nevermind, i must have had a selection. It doesn’t work like that.