So, it’s with a little bit of trepidation that I’m sharing my latest library. Minq bills itself as ‘a query language for Maya scenes.’ The goal is to simplify a very common task for Maya coders: finding things in a scene.
Now, that isn’t a particularly interesting job most of the time, but it’s one we do a lot: a quick grep of my own codebase shows over 600 calls to
cmds.ls()
, cmds.listRelatives()
, cmds.listHistory
and cmds.nodeType()
in various combinations: as far as I can tell, ls()
is actually the single most common call I make. Moreover, I’m reasonably certain (though I didn’t do the grepping to bear this out) that those hundreds of
ls()
calls are accompanied by hundreds of little snippets of code to deal with Maya’s quirks. How often have you run into little gems like this?stuff = ['top', 'something_thats_not_transform'] print cmds.ls(*stuff, type='transform') # [u'top'] stuff = [] print cmds.ls(*stuff, type='transform') # [u'front', u'persp', u'side', u'top']
for item in cmds.ls(my_meshes, type='mesh'): print item # Error: 'NoneType' object is not iterable # Traceback (most recent call last): # File "<maya console>", line 1, in <module> # TypeError: 'NoneType' object is not iterable #
ls()
calls alone will produce at least 5 bugs.More importantly – and, frankly, the whole reason for this project – dealing with these little gotchas is not an interesting job. Finding, filter and sorting stuff in your Maya scene is not am opportunity for you to display your brilliant algorithms or clever strategies for bending Maya to your will: it’s just a bunch of stuff you have to on your way to fixing the problems your users really want fixed.
Hence, Minq in actionminq.
The goal of minq is to provide a more concise and more readable way to find things in your maya scenes. Here’s an example to give you the idea of how the project is supposed to work.
Suppose you need to find all of your character skeletons and distinguish them from other things lying around in the scene. The easy way to do that is usually to look for assemblies (top level nodes) which have children who drive skinClusters. Here’s an example of how you could find all the root nodes in the scene which drive skins using conventional means:
def find_assemblies_that_drive_skins(): skinned = [] for asm in cmds.ls(assemblies=True) or []: children = cmds.listRelatives(asm, ad=True) or [] history = cmds.listHistory(children, future=True) if history and cmds.ls(history, type='skinCluster'): skinned.append(asm) return skinned
or []
to make sure we don’t get errors for failed queries. We have to create two temporary variables (childen
and history
) in order to store the intermediate results. And, obviously, we’re 3 layers deep when we get to the actual work item. Above all, though, you need to remember two little bits of Maya trivia to make sense of this code: that
cmds.ls(asm=True)
means ‘give me the assemblies’ and that listRelatives(ad=True)
gives you the children of an object. These are, of course, very clear to Maya vets – but there are over 50 flags in ls()
and more than a dozen in listRelatives()
. I’ve been working in Maya for 20 years and I still need to look up most of them. You pass those flags to Maya as strings which won’t get evaluated until runtime – and it’s possible to mistype them and not even know because ls()
, in particular, makes wierd tweaky decisions about how to interpret conflicting flags.Here’s the minq equivalent to the previous function:
def drives_skin(some_object): children = using(some_object).get(AllChildren) skin_clusters = children.get(Future).only(SkinClusters) return any(skin_clusters) unskinned_assemblies = Assemblies().where(drives_skin)
1.
drives_skin()
takes a maya object2. It gets all of that object’s children
3. It gets all of the future history of those children
4. It it filters down to only the skin clusters in that future history
5. it returns true if any skin clusters are present
The rest of it pretty self evident:
unskinned_assemblies
just collects all of the assemblies which pass drives_skin()
. The algorithm is exactly the same as the first version – but, at least to me, that algorithm is actually expressed much more clearly in the minq version. As for concision, I deliberately broke the query into two lines to make it easier to read -- otherwise it could all be done in a single expression.A purist will probably point out that there are important under-the-hood details in the first one that are hidden in the second, and s/he’d be right. However after doing a lot of this kind of code down the years I’m fairly certain that those important details have almost always been important because screwing them up causes problems – not because they provide an opportunity for a wizardly optimization or better approach to the problem. I’m interested in finding unskinned meshes, not in remembering to pass the correct flags to
ls
and listRelatives
.Here’s a couple of other examples to give you the flavor of what a minq query looks like:
# get all mesh transforms in a scene mesh_transforms = Meshes().get(Parents) # find stub joints def is_stub(obj): return not any (using(obj).get(Children).only(Transforms)) stubs = Joints().where(is_stub) # filtering by type, by name, and with functions cube_creator_nodes = PolyCreators().only('polyCube') used_to_be_cubes = cube_creator_nodes.get(Future).only(Meshes) has_8_verts = lambda p: cmds.polyEvaluate(p, v=True) == 8 still_are_cubes = used_to_be_cubes.where(has_8_verts) # adding, subtracting or intersecting queries too_high = Transforms().where(item.ty > 100) too_low = Transforms().where(item.ty < -100) middle_xforms = Transforms() - (too_high + too_low)
No comments:
Post a Comment