Source code for bgdev.utils.skincluster

"""Utility methods for skinclusters.

:created: 26/02/2017
:author: Benoit Gielly <benoit.gielly@gmail.com>
"""
from __future__ import absolute_import

import logging

import bgdev.utils.decorator
import bgdev.utils.shape
from maya import cmds, mel

LOG = logging.getLogger(__name__)

SKINCLUSTER_SETTINGS = {
    "toSelectedBones": True,
    "bindMethod": 0,
    "skinMethod": 2,
    "normalizeWeights": 1,
    "weightDistribution": 1,
    "maximumInfluences": 3,
    "obeyMaxInfluences": True,
    "dropoffRate": 4.0,
    "removeUnusedInfluence": False,
}


[docs]def get_skincluster(node): """Return the skincluster of given node. If the passed node already is a skincluster, just return it. Args: node (str): Can be either the skincluster or the bound node. Raises: RuntimeError: If the skincluster cannot be found. Returns: str: The skincluster bound to the node. """ skincluster = node if cmds.nodeType(node) != "skinCluster": skincluster = mel.eval("findRelatedSkinCluster {0}".format(node)) return skincluster
[docs]def get_influences(node, weighted=False): """Get influences of given skincluster. Args: node (str): Can be either the skincluster or the bound node. weighted (bool): If True, returns only weighted influences. Returns: list: List of influences. Raises: RuntimeError: If the skincluster cannot be found. """ skc = get_skincluster(node) if not skc: raise RuntimeError("Couldn't find a skincluster.") flags = {"influence": True} if weighted: flags = {"weightedInfluence": True} influences = cmds.skinCluster(skc, query=True, **flags) or [] return influences
[docs]@bgdev.utils.decorator.UNDO_REPEAT def add_influences_callback(): """Call back :func:`add_influences`.""" selection = cmds.ls(selection=True) if not selection: LOG.warning("Please select at least 2 nodes!") return joints, node = selection[:-1], selection[-1] add_influences(node, joints) cmds.select(selection)
[docs]def add_influences(node, joints): """Add given joints to the skincluster. Args: node (str): Can be either the skincluster or the bound node. joints (list): List of joints/influences to add. Raises: RuntimeError: If the skincluster cannot be found. """ # add each targets to the skincluster skincluster = get_skincluster(node) if not skincluster: raise RuntimeError("Couldn't find a skincluster.") influences = get_influences(skincluster) for each in joints: if each not in influences: cmds.skinCluster( skincluster, edit=True, addInfluence=each, weight=0 )
[docs]@bgdev.utils.decorator.UNDO_REPEAT def remove_influences_callback(unused=False): """Call back :func:`remove_influences`.""" selection = cmds.ls(selection=True) if not selection: LOG.warning("Please select at least 1 node!") return if unused: for node in selection: remove_influences(node, unused=True) else: joints, node = selection[:-1], selection[-1] remove_influences(node, joints)
[docs]def remove_influences(node, joints=None, unused=False, disconnect=False): """Remove given joints from the skincluster. Args: node (str): Can be either the skincluster or the bound node. joints (list): List of joints/influences to add. unused (bool): Removed all unused influences found. This flag ignores `joints` when used. Raises: RuntimeError: If the skincluster cannot be found. """ # add each targets to the skincluster skc = get_skincluster(node) if not skc: raise RuntimeError("Couldn't find a skincluster.") influences = get_influences(skc) if unused: weighted = get_influences(skc, weighted=True) joints = list(set(influences) - set(weighted)) LOG.info("Found %s influences to remove in %s", len(joints), skc) for each in joints: if not each in influences: continue if disconnect: index = influences.index(each) plug = "{}.matrix[{}]".format(skc, index) value = cmds.getAttr(plug) cmds.disconnectAttr(each + ".worldMatrix", plug) cmds.setAttr(plug, value, type="matrix") continue cmds.skinCluster(skc, edit=True, removeInfluence=each)
[docs]@bgdev.utils.decorator.UNDO_REPEAT def select_influences_callback(): """Call back :func:`select_influences`.""" selection = cmds.ls(selection=True) if not selection: LOG.warning("Please select at least 1 node!") return influences = [] for each in selection: influences.extend(get_influences(each)) cmds.select(influences)
[docs]@bgdev.utils.decorator.UNDO_REPEAT def bind_skincluster_callback(): """Call back :func:`bind_skincluster`.""" selection = cmds.ls(selection=True) if not selection: LOG.warning("Please select at least 1 node!") return joints = {_ for _ in selection if cmds.nodeType(_) == "joint"} nodes = set(selection) - joints for each in nodes: bind_skincluster(each, list(joints)) cmds.select(selection)
[docs]def bind_skincluster(mesh=None, bones=None): """Create a new skincluster with preset options. Args: mesh (str): Name of the geometry to bind. bones (list): List of bones/influences. Returns: str: The created skincluster. """ shapes = [] # create a default dictionnary to store the best settings for a skincluster default_settings = dict(SKINCLUSTER_SETTINGS) # if a mesh is given, rename the skincluster accordingly if mesh: default_settings["name"] = "{0}_skinCluster".format(mesh) # bind the mesh to the skincluster if mesh and bones: skc = cmds.skinCluster(bones, mesh, **default_settings)[0] else: # assumes that only one geometry is selected (errors otherwise) skc = cmds.skinCluster(**default_settings)[0] # retrieve the shape and its transform used to bind shapes = cmds.skinCluster(skc, query=True, geometry=True) mesh = cmds.listRelatives(shapes, parent=True)[0] skc = cmds.rename(skc, "{0}_skinCluster".format(mesh)) # rename tweak shapes = shapes or bgdev.utils.shape.get_shapes(mesh) if shapes: tweak = cmds.listConnections(shapes[0], source=True, type="tweak") if tweak: cmds.rename(tweak, "{0}_tweak".format(mesh)) return skc
[docs]@bgdev.utils.decorator.UNDO_REPEAT def copy_skincluster_callback(method="closestPoint"): """Call back :func:`copy_skincluster`.""" selection = cmds.ls(selection=True) if not selection: LOG.warning("Please select at least 2 nodes!") return source, targets = selection[0], selection[1:] for target in targets: copy_skincluster(source, target, method) cmds.select(selection)
[docs]def copy_skincluster(source, target, method="closestPoint"): """Copy source skincluster onto target and keep same weights. Args: source (str): source mesh to copy skincluster form target (str): target mesh to paste skincluster to method (str): influence association method. Default is closestPoint. Raises: RuntimeError: If the source skincluster cannot be found. """ copy_settings = { # "influenceAssociation": ["oneToOne", "closestJoint", "closestBone"], "influenceAssociation": ["closestJoint", "closestBone", "oneToOne"], "sampleSpace": 0, "noMirror": True, "smooth": True, } # get skinclusters and influences source_skc = bgdev.utils.skincluster.get_skincluster(source) if not source_skc: raise RuntimeError("Couldn't find a source skincluster.") source_infs = cmds.skinCluster(source_skc, query=True, influence=True) infs_objects = [_ for _ in source_infs if cmds.nodeType(_) != "joint"] source_infs = [_ for _ in source_infs if _ not in infs_objects] # add influences if skincluster already exists target_infs = None target_skc = bgdev.utils.skincluster.get_skincluster(target) if target_skc: target_infs = cmds.skinCluster(target_skc, query=True, influence=True) diff_influences = set(source_infs + infs_objects) - set(target_infs) if diff_influences: cmds.skinCluster( target_skc, edit=True, addInfluence=list(diff_influences) ) # create skincluster otherwise else: name = "{0}_skinCluster".format(target).rsplit("|")[-1] name = name + "#" if cmds.objExists(name) else name skc_settings = dict(bgdev.utils.skincluster.SKINCLUSTER_SETTINGS) skc_settings["skinMethod"] = cmds.skinCluster( source_skc, query=True, skinMethod=True ) skc_settings["normalizeWeights"] = cmds.skinCluster( source_skc, query=True, normalizeWeights=True ) target_skc = cmds.skinCluster( source_infs, target, name=name, **skc_settings )[0] if infs_objects: cmds.skinCluster(target_skc, edit=True, addInfluence=infs_objects) # copy skincluster weights if method == "uv": copy_settings["uvSpace"] = ["map1", "map1"] else: copy_settings["surfaceAssociation"] = method cmds.copySkinWeights( sourceSkin=source_skc, destinationSkin=target_skc, **copy_settings )
[docs]@bgdev.utils.decorator.UNDO_REPEAT def reset_skincluster_callback(): """Call back :func:`reset_skincluster`.""" selection = cmds.ls(selection=True) if not selection: LOG.warning("Please select at least 1 node!") return for each in selection: reset_skincluster(each)
[docs]def reset_skincluster(node): """Reset the skin cluster in place. Args: node (str): SkinCluster node or the geometry bound to it. """ # get skinclusters skincluster = node if cmds.nodeType(node) != "skinCluster": skincluster = mel.eval("findRelatedSkinCluster {0}".format(node)) # detach skinclusters influences = cmds.skinCluster(skincluster, query=True, influence=True) for i, influence in enumerate(influences): # reset bindPreMatrix for each joints matrix = cmds.getAttr(influence + ".worldInverseMatrix") cmds.setAttr( "{0}.bindPreMatrix[{1}]".format(skincluster, i), matrix, type="matrix", ) cmds.skinCluster(skincluster, edit=True, recacheBindMatrices=True) # reset dagPose dag_poses = cmds.listConnections( skincluster, source=True, type="dagPose" ) for each in dag_poses: cmds.dagPose(influence, reset=True, name=each)
[docs]@bgdev.utils.decorator.UNDO_REPEAT def detach_skincluster_callback(clean=False): """Call back :func:`detach_skincluster`.""" selection = cmds.ls(selection=True) if not selection: LOG.warning("Please select at least 1 node!") return for each in selection: detach_skincluster(each, clean)
[docs]def detach_skincluster(node, clean=False): """Detach the skin cluster attached to given node. Args: node (str): Name of the node to unbind. clean (bool): If True, remove shapeOrigs and unlock transforms. """ # detach skincluster if get_skincluster(node): cmds.skinCluster(node, edit=True, unbind=True) cmds.refresh() if not clean: return # make sure there's no other deformers before cleaning up for each in cmds.listHistory(node, levels=1): if "geometryFilter" in cmds.nodeType(each, inherited=True): LOG.info( "Other deformers than skincluster found. Skipping cleanup..." ) return # delete shapeOrig shapes = bgdev.utils.shape.get_shapes(node, True, True) if shapes: cmds.delete(shapes) # unlock transforms (if detach skincluster didn't do it) for attr in [x + y for x in "trs" for y in "xyz"]: cmds.setAttr("{0}.{1}".format(node, attr), lock=False)