Colin Knueppel
Colin Knueppel

Reputation: 103

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.

Upvotes: 1

Views: 1062

Answers (2)

Colin Knueppel
Colin Knueppel

Reputation: 103

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.

Upvotes: 0

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 96172

Not only is the first example invalid syntax, what you are attempting to do is fundamentally unsound: do not mix list-comprehensions with state-change (i.e. assigning to an object attribute). Whatever node is, it seems the best way to deal with your issue is to add a parameter to node.__init__ that allows you to set path when you instantiate a node object. And then you could do things = [node(x) for x in cmds.ls(sl=1, l=1)]

So, the most basic approach using a single positional argument to __init__:

class Node(object):
    def __init__(self, path):
        self.path = path
...

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

More importantly, though, using a for-loop is perfectly Pythonic. Trying to get your code to be all one-liners is a fundamentally misguided. Here is how I would work with what you have already and make it more Pythonic:

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

The above is perfectly pythonic as it is...

Upvotes: 1

Related Questions