Saturday, 4 June 2022

Scripting Is Way Nicer In Pymel

 So in addition to all that business about Pymel being able to store Maya objects as variables...

Don't Use Cmds - Use Pymel

... Pymel is a lot nicer to type out because every Maya object variable automatically has access to a slew of hella useful methods from the PyNode class:

https://help.autodesk.com/cloudhelp/2017/ENU/Maya-Tech-Docs/PyMel/pynodes.html

There are many methods to go through, but it's generally pretty intuitive. Most of the time the methods you'd expect to be there are there and are named exactly what you'd expect them to be named.

As a refresher, a "method" is just a function that comes packaged with a certain class.
If that's even more confusing to you, it's because a "class" is not at all what you'd think it is and I'm extremely resentful of whoever it was at Python LLC who chose the name "class" and I DON'T have time to untangle what a class should really be called, but forget all that...

... the PRACTICAL definition of a "method" is simply a function that comes after a variable name.

Example of a function:

num = 3.14159265359

roundedNum = round(num, 2)

Example of a method:

intro = "sam i am"

capitalizedIntro = intro.upper()

 

See in the second example how the function upper is preceded by a period and is attached to the end of the variable intro ?
Intro has a string assigned to it; so it's a string variable. And as a string variable it is a member of the string class, meaning it carries around with it a whole bunch of built in functions in the form of methods.

Methods are functions that get packed up in a little suitcase and carried around by the variables that are able to use them.

Classes native to python are things like string, list, tuple, dictionary, etc. meaning that string variables, list variables, tuples, dictionaries, etc, each have their own suite of inbuilt methods that can be used at any time using this period syntax.

W3Schools is a good reference for what these methods all are and what each one does. Take a look at all the methods in the string class alone:

https://www.w3schools.com/python/python_ref_string.asp


PyNode methods

Sounds pretty sweet.
So... what about Pymel?

Pymel comes with the Pynode class, which applies to any variable that contains a Maya object, (something only possible in Pymel, not in Cmds.)
And as a class, pynodes have a shit load of handy functions. Check this out.

I've got a nurbs curve. I have it assigned to a variable (in Cmds, technically I just have a static version of its name assigned to a variable as a string, but that's not important for once). Remember that a nurbs curve is technically two nodes: the transform (of node type 'transform') and a shape underneath is (of node type(nurbsCurve).
So I've got the transform, and from there I want to get the nurbs curve bellow.
In Cmds the best way I can think of is using Cmds' listRelatives function and the right combination of arguments:

circle = cmds.circle()[0]

circleShape = cmds.listRelatives(circle, shapes=True)[0]

Pretty clunky, right? listRelatives, depending on the parameters you invoke could return all of a node's children, it's parents, its shape nodes, ALL its grand kids or ALL its grand parents. We have to be very specific to make sure we get what we want.
Now in pymel, the listRelatives function is still there, we can use it if we want, OR we can use a handy-dandy method of the pynode class.
Since getting an object's shapes is such a common need while scripting in Maya, some kind soul over at Autodesk thought of that and built in a method for it. Watch and be amazed:

circle = pm.circle()[0]

circleShape = circle.getShape()

 You see that?
.getShape()
That's all you need!

And it gets better! Sometimes objects have multiple shape nodes. That's why listRelatives always returns a list of all nodes that met the specifications. And if you're expecting just one shape node then you've got to go through the extra step of pulling out the first and only entry in that returned list, hence the [0] on the end.

We didn't have to do that using the getShape method, because getShape if what you use when you're expecting only one shape node. If you use it on an object with multiple shape nodes, you just get the first shape node python found.
But if you are expecting multiple shape nodes, and you want to get them all in a list, guess what? There's a method for that too!

.getShapes() 

Returns all found shapes nodes as a list. If it only finds one shape node, it'll return a list with just one entry.


More examples!

If I wanted to set the value of an attribute using Cmds I'd have to do it like this, using the setAttr function:

cube = cmds.polyCube()

cmds.setAttr( circle+'.scale', 2, 1.5, 2, lock=True)

I could still do that in Pymel. Or I could just go and do this!

cube = pm.polyCube()

circle.scale.set( 2, 1.5, 2, lock=True)

Oh you heard me right. Pynode variables have all their native attributes ready for you as python attributes, and all of THOSE have handy methods like set, get, and connect.

Connecting attributes in Cmds (eww!)

cmds.connectAttr( cube + ".tx", circle + ".tx" )

Connecting attribute in Pymel (fuck yea!)

cube.tx.connect( circle.tx )


Getting a current value from an attribute in Cmds and assigning it to another attribute (booooooo!)

cmds.setAttr(circle + ".ty", cmds.getAttr( circle + ".tx" ) )

And doing it in Pymel (yaaaaaaaaaay!)

circle.ty.set( circle.tx.get() )

 

Note that it's only native attributes that come built in like this. If you got adding your own custom attribute to an object, accessing them inside Pymel will still require the use of that attribute's name as a string.

pm.addAttr( cube, longName="spin", keyable=True, attributeType="float", defaultValue=0 )

pm.setAttr(cube + ".spin", 6.283)


Yet more examples!!

Parent objects and getting objects' parents in Cmds:

cmds.parent(cube, circle)

circlePar = cmds.listRelatives( circle, parent=True )[0]

And in Pymel:

cube.setParent(circle)

circlePar = circle.getParent()

And if you're guessing that since Pynodes have a getParent method, maybe there's also a getChildren method, you'd be absolutely correct. If it would be useful, chances are it's already a method!

 

And information like name and node type can be got from objects in Pymel that in Cmds would require the use of entire functions.

print( cube.name() )

print( cube.type() )


Given how much of scripting in Maya (at least when rigging) is just about getting, comparing, setting, connecting attributes, and altering the hierarchy, just what's covered in this post alone will make your script less painful to stare at all day.

And like I said, if you're not ever sure if a certain method exists or how it works and you don't have time to look it up, you can still just do it the old fashioned way as you would in Cmds. All the same functions are available in Pymel, so that option is always there.

No comments:

Post a Comment