OpenMaya utilities

These are my findings when working with Maya attributes through the python API.

When creating a dag node from an MObject it uses the first one it encounters, which may appear random. Therefore using an MDagPath is better.

When selecting by name (MGlobal.getSelectionListByName) it will return an error if the node is not unique, but a path may be specified. cmds.ls(node_name,l=True) may help in finding the full path.

def getNodeFromName(in_name):
    selector = MSelectionList()
    MGlobal.getSelectionListByName(in_name, selector)
    node = MObject()
    selector.getDependNode(0, node)
    return node


def getDependNodeFromName(in_name):
    return MFnDependencyNode(getNodeFromName(in_name))


def getDagPathFromName(in_name):
    selector = MSelectionList()
    MGlobal.getSelectionListByName(in_name,selector)
    path = MDagPath()
    selector.getDagPath(0, path)
    return path

Attributes contain all information except the value, for this you need the plug. The plug has methods to return the value in the right type, but the plug itself has no clue whatsoever it’s own value consists of.

Hence you need to get the plug’s attribute, from the attribute (MObject) find out the matching function set, which means try everything, to find out the actual type of data. Then with most function sets, you need to find a type, as for example MFnNumericData contains a whole range of numeric data types in its unitType. Not all of these types are covered by the plug and some (like double, float, int) are immediately readable by python and some (like MDistance, MAngle) are not usable straight away.

Also, plugs may be array or compound, containing several child plugs, meaning they can be subdivided and each entry can in theory be a different type of data. Hence a big helper:

def findMPlug(in_node, in_attribute):
    '''
    @param in_node_name: string, unique name of the node,
    meaning the full path if multiple nodes of this name exist
    @param in_attribute_name: string, attribute to find,
    should exist or you'll get errors
    '''
    node = getNodeFromName(in_node)
    return MPlug(node, MFnDependencyNode(node).attribute(in_attribute))


def getPlugValue(in_plug):
    '''
    @param in_plug: MPlug, to get value from
    '''
    plugs = []
    if in_plug.isCompound():
        for i in in_plug.numChildren():
            plugs.append( in_plug.child(i) )
    elif in_plug.isArray():
        for i in in_plug.numElements():
            plugs.append( in_plug.getElementByPhysicalIndex(i) )
    else:
        plugs.append(in_plug)
    
    out = [] #compound list of all data in the plug or its child plugs
    for plug in plugs:
        attr = plug.attribute()
        if attr.hasFn(MFn.kNumericAttribute):
            type = MFnNumericAttribute(attr).unitType()
            if type in (MFnNumericData.kBoolean, MFnNumericData.kByte):
                out.append(plug.asBool())
            elif type == MFnNumericData.kChar:
                out.append(plug.asChar())
            elif type == MFnNumericData.kShort:
                out.append(plug.asShort())
            elif type in (MFnNumericData.kInt, MFnNumericData.kLong):
                out.append(plug.asInt())
            elif type == MFnNumericData.kFloat:
                out.append(plug.asFloat())
            elif type == MFnNumericData.kDouble:
                out.append(plug.asDouble())
        elif attr.hasFn(MFn.kUnitAttribute):
            type = MFnUnitAttribute(attr).unitType()
            if type == MFnUnitAttribute.kAngle:
                out.append(plug.asMAngle())
            elif type == MFnUnitAttribute.kDistance:
                out.append(plug.asMDistance())
            elif type == MFnUnitAttribute.kTime:
                out.append(plug.asMTime())
        elif attr.hasFn(MFn.kTypedAttribute):
            type = MFnTypedAttribute(attr).attrType()
            if type == MFnData.kString:
                out.append(plug.asString())
        else:
            #last resort for unimplemented data types
            out.append(plug.asMObject())
    return out

Also without needing plugs, as I tried that first before finding out plugs contained the data not attributes; but it may be useful anyway to turn a node’s attribute into… an attribute rather than an MObject.

def getAttrFn(in_attrobj):
    '''
    @param in_attrobj: MObject that has the MFnAttribute functionset
    '''
    if in_attrobj.hasFn(MFn.kCompoundAttribute):
        return MFnCompoundAttribute
    elif in_attrobj.hasFn(MFn.kEnumAttribute):
        return MFnEnumAttribute
    elif in_attrobj.hasFn(MFn.kGenericAttribute):
        return MFnGenericAttribute
    elif in_attrobj.hasFn(MFn.kLightDataAttribute):
        return MFnLightDataAttribute
    elif in_attrobj.hasFn(MFn.kMatrixAttribute):
        return MFnMatrixAttribute
    elif in_attrobj.hasFn(MFn.kMessageAttribute):
        return MFnMessageAttribute
    elif in_attrobj.hasFn(MFn.kNumericAttribute):
        return MFnNumericAttribute
    elif in_attrobj.hasFn(MFn.kTypedAttribute):
        return MFnTypedAttribute
    elif in_attrobj.hasFn(MFn.kUnitAttribute):
        return MFnUnitAttribute
    return MFnAttribute


def assignMFnAttribute(in_node_name, in_attribute_name):
    '''
    @param in_node_name: string, unique name of the node,
    meaning the full path if multiple nodes of this name exist
    @param in_attribute_name: attribute to find, should exist
    or you'll get errors
    '''
    attr = getDependNodeFromName(in_node_name).attribute(in_attribute_name)
    return getAttrFn(attr)(attr)

Wow when describing how hard it is compared to just… cmds.getAttr it actually seems really ridiculous. I guess the API really isn’t build to be used like a user, but only to add new nodes and functions – but even then, functions that require rather than set attribute data are a hassle. Difference being that these functions know what attributes and what types of nodes they’re made for, rather than being completely independent like getAttr.

2 thoughts on “OpenMaya utilities

  1. Fair point, I made it brighter for you. Also maybe check your screen calibration; because I never heard any complaints of this sort before…

Leave a Reply

Your email address will not be published. Required fields are marked *