In this post I'll go over how you can code something to get and set skin cluster weights in Maya. We'll also discuss different things you can do to improve performance, especially if were working with a high resolution model. What will be covered here can be done using the API for more efficiency and better performance. But first we will go over simply using **maya.cmds**

Our first order of business is to look at how we can query the weights in a skin cluster on a vertex base and parse this information to store in an organized manner that we can use later.

If you search around you'll find than many people suggest using **MFnSkinCluster.getWeights** method. In its defense it is the fastest to query a high resolution mesh with many influences. However, it return weights for all influences, and as you may already know, not all influences are use for every vertex in the skinCluster, having weight of 0, so you end up with a lot of unnecessary data. Since this stage of querying the weights is to give us data to use later, wether we will be normalizing, modifying, or what have you, when we actually have to iterate through all this 'empty' data there will be a drastic decrease in performance time.

In this example, we will take a look at the skinCluster node itself. The next thing is very important to understand. The node has an attribute called **weightList**.** **This attribute has indices based on the vertex id for the mesh. For each index, there is another attribute called **weights**.

Here is some code to query the vertex weights for the skin cluster:

def getSkinWeights(skinClusterName): ''' Get the weights per vertex on the meshName with the skinClusterName ''' # to store the weight information # the key represents the vertex id and the value another dictionary # with keys representing the influence object id and the the value the weight as a double # Format: {vertexId: {influenceId: weight, ...}, ...} weights = {} # get the Influence Objects influenceObjects = cmds.skinCluster(skinClusterName, q=True, weightedInfluence=True) # get the size of the vertex weight list weightListSize = cmds.getAttr(skinClusterName + ".weightList", size=True) # build weight dictionary for vertexId in range(0,weightListSize): # to store vertex weights vertexWeights = {} for influenceId in range(len(influenceObjects)): weight = cmds.getAttr(skinClusterName + ".weightList[%d].weights[%d]" % (vertexId,influenceId)) # only store non-zero weights if weight != 0: vertexWeights[influenceId] = weight # store the weights for the vertex id weights[vertexId] = vertexWeights return weights

Notice I opted for using a dictionary. The way you want to store the data is up to you really. However, dictionaries are really powerful for this sort of thing. We can quickly access data based on the id, and it works great since we can query only the verts we choose to.

With the weights stored we can now turn to how we can set the weights. Once again, if you look, you'll find that using

**MFnSkinCluster.setWeights**is a popular method and fast. But since we opted to query the weights in our own method, we can now quickly set our weight values using

**cmds.setAttr**. In addition, what is really nice is that we also get

**undo**which we don't with

**setWeights**. Trust me this is a really nice feature.

So we've covered how to query the skin weights. After which you can modify the weight values however you wish. Then we can set the weights back to the skinCluster.

Here is some code to set the vertex weights to the skin cluster:

def setSkinWeights(skinClusterName, weights): ''' Set the modified weights to the skinClusterName. ''' for vertexId in weights: # get influence dictionary influenceDict = weights[vertexId] # check if vertex is weighted - if there are any influence objects if influenceDict.items(): for influenceId in influenceDict: plug = skinClusterName + ".weightList[%d].weights[%d]" % (vertexId,influenceId) # set the weight weight = influenceDict[influenceId] cmds.setAttr(plug, weight)

**cmds.skinPercent**has a normalize flag for that. I've noticed that when normalizing the weights while the script is running causes undesired results on vertices that haven't been assigned their new weight values.

**setAttr**is already faster than

**skinPercent**, and can be faster depending on what you plan on doing with it, can be faster than

**MFnSkinCluster**as well. Lets just expand our method to be a little more robust.

You could also use prune small weights, also in

**skinPercent**, before setting the new weights to remove all zero weights.

def setSkinWeights(skinClusterName, weights, meshName, nonZero=True): ''' Set the modified weights to the skinClusterName. The nonZero flags assures that only nonZero weights are set. ''' for vertexId in weights: # get influence dictionary influenceDict = weights[vertexId] # check if vertex is weighted - if there are any influence objects if influenceDict.items(): for influenceId in influenceDict: if nonZero: influenceObj = cmds.listConnections("skinCluster1.matrix[%d]" % influenceId) if influenceObj: influenceObj = influenceObj[0] cmds.setAttr(influenceObj + ".liw", 0) # prune small weight values # need to turn off normalize to use prune plug = skinClusterName + ".normalizeWeights" normalize = cmds.getAttr(plug) if normalize: cmds.setAttr(plug, 0) cmds.skinPercent(skinClusterName, meshName, nrm=False, prw=.01) # return normalizing to what it was if normalize: cmds.setAttr(plug, normalize) plug = skinClusterName + ".weightList[%d].weights[%d]" % (vertexId,influenceId) # set the weight weight = influenceDict[influenceId] cmds.setAttr(plug, weight)

## 0 comments:

## Post a Comment