MayaX, open source library to work with Maya's nodes in an OOP way

Hi,

Sorry for “self-promoting” as my first post, but I thought maybe my recently-released open source library could be of some help to someone. It allows to work with Maya’s nodes and commands in an object-oriented way: https://github.com/chirieac/mayax

import mayax as mx

# retrieve object from scene
torus = mx.Node('pTorus1')

# create new node
cube = mx.cmd.polyCube()[0]

# connect nodes
torus['worldMatrix'].connect(cube['offsetParentMatrix'])

# set attribute value
cube.translate = mx.Vector(0, 5, 0)

# set attribute's properties
cube['translate'].locked = True

# add node reference as attribute
# (atttribute of type `message`, connected to `torus.message`)
cube.addAttr('parentDriver', torus)

# retrieve node reference
cube.parentDriver.worldPosition = mx.Vector(2, 5, 5)

If you find this useful, it will be great if you will let me know about that.

3 Likes

First off, Welcome to the forum!

This looks pretty darn good. I’m jealous that I didn’t write it :slight_smile:

It checks a bunch of boxes for me. It keeps track of the nodes via MObjects. It auto-casts inherited node types, and does so in an extensible way. You auto-generate a wrapper for most of (all of?) the cmds to take your object types natively. You’ve got Tests! It’s well laid out, and I could totally hack on this.

If I were to start messing with this, I’d probably add some more convenience getters/setters to the DagNode, or maybe more class specializations. And then I’d bang my head against a wall for a week and make it so the wrapped cmds return a consistent type for the given inputs. No more of this None when it should be a list. I can see you’ve done that in your Node class, and that probably takes care of 99% of the use-cases, but I’m irrational about this!

The only thing I caught was the import mayax.attribute in your node.py file. It’s the only place you reference mayax directly instead of doing something like from .attribute import Attribute

2 Likes

Hi @tfox_TD, thanks for looking at my library, I appreciate it.

To answer some of your questions/thoughts…

  • I’m wrapping all the commands. The bin/gencmd script will find all the commands from Maya’s online documentation and add them all in mayax.cmd. In this way we get nice auto-complete in an IDE, beside making them work with the custom Node class.

  • Yes, a few more getters/setters for the nodes or attributes could be nice. The ones I added are the ones I had use for.

  • More class specializations could be helpful. For example, I need to add a TransformNode and move some attributes from DagNode to it since a Dag node doesn’t have world position.

  • I wouldn’t mess with the wrapped commands to make them return better values ([] instead of None). I like them to be exactly like the maya.cmds to have consistency/predictability when working with normal commands to be easier to switch between them. What I would do instead would be to either add convenience properties/methods to the specialized classes, either functions to mayax.* namespace.

  • Good catch on import mayax.attribute. Tried many ways to avoid cyclic imports, but I think that should be from . import attribute (I will change it).

2 Likes

Ooh, I didn’t even think about that… But then you could extend that even further and build a stub file for type annotations. (Go go gadget Feature Creep!) I can’t wait to go Py3 at work so I can start putting in annotations.

Heh, Fair enough :slight_smile:

2 Likes

I’m curious to see how bulk performance looks compared to cmds.

When I did nodule I ended up walking back from the API after I noticed how slow it could be for things like getting a lot of objects back from an ls(). I ended up going with an explicit conversion instead so an author needed a list of 10,000 things but only wanted property access on 10 of them did not have to instantiate 10,000 MObjects first.

Still – this is the cool thing about Maya python, you can really bend it to your will!

Hey @Theodox,

I’m actually aware of nodule. At some point I looked at it, but I think it was not available when I started working on my library (May 2018). Only recently I decided to extract it from my rigging tool and release it as standalone and open source.

You are right, doing mayax.cmd.ls with 10.000 objects could be visibly slower than maya.cmds since it needs to do extra work to convert all the objects to Node instances. Which is why I want to keep mayax.cmd consistent with maya.cmds, to always fallback to it when needed and only explicitly converting the required objects to Node instances.

But it is still pretty fast, and faster than PyMel. I just tested with 10.000 transforms in the scene using timeit module in Maya 2020 and I got these results:

  • maya.cmds.ls: 0.0208407
  • mayax.cmd.ls: 0.1383669 (6.63x slower than maya.cmds, sometimes under 6x)
  • pymel.core.ls: 0.2888304 (2x slower than mayax.cmd and 13.85x slower than maya.cmds)

Yeah, Pymel is always a bit pokey. It’s interesting how hard it is to beat cmds, I ran into an even bigger disparity with minq where I had done the whole thing in the API before profiling, only to find out that I could never get close to the speed of cmds. Ended up rewriting the whole thing in cmds instead :slight_smile:

I toyed with the idea of runtime patching ls and listRelatives and so on to allow an extra flag that would return nodes on demand without extra imports, but I eventually decided it was too much magic. The older I get the more I appreciate simplicity.

The problem is that we don’t have a faster way to get a reference to the MObject based on the name.
Simply calling MGlobal.getSelectionListByName(name).getDependNode(0) for each item in cmds.ls makes it 3x slower. So, there’s room for improvement to lower the cost from 6x, but as you can see there’s a limit.

But still, 0.14 seconds for 10.000 transforms is not bad at all for what we’re getting in return.

Ok, I’m gonna ask the stupid questions…
If looking up by name individually is “slow”, how fast is the reverse operation? Say to heck with Maya’s selection list, and build your own dictionary by construction each MObject’s name instead of searching for each name. Or maybe a binary search is faster at some point because hashing is slow.
This is something where I would hope Maya already does something like that under the hood, but without testing who knows? How many objects does the scene need, and how many objects to query make the trade-off (if there even is a trade-off) worth it?

What if you compiled a c++ python extension that handled that 10k selection? Is there a way to make a simple module that gives you big gains in most cases?

Could making specialized typed lists be worth it? It’s certainly how you get speed out of numpy, so the pattern exists, and should hopefully be familiar to devs.


There is definitely a trade-off between dev-time and execution speed here. I personally lean towards dev-time being more precious … until it’s not :slight_smile:
What I’m saying is that it may not even be worth answering the questions I asked :laughing: But I’m still curious.

1 Like

@tfox_TD,

I don’t think we can move away from selection list. From what I know, that’s the only way to get an object handle from the name (and we need one). Ideally I would have expected MObject(name), but there’s no such thing.

Regarding the other optimizations, like C++ extension, maybe things could be improved, but it’s not worth the trouble I think. The 10k list problem seems a very specific/rare case to me, and in most cases might require special attention anyway. Depending on the required operation, you may want to build a specialized C++ plugin for it.

I don’t know, 3-6x performance hit might sound bad on paper, but it doesn’t matter if the numbers are still low anyway.

1 Like

Welcome!

Looks interesting, well done!

1 Like