Files
Nexus/plug-ins/ARTv2/Core/Scripts/System/riggingUtils.py
2025-12-07 23:00:40 +08:00

1452 lines
64 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
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.
"""
from __future__ import print_function
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(f'exportSkinWeights: Weights saved to: {filePath} in {(time2 - time1):.3f} sec')
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(f'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(f'VTXLIST: {vtxList}')
print(f'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(f'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(f'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(f'uvPoint: {uvPoint}')
print(f'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(f'vtxPos: {str(vtxPos)}')
print(f'Original UV vertex {vtxDict[str(vtxPos)]} maps to new UV vtx {vtxMaya}')
print(f'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(f'importSkinWeights: Weights loaded from {self.skinFile} in {(time2 - time1):.3f} sec')
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 range(influence_paths.length()):
name = influence_paths[count].partialPathName()
name = utils.remove_namespace(name)
weight_data = [weights[influence * influence_count + count] \
for influence in range(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 range(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 range(influence_paths.length())]
# build influences/weights
for imported_influence, imported_weights in self.data['weights'].items():
for inf_count in range(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 range(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 range(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)