aboutsummaryrefslogtreecommitdiff
path: root/Core/Scripts/System/riggingUtils.py
diff options
context:
space:
mode:
authorMobileMachine\jeremy <[email protected]>2017-06-06 22:59:03 -0400
committerMobileMachine\jeremy <[email protected]>2017-06-06 22:59:03 -0400
commit24725fa8681f906ab44d80687c09fecc171a2896 (patch)
tree312a601df29aca7f8db9f44082d96ebc7a679138 /Core/Scripts/System/riggingUtils.py
parentInitial commit (diff)
downloadartv2-24725fa8681f906ab44d80687c09fecc171a2896.tar.xz
artv2-24725fa8681f906ab44d80687c09fecc171a2896.zip
Initial Submission
First submission of current state of ARTv2. Currently considered to be in Alpha. There are a couple of animation tools not implemented yet, and one module not implemented yet, as well as incomplete documentation.
Diffstat (limited to 'Core/Scripts/System/riggingUtils.py')
-rw-r--r--Core/Scripts/System/riggingUtils.py1448
1 files changed, 1448 insertions, 0 deletions
diff --git a/Core/Scripts/System/riggingUtils.py b/Core/Scripts/System/riggingUtils.py
new file mode 100644
index 0000000..6bce000
--- /dev/null
+++ b/Core/Scripts/System/riggingUtils.py
@@ -0,0 +1,1448 @@
+"""
+Author: Jeremy Ernst
+
+This module is a collection of utility functions for generic rigging and skinning tasks.
+There are also classes in the module that deal with saving and loading skin weight files.
+"""
+
+import json
+import os
+import time
+
+import maya.cmds as cmds
+from maya import OpenMaya, OpenMayaAnim
+
+import System.mathUtils as mathUtils
+import utils as utils
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# GLOBALS
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+ATTRIBUTES = ['skinningMethod', 'normalizeWeights', 'dropoffRate',
+ 'maintainMaxInfluences', 'maxInfluences','bindMethod',
+ 'useComponents', 'normalizeWeights', 'weightDistribution',
+ 'heatmapFalloff']
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# FUNCTIONS (STAND-ALONE)
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def getScaleFactor():
+ # ===========================================================================
+ # #find meshes
+ # ===========================================================================
+ weightedMeshes = []
+ skinClusters = cmds.ls(type='skinCluster')
+
+ for cluster in skinClusters:
+ geometry = cmds.skinCluster(cluster, q=True, g=True)[0]
+ geoTransform = cmds.listRelatives(geometry, parent=True)[0]
+ weightedMeshes.append(geoTransform)
+
+ # scale factor of 1 = 180cm
+ if len(weightedMeshes) > 0:
+ scale = cmds.exactWorldBoundingBox(weightedMeshes, ii=True)[5]
+ scaleFactor = scale / 180
+ else:
+ scaleFactor = 1
+
+ return scaleFactor
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createDriverSkeleton():
+ # Original Author: Jeremy Ernst
+
+ # ===========================================================================
+ # #duplicate the entire skeleton
+ # ===========================================================================
+ duplicateSkel = cmds.duplicate("root", rc=True)[0]
+ cmds.select("root", hi=True)
+ joints = cmds.ls(sl=True)
+
+ cmds.select(duplicateSkel, hi=True)
+ dupeJoints = cmds.ls(sl=True)
+
+ # ===========================================================================
+ # #rename the duplicate joints
+ # ===========================================================================
+ driverJoints = []
+ for i in range(int(len(dupeJoints))):
+
+ if cmds.objExists(dupeJoints[i]):
+ driverJoint = cmds.rename(dupeJoints[i], "driver_" + joints[i])
+ driverJoints.append(driverJoint)
+
+ # ===========================================================================
+ # #create a direct connection between the driver and the export joints
+ # ===========================================================================
+ for joint in driverJoints:
+ exportJoint = joint.partition("driver_")[2]
+ cmds.connectAttr(joint + ".translate", exportJoint + ".translate")
+ cmds.connectAttr(joint + ".rotate", exportJoint + ".rotate")
+ cmds.connectAttr(joint + ".scale", exportJoint + ".scale")
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def buildSkeleton():
+ # get all of the rig modules in the scene
+ modules = utils.returnRigModules()
+
+ # go through each one, and find the created bones for that modules
+ createdJoints = []
+ for module in modules:
+ if module != "ART_Root_Module":
+ joints = cmds.getAttr(module + ".Created_Bones")
+ splitJoints = joints.split("::")
+
+ for bone in splitJoints:
+ if bone != "":
+ # create the joint
+ if cmds.objExists(bone):
+ cmds.warning(
+ "Object with name: " + bone + " already exists. Renaming object with _renamed suffix.")
+ cmds.rename(bone, bone + "_renamed")
+ cmds.select(clear=True)
+ newJoint = cmds.joint(name=str(bone))
+ cmds.select(clear=True)
+
+ # find the LRA control
+ moduleName = cmds.getAttr(module + ".moduleName")
+ baseName = cmds.getAttr(module + ".baseName")
+
+ # find prefix/suffix if available
+ prefix = moduleName.partition(baseName)[0]
+ suffix = moduleName.partition(baseName)[2]
+ if prefix == "":
+ prefixSeparator = " "
+ else:
+ prefixSeparator = prefix
+
+ if suffix == "":
+ suffixSeparator = " "
+ else:
+ suffixSeparator = suffix
+
+ # get the bone name (for example, thigh)
+ if prefix == "":
+ boneName = bone.partition(suffixSeparator)[0]
+ else:
+ boneName = bone.partition(prefixSeparator)[2].partition(suffixSeparator)[0]
+
+ # get the lra node
+ lra = prefix + baseName + suffix + "_" + boneName + "_lra"
+ lra = lra.replace(" ", "")
+
+ if not cmds.objExists(lra):
+ lra = prefix + baseName + suffix + "_lra"
+ lra = lra.replace(" ", "")
+
+ # position bone at lra
+ constraint = cmds.parentConstraint(lra, newJoint)[0]
+ cmds.delete(constraint)
+
+ # find parent joint and append data to list
+ if bone != splitJoints[0]:
+ relatives = str(cmds.listRelatives(lra, fullPath=True))
+ relatives = relatives.split("|")
+ relatives = relatives[::-1]
+ for relative in relatives:
+ searchKey = lra.partition("_lra")[0]
+
+ if relative.find(searchKey) == -1:
+ parentBoneName = relative.partition(moduleName + "_")[2].partition("_mover")[0]
+ parent = prefix + parentBoneName + suffix
+ createdJoints.append([bone, parent])
+ break
+ else:
+ parent = cmds.getAttr(module + ".parentModuleBone")
+ createdJoints.append([bone, parent])
+
+ # if this is the root module, it's a bit simpler
+ else:
+ jointName = cmds.getAttr(module + ".Created_Bones")
+ # create the joint
+ if cmds.objExists(jointName):
+ cmds.warning(
+ "Object with name: " + jointName + " already exists. Renaming object with _renamed suffix.")
+ cmds.rename(jointName, jointName + "_renamed")
+
+ cmds.select(clear=True)
+ cmds.joint(name=str(jointName))
+ cmds.select(clear=True)
+
+ # go through the data and setup the hierarchy now that all bones have been created
+ for joint in createdJoints:
+ cmds.parent(joint[0], joint[1])
+
+ # freeze rotation transformations
+ cmds.makeIdentity("root", t=False, r=True, s=False, apply=True)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createBoneConnection(mover, childMover, userSpecName):
+ if cmds.objExists(userSpecName + "_parentGrp"):
+ cmds.delete(userSpecName + "_parentGrp")
+
+ grp = cmds.group(empty=True, name=userSpecName + "_parentGrp")
+ cmds.parent(grp, userSpecName + "_bone_representations")
+
+ crv = cmds.curve(d=1, p=[(0, 0, 0), (0, 20, 0)], k=[0, 1], name=mover + "_to_" + childMover + "_connector_geo")
+ cmds.parent(crv, grp)
+
+ cmds.select(crv + ".cv[0]")
+ topCluster = cmds.cluster(name=mover + "_to_" + childMover + "_top_cluster")[1]
+ cmds.select(crv + ".cv[1]")
+ botCluster = cmds.cluster(name=mover + "_to_" + childMover + "_end_cluster")[1]
+ cmds.pointConstraint(mover, topCluster)
+ cmds.pointConstraint(childMover, botCluster)
+
+ cmds.setAttr(topCluster + ".v", False, lock=True)
+ cmds.setAttr(botCluster + ".v", False, lock=True)
+ cmds.setAttr(crv + ".overrideEnabled", 1)
+ cmds.setAttr(crv + ".overrideColor", 1)
+ cmds.setAttr(crv + ".overrideDisplayType", 2)
+
+ cmds.parent(topCluster, grp)
+ cmds.parent(botCluster, grp)
+ cmds.setAttr(crv + ".inheritsTransform", 0)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createFkRig(joints, networkNode, numRigs, slot):
+ # Original Author: Jeremy Ernst
+
+ # lists
+ fkControls = []
+
+ # ===========================================================================
+ # #take the list of incoming joints and find each joint's mover
+ # ===========================================================================
+ for joint in joints:
+ globalMover = utils.findAssociatedMover(joint, networkNode)
+
+ # =======================================================================
+ # #if a mover is found, duplicate it and unparent the duplicate
+ # =======================================================================
+ if globalMover != None:
+ dupe = cmds.duplicate(globalMover, rr=True)[0]
+ utils.deleteChildren(dupe)
+ parent = cmds.listRelatives(dupe, parent=True)
+ if parent != None:
+ cmds.parent(dupe, world=True)
+
+ # turn on visiblity of the control
+ cmds.setAttr(dupe + ".v", 1)
+
+ # ensure pivot is correct
+ piv = cmds.xform(joint, q=True, ws=True, rp=True)
+ cmds.xform(dupe, ws=True, rp=piv)
+
+ # rename the control
+ fkControl = cmds.rename(dupe, "fk_" + joint + "_anim")
+ fkControls.append([joint, fkControl])
+
+ # create a group for the control
+ controlGrp = cmds.group(empty=True, name="fk_" + joint + "_anim_grp")
+ constraint = cmds.parentConstraint(joint, controlGrp)[0]
+ cmds.delete(constraint)
+
+ # parent the control under the controlGrp
+ cmds.parent(fkControl, controlGrp)
+
+ # freeze transformations on the control
+ cmds.makeIdentity(fkControl, t=1, r=1, s=1, apply=True)
+
+ # color the control
+ colorControl(fkControl)
+
+ returnData = None
+ createdControls = []
+
+ # ===========================================================================
+ # #go through the fk controls data and get the joint parent to create the hierarchy
+ # ===========================================================================
+ for data in fkControls:
+ joint = data[0]
+ control = data[1]
+ createdControls.append(control)
+
+ # =======================================================================
+ # #parent the anim group to the parent control
+ # =======================================================================
+ parent = cmds.listRelatives(joint, parent=True)
+ if parent != None:
+ group = control + "_grp"
+ parentControl = "fk_" + parent[0] + "_anim"
+ if parent[0] in joints:
+ if cmds.objExists(parentControl):
+ cmds.parent(group, parentControl)
+
+ else:
+ returnData = group
+
+ # =======================================================================
+ # #lastly, connect controls up to blender nodes to drive driver joints
+ # =======================================================================
+ cmds.pointConstraint(control, "driver_" + joint, mo=True)
+ cmds.orientConstraint(control, "driver_" + joint)
+
+ # plug master control scale into a new mult node that takes joint.scale into input 1, and master.scale into
+ # input 2, and plugs that into driver joint
+ if cmds.objExists("master_anim"):
+ globalScaleMult = cmds.shadingNode("multiplyDivide", asUtility=True, name=joint + "_globalScale")
+ cmds.connectAttr("master_anim.scale", globalScaleMult + ".input1")
+ cmds.connectAttr(control + ".scale", globalScaleMult + ".input2")
+ createConstraint(globalScaleMult, "driver_" + joint, "scale", False, numRigs, slot, "output")
+ else:
+ createConstraint(control, "driver_" + joint, "scale", False, numRigs, slot)
+
+ return [returnData, createdControls]
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createCounterTwistRig(joints, name, networkNode, startJoint, endJoint, parentGrp, offset=[0, 0, 0]):
+ # Original Author: Jeremy Ernst
+
+ # Usage: create a counter twist rig using the incoming joints
+ if len(joints) == 0:
+ return
+
+ # data lists
+ createdGroups = []
+ createdControls = []
+
+ # ===========================================================================
+ # #create the manual controls for the twist joints
+ # ===========================================================================
+ for i in range(len(joints)):
+
+ # driven group
+ group = cmds.group(empty=True, name=joints[i] + "_driven_grp")
+
+ constraint = cmds.parentConstraint(joints[i], group)[0]
+ cmds.delete(constraint)
+
+ # anim group
+ animGrp = cmds.duplicate(group, po=True, name=joints[i] + "_anim_grp")[0]
+ cmds.parent(group, animGrp)
+ createdGroups.append(animGrp)
+
+ # create the twist control
+ twistCtrl = createControl("arrow", 1.5, joints[i] + "_anim")
+ createdControls.append(twistCtrl)
+ constraint = cmds.pointConstraint(joints[i], twistCtrl)[0]
+ cmds.delete(constraint)
+ cmds.parent(twistCtrl, group)
+ colorControl(twistCtrl)
+
+ cmds.setAttr(twistCtrl + ".rx", offset[0])
+ cmds.setAttr(twistCtrl + ".ry", offset[1])
+ cmds.setAttr(twistCtrl + ".rz", offset[2])
+ cmds.setAttr(twistCtrl + ".v", lock=True, keyable=False)
+
+ cmds.makeIdentity(twistCtrl, t=1, r=1, s=1, apply=True)
+
+ # constrain the driver joint to the twist control
+ cmds.parentConstraint(twistCtrl, "driver_" + joints[i])
+
+ # plug master control scale into a new mult node that takes joint.scale into input 1, and master.scale into
+ # input 2, and plugs that into driver joint
+ if cmds.objExists("master_anim"):
+ globalScaleMult = cmds.shadingNode("multiplyDivide", asUtility=True, name=twistCtrl + "_globalScale")
+ cmds.connectAttr("master_anim.scale", globalScaleMult + ".input1")
+ cmds.connectAttr(twistCtrl + ".scale", globalScaleMult + ".input2")
+ createConstraint(globalScaleMult, "driver_" + joints[i], "scale", False, 2, 1, "output")
+ else:
+ createConstraint(twistCtrl, "driver_" + joints[i], "scale", False, 2, 1)
+
+ # ===========================================================================
+ # #create the ik twist joint chain
+ # ===========================================================================
+ ikTwistUpper = cmds.createNode("joint", name=name + "_counter_twist_ik")
+ ikTwistLower = cmds.createNode("joint", name=name + "_counter_twist_ik_end")
+
+ cmds.parent(ikTwistLower, ikTwistUpper)
+
+ # parent under the driver startJoint
+ cmds.parent(ikTwistUpper, "driver_" + startJoint)
+ constraint = cmds.parentConstraint("driver_" + startJoint, ikTwistUpper)[0]
+ cmds.delete(constraint)
+ constraint = cmds.parentConstraint("driver_" + endJoint, ikTwistLower)[0]
+ cmds.delete(constraint)
+
+ # add rp ik to those joints
+ twistIK = cmds.ikHandle(sol="ikRPsolver", name=name + "_counter_twist_ikHandle", sj=ikTwistUpper, ee=ikTwistLower)[
+ 0]
+ cmds.parent(twistIK, "driver_" + startJoint)
+
+ ikWorldPos = cmds.xform(twistIK, q=True, ws=True, t=True)[0]
+ if ikWorldPos < 0:
+ cmds.setAttr(twistIK + ".twist", 180)
+
+ # create ik pole vector
+ twistVector = cmds.spaceLocator(name=name + "_counter_twist_PoleVector")[0]
+ constraint = cmds.parentConstraint("driver_" + startJoint, twistVector)[0]
+ cmds.delete(constraint)
+ cmds.parent(twistVector, "driver_" + startJoint)
+
+ # create pole vector constraint
+ cmds.poleVectorConstraint(twistVector, twistIK)
+
+ # ===========================================================================
+ # #create roll locator (locator that actually will drive the roll amount)
+ # ===========================================================================
+ rollLoc = cmds.spaceLocator(name=name + "_counter_twist_tracker")[0]
+ cmds.setAttr(rollLoc + ".v", 0, lock=True)
+ constraint = cmds.parentConstraint("driver_" + startJoint, rollLoc)[0]
+ cmds.delete(constraint)
+
+ cmds.parent(rollLoc, name + "_group")
+ cmds.makeIdentity(rollLoc, t=0, r=1, s=0, apply=True)
+ cmds.orientConstraint("driver_" + startJoint, rollLoc, skip=["y", "z"])
+
+ # Group up and parent into rig
+ twistGrp = cmds.group(empty=True, name=name + "_counter_twist_grp")
+ constraint = cmds.parentConstraint(startJoint, twistGrp)[0]
+ cmds.delete(constraint)
+
+ for group in createdGroups:
+ cmds.parent(group, twistGrp)
+
+ # ===========================================================================
+ # #add twist grp to parent group and add all anim groups to twist group
+ # ===========================================================================
+ cmds.parent(twistGrp, parentGrp)
+
+ # constrain the group to the startJoint so it follows with the action of the limb
+ cmds.parentConstraint("driver_" + startJoint, twistGrp, mo=True)
+
+ # ===========================================================================
+ # #hook up multiply divide nodes
+ # ===========================================================================
+
+ # first one takes rollLoc rotateX and multiplies it by -1 to get the counter
+ counterMultNode = cmds.shadingNode("multiplyDivide", asUtility=True, name=name + "_counter_twist_counterNode")
+ cmds.connectAttr(rollLoc + ".rotateX", counterMultNode + ".input1X")
+ cmds.setAttr(counterMultNode + ".input2X", -1)
+
+ # second one takes the output of the counterMultNode and multiplies it with the twist amount
+ increment = float(1.0) / float(len(joints))
+ defaultValue = 1
+ for i in range(len(joints)):
+ attrName = joints[i] + "_twistAmt"
+ grpName = joints[i] + "_driven_grp"
+ cmds.addAttr(name + "_settings", ln=attrName, dv=defaultValue, keyable=True)
+ defaultValue -= increment
+
+ # multNode creation
+ rollMultNode = cmds.shadingNode("multiplyDivide", asUtility=True, name=joints[i] + "_rollNode")
+ cmds.connectAttr(counterMultNode + ".outputX", rollMultNode + ".input1X")
+ cmds.connectAttr(name + "_settings." + attrName, rollMultNode + ".input2X")
+
+ # connect output of roll node to driven group
+ cmds.connectAttr(rollMultNode + ".outputX", grpName + ".rotateX")
+
+ # add attr on rig settings node for manual twist control visibility
+ cmds.addAttr(name + "_settings", longName=(startJoint + "_twistCtrlVis"), at='bool', dv=0, keyable=True)
+ cmds.connectAttr(name + "_settings." + startJoint + "_twistCtrlVis", twistGrp + ".v")
+
+ return createdControls
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createTwistRig(joints, name, networkNode, startJoint, endJoint, parentGrp, offset=[0, -90, 0]):
+ # Original Author: Jeremy Ernst
+
+ # Usage: create a standard twist rig using the incoming joints
+ defaultValue = .75
+ createdControls = []
+
+ if len(joints) >= 1:
+ # create our roll group
+ rollGrp = cmds.group(empty=True, name=name + "_" + startJoint + "_twist_grp")
+ cmds.parent(rollGrp, parentGrp)
+ cmds.parentConstraint("driver_" + startJoint, rollGrp)
+
+ for i in range(len(joints)):
+
+ # =======================================================================
+ # #create the twist ctrl group
+ # =======================================================================
+ twistCtrlGrp = cmds.group(empty=True, name=joints[i] + "_twist_ctrl_grp")
+ constraint = cmds.parentConstraint("driver_" + joints[i], twistCtrlGrp)[0]
+ cmds.delete(constraint)
+
+ cmds.parent(twistCtrlGrp, rollGrp)
+
+ # =======================================================================
+ # #create the driven twist group
+ # =======================================================================
+ twistDrivenGrp = cmds.duplicate(twistCtrlGrp, po=True, name=joints[i] + "_twist_driven_grp")[0]
+ cmds.parent(twistDrivenGrp, twistCtrlGrp)
+
+ # =======================================================================
+ # #create the manual twist control
+ # =======================================================================
+ twistCtrl = createControl("arrow", 1, joints[i] + "_anim")
+ createdControls.append(twistCtrl)
+ cmds.makeIdentity(twistCtrl, t=0, r=1, s=0, apply=True)
+ constraint = cmds.parentConstraint(twistCtrlGrp, twistCtrl)[0]
+ cmds.delete(constraint)
+
+ cmds.setAttr(twistCtrl + ".rx", offset[0])
+ cmds.setAttr(twistCtrl + ".ry", offset[1])
+ cmds.setAttr(twistCtrl + ".rz", offset[2])
+ cmds.setAttr(twistCtrl + ".v", lock=True, keyable=False)
+
+ cmds.parent(twistCtrl, twistDrivenGrp)
+ cmds.makeIdentity(twistCtrl, t=1, r=1, s=1, apply=True)
+ colorControl(twistCtrl)
+
+ # =======================================================================
+ # #drive the twist
+ # =======================================================================
+ cmds.addAttr(name + "_settings", ln=joints[i] + "_twistAmt", dv=defaultValue, keyable=True)
+ twistMultNode = cmds.shadingNode("multiplyDivide", asUtility=True, name=joints[i] + "_mult_node")
+ cmds.connectAttr("driver_" + endJoint + ".rx", twistMultNode + ".input1X")
+ cmds.connectAttr(name + "_settings." + joints[i] + "_twistAmt", twistMultNode + ".input2X")
+ cmds.connectAttr(twistMultNode + ".outputX", twistDrivenGrp + ".rx")
+
+ # constrain the driver joint to the twist control
+ cmds.parentConstraint(twistCtrl, "driver_" + joints[i])
+ cmds.scaleConstraint(twistCtrl, "driver_" + joints[i])
+
+ # plug master control scale into a new mult node that takes joint.scale into input 1, and master.scale into
+ # input 2, and plugs that into driver joint
+ if cmds.objExists("master_anim"):
+ globalScaleMult = cmds.shadingNode("multiplyDivide", asUtility=True, name=twistCtrl + "_globalScale")
+ cmds.connectAttr("master_anim.scale", globalScaleMult + ".input1")
+ cmds.connectAttr(twistCtrl + ".scale", globalScaleMult + ".input2")
+ createConstraint(globalScaleMult, "driver_" + joints[i], "scale", False, 2, 1, "output")
+ else:
+ createConstraint(twistCtrl, "driver_" + joints[i], "scale", False, 2, 1)
+
+ # increment the default value
+ increment = float(0.75) / float(len(joints))
+ defaultValue -= increment
+
+ # add attr on rig settings node for manual twist control visibility
+ if len(joints) >= 1:
+ cmds.addAttr(name + "_settings", longName=(startJoint + "_twistCtrlVis"), at='bool', dv=0, keyable=True)
+ cmds.connectAttr(name + "_settings." + startJoint + "_twistCtrlVis", rollGrp + ".v")
+
+ return createdControls
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createControlFromMover(joint, networkNode, orientToJoint, buildSpaceSwitch):
+ # ===========================================================================
+ # #take the list of incoming joints and find each joint's mover
+ # ===========================================================================
+ globalMover = utils.findAssociatedMover(joint, networkNode)
+
+ # ===========================================================================
+ # #if a mover is found, duplicate it and unparent the duplicate
+ # ===========================================================================
+ if globalMover is not None:
+ control = cmds.duplicate(globalMover, rr=True)[0]
+ utils.deleteChildren(control)
+ parent = cmds.listRelatives(control, parent=True)
+ if parent is not None:
+ cmds.parent(control, world=True)
+
+ # turn on visiblity of the control
+ cmds.setAttr(control + ".v", 1)
+
+ for attr in [".translateX", ".translateY", ".translateZ", ".rotateX", ".rotateY", ".rotateZ", ".scaleX",
+ ".scaleY", ".scaleZ"]:
+ cmds.setAttr(control + attr, lock=False, keyable=True)
+
+ # ensure pivot is correct
+ piv = cmds.xform(joint, q=True, ws=True, rp=True)
+ cmds.xform(control, ws=True, rp=piv)
+
+ # create a group for the control
+ controlGrp = cmds.group(empty=True, name=control + "_grp")
+ if orientToJoint:
+ constraint = cmds.parentConstraint(joint, controlGrp)[0]
+ else:
+ constraint = cmds.pointConstraint(joint, controlGrp)[0]
+
+ cmds.delete(constraint)
+
+ # parent the control under the controlGrp
+ cmds.parent(control, controlGrp)
+
+ # freeze transformations on the control
+ cmds.makeIdentity(control, t=1, r=1, s=1, apply=True)
+
+ # =======================================================================
+ # #buildSpaceSwitch nodes if needed
+ # =======================================================================
+ if buildSpaceSwitch:
+ spaceSwitchFollow = cmds.duplicate(controlGrp, po=True, name=control + "_space_switcher_follow")[0]
+ spaceSwitch = cmds.duplicate(controlGrp, po=True, name=control + "_space_switcher")[0]
+
+ utils.deleteChildren(spaceSwitchFollow)
+ utils.deleteChildren(spaceSwitch)
+
+ cmds.parent(spaceSwitch, spaceSwitchFollow)
+ cmds.parent(controlGrp, spaceSwitch)
+
+ return [control, controlGrp, spaceSwitch, spaceSwitchFollow]
+
+ else:
+ return [control, controlGrp]
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createConstraint(target, destination, attr, maintainOffset, numRigs, slot, customAttr=None):
+ # Original Author: Jeremy Ernst
+
+ # ===========================================================================
+ # #takes the incoming destination and hooks it up to the source using a ramp node
+ # ===========================================================================
+ if not cmds.objExists(destination + "_" + attr + "_ramp_node"):
+ cmds.shadingNode("ramp", asTexture=True, name=destination + "_" + attr + "_ramp_node")
+ cmds.setAttr(destination + "_" + attr + "_ramp_node.type", 1)
+
+ # ===========================================================================
+ # #if maintainOffset is passed in, store current destination attr values in an add node as an offset
+ # ===========================================================================
+ if maintainOffset:
+ if not cmds.objExists(destination + "_" + attr + "_offset_node_" + str(slot)):
+ cmds.shadingNode("plusMinusAverage", asUtility=True,
+ name=destination + "_" + attr + "_offset_node_" + str(slot))
+
+ offsetValues = cmds.getAttr(destination + "." + attr)[0]
+ cmds.setAttr(destination + "_" + attr + "_offset_node_" + str(slot) + ".input3D[0]", offsetValues[0],
+ offsetValues[1], offsetValues[2], type="double3")
+
+ # ===========================================================================
+ # #if maintainOffset is True, passed in the target attribute into the add node to add with the offset values
+ # ===========================================================================
+ if maintainOffset:
+ cmds.connectAttr(target + "." + attr, destination + "_" + attr + "_offset_node_" + str(slot) + ".input3D[1]")
+ cmds.connectAttr(destination + "_" + attr + "_offset_node_" + str(slot) + ".output3D",
+ destination + "_" + attr + "_ramp_node.colorEntryList[" + str(slot) + "].color")
+
+ # ===========================================================================
+ # #otherwise, connect target + .attr to the ramp
+ # ===========================================================================
+ else:
+ if customAttr is None:
+ cmds.connectAttr(target + "." + attr,
+ destination + "_" + attr + "_ramp_node.colorEntryList[" + str(slot) + "].color")
+ else:
+ cmds.connectAttr(target + "." + customAttr,
+ destination + "_" + attr + "_ramp_node.colorEntryList[" + str(slot) + "].color")
+
+ # ===========================================================================
+ # #get the point position on the ramp for this slot.
+ # ===========================================================================
+ try:
+ pointPos = 1 / (numRigs - 1) * slot
+ except:
+ pointPos = 0
+
+ cmds.setAttr(destination + "_" + attr + "_ramp_node.colorEntryList[" + str(slot) + "].position", pointPos)
+
+ # ===========================================================================
+ # #make the output connection if it doesn't yet exist
+ # ===========================================================================
+ rampExists = cmds.connectionInfo(destination + "." + attr, isDestination=True)
+ if not rampExists:
+ cmds.connectAttr(destination + "_" + attr + "_ramp_node.outColor", destination + "." + attr)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def coPlanarModeSnap(instance, snapTarget, snapMover, ikJoints, orientMovers, orientCtrl, skipAttrs):
+ # snap the incoming snapMover to snapTarget using the input attribute
+ cmds.undoInfo(openChunk=True)
+ cmds.cycleCheck(e=False)
+
+ # get current aimState
+ for attr in [".tx", ".ty", ".tz", ".rx", ".ry", ".rz"]:
+ cmds.setAttr(snapMover + attr, lock=False)
+
+ constraint = cmds.pointConstraint(snapTarget, snapMover, skip=skipAttrs)[0]
+ cmds.delete(constraint)
+ cmds.cycleCheck(e=True)
+
+ for attr in [".tx", ".ty", ".tz", ".rx", ".ry", ".rz"]:
+ cmds.setAttr(snapMover + attr, lock=True)
+
+ for i in range(len(ikJoints)):
+ connection = cmds.connectionInfo(orientMovers[i] + ".rotateX", sourceFromDestination=True)
+ aimConstraint = connection.rpartition(".")[0]
+
+ for attr in [[".rx", ".offsetX"]]:
+ value = cmds.getAttr(orientCtrl + attr[0])
+ cmds.setAttr(aimConstraint + attr[1], value)
+
+ cmds.undoInfo(closeChunk=True)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def createControl(controlType, size, name, useScaleFactor=True):
+ if useScaleFactor:
+ scale = getScaleFactor()
+ else:
+ scale = 1
+
+ if controlType == "circle":
+ control = cmds.circle(c=(0, 0, 0), sw=360, r=size * scale, d=3, name=name)[0]
+
+ if controlType == "square":
+ control = cmds.circle(c=(0, 0, 0), s=4, sw=360, r=size * scale, d=1, name=name)[0]
+ cmds.setAttr(control + ".rz", 45)
+
+ if controlType == "arrow":
+ control = cmds.curve(name=name, d=1,
+ p=[(0, -45, 0), (5, -45, 0), (5, -62, 0), (10, -62, 0), (0, -72, 0), (-10, -62, 0),
+ (-5, -62, 0), (-5, -45, 0), (0, -45, 0)])
+ cmds.xform(control, cp=True)
+ bounds = cmds.exactWorldBoundingBox(control)
+ length = abs(bounds[1] - bounds[4]) / 2
+ cmds.xform(control, r=True, rp=(0, length, 0), sp=(0, length, 0))
+ cmds.move(0, 0, 0, rpr=True)
+
+ cmds.setAttr(control + ".scaleX", size * scale)
+ cmds.setAttr(control + ".scaleY", size * scale)
+ cmds.setAttr(control + ".scaleZ", size * scale)
+
+ if controlType == "arrowOnBall":
+ control = cmds.curve(name=name, d=1, p=[(0.80718, 0.830576, 8.022739), (0.80718, 4.219206, 7.146586),
+ (0.80718, 6.317059, 5.70073), (2.830981, 6.317059, 5.70073),
+ (0, 8.422749, 2.94335), (-2.830981, 6.317059, 5.70073),
+ (-0.80718, 6.317059, 5.70073), (-0.80718, 4.219352, 7.146486),
+ (-0.80718, 0.830576, 8.022739), (-4.187851, 0.830576, 7.158003),
+ (-6.310271, 0.830576, 5.705409), (-6.317059, 2.830981, 5.7007),
+ (-8.422749, 0, 2.94335), (-6.317059, -2.830981, 5.70073),
+ (-6.317059, -0.830576, 5.70073), (-4.225134, -0.830576, 7.142501),
+ (-0.827872, -0.830576, 8.017446), (-0.80718, -4.176512, 7.160965),
+ (-0.80718, -6.317059, 5.70073), (-2.830981, -6.317059, 5.70073),
+ (0, -8.422749, 2.94335), (2.830981, -6.317059, 5.70073),
+ (0.80718, -6.317059, 5.70073), (0.80718, -4.21137, 7.151987),
+ (0.80718, -0.830576, 8.022739), (4.183345, -0.830576, 7.159155),
+ (6.317059, -0.830576, 5.70073), (6.317059, -2.830981, 5.70073),
+ (8.422749, 0, 2.94335), (6.317059, 2.830981, 5.70073),
+ (6.317059, 0.830576, 5.70073), (4.263245, 0.830576, 7.116234),
+ (0.80718, 0.830576, 8.022739)])
+
+ cmds.setAttr(control + ".scaleX", size * scale)
+ cmds.setAttr(control + ".scaleY", size * scale)
+ cmds.setAttr(control + ".scaleZ", size * scale)
+
+ if controlType == "semiCircle":
+ control = cmds.curve(name=name, d=3,
+ p=[(0, 0, 0), (7, 0, 0), (8, 0, 0), (5, 4, 0), (0, 5, 0), (-5, 4, 0), (-8, 0, 0),
+ (-7, 0, 0), (0, 0, 0)])
+ cmds.xform(control, ws=True, t=(0, 5, 0))
+ cmds.xform(control, ws=True, piv=(0, 0, 0))
+ cmds.makeIdentity(control, t=1, apply=True)
+
+ cmds.setAttr(control + ".scaleX", size * scale)
+ cmds.setAttr(control + ".scaleY", size * scale)
+ cmds.setAttr(control + ".scaleZ", size * scale)
+
+ if controlType == "pin":
+ control = cmds.curve(name=name, d=1, p=[(12, 0, 0), (0, 0, 0), (-12, -12, 0), (-12, 12, 0), (0, 0, 0)])
+ cmds.xform(control, ws=True, piv=[12, 0, 0])
+ cmds.setAttr(control + ".scaleY", .5)
+ cmds.makeIdentity(control, t=1, apply=True)
+
+ cmds.setAttr(control + ".scaleX", size * scale)
+ cmds.setAttr(control + ".scaleY", size * scale)
+ cmds.setAttr(control + ".scaleZ", size * scale)
+
+ if controlType == "sphere":
+ points = [(0, 0, 1), (0, 0.5, 0.866), (0, 0.866025, 0.5), (0, 1, 0), (0, 0.866025, -0.5), (0, 0.5, -0.866025),
+ (0, 0, -1), (0, -0.5, -0.866025), (0, -0.866025, -0.5), (0, -1, 0), (0, -0.866025, 0.5),
+ (0, -0.5, 0.866025), (0, 0, 1), (0.707107, 0, 0.707107), (1, 0, 0), (0.707107, 0, -0.707107),
+ (0, 0, -1), (-0.707107, 0, -0.707107), (-1, 0, 0), (-0.866025, 0.5, 0), (-0.5, 0.866025, 0),
+ (0, 1, 0), (0.5, 0.866025, 0), (0.866025, 0.5, 0), (1, 0, 0), (0.866025, -0.5, 0),
+ (0.5, -0.866025, 0), (0, -1, 0), (-0.5, -0.866025, 0), (-0.866025, -0.5, 0), (-1, 0, 0),
+ (-0.707107, 0, 0.707107), (0, 0, 1)]
+ control = cmds.curve(name=name, d=1, p=points)
+
+ cmds.setAttr(control + ".scaleX", size * scale)
+ cmds.setAttr(control + ".scaleY", size * scale)
+ cmds.setAttr(control + ".scaleZ", size * scale)
+
+ return control
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def colorControl(control, value=False):
+ # unlock overrides
+ cmds.setAttr(control + ".overrideEnabled", 1)
+
+ if value == False:
+ # get the world position of the control
+ worldPos = cmds.xform(control, q=True, ws=True, t=True)
+ if worldPos[0] > 0:
+ cmds.setAttr(control + ".overrideColor", 6)
+ else:
+ cmds.setAttr(control + ".overrideColor", 13)
+
+ else:
+ cmds.setAttr(control + ".overrideColor", value)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def fixSkinWeights():
+ # get selection
+ selection = cmds.ls(sl=True)
+ newSelection = []
+
+ # loop through selection getting the shapes and meshes
+ for each in selection:
+ shapes = cmds.listRelatives(each, shapes=True, pa=True)
+ meshes = cmds.ls(shapes, type="mesh")
+
+ # duplicate the object
+ duplicateObject = cmds.duplicate(each, rr=True)
+ cmds.delete(duplicateObject[0], ch=True)
+
+ newShapes = cmds.listRelatives(duplicateObject[0], shapes=True, pa=True)
+ newMeshes = cmds.ls(newShapes, type="mesh")
+
+ # find skinCluster of original object
+ skinCluster = findRelatedSkinCluster(each)
+
+ # find bones in skinCluster
+ bones = cmds.listConnections(skinCluster + ".matrix", d=True)
+ maxInfs = cmds.skinCluster(skinCluster, q=True, mi=True)
+ newSkin = cmds.skinCluster(bones, duplicateObject[0], mi=maxInfs, dr=4)[0]
+ cmds.copySkinWeights(ss=skinCluster, ds=newSkin, nm=True)
+
+ # rename
+ cmds.delete(each)
+ newObj = cmds.rename(duplicateObject, each)
+ newSelection.append(newObj)
+
+ cmds.select(newSelection)
+
+
+def export_skin_weights(file_path=None, geometry=None):
+ """Exports out skin weight from selected geometry"""
+ data = list()
+ # error handling
+ if not file_path:
+ return OpenMaya.MGlobal_displayError("No file path given.")
+ if not geometry:
+ geometry = _geometry_check(geometry)
+ if not geometry:
+ return OpenMaya.MGlobal_displayError("No valid geometry.")
+
+ # build up skin data
+ skin_clusters = find_skin_clusters(geometry)
+ if not skin_clusters:
+ skin_message = "No skin clusters found on {0}.".format(geometry)
+ OpenMaya.MGlobal_displayWarning(skin_message)
+ for skin_cluster in skin_clusters:
+ skin_data_init = SkinData(skin_cluster)
+ skin_data = skin_data_init.gather_data()
+ data.append(skin_data)
+ args = [skin_data_init.skin_cluster, file_path]
+ export_message = "SkinCluster: {0} has " \
+ "been exported to {1}.".format(*args)
+ OpenMaya.MGlobal_displayInfo(export_message)
+
+ # dump data
+ file_path = utils.win_path_convert(file_path)
+ data = json.dumps(data, sort_keys=True, ensure_ascii=True, indent=2)
+ fobj = open(file_path, 'wb')
+ fobj.write(data)
+ fobj.close()
+
+
+def find_skin_clusters(nodes):
+ """Uses all incoming to search relatives to
+ find associated skinCluster.
+ @PARAMS:
+ nodes: list
+ """
+ skin_clusters = list()
+ if not isinstance(nodes, list):
+ nodes = [nodes]
+ relatives = cmds.listRelatives(nodes, ad=True, path=True)
+ all_incoming = utils.find_all_incoming(relatives)
+ for node in all_incoming:
+ if cmds.nodeType(node) == "skinCluster":
+ if node not in skin_clusters:
+ skin_clusters.append(node)
+ return skin_clusters
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+def findRelatedSkinCluster(object):
+ skinClusters = cmds.ls(type='skinCluster')
+
+ for cluster in skinClusters:
+ geometry = cmds.skinCluster(cluster, q=True, g=True)[0]
+ geoTransform = cmds.listRelatives(geometry, parent=True)[0]
+
+ dagPath = cmds.ls(geoTransform, long=True)[0]
+
+ if geoTransform == object:
+ return cluster
+ elif dagPath == object:
+ return cluster
+
+
+def import_skin_weights(file_path=None, geometry=None, remove_unused=None):
+ # error handling
+ if not file_path:
+ return OpenMaya.MGlobal_displayError("No file path given.")
+ if not geometry:
+ return
+
+ # load data
+ if not os.path.exists(file_path):
+ path_message = "Could not find {0} file.".format(file_path)
+ return OpenMaya.MGlobal_displayWarning(path_message)
+
+ fobj = open(file_path, "rb")
+ data = json.load(fobj)
+
+ # check verts
+ vert_check = _vert_check(data, geometry)
+ if not vert_check:
+ return
+
+ # import skin weights
+ _import_skin_weights(data, geometry, file_path, remove_unused)
+
+
+def _import_skin_weights(data, geometry, file_path, remove_unused=None):
+ # loop through skin data
+ for skin_data in data:
+ geometry = skin_data["shape"]
+ if not cmds.objExists(geometry):
+ continue
+
+ skin_clusters = find_skin_clusters(geometry)
+ if skin_clusters:
+ skin_cluster = SkinData(skin_clusters[0])
+ skin_cluster.set_data(skin_data)
+ else:
+ # TODO: make joint remapper, Chris has a setup for this already
+ skin_cluster = _create_new_skin_cluster(skin_data, geometry)
+ if not skin_cluster:
+ continue
+ skin_cluster[0].set_data(skin_data)
+ if remove_unused:
+ if skin_clusters:
+ _remove_unused_influences(skin_clusters[0])
+ else:
+ _remove_unused_influences(skin_cluster[1])
+ OpenMaya.MGlobal_displayInfo("Imported {0}".format(file_path))
+
+
+def _vert_check(data, geometry):
+ # check vertex count
+ for skin_data in data:
+ geometry = skin_data["shape"]
+ vert_count = cmds.polyEvaluate(geometry, vertex=True)
+ import_vert_count = len(skin_data["blendWeights"])
+ if vert_count != import_vert_count:
+ vert_message = "The vert count does not match for this geometry: " + str(geometry)
+ return OpenMaya.MGlobal_displayError(vert_message)
+ return True
+
+
+def _create_new_skin_cluster(skin_data, geometry):
+ # check joints
+ joints = skin_data["weights"].keys()
+ unused_joints = list()
+ scene_joints = set([utils.remove_namespace(joint) for joint \
+ in cmds.ls(type="joint")])
+ for joint in joints:
+ if not joint in scene_joints:
+ unused_joints.append(joint)
+ # TODO: make joint remapper, Chris has a setup for this already
+ if unused_joints and not scene_joints:
+ return
+
+ skin_cluster = cmds.skinCluster(joints, geometry, tsb=True, nw=2,
+ n=skin_data["skinCluster"])[0]
+ return (SkinData(skin_cluster), skin_cluster)
+
+
+def _remove_unused_influences(skin_cluster):
+ influences_to_remove = list()
+ weighted_influences = cmds.skinCluster(skin_cluster, q=True, wi=True)
+ final_transforms = cmds.skinCluster(skin_cluster, q=True, inf=True)
+ for influence in final_transforms:
+ if influence not in weighted_influences:
+ influences_to_remove.append(influence)
+ for influence in influences_to_remove:
+ cmds.skinCluster(skin_cluster, e=True, ri=influence)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# CLASSES
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class SkinWeights(object):
+ '''
+ Takes in a .WEIGHTS file from disk, an existing skinCluster, or a skinned mesh as inputs
+ If qtProgbar == 'internal' then it will generate a modal progbar
+ '''
+
+ def __init__(self, skinFile=None, skinCluster=None, mesh=None):
+ self.skin = None
+ self.vertices = None
+ self.skinFile = None
+ self.mesh = None
+ self.uvSet = None
+ self.numVerts = None
+ self.skinDict = None
+ self.joints = []
+ self.applied = False
+
+ # TODO: check that the file is really on disk
+ if skinFile:
+ if os.path.exists(skinFile):
+ self.skinDict = self.importSkinFile(skinFile)
+ self.skinFile = skinFile
+ else:
+ cmds.warning('riggingUtils.SkinWeights: cannot find file on disk: ' + skinFile)
+ if skinCluster:
+ if cmds.objExists(skinCluster):
+ self.skin = skinCluster
+ self.mesh = cmds.listConnections(skinCluster + '.outputGeometry')[0]
+ else:
+ cmds.warning('riggingUtils.SkinWeights: cannot find skinCluster: ' + skinCluster)
+ if mesh:
+ if cmds.objExists(mesh):
+ self.mesh = mesh
+ sk = findRelatedSkinCluster(mesh)
+ if sk:
+ self.skin = sk
+ else:
+ cmds.warning('riggingUtils.SkinWeights: mesh has no skinCluster: ' + mesh)
+ else:
+ cmds.warning('riggingUtils.SkinWeights: Cannot find mesh: ' + mesh)
+
+ def exportSkinWeights(self, filePath, qtProgbar=None, freq=1):
+ time1 = time.time()
+ vertPoints = cmds.getAttr(self.mesh + '.vtx[*]')
+
+ f = {}
+ header = {}
+ vtxDict = {}
+ skin = findRelatedSkinCluster(self.mesh)
+
+ numVerts = cmds.polyEvaluate(self.mesh, vertex=1)
+
+ # fill header
+ header['mesh'] = self.mesh
+ header['skinCluster'] = skin
+ header['numVerts'] = numVerts
+ header['uvSet'] = cmds.polyUVSet(self.mesh, currentUVSet=1, q=1)[0]
+
+ # fill vtxDict
+ if qtProgbar == 'internal':
+ from System.interfaceUtils import progressDialog
+ qtProgbar = progressDialog((0, numVerts), label="Exporting skin weights...")
+ elif qtProgbar:
+ qtProgbar.setRange(0, numVerts)
+ qtProgbar.setValue(0)
+ for vtx in range(0, numVerts):
+ vtxDict[vtx] = {}
+ vtxDict[vtx]['world'] = tuple(cmds.pointPosition(self.mesh + '.vtx[' + str(vtx) + ']', w=1))
+ vtxDict[vtx]['local'] = tuple(cmds.pointPosition(self.mesh + '.vtx[' + str(vtx) + ']', l=1))
+ # we will loop all the skinning influences of this vertex and record their names and values
+ vtxDict[vtx]['skinning'] = []
+
+ joints = cmds.skinPercent(skin, self.mesh + '.vtx[' + str(vtx) + ']', q=1, t=None)
+ influence_value = cmds.skinPercent(skin, self.mesh + '.vtx[' + str(vtx) + ']', q=True, v=True)
+
+ for jnt, val in zip(joints, influence_value):
+ if val > 0:
+ vtxDict[vtx]['skinning'].append([jnt, val])
+
+ uv = cmds.polyListComponentConversion(self.mesh + ".vtx[" + str(vtx) + "]", fv=True, tuv=True)
+ vtxDict[vtx]['uv'] = cmds.polyEditUV(uv, q=True)
+ if qtProgbar:
+ qtProgbar.setValue(vtx)
+
+ f['header'] = header
+ f['vtxDict'] = vtxDict
+ wFile = file(filePath, mode='w')
+ wFile.write(json.dumps(f, wFile, indent=4))
+ wFile.close()
+
+ # TODO: validate this
+ time2 = time.time()
+ print 'exportSkinWeights: Weights saved to: %s in %0.3f sec' % (filePath, (time2 - time1))
+ return True
+
+ def importSkinFile(self, filePath):
+ import json
+ if os.path.exists(filePath):
+ f = open(filePath, 'r')
+ self.skinDict = json.load(f)
+ f.close()
+
+ self.skin = self.skinDict['header']['skinCluster']
+ self.mesh = self.skinDict['header']['mesh']
+ self.uvSet = self.skinDict['header']['uvSet']
+ self.numVerts = self.skinDict['header']['numVerts']
+ self.vertices = self.skinDict['vtxDict']
+
+ for vtx in self.skinDict['vtxDict']:
+ # build joint list
+ for inf in self.skinDict['vtxDict'][vtx]['skinning']:
+ if inf[0] not in self.joints:
+ self.joints.append(inf[0])
+
+ # This validates that the influence joints exist
+ def verifiedInfluences(self, joints):
+ jointsInScene = []
+ for jnt in joints:
+ if cmds.objExists(jnt):
+ jointsInScene.append(jnt)
+ else:
+ cmds.warning('SKIN WEIGHT IMPORT: Cannot find joint that matches', jnt, 'in the current scene.')
+ return jointsInScene
+
+ def square_distance(self, pointA, pointB):
+ # squared euclidean distance
+ distance = 0
+ dimensions = len(pointA) # assumes both points have the same dimensions
+ for dimension in range(dimensions):
+ distance += (pointA[dimension] - pointB[dimension]) ** 2
+ return distance
+
+ # Toss the UV list to a point array
+ def uvListToPointArray(self, uvPoints):
+ returnArray = []
+ if len(uvPoints) > 2:
+ for i in range(0, len(uvPoints), 2):
+ newPos = [uvPoints[i], uvPoints[i + 1]]
+ if len(newPos) != 2:
+ cmds.error(newPos + ' not 2d!')
+ returnArray.append(newPos)
+ else:
+ returnArray.append(uvPoints)
+ return returnArray
+
+ def applySkinWeights(self, mesh, applyBy='Vertex Order', killSkin=0, debug=0, qtProgbar=None):
+ self.skin = findRelatedSkinCluster(mesh)
+
+ # remove existing skin cluster
+ if self.skin and killSkin or not self.skin:
+ if killSkin:
+ bindposes = cmds.listConnections(self.skin + '.bindPose')
+ bindposes.append(self.skin)
+ for obj in bindposes:
+ cmds.delete(obj)
+ bind = self.verifiedInfluences(self.joints)
+ bind.append(mesh)
+ sel = cmds.ls(sl=1)
+ cmds.select(cl=1)
+ cmds.select(bind)
+ self.skin = cmds.skinCluster(skinMethod=0, name=(mesh.split('|')[-1] + "_skinCluster"))[0]
+ cmds.select(sel)
+
+ if self.skin:
+ # build kdTree if required
+ vtxDict = {}
+ vtxList = []
+ vtxTree = None
+
+ # parse weight file
+ if applyBy == 'World Position':
+ for vtx in self.vertices:
+ pos = self.vertices[vtx]['world']
+ vtxDict[str(pos)] = vtx
+ vtxList.append(pos)
+ start = time.time()
+ vtxTree = mathUtils.KDTree.construct_from_data(vtxList)
+ elapsed = (time.time() - start)
+ if applyBy == 'Local Position':
+ for vtx in self.vertices:
+ pos = self.vertices[vtx]['local']
+ vtxDict[str(pos)] = vtx
+ vtxList.append(pos)
+ start = time.time()
+ vtxTree = mathUtils.KDTree.construct_from_data(vtxList)
+ elapsed = (time.time() - start)
+ if applyBy == 'UV Position':
+ for vtx in self.vertices:
+ pos = self.vertices[vtx]['uv']
+ # When one vtx has multiple UV locations, we add it to the mapping table once for each location
+ if len(pos) > 2:
+ for i in range(0, len(pos), 2):
+ newPos = [pos[i], pos[i + 1]]
+ if debug:
+ print 'NEWPOS:', newPos
+ if len(newPos) > 2:
+ cmds.error(newPos + ' not 2d!')
+ vtxDict[str(newPos)] = vtx
+ vtxList.append(newPos)
+
+ else:
+ vtxDict[str(pos)] = vtx
+ vtxList.append(pos)
+ if debug:
+ print 'VTXLIST:', vtxList
+ print 'VTXDICT:', vtxDict
+ start = time.time()
+ vtxTree = mathUtils.KDTree.construct_from_data(vtxList)
+ elapsed = (time.time() - start)
+
+ verts = cmds.polyEvaluate(mesh, vertex=1)
+ # progress bar
+ if qtProgbar == 'internal':
+ from System.interfaceUtils import progressDialog
+ qtProgbar = progressDialog((0, verts), label="Importing skin weights...")
+ elif qtProgbar:
+ qtProgbar.setRange(0, verts)
+ qtProgbar.setValue(0)
+
+ # set the weights
+ try:
+ cmds.undoInfo(openChunk=True)
+ time1 = time.time()
+ for vtx in range(0, verts):
+ if applyBy == 'Vertex Order':
+ # TODO: check if numVerts match
+ cmds.skinPercent(self.skin, mesh + ".vtx[" + str(vtx) + "]",
+ transformValue=self.vertices[str(vtx)]['skinning'])
+ if applyBy == 'World Position':
+ if vtxTree:
+ vtxPos = \
+ vtxTree.query(tuple(cmds.pointPosition(mesh + '.vtx[' + str(vtx) + ']', w=1)), t=1)[0]
+ vtxMaya = mesh + '.vtx[' + str(vtx) + ']'
+ if debug:
+ print 'Original vertex', vtxDict[str(vtxPos)], 'maps to new vtx', vtxMaya
+ cmds.skinPercent(self.skin, vtxMaya,
+ transformValue=self.vertices[vtxDict[str(vtxPos)]]['skinning'])
+ else:
+ cmds.warning('IMPORT SKIN WEIGHTS: No existing kdTree built.')
+ if applyBy == 'Local Position':
+ if vtxTree:
+ vtxPos = vtxTree.query(tuple(cmds.pointPosition(mesh + '.vtx[' + str(vtx) + ']')), t=1)[0]
+ vtxMaya = mesh + '.vtx[' + str(vtx) + ']'
+ if debug:
+ print 'Original vertex', vtxDict[str(vtxPos)], 'maps to new vtx', vtxMaya
+ cmds.skinPercent(self.skin, vtxMaya,
+ transformValue=self.vertices[vtxDict[str(vtxPos)]]['skinning'])
+ else:
+ cmds.warning('IMPORT SKIN WEIGHTS: No existing kdTree built.')
+ if applyBy == 'UV Space':
+ if vtxTree:
+ uvPoint = cmds.polyEditUV(
+ cmds.polyListComponentConversion(mesh + ".vtx[" + str(vtx) + "]", fv=True,
+ tuv=True),
+ q=1)
+ pos = self.uvListToPointArray(uvPoint)
+ vtxPos = None
+
+ if debug:
+ print 'uvPoint:', uvPoint
+ print 'pos:', pos
+ # check for multiple uv points assoc with the vert
+ if len(pos) > 1:
+ distance = None
+ vtxPos = None
+ for p in pos:
+ if debug:
+ print pos
+ print p
+ closest = vtxTree.query(tuple(p), t=1)[0]
+ dist = self.square_distance(p, closest)
+ if not distance:
+ distance = dist
+ vtxPos = closest
+ else:
+ if dist < distance:
+ distance = dist
+ vtxPos = closest
+
+ else:
+ vtxPos = vtxTree.query(tuple(pos[0]), t=1)[0]
+ vtxMaya = mesh + '.vtx[' + str(vtx) + ']'
+ if debug:
+ print 'vtxPos:', str(vtxPos)
+ print 'Original UV vertex', vtxDict[str(vtxPos)], 'maps to new UV vtx', vtxMaya
+ print 'SKINNING:', self.vertices[vtxDict[str(vtxPos)]]['skinning'], vtxDict[str(vtxPos)]
+ cmds.skinPercent(self.skin, vtxMaya,
+ transformValue=self.vertices[vtxDict[str(vtxPos)]]['skinning'])
+ else:
+ cmds.warning('IMPORT SKIN WEIGHTS: No existing kdTree built.')
+
+ # Update the progressbar, but only every 100th vertex
+ if qtProgbar:
+ if vtx % 100 == 0 or (vtx + 1) == verts:
+ qtProgbar.setValue(vtx + 1)
+ time2 = time.time()
+ print 'importSkinWeights: Weights loaded from %s in %0.3f sec' % (self.skinFile, (time2 - time1))
+ except Exception as e:
+ print e
+ finally:
+ cmds.undoInfo(closeChunk=True)
+
+ else:
+ cmds.warning('IMPORT SKIN WEIGHTS: No skinCluster found on: ' + mesh)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+class SkinData(object):
+ def __init__(self, skin_cluster):
+
+ # globals/data
+ self.skin_cluster = skin_cluster
+ deformer = cmds.deformer(skin_cluster, q=True, g=True)[0]
+ self.shape = cmds.listRelatives(deformer, parent=True, path=True)[0]
+ self.mobject = utils.get_mobject(self.skin_cluster)
+ self.skin_set = OpenMayaAnim.MFnSkinCluster(self.mobject)
+ self.data = {
+ "weights": dict(),
+ "blendWeights": list(),
+ "skinCluster": self.skin_cluster,
+ "shape": self.shape
+ }
+
+ def gather_data(self):
+
+ # get incluence and blend weight data
+ dag_path, mobject = self.get_skin_dag_path_and_mobject()
+ self.get_influence_weights(dag_path, mobject)
+ self.get_blend_weights(dag_path, mobject)
+
+ # add in attribute data
+ for attribute in ATTRIBUTES:
+ self.data[attribute] = cmds.getAttr("{0}.{1}". \
+ format(self.skin_cluster,
+ attribute))
+ return self.data
+
+ def get_skin_dag_path_and_mobject(self):
+ function_set = OpenMaya.MFnSet(self.skin_set.deformerSet())
+ selection_list = OpenMaya.MSelectionList()
+ function_set.getMembers(selection_list, False)
+ dag_path = OpenMaya.MDagPath()
+ mobject = OpenMaya.MObject()
+ selection_list.getDagPath(0, dag_path, mobject)
+ return dag_path, mobject
+
+ def get_influence_weights(self, dag_path, mobject):
+ weights = self._get_weights(dag_path, mobject)
+
+ influence_paths = OpenMaya.MDagPathArray()
+ influence_count = self.skin_set.influenceObjects(influence_paths)
+ components_per_influence = weights.length() / influence_count
+ for count in xrange(influence_paths.length()):
+ name = influence_paths[count].partialPathName()
+ name = utils.remove_namespace(name)
+ weight_data = [weights[influence * influence_count + count] \
+ for influence in xrange(components_per_influence)]
+ self.data["weights"][name] = weight_data
+
+ def _get_weights(self, dag_path, mobject):
+ """Where the API magic happens."""
+ weights = OpenMaya.MDoubleArray()
+ util = OpenMaya.MScriptUtil()
+ util.createFromInt(0)
+ pointer = util.asUintPtr()
+
+ # magic call
+ self.skin_set.getWeights(dag_path, mobject, weights, pointer);
+ return weights
+
+ def get_blend_weights(self, dag_path, mobject):
+ return self._get_blend_weights(dag_path, mobject)
+
+ def _get_blend_weights(self, dag_path, mobject):
+ weights = OpenMaya.MDoubleArray()
+
+ # magic call
+ self.skin_set.getBlendWeights(dag_path, mobject, weights)
+ blend_data = [weights[blend_weight] for \
+ blend_weight in xrange(weights.length())]
+ self.data["blendWeights"] = blend_data
+
+ def set_data(self, data):
+ """Final point for importing weights. Sets and applies influences
+ and blend weight values.
+ @PARAMS:
+ data: dict()
+ """
+ self.data = data
+ dag_path, mobject = self.get_skin_dag_path_and_mobject()
+ self.set_influence_weights(dag_path, mobject)
+ self.set_blend_weights(dag_path, mobject)
+
+ # set skinCluster Attributes
+ for attribute in ATTRIBUTES:
+ cmds.setAttr('{0}.{1}'.format(self.skin_cluster, attribute),
+ self.data[attribute])
+
+ def set_influence_weights(self, dag_path, mobject):
+ weights = self._get_weights(dag_path, mobject)
+ influence_paths = OpenMaya.MDagPathArray()
+ influence_count = self.skin_set.influenceObjects(influence_paths)
+ components_per_influence = weights.length() / influence_count
+
+ # influences
+ unused_influences = list()
+ influences = [influence_paths[inf_count].partialPathName() for \
+ inf_count in xrange(influence_paths.length())]
+
+ # build influences/weights
+ for imported_influence, imported_weights in self.data['weights'].items():
+ for inf_count in xrange(influence_paths.length()):
+ influence_name = influence_paths[inf_count].partialPathName()
+ influence_name = utils.remove_namespace(influence_name)
+ if influence_name == imported_influence:
+ # set the weights
+ for count in xrange(components_per_influence):
+ weights.set(imported_weights[count],
+ count * influence_count + inf_count)
+ influences.remove(influence_name)
+ break
+ else:
+ unused_influences.append(imported_influence)
+
+ # TODO: make joint remapper
+ if unused_influences and influences:
+ OpenMaya.MGlobal_displayWarning("Make a joint remapper, Aaron!")
+
+ # set influences
+ influence_array = OpenMaya.MIntArray(influence_count)
+ for count in xrange(influence_count):
+ influence_array.set(count, count)
+ # set weights
+ self.skin_set.setWeights(dag_path, mobject, influence_array, weights, False)
+
+ def set_blend_weights(self, dag_path, mobject):
+ blend_weights = OpenMaya.MDoubleArray(len(self.data['blendWeights']))
+ for influence, weight in enumerate(self.data['blendWeights']):
+ blend_weights.set(weight, influence)
+ self.skin_set.setBlendWeights(dag_path, mobject, blend_weights)