Skip to content

Instantly share code, notes, and snippets.

@chris-lesage
Created January 29, 2020 23:31
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save chris-lesage/5d8eb501915e4a6335e7eb00aedad7d0 to your computer and use it in GitHub Desktop.
Save chris-lesage/5d8eb501915e4a6335e7eb00aedad7d0 to your computer and use it in GitHub Desktop.
Check skinClusters for too many influences per vertex in Autodesk Maya.
import maya.OpenMaya as OpenMaya
import pymel.core as pm
import maya.OpenMayaAnim as OpenMayaAnim
import maya.cmds as cmds
import maya.mel as mel
class checkMaxSkinInfluences(object):
''' This script takes a mesh with a skinCluster and checks it for N skin weights.
If it has more than N, it selects the verts, so you can edit them.
The script automatically prunes tiny values, because if you paint away an influence,
it won't always zero out the values properly.
Usage: select the number of influences your engine supports and a tiny prune value.
Select the mesh and run the script
# based on the script by Tyler Thornock from http://www.charactersetup.com/tutorial_skinWeights.html
Modified for use by Chris Lesage
'''
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
self.name = 'checkMaxSkinInfluences'
self.title = 'Check Max Skin Influences'
self.version = 0.8
self.author = 'Chris Lesage'
self.maxInfluences = 4
self.pruneValue = 0.001
self.btn1 = None
self.btn2 = None
self.ui()
def ui(self):
if pm.window(self.name, q=1, exists=1):
pm.deleteUI(self.name)
with pm.window(self.name, title=self.title + " v" + str(self.version), width=200, menuBar=True) as win:
with pm.rowLayout(nc=4):
pm.text(label='Max Influences: ', font='boldLabelFont', align='center')
self.btn1 = pm.intField(width=40)
self.btn1.setValue(self.maxInfluences)
self.btn1.changeCommand(pm.Callback(self.max_inf_value_change, self.btn1, self.maxInfluences))
pm.text(label='Auto Prune: ', font='boldLabelFont', align='center')
self.btn2 = pm.floatField(width=40 * 3)
self.btn2.setValue(self.pruneValue)
self.btn2.changeCommand(pm.Callback(self.prune_value_change, self.btn2, self.pruneValue))
with pm.horizontalLayout() as layout:
btn = pm.button(width=290, label=str('Check Max Influences'), command=pm.Callback(self.do_the_thing))
layout.redistribute()
pm.showWindow()
def prune_value_change(self, button, value):
self.pruneValue = button.getValue()
def max_inf_value_change(self, button, value):
self.maxInfluences = button.getValue()
def check_influences(self, mesh, maxInfluences, pruneValue):
# TODO: Make a simple interface for choosing the options.
#pm.select(mesh, d=True)
skinCluster = None
for node in pm.listHistory(mesh):
if type(node) == pm.nodetypes.SkinCluster:
skinCluster = node
break
#TODO: Do a first pass with NO pruning. If it passes check, make no change!
pm.skinPercent(skinCluster, mesh, pruneWeights=pruneValue)
# get the MFnSkinCluster for skinCluster
selList = OpenMaya.MSelectionList()
selList.add(skinCluster.name())
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
overWeighted = [x for x in weights.keys() if len(weights[x]) > maxInfluences]
[pm.select(mesh.vtx[x], add=True) for x in overWeighted]
if len(overWeighted) > 0:
pm.selectMode(component=True)
pm.warning('{1} has {0} overloaded ({2}) influences.'.format(len(overWeighted), mesh, self.maxInfluences))
else:
pm.select(mesh.vtx, d=True)
print('{1} is properly pruned to max {2}.'.format(len(overWeighted), mesh, self.maxInfluences))
def do_the_thing(self):
# hack: get the values of the buttons, in case they didn't register a change. (type but don't hit enter)
self.maxInfluences = self.btn1.getValue()
self.pruneValue = self.btn2.getValue()
# NOTE: This is a bit of a selection hack. The purpose is this:
# Even if I have component or object selected, it will search the proper mesh
# And it puts me into component mode so I can instantly work with the weights
# And it doesn't remove the transform from my selection list, so I can keep working on the same mesh
# AND it clears the existing component selection (if you don't, it causes bugs.)
pm.selectMode(object=True) # this lets you have components selected when running the script
for node in pm.selected(type='transform'):
pm.selectMode(component=True)
pm.select(node.vtx, d=True) # first, clear any vtx selection
pm.selectMode(object=True)
for node in pm.selected(type='transform'):
self.check_influences(node, self.maxInfluences, self.pruneValue) # mesh, maxInfluences, pruneValue
checkMaxSkinInfluences()
@ddv0504
Copy link

ddv0504 commented Jun 8, 2021

Awesome~!

@JorgeRodiles
Copy link

how to use it?
I selected an skinned geometry then I ran the script but this happend:
checkMaxSkinInfluences();
// Error: class checkMaxSkinInfluences(object):
//
// Error: Line 8.36: Invalid use of Maya object "object". //

@chris-lesage
Copy link
Author

how to use it? I selected an skinned geometry then I ran the script but this happend: checkMaxSkinInfluences(); // Error: class checkMaxSkinInfluences(object): // // Error: Line 8.36: Invalid use of Maya object "object". //

Hi @JorgeRodiles it looks like you tried to run it in a MEL script. Make sure to choose a Python tab in your script editor.

Also, I haven't tested this in Maya 2022 or 2023, or Python3 yet. So it will need a few edits to work if you are using Python3. (For example, xrange needs to be changed to range. There might be more.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment