582 lines
22 KiB
Python
582 lines
22 KiB
Python
try:
|
||
import pymel.core as pm
|
||
except ImportError:
|
||
pm = None
|
||
|
||
import maya.cmds as cmds
|
||
import maya.OpenMaya as OpenMaya
|
||
import maya.OpenMayaAnim as OpenMayaAnim
|
||
import traceback
|
||
import time
|
||
import copy
|
||
|
||
try:
|
||
import skin_api.Utils as apiUtils
|
||
except ImportError:
|
||
from . import Utils as apiUtils
|
||
|
||
|
||
def getMfNSkinCluster(clusterName):
|
||
'''
|
||
Helper function to generate an MFnSkinCluster object
|
||
:param clusterName:
|
||
:return:
|
||
'''
|
||
try:
|
||
# get the MFnSkinCluster for clusterName
|
||
selList = OpenMaya.MSelectionList()
|
||
selList.add(clusterName)
|
||
clusterNode = OpenMaya.MObject()
|
||
selList.getDependNode(0, clusterNode)
|
||
skinFn = OpenMayaAnim.MFnSkinCluster(clusterNode)
|
||
return skinFn
|
||
except:
|
||
raise "Could not find an MFnSkinCluster named '%s'" % clusterName
|
||
|
||
|
||
|
||
def getInfluenceNames(skincluster):
|
||
'''
|
||
Returns a list of names of influences (joints) in a skincluster
|
||
:param skincluster: <skincluster object> or <string>
|
||
:return: <list>
|
||
'''
|
||
if pm:
|
||
return [str(inf) for inf in pm.skinCluster(skincluster, influence=True, q=True)]
|
||
else:
|
||
return [str(inf) for inf in cmds.skinCluster(skincluster, influence=True, q=True)]
|
||
|
||
|
||
|
||
def getMaxInfluences(skincluster):
|
||
'''
|
||
Returns the maxInfluences for a skincluster as an int
|
||
:param skincluster: <skincluster object> or <string>
|
||
:return: <int>
|
||
'''
|
||
if pm:
|
||
return pm.getAttr(skincluster + ".maxInfluences")
|
||
else:
|
||
return cmds.getAttr(skincluster + ".maxInfluences")
|
||
|
||
|
||
|
||
def getSkinClusterNode(node):
|
||
'''
|
||
Gets the connected skincluster node for a given node
|
||
:param objectName:
|
||
:return:
|
||
'''
|
||
|
||
if pm:
|
||
objHistory = pm.listHistory(pm.PyNode(node))
|
||
skinClusterList = pm.ls(objHistory, type="skinCluster")
|
||
else:
|
||
# cmds 版本
|
||
objHistory = cmds.listHistory(node)
|
||
if objHistory:
|
||
skinClusterList = [h for h in objHistory if cmds.nodeType(h) == "skinCluster"]
|
||
else:
|
||
skinClusterList = []
|
||
|
||
if len(skinClusterList):
|
||
return str(skinClusterList[0])
|
||
|
||
return None
|
||
|
||
|
||
|
||
def getSkinClusterInfo(objectName, saveJointInfo=False):
|
||
'''
|
||
Builds a skincluster info dict. Structured as:
|
||
{"ObjectName": {skinCluster:{clusterWeights: {}, clusterInflNames: [], clusterMaxInf: int}
|
||
:param objectName: <shape> or <transform>
|
||
:param saveJointInfo: <bool> saves joint orient, world transform and parent joint info, default is False
|
||
:return:
|
||
'''
|
||
|
||
skinClustName = getSkinClusterNode(objectName)
|
||
|
||
if skinClustName:
|
||
skinClustInfoDict = {}
|
||
skinClustInfoDict["clusterInfluenceNames"] = getInfluenceNames(skinClustName)
|
||
skinClustInfoDict["clusterMaxInf"] = getMaxInfluences(skinClustName)
|
||
skinClustInfoDict["clusterWeights"] = getSkinClusterWeights(skinClustName)
|
||
if saveJointInfo:
|
||
skinClustInfoDict["skinJointInformation"] = getSkinJointInformation(skinClustInfoDict["clusterInfluenceNames"])
|
||
return skinClustInfoDict
|
||
|
||
else:
|
||
print(objectName + " is not connected to a skinCluster")
|
||
return False
|
||
|
||
def getSkinJointInformation(influences):
|
||
"""
|
||
获取骨骼信息(父节点、矩阵、旋转、关节方向)
|
||
兼容 PyMEL 和 cmds,处理无父节点的情况
|
||
"""
|
||
jointInformation = {}
|
||
|
||
for inf in influences:
|
||
jointInfo = {}
|
||
try:
|
||
if pm:
|
||
infNode = pm.PyNode(inf)
|
||
# 安全获取父节点,避免 None.name() 错误
|
||
parent = infNode.getParent()
|
||
jointInfo["parent"] = str(parent.name()) if parent else ""
|
||
jointInfo["matrix"] = infNode.getMatrix(worldSpace=True)
|
||
jointInfo["rotation"] = infNode.getRotation()
|
||
jointInfo["jointOrient"] = infNode.getAttr("jointOrient")
|
||
jointInformation[str(infNode)] = copy.deepcopy(jointInfo)
|
||
else:
|
||
# cmds 版本
|
||
infName = str(inf)
|
||
parents = cmds.listRelatives(infName, parent=True)
|
||
jointInfo["parent"] = parents[0] if parents else ""
|
||
jointInfo["matrix"] = cmds.xform(infName, q=True, matrix=True, worldSpace=True)
|
||
jointInfo["rotation"] = cmds.xform(infName, q=True, rotation=True)
|
||
jointInfo["jointOrient"] = cmds.getAttr(infName + ".jointOrient")[0]
|
||
jointInformation[infName] = copy.deepcopy(jointInfo)
|
||
except Exception as e:
|
||
print(f"Warning: Failed to get joint information for {inf}: {e}")
|
||
# 使用默认值
|
||
jointInfo["parent"] = ""
|
||
jointInfo["matrix"] = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
|
||
jointInfo["rotation"] = [0, 0, 0]
|
||
jointInfo["jointOrient"] = [0, 0, 0]
|
||
jointInformation[str(inf)] = copy.deepcopy(jointInfo)
|
||
|
||
return jointInformation
|
||
|
||
def getMPlugObjects(MFnSkinCluster):
|
||
'''
|
||
Gets the plug objects for a given MFnSkinCluster object
|
||
Weights are stored in objects like this:
|
||
skinCluster1.weightList[vtxID].weights[influenceId] = floatvalue
|
||
:param MFnSkinCluster: <MFnSkinCluster>
|
||
:return:
|
||
'''
|
||
|
||
weightListPlug = MFnSkinCluster.findPlug('weightList')
|
||
weightsPlug = MFnSkinCluster.findPlug('weights')
|
||
weightListAttribute = weightListPlug.attribute()
|
||
weightsAttribute = weightsPlug.attribute()
|
||
|
||
return weightListPlug, weightsPlug, weightListAttribute, weightsAttribute
|
||
|
||
|
||
|
||
def getSkinClusterWeights(skinCluster):
|
||
'''
|
||
Gets the clusterweights for a given skincluster node
|
||
Reads them by using the MPlug attribute from OpenMaya
|
||
:param skinCluster:
|
||
:return:
|
||
'''
|
||
|
||
try:
|
||
# get the MFnSkinCluster for clusterName
|
||
skinFn = getMfNSkinCluster(str(skinCluster))
|
||
|
||
# get the MDagPath for all influence
|
||
infDagArray = OpenMaya.MDagPathArray()
|
||
skinFn.influenceObjects(infDagArray)
|
||
|
||
# create a dictionary whose key is the MPlug indice id and
|
||
# whose value is the influence list id
|
||
infIds = {}
|
||
|
||
for i in range(infDagArray.length()):
|
||
infId = int(skinFn.indexForInfluenceObject(infDagArray[i]))
|
||
infIds[infId] = i
|
||
|
||
# get the MPlug for the weightList and weights attributes
|
||
wlPlug, wPlug, wlAttr, wAttr = getMPlugObjects(skinFn)
|
||
|
||
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 range(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
|
||
vWeights[infIds[infId]] = infPlug.asDouble()
|
||
|
||
weights[vId] = vWeights
|
||
return weights
|
||
except:
|
||
print(traceback.format_exc())
|
||
print("Unable to query skincluster, influence order have changed on the skincluster.\n Please re-initialize the skincluster with a clean influence index order")
|
||
return False
|
||
|
||
|
||
|
||
def setMaxInfluencesDialog():
|
||
'''
|
||
Uses the setMaxInfluencesEngine and allows user to give input max value in a promptbox
|
||
:return: <bool> success
|
||
'''
|
||
|
||
nodeList = pm.ls(sl=True)
|
||
|
||
if len(nodeList):
|
||
maxValueWin = pm.promptDialog(title="API Max Influence", message='Set Max influence Value', button=['OK'])
|
||
|
||
try:
|
||
maxInfValue = int(pm.promptDialog(q=True, tx=True))
|
||
except:
|
||
return "Unable to fetch an integer input from the dialog"
|
||
|
||
if maxValueWin == "OK":
|
||
return setMaxInfluences(maxInfValue, nodeList)
|
||
|
||
else:
|
||
return "Nothing selected"
|
||
|
||
|
||
|
||
def setMaxInfluences(maxInfValue, nodeList=[]):
|
||
'''
|
||
Reads the skin weights for nodes given as list.
|
||
Then renormalizes and prunes the influence weights down to value specified by maxInfValues
|
||
:param maxInfValue: <int>
|
||
:param nodeList: <list>
|
||
:return:
|
||
'''
|
||
|
||
if maxInfValue > 0:
|
||
if not len(nodeList):
|
||
nodeList = apiUtils.getTransforms(pm.ls(os=True))
|
||
if len(nodeList):
|
||
if None in apiUtils.getShapes(nodeList):
|
||
print("Non-mesh objects found in selection")
|
||
return False
|
||
else:
|
||
print("Nothing selected")
|
||
return False
|
||
|
||
for node in nodeList:
|
||
skinClusterNode = getSkinClusterNode(node)
|
||
if skinClusterNode:
|
||
clusterWeights = getSkinClusterWeights(skinClusterNode)
|
||
# build a new cluster weight list
|
||
newClusterWeights = {}
|
||
for vtxId, weights in clusterWeights.items():
|
||
sizeSortedDict = {}
|
||
for x in range(maxInfValue):
|
||
lastResult = 0.0
|
||
lastKey = -1
|
||
for key, value in weights.items():
|
||
if value >= lastResult and key not in sizeSortedDict.keys():
|
||
lastResult = value
|
||
lastKey = key
|
||
if lastKey >= 0:
|
||
sizeSortedDict[lastKey] = lastResult
|
||
|
||
# normalize values to a total of 1 and remove invalid key values from dictionary
|
||
maxVal = sum(sizeSortedDict.values())
|
||
for key in sizeSortedDict.keys():
|
||
sizeSortedDict[key] = sizeSortedDict[key] / maxVal
|
||
|
||
# replace old vtxId info with the new normalized and re-sized influence dict
|
||
newClusterWeights[vtxId] = sizeSortedDict
|
||
|
||
# turn of normalization to nuke weights to 0, this is to get a true 1->1 application of old weights
|
||
pm.setAttr('%s.normalizeWeights' % skinClusterNode, 0)
|
||
pm.skinPercent(skinClusterNode, node, nrm=False, prw=100)
|
||
pm.setAttr('%s.normalizeWeights' % skinClusterNode, 1)
|
||
|
||
# set new skinweights
|
||
setSkinWeights(skinClusterNode, newClusterWeights)
|
||
|
||
pm.setAttr(skinClusterNode + ".maxInfluences", maxInfValue)
|
||
pm.skinCluster(skinClusterNode, e=True, fnw=True)
|
||
|
||
print("Max Influences set for '%s'" % node)
|
||
else:
|
||
print("'%s' is not connected to a skinCluster" % node)
|
||
else:
|
||
return "Max Influences has to be a positive integer"
|
||
|
||
|
||
|
||
def setSkinWeights(skinClusterNode, clusterWeightDict, vtxIdFilter=[]):
|
||
'''
|
||
Sets the weight for a given skinCluster using a given weightDict in the format given by .getSkinClusterWeights()
|
||
:param skinClusterNode: <skinCluster>
|
||
:param weightDict: <dict>
|
||
:param vtxIdFilter: <dict BETA: Very untested way of filtering vertices in setting weights
|
||
'''
|
||
|
||
# get the mfnskinCluster node
|
||
skinFn = getMfNSkinCluster(str(skinClusterNode))
|
||
|
||
# get the MPlug for the weightList and weights attributes
|
||
wlPlug, wPlug, wlAttr, wAttr = getMPlugObjects(skinFn)
|
||
|
||
# weights are stored in objects like this:
|
||
# skinCluster1.weightList[vtxID].weights[influenceId] = floatvalue
|
||
# {0: {0: 0.5, 1: 0.5}
|
||
# or {vtxId: {weights: floatvalue}}
|
||
|
||
if not len(vtxIdFilter):
|
||
for vId in clusterWeightDict.keys():
|
||
wPlug.selectAncestorLogicalIndex(vId, wlAttr)
|
||
infPlug = OpenMaya.MPlug(wPlug)
|
||
for infId in clusterWeightDict[vId]:
|
||
infPlug.selectAncestorLogicalIndex(infId, wAttr)
|
||
infPlug.setDouble(clusterWeightDict[vId][infId])
|
||
else:
|
||
for vId in clusterWeightDict.keys():
|
||
if vId in vtxIdFilter:
|
||
wPlug.selectAncestorLogicalIndex(vId, wlAttr)
|
||
infPlug = OpenMaya.MPlug(wPlug)
|
||
for infId in clusterWeightDict[vId]:
|
||
infPlug.selectAncestorLogicalIndex(infId, wAttr)
|
||
infPlug.setDouble(clusterWeightDict[vId][infId])
|
||
|
||
|
||
|
||
def buildSkinWeightsDict(objectList, showLoadingBar=True, saveJointInfo=False):
|
||
'''
|
||
Builds a weight dictionary for skin weights in the apiVtxAttribs format
|
||
:param objectList: <list> List of objects to gather weight info from
|
||
:param showLoadingBar: <bool> default is True
|
||
:param saveJointInfo: <bool> saves joint orient, world transform and parent joint info, default is False
|
||
:return: <dict> dictionary for skincluster
|
||
'''
|
||
loadBarMaxVal = len(objectList)
|
||
loadBarObj = apiUtils.LoadingBar()
|
||
|
||
sourceWeightDict = {}
|
||
for object in objectList:
|
||
try:
|
||
if pm:
|
||
# 安全转换为字符串,处理可能的 None 或无效对象
|
||
obj_node = pm.PyNode(object) if not isinstance(object, pm.PyNode) else object
|
||
objectAsString = str(obj_node.name()) if obj_node else str(object)
|
||
else:
|
||
# cmds 版本 - object 已经是字符串
|
||
objectAsString = str(object)
|
||
except Exception as e:
|
||
print(f"Warning: Failed to process object {object}: {e}")
|
||
objectAsString = str(object)
|
||
|
||
if showLoadingBar:
|
||
loadBarObj.loadingBar("Building Skinweights Info...", loadBarMaxVal, "Building...")
|
||
|
||
skinClusterInfo = getSkinClusterInfo(object, saveJointInfo=saveJointInfo)
|
||
|
||
if skinClusterInfo:
|
||
sourceWeightDict[objectAsString] = {}
|
||
sourceWeightDict[objectAsString]["vtxCount"] = apiUtils.getVtxCount(object)
|
||
sourceWeightDict[objectAsString]["skinCluster"] = skinClusterInfo
|
||
|
||
if bool(sourceWeightDict):
|
||
return sourceWeightDict
|
||
else:
|
||
return False
|
||
|
||
|
||
|
||
def transferSkinWeights(transferNodes=None, showLoadingBar=True):
|
||
'''
|
||
transfers skin weights between a given set of meshes by:
|
||
Getting the source skincluster info from the first mesh in the transfer objects list
|
||
Calculating the barycentric relationship with all consecutive meshes
|
||
Zipping the source and target mesh clusterweight dictionaries
|
||
Importing the resulting skin cluster weight dict to the skinclusterbuilder
|
||
|
||
If no transferObjects are provided the function will attempt to fetch a selection from the scene to operate on
|
||
:param transferNodes: <list> A list of transfer objects
|
||
:return:
|
||
'''
|
||
|
||
if transferNodes is None:
|
||
transferNodes = apiUtils.handleTransferNodesList(transferNodes)
|
||
# buffer and re-assign
|
||
|
||
success = False
|
||
if len(transferNodes):
|
||
|
||
sourceObj = transferNodes[0]
|
||
# 安全获取名称
|
||
try:
|
||
sourceName = str(sourceObj.name()) if hasattr(sourceObj, 'name') else str(sourceObj)
|
||
except:
|
||
sourceName = str(sourceObj)
|
||
targetNameList = transferNodes[1:]
|
||
|
||
loadBarMaxVal = len(targetNameList)
|
||
loadBarObj = apiUtils.LoadingBar()
|
||
|
||
# initialize self.sourceWeightDict which is populated by both functions
|
||
sourceWeightDict = buildSkinWeightsDict([sourceName], showLoadingBar)
|
||
|
||
if sourceWeightDict:
|
||
|
||
for tgtObject in targetNameList:
|
||
|
||
# deep copy because: Mutable datatypes
|
||
sourceWeightDictCopy = copy.deepcopy(sourceWeightDict)
|
||
|
||
# 安全获取名称
|
||
try:
|
||
targetName = str(tgtObject.name()) if hasattr(tgtObject, 'name') else str(tgtObject)
|
||
except:
|
||
targetName = str(tgtObject)
|
||
|
||
barycentrWeightDict = apiUtils.getBarycentricWeights(sourceName, targetName)
|
||
|
||
# initialize transferWeightDict
|
||
transferWeightDict = {}
|
||
# clone the sourceweight skincluster information
|
||
transferWeightDict[targetName] = sourceWeightDictCopy[sourceName]
|
||
# swap the clusterweights for the zipped result from zipClusterWeights
|
||
transferWeightDict[targetName]["skinCluster"]["clusterWeights"] = apiUtils.zipClusterWeights(
|
||
barycentrWeightDict, sourceWeightDictCopy[sourceName]["skinCluster"]["clusterWeights"])
|
||
|
||
skinClusterBuilder(targetName, transferWeightDict)
|
||
maxInfVal = transferWeightDict[targetName]["skinCluster"]["clusterMaxInf"]
|
||
setMaxInfluences(maxInfVal, [targetName])
|
||
|
||
#progress loadingBar
|
||
if showLoadingBar:
|
||
loadBarObj.loadingBar("Transferring Skinweights...", loadBarMaxVal, "Transferring...")
|
||
|
||
success = True
|
||
|
||
if success:
|
||
pm.select(transferNodes, r=True)
|
||
|
||
|
||
def skinClusterBuilder(objName, weightDict, deleteHist=True, stripJointNamespaces=False, addNewToHierarchy=False):
|
||
'''
|
||
Builds a skin cluster for a given object and a given weight dictionary
|
||
By default the function deletes the history on the target object to create a "clean start"
|
||
:param objName:
|
||
:param weightDict: <dict>
|
||
:param deleteHist: <bool>
|
||
:param stripJointNamespaces: <bool> strips joint namespaces on skinWeights file, default is False
|
||
:param addNewToHierarchy: <bool> adds missing joints, world transform and parent only correct when exported with joint info, default is False
|
||
:return:
|
||
'''
|
||
startBuildTimer = time.time()
|
||
|
||
# clear any messy history on the validObjects
|
||
if deleteHist:
|
||
if pm:
|
||
pm.select(objName, r=True)
|
||
pm.mel.eval("DeleteHistory;")
|
||
pm.select(clear=True)
|
||
else:
|
||
cmds.select(objName, r=True)
|
||
cmds.delete(objName, constructionHistory=True)
|
||
cmds.select(clear=True)
|
||
|
||
clusterInfo = weightDict[objName]["skinCluster"]
|
||
|
||
# lclusterInfo has 4 attribute keys :
|
||
# ['clusterInfluenceNames', 'clusterMaxInf', 'clusterVtxCount', 'clusterWeights']
|
||
clusterJoints = clusterInfo["clusterInfluenceNames"]
|
||
clusterMaxInf = clusterInfo["clusterMaxInf"]
|
||
clusterJointInfo = clusterInfo.get("skinJointInformation")
|
||
|
||
for jointNameOrig in clusterJoints:
|
||
jointName = jointNameOrig
|
||
if stripJointNamespaces:
|
||
jointName = jointNameOrig.split(":")[-1]
|
||
|
||
objExists = pm.objExists(jointName) if pm else cmds.objExists(jointName)
|
||
if not objExists:
|
||
if pm:
|
||
pm.select(cl=True)
|
||
joint = pm.joint(p=(0, 0, 0), name=jointName)
|
||
# putting joint in the hierarchy and setting matrix
|
||
if addNewToHierarchy and clusterJointInfo:
|
||
jointInfo = clusterJointInfo.get(jointNameOrig)
|
||
if not jointInfo:
|
||
continue
|
||
parentJoint = jointInfo.get("parent")
|
||
if stripJointNamespaces:
|
||
parentJoint = parentJoint.split(":")[-1]
|
||
if pm.objExists(parentJoint):
|
||
parentJoint = pm.PyNode(parentJoint)
|
||
joint.setParent(parentJoint)
|
||
joint.setMatrix(jointInfo.get("matrix"), worldSpace=True)
|
||
joint.setAttr("jointOrient", jointInfo.get("jointOrient"))
|
||
joint.setRotation(jointInfo.get("rotation"))
|
||
else:
|
||
joint.setMatrix(jointInfo.get("matrix"), worldSpace=True)
|
||
pm.select(cl=True)
|
||
else:
|
||
# cmds 版本
|
||
cmds.select(clear=True)
|
||
joint = cmds.joint(position=(0, 0, 0), name=jointName)
|
||
# putting joint in the hierarchy and setting matrix
|
||
if addNewToHierarchy and clusterJointInfo:
|
||
jointInfo = clusterJointInfo.get(jointNameOrig)
|
||
if not jointInfo:
|
||
continue
|
||
parentJoint = jointInfo.get("parent")
|
||
if stripJointNamespaces:
|
||
parentJoint = parentJoint.split(":")[-1]
|
||
if cmds.objExists(parentJoint):
|
||
cmds.parent(joint, parentJoint)
|
||
cmds.xform(joint, matrix=jointInfo.get("matrix"), worldSpace=True)
|
||
cmds.setAttr(joint + ".jointOrient", *jointInfo.get("jointOrient"))
|
||
cmds.xform(joint, rotation=jointInfo.get("rotation"))
|
||
else:
|
||
cmds.xform(joint, matrix=jointInfo.get("matrix"), worldSpace=True)
|
||
cmds.select(clear=True)
|
||
try:
|
||
if stripJointNamespaces:
|
||
clusterJoints = [a.split(":")[-1] for a in clusterJoints]
|
||
|
||
if pm:
|
||
clusterNode = pm.skinCluster(clusterJoints, objName, tsb=True, mi=clusterMaxInf, omi=True)
|
||
# turn of normalization to nuke weights to 0, this is to get a true 1->1 application of old weights
|
||
pm.setAttr('%s.normalizeWeights' % clusterNode, 0)
|
||
pm.skinPercent(clusterNode, objName, nrm=False, prw=100)
|
||
pm.setAttr('%s.normalizeWeights' % clusterNode, 1)
|
||
clusterNodeName = str(clusterNode)
|
||
else:
|
||
# cmds 版本
|
||
clusterNode = cmds.skinCluster(clusterJoints, objName, tsb=True, mi=clusterMaxInf, omi=True)[0]
|
||
# turn of normalization to nuke weights to 0, this is to get a true 1->1 application of old weights
|
||
cmds.setAttr('%s.normalizeWeights' % clusterNode, 0)
|
||
cmds.skinPercent(clusterNode, objName, normalize=False, pruneWeights=100)
|
||
cmds.setAttr('%s.normalizeWeights' % clusterNode, 1)
|
||
clusterNodeName = clusterNode
|
||
|
||
# set the skinweights
|
||
setSkinWeights(clusterNodeName, clusterInfo["clusterWeights"])
|
||
|
||
|
||
except RuntimeError:
|
||
print("Failed to build new skinCluster on %s" % objName)
|
||
except:
|
||
print(traceback.format_exc())
|
||
|
||
endBuildTimer = time.time()
|
||
print("Skin Cluster Built %s : " % objName + (str(endBuildTimer - startBuildTimer)))
|
||
|
||
|
||
|