Tyler Thornock - Character TD
Rigs Scripts Tutorials Demo Reel Resume

Dealing with skinCluster weights in Maya

There are a lot of things you can do to speed up the query and set of skinCluster weights in Maya, but you do have to delve into the API and deal with more complex code.

People will often bring up MFnSkinCluster.getWeights since it is technically the fastest method to query a dense mesh with lots of influences. However, what it returns back is not a good way to work with weights in python. For each vertex it returns weights for all influences, and since most of these are zero, it is a lot of "useless" information python has to iterate over. So if you are trying to iterate over the weights to modify, normalize, etc, python really starts to bog down.

The fastest and most python friendly way to query a skinCluster's weights (that I know about) is to use MPlug. There is an attribute on the skinCluster called weightList, whose index is the vertex id, which has a child attribute called weights, whose index is the influence id, which stores the weight value. The great thing for skin weights is maya only "activates" the influence ids in this weights attribute that are non-zero for the vertex.

MPlug.getExistingArrayAttributeIndices returns the indices of these non-zero weights. Technically a zero value could exist, but unless you set it manually that shouldn't happen for skin weights (and could bloat your file size if you went around setting a bunch of zeroes).

However, to be useful, we need these indices to match up with specific influences. MFnSkinCluster.indexForInfluenceObject returns the setAttr index for the given dag path of an influence. Now you know which influence is associated with which index returned by the MPlug.

So here is some example code to query weights:

import maya.OpenMaya as OpenMaya
import maya.OpenMayaAnim as OpenMayaAnim
import maya.cmds as cmds
import maya.mel as mel

# poly mesh and skinCluster name
shapeName = 'pSphere1'
clusterName = 'skinCluster1'

# get the MFnSkinCluster for clusterName
selList = OpenMaya.MSelectionList()
selList.add(clusterName)
clusterNode = OpenMaya.MObject()
selList.getDependNode(0, clusterNode)
skinFn = OpenMayaAnim.MFnSkinCluster(clusterNode)

# get the MDagPath for all influence
infDags = OpenMaya.MDagPathArray()
skinFn.influenceObjects(infDags)

# create a dictionary whose key is the MPlug indice id and 
# whose value is the influence list id
infIds = {}
infs = []
for x in xrange(infDags.length()):
	infPath = infDags[x].fullPathName()
	infId = int(skinFn.indexForInfluenceObject(infDags[x]))
	infIds[infId] = x
	infs.append(infPath)

# get the MPlug for the weightList and weights attributes
wlPlug = skinFn.findPlug('weightList')
wPlug = skinFn.findPlug('weights')
wlAttr = wlPlug.attribute()
wAttr = wPlug.attribute()
wInfIds = OpenMaya.MIntArray()

# the weights are stored in dictionary, the key is the vertId, 
# the value is another dictionary whose key is the influence id and 
# value is the weight for that influence
weights = {}
for vId in xrange(wlPlug.numElements()):
	vWeights = {}
	# tell the weights attribute which vertex id it represents
	wPlug.selectAncestorLogicalIndex(vId, wlAttr)
	
	# get the indice of all non-zero weights for this vert
	wPlug.getExistingArrayAttributeIndices(wInfIds)

	# create a copy of the current wPlug
	infPlug = OpenMaya.MPlug(wPlug)
	for infId in wInfIds:
		# tell the infPlug it represents the current influence id
		infPlug.selectAncestorLogicalIndex(infId, wAttr)
		
		# add this influence and its weight to this verts weights
		try:
			vWeights[infIds[infId]] = infPlug.asDouble()
		except KeyError:
			# assumes a removed influence
			pass
	weights[vId] = vWeights
    

Now that you have the weights, it is quick and easy to modify them. If you are not used to dictionary they might seem weird at first, but I really like using them in this case since you may not be querying all vertices.

Once you are done modifying the weights, you set them using setAttr. MFnSkinCluster.setWeights is faster, but doesn't really work with how the weights are stored now, and you don't get free undo support like you do with setAttr. The setAttr index is the same as the indices returned by MPlug.getExistingArrayAttributeIndices which is the same as the influence ids the code above stored in the weights attribute. You can call something like:

cmds.setAttr('skinCluster1.weightList[5],weights[0]', .5)

which would set the weights of influence number 0 to .5 for vertex number 5. The main catch with this method is NO error checking is done by maya... so if your weights dont add up to 1, you add a negative weight, you add a bunch of zero weights, you set weights for an invalid influence index, etc maya wont fix it. Typically, I run the weights through a validation.

You also wouldn't use this to set weights to 0, if you do it will slow your scene and bloat your file size. Typically when using this method you will run a skinPercent prune to remove all weights on all vertices being modified and then setAttr the modified weights back. And when I say "remove" maya doesn't just set it to zero, it "pops" the indice, which is what allow the MPlug technique to work. Technically this prune only needs done if an influence needs zeroed/removed, so you can keep track of that if you really want it optimized. Some have also had success with the removeMultiInstance mel command, which pops a specific indice.

The following removes all weighting so only non-zero weights need applied:

# unlock influences used by skincluster
for inf in infs:
	cmds.setAttr('%s.liw' % inf)

# normalize needs turned off for the prune to work
skinNorm = cmds.getAttr('%s.normalizeWeights' % clusterName)
if skinNorm != 0:
	cmds.setAttr('%s.normalizeWeights' % clusterName, 0)
cmds.skinPercent(clusterName, shapeName, nrm=False, prw=100)

# restore normalize setting
if skinNorm != 0:
	cmds.setAttr('%s.normalizeWeights' % clusterName, skinNorm)

Now to set the weights using the weights variable you would use something like this:

for vertId, weightData in weights.items():
	wlAttr = '%s.weightList[%s]' % (clusterName, vertId)
	for infId, infValue in weightData.items():
		wAttr = '.weights[%s]' % infId
		cmds.setAttr(wlAttr + wAttr, infValue)

This method is way faster then skinPercent and, for python, sets the weights up in a way that provides probably the fastest iteration... so depending on your specific use, can be faster then MFnSkinCluster and you get free undo with setAttr!