diff options
| author | MobileMachine\jeremy <[email protected]> | 2017-06-06 22:59:03 -0400 |
|---|---|---|
| committer | MobileMachine\jeremy <[email protected]> | 2017-06-06 22:59:03 -0400 |
| commit | 24725fa8681f906ab44d80687c09fecc171a2896 (patch) | |
| tree | 312a601df29aca7f8db9f44082d96ebc7a679138 /Core/Scripts/System/riggingUtils.py | |
| parent | Initial commit (diff) | |
| download | artv2-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.py | 1448 |
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) |