Object Oriented Python in Maya Pt. 1
I have written many tools at different companies, I taught myself, and don’t have a CS degree. I enjoy shot-sculpting, skinning, and have been known to tweak parameters of on-screen visuals for hours; I don’t consider myself a ‘coder’; still can’t allocate my own memory. I feel I haven’t really used OOP from an architecture standpoint. So I bought an OOP book, and set out on a journey of self improvement.
‘OOP’ In Maya
In Maya, you often use DG nodes as ‘objects’. At Crytek we have our own modular nodes that create meta-frameworks encapsulating the character pipeline at multiple levels (characters, characterParts, and rigParts). Without knowing it, we were using Object Oriented Analysis when designing our frameworks, and even had some charts that look quite a bit like UML. DG node networks are connected often with message nodes, this is akin to a pointer to the object in memory, whereas with a python ‘object’ I felt it could always easily lose it’s mapping to the scene.
It is possible now with the OpenMaya C++ API to store a pointer to the DG node in memory and just request the full dag path any time you want it, also PyMel objects are Python classes and link to the DG node even when the string name changes.
“John is 47 Years Old and 6 Feet Tall”
Classes always seemed great for times when I had a bunch of data objects, the classic uses are books in a library, or customers: John is 47 years old and likes the color purple. Awesome. However, in Maya, all our data is in nodes already, and those nodes have attributes, those attributes serialize into a Maya file when I save: so I never really felt the need to use classes.
Although, all this ‘getting’, ‘setting’ and ‘listing’ really grows tiresome, even when you have custom methods to do it fairly easily.
It was difficult to find any really useful examples of OOP with classes in Maya. Most of our code is for ‘constructing’: building a rig, building a window, etc. Code that runs in a linear fashion and does ‘stuff’. There’s no huge architecture, the architecture is Maya itself.
Class Warfare
I wanted to package my information in classes and pass that back and forth in a more elegant way –at all times, not just while constructing things. So for classes to be useful to me, I needed them to synchronously exist with DG nodes.
I also didn’t want to have to get and set the information when syncing the classes with DG nodes, that kind of defeats the purpose of Python classes IMO.
Any time I opened a tool I would ‘wrap’ DG nodes in classes that harnessed the power of Python and OOP. To do this meant diving into more of the deep end, but since that was what was useful to me, that’s what I want to talk about here.
To demonstrate, let’s construct this example:
#the setup loc = cmds.spaceLocator() cons = [cmds.circle()[0], cmds.circle()[0]] meshes = [cmds.sphere()[0], cmds.sphere()[0], cmds.sphere()[0]] cmds.addAttr(loc, sn='controllers', at='message') cmds.addAttr(cons, sn='rigging', at='message') for con in cons: cmds.connectAttr(loc[0] + '.controllers', con + '.rigging') cmds.addAttr(loc, sn='rendermeshes', at='message') cmds.addAttr(meshes, sn='rendermesh', at='message') for mesh in meshes: cmds.connectAttr(loc[0] + '.rendermeshes', mesh + '.rendermesh') |
So now we have this little node network:
Now if I wanted to wrap this network in a class. We are going to use @property to give us the functionality of an attribute, but really a method that runs to return us a value (from the DG node) when the ‘attribute’ is queried. I believe using properties is key to harnessing the power of classes in Maya.
class GameThing(object): def __init__(self, node): self.node = node #controllers @property def controllers(self): return cmds.listConnections(self.node + ".controllers") |
So now we can query the ‘controllers’ attribute/property, and it returns our controllers:
test = GameThing(loc) print test.controllers ##>>[u'nurbsCircle2', u'nurbsCircle1'] |
Next up, we add a setter, which runs code when you set a property ‘attribute’:
class GameThing(object): def __init__(self, node): self.node = node #controllers @property def controllers(self): return cmds.listConnections(self.node + ".controllers") @controllers.setter def controllers(self, cons): #disconnect existing controller connections for con in cmds.listConnections(self.node + '.controllers'): cmds.disconnectAttr(self.node + '.controllers', con + '.rigging') for con in cons: if cmds.objExists(con): if not cmds.attributeQuery('rigging', n=con, ex=1): cmds.addAttr(con, longName='rigging', attributeType='message', s=1) cmds.connectAttr((self.node + '.controllers'), (con + '.rigging'), f=1) else: cmds.error(con + ' does not exist!') |
So now when we set the ‘controllers’ attribute/property, it runs a method that blows away all current message connections and adds new ones connecting your cons:
test = GameThing(loc) print test.controllers ##>>[u'nurbsCircle2', u'nurbsCircle1'] test.controllers = [cmds.nurbsSquare()[0]] print test.controllers ##>>[u'nurbsSquare1'] |
To me, something like properties makes classes infinitely more useful in Maya. For a short time we tried to engineer a DG node at Crytek that when an attr changed, could eval a string with a similar name on the node. This is essentially what a property can do, and it’s pretty powerful. Take a moment to look through code of some of the real ‘heavy lifters’ in the field, like zooToolBox, and you’ll see @property all over the place.
I hope you found this as useful as I would have.