WorkBench – Progress and Changes

Okay, so I have made a fair amount of progress. I’ve finished the templates for nodes and commands and made good progress on actual implementation – and I’ve made some really cool discoveries along the way!

One major change I’ve made is that I’m basically ignoring the standalone mode option for now. Previously I was trying to write implementation for it by storing all of the arguments passed to __init__ on every class as an attribute. That, frankly, was a waste of time. Firstly my standalone version, when I finally get around to creating it, will have its own system for holding node attributes and it certainly won’t be so fragile as relying on the python objects. Secondly this assignment is about the Maya implementation, so worrying about the standalone (except so far as ensuring that I write abstractly enough to make implementation a fairly painless affair) is pointless at this stage.

I’ve also borrowed my naming delimiter system (i.e. the ability to add characters or numbers when naming more than one object) from the Control Projection Tool and turned it into a generator. I really should have done that the first time around, but honestly I’d made the mistake of discounting generators until now. So hurray for breaking new ground! My first generator.
I grabbed the rest of my naming util libraries from my earlier rigger concept and rejigged them to only deal in strings. Then the rename method on nodes points to those if it needs to.

That’s another thing; most of my commands (e.g. basically all attribute commands, hide/unhide, duplicate, delete, etc) actually point to the methods with those same names on the Node or Attribute objects. I originally said it would be the other way around, but this is just so much cleaner. It makes a lot more sense for the nodes to know what commands work on them (primarily by either having or not having those methods) than it does for the commands to have that information.
In fact I’m actually considering not keeping commands for which methods exist at all! My reasoning is this:
It is far more logical to (for example) set the value on an attribute by typing node.attribute.set(value) than it is to type setAttribute(node.attribute, value). If people have both options, there is a very real possibility that they will use commands sometimes and methods other times, which could result in rather haphazard code. So if I remove the commands for which there are methods already then I will be helping people maintain tidy code.
The argument for the other side is that anyone who writes scripts for Maya and doesn’t use pymel will be used to using commands. It might be easier for those people to transition to Workbench (and therefore make it a more appealing solution) if the scripting interface is similar to what they’re accustomed to.

Speaking about the Maya Commands (like that segue?), that’s what I’m currently primarily using to interface with Maya. For anything to do with attributes I’m using the API (thanks to Austin J Baker for some amazingly helpful posts on that topic). I’m also using the API to get node names as I mentioned in a previous post. I’m aiming to update it to exclusively (or nearly exclusively) use the API later on so that building heavier rigs will be quicker, but that’ll depend partially on how quickly I learn the API and benchmark tests once I have this version sorted.

Using the API for node names has one really really useful side-effect – I can wrap nodes by getting the function set (the MFn stuff I talked about in an earlier post) for an existing node in Maya, instead of the set of the newly created node. My getSelection and listNodes commands takes advantage of this by wrapping nodes before returning them – which means that I can do stuff like getSelection()[0].delete() (i.e. I can use node methods straight from the returned selection).

There are also some things that I have changed which are fairly significant. I’m gonna list them by saying how I was doing things before and how I’m doing it now.

Before: for wrapping nodes, I was just letting my name property setter take care of it. My __init__ for the Node base class was really messy as a result, since I was checking if I was wrapping a node twice (once before setting the name and another before calling the build method). It also meant every single time I requested the name it was getting the maya function set again, which is far from efficient. Also that’s just not a logical place to put the node wrapping function – which is what it should be. It’s own function (well, method…).Now: I’ve put all of the function set stuff into its own wrapNode method. Now __init__ only has to check if it’s wrapping a node once. If it is wrapping a node, it’ll call wrapNode and pass the full path name of the node it wants to wrap. If it’s not, it’ll call build. After the build method has created whatever it’s creating, it’ll call wrapNode and pass the newly created node, so that I have its function set. tadaaa~ nice and tidy. Everything’s squared away in its own logical space.

Before: because all of my node classes inherit from the base Node class (which obviously can’t expect a named parameter for every node type ’cause that’d be silly), and because I was trying to implement the standalone mode pre-emptively, I had been storing all arguments passed to __init__ as attributes of the instanced object. I only really need those arguments until the node is built, and it’s built on instantiation (via the build method), so holding onto those arguments as attributes beyond that is a waste of resources.Now: I remembered that I can catch named arguments that are not explicitly required as **kwargs. And I can pass them as **kwargs as well. So the __init__ method of each class passes its specifically required named parameters to super(class, self).__init__, which catches and passes them on to the build method (which I only define on Node) as **kwargs, which in turn catches them and passes them to the _buildMaya method (which is defined in every node class and specifies exactly how to build each specific node in Maya) as (you guessed it) **kwargs. _buildMaya then catches them as the specific and appropriate named arguments and can use them as though they were passed directly! It sounds like a lot of run-around, but really it’s a lot cleaner than what I was doing before.

Here is an example of how I am handling node wrapping and getting **kwargs through to _buildMaya:

class Constraint(Node):
def __init__(self, name=None, wrapNode=None, node=None, parent=None, etcetc):
#some specific things for this node go here
#now we send all of those arguments to the superclass of Constraint (which is Node) which will catch them as **kwargs and pass them to build etc etc.
super(Constraint, self).__init__(name=name, wrapNode=wrapNode, node=node, parent=parent, etcetc)

it seems that my formatting is being eaten by wordpress…. another reason for me to make my own backend. Anyway, just pretend that the appropriate white space is in there….

You could sum up the nodes and commands section of this project as essentially being a rewrite of the rigging-related portions of pymel. The key difference between this and pymel is (other than the extreme difference in scope) that I’m not inheriting any of the maya API classes like pymel does. That allows me to be more application agnostic in my approach, which means later I can very easily add in modes for 3ds Max, Softimage, Blender, etc by just adding in their wrapNode and build info and any appropriate stuff in the commands.

The huge benefit there (other than meaning you can use the same commands between applications) is that when I start building my components and modules (which are essentially scripts for connecting specific nodes in specific ways to create (for example) limb control rigs or stretchy IK/FK chains, or what-have-you) I won’t have to even think about what application I’m writing those for because I will have already done that. I’ll only be using my own commands and nodes, so they’ll automatically work on all applications for which I have written node and command functionality.