The Maya API is an incredibly useful tool to have in your toolbelt as a td. With the data it gives you access to, you can do operations that would be slow, if not impossible, to do with MEL or maya.cmds
In this post, I'll document how I get and set Skin Cluster weights so as to do operations like influence pruning, smoothing, copy/paste average, and more.
The basic structure of these scripts is all the same. You get the selected components, find the skinCluster they belong to, get its influence and weight list, do something to change that list, and then set the weights again. I'm using the Python API for this, but if you know C++ and how to compile it with Maya you are at a huge speed advantage, especially when iterating over a ton of components.
API Reference
Getting the Selection
The first thing I usually do is store the original selection in a MSelectionList object so that we can reset the selection to that at the very end. This helps keep the user's workflow (generally mine) less interrupted by constant object/component level switching.
original_sel = om.MSelectionList()
om.MGlobal.getActiveSelectionList(original_sel)"om" is my chosen abbreviation for the maya.OpenMaya module.
Getting the Selected Mesh and Components
The next step is to a quick way to make sure you're iterating over vertices. We do this since all the data we'll be looking for is stored at a vertex level. In this case, we're only going to do operations over one mesh. If you wanted to do this over multiple meshes at once, you'd use a MItSelectionList object.
cmds.select(cmds.polyListComponentConversion(toVertex=True))
# get the selected mesh and components
sel = om.MSelectionList()
om.MGlobal.getActiveSelectionList(sel)
if not sel.length():
return
selected_components = om.MObject()
dag = om.MDagPath()
sel.getDagPath(0, dag, selected_components)
dag.extendToShape()
if dag.apiType() != 296:
om.MGlobal.displayError("Selection must be a polygon mesh.")
returnAbove, I'm using a handy maya.cmds function to quickly convert our selections to vertices. I do this after I store the original selection and before I store it in our selection list since I want that selection list to hold vertices. If I didn't, it wouldn't be able to derive the correct vertices from the edges/faces/objects I had selected.
If there isn't anything selected, I get out of the function. Otherwise I get the MDagPath object of the selection as well as any components on it that are currently selected. Then I do a check to see if the apiType of its shape node is 296, which is the type for polygonal meshes. This iterator isn't designed for anything except skinClusters on polygons. That's what I'm almost always using anyways.
Getting the SkinCluster's Name and MObject
Next I use a handy function that again utilizes some quick maya.cmds functions to get us data quicker. If you're using the Maya Python API, you have the advantage of also using maya.cmds as you work. Don't kill yourself over iterating up a DG if you can do the same thing in one line of maya.cmds. If speed is your concern, don't use maya.cmds in loops that iterate over thousands of components.
def getSkinCluster(self, dag):
"""A convenience function for finding the skinCluster deforming a mesh.
params:
dag (MDagPath): A MDagPath for the mesh we want to investigate.
"""
# useful one-liner for finding a skinCluster on a mesh
skin_cluster = cmds.ls(cmds.listHistory(dag.fullPathName()), type="skinCluster")
if len(skin_cluster) > 0:
# get the MObject for that skinCluster node if there is one
sel = om.MSelectionList()
sel.add(skin_cluster[0])
skin_cluster_obj = om.MObject()
sel.getDependNode(0, skin_cluster_obj)
return skin_cluster[0], skin_cluster_obj
else:
raise RuntimeError("Selected mesh has no skinCluster")
The line cmds.ls(cmds.listHistory(dag.fullPathName()), type="skinCluster") is an especially useful one-liner that I use ALL THE TIME. I'm using the ls command to filter any skinCluster type objects out of an object's history list. If there is a length to that list, then one exists. I'm then using MSelectionList to get the MObject from that skin cluster node.
Getting the Skin Weights
Now that we have the MObject for our skinCluster we can make our MFnSkinCluster object and get access to all the handy functions it provides. Much of what I do next is gathering the information required to get and set the weights for our skinCluster. One of those is an MDagPathArray of the influence objects (joints) that influence our mesh. The other is a pointer to an unsigned integer object that holds the amount of influence objects on the skinCluster. The last is an MIntArray that basically just goes from 0 to 1 minus the amount of influence objects. Again, all just information we need to pass to MFnSkinCluster.getWeights, but once you do it right, you don't really need to write it again.
# doing this can speed up iteration and also allows you to undo all of this
cmds.skinPercent(skin_cluster, pruneWeights=0.005)
mFnSkinCluster = omAnim.MFnSkinCluster(skin_cluster_obj)
inf_objects = om.MDagPathArray()
# returns a list of the DagPaths of the joints affecting the mesh
mFnSkinCluster.influenceObjects(inf_objects)
inf_count_util = om.MScriptUtil(inf_objects.length())
# c++ utility needed for the get/set weights functions
inf_count_ptr = inf_count_util.asUintPtr()
inf_count = inf_count_util.asInt()
influence_indices = om.MIntArray()
# create an MIntArray that just counts from 0 to inf_count
for i in range(0, inf_count):
influence_indices.append(i)
old_weights = om.MDoubleArray()
# don't use the selected_components MObject we made since we want to get the weights for each vertex
# on this mesh, not just the selected one
empty_object = om.MObject()
mFnSkinCluster.getWeights(dag, empty_object, old_weights, inf_count_ptr)
# new_weights just starts as a copy of old_weights
new_weights = om.MDoubleArray(old_weights)Iterating and DOING STUFF
Now we're ready to actually DO STUFF to the weight list we got. That weight list is kind of strange at first and can be tricky to work with. It's a one-dimensional list of length (influence_count * component_count) so that the "weights" for one vertex is at the subset weight_list[this_vert_weight_index: this_vert_weight_index + inf_count].
So now we iterate over the selected components using a MItMeshVertex object, which again gives us a TON of information at a vertex level like its index (which will correspond to the correct index in the weight list), its neighbors, and its normal vector. If you needed more information or needed to do stuff like raycasting, you can make a MFnMesh object outside of the iterator and get even more information from your mesh. Pretty nice, huh.
# iterate over the selected verts itVerts = om.MItMeshVertex(dag, selected_components) while not itVerts.isDone(): this_vert_weight_index = itVerts.index() * inf_count vert_weights = list(new_weights[this_vert_weight_index: this_vert_weight_index + inf_count]) # makes the weights for the closest vertex equal to the outer vertex new_weights[this_vert_weight_index: this_vert_weight_index + inf_count] = SOME AWESOME FUNCTION itVerts.next() # set weights all at once mFnSkinCluster.setWeights(dag, empty_object, influence_indices, new_weights, True, old_weights) om.MGlobal.setActiveSelectionList(original_sel)


