Search code examples
pythonmayapymel

How are attributes accessed in PyMEL?


There are multiple ways to access attributes of a PyNode such as Transform, which has the attributes translation, rotation and so on.

import pymel.core as pm

cube = pm.polyCube()[0]

cube.translationX.set(1)
cube.translation.translationX.set(2)
cube.tx.set(3)
cube.t.tx.set(4)

# and more

What I don't understand is how the attributes are found because I cannot find any documentation that lists the attributes? Does Transform have all those attributes where each points at the corresponding, underlying attribute of the DAG node? Where does the mapping happen so the right attribute is found?


Solution

  • You can just use :

    cube.listAttr()
    

    Im not sure they wrapped all attributes.

    I have a simple cmds wrapper myself where Im just adding dynamicly any attributes of a node by overriding : getitem, getattr

    here is the full code on git : https://github.com/DrWeeny/stackoverflow/blob/master/59185039.py it is just an example

    class MAttr(object):
        """Represent a maya attribute
    
        Args:
            node (str): a string for the maya node affected
            attr (str): a string that represent the attribute
        Attributes:
            attr_bypass (str): regex to bypass the compound attributes, because i need to connect and getattr
        """
        attr_bypass = re.compile('\[(\d+)?:(\d+)?\]')
    
        def __init__(self, node, attr='result'):
            self.__dict__['node'] = node  #: str: current priority node evaluated
            self.__dict__['attribute'] = attr  #: str: current priority node evaluated
            self.__dict__['idx'] = 0  #: str: current priority node evaluated
    
        def __getitem__(self, item):
            """
            getitem has been overrided so you can select the compound attributes
            ``mn = MayaNode('cluster1')``
            ``mn.node.weightList[0]``
            ``mn.node.weightList[1].weights[0:100]``
    
            Notes:
                the notations with list[0:0:0] is class slice whereas list[0] is just and int
    
            Args:
                item (Any): should be slice class or an int
    
            Returns:
                cls : MAttr is updated with the chosen index/slice
            """
            if isinstance(item, int):
                self.__dict__['attribute'] = '{}[{}]'.format(self.attr, item)
            else:
                if not item.start and not item.stop and not item.step:
                    item = ':'
                else:
                    item = ':'.join([str(i) for i in [item.start, item.stop, item.step] if i != None])
                self.__dict__['attribute'] = '{}[{}]'.format(self.attr, item)
            return self
    
        def __getattr__(self, attr):
            """
            __getattr__ is overriden in order to select attributes
            Args:
                attr (str): name of the attribute, even if we loop throught,
    
            Returns:
                str: it join all the attributes that has been chained : weightList[0].weight
    
            """
            myattr = '{}.{}'.format(self.attr, attr)
            if myattr in self.listAttr(myattr):
                return MAttr(self._node, '{}.{}'.format(self.attr, attr))
            else:
                return self.__getattribute__(attr)
    
    class MayaNode(object):
        """Represent a maya node as a class like pymel
    
        Only provide the name as a string, if you use preset, you can create a new node
    
        Note:
            Should use maya api for getting the node
    
        Args:
            name (str): a string for the maya node you want to encapsulate
            preset (:obj:`dict`, optional): Used for creating a new node from 'attrPreset'
                you can also just specify the nodeType with a string instead
    
        Attributes:
            maya_attr_name (maya_data): Any attribute from the name will look for actual maya attribute
    
        """
    
        def __init__(self, name, preset={}, blendValue=1):
    
            # this dict method is used to avoid calling __getattr__
            self.__dict__['node'] = name  #: str: current priority node evaluated
            self.__dict__['item'] = 1  #: int: can be either 0 or 1 and should be exented with Mesh or Cluster
            if preset:
                targ_ns = name.rsplit(':', 1)[0]
                self.loadNode(preset, blendValue, targ_ns)
    
        def __getitem__(self, item):
            """
            getitem has been overrided so you can select the main nodes sh or tr so when you do:
            ``mn = MayaNode('pCube1')``
            ``mn[0].node`` result as the transform
            ``mn[1].node`` result as the shape
    
            Note:
                By default ``mn.node`` is the transform
            """
            return self.setNode(item)
    
        def __getattr__(self, attr):
            """
            getattr has been overrided so you can select maya attributes and set them
            if you type an attribute, it will try to find if it exists in either shape or transform
            if it exists in both, it will always warn you that it returns shape in priority
            ``mn = MayaNode('pCube1')``
            ``mn.translateX = 10`` result in doing a cmds.setAttr
            ``mn.translateX.set(10)``
            ``mn.translateX.get()`` note that you to use get in order to do getAttr otherwise it __repr__ or __str__
    
            ``mn = MayaNode('cluster1')``
            ``mn.weightList[1].weights.get()`` is equivalent of cmds.getAttr('cluster1.weightList[1].weights[:]')
    
            Note:
                You cant have the value without .get()
            """
            if attr in self.listAttr(attr):
                return MAttr(self.node, attr)
            elif attr in self.__dict__:
                return self.__dict__[attr]
            else:
                try:
                    return self.__getattribute__(attr)
                except AttributeError:
                    cmds.warning('{} doesn\'t exists, return None instead')
                    return None