, dev
Why I’m Not Using the MEL and Python Command Layers of Maya
There’s a large amount of things that frustrate me in Maya. The MEL and the Python command layers being two of those that I’d rather not have to ever use again.
But first, here’s a disclaimer. My sole objective here is to genuinely share my—hopefully objective—point of view and experience in the hope that it’ll get some to think about it. I’m not saying that this is the one and only valid approach, I’m just explaining why I’ve chosen this one. Good on you if you manage to create some bulletproof and awesome scripts using the MEL or the Python command layer.
Preface
To draw a quick parallel, both a command layer and an API are also available in Softimage. The question of which one to use has never been asked but by the beginners just getting started.
The API has always been the obvious choice. The command layer has its use but mostly for the artists out there who want to automatize a task by copying the log from the script editor that results from their interactions with the UI.
Funnily, I must be one of the rare living soul to use the Python API of Maya for daily tasks.[1] But does being alone against all necessarily mean that I’m doing it the wrong way? I hope not.
Introducing the MEL Command Layer
Back then, there only was 2 options to code within Maya: MEL or the C++ API. The latter is largely overkilled for most of the tasks of a TD and is also harder to access and iterate. Logically, everyone turned to MEL. But what is MEL?
The official documentation of Maya tells us that MEL is derived from a UNIX shell language. Hence why it is called a command layer. It exposes a bunch of global commands like the ones that we would type into a shell to list the content of the current directory or to move onto another directory. ls -la
, cd ./path/to/other/directory
and the likes. Hey wait, there’s even a ls()
command in MEL too!
I’ve been baffled more than once by the quality of the MEL code that I was seeing. It was written with incredibly terrible coding practices to the point that I was wondering if the persons knew anything about coding at all. Now I finally understand why it was the case. They’re not to blame. That’s MEL’s fault.
My own theory is that MEL has been created with the sole purpose of describing ASCII Maya scenes. It probably wasn’t intented to be used as a scripting language with which one could write any serious code with. It just happened to be the case because the community was lacking of options.
Of course I know that you’ve all made awesome scripts that can do this and that. I wrote some myself, maybe not that awesome. But it’s like trying to sprint with ice skates.
Look at it, all we’ve got is that jungle of nonsensical global commands, each spammed with a ton of confusing flags that are often incompatible with each other. Good luck at figuring out a logic there.
It gets better over time, but not because it starts to make sense. Only because we’re getting used to it. That said, even today I’m still never sure which command from getAttr()
, attributeInfo()
, and attributeQuery()
I should be using when needing to retrieve some information about an attribute. Any hint?
The point being that those commands are not made to make sense as a whole. They all achieve something specific but don’t always play well as part of a team. It’s as if the program to write was a huge puzzle and that we were constantly looking for pieces that might hopefully cover the surface without necessarily fitting into the neighbour pieces.
Once again this becomes almost anecdotal over time so let’s move on to something more important. Knowing how complex the dependency graph in Maya is, how can referring to a node in a scene solely by its name can be a robust approach at all?
Strings Are Not Sexy
To put things back into context, I’m a rigger. I’m spending a lot of time dealing with hierarchies and I like writing modular rigs.
Modular rigs makes it more than likely to have a same hierarchy of nodes being reused in a same scene. Instead of prefixing the name of each of my nodes with a hundredth of characters worth of different information to ensure that their names are unique within the scene, I prefer to stay simple and call them ‘root’, ‘spine’, ‘arm’, ‘whatever’, and so on.
From there, let’s consider a first example.
from maya import cmds
root = cmds.group(name='character', empty=True)
l_arm = cmds.group(name='l_arm', parent=root, empty=True)
r_arm = cmds.group(name='r_arm', parent=root, empty=True)
l_node = cmds.group(name='node', parent=l_arm, empty=True)
r_node = cmds.group(name='node', parent=r_arm, empty=True)
l_child = cmds.group(name='child', parent=l_node, empty=True)
r_child = cmds.group(name='child', parent=r_node, empty=True)
See the problem here? The variable l_node
is pointing to a node named node but can suddenly become invalid simply because another node has been created in the scene, and thus even though the node pointed by l_node
hasn’t been modified in any way.
Of course this snippet of code reflects a terribly poor coding practice and the issue could be avoided much easily.
But the point here is to show a weakness in referring to nodes by their names. As bad as this snippet can be, it is still made of a valid serie of calls and should work in any decent API. It should not require the developer to know how the internals of the API works to workaround an issue that shouldn’t require any workaround.
Ok, let’s move on with another example.
from maya import cmds
def toFullPathName(node):
return cmds.ls(node, long=True)[0]
root = cmds.group(name='character', empty=True)
for arm_name in ['l_arm', 'r_arm']:
arm = toFullPathName(cmds.group(name=arm_name, parent=root, empty=True))
node = toFullPathName(cmds.group(name='node', parent=arm, empty=True))
child = toFullPathName(cmds.group(name='child', parent=node, empty=True))
This snippet solves the previous issue by introducing a loop statement.
Also, this time each node is stored by using its full path name instead of its partial path name to ensure a false impression of robustness that will be highlighted by the next example to come.
Note here the lack of consistency within the command layer: the group()
command doesn’t have any flag to retrieve the full path name when many other commands have it. Funnily this flag can sometimes be accessible in other commands but under different names such as long
, longName
or fullPath
, probably depending on the personal preferences of the coder at that time.
As a result, we don’t know what we’ll get as a return value from the group()
command: a name or a partial path name? It’s like magic and depends on the content of the scene. Yet another thing to take into consideration when coding.
If we want to consistently deal with full path names then we’re left with calling a quite ugly workaround using the ls()
command.
Now, and even though the flaws listed so far are not excusable, there’s still nothing that we can’t get used to with a bit of time.
The straw that broke the camel’s back is that, as a rigger being, I often have to insert objects within a hierarchy, reparent nodes, and so on. It would for example be common for me to add a parent to each node from a hierarchy to set their local transformations to identity in a post-process pass.
A naive approach of such an operation would look like this:
from maya import cmds
def toFullPathName(node):
return cmds.ls(node, long=True)[0]
nodes = []
root = cmds.group(name='character', empty=True)
for arm_name in ['l_arm', 'r_arm']:
arm = toFullPathName(cmds.group(name=arm_name, parent=root, empty=True))
node = toFullPathName(cmds.group(name='node', parent=arm, empty=True))
child = toFullPathName(cmds.group(name='child', parent=node, empty=True))
nodes.extend([node, child])
for node in nodes:
parent = cmds.listRelatives(node, parent=True, fullPath=True)[0]
buffer = toFullPathName(cmds.group(name='buffer', parent=parent, empty=True))
cmds.parent(node, buffer)
Without knowing the API and simply by reading the code from this snippet, the result seems fairly predictable. Except it’s not. This code errors out due to trying to retrieve an object from a full path name that is not valid anymore because of the changes made to the hierarchy. You thought you that had a solid reference to your node? You don’t. You simply can’t with strings.
Some possible workarounds to this one are quite funny actually. Either we could do our parenting pass after having sorted the list of nodes from the one at the bottom of the hiearchy to the one at the top, or we could store them in a temporary set to make sure that we keep a solid reference to them.
In both cases we would still end up with invalid references if we had to use these nodes again later on in our code. The exception being if we reassign the variables each time with their updated full path name. Quite error-prone.
So What?
Those are only 2 basic examples that I can remember despite not having used Maya in over 1 year. Believe me, I banged my head in frustration a whole lot more than that.
So how do I feel about this command layer? Pretty insecure I have to say. Why would I be keen in using a scripting interface that exposes so many inconsistencies and flaws?
Yes it’s usable and works well if one knows all the corner cases and take care of them. But it’s not something that I want to waste my energy dealing with only to end up in a state in which I would never have the confidence that a certain variable still points to the node I’d expect it to. And that sucks. Heaps.
Those are facts that most of you probably already know and have accepted dealing with. I didn’t.
Once again, I fully respect any approach and believe they are valid as soon as the code produced is robust. I chose the Maya API back in 2010 because it was simpler for me to write such code, that’s all.
Now if you expect me to praise the Maya API, this won’t happen neither. I actually understand why not so many people dare to adventure in there—it goes straight in the lower end of the user-friendliness scale, especially its Frankenstein-like Python version, and has its own pitfalls.
But at least it has a sort of organization with its object-oriented approach and most importantly: we can reference nodes as nodes.
In other words, I chose it because my priority was to write robust code and it felt like the less worse option at that point in time. Also I was keen to invest some of my time to make it more usable on a daily basis by developing some extensions through monkey patching.