Teaming up with @voMethod on this one. Mr. Seth Gibson offered some help and extra content for my GDC Power Python talk. Unfortunately, I was limited to 50 minutes so I did not get time to illustrate some great examples he offered. Now we have the time, so here is a nice piece on subclassing PyMEL nodes.
PyMEL has created classes for most common node types in Maya. This offeres the tremendous advantage of being able to instanciate the class while you create an object or node in Maya. With this class instance, you can now act upon that node using methods attached to your class instance. For further explanation of why and how this works, please refer to the Power Python GDC talk. The PyMEL team has not wrapped EVERY Maya node into a custom PyMEL class, but they have offered a way for you to do that on your own when you feel it is necessary using the registerVirtualClass function in the factories module. Seth put a great sample script together illustrating how to do this. He’s picked Maya’s Network node as a prime example to subclass PyMEL with. Let’s look at it section by section:
import os import pymel.all as pm import xml.etree.cElementTree as cetree class MyVirtualNode(pm.nt.Network): """ this is an example of how to create your own subdivisions of existing nodes. """ @classmethod def list(cls,*args,**kwargs): """ Returns all instances the node in the scene """ kwargs['type'] = cls.__melnode__ return [ node for node in pm.ls(*args,**kwargs) if isinstance(node,cls)]
Line 5 creates a class that inherits from PyMEL’s Network class which is a generic Maya node made for holding metadata in Maya’s Dependency Graph.
Line 7/8 declares the first method as a classmethod. These are a little mysterious to me but from what I can tell, it means that they are methods that are callable without having to actually instantiate the main class. They seem to be simple utilities and cannot refer to any variables in other methods in the class because they are not declared with the ‘self’ argument, so there is no ‘self’ attributes inside of the classmethod. The method’s function is to lists all nodes of ‘MyVirtualNode’ type. Because Line 7 made it a special classmethod, you can call it like this:
Since there are no paranthesis after the word ‘MyVirtualNode’, the class itself is not being instanciated. It is a direct call to the method inside of it. Next …
# @classmethod def _isVirtual( cls, obj, name ): """PyMEL code should not be used inside the callback, only API and maya.cmds. """ fn = pm.api.MFnDependencyNode(obj) try: if fn.hasAttribute('myString'): plug = fn.findPlug('myString') if plug.asString() == 'virtualNode': return True return False except: pass return False
This method defines the test case for PyMEL to see if your node comes back as the type you’re defining via an ‘isinstance’ test. In this case, Seth created a custom attr caleld myString and defined it with ‘virtualNode’. This is an internal signifier on the node that it is of our ‘MyVirutalNode’ class.
# @classmethod def _preCreateVirtual(cls, **kwargs ): """This is called before creation. python allowed.""" return kwargs @classmethod def _postCreateVirtual(cls, newNode ): """ This is called after creation, pymel/cmds allowed. Treat newNode as self.""" newNode.addAttr('myName', dt='string') newNode.addAttr('myString', dt='string') newNode.myString.set('virtualNode') newNode.addAttr('myFloat', at='float') newNode.myFloat.set(.125) newNode.addAttr('myConnection', at='message') # Getters and setters are not required, we have these here for unittesting def get_my_name(self): return self.myName.get() def set_my_name(self,value): self.myName.set(value) def get_my_string(self): return self.myString.get() def set_my_string(self, value): self.myString.set(value) def get_my_float(self): return self.myFloat.get() def set_my_float(self, value): self.myFloat.set(value) def get_my_connection(self, index=0): return self.myConnection.listConnections()[index] def set_my_connection(self, node): self.myConnection.connect(node.blackBox) def toXML(self): xml = cetree.Element('MyVirtualNode') xml.set('Name', self.get_my_name()) return xml
We have a pre- and post-creation method and several utility methods that are attached to our new class.
Seth shows another class MyVirtualSubNode which inherits from MyVirtualNode:
class MyVirtualSubNode(MyVirtualNode): SUBNODE_TYPE = 'subNodeTypeA' @classmethod def list(cls,*args,**kwargs): """ Returns all instances of all characters in the scene """ kwargs['type'] = cls.__melnode__ return [ node for node in pm.ls(*args,**kwargs) if isinstance(node,cls)] @classmethod def _isVirtual( cls, obj, name ): """PyMEL code should not be used inside the callback, only API and maya.cmds. """ fn = pm.api.MFnDependencyNode(obj) try: if fn.hasAttribute('myString'): plug = fn.findPlug('myString') if plug.asString() == 'virtualNode': if fn.hasAttribute('mySubNodeType'): plug = fn.findPlug('mySubNodeType') if plug.asString() == cls.SUBNODE_TYPE: return True return False except: pass return False @classmethod def _postCreateVirtual(cls, newNode ): """ This is called before creation, pymel/cmds allowed.""" MyVirtualNode._postCreateVirtual(newNode) newNode.addAttr('mySubNodeType', dt='string') newNode.mySubNodeType.set('subNodeTypeA')
This is very similar except the _isVirtual method tests for a special attribute called mySubNodeType. The _postCreateVirtual method first runs MyVirtualNode’s _postCreateVirtual, then adds its custom attr to it.
The last two lines actually register these classes into PyMEL’s system:
pm.factories.registerVirtualClass( MyVirtualNode, nameRequired=False ) pm.factories.registerVirtualClass( MyVirtualSubNode, nameRequired=False )
So go ahead and subclass away. Embrace PyMEL’s potential.
Thanks Seth for sharing more of PyMEL’s awesomeness. Too bad I couldn’t squeeze it into the talk, but those of you who really care about this stuff made your way here afterall.
Get the scripts in Downloads.
For further reading, a heavy-duty post on the subject over at Python_Inside_Maya: http://bit.ly/iJTCFC