MetaBox/Scripts/Animation/IK_FK_Switcher.py

1617 lines
85 KiB
Python
Raw Permalink Normal View History

2025-01-14 03:07:03 +08:00
"""
You can use this script for any commercial or non-commercial projects. You're not allowed to sell this script.
Author - Petar3D
Initial Release Date - 10.05.2023
Version - 1.0 (10.05.2023)
Version - 2.0 (02.11.2023) - Current
Description - Tool that allows you to build a temporary IK/FK setup on any rig, while preserving animation. Example - You select the controls for you arm FK chain (Shoulder, Elbow, Wrist),
and then click the "FK to IK" button to apply an IK setup on top of your original FK controls. You can also isolate the code from the UI so you can put it into a marking menu or on the shelf.
"""
import maya.cmds as cmds
import maya.mel as mel
import maya.OpenMaya as om
#from math import isclose
from sys import exit
##################################################################################################################################################################################################################
# GENERAL FUNCTIONS
def get_timeline_start_end(): # ---------- DONE
timeline_start = cmds.playbackOptions(min=True, q=True)
timeline_end = cmds.playbackOptions(max=True, q=True)
return timeline_start, timeline_end
def filter_list_unpack_brackets(*list): # IF LIST HAS 1 ITEM, WE RETURN IT AS ITEM[0] TO REMOVE THE BRACKETS
new_list = [item if len(item) > 1 else item[0] for item in list]
return new_list
def create_control(name):
points = [(-1, 0, -0), (1, 0, 0), (0, 0, 0), (0, 0, -1), (0, 0, 1), (0, 0, 0), (0, -1, 0), (0, 1, 0)]
temp_control = cmds.curve(n=name, d=1, p=points)
return temp_control
def apply_euler_filter(controls):
apply_key_reducer = cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", q=True, v1=True)
key_reducer_intensity = cmds.floatFieldGrp("Intensity_FloatField", q=True, v1=True)
for control in controls:
cmds.select(control)
cmds.filterCurve(control + ".translate", control + ".rotate", f="euler")
if apply_key_reducer:
cmds.filterCurve(control + ".translate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity)
cmds.filterCurve(control + ".rotate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity + 10)
def hide_attributes(type, *controls): # ---------- DONE
for item in controls:
for attr in ["." + type + "X", "." + type + "Y", "." + type + "Z"]:
cmds.setAttr(item + attr, k=False, l=True, cb=False)
def hide_controls(*controls): # ---------- DONE
for control in controls:
cmds.setAttr(control + ".v", 0)
def set_control_color(objects, color):
for obj in objects:
cmds.setAttr(obj + ".overrideEnabled", 1)
cmds.setAttr(obj + ".overrideColor", color)
def set_control_thickness(objects, value):
for obj in objects:
cmds.setAttr(obj + ".lineWidth", value)
def set_form_layout_coordinates(form_layout, name, top_coordinates, left_coordinates): # ---------- DONE
#ADJUSTS THE POSITION OF THE UI FEATURES
cmds.formLayout(form_layout, edit=True, attachForm=[(name, "top", top_coordinates), (name, "left", left_coordinates)])
def documentation():
cmds.showHelp("https://petarpehchevski3d.gumroad.com/l/ikfkswitcher", a=True)
def assist_message(message, time, to_exit=True): # ---------- DONE
#POPS UP A MESSAGE ON THE USER'S SCREEN TO INFORM THEM OF SOMETHING
cmds.inViewMessage(amg="<hl>" + message + "<hl>", pos='midCenter', fade=True, fst=time, ck=True)
if to_exit:
exit()
def get_constraint_attribute(constraint_type):
#SETS A KEY ON THE START AND END OF THE TIMELINE, SO THAT WE ENSURE THERE'S A BLEND NODE ALL THE TIME. IF THERE'S NO KEY BEFORE ADDING THE SETUP, THE SCRIPT WON'T APPLY A SWITCH ON THE BLEND NODE
temp_attribute = []
if constraint_type == "orient":
temp_attribute = "rotate"
elif constraint_type == "point":
temp_attribute = "translate"
elif constraint_type == "parent":
temp_attribute = ["translate", "rotate"]
return temp_attribute
def get_locked_attributes(control, attribute): # ---------- OPTIMIZE
#CHECK WHICH ATTRIBUTES ON THE CONTROL ARE LOCKED, SO AS TO KNOW WHICH ONES TO SKIP WHEN APPLYING CONSTRAINTS
if attribute == "parent":
translate_attributes = [".translateX", ".translateY", ".translateZ"]
rotate_attributes = [".rotateX", ".rotateY", ".rotateZ"]
locked_translate = [attr.lower()[-1:] for attr in translate_attributes if cmds.getAttr(control + attr, lock=True)]
locked_rotate = [attr.lower()[-1:] for attr in rotate_attributes if cmds.getAttr(control + attr, lock=True)]
locked_attributes = [locked_translate, locked_rotate]
else:
attributes = ["." + attribute + "X", "." + attribute + "Y", "." + attribute + "Z"]
locked_attributes = [attr.lower()[-1:] for attr in attributes if cmds.getAttr(control + attr, lock=True)]
return locked_attributes
def constraint(parent, child, type, mo=True): # ---------- DONE
#CONSTRAINT SYSTEM
try:
locked_attributes = get_locked_attributes(child, type)
if type == "parent":
constraint = cmds.parentConstraint(parent, child, maintainOffset = mo, skipTranslate = locked_attributes[0], skipRotate = locked_attributes[1])
if type == "translate":
constraint = cmds.pointConstraint(parent, child, maintainOffset = mo, skip = locked_attributes)
if type == "rotate":
constraint = cmds.orientConstraint(parent, child, maintainOffset = mo, skip = locked_attributes)
except RuntimeError:
assist_message("Error: Your selected controls are already being influenced by some other source.", 5000)
return constraint[0]
def check_negative_time_range(timelineStart, timelineEnd):
# PREVENTS THE USER FROM APPLYING THE SETUP IN A NEGATIVE RANGE TIMELINE
if timelineStart < 0 or timelineEnd < 0:
assist_message("Error: You can't apply a locator setup on a negative time-range", 5000, True)
def get_timeline_range():
aTimeSlider = mel.eval('$tmpVar=$gPlayBackSlider')
timeline_start, timeline_end = cmds.timeControl(aTimeSlider, q=True, rangeArray=True)
if 1 < (timeline_end - timeline_start):
to_isolate_timeline = True
timeline_end = timeline_end - 1
else:
to_isolate_timeline = False
timeline_start, timeline_end = get_timeline_start_end()
return timeline_start, timeline_end, to_isolate_timeline
####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ######
def divide_influence(curve, first_value, second_value):
cmds.setKeyframe(curve, t=(timeline_start, timeline_end), value=first_value)
cmds.setKeyframe(curve, t=(timeline_start - 1, timeline_end + 1), value=second_value)
def get_constraint_blend_index(constraint, control):
# WE'RE TRYING TO FIND THE INDEX AT THE END OF THE CONSTRAINT'S WEIGHT ATTRIBUTE. BECAUSE THERE COULD BE MANY CONSTRAINTS APPLIED ON THE SAME OBJECT, WE CAN'T ALWAYS KNOW WHAT THAT NUMBER WILL BE
for item in cmds.listConnections(constraint, c=True):
if "{0}.{1}W".format(constraint, control) in item:
constraint_index = item[-1:]
last_three_characters = constraint[-3:]
blend_index = "".join([i for i in last_three_characters if i.isdigit()]) # SOMETIMES THE BLEND INDEX MAY BE 10, OR WHATEVER NUMBER, SO WE TAKE THE LAST 3 STRING AND SEE IF THEY'RE AN INT, IF THEY ARE WE CONCATENATE THEM
return constraint_index, blend_index
def check_if_referenced(original_control, temp_control):
# IF THE RIG IS REFERENCED, WE STORE THE NAME OF THE TEMP LOCATOR WITHOUT THE NAMESPACE, BECAUSE THE CONSTRAINT WE'LL INFLUENCE DON'T HAVE THE NAMESPACE INSIDE
if cmds.referenceQuery(original_control, isNodeReferenced=True) or ":" in original_control:
temp_control = temp_control.split(":")[-1:][0]
return temp_control
def set_key_frame(constraint_type, control):
temp_attribute = get_constraint_attribute(constraint_type.lower())
# CHECKS TO SEE IF ORIGINAL CONTROL HAS ANY KEYS ON CURVES ALREADY, IF NOT IT PLACES THEM TO ACTIVATE THE BLEND INDEX
if cmds.keyframe(control, at=temp_attribute, q=True) == None:
cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute)
else:
cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute, i=True)
def isolate_constraint(constraints, temp_controls, original_controls, constraint_type):
for constraint, temp_control, original_control in zip(constraints, temp_controls, original_controls):
temp_control = check_if_referenced(original_control, temp_control)
set_key_frame(constraint_type, original_control)
constraint_index, blend_index = get_constraint_blend_index(constraint, temp_control)
divide_influence(constraint + "." + temp_control + "W" + constraint_index, 1, 0)
divide_influence(original_control + ".blend" + constraint_type + blend_index, 1, 0)
def isolate_visibility(controls, first_value, second_value):
for control in controls:
temp_shape_nodes = cmds.listRelatives(control, shapes=True, children=True, pa=True)
if temp_shape_nodes != None:
for temp_shape_node in temp_shape_nodes:
divide_influence(temp_shape_node + ".v", first_value, second_value)
def delete_visibility_keys(*original_controls):
if influence_visibility:
for original_control in original_controls:
temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True)
if temp_shape_nodes != None:
for temp_shape_node in temp_shape_nodes:
cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys")
####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ######
####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ######
######### CLEAR REDUNDANT KEYS ##########
def remove_unnecessary_keys(controls, attributes):
tolerance = 0.000002
for control in controls:
for attr in attributes:
for frame in range(int(timeline_start) + 1, int(timeline_end)):
current_value = cmds.keyframe(control, q=True, t=(frame, frame), at=attr, eval=True)[0]
previous_value = cmds.keyframe(control, q=True, t=(frame - 1, frame - 1), at=attr, eval=True)[0]
#next_value = cmds.keyframe(control, q=True, t=(frame + 1, frame + 1), at=attr, eval=True)[0]
if abs(current_value - previous_value) <= max(1e-09 * max(abs(current_value), abs(previous_value)), tolerance):
cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys")
# if isclose(current_value, previous_value, abs_tol=tolerance):
# cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys")
################# APPLY TO BOTH ##############
def check_if_setup_exists(controls): # DONE
for control in controls:
if cmds.objExists(control + "_temp_IK_Name*") or cmds.objExists(control + "_temp_FK_Name*"):
assist_message("Error: One or more selected controls are part of a pre-existing setup somewhere else on the timeline.", 5000)
def create_name_reference_groups(contents, suffix): # ---------- DONE
# #CREATES EMPTY GROUPS AS PROXIES FOR REFERENCING THE ACTUAL CONTROLS LATER ON
timeline_mode_keyword = "ISR" if specific_timeline_mode == True else "NIF"
# BECAUSE SOME NAMES HAVE DASH IN THE MIDDLE IF THEY'RE NOT UNIQUE NAMES, WE HAVE TO REPLACE IT WITH A NEW KEYWORD "_SEPARATOR_" FOR WHEN REFERENCING IT BACK WHEN BAKING.
new_contents = [obj.replace("|", "_dash_") if "|" in obj else obj for obj in contents]
reference_grps = [cmds.group(n=obj + suffix + "_" + timeline_mode_keyword + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end)), em=True) for obj in new_contents]
return reference_grps
def create_temp_grp(suffix, control_name, controls_to_group): # DONE
temp_group = cmds.group(*controls_to_group, n=control_name + suffix)
#cmds.lockNode(temp_group)
def match_rotation_order(parent, child): # ---------- DONE
# MATCHES THE ROTATION ORDER FROM THE ORIGINAL SELECTIONS ONTO THE TEMPORARY SETUP CONTROLS
original_rotation_order = cmds.getAttr(parent + ".rotateOrder")
cmds.setAttr(child + ".rotateOrder", original_rotation_order)
def match_control_scale(parent, children):
# SCALE UP AN OBJECT TO ANOTHER ONE'S BOUNDING BOX SCALE, INCASE IT'S BEEN FREEZE-TRANSFORMED. THIS WAY THE USER DOESN'T HAVE TO MANUALLY ADJUST THE SIZE
children = cmds.ls(children, flatten=True)
if cmds.objectType(parent) == "joint":
parentShapeNode = parent
else:
parentShapeNode = cmds.listRelatives(parent, shapes=True, children=True, pa=True)[0]
xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(parentShapeNode)
parentDistanceX, parentDistanceY, parentDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin]
for child in children:
xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(child)
childDistanceX, childDistanceY, childDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin]
# WE QUERY THE ORIGINAL SCALE OF THE LOCATOR
originalX, originalY, originalZ = cmds.xform(child, q=True, s=True, r=True)
divisionX, divisionY, divisionZ = [parentDistanceX / childDistanceX, parentDistanceY / childDistanceY, parentDistanceZ / childDistanceZ]
# WE GET THE FINAL SCALE HERE, WE TAKE THE LONGEST NUMBER AND APPLY THAT TO ALL SCALE AXIS
largestAxis = max([originalX * divisionX, originalY * divisionY, originalZ * divisionZ]) * 3
newScale = [largestAxis, largestAxis, largestAxis]
if cmds.objectType(parent) == "joint":
if cmds.currentUnit(q=True) == "cm":
cmds.xform(child, scale=(40, 40, 40))
else:
cmds.xform(child, scale=(0.7, 0.7, 0.7))
else:
cmds.xform(child, scale=newScale)
def check_anim_layer(controls):
root_layer = cmds.animLayer(q=True, r=True)
if root_layer:
layer_list = cmds.animLayer(root_layer, q=True, c=True)
if layer_list != None:
for layer in layer_list:
attribute_list = cmds.animLayer(layer, q=True, attribute=True)
if attribute_list != None:
for attr in attribute_list:
for obj in controls:
if obj in attr:
assist_message("Warning: A control you've selected is in an anim layer. This may mess up the baking process and give bad results", 6000, False)
################# APPLY TO BOTH ##############
############# CREATE IK CONTROLS #################
def get_original_fk_controls(): # ---------- DONE
fk_ctrls = cmds.ls(sl=True)
if len(fk_ctrls) != 3:
assist_message("Incorrect number of controls selected. To apply an IK setup, you need to select 3 FK controls, in order of parent to child.", 4000)
cmds.select(cl=True)
return fk_ctrls
def create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl): # ---------- DONE
parent_jnt = cmds.joint(n=parent_ctrl + "_temp_jnt")
middle_jnt = cmds.joint(n=middle_ctrl + "_temp_jnt")
child_jnt = cmds.joint(n=child_ctrl + "_temp_jnt")
return parent_jnt, middle_jnt, child_jnt
def create_ik_controls(middle_ctrl, child_ctrl): # ---------- DONE
ik_ctrl = create_control(child_ctrl + "_ik_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end)))
pole_vector_ctrl = create_control(middle_ctrl + "_pole_vector_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end)))
match_control_scale(child_ctrl, [ik_ctrl, pole_vector_ctrl])
return ik_ctrl, pole_vector_ctrl
def create_ik_handle(parent_jnt, child_jnt, ik_ctrl): # ---------- DONE
# SETS PREFERRED ANGLE ON THE TEMP JOINT CHAIN, APPLIES AN IK HANDLE ON IT
cmds.joint(parent_jnt, e=True, spa=True, ch=True)
temp_IK_Handle = cmds.ikHandle(n=ik_ctrl + "_ikHandle1", sj=parent_jnt, ee=child_jnt)[0]
cmds.matchTransform(temp_IK_Handle, child_jnt)
return temp_IK_Handle
def create_temp_ik_controls(): # DONE
# STORES ORIGINAL SELECTED FK CONTROLS INTO VARIABLES
parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls = get_original_fk_controls()
check_if_setup_exists([parent_ctrl, middle_ctrl, child_ctrl])
check_anim_layer(fk_ctrls)
delete_visibility_keys(parent_ctrl, middle_ctrl, child_ctrl)
# CREATES TEMPORARY IK JOINTS AND CONTROLS
parent_jnt, middle_jnt, child_jnt = ik_jnts = create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl)
ik_ctrl, pole_vector_ctrl = ik_ctrls = create_ik_controls(middle_ctrl, child_ctrl)
# CREATES EMPTY GROUPS FOR REFERENCING THE ORIGINAL CONTROLS WHEN BAKING/DELETING SETUPS
ik_reference_grps = create_name_reference_groups([parent_ctrl, middle_ctrl, child_ctrl], "_temp_IK_Name")
# STORES WHOLE SETUP IN GROUP
create_temp_grp("_temp_IK_Group", parent_ctrl, [parent_jnt, ik_ctrl, pole_vector_ctrl, ik_reference_grps])
return fk_ctrls, ik_jnts, ik_ctrls
############# CREATE IK CONTROLS #################
############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ##################
# SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CONTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS
def create_reverse_constraint_setup_ik(parent, child):
bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True)
match_rotation_order(parent, child)
# POSITIONS THE NEW CONTROLS
if "pole_vector_ctrl" not in child: ################## DON'T LIKE HOW I HAVE TO ADD THESE EXTRA CONDITIONALS IN HERE #####################
cmds.matchTransform(child, parent, position=True, rotation=True)
if "jnt" in child:
cmds.makeIdentity(child, apply=True, t=True, r=True, s=True)
# CONSTRAINTS ORIGINAL CONTROLS TO TEMPORARY, BAKES THE TEMP TO INHERIT ANIM DATA, DELETES CONSTRAINT, CONSTRAINTS TEMPORARY TO ORIGINAL
temp_constraint = constraint(parent, child, "parent")
cmds.bakeResults(child, t=(timeline_start, timeline_end), sb=bake_interval)
cmds.delete(temp_constraint)
if "pole_vector_ctrl" not in child:
temp_constraint = constraint(child, parent, "rotate")
return temp_constraint
def get_vector(position): # ---------- DONE
return om.MVector(position[0], position[1], position[2])
def get_pole_vector_position(parent_jnt, middle_jnt, child_jnt): # ---------- DONE
vector_parent = get_vector(cmds.xform(parent_jnt, q=True, t=True, ws=True))
vector_middle = get_vector(cmds.xform(middle_jnt, q=True, t=True, ws=True))
vector_child = get_vector(cmds.xform(child_jnt, q=True, t=True, ws=True))
parent_to_child_vector = (vector_child - vector_parent)
parent_to_middle_vector = (vector_middle - vector_parent)
scale_value = (parent_to_child_vector * parent_to_middle_vector) / (parent_to_child_vector * parent_to_child_vector)
parent_to_child_middle_point = parent_to_child_vector * scale_value + vector_parent
parent_to_middle_length = (vector_middle - vector_parent).length()
middle_to_child_length = (vector_child - vector_middle).length()
total_length = parent_to_middle_length + middle_to_child_length
pole_vector_position = (vector_middle - parent_to_child_middle_point).normal() * total_length + vector_middle
return pole_vector_position
def set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl):
for frame in range(int(timeline_start), int(timeline_end) + 1):
cmds.currentTime(int(frame))
pole_vector_position = get_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl)
cmds.move(pole_vector_position.x, pole_vector_position.y, pole_vector_position.z, pole_vector_ctrl)
cmds.setKeyframe(pole_vector_ctrl, time=(frame, frame), at="translate")
def set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls):
parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls
ik_ctrl, pole_vector_ctrl = ik_ctrls
parent_jnt, middle_jnt, child_jnt = ik_jnts
# JOINTS
temp_constraint = [create_reverse_constraint_setup_ik(parent, child) for parent, child in zip(fk_ctrls, ik_jnts)]
# IK CTRL
ik_orient_constraint = create_reverse_constraint_setup_ik(child_jnt, ik_ctrl)
# POLE-VECTOR
set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl)
#create_reverse_constraint_setup_ik(middle_jnt, pole_vector_ctrl)
return temp_constraint, ik_orient_constraint
############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ##################
#CREATES A TEMPORARY IK SET-UP BY SELECTING EXISTING FK CONTROLS
def fk_to_ik():
global timeline_start, timeline_end, specific_timeline_mode, influence_visibility
influence_visibility = cmds.checkBoxGrp("influence_visibility", q=True, v1=True)
clear_keys = cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)
cmds.autoKeyframe(state=True)
timeline_start, timeline_end, specific_timeline_mode = get_timeline_range()
check_negative_time_range(timeline_start, timeline_end)
###### CREATE CONTROLS ######
[parent_ctrl, middle_ctrl, child_ctrl], [parent_jnt, middle_jnt, child_jnt], [ik_ctrl, pole_vector_ctrl] = fk_ctrls, ik_jnts, ik_ctrls = create_temp_ik_controls()
###### REPOSITION CONTROLS ######
temp_constraints, ik_orient_constraint = set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls)
# CREATES IK HANDLE, ADDS POLE VECTOR CONSTRAINTz
cmds.currentTime(timeline_start)
temp_ik_handle = create_ik_handle(parent_jnt, child_jnt, ik_ctrl)
cmds.parent(temp_ik_handle, ik_ctrl, s=True)
pole_vector_constraint = cmds.poleVectorConstraint(pole_vector_ctrl, temp_ik_handle)
# MAKES THE IK SETUP WORLD-SPACE - WHEN SHOULDER ROTATES THE IK TRANSLATES BUT DOESN'T ROTATE
point_constraint = constraint(parent_ctrl, parent_jnt, "translate")
####### ISOLATE VISIBILITY ######
if specific_timeline_mode:
if influence_visibility:
isolate_visibility([parent_ctrl, middle_ctrl, child_ctrl], 0, 1)
isolate_visibility([ik_ctrl, pole_vector_ctrl], 1, 0)
####### ISOLATE CONSTRAINT ######
isolate_constraint(temp_constraints, ik_jnts, fk_ctrls, "Orient")
else:
if influence_visibility:
for original_control in fk_ctrls:
temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True)
if temp_shape_nodes != None:
for temp_shape_node in temp_shape_nodes:
if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True:
cmds.setAttr(temp_shape_node + ".v", 0)
#isolate_constraint([ik_orient_constraint], [ik_ctrl], [child_jnt], "Orient")
#isolate_constraint([point_constraint], [parent_ctrl], [parent_jnt], "Point")
############################# - DONE - #############################
#CLEAN-UP # - COULD MAKE INTO ITS OWN FUNCTION
hide_controls(parent_jnt, temp_ik_handle)
cmds.cutKey(pole_vector_ctrl, t=(timeline_start, timeline_end), at="rotate", option="keys")
hide_attributes("rotate", pole_vector_ctrl)
hide_attributes("scale", ik_ctrl, pole_vector_ctrl)
set_control_color(ik_ctrls, 18)
set_control_thickness(ik_ctrls, 2)
#set_control_thickness([ik_ctrl, pole_vector_ctrl], 2)
#cmds.keyTangent(ik_ctrls, e=True, itt="auto", ott="auto")
apply_euler_filter(ik_ctrls)
if clear_keys:
remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"])
remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"])
cmds.select(ik_ctrl)
################ GET ORIGINAL CONTROLS ####################
def get_original_ik_controls():
ik_ctrls = cmds.ls(sl=True)
if len(ik_ctrls) != 2:
assist_message("Incorrect number of controls selected. To apply an FK setup, you need to select the Pole Vector first and then the IK Control, in order.", 4000)
cmds.select(cl=True)
return ik_ctrls
def get_ik_handle(pole_vector): # ----- NOT DONE, ADD MORE CONDITIONALS
#FROM THE POLE VECTOR, WE DERIVE THE SELECTION OF THE PARENT AND MIDDLE JOINT THAT THE IK HANDLE INFLUENCES, AND STORE THEM IN VARIABLES
cmds.select(pole_vector, hi=True)
pole_vector_hierarchy = cmds.ls(sl=True)
for obj in pole_vector_hierarchy:
poleVectorConstraint = cmds.listConnections(obj, type = "poleVectorConstraint")
ik_handle = cmds.listConnections(poleVectorConstraint, type = "ikHandle")
if ik_handle != None:
break
if ik_handle == None:
keywords = "_".join(pole_vector.split("_")[:3])
ik_handle = cmds.ls(keywords + "*", type="ikHandle")
if ik_handle == None:
keywords = "_".join(pole_vector.split("_")[:2])
ik_handle = cmds.ls(keywords + "*", type="ikHandle")
if ik_handle == None or len(ik_handle) == 0:
assist_message("Couldn't obtain IK handle from the rig. Selection order must be Pole Vector first, then IK control. If that doesn't work, this feature is incompatible with this rig.", 4000)
return ik_handle[0]
def get_ik_handle_joints(ik_handle):
parent_jnt, middle_jnt = cmds.ikHandle(ik_handle, q=True, jl=True)
return parent_jnt, middle_jnt
def get_original_controls():
# SEPARATES THE SELECTED CONTROLS INTO THEIR OWN VARIABLES
pole_vector, ik_ctrl = get_original_ik_controls()
check_if_setup_exists([pole_vector, ik_ctrl])
ik_handle = get_ik_handle(pole_vector)
parent_jnt, middle_jnt = get_ik_handle_joints(ik_handle)
return pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt
################ GET ORIGINAL CONTROLS ####################
################ CREATE TEMP CONTROLS ####################
def create_fk_controls(*controls):
fk_ctrls = [create_control(control + "_temp_FK_CTRL") for control in controls]
fk_ctrls_grps = [cmds.group(fk_ctrl, n=fk_ctrl + "_GRP") for fk_ctrl in fk_ctrls]
return filter_list_unpack_brackets(fk_ctrls_grps, fk_ctrls)
def create_parent_hierarchy_offsets_controls(offsets, controls):
# SHIFTS THE OFFSETS ELEMENTS SO THAT THE SECOND OFFSET MATCHES THE FIRST CONTROL AND WE PROPERLY PARENT THEM
for offset, control in zip((offsets[1:] + offsets[:1])[:-1], controls[:-1]):
cmds.parent(offset, control)
def create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt):
# CREATE 3 TEMP LOCATORS, ADD A GROUP ON TOP OF THEM AND PARENT THEM TO EACH OTHER
fk_ctrls_grps, fk_ctrls = create_fk_controls(parent_jnt, middle_jnt, ik_ctrl, pole_vector)
temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps
temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls
match_control_scale(ik_ctrl, [temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL])
create_parent_hierarchy_offsets_controls(fk_ctrls_grps, fk_ctrls)
cmds.parent(temp_poleVector_CTRL_GRP, temp_parent_FK_CTRL) # THE PREVIOUS FUNCTION DOESN'T PLACE THE TEMP POLE VECTOR UNDER THE RIGHT PARENT, SO HERE WE ADJUST THAT
# CREATES NAME REFERENCE GROUPS FOR ALL ORIGINAL CONTROLS THAT NEED TO BE BAKED WHEN DELETING THE SETUP, PLACES ALL THE CONTENTS IN A NEW TEMP GROUP
reference_grps = create_name_reference_groups([ik_ctrl, pole_vector, ik_handle], "_temp_FK_Name")
create_temp_grp("_temp_FK_Group", parent_jnt, [temp_parent_FK_CTRL_GRP, reference_grps])
return fk_ctrls_grps, fk_ctrls
################ CREATE TEMP CONTROLS ####################
################# CREATES THE REVERSE CONSTRAINT SETUP ##################
#SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CO NTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS
def create_reverse_constraint_setup_fk(parent, group, child):
bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True)
cmds.matchTransform(group, parent, position=True, rotation=True)
temp_constraint = constraint(parent, child, "parent", True)
cmds.bakeResults(child, t=(timeline_start, timeline_end), simulation=False, sb=bake_interval)
cmds.delete(temp_constraint)
def reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt):
temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls
# REVERSE CONSTRAINT FROM THE TEMP CONTROLS TO THE ORIGINALS
parent_constraint = constraint(temp_parent_FK_CTRL, parent_jnt, "rotate")
middle_constraint = constraint(temp_middle_FK_CTRL, middle_jnt, "rotate")
child_constraint = constraint(temp_child_FK_CTRL, ik_ctrl, "parent")
point_constraint = constraint(parent_jnt, temp_parent_FK_CTRL, "translate") # WHEN YOU ROTATE CLAVICLE, NEW PARENT CONTROL TRANSLATES
parent_constraint_two = constraint(temp_middle_FK_CTRL, temp_poleVector_CTRL, "parent") # MAKES THE NEW POLE VECTOR FOLLOW THE PARENT CONTROL
point_constraint_two = constraint(temp_poleVector_CTRL, pole_vector, "translate") # MAKES THE OG POLE VECTOR FOLLOW THE NEW ONE
return parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two
################# CREATES THE REVERSE CONSTRAINT SETUP ##################
#CREATES A TEMPORARY FK SET-UP BY SELECTING EXISTING IK CONTROLS
def ik_to_fk():
global timeline_start, timeline_end, specific_timeline_mode, influence_visibility
influence_visibility = cmds.checkBoxGrp("influence_visibility", q=True, v1=True)
clear_keys = cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)
cmds.autoKeyframe(state=True)
timeline_start, timeline_end, specific_timeline_mode = get_timeline_range()
check_negative_time_range(timeline_start, timeline_end)
pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt = get_original_controls()
check_anim_layer([pole_vector, ik_ctrl])
delete_visibility_keys(pole_vector, ik_ctrl)
fk_ctrls_grps, fk_ctrls = create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt)
temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps
temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls
match_rotation_order(ik_ctrl, temp_parent_FK_CTRL)
match_rotation_order(ik_ctrl, temp_middle_FK_CTRL)
match_rotation_order(ik_ctrl, temp_child_FK_CTRL)
############################# - DONE - #############################
create_reverse_constraint_setup_fk(parent_jnt, temp_parent_FK_CTRL_GRP, temp_parent_FK_CTRL)
create_reverse_constraint_setup_fk(middle_jnt, temp_middle_FK_CTRL_GRP, temp_middle_FK_CTRL)
create_reverse_constraint_setup_fk(ik_ctrl, temp_child_FK_CTRL_GRP, temp_child_FK_CTRL)
create_reverse_constraint_setup_fk(pole_vector, temp_poleVector_CTRL_GRP, temp_poleVector_CTRL) # THIS LOCATOR LACHES ONTO THE OG POLE VECTOR - SO WE KNOW HOW TO BAKE THE OG POLE VECTOR IN THE END
parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two = reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt)
# ISOLATE VISIBILITY #
if specific_timeline_mode:
if influence_visibility:
isolate_visibility([ik_ctrl, pole_vector], 0, 1)
isolate_visibility([temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL], 1, 0)
# ISOLATE CONSTRAINT
isolate_constraint([parent_constraint, middle_constraint], [temp_parent_FK_CTRL, temp_middle_FK_CTRL], [parent_jnt, middle_jnt], "Orient")
isolate_constraint([child_constraint], [temp_child_FK_CTRL], [ik_ctrl], "Parent")
isolate_constraint([point_constraint], [parent_jnt], [temp_parent_FK_CTRL], "Point")
isolate_constraint([parent_constraint_two], [temp_middle_FK_CTRL], [temp_poleVector_CTRL], "Parent")
isolate_constraint([point_constraint_two], [temp_poleVector_CTRL], [pole_vector], "Point")
divide_influence(ik_handle + ".ikBlend", 0, 1)
else:
cmds.setAttr(ik_handle + ".ikBlend", 0)
if influence_visibility:
for original_control in [ik_ctrl, pole_vector]:
temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True)
if temp_shape_nodes != None:
for temp_shape_node in temp_shape_nodes:
if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True:
cmds.setAttr(temp_shape_node + ".v", 0)
#CLEAN-UP
hide_controls(temp_poleVector_CTRL)
cmds.cutKey(fk_ctrls, t=(timeline_start, timeline_end), at="translate", option="keys")
hide_attributes("translate", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL)
hide_attributes("scale", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL)
set_control_color(fk_ctrls, 17)
set_control_thickness(fk_ctrls, 2)
#cmds.keyTangent(fk_ctrls, e=True, itt="auto", ott="auto")
apply_euler_filter(fk_ctrls)
if clear_keys:
remove_unnecessary_keys(fk_ctrls, ["rotateX", "rotateY", "rotateZ"])
cmds.select(ik_handle)
##############################################################################################################################################################################
###########################################################################################################################################################################################################
##############################################################################################################################################################################
def get_temporary_setup_selection():
temp_Selection = cmds.ls(sl=True)
if len(temp_Selection) == 0:
assist_message("To delete a temporary setup, you have to select one of its controls.", 2500)
return temp_Selection
def check_if_correct_selection(temp_Selection):
if "FK_CTRL" in temp_Selection or "ik_ctrl" in temp_Selection or "pole_vector_ctrl" in temp_Selection:
pass
else:
assist_message("Incorrect selection: " + temp_Selection + " is not a part of any IK FK setup. To delete a setup, select one of its controls and then click the button.", 4500)
def get_group_selection(selection):
cmds.select(selection)
for i in range(6):
cmds.pickWalk(d="up")
temp_Group = cmds.ls(sl=True)[0]
return temp_Group
def delete_visibility_keys_after_bake(original_control):
temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True)
if temp_shape_nodes != None:
for temp_shape_node in temp_shape_nodes:
cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys")
cmds.setKeyframe(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", v=1)
def clean_up(i, attributes, group_Contents):
global timeline_start, timeline_end
bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True)
cmds.autoKeyframe(state=True)
if group_Contents[i].split("_")[-3:-2][0] == "ISR":
timeline_start, timeline_end = group_Contents[i].split("_")[-2:]
else:
timeline_start, timeline_end = get_timeline_start_end()
original_control = "_".join(group_Contents[i].split("_")[:-6])
if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME
original_control = original_control.replace("_dash_", "|")
cmds.bakeResults(original_control, t=(timeline_start, timeline_end), at=attributes, pok=True, sb=bake_interval)
delete_visibility_keys_after_bake(original_control)
apply_euler_filter([original_control])
# #SETS VISIBILITY BACK
# temp_shape_node = cmds.listRelatives(original_control, shapes=True, children=True)[0]
# cmds.setAttr(temp_shape_node + ".v", 1)
#YOU STORE THE TANGENT TYPE BEFORE THE BAKE, SO THAT INCASE IT WAS IN STEPPED, WE MAKE THE TANGENT STEPPED AGAIN CUZ OTHERWISE IT MESSES UP THE ELBOW AFTERWARDS
return original_control
def delete_ik_blend(i, group_Contents):
original_control = "_".join(group_Contents[i].split("_")[:-6])
if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME
original_control = original_control.replace("_dash_", "|")
cmds.cutKey(original_control, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="ikBlend", option="keys")
cmds.setAttr(original_control + ".ikBlend", 1)
return original_control
def delete_setup():
clear_keys = cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)
#BAKES THE PREVIOUS CONTROLS, CLEANS UP THE CURVES, DELETES CURRENT CONTROLS AND BRINGS BACK ORIGINALS
temp_selection = get_temporary_setup_selection()
for selection in temp_selection:
if cmds.objExists(selection):
check_if_correct_selection(selection)
temp_group = get_group_selection(selection)
group_contents = cmds.listRelatives(temp_group)
if "temp_IK_Group" in temp_group:
original_controls = [clean_up(i, ["rotate"], group_contents) for i in range(3,6)]
if clear_keys:
remove_unnecessary_keys(original_controls, ["rotateX", "rotateY", "rotateZ"])
# remove_unnecessary_keys([original_controls[0], original_controls[2]], ["rotate"])
# remove_unnecessary_keys([original_controls[1]], ["rotateX"])
elif "temp_FK_Group" in temp_group:
ik_ctrl, pole_vector_ctrl = [clean_up(i, ["translate", "rotate"], group_contents) for i in range(1,3)]
if clear_keys:
remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"])
remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"])
ik_handle = delete_ik_blend(3, group_contents)
#cmds.lockNode(temp_group, l=False)
if cmds.objExists(temp_group):
cmds.delete(temp_group)
##############################################################################################################################################################################
###########################################################################################################################################################################################################
##############################################################################################################################################################################
#UI LOGIC
def run():
if cmds.window("IK_FK_Switcher", ex=True):
cmds.deleteUI("IK_FK_Switcher")
cmds.window("IK_FK_Switcher", title="IK/FK Switcher, by Petar3D", wh=[360, 242], s=False)
cmds.formLayout("ikfk_form_layout", numberOfDivisions=100, w=360, h=242)
cmds.button("fkToIK_Button", l="FK to IK", recomputeSize = True, bgc=[0.6220035095750363, 0.8836957351033798, 1.0], h = 43, w = 100, parent ="ikfk_form_layout", command=lambda *args:fk_to_ik(),
ann="Applies a temporary IK setup on top of your existing FK chain.\nHow to use: Select 3 FK controls, starting from the parent to the child, then click this button.")
set_form_layout_coordinates("ikfk_form_layout", "fkToIK_Button", 16, 16)
cmds.button("ikToFK_Button", l="IK to FK ", recomputeSize = True, bgc=[1.0, 1.0, 0.6220035095750363], h = 43, w = 100, parent ="ikfk_form_layout", command=lambda *args:ik_to_fk(),
ann="Applies a temporary FK setup on top of your existing IK chain.\nHow to use: Select the pole vector and then the IK control, then click this button.")
set_form_layout_coordinates("ikfk_form_layout", "ikToFK_Button", 16, 131)
cmds.button("DeleteSetup_Button", l="Delete Setup", recomputeSize = True, bgc=[1.0, 0.6220035095750363, 0.6220035095750363], h = 43, w = 99, parent ="ikfk_form_layout", command=lambda *args:delete_setup(),
ann="Deletes the temporary IK/FK setups and brings back the original.\nHow to use: Select a control from the current setup, then click this button.")
set_form_layout_coordinates("ikfk_form_layout", "DeleteSetup_Button", 16, 246)
cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", l="Reduce Keys: ", ncb=1, l1="", cw=(1, 79.5), w=151, vr=False, parent="ikfk_form_layout",
ann="When the animation bakes across, this feature reduces the amount of keyframes on your curves.")
set_form_layout_coordinates("ikfk_form_layout", "ApplyKeyReducer_CheckBox", 105, 5)
cmds.checkBoxGrp("remove_unnecessary_keys", l="Remove Keys With Same Values: ", ncb=1, l1="", cw = (1, 170), w = 190, vr=False, v1=False, parent ="ikfk_form_layout",
ann="When ticked on, after the bake, static channels get removed.\nStatic channels are curves like scaleX/Y/Z that get baked but have no changes to them across the timeparent_to_child_vector, so they're redundant and take up space.")
set_form_layout_coordinates("ikfk_form_layout", "remove_unnecessary_keys", 133, 11)
# cmds.floatSliderGrp("remove_keys_threshold", l="Threshold: ", f=True, v=1, cw=(1, 60), w=240, min=1, max=500000,
# parent="ikfk_form_layout",
# ann="The higher the amount, the less keyframes you'll have when applying the key reducer,\nbut you lose out on how precisely the animation gets baked across.")
# set_form_layout_coordinates("remove_keys_threshold", 210, 30)
cmds.checkBoxGrp("influence_visibility", l="Hide Original Controls: ", ncb=1, l1="", cw=(1, 128), w=166,
vr=False, parent="ikfk_form_layout", v1=True,
ann="When ticked on, after the bake, static channels get removed.\nStatic channels are curves like scaleX/Y/Z that get baked but have no changes to them across the timeparent_to_child_vector, so they're redundant and take up space.")
set_form_layout_coordinates("ikfk_form_layout", "influence_visibility", 160, 9)
cmds.floatFieldGrp("Intensity_FloatField", l="Intensity: ", numberOfFields=1, v1=1.0, cw = (1, 52), w = 137, parent ="ikfk_form_layout",
ann="The higher the amount, the less keyframes you'll have when applying the key reducer,\nbut you lose out on how precisely the animation gets baked across.")
set_form_layout_coordinates("ikfk_form_layout", "Intensity_FloatField", 102, 110)
cmds.intFieldGrp("BakeInterval_IntField", l="Bake Interval: ", numberOfFields=1, v1=1, cw=(1, 85.0), w=198, parent="ikfk_form_layout")
set_form_layout_coordinates("ikfk_form_layout", "BakeInterval_IntField", 75, 1)
cmds.button("ExtraOptions_Button", l="Extra Options", recomputeSize=True,
bgc=[0.6220035095750363, 1.0, 0.6656137941557946], h=34, w=90, parent="ikfk_form_layout",
command=lambda *args:extraOptions())
set_form_layout_coordinates("ikfk_form_layout", "ExtraOptions_Button", 194, 255)
cmds.button("ik_fk_documentation", l="Documentation", recomputeSize=True,
bgc=[0.8, 0.8, 0.8], h=34, w=100, parent="ikfk_form_layout",
command=lambda *args:documentation())
set_form_layout_coordinates("ikfk_form_layout", "ik_fk_documentation", 194, 16.5)
# cmds.separator("Proxy_HRSeparator", hr=True,bgc=[0.6569924467841611, 0.6569924467841611, 0.6569924467841611], style="none", h = 3, w = 394)
# set_form_layout_coordinates("ikfk_form_layout", "Proxy_HRSeparator", 63, -8)
cmds.showWindow("IK_FK_Switcher")
def generateCode():
# YOU SELECT ONE OF THREE OPTIONS FOR WHAT CODE YOU WANT TO ISOLATE FROM THE SCRIPT: FK TO IK, IK TO FK, DELETE SETUP.
cmds.scrollField("GenerateCodeOutputWindow", e=True, cl=True)
code = """
import maya.cmds as cmds
import maya.mel as mel
import maya.OpenMaya as om
from sys import exit
bake_interval = """ + str(cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True)) + """
apply_key_reducer = """ + str(cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", q=True, v1=True)) + """
key_reducer_intensity = """ + str(cmds.floatFieldGrp("Intensity_FloatField", q=True, v1=True)) + """
clear_keys = """ + str(cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)) + """
influence_visibility = """ + str(cmds.checkBoxGrp("influence_visibility", q=True, v1=True)) + """
##################################################################################################################################################################################################################
# GENERAL FUNCTIONS
def get_timeline_start_end(): # ---------- DONE
timeline_start = cmds.playbackOptions(min=True, q=True)
timeline_end = cmds.playbackOptions(max=True, q=True)
return timeline_start, timeline_end
def filter_list_unpack_brackets(*list): # IF LIST HAS 1 ITEM, WE RETURN IT AS ITEM[0] TO REMOVE THE BRACKETS
new_list = [item if len(item) > 1 else item[0] for item in list]
return new_list
def create_control(name):
points = [(-1, 0, -0), (1, 0, 0), (0, 0, 0), (0, 0, -1), (0, 0, 1), (0, 0, 0), (0, -1, 0), (0, 1, 0)]
temp_control = cmds.curve(n=name, d=1, p=points)
return temp_control
def apply_euler_filter(controls):
apply_key_reducer = cmds.checkBoxGrp("ApplyKeyReducer_CheckBox", q=True, v1=True)
key_reducer_intensity = cmds.floatFieldGrp("Intensity_FloatField", q=True, v1=True)
for control in controls:
cmds.select(control)
cmds.filterCurve(control + ".translate", control + ".rotate", f="euler")
if apply_key_reducer:
cmds.filterCurve(control + ".translate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity)
cmds.filterCurve(control + ".rotate", f="keyReducer", startTime=timeline_start, endTime=timeline_end, pm=1, pre=key_reducer_intensity + 10)
def hide_attributes(type, *controls): # ---------- DONE
for item in controls:
for attr in ["." + type + "X", "." + type + "Y", "." + type + "Z"]:
cmds.setAttr(item + attr, k=False, l=True, cb=False)
def hide_controls(*controls): # ---------- DONE
for control in controls:
cmds.setAttr(control + ".v", 0)
def set_control_color(objects, color):
for obj in objects:
cmds.setAttr(obj + ".overrideEnabled", 1)
cmds.setAttr(obj + ".overrideColor", color)
def set_control_thickness(objects, value):
for obj in objects:
cmds.setAttr(obj + ".lineWidth", value)
def set_form_layout_coordinates(form_layout, name, top_coordinates, left_coordinates): # ---------- DONE
#ADJUSTS THE POSITION OF THE UI FEATURES
cmds.formLayout(form_layout, edit=True, attachForm=[(name, "top", top_coordinates), (name, "left", left_coordinates)])
def assist_message(message, time, to_exit=True): # ---------- DONE
#POPS UP A MESSAGE ON THE USER'S SCREEN TO INFORM THEM OF SOMETHING
cmds.inViewMessage(amg="<hl>" + message + "<hl>", pos='midCenter', fade=True, fst=time, ck=True)
if to_exit:
exit()
def get_constraint_attribute(constraint_type):
#SETS A KEY ON THE START AND END OF THE TIMELINE, SO THAT WE ENSURE THERE'S A BLEND NODE ALL THE TIME. IF THERE'S NO KEY BEFORE ADDING THE SETUP, THE SCRIPT WON'T APPLY A SWITCH ON THE BLEND NODE
temp_attribute = []
if constraint_type == "orient":
temp_attribute = "rotate"
elif constraint_type == "point":
temp_attribute = "translate"
elif constraint_type == "parent":
temp_attribute = ["translate", "rotate"]
return temp_attribute
def get_locked_attributes(control, attribute): # ---------- OPTIMIZE
#CHECK WHICH ATTRIBUTES ON THE CONTROL ARE LOCKED, SO AS TO KNOW WHICH ONES TO SKIP WHEN APPLYING CONSTRAINTS
if attribute == "parent":
translate_attributes = [".translateX", ".translateY", ".translateZ"]
rotate_attributes = [".rotateX", ".rotateY", ".rotateZ"]
locked_translate = [attr.lower()[-1:] for attr in translate_attributes if cmds.getAttr(control + attr, lock=True)]
locked_rotate = [attr.lower()[-1:] for attr in rotate_attributes if cmds.getAttr(control + attr, lock=True)]
locked_attributes = [locked_translate, locked_rotate]
else:
attributes = ["." + attribute + "X", "." + attribute + "Y", "." + attribute + "Z"]
locked_attributes = [attr.lower()[-1:] for attr in attributes if cmds.getAttr(control + attr, lock=True)]
return locked_attributes
def constraint(parent, child, type, mo=True): # ---------- DONE
#CONSTRAINT SYSTEM
try:
locked_attributes = get_locked_attributes(child, type)
if type == "parent":
constraint = cmds.parentConstraint(parent, child, maintainOffset = mo, skipTranslate = locked_attributes[0], skipRotate = locked_attributes[1])
if type == "translate":
constraint = cmds.pointConstraint(parent, child, maintainOffset = mo, skip = locked_attributes)
if type == "rotate":
constraint = cmds.orientConstraint(parent, child, maintainOffset = mo, skip = locked_attributes)
except RuntimeError:
assist_message("Error: Your selected controls are already being influenced by some other source.", 5000)
return constraint[0]
def check_negative_time_range(timelineStart, timelineEnd):
# PREVENTS THE USER FROM APPLYING THE SETUP IN A NEGATIVE RANGE TIMELINE
if timelineStart < 0 or timelineEnd < 0:
assist_message("Error: You can't apply a locator setup on a negative time-range", 5000, True)
def get_timeline_range():
aTimeSlider = mel.eval('$tmpVar=$gPlayBackSlider')
timeline_start, timeline_end = cmds.timeControl(aTimeSlider, q=True, rangeArray=True)
if 1 < (timeline_end - timeline_start):
to_isolate_timeline = True
timeline_end = timeline_end - 1
else:
to_isolate_timeline = False
timeline_start, timeline_end = get_timeline_start_end()
return timeline_start, timeline_end, to_isolate_timeline
####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ######
def divide_influence(curve, first_value, second_value):
cmds.setKeyframe(curve, t=(timeline_start, timeline_end), value=first_value)
cmds.setKeyframe(curve, t=(timeline_start - 1, timeline_end + 1), value=second_value)
def get_constraint_blend_index(constraint, control):
# WE'RE TRYING TO FIND THE INDEX AT THE END OF THE CONSTRAINT'S WEIGHT ATTRIBUTE. BECAUSE THERE COULD BE MANY CONSTRAINTS APPLIED ON THE SAME OBJECT, WE CAN'T ALWAYS KNOW WHAT THAT NUMBER WILL BE
for item in cmds.listConnections(constraint, c=True):
if "{0}.{1}W".format(constraint, control) in item:
constraint_index = item[-1:]
blend_index = constraint[-1:]
return constraint_index, blend_index
def check_if_referenced(original_control, temp_control):
# IF THE RIG IS REFERENCED, WE STORE THE NAME OF THE TEMP LOCATOR WITHOUT THE NAMESPACE, BECAUSE THE CONSTRAINT WE'LL INFLUENCE DON'T HAVE THE NAMESPACE INSIDE
if cmds.referenceQuery(original_control, isNodeReferenced=True) or ":" in original_control:
temp_control = temp_control.split(":")[-1:][0]
return temp_control
def set_key_frame(constraint_type, control):
temp_attribute = get_constraint_attribute(constraint_type.lower())
# CHECKS TO SEE IF ORIGINAL CONTROL HAS ANY KEYS ON CURVES ALREADY, IF NOT IT PLACES THEM TO ACTIVATE THE BLEND INDEX
if cmds.keyframe(control, at=temp_attribute, q=True) == None:
cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute)
else:
cmds.setKeyframe(control, t=(timeline_start - 1, timeline_end + 1), at=temp_attribute, i=True)
def isolate_constraint(constraints, temp_controls, original_controls, constraint_type):
for constraint, temp_control, original_control in zip(constraints, temp_controls, original_controls):
temp_control = check_if_referenced(original_control, temp_control)
set_key_frame(constraint_type, original_control)
constraint_index, blend_index = get_constraint_blend_index(constraint, temp_control)
divide_influence(constraint + "." + temp_control + "W" + constraint_index, 1, 0)
divide_influence(original_control + ".blend" + constraint_type + blend_index, 1, 0)
def isolate_visibility(controls, first_value, second_value):
for control in controls:
temp_shape_nodes = cmds.listRelatives(control, shapes=True, children=True, pa=True)
if temp_shape_nodes != None:
for temp_shape_node in temp_shape_nodes:
divide_influence(temp_shape_node + ".v", first_value, second_value)
def delete_visibility_keys(*original_controls):
if influence_visibility:
for original_control in original_controls:
temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True)
if temp_shape_nodes != None:
for temp_shape_node in temp_shape_nodes:
cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys")
####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ######
####### ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ############# ISOLATE ######
######### CLEAR REDUNDANT KEYS ##########
def remove_unnecessary_keys(controls, attributes):
tolerance = 0.000002
for control in controls:
for attr in attributes:
for frame in range(int(timeline_start) + 1, int(timeline_end)):
current_value = cmds.keyframe(control, q=True, t=(frame, frame), at=attr, eval=True)[0]
previous_value = cmds.keyframe(control, q=True, t=(frame - 1, frame - 1), at=attr, eval=True)[0]
#next_value = cmds.keyframe(control, q=True, t=(frame + 1, frame + 1), at=attr, eval=True)[0]
if abs(current_value - previous_value) <= max(1e-09 * max(abs(current_value), abs(previous_value)), tolerance):
cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys")
# if isclose(current_value, previous_value, abs_tol=tolerance):
# cmds.cutKey(control, time=(int(frame), int(frame)), at=attr, option="keys")
################# APPLY TO BOTH ##############
def check_if_setup_exists(controls): # DONE
for control in controls:
if cmds.objExists(control + "_temp_IK_Name*") or cmds.objExists(control + "_temp_FK_Name*"):
assist_message("Error: One or more selected controls are part of a pre-existing setup somewhere else on the timeline.", 5000)
def create_name_reference_groups(contents, suffix): # ---------- DONE
# #CREATES EMPTY GROUPS AS PROXIES FOR REFERENCING THE ACTUAL CONTROLS LATER ON
timeline_mode_keyword = "ISR" if specific_timeline_mode == True else "NIF"
# BECAUSE SOME NAMES HAVE DASH IN THE MIDDLE IF THEY'RE NOT UNIQUE NAMES, WE HAVE TO REPLACE IT WITH A NEW KEYWORD "_SEPARATOR_" FOR WHEN REFERENCING IT BACK WHEN BAKING.
new_contents = [obj.replace("|", "_dash_") if "|" in obj else obj for obj in contents]
reference_grps = [cmds.group(n=obj + suffix + "_" + timeline_mode_keyword + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end)), em=True) for obj in new_contents]
return reference_grps
def create_temp_grp(suffix, control_name, controls_to_group): # DONE
temp_group = cmds.group(*controls_to_group, n=control_name + suffix)
#cmds.lockNode(temp_group)
def match_rotation_order(parent, child): # ---------- DONE
# MATCHES THE ROTATION ORDER FROM THE ORIGINAL SELECTIONS ONTO THE TEMPORARY SETUP CONTROLS
original_rotation_order = cmds.getAttr(parent + ".rotateOrder")
cmds.setAttr(child + ".rotateOrder", original_rotation_order)
def match_control_scale(parent, children):
# SCALE UP AN OBJECT TO ANOTHER ONE'S BOUNDING BOX SCALE, INCASE IT'S BEEN FREEZE-TRANSFORMED. THIS WAY THE USER DOESN'T HAVE TO MANUALLY ADJUST THE SIZE
children = cmds.ls(children, flatten=True)
if cmds.objectType(parent) == "joint":
parentShapeNode = parent
else:
parentShapeNode = cmds.listRelatives(parent, shapes=True, children=True, pa=True)[0]
xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(parentShapeNode)
parentDistanceX, parentDistanceY, parentDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin]
for child in children:
xMin, yMin, zMin, xMax, yMax, zMax = cmds.exactWorldBoundingBox(child)
childDistanceX, childDistanceY, childDistanceZ = [xMax - xMin, yMax - yMin, zMax - zMin]
# WE QUERY THE ORIGINAL SCALE OF THE LOCATOR
originalX, originalY, originalZ = cmds.xform(child, q=True, s=True, r=True)
divisionX, divisionY, divisionZ = [parentDistanceX / childDistanceX, parentDistanceY / childDistanceY, parentDistanceZ / childDistanceZ]
# WE GET THE FINAL SCALE HERE, WE TAKE THE LONGEST NUMBER AND APPLY THAT TO ALL SCALE AXIS
largestAxis = max([originalX * divisionX, originalY * divisionY, originalZ * divisionZ]) * 3
newScale = [largestAxis, largestAxis, largestAxis]
if cmds.objectType(parent) == "joint":
if cmds.currentUnit(q=True) == "cm":
cmds.xform(child, scale=(40, 40, 40))
else:
cmds.xform(child, scale=(0.7, 0.7, 0.7))
else:
cmds.xform(child, scale=newScale)
def check_anim_layer(controls):
root_layer = cmds.animLayer(q=True, r=True)
if root_layer:
layer_list = cmds.animLayer(root_layer, q=True, c=True)
if layer_list != None:
for layer in layer_list:
attribute_list = cmds.animLayer(layer, q=True, attribute=True)
if attribute_list != None:
for attr in attribute_list:
for obj in controls:
if obj in attr:
assist_message("Warning: A control you've selected is in an anim layer. This may mess up the baking process and give bad results", 6000, False)
"""
if cmds.radioButtonGrp("GenerateCodeOptions_RadioB", q=True, select=True) == 1:
code += """
############# CREATE IK CONTROLS #################
def get_original_fk_controls(): # ---------- DONE
fk_ctrls = cmds.ls(sl=True)
if len(fk_ctrls) != 3:
assist_message("Incorrect number of controls selected. To apply an IK setup, you need to select 3 FK controls, in order of parent to child.", 4000)
cmds.select(cl=True)
return fk_ctrls
def create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl): # ---------- DONE
parent_jnt = cmds.joint(n=parent_ctrl + "_temp_jnt")
middle_jnt = cmds.joint(n=middle_ctrl + "_temp_jnt")
child_jnt = cmds.joint(n=child_ctrl + "_temp_jnt")
return parent_jnt, middle_jnt, child_jnt
def create_ik_controls(middle_ctrl, child_ctrl): # ---------- DONE
ik_ctrl = create_control(child_ctrl + "_ik_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end)))
pole_vector_ctrl = create_control(middle_ctrl + "_pole_vector_ctrl" + "_" + str(int(timeline_start)) + "_" + str(int(timeline_end)))
match_control_scale(child_ctrl, [ik_ctrl, pole_vector_ctrl])
return ik_ctrl, pole_vector_ctrl
def create_ik_handle(parent_jnt, child_jnt, ik_ctrl): # ---------- DONE
# SETS PREFERRED ANGLE ON THE TEMP JOINT CHAIN, APPLIES AN IK HANDLE ON IT
cmds.joint(parent_jnt, e=True, spa=True, ch=True)
temp_IK_Handle = cmds.ikHandle(n=ik_ctrl + "_ikHandle1", sj=parent_jnt, ee=child_jnt)[0]
cmds.matchTransform(temp_IK_Handle, child_jnt)
return temp_IK_Handle
def create_temp_ik_controls(): # DONE
# STORES ORIGINAL SELECTED FK CONTROLS INTO VARIABLES """
if len(cmds.ls(sl=True)) == 3:
fk_ctrls = cmds.ls(sl=True)
code += """
parent_ctrl = """ "\"" + fk_ctrls[0] + "\"" """
middle_ctrl = """ "\"" + fk_ctrls[1] + "\"" """
child_ctrl = """ "\"" + fk_ctrls[2] + "\"" """
fk_ctrls = parent_ctrl, middle_ctrl, child_ctrl
cmds.select(cl=True)
"""
elif len(cmds.ls(sl=True)) == 0:
code += """
parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls = get_original_fk_controls() """
else:
assist_message(
"Incorrect number of controls selected. For a specific IK setup, select 3 FK controls. For a generic setup, have no selections.",
5000)
code += """
check_if_setup_exists([parent_ctrl, middle_ctrl, child_ctrl])
check_anim_layer(fk_ctrls)
delete_visibility_keys(parent_ctrl, middle_ctrl, child_ctrl)
# CREATES TEMPORARY IK JOINTS AND CONTROLS
parent_jnt, middle_jnt, child_jnt = ik_jnts = create_ik_joints(parent_ctrl, middle_ctrl, child_ctrl)
ik_ctrl, pole_vector_ctrl = ik_ctrls = create_ik_controls(middle_ctrl, child_ctrl)
# CREATES EMPTY GROUPS FOR REFERENCING THE ORIGINAL CONTROLS WHEN BAKING/DELETING SETUPS
ik_reference_grps = create_name_reference_groups([parent_ctrl, middle_ctrl, child_ctrl], "_temp_IK_Name")
# STORES WHOLE SETUP IN GROUP
create_temp_grp("_temp_IK_Group", parent_ctrl, [parent_jnt, ik_ctrl, pole_vector_ctrl, ik_reference_grps])
return fk_ctrls, ik_jnts, ik_ctrls
############# CREATE IK CONTROLS #################
############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ##################
# SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CONTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS
def create_reverse_constraint_setup_ik(parent, child):
bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True)
match_rotation_order(parent, child)
# POSITIONS THE NEW CONTROLS
if "pole_vector_ctrl" not in child: ################## DON'T LIKE HOW I HAVE TO ADD THESE EXTRA CONDITIONALS IN HERE #####################
cmds.matchTransform(child, parent, position=True, rotation=True)
if "jnt" in child:
cmds.makeIdentity(child, apply=True, t=True, r=True, s=True)
# CONSTRAINTS ORIGINAL CONTROLS TO TEMPORARY, BAKES THE TEMP TO INHERIT ANIM DATA, DELETES CONSTRAINT, CONSTRAINTS TEMPORARY TO ORIGINAL
temp_constraint = constraint(parent, child, "parent")
cmds.bakeResults(child, t=(timeline_start, timeline_end), sb=bake_interval)
cmds.delete(temp_constraint)
if "pole_vector_ctrl" not in child:
temp_constraint = constraint(child, parent, "rotate")
return temp_constraint
def get_vector(position): # ---------- DONE
return om.MVector(position[0], position[1], position[2])
def get_pole_vector_position(parent_jnt, middle_jnt, child_jnt): # ---------- DONE
vector_parent = get_vector(cmds.xform(parent_jnt, q=True, t=True, ws=True))
vector_middle = get_vector(cmds.xform(middle_jnt, q=True, t=True, ws=True))
vector_child = get_vector(cmds.xform(child_jnt, q=True, t=True, ws=True))
parent_to_child_vector = (vector_child - vector_parent)
parent_to_middle_vector = (vector_middle - vector_parent)
scale_value = (parent_to_child_vector * parent_to_middle_vector) / (parent_to_child_vector * parent_to_child_vector)
parent_to_child_middle_point = parent_to_child_vector * scale_value + vector_parent
parent_to_middle_length = (vector_middle - vector_parent).length()
middle_to_child_length = (vector_child - vector_middle).length()
total_length = parent_to_middle_length + middle_to_child_length
pole_vector_position = (vector_middle - parent_to_child_middle_point).normal() * total_length + vector_middle
return pole_vector_position
def set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl):
for frame in range(int(timeline_start), int(timeline_end) + 1):
cmds.currentTime(int(frame))
pole_vector_position = get_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl)
cmds.move(pole_vector_position.x, pole_vector_position.y, pole_vector_position.z, pole_vector_ctrl)
cmds.setKeyframe(pole_vector_ctrl, time=(frame, frame), at="translate")
def set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls):
parent_ctrl, middle_ctrl, child_ctrl = fk_ctrls
ik_ctrl, pole_vector_ctrl = ik_ctrls
parent_jnt, middle_jnt, child_jnt = ik_jnts
# JOINTS
temp_constraint = [create_reverse_constraint_setup_ik(parent, child) for parent, child in zip(fk_ctrls, ik_jnts)]
# IK CTRL
ik_orient_constraint = create_reverse_constraint_setup_ik(child_jnt, ik_ctrl)
# POLE-VECTOR
set_pole_vector_position(parent_ctrl, middle_ctrl, child_ctrl, pole_vector_ctrl)
#create_reverse_constraint_setup_ik(middle_jnt, pole_vector_ctrl)
return temp_constraint, ik_orient_constraint
############## CREATES REVERSE CONSTRAINT SETUP/ POSITIONS THE CONTROLS IN PLACE ##################
#CREATES A TEMPORARY IK SET-UP BY SELECTING EXISTING FK CONTROLS
def fk_to_ik():
global timeline_start, timeline_end, specific_timeline_mode, influence_visibility
influence_visibility = cmds.checkBoxGrp("influence_visibility", q=True, v1=True)
clear_keys = cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)
cmds.autoKeyframe(state=True)
timeline_start, timeline_end, specific_timeline_mode = get_timeline_range()
check_negative_time_range(timeline_start, timeline_end)
###### CREATE CONTROLS ######
[parent_ctrl, middle_ctrl, child_ctrl], [parent_jnt, middle_jnt, child_jnt], [ik_ctrl, pole_vector_ctrl] = fk_ctrls, ik_jnts, ik_ctrls = create_temp_ik_controls()
###### REPOSITION CONTROLS ######
temp_constraints, ik_orient_constraint = set_temp_ik_controls_position(fk_ctrls, ik_jnts, ik_ctrls)
# CREATES IK HANDLE, ADDS POLE VECTOR CONSTRAINTz
cmds.currentTime(timeline_start)
temp_ik_handle = create_ik_handle(parent_jnt, child_jnt, ik_ctrl)
cmds.parent(temp_ik_handle, ik_ctrl, s=True)
pole_vector_constraint = cmds.poleVectorConstraint(pole_vector_ctrl, temp_ik_handle)
# MAKES THE IK SETUP WORLD-SPACE - WHEN SHOULDER ROTATES THE IK TRANSLATES BUT DOESN'T ROTATE
point_constraint = constraint(parent_ctrl, parent_jnt, "translate")
####### ISOLATE VISIBILITY ######
if specific_timeline_mode:
if influence_visibility:
isolate_visibility([parent_ctrl, middle_ctrl, child_ctrl], 0, 1)
isolate_visibility([ik_ctrl, pole_vector_ctrl], 1, 0)
####### ISOLATE CONSTRAINT ######
isolate_constraint(temp_constraints, ik_jnts, fk_ctrls, "Orient")
else:
if influence_visibility:
for original_control in fk_ctrls:
temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True)
if temp_shape_nodes != None:
for temp_shape_node in temp_shape_nodes:
if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True:
cmds.setAttr(temp_shape_node + ".v", 0)
#isolate_constraint([ik_orient_constraint], [ik_ctrl], [child_jnt], "Orient")
#isolate_constraint([point_constraint], [parent_ctrl], [parent_jnt], "Point")
############################# - DONE - #############################
#CLEAN-UP # - COULD MAKE INTO ITS OWN FUNCTION
hide_controls(parent_jnt, temp_ik_handle)
cmds.cutKey(pole_vector_ctrl, t=(timeline_start, timeline_end), at="rotate", option="keys")
hide_attributes("rotate", pole_vector_ctrl)
hide_attributes("scale", ik_ctrl, pole_vector_ctrl)
set_control_color(ik_ctrls, 18)
set_control_thickness(ik_ctrls, 2)
#set_control_thickness([ik_ctrl, pole_vector_ctrl], 2)
#cmds.keyTangent(ik_ctrls, e=True, itt="auto", ott="auto")
apply_euler_filter(ik_ctrls)
if clear_keys:
remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"])
remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"])
cmds.select(ik_ctrl)
fk_to_ik()
"""
elif cmds.radioButtonGrp("GenerateCodeOptions_RadioB", q=True, select=True) == 2:
code += """
################ GET ORIGINAL CONTROLS ####################
def get_original_ik_controls():
ik_ctrls = cmds.ls(sl=True)
if len(ik_ctrls) != 2:
assist_message("Incorrect number of controls selected. To apply an FK setup, you need to select the Pole Vector first and then the IK Control, in order.", 4000)
cmds.select(cl=True)
return ik_ctrls
def get_ik_handle(pole_vector): # ----- NOT DONE, ADD MORE CONDITIONALS
#FROM THE POLE VECTOR, WE DERIVE THE SELECTION OF THE PARENT AND MIDDLE JOINT THAT THE IK HANDLE INFLUENCES, AND STORE THEM IN VARIABLES
cmds.select(pole_vector, hi=True)
pole_vector_hierarchy = cmds.ls(sl=True)
for obj in pole_vector_hierarchy:
poleVectorConstraint = cmds.listConnections(obj, type = "poleVectorConstraint")
ik_handle = cmds.listConnections(poleVectorConstraint, type = "ikHandle")
if ik_handle != None:
break
if ik_handle == None:
keywords = "_".join(pole_vector.split("_")[:3])
ik_handle = cmds.ls(keywords + "*", type="ikHandle")
if ik_handle == None:
keywords = "_".join(pole_vector.split("_")[:2])
ik_handle = cmds.ls(keywords + "*", type="ikHandle")
if ik_handle == None or len(ik_handle) == 0:
assist_message("Couldn't obtain IK handle from the rig. Selection order must be Pole Vector first, then IK control. If that doesn't work, this feature is incompatible with this rig.", 4000)
return ik_handle[0]
def get_ik_handle_joints(ik_handle):
parent_jnt, middle_jnt = cmds.ikHandle(ik_handle, q=True, jl=True)
return parent_jnt, middle_jnt
def get_original_controls():
# SEPARATES THE SELECTED CONTROLS INTO THEIR OWN VARIABLES
"""
if len(cmds.ls(sl=True)) == 2:
ik_ctrls = cmds.ls(sl=True)
code += """
pole_vector = """ "\"" + ik_ctrls[0] + "\"" """
ik_ctrl = """ "\"" + ik_ctrls[1] + "\"" """
cmds.select(cl=True)
"""
elif len(cmds.ls(sl=True)) == 0:
code += """
pole_vector, ik_ctrl = get_original_ik_controls()
"""
else:
assist_message(
"Incorrect number of controls selected. For a specific FK setup, select the Pole Vector and IK Control in order. For a generic setup, have no selections.",
5000)
code += """
check_if_setup_exists([pole_vector, ik_ctrl])
ik_handle = get_ik_handle(pole_vector)
parent_jnt, middle_jnt = get_ik_handle_joints(ik_handle)
return pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt
################ GET ORIGINAL CONTROLS ####################
################ CREATE TEMP CONTROLS ####################
def create_fk_controls(*controls):
fk_ctrls = [create_control(control + "_temp_FK_CTRL") for control in controls]
fk_ctrls_grps = [cmds.group(fk_ctrl, n=fk_ctrl + "_GRP") for fk_ctrl in fk_ctrls]
return filter_list_unpack_brackets(fk_ctrls_grps, fk_ctrls)
def create_parent_hierarchy_offsets_controls(offsets, controls):
# SHIFTS THE OFFSETS ELEMENTS SO THAT THE SECOND OFFSET MATCHES THE FIRST CONTROL AND WE PROPERLY PARENT THEM
for offset, control in zip((offsets[1:] + offsets[:1])[:-1], controls[:-1]):
cmds.parent(offset, control)
def create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt):
# CREATE 3 TEMP LOCATORS, ADD A GROUP ON TOP OF THEM AND PARENT THEM TO EACH OTHER
fk_ctrls_grps, fk_ctrls = create_fk_controls(parent_jnt, middle_jnt, ik_ctrl, pole_vector)
temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps
temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls
match_control_scale(ik_ctrl, [temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL])
create_parent_hierarchy_offsets_controls(fk_ctrls_grps, fk_ctrls)
cmds.parent(temp_poleVector_CTRL_GRP, temp_parent_FK_CTRL) # THE PREVIOUS FUNCTION DOESN'T PLACE THE TEMP POLE VECTOR UNDER THE RIGHT PARENT, SO HERE WE ADJUST THAT
# CREATES NAME REFERENCE GROUPS FOR ALL ORIGINAL CONTROLS THAT NEED TO BE BAKED WHEN DELETING THE SETUP, PLACES ALL THE CONTENTS IN A NEW TEMP GROUP
reference_grps = create_name_reference_groups([ik_ctrl, pole_vector, ik_handle], "_temp_FK_Name")
create_temp_grp("_temp_FK_Group", parent_jnt, [temp_parent_FK_CTRL_GRP, reference_grps])
return fk_ctrls_grps, fk_ctrls
################ CREATE TEMP CONTROLS ####################
################# CREATES THE REVERSE CONSTRAINT SETUP ##################
#SNAPS THE TEMP CONTROLS TO THE POSITION OF THE ORIGINAL CO NTROLS, BAKES THE ANIMATION DATA, THEN DELETES CONSTRAINTS
def create_reverse_constraint_setup_fk(parent, group, child):
bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True)
cmds.matchTransform(group, parent, position=True, rotation=True)
temp_constraint = constraint(parent, child, "parent", True)
cmds.bakeResults(child, t=(timeline_start, timeline_end), simulation=False, sb=bake_interval)
cmds.delete(temp_constraint)
def reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt):
temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls
# REVERSE CONSTRAINT FROM THE TEMP CONTROLS TO THE ORIGINALS
parent_constraint = constraint(temp_parent_FK_CTRL, parent_jnt, "rotate")
middle_constraint = constraint(temp_middle_FK_CTRL, middle_jnt, "rotate")
child_constraint = constraint(temp_child_FK_CTRL, ik_ctrl, "parent")
point_constraint = constraint(parent_jnt, temp_parent_FK_CTRL, "translate") # WHEN YOU ROTATE CLAVICLE, NEW PARENT CONTROL TRANSLATES
parent_constraint_two = constraint(temp_middle_FK_CTRL, temp_poleVector_CTRL, "parent") # MAKES THE NEW POLE VECTOR FOLLOW THE PARENT CONTROL
point_constraint_two = constraint(temp_poleVector_CTRL, pole_vector, "translate") # MAKES THE OG POLE VECTOR FOLLOW THE NEW ONE
return parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two
################# CREATES THE REVERSE CONSTRAINT SETUP ##################
#CREATES A TEMPORARY FK SET-UP BY SELECTING EXISTING IK CONTROLS
def ik_to_fk():
global timeline_start, timeline_end, specific_timeline_mode, influence_visibility
influence_visibility = cmds.checkBoxGrp("influence_visibility", q=True, v1=True)
clear_keys = cmds.checkBoxGrp("remove_unnecessary_keys", q=True, v1=True)
cmds.autoKeyframe(state=True)
timeline_start, timeline_end, specific_timeline_mode = get_timeline_range()
check_negative_time_range(timeline_start, timeline_end)
pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt = get_original_controls()
check_anim_layer([pole_vector, ik_ctrl])
delete_visibility_keys(pole_vector, ik_ctrl)
fk_ctrls_grps, fk_ctrls = create_new_fk_controls(pole_vector, ik_ctrl, ik_handle, parent_jnt, middle_jnt)
temp_parent_FK_CTRL_GRP, temp_middle_FK_CTRL_GRP, temp_child_FK_CTRL_GRP, temp_poleVector_CTRL_GRP = fk_ctrls_grps
temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL, temp_poleVector_CTRL = fk_ctrls
match_rotation_order(ik_ctrl, temp_parent_FK_CTRL)
match_rotation_order(ik_ctrl, temp_middle_FK_CTRL)
match_rotation_order(ik_ctrl, temp_child_FK_CTRL)
############################# - DONE - #############################
create_reverse_constraint_setup_fk(parent_jnt, temp_parent_FK_CTRL_GRP, temp_parent_FK_CTRL)
create_reverse_constraint_setup_fk(middle_jnt, temp_middle_FK_CTRL_GRP, temp_middle_FK_CTRL)
create_reverse_constraint_setup_fk(ik_ctrl, temp_child_FK_CTRL_GRP, temp_child_FK_CTRL)
create_reverse_constraint_setup_fk(pole_vector, temp_poleVector_CTRL_GRP, temp_poleVector_CTRL) # THIS LOCATOR LACHES ONTO THE OG POLE VECTOR - SO WE KNOW HOW TO BAKE THE OG POLE VECTOR IN THE END
parent_constraint, middle_constraint, child_constraint, point_constraint, parent_constraint_two, point_constraint_two = reverse_constraints_fk(fk_ctrls, ik_ctrl, pole_vector, parent_jnt, middle_jnt)
# ISOLATE VISIBILITY #
if specific_timeline_mode:
if influence_visibility:
isolate_visibility([ik_ctrl, pole_vector], 0, 1)
isolate_visibility([temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL], 1, 0)
# ISOLATE CONSTRAINT
isolate_constraint([parent_constraint, middle_constraint], [temp_parent_FK_CTRL, temp_middle_FK_CTRL], [parent_jnt, middle_jnt], "Orient")
isolate_constraint([child_constraint], [temp_child_FK_CTRL], [ik_ctrl], "Parent")
isolate_constraint([point_constraint], [parent_jnt], [temp_parent_FK_CTRL], "Point")
isolate_constraint([parent_constraint_two], [temp_middle_FK_CTRL], [temp_poleVector_CTRL], "Parent")
isolate_constraint([point_constraint_two], [temp_poleVector_CTRL], [pole_vector], "Point")
divide_influence(ik_handle + ".ikBlend", 0, 1)
else:
cmds.setAttr(ik_handle + ".ikBlend", 0)
if influence_visibility:
for original_control in [ik_ctrl, pole_vector]:
temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True)
if temp_shape_nodes != None:
for temp_shape_node in temp_shape_nodes:
if cmds.getAttr(temp_shape_node + ".v", se=True) == True or cmds.getAttr(temp_shape_node + ".v", l=True) == True:
cmds.setAttr(temp_shape_node + ".v", 0)
#CLEAN-UP
hide_controls(temp_poleVector_CTRL)
cmds.cutKey(fk_ctrls, t=(timeline_start, timeline_end), at="translate", option="keys")
hide_attributes("translate", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL)
hide_attributes("scale", temp_parent_FK_CTRL, temp_middle_FK_CTRL, temp_child_FK_CTRL)
set_control_color(fk_ctrls, 17)
set_control_thickness(fk_ctrls, 2)
#cmds.keyTangent(fk_ctrls, e=True, itt="auto", ott="auto")
apply_euler_filter(fk_ctrls)
if clear_keys:
remove_unnecessary_keys(fk_ctrls, ["rotateX", "rotateY", "rotateZ"])
cmds.select(temp_parent_FK_CTRL)
ik_to_fk()
"""
elif cmds.radioButtonGrp("GenerateCodeOptions_RadioB", q=True, select=True) == 3:
code += """
def get_temporary_setup_selection():
temp_Selection = cmds.ls(sl=True)
if len(temp_Selection) == 0:
assist_message("To delete a temporary setup, you have to select one of its controls.", 2500)
return temp_Selection
def check_if_correct_selection(temp_Selection):
if "FK_CTRL" in temp_Selection or "ik_ctrl" in temp_Selection or "pole_vector_ctrl" in temp_Selection:
pass
else:
assist_message("Incorrect selection: " + temp_Selection + " is not a part of any IK FK setup. To delete a setup, select one of its controls and then click the button.", 4500)
def get_group_selection(selection):
cmds.select(selection)
for i in range(6):
cmds.pickWalk(d="up")
temp_Group = cmds.ls(sl=True)[0]
return temp_Group
def delete_visibility_keys_after_bake(original_control):
temp_shape_nodes = cmds.listRelatives(original_control, shapes=True, children=True, pa=True)
if temp_shape_nodes != None:
for temp_shape_node in temp_shape_nodes:
cmds.cutKey(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", option="keys")
cmds.setKeyframe(temp_shape_node, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="visibility", v=1)
def clean_up(i, attributes, group_Contents):
global timeline_start, timeline_end
bake_interval = cmds.intFieldGrp("BakeInterval_IntField", q=True, v1=True)
cmds.autoKeyframe(state=True)
if group_Contents[i].split("_")[-3:-2][0] == "ISR":
timeline_start, timeline_end = group_Contents[i].split("_")[-2:]
else:
timeline_start, timeline_end = get_timeline_start_end()
original_control = "_".join(group_Contents[i].split("_")[:-6])
if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME
original_control = original_control.replace("_dash_", "|")
cmds.bakeResults(original_control, t=(timeline_start, timeline_end), at=attributes, pok=True, sb=bake_interval)
delete_visibility_keys_after_bake(original_control)
apply_euler_filter([original_control])
# #SETS VISIBILITY BACK
# temp_shape_node = cmds.listRelatives(original_control, shapes=True, children=True)[0]
# cmds.setAttr(temp_shape_node + ".v", 1)
#YOU STORE THE TANGENT TYPE BEFORE THE BAKE, SO THAT INCASE IT WAS IN STEPPED, WE MAKE THE TANGENT STEPPED AGAIN CUZ OTHERWISE IT MESSES UP THE ELBOW AFTERWARDS
return original_control
def delete_ik_blend(i, group_Contents):
original_control = "_".join(group_Contents[i].split("_")[:-6])
if "_dash_" in original_control: # FOR IF THE CONTROL DOESN'T HAVE A UNIQUE NAME
original_control = original_control.replace("_dash_", "|")
cmds.cutKey(original_control, time=(int(timeline_start) - 1, int(timeline_end) + 1), at="ikBlend", option="keys")
cmds.setAttr(original_control + ".ikBlend", 1)
return original_control
def delete_setup():
#BAKES THE PREVIOUS CONTROLS, CLEANS UP THE CURVES, DELETES CURRENT CONTROLS AND BRINGS BACK ORIGINALS
"""
selected_controls = cmds.ls(sl=True)
if len(selected_controls) > 0:
for i in range(6):
cmds.pickWalk(d="up")
for obj in cmds.ls(sl=True):
if "temp_IK_Group" in obj or "temp_FK_Group" in obj:
pass
else:
assist_message(
"Incorrect selection. To generate the code for deleting an IK or FK setup, select one of its controls and then click the button.",
4500)
code += """
temp_selection = """ + str(selected_controls) + """
"""
elif len(cmds.ls(sl=True)) == 0:
code += """
temp_selection = get_temporary_setup_selection()"""
code += """
for selection in temp_selection:
if cmds.objExists(selection):
check_if_correct_selection(selection)
temp_group = get_group_selection(selection)
group_contents = cmds.listRelatives(temp_group)
if "temp_IK_Group" in temp_group:
original_controls = [clean_up(i, ["rotate"], group_contents) for i in range(3,6)]
if clear_keys:
remove_unnecessary_keys(original_controls, ["rotateX", "rotateY", "rotateZ"])
# remove_unnecessary_keys([original_controls[0], original_controls[2]], ["rotate"])
# remove_unnecessary_keys([original_controls[1]], ["rotateX"])
elif "temp_FK_Group" in temp_group:
ik_ctrl, pole_vector_ctrl = [clean_up(i, ["translate", "rotate"], group_contents) for i in range(1,3)]
if clear_keys:
remove_unnecessary_keys([ik_ctrl], ["translateX", "translateY", "translateZ", "rotateX", "rotateY", "rotateZ"])
remove_unnecessary_keys([pole_vector_ctrl], ["translateX", "translateY", "translateZ"])
ik_handle = delete_ik_blend(3, group_contents)
#cmds.lockNode(temp_group, l=False)
if cmds.objExists(temp_group):
cmds.delete(temp_group)
delete_setup()
"""
cmds.scrollField("GenerateCodeOutputWindow", e=True, tx=code)
def extraOptions():
if cmds.window("Extra_Options", ex=True):
cmds.deleteUI("Extra_Options")
cmds.window("Extra_Options", title="Extra Options", wh=[344, 242], s=False)
cmds.formLayout("ikfk_form_layout_extra", numberOfDivisions=100, w=343, h=240)
cmds.button("Generate_Code_Button", l="Generate Code", recomputeSize=True,
bgc=[0.6220035095750363, 1.0, 0.7237659266041047], h=44, w=110, parent="ikfk_form_layout_extra",
command=lambda *args: generateCode(),
ann="It isolates the code from the UI, so you can put it on a shelf or add it to a marking menu, and you don't have to come back to the UI every time.\nThere's 2 ways to use this.\n"
"Specific setup: If you select the controls in the scene as if you were applying the setup, when you hit generate it'll store the selection into the code, so you don't have to select them every time.\n"
"General setup: If you have nothing selected in the scene and hit generate, it'll produce a generic version of the code, and you'll have to select the controls every time before executing the code, but it's more flexible.\n"
"Important note: Whatever values you have for the settings in the main window, those values will be stored within the code you generate.")
set_form_layout_coordinates("ikfk_form_layout_extra", "Generate_Code_Button", 17, 16)
cmds.radioButtonGrp("GenerateCodeOptions_RadioB", vr=True, numberOfRadioButtons=3, en1=True, l1="FK to IK",
l2="IK to FK", l3="Delete Setup", parent="ikfk_form_layout_extra")
cmds.radioButtonGrp("GenerateCodeOptions_RadioB", e=True, select=1)
set_form_layout_coordinates("ikfk_form_layout_extra","GenerateCodeOptions_RadioB", 11.5, 135)
cmds.scrollField("GenerateCodeOutputWindow", width=312, height=150)
set_form_layout_coordinates("ikfk_form_layout_extra", "GenerateCodeOutputWindow", 75, 16)
cmds.showWindow("Extra_Options")
if __name__ == "__main__":
run()