|
"""2018 James Dunlop""" |
|
import maya.api.OpenMaya as om2 |
|
import maya.cmds as cmds |
|
import logging |
|
import time |
|
|
|
logging.basicConfig() |
|
logger = logging.getLogger(__name__) |
|
|
|
def skinTo(maxInfluences=4, byUVSpace=False, uvSpace=['map1', 'map1'], surfaceAssociation="closestComponent"): |
|
""" |
|
Supports: kMesh and kNurbsCurve |
|
Use this to copy the skin weights from one mesh to another. |
|
Note: You don't need to have an existing skinCluster on the mesh you want to transfer to. It will bind for you. |
|
@:param maxInfluences: is the number of max influences on the source |
|
@:param byUVSpace: if you want to xFer using UV maps or not |
|
@:param uvSpace: The uvSpace flag indicates that the weight transfer should occur in UV space, based on the source |
|
and destination UV sets specified. |
|
@:param surfaceAssociation: The surfaceAssociation flag controls how the weights are transferred between the |
|
surfaces: "closestPoint", "rayCast", or "closestComponent". The default is closestComponent. |
|
:return: |
|
""" |
|
start = time.time() |
|
mySel = om2.MSelectionList() |
|
for eachMesh in cmds.ls(sl=True, long=True): |
|
mySel.add(eachMesh) |
|
|
|
## Bail out if bad selection |
|
if mySel.length() < 2: |
|
logger.warning("You must select a source mesh then ## of destination meshes!") |
|
return |
|
|
|
# Find the skinCluster and the influences we need to bind. |
|
sourceMObj = mySel.getDependNode(0) |
|
sourceMObjH = om2.MObjectHandle(sourceMObj) |
|
sourceMFnDep = om2.MFnDependencyNode(sourceMObj) |
|
sourceSkCls = _findSkinCluster(sourceMObjH) |
|
if sourceSkCls is None: |
|
logger.warning("No valid skinCluster could be found on {}".format(sourceMFnDep.name())) |
|
return |
|
|
|
bindInfluences = _findInfluences(sourceSkCls) |
|
|
|
## Now bind each mesh to these influences |
|
for x in range(mySel.length()): |
|
## Skip source mesh |
|
if x == 0: |
|
continue |
|
destMObj = mySel.getDependNode(x) |
|
destMObjH = om2.MObjectHandle(destMObj) |
|
destMFnDep = om2.MFnDependencyNode(destMObj) |
|
skCls = _findSkinCluster(destMObjH) |
|
if skCls is not None: |
|
logger.warning("Found a skinCluster on {}. Skipping bind!".format(destMFnDep.name())) |
|
else: |
|
## Here we have to use CMDS to create the darn skinCluster and xfer! |
|
## Not happy mixing cmds and om2 but short of writing a full om2 bind I'm sticking to this for now. |
|
cmds.skinCluster(bindInfluences + [destMFnDep.name()], name="{}_skCls".format(destMFnDep.name()), |
|
before=True, maximumInfluences=maxInfluences) |
|
|
|
## Now copy the weights over |
|
srcSkCls_MFnDep = om2.MFnDependencyNode(sourceSkCls.object()) |
|
if byUVSpace: |
|
cmds.copySkinWeights(sourceSkin=srcSkCls_MFnDep.name(), |
|
destinationSkin="{}_skCls".format(destMFnDep.name()), |
|
sa=surfaceAssociation, |
|
noMirror=True, |
|
ia=["closestJoint", "label", ], |
|
uvSpace=uvSpace) |
|
else: |
|
cmds.copySkinWeights(sourceSkin=srcSkCls_MFnDep.name(), |
|
destinationSkin="{}_skCls".format(destMFnDep.name()), |
|
sa=surfaceAssociation, |
|
noMirror=True, |
|
ia=["closestJoint", "label", ]) |
|
|
|
logger.info("SkinTo complete! Time taken: {}secs".format(start-time.time())) |
|
|
|
####################################################################################################################### |
|
### UTILS |
|
def iterForSkinCluster(node): |
|
""" |
|
:param node: MObject for the source connection |
|
:return: MObject |
|
""" |
|
if node.apiType() == om2.MFn.kSkinClusterFilter: |
|
return om2.MObjectHandle(node) |
|
|
|
iterDg = om2.MItDependencyGraph(node, |
|
om2.MItDependencyGraph.kDownstream, |
|
om2.MItDependencyGraph.kPlugLevel) |
|
while not iterDg.isDone(): |
|
currentItem = iterDg.currentNode() # use currentItem for 2017 and below |
|
if currentItem.hasFn(om2.MFn.kSkinClusterFilter): |
|
return om2.MObjectHandle(currentItem) |
|
|
|
iterDg.next() |
|
|
|
def _findSkinCluster(mesh=None): |
|
""" |
|
Returns a skinCluster attached to the kMesh or kNurbsCurve |
|
@:param mesh: MObjectHandle. Using the handles here may be playing it a little too safe. But meh. |
|
:return: MObject |
|
""" |
|
if not mesh.isValid(): |
|
logger.warning("Destination is no longer valid!") |
|
return |
|
|
|
dagPath = om2.MDagPath() |
|
geo = dagPath.getAPathTo(mesh.object()) |
|
|
|
## Does it have a valid number of shapes? |
|
if geo.numberOfShapesDirectlyBelow() != 0: |
|
## Fetch the shape of the geo now. |
|
shapeMobj = geo.extendToShape().node() |
|
mFn_shape = om2.MFnDependencyNode(shapeMobj) |
|
apiType = shapeMobj.apiType() |
|
if apiType == om2.MFn.kMesh: |
|
## Look at the inMesh attr for the source |
|
inMesh_attr = mFn_shape.attribute('inMesh') |
|
elif apiType == om2.MFn.kNurbsCurve: |
|
inMesh_attr = mFn_shape.attribute('create') |
|
else: |
|
logger.warning("This type of om2.MFn node is not supported! int: {}".format(apiType)) |
|
return |
|
|
|
inMesh_plug = om2.MPlug(shapeMobj, inMesh_attr) |
|
getSource = inMesh_plug.source().node() |
|
|
|
## Now use the iterForSkinCluster() function to find the skinCluster in the connected network. |
|
skinClusterNode_MObjH = _iterForSkinCluster(getSource) |
|
|
|
if skinClusterNode_MObjH is not None: |
|
return skinClusterNode_MObjH |
|
else: |
|
return None |
|
|
|
return None |
|
|
|
def _findInfluences(skinClusterMobjH=None): |
|
""" |
|
Returns all the valid influences from the .matrix attribute on the skinCluster node. |
|
@:param mesh: MObjectHandle for the skinCluster. Using the handles here may be playing it a little too safe. But meh. |
|
:return: MObject |
|
""" |
|
if not skinClusterMobjH.isValid(): |
|
logger.warning("Skincluster is no longer valid! Did it get deleted?") |
|
return |
|
|
|
skClsMFnDep = om2.MFnDependencyNode(skinClusterMobjH.object()) |
|
mtxAttr = skClsMFnDep.attribute("matrix") |
|
matrixPlug = om2.MPlug(skinClusterMobjH.object(), mtxAttr) |
|
|
|
## Get a list of all the valid connected indices in the matrix array now. |
|
indices = matrixPlug.getExistingArrayAttributeIndices() |
|
influences = [] |
|
for idx in indices: |
|
influences.append(om2.MFnDependencyNode(matrixPlug.elementByLogicalIndex(idx).source().node()).absoluteName()) |
|
|
|
return influences |