Search code examples
pythonmaya

Maya Python single line class instance and class variable assignment?


I want to do something like this,

things = [ node().path = x for x in cmds.ls(sl=1,l=1)]

but I'm getting an invalid syntax error. So I've had to use this instead,

things = []
for i, thing in enumerate(cmds.ls(sl=1,l=1)):
    things.append(node())
    things[i].path = thing

The first invalid code is nice clean and short. The second one is tedious. How can I get some code that allows me to assign a class variable in the same creation line WITHOUT using an initialization. I'm avoiding initialization because this class is going to be inherited into many other classes across multiple files, and my previous version that uses initializations breaks horrible when imported into too many packages, causing unbound method errors.


Solution

  • The reasons for the original question are related to this problem: Maya Python: Unbound Method due to Reload(). The solution I've found is essentially to rework my tracking method to not need an initialization, and then, to avoid the unbound error, I create the initialization sequence nearest to its use.

    trackingMethod file:

    import maya.cmds as cmds
    import maya.OpenMaya as om
    
    class pathTracking(object):
        def setPathing(self, instance):
            sel = om.MSelectionList()
            sel.add(instance.nodeName)
            try:
                instance.obj = om.MObject()
                sel.getDependNode(0, instance.obj)
            except:
                cmds.warning(instance.nodeName + " is somehow invalid as an openMaya obj node.")
            try:
                instance.dag = om.MDagPath()
                sel.getDagPath(0,instance.dag)
            except:
                pass
        def __set__(self, instance, value):
            if isinstance(value,dict):
                if "dag" in value:
                    instance.dag = value["dag"]
                if "obj" in value:
                    instance.obj = value["obj"]
                if "nodeName" in value:
                    instance.nodeName = value["nodeName"]
                    self.setPathing(instance)
            else:
                if isinstance(value, basestring):
                    instance.nodeName = value
                    self.setPathing(instance)
        def __get__(self, instance, owner):
            if instance.dag and instance.dag.fullPathName():
                return instance.dag.fullPathName()
            return om.MFnDependencyNode(instance.obj).name()
    
    class exampleNode(object):
        path = pathTracking()
        dag = None
        obj = None
        nodeName = ""
        someVar1 = "blah blah"
    
        def initialize(self,nodeName,obj,dag):
            if obj or dag:
                self.obj = obj
                self.dag = dag
            elif nodeName:
                self.path = nodeName
            else:
                return False
            return True
    

    other file:

    import trackingMethod as trm
    
    circleExample(trm.exampleNode):
        def __init__(self,nodeName="",dag=None,obj=None):
            if not self.initialize(nodeName,obj,dag)
                self.path = cmds.circle()[0]
    

    and with this method I can do

    circles = [circleExample(nodeName=x) for x in cmds.ls(sl=1,l=1)]
    

    PS. I ran into some stuff that required the class to be initialized before some of its bits could be created. Below is a custom dict that required I pass an instance of self to it at the time the dict is created. Recreating these dict structures in every class that interacts with a transform would have been tedious. The solution was to put these dependent class initializations into a function in the transform class. That way the final class inherits the function to create the dicts and can call it in their init. This avoids a whole russian nesting doll of init statements that breaks when you have multiple files inheriting from a single class.

    While this solution may seem obvious to some, I was just about pulling out hair to think of a way around a chicken egg situation of needing to init the class to get self, but not being able to init class due to unbound method errors.

    class sqetDict(dict):
        def __init__(self,instance,*args,**kwargs):
            self.instance = instance
            dict.__init__(self,*args,**kwargs)
    
        def __getitem__(self, key):
            thing = dict.__getitem__(self,key)
            if key in self and isinstance(thing,(connection,Attribute,xform)):
                return thing.__get__(self.instance,None)
            else:
                return dict.__getitem__(self,key)
    
        def __setitem__(self, key, value):
            thing = dict.__getitem__(self,key)
            if key in self and isinstance(thing,(connection,Attribute,xform)):
                thing.__set__(self.instance,value)
            else:
                dict.__setitem__(self,key,value)
    

    These dicts would be initialized like this:

    def enableDicts(self):
        self.connection = sqetDict(self, {"txyz": connection("translate"), "tx": connection("tx"),
                                          "ty": connection("ty"), "tz": connection("tz"),
                                          "rxyz": connection("rotate"),
                                          "rx": connection("rx"), "ry": connection("ry"), "rz": connection("rz"),
                                          "sxyz": connection("scale"),
                                          "sx": connection("sx"), "sy": connection("sy"), "sz": connection("sz"),
                                          "joxyz": connection("jointOrient"),
                                          "jox": connection("jox"), "joy": connection("joy"), "joz": connection("joz"),
                                          "worldMatrix": connection("worldMatrix"),
                                          "worldInvMatrix": connection("worldInverseMatrix"),
                                          "parentInvMatrix": connection("parentInverseMatrix")})
        self.value = sqetDict(self, {"txyz": Attribute("translate", "double3"),
                                     "tx": Attribute("tx", "float"), "ty": Attribute("ty", "float"),
                                     "tz": Attribute("tz", "float"),
                                     "rxyz": Attribute("rotate", "double3"),
                                     "rx": Attribute("rx", "float"), "ry": Attribute("ry", "float"),
                                     "rz": Attribute("rz", "float"),
                                     "sxyz": Attribute("scale", "double3"),
                                     "sx": Attribute("sx", "float"), "sy": Attribute("sy", "float"),
                                     "sz": Attribute("sz", "float"),
                                     "joxyz": Attribute("jointOrient", "double3"),
                                     "jox": Attribute("jox", "float"), "joy": Attribute("joy", "float"),
                                     "joz": Attribute("joz", "float"),
                                     "rotOrder": Attribute("rotateOrder", "string"),
                                     "worldMatrix": Attribute("worldMatrix", "matrix"),
                                     "worldInvMatrix": Attribute("worldInverseMatrix", "matrix"),
                                     "parentInvMatrix": Attribute("parentInverseMatrix", "matrix"),
                                     "rotatePivot": Attribute("rotatePivot", "double3"),
                                     "visibility": Attribute("visibility", "long")})
        self.xform = sqetDict(self, {"t": xform("t"), "ro": xform("ro"), "s": xform("s")})
    

    my connection class does the cmds.connectAttr when sent a value, and it returns the various attributes of a connection as a dict like {"in": "in connection", "out":["outConn1","outCon2",etc..], "path":"fullpath name to attribute"}. So you could do something like, thingA.connection["txyz"] = thingB.connection["txyz"]["path"] to connect the relative translates of two objects.

    My Attribute class allows set and get of attribute values, like temp = thing.value["txyz"] results in temp = (value,value,value), and thing.value["txyz"]=(0,0,0) would zero the translate.

    xform does the values thing but in absolute world space values.