""" 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="" + message + "", 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="" + message + "", 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()