Using getWeights and setWeights
in the Maya Python API
A problem I solved recently was coming up with a way to speed up exporting and importing weights on a character, in Maya, in Python. Typically, if you’re doing such a task you’d either write it in C++ (not a bad option) or you iter through the verts in Python, which actually takes a ridiculous amount of time, even if you’re using xrange and/or mayapy. In my opinion, I think one of my coworkers will shoot me for saying this, C++ is a great way to go about it and it’s definitely the fastest way. The only dilemma is that it needs to be recompiled for each API version, which can actually get a little confusing when you have different dev-kits for Maya 2016 and Maya 2016.5, not to mention if you’re building for community, which you’d want to support a larger range of Maya versions. Compiling doesn’t take to long, so I don’t think it’s a terrible option, however, the Maya Python API has gotten a lot better over the years and gives us many API options that do what we want. If you’re not type casting your variables or following C++ syntax, I think the Python Maya API can be a little confusing and seem a little arbitrary, however, once you dive in a bit, you’ll realize you can do so much with it; in a lot of cases, the API removes a lot of unnecessary nested for loops you might run into, in this case, the getWeights and setWeights commands remove the need to loop through the verts of a mesh, and instead allows you to get and set all in one go.
The Anatomy of “Getting the Weights”
The structure found in the C++ docs:
- getWeights (const MDagPath &path, const MObject &components, unsigned int influenceIndex, MDoubleArray &weights) “const
But, we’re following the Python API, which is a little different:
- getWeights(shape, components) -> (MDoubleArray, int)
- getWeights(shape, components, influence) -> MDoubleArray
- getWeights(shape, components, influences) -> MDoubleArray
It also notes
“If no influence index is provided then a tuple containing the weights
and the number of influence objects will be returned.”
Personally, I’d rather retain and manage my own influence index.
So let’s begin
- The first thing you need is the skinCluster of the object you’re trying to get the weights from. Getting this is relatively easy, so I won’t cover it (hint: it’s a type).
- Next, you’ll need to get the dagPath and MObject. There’s actually a few ways to get them, but here’s one way (Which is pretty close to how you would do it in C++):
- You’ll need to create a MFnSkinCluster object to access the setWeights call, basically, just pass OpenMayaAnim.MFnSkinCluster the MObject. In C++ you’d (MFnSkinCluster skinCluster(object)). Basically, this is granting you access to the API (I urge you to read the docs to understand it better, link posted at the end of the post).
- Next you’ll need a MDoubleArray to hold your weights, which is the data type Maya’s API needs to store the data.
- Next, you’ll need an iterable that keeps track of the vert order (you don’t have to do this, but like I said before, I’d rather be explicit than implicit). In C++ it’s actually an unsigned int data type. In Maya’s Python API it’s just an int, you can get it like so:
- And, finally, the magic call that does it all
This stores the weight data in your MDoubleArray (weights), so just return weights after you run getWeights and you’ll have your weights, ALL. IN. ONE. GO.
You’ll want to obviously serialize the data out to a file so that you can import it. I actually save out a bit of data, minimally (“weights”, “skinCluster”, “shape”) and I serialize out to a json file.
In addition to your own keys, you’ll need to serialize out the attributes of the skinCluster:
The Anatomy of “Setting the Weights”
As you can see, it’s similar, however it’s a bit more work actually getting the weight data since you can’t just pass a list of integers/floats. Let’s start by looking at the C++ API call and then the Python API call:
setWeights (const MDagPath &path, const MObject &components, unsigned int jointIndex, double value, bool normalize=true, MDoubleArray *oldValues=NULL)
setWeights(shape, components, influence, weight, normalize=True, returnOldWeights=False) -> None or MDoubleArray
setWeights(shape, components, influences, weights, normalize=True, returnOldWeights=False) -> None or MDoubleArray
- The real difference comes when you’re gathering the the influences and weights. The serialized data can’t just be passed in, you have to pass the weights into the MDoubleArray (right now it’s just a container of nothingness). To do that, you can loop over the imported influence data (looping through serialized data is much faster than actually looping through verts, especially when using xrange), then, once you have the correct vert order and data (assuming it is, if it’s not, you can either copy the weights or just add some error handling), you can set the weights into the MDoubleArray container. This also gives you a chance to handle unused_influences because you can track whether there’s actual data or not.
- Next, in a similar way, you need to set the influence array with the data from the influence count (which you can get from the skinCluster data you saved out).
- Now, for the magic call
- Lastly, set your attribute data:
This is a really SIMPLE overview of how you can use the Maya Python API to gather up weight data and set it. There’s so many more things that are involved, but hopefully this will give someone (anyone) a starting point to work from. Hope it helps! 🙂