-
-
Save chris-lesage/5d8eb501915e4a6335e7eb00aedad7d0 to your computer and use it in GitHub Desktop.
| 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() |
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". //
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.)
Awesome~!