diff --git a/Scripts/Animation/IK_FK_Switcher.py b/Scripts/Animation/IK_FK_Switcher.py new file mode 100644 index 0000000..de58705 --- /dev/null +++ b/Scripts/Animation/IK_FK_Switcher.py @@ -0,0 +1,1617 @@ +""" +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() \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/.gitattributes b/Scripts/Animation/MotionCapHelper/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/Scripts/Animation/MotionCapHelper/bin/LICENSE b/Scripts/Animation/MotionCapHelper/bin/LICENSE new file mode 100644 index 0000000..564fab5 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 yxnhyxnh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Scripts/Animation/MotionCapHelper/bin/__inti__.py b/Scripts/Animation/MotionCapHelper/bin/__inti__.py new file mode 100644 index 0000000..e69de29 diff --git a/Scripts/Animation/MotionCapHelper/bin/mocaphelper.py b/Scripts/Animation/MotionCapHelper/bin/mocaphelper.py new file mode 100644 index 0000000..f45c44a --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/mocaphelper.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +''' +this plugin is created for use with autodesk maya 2018+. +(because it import pyside2 module,and i don't know in which version maya would update this.) + +''' + + + +import sys + +import maya.api.OpenMaya as om +import mocaphelperui +import mocaphelpersaccore +import mocaphelperfacore +import mocaphelperutility + +import PySide2.QtWidgets + +from PySide2 import QtCore + + +version = 1.44 +ui = None + +translator = QtCore.QTranslator() + + + +def maya_useNewAPI(): + """ + The presence of this function tells Maya that the plugin produces, and + expects to be passed, objects created using the Maya Python API 2.0. + """ + pass + +# Initialize the plug-in +def initializePlugin(plugin): + pluginFn = om.MFnPlugin(plugin) + try: + pluginFn.registerCommand( + mocaphelpersaccore.SmoothAnimCurve.kPluginCmdName, mocaphelpersaccore.SmoothAnimCurve.cmdCreator,mocaphelpersaccore.syntaxCreator + ) + + except: + sys.stderr.write( + "Failed to register command: %s\n" % mocaphelpersaccore.SmoothAnimCurve.kPluginCmdName + ) + raise + + try: + pluginFn.registerCommand( + openui.kPluginCmdName, openui.cmdCreator,openuiSyntaxCreator + ) + except: + sys.stderr.write( + "Failed to register command: %s\n" % openui.kPluginCmdName + ) + raise + + try: + pluginFn.registerCommand( + mocaphelperfacore.FrameAlign.kPluginCmdName, mocaphelperfacore.FrameAlign.cmdCreator,mocaphelperfacore.syntaxCreator + ) + except: + sys.stderr.write( + "Failed to register command: %s\n" % mocaphelperfacore.FrameAlign.kPluginCmdName + ) + raise + + try: + pluginFn.registerCommand( + Eval.kPluginCmdName, Eval.cmdCreator,evalSyntaxCreator + ) + except: + sys.stderr.write( + "Failed to register command: %s\n" % Eval.kPluginCmdName + ) + raise + + + +# Uninitialize the plug-in +def uninitializePlugin(plugin): + pluginFn = om.MFnPlugin(plugin) + try: + pluginFn.deregisterCommand(mocaphelpersaccore.SmoothAnimCurve.kPluginCmdName) + except: + sys.stderr.write( + "Failed to unregister command: %s\n" % mocaphelpersaccore.SmoothAnimCurve.kPluginCmdName + ) + raise + try: + pluginFn.deregisterCommand(openui.kPluginCmdName) + except: + sys.stderr.write( + "Failed to unregister command: %s\n" % openui.kPluginCmdName + ) + raise + + + try: + pluginFn.deregisterCommand(mocaphelperfacore.FrameAlign.kPluginCmdName) + except: + sys.stderr.write( + "Failed to unregister command: %s\n" % mocaphelperfacore.FrameAlign.kPluginCmdName + ) + raise + + try: + pluginFn.deregisterCommand(Eval.kPluginCmdName) + except: + sys.stderr.write( + "Failed to unregister command: %s\n" % Eval.kPluginCmdName + ) + raise + + +# command + + +class openui(om.MPxCommand): + + kPluginCmdName = "moCapHelper_showUi" + + uiLanguageFlagShortName = "lan" + uiLanguageFlagLongName = "language" + lang = None + def __init__(self): + om.MPxCommand.__init__(self) + + @staticmethod + def cmdCreator(): + return openui() + + def parseArguments(self,args): + argdata = om.MArgParser(self.syntax(),args) + + if argdata.isFlagSet( self.uiLanguageFlagShortName ): + self.lang = argdata.flagArgumentString(self.uiLanguageFlagShortName,0) + else: + self.lang = None + + + + def doIt(self, args): + self.parseArguments(args) + global ui + if ui != None: + print("saved ui ref:",ui) + print("closing ui:--------",ui.destroy(True,True)) + + + app = PySide2.QtWidgets.QApplication.instance() + + app.installTranslator(translator) + dir = mocaphelperutility.getDir() + # translate: + if self.lang == "CN": + + + + translator.load("ui_CN",dir) + ui = mocaphelperui.MoCapHelperUI() + ui.setWindowTitle('动补助手 v'+str(version)) + # print(translator.filePath()) + # print(translator.translate()) + + # mocaphelperui.translateUi(ui.ui,ui.ui) + else: + translator.load("") + ui = mocaphelperui.MoCapHelperUI() + + ui.show() + + + + +def openuiSyntaxCreator(): + + syntax = om.MSyntax() + + syntax.addFlag( openui.uiLanguageFlagShortName, openui.uiLanguageFlagLongName, om.MSyntax.kString ) + + + return syntax + + + +class Eval(om.MPxCommand): + + kPluginCmdName = "moCapHelper_eval" + + strFlagShortName = "s" + strFlagLongName = "string" + cmd = "" + + def __init__(self): + om.MPxCommand.__init__(self) + + + + def parseArguments(self,args): + argdata = om.MArgParser(self.syntax(),args) + + if argdata.isFlagSet( self.strFlagShortName ): + self.cmd = argdata.flagArgumentString(self.strFlagShortName,0) + else: + raise Exception("no str input") + + + @staticmethod + def cmdCreator(): + + return Eval() + + + def doIt(self, args): + self.parseArguments(args) + print(self.cmd) + global ui + if ui != None: + exec(self.cmd) + else: + raise Exception("please create ui first.") + + +def evalSyntaxCreator(): + + syntax = om.MSyntax() + + syntax.addFlag( Eval.strFlagShortName, Eval.strFlagLongName, om.MSyntax.kString ) + + + return syntax + diff --git a/Scripts/Animation/MotionCapHelper/bin/mocaphelperarbcore.py b/Scripts/Animation/MotionCapHelper/bin/mocaphelperarbcore.py new file mode 100644 index 0000000..e79de7c --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/mocaphelperarbcore.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- +import maya.cmds as cmds +import maya.mel as mel + +import mocaphelperutility +import math + +def createLoc(strname,cd = (0,0,0),rot = (0,0,0),roo = "xyz"): + locname = cmds.spaceLocator(name = strname) + mocaphelperutility.setWorldPos(locname,cd) + mocaphelperutility.setWorldRot(locname,rot,roo) + return locname + +def reposition(objA,objB): + tempp = mocaphelperutility.getWorldPos(objB) + tempr = mocaphelperutility.getWorldRot(objB) + temproo = mocaphelperutility.getRotOrder(objB) + mocaphelperutility.setWorldPos(objA,tempp) + mocaphelperutility.setWorldRot(objA,tempr,temproo) + + +def checkConstraint(obj): + parent = cmds.parentConstraint(obj, targetList = True ,q = True ) + point = cmds.pointConstraint(obj, targetList = True ,q = True ) + orient = cmds.orientConstraint(obj, targetList = True ,q = True ) + if parent == None and point == None and orient == None: + return False + else: + conslist = [parent,point,orient] + return conslist + +def deleteAllConstraint(obj): + cmds.delete(obj,cn=True) + +def createPointConstraint(ctrlobj,targetobj,maintainoffset = False): + name = cmds.pointConstraint(ctrlobj,targetobj,mo = maintainoffset) + return name + +def createOrientConstraint(ctrlobj,targetobj,maintainoffset = False): + name = cmds.orientConstraint(ctrlobj,targetobj,mo = maintainoffset) + return name + +def createParentConstraint(ctrlobj,targetobj,maintainoffset = False): + name = cmds.parentConstraint(ctrlobj,targetobj,mo = maintainoffset) + return name +''' +def deletePointConstraint(ctrlobj,targetobj): + cmds.pointConstraint(ctrlobj,targetobj,remove = True) + +def deleteParentConstraint(ctrlobj,targetobj): + cmds.parentConstraint(ctrlobj,targetobj,remove = True) + +def deleteOrientConstraint(ctrlobj,targetobj): + cmds.orientConstraint(ctrlobj,targetobj,remove = True) +''' +def bake(objs,mintime,maxtime,type = "all"): + # if smartbake == True: + if type == "all": + cmds.bakeResults(objs,t =(mintime,maxtime),simulation = True) + else: + if type == "parent": + atb = ["tx","ty","tz","sx","sy","sz","rx","ry","rz"] + + elif type == "point": + atb = ["tx","ty","tz"] + + elif type == "orient": + atb = ["rx","ry","rz"] + + elif type == "onlyscale": + atb = ["sx","sy","sz"] + + else: + raise Exception("bake error:type unidentified:",type) + cmds.bakeResults(objs,t =(mintime,maxtime),at = atb) + +def smartbake(objs,mintime,maxtime,type = "all"): + if type == "all": + cmds.bakeResults(objs,t =(mintime,maxtime),smart = True,simulation = True) + else: + if type == "parent": + atb = ["tx","ty","tz","sx","sy","sz","rx","ry","rz"] + + elif type == "point": + atb = ["tx","ty","tz"] + + elif type == "orient": + atb = ["rx","ry","rz"] + + elif type == "onlyscale": + atb = ["sx","sy","sz"] + + else: + raise Exception("bake error:type unidentified") + cmds.bakeResults(objs,t =(mintime,maxtime),smart = True,at = atb) + + +def deleteInrange(objs,start,end): + if cmds.keyframe(objs,q = True) == None : + raise Exception("ref has no keys!") + else: + mocaphelperutility.cutKey(objs,start+0.01,end-0.01) + # mocaphelperutility.autoKetTangent(objs,start,start) + # mocaphelperutility.autoKetTangent(objs,end,end) + +def deleteOutrange(objs,start,end): + if cmds.keyframe(objs,q = True) == None : + raise Exception("ref has no keys!") + + framelist = set(cmds.keyframe(objs,q = True)) + minframe = min(framelist) + maxframe = max(framelist) + if minframe < start: + mocaphelperutility.cutKey(objs,minframe,start-0.01) + # mocaphelperutility.autoKetTangent(objs,start,start) + if maxframe > end: + mocaphelperutility.cutKey(objs,end+0.01,maxframe) + # mocaphelperutility.autoKetTangent(objs,end,end) + + +def offsetFrame(objs,offset): + cmds.keyframe(objs,e = True,tc = float(offset),o = "over",relative = True) + +def stickyDelete(ui): + objs = mocaphelperutility.getSelectedNodes() + fromframe = eval(ui.ui.arb_fromEdit.text()) + curframe = mocaphelperutility.getCurrentFrame() + if fromframe != curframe: + mocaphelperutility.cutKey(objs,fromframe,curframe,True) + mocaphelperutility.autoKetTangent(objs,fromframe,curframe) + ui.ui.arb_fromEdit.setText(str(curframe)) + ui.ui.arb_toEdit.setText(str(curframe)) + else: + raise Exception("from == curframe") + + +def bakeAtoB(A,B,start,end,type,maintainoffset = False,smart = False): + print("type == ",type) + if type == "parent" or type == "all": + cons = createParentConstraint(A,B,maintainoffset) + elif type == "point": + cons = createPointConstraint(A,B,maintainoffset) + elif type == "orient": + cons = createOrientConstraint(A,B,maintainoffset) + elif type == "onlyscale": + cons = createParentConstraint(A,B,maintainoffset) + + if smart: + smartbake(B,start,end,type) + else: + bake(B,start,end,type) + + mocaphelperutility.deleteObj(cons) + + +def pinCurPos(objs,mintime,maxtime): + min = mintime + max = maxtime + if min > max: + min = maxtime + max = mintime + if min == max: + raise Exception("min == max.") + + + cdlist = [] + # cmds.currentTime(min) + for obj in objs: + + pos = mocaphelperutility.getWorldPos(obj) + rot = mocaphelperutility.getWorldRot(obj) + cdlist.append([pos,rot]) + for i in range(int(math.floor(min)),int(math.ceil(max)+1)): + + cmds.currentTime(float(i)) + cmds.setKeyframe(objs,at = ["tx","ty","tz","rx","ry","rz"]) + for i in range(len(objs)): + pos = cdlist[i][0] + rot = cdlist[i][1] + cmds.xform(objs[i],ws = True,translation = pos,rotation = rot) + + +def pinParentPos(objs,mintime,maxtime): + + leng = len(objs) + if leng <= 1: + raise Exception("not enough obj selected,at least 2.") + min = mintime + max = maxtime + if min > max: + min = maxtime + max = mintime + if min == max: + raise Exception("min == max.") + + + # cmds.currentTime(min) + consobj = objs[:-1] + beconsobj = objs[-1] + + pos = mocaphelperutility.getWorldPos(beconsobj) + rot = mocaphelperutility.getWorldRot(beconsobj) + roo = mocaphelperutility.getRotOrder(beconsobj) + loc = createLoc("mocaphelper_arb_pinparent_temp_loc",pos,rot,roo)[0] + createParentConstraint(consobj,loc,True) + bake(loc,min,max,type="parent") + + + + for i in range(int(math.floor(min)),int(math.ceil(max)+1)): + + cmds.currentTime(float(i)) + cmds.setKeyframe(beconsobj,at = ["tx","ty","tz","rx","ry","rz"]) + reposition(beconsobj,loc) + + mocaphelperutility.deleteObj(loc) + mocaphelperutility.selectNodes(beconsobj) \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/bin/mocaphelperfacore.py b/Scripts/Animation/MotionCapHelper/bin/mocaphelperfacore.py new file mode 100644 index 0000000..dc64009 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/mocaphelperfacore.py @@ -0,0 +1,100 @@ +import maya.api.OpenMaya as om +import maya.api.OpenMayaAnim as omani +import maya.cmds as cmds + +import maya.mel as mel + +import mocaphelperutility + +refobjsShortFlagName = "-ref" +refobjsLongFlagName = "-refobjs" + + + +def syntaxCreator(): + + syntax = om.MSyntax() + + syntax.addFlag( refobjsShortFlagName, refobjsLongFlagName, om.MSyntax.kString ) + + + return syntax + +class FrameAlign(om.MPxCommand): + kPluginCmdName = "moCapHelper_frameAlign" + + ref = "" + animcruvechanage = None + + def __init__(self): + om.MPxCommand.__init__(self) + + @staticmethod + def cmdCreator(): + return FrameAlign() + + + def parseArguments(self,args): + + argdata = om.MArgParser(self.syntax(),args) + + if argdata.isFlagSet( refobjsShortFlagName ): + self.ref = argdata.flagArgumentString(refobjsShortFlagName,0) + else: + raise Exception("No refence argument!") + + def isUndoable(self): + return True + + + def doIt(self,args): + self.animcruvechanage = omani.MAnimCurveChange() + self.parseArguments(args) + # start = cmds.playbackOptions( q=True,min=True ) + # end = cmds.playbackOptions( q=True,max=True ) + if mocaphelperutility.objExist(self.ref) == False: + raise Exception("ref obj does not exist!") + selectedobjs = mocaphelperutility.getSelectedNodes() + print(self.ref) + if cmds.keyframe(self.ref,q = True) == None : + raise Exception("ref has no keys!") + else: + framelist = set(cmds.keyframe(self.ref,q = True)) + for frame in framelist: + cmds.setKeyframe(selectedobjs,t = (frame,frame),rk= True,hierarchy = "none",i = True,itt = "auto") + + for obj in selectedobjs: + objframelist = set(cmds.keyframe(obj,q = True)) + for objframe in objframelist: + if objframe in framelist: + continue + else: + mocaphelperutility.cutKey(obj,objframe,objframe) + + + def undoIt(self): + if self.animcruvechanage != None : + self.animcruvechanage.undoIt() + print("undo success") + else: + print("undo failed:self.animcruvechanage == None") + + def redoIt(self): + if self.animcruvechanage != None : + self.animcruvechanage.redoIt() + print("redo success") + else: + print("redo failed:self.animcruvechanage == None") + + + + +# def cutlist(sourcelist,splitlist): +# backindex = 0 +# forwardindex = 0 +# for i in len(sourcelist): +# if sourcelist[i] in splitlist: + +# def fastRecord(obj): +# pos = mocaphelperutility.getWorldPos(obj) +# loc = mocaphelperarbcore.createLoc("mocaphelper_fa_temp_loc") \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/bin/mocaphelperpbcore.py b/Scripts/Animation/MotionCapHelper/bin/mocaphelperpbcore.py new file mode 100644 index 0000000..07250d2 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/mocaphelperpbcore.py @@ -0,0 +1,53 @@ +import datetime +import getpass +import glob +import os +import shutil +import subprocess + +import maya.cmds as cmds +import maya.mel as mel + + +PREV_DIR_ENV = "Mocaphelper_Playblast_Path" + + + + + +def get_prev_path(): + if cmds.optionVar(exists=PREV_DIR_ENV): + return cmds.optionVar(q=PREV_DIR_ENV) + else: + return None + +def set_prev_path(dir_path): + cmds.optionVar(sv=(PREV_DIR_ENV, dir_path)) + + +def getMagicMaskNode(): + magic_mask_nodes = cmds.ls(type='magicMask') + if magic_mask_nodes == None: + return None + else: + return magic_mask_nodes[0] + + +def resetHUD(): + mel.eval('source "initHUD";') + + +def removeHUD(): + default_hud_list = cmds.headsUpDisplay(lh=True) + if default_hud_list: + for default_hud in default_hud_list: + cmds.headsUpDisplay(default_hud, rem=True) + + + + # cmds.setAttr('%s.displayFilmGate' % main_cam, 0) + # cmds.setAttr('%s.displayResolution' % main_cam, 0) + + +def moCapHelper_Playblast(cam,filepath,offscreen): + pass \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/bin/mocaphelperrecore.py b/Scripts/Animation/MotionCapHelper/bin/mocaphelperrecore.py new file mode 100644 index 0000000..c723bd6 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/mocaphelperrecore.py @@ -0,0 +1,168 @@ +import maya.api.OpenMaya as om +import maya.cmds as cmds +import maya.mel as mel + +import re + +import mocaphelperutility + + +def retestlist(strlist,exp,casecheck = False): + matchlist = [] + pyVersion = mocaphelperutility.getPythonVersion() + if pyVersion < 3: + + if (type(exp)== unicode ): + raw_exp = repr(exp)[2:-1] + elif (type(exp)== str ): + raw_exp = exp + else: + raise Exception("unidentified string format input!") + + else: + raw_exp = exp + + print("convert---"+exp+"---to---"+raw_exp) + for longname in strlist: + shortname = longname2shortname(longname)[0] + print("checking:---exp---"+raw_exp+"---objname---"+shortname) + if casecheck == False: + if re.search(raw_exp,shortname,re.I) != None: + matchlist.append(longname) + print("succeed") + else: + print("failed") + continue + else: + if re.search(raw_exp,shortname) != None: + matchlist.append(longname) + print("succeed") + else: + print("failed") + continue + + if len(matchlist) == 0: + print("no obj match expression!") + return None + else: + return matchlist + + + +def expCreateDisplayLayer(exp,name,prefix = "",casecheck = False,childcheck = True,nodetype = "all"): + selected = mocaphelperutility.getSelectedNodes() + if childcheck: + + untestlist = mocaphelperutility.getChildNodes(selected,nodetype=nodetype) + else: + untestlist = selected + combinelist = [] + #check for multiple exp exist + if exp.find(";") != -1 or name.find(";") != -1: + splitedexplist = exp.split(";") + splitednamelist = name.split(";") + if len(splitedexplist) != len(splitednamelist): + raise Exception("name and exp count not match!") + else: + for i in range(len(splitedexplist)): + combinelist.append([splitedexplist[i],splitednamelist[i]]) + else: + combinelist =[[exp,name]] + + for combine in combinelist: + + matchlist = retestlist(untestlist,combine[0],casecheck) + print("matchlist:",matchlist) + + createDisplayLayer(prefix+combine[1],matchlist) + + +def createDisplayLayer(name,objlist): + cmds.createDisplayLayer(n= name,nr = True,empty = True) + cmds.editDisplayLayerMembers(name,objlist,nr = True) + + + +def extractLayerInfo(ui): + name = cmds.editDisplayLayerGlobals( query=True, cdl=True ) + members =cmds.editDisplayLayerMembers( name, query=True ) + exp = "" + for member in members: + noPrefixMember = deleteRigPrefix(member) + if noPrefixMember[1] == True: + exp = exp+"."+ noPrefixMember[0] +"|" + else: + exp = exp+ noPrefixMember[0] +"|" + # remove last "|",cause it will match all name str. + exp = exp[0:-1] + ui.ui.re_layerNameEdit.setText(name) + ui.ui.re_expEdit.setText(exp) + return + +def selMainCtrl(): +# main name: +# ".MainExtra2" ".wenchangtai" ".Main_all" + curves = cmds.ls(type = "dagNode") + curves = mocaphelperutility.filterCertainTypeInList(curves,type = "nurbsCurve") + matchlist = [] + a = retestlist(curves,".MainExtra2$|.wenchangtai$|.Main_all$") + if a != None: + matchlist += a + cmds.select(cl = True) + cmds.select(matchlist) + + +def longname2shortname(name): + id = name.rfind("|") + if id == -1: + return name,False + else: + return name[id+1:],True + +def deleteRigPrefix(name): + id = name.rfind(":") + if id == -1: + return name,False + else: + return name[id:],True + + + +def presetfileRebuild(filepath,backupfilepath): + backupstr = presetFileRead(backupfilepath) + presetFileWrite(filepath,backupstr) + + + +def rawStrBuild(rawstr): + split = rawstr.split("\n") + newlist = [] + for i in range(int(len(split)/2)): + name = split[2*i] + exp = split[2*i+1] + newlist.append([name,exp]) + if len(newlist) == 0: + raise Exception("split list is empty!") + else: + return newlist + +def strAppend(oristr,line1,line2): + newstr = oristr+"\n"+str(line1)+"\n"+str(line2) + return newstr + + +def presetFileRead(filepath): + file = open(filepath,mode ="r+") + file.seek(0,0) + raw = file.read() + file.close() + return raw + + +def presetFileWrite(filepath,str): + print("writing file:\n"+str) + file = open(filepath,mode ="w+") + file.seek(0,0) + file.write(str) + file.close() + diff --git a/Scripts/Animation/MotionCapHelper/bin/mocaphelpersaccore.py b/Scripts/Animation/MotionCapHelper/bin/mocaphelpersaccore.py new file mode 100644 index 0000000..fd0ab04 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/mocaphelpersaccore.py @@ -0,0 +1,278 @@ +import maya.api.OpenMaya as om +import maya.api.OpenMayaAnim as omani +import maya.cmds as cmds + +import maya.mel as mel +import math + + +# selectionList = om.MGlobal.getActiveSelectionList() +# iterator = om.MItSelectionList( selectionList, om.MFn.kDagNode ) +methodShortFlagName = "-m" +methodLongFlagName = "-method" +iterShortFlagName = "-i" +iterLongFlagName = "-iteration" +intensityShortFlagName = "-in" +intensityLongFlagName = "-intensity" +seltypeShortFlagName = "-sel" +seltypeLongFlagName = "-selecttype" +useTimeShortFlagName = "-u" +useTimeLongFlagName = "-usetime" +timeUnitShortFlagName = "-tu" +timeUnitLongFlagName = "-timeunit" + + + +def syntaxCreator(): + + syntax = om.MSyntax() + + syntax.addFlag( methodShortFlagName, methodLongFlagName, om.MSyntax.kString ) + syntax.addFlag( iterShortFlagName, iterLongFlagName, om.MSyntax.kLong ) + syntax.addFlag( intensityShortFlagName, intensityLongFlagName, om.MSyntax.kDouble ) + syntax.addFlag( seltypeShortFlagName, seltypeLongFlagName, om.MSyntax.kLong ) + syntax.addFlag( useTimeShortFlagName, useTimeLongFlagName, om.MSyntax.kBoolean ) + syntax.addFlag( timeUnitShortFlagName, timeUnitLongFlagName, om.MSyntax.kDouble ) + + + + return syntax + + +class SmoothAnimCurve(om.MPxCommand): + kPluginCmdName = "moCapHelper_smoothAniCurve" + method = 0 + iteration = 1 + intensity = 1.0 + seltype = 0 + useTime = True + timeUnit = 5.0 + + animcruvechanage = None + + def __init__(self): + om.MPxCommand.__init__(self) + + + def parseArguments(self,args): + + argdata = om.MArgParser(self.syntax(),args) + + if argdata.isFlagSet( methodShortFlagName ): + if argdata.flagArgumentString(methodShortFlagName,0) == "3linear": + self.method = 0 + elif argdata.flagArgumentString(methodShortFlagName,0) == "3bell": + self.method = 1 + elif argdata.flagArgumentString(methodShortFlagName,0) == "3ham": + self.method = 2 + elif argdata.flagArgumentString(methodShortFlagName,0) == "5quad": + self.method = 10 + elif argdata.flagArgumentString(methodShortFlagName,0) == "5bell": + self.method = 11 + elif argdata.flagArgumentString(methodShortFlagName,0) == "5ham": + self.method = 12 + else: + raise Exception("input method argument is not vaild!"+str(argdata.flagArgumentString(methodShortFlagName,0))) + else: + raise Exception("no input method argument") + + if argdata.isFlagSet( iterShortFlagName ): + if argdata.flagArgumentInt(iterShortFlagName,0) <= 100: + self.iteration = argdata.flagArgumentInt(iterShortFlagName,0) + else: + raise Exception("iteration is bigger than 100,little bit too high,right?") + + if argdata.isFlagSet( intensityShortFlagName ): + self.intensity = argdata.flagArgumentFloat(intensityShortFlagName,0) + + if argdata.isFlagSet( seltypeShortFlagName ): + self.seltype = argdata.flagArgumentInt(seltypeShortFlagName,0) + + if argdata.isFlagSet( useTimeShortFlagName ): + self.useTime = argdata.flagArgumentBool(useTimeShortFlagName,0) + + if argdata.isFlagSet( timeUnitShortFlagName ): + self.timeUnit = argdata.flagArgumentFloat(timeUnitShortFlagName,0) + + @staticmethod + def cmdCreator(): + return SmoothAnimCurve() + + def doIt(self, args): + + self.animcruvechanage = omani.MAnimCurveChange() + self.parseArguments(args) + + + + + animcurves = self.getanimcurves() + + for iter in range(self.iteration): + + for cv in animcurves: + keylist = self.getkeylist(cv) + templist = self.smoothkeylistvalue(cv,keylist) + print(templist) + mslist = om.MGlobal.getSelectionListByName(repr(cv)[2:-1]) + mit = om.MItSelectionList(mslist) + mobj = mit.getDependNode() + manimcurve = omani.MFnAnimCurve(mobj) + curveisangular = False + if manimcurve.animCurveType == manimcurve.kAnimCurveTA or manimcurve.animCurveType == manimcurve.kAnimCurveUA : + curveisangular = True + for i in templist: + if curveisangular: + value = math.radians(i[1]) + else: + value = i[1] + manimcurve.setValue(i[0],value,self.animcruvechanage) + pass + + + print("using smoothanimcurve cmd with argument:\nselect type:",self.seltype,"\niteration:",self.iteration,"\nintensity:",self.intensity,"\nusetime:",self.useTime,"\ntimeunit:",self.timeUnit,"\nmethod:",self.method) + + + def undoIt(self): + if self.animcruvechanage != None : + self.animcruvechanage.undoIt() + print("undo success") + else: + print("undo failed:self.animcruvechanage == None") + + def redoIt(self): + if self.animcruvechanage != None : + self.animcruvechanage.redoIt() + print("redo success") + else: + print("redo failed:self.animcruvechanage == None") + + def isUndoable(self): + return True + + def getanimcurves(self): + selectflag = cmds.animCurveEditor("graphEditor1GraphEd",query = True,areCurvesSelected = True) + showncurves = cmds.animCurveEditor("graphEditor1GraphEd",query = True,curvesShown = True) + selectedcurves = cmds.keyframe(q = True,s = True,name = True) + isoflag = 0 + if showncurves == None: + raise Exception("plugin can't find any curve in graph editor,no curve shown or selected.") + else: + isoflag = len(showncurves) != len(selectedcurves) + + if selectflag: + return selectedcurves + if isoflag: + self.seltype == 0 + return showncurves + else: + raise Exception("plugin can't find any curve in graph editor,no curve shown or selected.") + + # if selectflag: + # return selectedcurves + # else: + # return showncurves + # print(showncurves) + # print("----------------") + # print(selectedcurves) + + def getkeylist(self,animcurve): + numkeys = cmds.keyframe(animcurve,q = True,keyframeCount = True) + keylist = [] + #seltype == 0 :all keys on curves + if self.seltype == 0: + for i in range(numkeys): + keylist.append([i,0.0]) + #seltype == 1 :selected keys only + elif self.seltype == 1: + numselkeyindexlist = cmds.keyframe(animcurve,sl = True,q=True,indexValue = True) + for index in numselkeyindexlist: + keylist.append([int(index),0.0]) + return keylist + + + + def smoothkeylistvalue(self,curve,keylist): + flag5pt = False + compareint = 0 + if self.method>=9: + compareint = 1 + flag5pt = True + numkeys = cmds.keyframe(curve,q = True,keyframeCount = True) + for key in keylist: + time = getkeytime(curve,key[0]) + value = getkeyvalue(curve,time) + + + if key[0] <=compareint or numkeys-key[0]<=compareint+1: + + key[1] = value + + else: + pretime2 = 0 + prevalue2 = 0 + nexttime2 = 0 + nextvalue2 = 0 + + pretime = getkeytime(curve,key[0]-1) + nexttime = getkeytime(curve,key[0]+1) + + if self.useTime: + pretime = time - self.timeUnit + nexttime = time + self.timeUnit + else: + pass + + prevalue = getkeyvalue(curve,pretime) + nextvalue = getkeyvalue(curve,nexttime) + + + + + if flag5pt: + pretime2 = getkeytime(curve,key[0]-2) + nexttime2 = getkeytime(curve,key[0]+2) + + if self.useTime: + pretime2 = time - self.timeUnit*2 + nexttime2 = time + self.timeUnit*2 + else: + pass + + + prevalue2 = getkeyvalue(curve,pretime2) + nextvalue2 = getkeyvalue(curve,nexttime2) + + if self.method == 0: + tempvalue = value+prevalue+nextvalue + key[1] = lerp(value,tempvalue/3,self.intensity) + elif self.method == 1: + tempvalue = 0.576*value+0.212*prevalue+0.212*nextvalue + key[1] = lerp(value,tempvalue,self.intensity) + elif self.method == 2: + tempvalue = 0.86*value+0.07*prevalue+0.07*nextvalue + key[1] = lerp(value,tempvalue,self.intensity) + elif self.method == 10: + tempvalue = 12*prevalue+nextvalue-3*prevalue2+nextvalue2+17*value + key[1] = lerp(value,tempvalue/35,self.intensity) + elif self.method == 11: + tempvalue = 0.11*(prevalue2+nextvalue2)+0.24*(prevalue+nextvalue)+0.3*(value) + key[1] = lerp(value,tempvalue,self.intensity) + elif self.method == 12: + tempvalue = 0.04*(prevalue2+nextvalue2)+0.24*(prevalue+nextvalue)+0.44*(value) + key[1] = lerp(value,tempvalue,self.intensity) + else: + raise Exception("method error while using smoothkeylistvalue()") + + return keylist + +def getkeytime(curve,id): + time = cmds.keyframe(curve,index=(id,id),q=True)[0] + return time +def getkeyvalue(curve,time): + value = cmds.keyframe(curve,t = (time,time),ev = True , q = True )[0] + return value +def lerp(originvalue,newvalue,intensity): + temp = newvalue-originvalue + return originvalue+temp*intensity + diff --git a/Scripts/Animation/MotionCapHelper/bin/mocaphelperui.py b/Scripts/Animation/MotionCapHelper/bin/mocaphelperui.py new file mode 100644 index 0000000..323fe4e --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/mocaphelperui.py @@ -0,0 +1,661 @@ +# coding=utf-8 +# -*- coding: utf-8 -*- + + +import os + +from mocaphelper import version +import mocaphelperutility +import mocaphelperrecore +import mocaphelperarbcore + + +from maya import cmds +from maya import mel +from maya import OpenMayaUI as omui + +from PySide2.QtCore import * +from PySide2.QtGui import * +from PySide2.QtWidgets import * +from PySide2.QtUiTools import * +from shiboken2 import wrapInstance + +mayaMainWindowPtr = omui.MQtUtil.mainWindow() +pyVersion = mocaphelperutility.getPythonVersion() +if pyVersion < 3: + mayaMainWindow = wrapInstance(long(mayaMainWindowPtr), QWidget) +else: + mayaMainWindow = wrapInstance(int(mayaMainWindowPtr), QWidget) + + +class MoCapHelperUI(QWidget): + #sac: + sac_iteration = 1 + sac_intensity = 1.0 + sac_useTime = True + sac_method = "3linear" + sac_selType = "curves" + sac_timeUnit = 5.0 + + #fa: + fa_refobjs = None + + #re: + re_exp = "" + re_name = "" + re_caseCheck = False + re_prefix = "" + + #arb: + arb_fromtime = 10.0 + arb_totime = 11.0 + def __init__(self, *args, **kwargs): + + super(MoCapHelperUI, self).__init__(*args, **kwargs) + self.setParent(mayaMainWindow) + self.setWindowFlags(Qt.Window) + self.setObjectName('mocaphelperui') + self.setWindowTitle('moCapHelper v'+str(version)) + self.loadUI() + self.initSac() + self.initFa() + self.initre() + self.intiArb() + self.re_exp = self.ui.re_expEdit.text() + # self.arb_syncRange() + + def loadUI(self): + loader = QUiLoader() + currentDir = os.path.dirname(__file__) + file = QFile(currentDir+"/mocaphelperui.ui") + file.open(QFile.ReadOnly) + try: + self.ui = loader.load(file, parentWidget=self) + finally: + file.close() + + layout = QVBoxLayout() + layout.addWidget(self.ui) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + self.setLayout(layout) + + #sac: + def initSac(self): + + self.ui.sac_itEdit.textEdited.connect(self.on_sac_itEditEdited) + self.ui.sac_inEdit.textEdited.connect(self.on_sac_inEditEdited) + self.ui.sac_utCheckBox.stateChanged.connect(self.on_sac_utCheckBoxChanged) + self.ui.sac_tuEdit.textEdited.connect(self.on_sac_tuEditEdited) + self.ui.sac_selTypeComboBox.activated[str].connect(self.on_sac_selTypeComboBoxChanged) + self.ui.sac_smoothMethodComboBox.activated[str].connect(self.on_sac_smoothMethodComboBoxChanged) + self.ui.sac_cmdButton.clicked.connect(self.on_sac_cmdButtonClicked) + + def on_sac_itEditEdited(self): + self.sac_iteration = int(eval(self.ui.sac_itEdit.text())) + + def on_sac_inEditEdited(self): + self.sac_intensity = float(eval(self.ui.sac_inEdit.text())) + + def on_sac_utCheckBoxChanged(self): + self.sac_useTime = self.ui.sac_utCheckBox.isChecked() + + def on_sac_tuEditEdited(self): + self.sac_timeUnit = float(eval(self.ui.sac_tuEdit.text())) + + def on_sac_selTypeComboBoxChanged(self, text): + self.sac_selType = text + + def on_sac_smoothMethodComboBoxChanged(self, text): + self.sac_method = text + + def on_sac_cmdButtonClicked(self): + seltype = 0 + + if self.sac_selType == 'selected keys' : + seltype = 1 + + cmds.moCapHelper_smoothAniCurve( method = self.sac_method , i = self.sac_iteration , intensity = self.sac_intensity , sel = seltype , u = self.sac_useTime , tu = self.sac_timeUnit ) + + + + #fa: + def initFa(self): + + self.ui.fa_assignButton.clicked.connect(self.on_fa_assignButtonClicked) + self.ui.fa_cmdButton.clicked.connect(self.on_fa_cmdButtonClicked) + self.ui.fa_fastRecordButton.clicked.connect(self.on_fa_fastRecordButtonClicked) + + + def on_fa_assignButtonClicked(self): + curnode = mocaphelperutility.getSelectedNodes()[0] + self.ui.fa_refEdit.setText(curnode) + + def on_fa_cmdButtonClicked(self): + if self.ui.fa_refEdit.text() =="": + raise Exception("no ref obj in ui!") + else: + #cmds.moCapHelper_frameAlign(ref = self.fa_refobjs) + mel.eval("moCapHelper_frameAlign -ref "+self.ui.fa_refEdit.text()) + + def on_fa_fastRecordButtonClicked(self): + obj = mocaphelperutility.getSelectedNodes()[0] + loc = mocaphelperarbcore.createLoc("mocaphelper_fa_temp_loc")[0] + cmds.setKeyframe(loc,at = ["tx"]) + self.ui.fa_refEdit.setText(obj) + mocaphelperutility.selectNodes(loc) + self.on_fa_cmdButtonClicked() + self.ui.fa_refEdit.setText(loc) + + #re: + def initre(self): + currentDir = os.path.dirname(__file__) + self.re_presetFilePath = currentDir+"\\re_exp_preset.txt" + self.re_backupPresetFilePath = currentDir+"\\re_exp_preset_backup.txt" + if os.path.exists(self.re_presetFilePath) == False: + mocaphelperrecore.presetfileRebuild(self.re_presetFilePath,self.re_backupPresetFilePath) + + + self.ui.re_expPresetComboBox.currentIndexChanged.connect(self.on_re_expPresetComboBoxChanged) + self.re_reloadPreset() + + self.ui.re_reloadButton.clicked.connect(self.on_re_reloadButtonClicked) + self.ui.re_openFileButton.clicked.connect(self.on_re_openFileButtonClicked) + self.ui.re_assignButton.clicked.connect(self.on_re_assignButtonClicked) + self.ui.re_recordButton.clicked.connect(self.on_re_recordButtonClicked) + + + + + + + + self.ui.re_layerNameEdit.setText("layername") + self.ui.re_prefixEdit.textChanged.connect(self.on_re_layerPrefixEditEdited) + self.ui.re_prefixEdit.textEdited.connect(self.on_re_layerPrefixEditEdited) + self.ui.re_layerNameEdit.textChanged.connect(self.on_re_layerNameEditEdited) + self.ui.re_layerNameEdit.textEdited.connect(self.on_re_layerNameEditEdited) + + self.ui.re_expEdit.textChanged.connect(self.on_re_expEditEdited) + self.ui.re_expEdit.textEdited.connect(self.on_re_expEditEdited) + self.ui.re_selectMainCtrlButton.clicked.connect(self.on_re_selectMainCtrlButtonClicked) + self.ui.re_selectChildButton.clicked.connect(self.on_re_selectChildButtonClicked) + self.ui.re_selectChildCtrlButton.clicked.connect(self.on_re_selectChildCtrlButtonClicked) + self.ui.re_selectVisChildCtrlButton.clicked.connect(self.on_re_selectVisChildCtrlButtonClicked) + self.ui.re_expSelectButton.clicked.connect(self.on_re_expSelectButtonClicked) + self.ui.re_expCreateLayerButton.clicked.connect(self.on_re_expcreateLayerButtonClicked) + self.ui.re_selectedCreateLayerButton.clicked.connect(self.on_re_selcreateLayerButtonClicked) + self.ui.re_caseCheckCheckBox.stateChanged.connect(self.on_re_caseCheckCheckBoxChanged) + self.ui.re_extractLayerInfoButton.clicked.connect(self.on_re_extractLayerInfoButtonClicked) + + def on_re_layerPrefixEditEdited(self): + self.re_prefix = self.ui.re_prefixEdit.text() + + def on_re_layerNameEditEdited(self): + self.re_name = self.ui.re_layerNameEdit.text() + + def on_re_expEditEdited(self): + self.re_exp = self.ui.re_expEdit.text() + + def on_re_selectChildButtonClicked(self): + selectednodes = mocaphelperutility.getSelectedNodes() + children = mocaphelperutility.getChildNodes(selectednodes,nodetype="all") + mocaphelperutility.selectNodes(children) + print("objs selected: ",children) + + def on_re_selectChildCtrlButtonClicked(self): + selectednodes = mocaphelperutility.getSelectedNodes() + children = mocaphelperutility.getChildNodes(selectednodes,nodetype="nurbsCurve") + mocaphelperutility.selectNodes(children) + print("objs selected: ",children) + + def on_re_selectVisChildCtrlButtonClicked(self): + selectednodes = mocaphelperutility.getSelectedNodes() + children = mocaphelperutility.getChildNodes(selectednodes,nodetype="nurbsCurve") + mocaphelperutility.selectNodes(children,visible=True) + print("objs selected: ",children) + + def on_re_selectMainCtrlButtonClicked(self): + mocaphelperrecore.selMainCtrl() + + def on_re_expSelectButtonClicked(self): + actOnType = getReActOnType(self) + childcheck = self.ui.re_childCheckBox.isChecked() + seltednodes = mocaphelperutility.getSelectedNodes() + if childcheck: + ctrls = mocaphelperutility.getChildNodes(seltednodes,nodetype=actOnType) + else: + ctrls = seltednodes + matchlist = mocaphelperrecore.retestlist(ctrls,self.re_exp,self.re_caseCheck) + mocaphelperutility.selectNodes(matchlist) + print("objs selected: ",matchlist) + + + def on_re_selcreateLayerButtonClicked(self): + sel = mocaphelperutility.getSelectedNodes() + mocaphelperrecore.createDisplayLayer(self.re_prefix+self.re_name,sel) + + def on_re_expcreateLayerButtonClicked(self): + actOnType = getReActOnType(self) + childcheck = self.ui.re_childCheckBox.isChecked() + mocaphelperutility.openUndoChunk() + try: + + mocaphelperrecore.expCreateDisplayLayer(self.re_exp,self.re_name,self.re_prefix,self.re_caseCheck,childcheck = childcheck ,nodetype = actOnType) + finally: + mocaphelperutility.closeUndoChunk() + def on_re_extractLayerInfoButtonClicked(self): + mocaphelperrecore.extractLayerInfo(self) + + def on_re_caseCheckCheckBoxChanged(self): + self.re_caseCheck = self.ui.re_caseCheckCheckBox.isChecked() + + def on_re_expPresetComboBoxChanged(self): + pass + + def on_re_assignButtonClicked(self): + index = self.ui.re_expPresetComboBox.currentIndex() + self.ui.re_layerNameEdit.setText(self.re_itemlist[index][0]) + self.ui.re_expEdit.setText(self.re_itemlist[index][1]) + + + def on_re_recordButtonClicked(self): + self.re_rawstr = mocaphelperrecore.strAppend(self.re_rawstr,self.re_name,self.re_exp) + self.re_itemlist = mocaphelperrecore.rawStrBuild(self.re_rawstr) + mocaphelperrecore.presetfileRebuild(self.re_presetFilePath,self.re_backupPresetFilePath) + mocaphelperrecore.presetFileWrite(self.re_presetFilePath,self.re_rawstr) + self.re_reloadPreset() + + def on_re_openFileButtonClicked(self): + os.startfile(self.re_presetFilePath) + + def on_re_reloadButtonClicked(self): + self.re_reloadPreset() + + def re_expPresetComboBoxRebuild(self): + self.ui.re_expPresetComboBox.clear() + for item in self.re_itemlist: + convertedstr = "\""+item[0]+"\"\""+item[1]+"\"" + self.ui.re_expPresetComboBox.addItem( convertedstr ) + self.ui.re_expPresetComboBox.setCurrentIndex(0) + + def re_reloadPreset(self): + if os.path.exists(self.re_presetFilePath) == False: + mocaphelperrecore.presetfileRebuild(self.re_presetFilePath,self.re_backupPresetFilePath) + self.re_rawstr = mocaphelperrecore.presetFileRead(self.re_presetFilePath) + self.re_itemlist = mocaphelperrecore.rawStrBuild(self.re_rawstr) + self.re_expPresetComboBoxRebuild() + + + + # arb: + def intiArb(self): + # timerange + self.ui.arb_setFromButton.clicked.connect(self.arb_setFrom) + self.ui.arb_setToButton.clicked.connect(self.arb_setTo) + self.ui.arb_syncRangeButton.clicked.connect(self.arb_syncRange) + self.ui.arb_delInRangeButton.clicked.connect(self.on_arb_delInRangeButtonClicked) + self.ui.arb_delOutRangeButton.clicked.connect(self.on_arb_delOutRangeButtonClicked) + self.ui.arb_offsetFrameButton.clicked.connect(self.on_arb_offsetFrameButtonClicked) + self.ui.arb_stickyDelButton.clicked.connect(self.on_arb_stickyDelButtonClicked) + + self.ui.arb_fromEdit.textChanged.connect(self.on_arb_fromEditEdited) + self.ui.arb_fromEdit.textEdited.connect(self.on_arb_fromEditEdited) + + self.ui.arb_toEdit.textChanged.connect(self.on_arb_toEditEdited) + self.ui.arb_toEdit.textEdited.connect(self.on_arb_toEditEdited) + # basic + self.ui.arb_createLocButton.clicked.connect(self.on_arb_createLocButtonClicked) + self.ui.arb_rePosButton.clicked.connect(self.on_arb_rePosButtonClicked) + self.ui.arb_pointConstraintButton.clicked.connect(self.on_arb_pointConstraintButtonClicked) + self.ui.arb_orientConstraintButton.clicked.connect(self.on_arb_orientConstraintButtonClicked) + self.ui.arb_parentConstraintButton.clicked.connect(self.on_arb_parentConstraintButtonClicked) + self.ui.arb_delConstraintButton.clicked.connect(self.on_arb_delConstraintButtonClicked) + self.ui.arb_bakeButton.clicked.connect(self.on_arb_bakeButtonClicked) + # simplebake + self.ui.arb_simpleBakeAAssignButton.clicked.connect(self.on_arb_simpleBakeAAssignButtonClicked) + self.ui.arb_simpleBakeBAssignButton.clicked.connect(self.on_arb_simpleBakeBAssignButtonClicked) + + self.ui.arb_simpleBakeToLocButton.clicked.connect(self.on_arb_simpleBakeToLocButtonClicked) + self.ui.arb_simpleBakeAToBButton.clicked.connect(self.on_arb_simpleBakeAToBButtonClicked) + self.ui.arb_simpleBakeBToAButton.clicked.connect(self.on_arb_simpleBakeBToAButtonClicked) + # fk2ik + + self.ui.arb_fk2ikEndCtrlAssignButton.clicked.connect(self.on_arb_fk2ikEndCtrlAssignButtonClicked) + self.ui.arb_fk2ikCtrlRefAssignButton.clicked.connect(self.on_arb_fk2ikCtrlRefAssignButtonClicked) + self.ui.arb_fk2ikTwistAssignButton.clicked.connect(self.on_arb_fk2ikTwistAssignButtonClicked) + self.ui.arb_fk2ikTwistRefAssignButton.clicked.connect(self.on_arb_fk2ikTwistRefAssignButtonClicked) + self.ui.arb_fk2ikConvertButton.clicked.connect(self.on_arb_fk2ikConvertButtonClicked) + + # pinpos + self.ui.arb_pinPosPinCurButton.clicked.connect(self.on_arb_pinPosPinCurButtonClicked) + self.ui.arb_pinPosPinParentButton.clicked.connect(self.on_arb_pinPosPinParentButtonClicked) + + self.arb_syncRange() + + def on_arb_fromEditEdited(self): + print("setfrom:",eval(self.ui.arb_fromEdit.text())) + self.arb_fromtime = float(eval(self.ui.arb_fromEdit.text())) + + + def on_arb_toEditEdited(self): + print("setto:",eval(self.ui.arb_toEdit.text())) + self.arb_totime = float(eval(self.ui.arb_toEdit.text())) + # print("setto:",self.ui.arb_toEdit.text(),self.arb_totime) + + def arb_syncRange(self): + timerange = mocaphelperutility.getTimeRange() + print("syncrangeresult:",timerange) + self.ui.arb_fromEdit.setText(str(timerange[0])) + self.ui.arb_toEdit.setText(str(timerange[1])) + + + def arb_setFrom(self): + time = mocaphelperutility.getCurrentFrame() + self.ui.arb_fromEdit.setText(str(time)) + + def arb_setTo(self): + time = mocaphelperutility.getCurrentFrame() + self.ui.arb_toEdit.setText(str(time)) + + def on_arb_delInRangeButtonClicked(self): + objs = mocaphelperutility.getSelectedNodes() + print("delete inrange",self.arb_fromtime,self.arb_totime) + mocaphelperarbcore.deleteInrange(objs,self.arb_fromtime,self.arb_totime) + + def on_arb_delOutRangeButtonClicked(self): + if self.arb_fromtime>self.arb_totime: + raise Exception("fromtime > totime") + else: + objs = mocaphelperutility.getSelectedNodes() + print("delete outrange",self.arb_fromtime,self.arb_totime) + mocaphelperarbcore.deleteOutrange(objs,self.arb_fromtime,self.arb_totime) + + def on_arb_offsetFrameButtonClicked(self): + if self.arb_fromtime==self.arb_totime: + raise Exception("fromtime == totime") + else: + offsettime = self.arb_totime-self.arb_fromtime + objs = mocaphelperutility.getSelectedNodes() + mocaphelperarbcore.offsetFrame(objs,offsettime) + + def on_arb_stickyDelButtonClicked(self): + mocaphelperarbcore.stickyDelete(self) + + def on_arb_createLocButtonClicked(self): + objs = mocaphelperutility.getSelectedNodes() + if len(objs) == 0: + cmds.warning("No nodes selected") + return + pos = mocaphelperutility.getWorldPos(objs[0]) + rot = mocaphelperutility.getWorldRot(objs[0]) + roo = mocaphelperutility.getRotOrder(objs[0]) + mocaphelperarbcore.createLoc("c_loc_"+objs[0],pos,rot,roo) + + def on_arb_rePosButtonClicked(self): + objs = mocaphelperutility.getSelectedNodes() + if len(objs) == 0: + cmds.warning("No nodes selected") + return + mocaphelperarbcore.reposition(objs[0],objs[1]) + + def on_arb_pointConstraintButtonClicked(self): + objs = mocaphelperutility.getSelectedNodes() + ctrlobj = objs[:-1] + targetobj = objs[-1] + mocaphelperarbcore.createPointConstraint(ctrlobj,targetobj,maintainoffset=self.ui.arb_maintainOffsetCheckBox.isChecked()) + + def on_arb_orientConstraintButtonClicked(self): + objs = mocaphelperutility.getSelectedNodes() + ctrlobj = objs[:-1] + targetobj = objs[-1] + mocaphelperarbcore.createOrientConstraint(ctrlobj,targetobj,maintainoffset=self.ui.arb_maintainOffsetCheckBox.isChecked()) + + def on_arb_parentConstraintButtonClicked(self): + objs = mocaphelperutility.getSelectedNodes() + ctrlobj = objs[:-1] + targetobj = objs[-1] + mocaphelperarbcore.createParentConstraint(ctrlobj,targetobj,maintainoffset=self.ui.arb_maintainOffsetCheckBox.isChecked()) + + def on_arb_delConstraintButtonClicked(self): + objs = mocaphelperutility.getSelectedNodes() + mocaphelperarbcore.deleteAllConstraint(objs) + # for obj in objs: + # check = mocaphelperarbcore.checkConstraint(obj) + # if check != False: + # if check[0] != None: + # mocaphelperarbcore.deleteParentConstraint(check[0],obj) + # elif check[1] != None : + # mocaphelperarbcore.deletePointConstraint(check[1],obj) + # elif check[2] != None : + # mocaphelperarbcore.deleteOrientConstraint(check[2],obj) + # else: + # raise Exception("delconstraint error: constraint list out of range!") + # else: + # point = cmds.listRelatives(obj,children = True,type = "pointConstraint") + # orient = cmds.listRelatives(obj,children = True,type = "orientConstraint") + + + def on_arb_bakeButtonClicked(self): + mintime = self.arb_fromtime + maxtime = self.arb_totime + objs = mocaphelperutility.getSelectedNodes() + singleobj = objs[0] + check = mocaphelperarbcore.checkConstraint(singleobj) + smartbake = self.ui.arb_smartBakeCheckBox.isChecked() + if len(objs) == 1 and self.ui.arb_onlyBakeConstraintCheckBox.isChecked() and check != False: + type = "all" + if check[0] != None: + type = "parent" + elif check[1] != None : + type = "point" + elif check[2] != None : + type = "orient" + else: + type = "parent" + + if smartbake: + mocaphelperarbcore.smartbake(objs,mintime,maxtime,type) + else: + mocaphelperarbcore.bake(objs,mintime,maxtime,type) + + + + + + def on_arb_simpleBakeAAssignButtonClicked(self): + obj = mocaphelperutility.getSelectedNodes()[0] + self.ui.arb_simpleBakeAEdit.setText(obj) + + def on_arb_simpleBakeBAssignButtonClicked(self): + obj = mocaphelperutility.getSelectedNodes()[0] + self.ui.arb_simpleBakeBEdit.setText(obj) + + def on_arb_simpleBakeToLocButtonClicked(self): + mintime = self.arb_fromtime + maxtime = self.arb_totime + targetA = self.ui.arb_simpleBakeAEdit.text() + mocaphelperutility.openUndoChunk() + try: + + # obj = mocaphelperutility.getSelectedNodes()[0] + locA = mocaphelperarbcore.createLoc(targetA +"_loc") + mocaphelperarbcore.createParentConstraint(targetA,locA,False) + mocaphelperarbcore.bake(locA,mintime,maxtime,"all") + mocaphelperarbcore.deleteAllConstraint(locA) + self.ui.arb_simpleBakeBEdit.setText(locA[0]) + + finally: + mocaphelperutility.closeUndoChunk() + + + + def on_arb_simpleBakeAToBButtonClicked(self): + targetA = self.ui.arb_simpleBakeAEdit.text() + targetB = self.ui.arb_simpleBakeBEdit.text() + maintainoffset=self.ui.arb_maintainOffsetCheckBox.isChecked() + mocaphelperutility.openUndoChunk() + try: + check = mocaphelperarbcore.checkConstraint(targetB) + if check != False: + raise Exception("obj B has constraint already!") + else: + mintime = self.arb_fromtime + maxtime = self.arb_totime + type = getArbType(self) + mocaphelperarbcore.bakeAtoB(targetA,targetB,mintime,maxtime,type,maintainoffset,smart = self.ui.arb_smartBakeCheckBox.isChecked()) + + finally: + mocaphelperutility.closeUndoChunk() + + def on_arb_simpleBakeBToAButtonClicked(self): + targetA = self.ui.arb_simpleBakeAEdit.text() + targetB = self.ui.arb_simpleBakeBEdit.text() + maintainoffset=self.ui.arb_maintainOffsetCheckBox.isChecked() + mocaphelperutility.openUndoChunk() + try: + check = mocaphelperarbcore.checkConstraint(targetA) + if check != False: + raise Exception("obj A has constraint already!") + else: + mintime = self.arb_fromtime + maxtime = self.arb_totime + type = getArbType(self) + mocaphelperarbcore.bakeAtoB(targetB,targetA,mintime,maxtime,type,maintainoffset,smart = self.ui.arb_smartBakeCheckBox.isChecked()) + finally: + mocaphelperutility.closeUndoChunk() + + + def on_arb_fk2ikEndCtrlAssignButtonClicked(self): + obj = mocaphelperutility.getSelectedNodes()[0] + self.ui.arb_fk2ikEndCtrlEdit.setText(obj) + + def on_arb_fk2ikCtrlRefAssignButtonClicked(self): + obj = mocaphelperutility.getSelectedNodes()[0] + self.ui.arb_fk2ikCtrlRefEdit.setText(obj) + + def on_arb_fk2ikTwistAssignButtonClicked(self): + obj = mocaphelperutility.getSelectedNodes()[0] + self.ui.arb_fk2ikTwistEdit.setText(obj) + + def on_arb_fk2ikTwistRefAssignButtonClicked(self): + obj = mocaphelperutility.getSelectedNodes()[0] + self.ui.arb_fk2ikTwistRefEdit.setText(obj) + + def on_arb_fk2ikConvertButtonClicked(self): + mintime = self.arb_fromtime + maxtime = self.arb_totime + maintainoffset=self.ui.arb_maintainOffsetCheckBox.isChecked() + smartbake = self.ui.arb_smartBakeCheckBox.isChecked() + endctrl = self.ui.arb_fk2ikEndCtrlEdit.text() + ctrlref = self.ui.arb_fk2ikCtrlRefEdit.text() + twist = self.ui.arb_fk2ikTwistEdit.text() + twistref = self.ui.arb_fk2ikTwistRefEdit.text() + mocaphelperutility.openUndoChunk() + try: + if mocaphelperutility.objExist(endctrl) == False: + endctrl = "" + if mocaphelperutility.objExist(ctrlref) == False: + ctrlref = "" + if mocaphelperutility.objExist(twist) == False: + twist = "" + if mocaphelperutility.objExist(twistref) == False: + twistref = "" + + check = mocaphelperarbcore.checkConstraint(endctrl) + if check != False: + raise Exception("endctrl has constraint already!") + if mocaphelperutility.objExist(twist) == True: + check = mocaphelperarbcore.checkConstraint(twist) + if check != False: + raise Exception("twist has constraint already!") + + if endctrl== "" or ctrlref =="": + raise Exception("endctrl or ctrlref not set or not exist") + else: + if twist == "" or twistref == "": + + mocaphelperarbcore.bakeAtoB(ctrlref,endctrl,mintime,maxtime,"parent",maintainoffset,smartbake) + else: + mocaphelperarbcore.bakeAtoB(ctrlref,endctrl,mintime,maxtime,"parent",maintainoffset,smartbake) + + twistpos = mocaphelperutility.getWorldPos(twist) + twistrot = mocaphelperutility.getWorldRot(twist) + twistroo = mocaphelperutility.getRotOrder(twist) + twistloc = mocaphelperarbcore.createLoc("temp_twist_loc",twistpos,twistrot,twistroo) + cons1 = mocaphelperarbcore.createParentConstraint(twistref,twistloc,True) + cons2 = mocaphelperarbcore.createPointConstraint(twistloc,twist,False) + if smartbake: + mocaphelperarbcore.smartbake(twist,mintime,maxtime,"point") + else: + mocaphelperarbcore.bake(twist,mintime,maxtime,"point") + + mocaphelperutility.deleteObj(cons1) + mocaphelperutility.deleteObj(cons2) + mocaphelperutility.deleteObj(twistloc) + + finally: + mocaphelperutility.closeUndoChunk() + + def on_arb_pinPosPinCurButtonClicked(self): + mintime = self.arb_fromtime + maxtime = self.arb_totime + mocaphelperutility.openUndoChunk() + try: + + objs = mocaphelperutility.getSelectedNodes() + mocaphelperarbcore.pinCurPos(objs,mintime,maxtime) + finally: + mocaphelperutility.closeUndoChunk() + + def on_arb_pinPosPinParentButtonClicked(self): + mintime = self.arb_fromtime + maxtime = self.arb_totime + mocaphelperutility.openUndoChunk() + try: + objs = mocaphelperutility.getSelectedNodes() + mocaphelperarbcore.pinParentPos(objs,mintime,maxtime) + finally: + mocaphelperutility.closeUndoChunk() + + + +def getArbType(obj): + index = obj.ui.arb_simpleBakeTypeComboBox.currentIndex() + # type = mocaphelperutility.unicodeToStr(type) + # enList = ["all","parent","point","orient","onlyscale"] + # cnList = ["平移旋转","平移","旋转","所有属性"] + type = None + if index == 0: + type = "all" + + elif index == 1: + type = "parent" + + elif index == 2: + type = "point" + + elif index == 3: + type = "orient" + + else: + raise Exception("id out of range") + print("index is now:",index,".type is now:",type) + + return type + + +def getReActOnType(obj): + index = obj.ui.re_actTypeComboBox.currentIndex() + + type = None + if index == 0: + type = "all" + + elif index == 1: + type = "nurbsCurve" + + + + print("index is now:",index,".type is now:",type) + return type \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/bin/mocaphelperui.ui b/Scripts/Animation/MotionCapHelper/bin/mocaphelperui.ui new file mode 100644 index 0000000..989d382 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/mocaphelperui.ui @@ -0,0 +1,1512 @@ + + + moCapHelper + + + + 0 + 0 + 322 + 344 + + + + + Arial + 10 + 50 + false + + + + uiwindow + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Arial + 10 + 50 + false + + + + Qt::DefaultContextMenu + + + QTabWidget::West + + + QTabWidget::Rounded + + + 1 + + + false + + + false + + + + + Arial + 10 + 50 + false + + + + SAC/FA/RE + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + Arial + 10 + 50 + false + + + + 2 + + + true + + + + + Arial + 10 + 50 + false + false + true + + + + smoothAnim + + + + 3 + + + 3 + + + 3 + + + 3 + + + 5 + + + + + iteration: + + + + + + + 1 + + + + + + + intensity: + + + + + + + 1.0 + + + + + + + use time unit + + + true + + + + + + + timeunit: + + + + + + + 5.0 + + + + + + + perform on: + + + + + + + + curves + + + + + selected keys + + + + + + + + smooth method: + + + + + + + 2 + + + + 3linear + + + + + 3bell + + + + + 3ham + + + + + 5quad + + + + + 5bell + + + + + 5ham + + + + + + + + true + + + smooth + + + + + + + + + Arial + 10 + 50 + false + true + + + + frameAlign + + + + 5 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + 5 + + + + + fast record selected + + + + + + + + + 5 + + + + + ref obj: + + + + + + + true + + + true + + + + + + + assign obj + + + + + + + + + 5 + + + + + align selection + + + + + + + + + + + Arial + 10 + 50 + false + true + + + + regularExp + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 5 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + Arial + 10 + 50 + false + true + + + + preset: + + + + + + + true + + + open file + + + + + + + true + + + record + + + + + + + true + + + reload + + + + + + + + + 3 + + + 3 + + + 3 + + + 3 + + + 5 + + + + + true + + + + 0 + 0 + + + + assign + + + + + + + + + + + + + + case check + + + + + + + children check + + + true + + + + + + + + + 5 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + prefix: + + + + + + + + + + + + + + + + 5 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + name: + + + + + + + default + + + + + + + + + 5 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 70 + 0 + + + + expression: + + + + + + + . + + + + + + + + + + + + 0 + 0 + + + + act on : + + + + + + + + 0 + 0 + + + + + 208 + 16777215 + + + + 1 + + + + all + + + + + ctrl(nurbs) + + + + + + + + + + true + + + 0 + + + + utility + + + + 0 + + + 0 + + + 0 + + + 0 + + + 5 + + + + + true + + + select child + + + + + + + true + + + select main ctrls + + + + + + + true + + + select child ctrl + + + + + + + true + + + select visible child ctrl + + + + + + + + expression + + + + 0 + + + 0 + + + 0 + + + 0 + + + 5 + + + + + true + + + exp select + + + + + + + true + + + extract layer info + + + + + + + + layercreate + + + + 0 + + + 0 + + + 0 + + + 0 + + + 5 + + + + + true + + + exp create layer + + + + + + + true + + + selected create layer + + + + + + + + + + + + + + + + ARB + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + maintain offset + + + true + + + + + + + smart bake + + + + + + + + + + 0 + + + + + Arial + 10 + 50 + false + + + + basic + + + + 0 + + + 0 + + + 0 + + + 0 + + + 5 + + + + + orient constraint + + + + + + + reposition + + + + + + + 1 + + + Qt::Horizontal + + + + + + + parent constraint + + + + + + + create locator + + + + + + + delete constraint + + + + + + + bake key + + + + + + + point constraint + + + + + + + only bake constraint + + + true + + + false + + + + + + + + simpleBake + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 45 + 0 + + + + target A: + + + + + + + true + + + + + + + assign + + + + + + + + + + + + 45 + 0 + + + + target B: + + + + + + + true + + + + + + + assign + + + + + + + + + + + parent + + + 1 + + + 4 + + + + all + + + + + parent + + + + + point + + + + + orient + + + + + + + + bake A into B + + + + + + + bake B into A + + + + + + + + + bake A into new locator + + + + + + + + fk2ik + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 45 + 0 + + + + endctrl: + + + + + + + true + + + + + + + assign + + + + + + + + + + + + 45 + 0 + + + + ctrlref: + + + + + + + true + + + + + + + assign + + + + + + + + + + + + 8 + 0 + + + + + 45 + 0 + + + + + 4 + 0 + + + + twist: + + + + + + + + 0 + 0 + + + + true + + + + + + + + 0 + 0 + + + + assign + + + + + + + + + + + twistref: + + + + + + + true + + + + + + + assign + + + + + + + + + convert it ! + + + + + + + + pinPos + + + + + + pin to current position + + + + + + + parent to first obj position + + + + + + + + + + + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 5 + + + + + set from + + + + + + + sync range + + + + + + + set to + + + + + + + + + 5 + + + + + from: + + + + + + + + + + to: + + + + + + + + + + + + 5 + + + + + delete inrange + + + + + + + delete outrange + + + + + + + + + 5 + + + + + offset frame + + + + + + + + 100 + 0 + + + + sticky delete + + + + + + + + + + + + + + + + + + + + diff --git a/Scripts/Animation/MotionCapHelper/bin/mocaphelperutility.py b/Scripts/Animation/MotionCapHelper/bin/mocaphelperutility.py new file mode 100644 index 0000000..c73df80 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/mocaphelperutility.py @@ -0,0 +1,139 @@ +import maya.api.OpenMaya as om +import maya.cmds as cmds +import maya.mel as mel + +import os + +from sys import version as sysver + +def getPythonVersion(): + return int(sysver[0]) + + +def selectNodes(nodelist,visible = False): + cmds.select(cl=True) + if visible == True: + cmds.select(nodelist,vis = True) + else: + cmds.select(nodelist) + +def addSelectNodes(nodelist): + for node in nodelist: + cmds.select(node,add = True) + +def orderSelectNodes(nodelist): + cmds.select(cl=True) + for node in nodelist: + cmds.select(node,add = True) + +def getSelectedNodes(nodetype = "all",longname = True): + selectednodes = cmds.ls(selection = True,type = "dagNode",long = longname) + if nodetype != "all": + selectednodes = filterCertainTypeInList(selectednodes,nodetype) + return selectednodes + +def getChildNodes(parents,nodetype = "all",combine = True): + list = [] + + for parent in parents: + childlist = cmds.listRelatives(parent, ad=True, type = "transform",fullPath = True) + + if childlist == None: + continue + else: + if nodetype == "all": + list += childlist + + else: + list += filterCertainTypeInList(childlist,nodetype) + + if list != None: + if combine == True: + return list+parents + else: + return list + else: + raise Exception("error when getting child") + +def filterCertainTypeInList(list,type = "nurbsCurve"): + matchlist = [] + for node in list: + if getShapeType(node) == type: + matchlist.append(node) + + return matchlist + +def getShapeType( node = "" ): + shape = cmds.listRelatives(node,shapes = True,fullPath = True) + if shape != None: + shape = shape[0] + return cmds.nodeType(shape) + else: + return None + + +def objExist(obj): + if len(cmds.ls(obj)) == 0: + return False + else: + return True + +def getCurrentFrame(): + return cmds.currentTime(q = True) + +def getTimeRange(local = False): + #local means time slider range in maya time line + if local == True: + return cmds.playbackOptions(q = True,min = True),cmds.playbackOptions(q = True,max = True) + else: + return cmds.playbackOptions(q = True,ast = True),cmds.playbackOptions(q = True,aet = True) + + +def getWorldPos(obj): + return cmds.xform(obj,q = True,ws = True,t = True) + +def getWorldRot(obj): + return cmds.xform(obj,q = True,ws = True,ro = True) + +def getRotOrder(obj): + return cmds.xform(obj,q = True,roo = True) + +def setWorldPos(obj,cd = (0.0,0.0,0.0)): + cmds.xform(obj,ws = True,t = cd) + +def setWorldRot(obj,rot = (0.0,0.0,0.0),targetRotOrder = "xyz"): + originOrder = cmds.xform(obj,q = True,roo = True) + if originOrder == targetRotOrder: + cmds.xform(obj,ws = True,ro = rot) + else: + if targetRotOrder in ['xyz', 'yzx','zxy','xzy','yxz','zyx']: + cmds.xform(obj,ws = True,ro = rot,roo = targetRotOrder) + cmds.xform(obj,ws = True,roo = originOrder) + else: + raise Exception("rot order does not exist.") + +def cutKey(objlist,mintime,maxtime,insertside = False): + if insertside: + cmds.cutKey(objlist,t =(mintime,maxtime),cl = True,option = "curve") + else: + cmds.cutKey(objlist,t =(mintime,maxtime),cl = True) + +def autoKetTangent(objs,mintime,maxtime): + cmds.keyTangent(objs,e = True,itt="auto", ott="auto", t=(mintime,maxtime)) + + +def deleteObj(obj): + cmds.delete(obj) + +def unicodeToStr(str): + return repr(str)[2:-1] + +def getDir(): + dir = os.path.dirname(__file__) + return dir + +def openUndoChunk(): + return cmds.undoInfo(openChunk=True) + +def closeUndoChunk(): + return cmds.undoInfo(closeChunk=True) \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/bin/re_exp_preset.txt b/Scripts/Animation/MotionCapHelper/bin/re_exp_preset.txt new file mode 100644 index 0000000..8cf1442 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/re_exp_preset.txt @@ -0,0 +1,13 @@ +torso +.ctrl.torso. +torso +.torso.ctrl. +l_arm +.ctrl.l.arm. +l_hand;l_arm +.:FKIndexFinger2_L|.:FKIndexFinger1_L|.:FKIndexFinger3_L|.:FKMiddleFinger1_L|.:FKMiddleFinger2_L|.:FKMiddleFinger3_L|.:FKRingFinger1_L|.:FKPinkyFinger1_L|.:FKPinkyFinger2_L|.:FKPinkyFinger3_L|.:FKThumbFinger2_L|.:FKThumbFinger3_L|.:FKThumbFinger1_L;.:FKElbow_L|.:FKWrist_L|.:FKShoulder_L + + +. + +. \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/bin/re_exp_preset_backup.txt b/Scripts/Animation/MotionCapHelper/bin/re_exp_preset_backup.txt new file mode 100644 index 0000000..e77f885 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/re_exp_preset_backup.txt @@ -0,0 +1,8 @@ +torso +.ctrl.torso. +torso +.torso.ctrl. +l_arm +.ctrl.l.arm. +r_arm +.l.arm.ctrl. \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/bin/ui.ts b/Scripts/Animation/MotionCapHelper/bin/ui.ts new file mode 100644 index 0000000..9760822 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/ui.ts @@ -0,0 +1,500 @@ + + + + + moCapHelper + + Form + 形状 + + + + SAC/FA/RE + SAC/FA/DLC + 平滑曲线/对齐帧/正则 + + + + smoothAnim + 平滑曲线 + + + + iteration: + 迭代次数: + + + + 1 + + + + + intensity: + 单次强度: + + + + 1.0 + + + + + use time unit + 使用时间单位 + + + + timeunit: + 时间单位: + + + + 5.0 + + + + + perform on: + 作用于: + + + + curves + + + + + selected keys + + + + + smooth method: + 平滑方法: + + + + 3linear + + + + + 3bell + + + + + 3ham + + + + + 5quad + + + + + 5bell + + + + + 5ham + + + + + smooth + 平滑 + + + + frameAlign + 对齐帧 + + + + fast record selected + 快速记录选择物体的帧信息 + + + + ref obj: + 参考物体: + + + + assign obj + 指定 + + + + align selection + 对齐选择 + + + layerCreate + 创建显示层 + + + + preset: + 预设: + + + + open file + 打开文件 + + + + record + 记录当前 + + + + reload + 重载文件 + + + + + + + + + + assign + 指定 + + + + case check + 大小写检测 + + + + prefix: + 前缀: + + + layer name: + 层名字: + + + + default + + + + + expression: + 正则表达式: + + + + . + + + + + utility + 工具 + + + select child ctrls + 选择子控制器 + + + + selected create layer + 依据选集创建层 + + + + expression + 表达式工具 + + + + exp select + 表达式选择 + + + + exp create layer + 表达式创建层 + + + + extract layer info + 提取当前层信息 + + + + ARB + 动画重建 + + + animRebuild + 动画重建 + + + + basic + 基础工具 + + + + orient constraint + 旋转约束 + + + + delete constraint + 删除约束 + + + + parent constraint + 父子约束 + + + + bake key + 烘焙帧 + + + + create locator + 创建定位器 + + + + maintain offset + 保持偏移 + + + + smart bake + 智能烘焙(慎用) + + + + point constraint + 点约束 + + + + reposition + 重新定位 + + + + only bake constraint + 尝试仅烘焙约束的通道 + + + + simpleBake + 简易烘焙 + + + + target A: + 目标A: + + + + target B: + 目标B: + + + + + all + 所有属性 + + + + + parent + 平移旋转 + + + + uiwindow + 窗口 + + + + regularExp + 正则表达式 + + + + children check + 检查子级 + + + + name: + 名称: + + + + act on : + 作用于: + + + + ctrl(nurbs) + 控制器(nurbs曲线) + + + + select child + 选择子级 + + + + select main ctrls + 选择大圈 + + + + select child ctrl + 选择子控制器 + + + + select visible child ctrl + 选择可见子控制器 + + + + layercreate + 创建显示层 + + + + point + 平移 + + + + orient + 旋转 + + + + bake A into B + 烘焙A到B + + + + bake B into A + 烘焙B到A + + + + bake A into new locator + 将A烘焙到空定位器 + + + + fk2ik + fk转ik + + + + endctrl: + 末端: + + + + ctrlref: + 末端参考: + + + + twist: + 方向: + + + + twistref: + 方向参考: + + + + convert it ! + 转换! + + + + pinPos + 钉位 + + + + pin to current position + 钉在当前帧位置 + + + + parent to first obj position + 快速烘焙父子约束效果 + + + + set from + 设置:从 + + + + sync range + 同步范围 + + + + set to + 设置:到 + + + + from: + 从: + + + + to: + 到: + + + + delete inrange + 删除范围内 + + + + delete outrange + 删除范围外 + + + + offset frame + 偏移帧 + + + + sticky delete + 提取帧 + + + language + 改变语言 + + + diff --git a/Scripts/Animation/MotionCapHelper/bin/ui_CN.qm b/Scripts/Animation/MotionCapHelper/bin/ui_CN.qm new file mode 100644 index 0000000..6e51fe7 Binary files /dev/null and b/Scripts/Animation/MotionCapHelper/bin/ui_CN.qm differ diff --git a/Scripts/Animation/MotionCapHelper/bin/安装__install.txt b/Scripts/Animation/MotionCapHelper/bin/安装__install.txt new file mode 100644 index 0000000..2092b8f --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/bin/安装__install.txt @@ -0,0 +1,23 @@ +安装: +将这个文件夹的目录添加到到对应版本的maya.env文件中的MAYA_PLUG_IN_PATH这一行。 +例子: +MAYA_PLUG_IN_PATH =C:\Users\(你的用户名)\Documents\maya\2018\plug-ins\MotionCapHelper + +启动maya,在maya里打开插件管理器,如果出现了这个插件的名字mocaphelper.py就说明安装 +顺利,只要勾选这一个文件的加载就好,不需要勾选其他的比如mocaphelperrecore.py等等文件 + +勾选后maya会载入插件,之后使用mel或者python的moCapHelper_showUi -lan "CN"指令打开插件的中文界面 +或者moCapHelper_showUi打开英文界面(更推荐一点,因为懒得更新汉化) +可以把这个指令新建一个工具架的工具,方面后面调用 + +Installation: +Add the directory of this folder to the in the corresponding version of the maya.env file. +Example: +MAYA_PLUG_IN_PATH =C:\Users\\Documents\maya\2018\plug-ins\MotionCapHelper + +Start Maya, open the plugin manager in Maya, you should see a list of mocaphelper.py/mocaphelperui.py..... + just check the load state of "mocaphelper.py" file only, no need to check any other files such as mocaphelperrecore.py.... + +After checking this box, maya will load the plug-in, and then you can use "moCapHelper_showUi" command in mel or python to open the interface of this plug-in. + +You can create a new shelf tool with this command. \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/extra_scripts/plugin interact(与插件交互).py b/Scripts/Animation/MotionCapHelper/extra_scripts/plugin interact(与插件交互).py new file mode 100644 index 0000000..cb91b24 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/extra_scripts/plugin interact(与插件交互).py @@ -0,0 +1,12 @@ +''' +this is an example of how you set hotkey or use scripts to drive this plugin's cmds. +if you know python and pyside2,go see codes and .ui file to find what you need. +the most simple usage is to drive a qt pushbutton.(which is exactly what you did when you hit a button in plugin ui.) +and you might need QLineEdit.setText() to set str in those text field. +anyway,go check qt wiki :https://doc.qt.io/qtforpython-5/PySide2/QtWidgets/QWidget.html#qwidget +have fun scripting. +''' + +import maya.cmds as cmds + +cmds.moCapHelper_eval(s ="ui.arb_stickyDelButton.click()" ) \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/extra_scripts/select display layer objs(选择显示层物体).py b/Scripts/Animation/MotionCapHelper/extra_scripts/select display layer objs(选择显示层物体).py new file mode 100644 index 0000000..afd80ea --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/extra_scripts/select display layer objs(选择显示层物体).py @@ -0,0 +1,15 @@ +import maya.cmds as cmds +import maya.mel as mel + +curlayer = cmds.editDisplayLayerGlobals( query=True, cdl=True ) +if curlayer =="defaultLayer" : + raise Exception("using default layer!") +else: + slist = cmds.editDisplayLayerMembers(curlayer,query=True ) + print(slist) + cmds.select( clear=True ) + cmds.select(slist, visible=True ) + + mel.eval("syncChannelBoxFcurveEd;") + mel.eval("syncChannelBoxFcurveEd;") + mel.eval("syncChannelBoxFcurveEd;") diff --git a/Scripts/Animation/epic_pose_wrangler/view/__init__.py b/Scripts/Animation/epic_pose_wrangler/view/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Scripts/Animation/epic_pose_wrangler/view/log_widget.py b/Scripts/Animation/epic_pose_wrangler/view/log_widget.py new file mode 100644 index 0000000..a149327 --- /dev/null +++ b/Scripts/Animation/epic_pose_wrangler/view/log_widget.py @@ -0,0 +1,140 @@ +# Copyright Epic Games, Inc. All Rights Reserved. + +import logging + +from PySide2 import QtWidgets, QtCore, QtGui + + +class LogWidget(logging.Handler): + """ + Custom Log Handler with embedded QtWidgets.QDockWidget + """ + + def __init__(self): + super(LogWidget, self).__init__() + # Set default formatting + self.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s', '%Y-%m-%d %H:%M:%S')) + # Create dock + self._log_dock = QtWidgets.QDockWidget() + # Only allow it to be parented to the bottom + self._log_dock.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea) + + central_widget = QtWidgets.QWidget() + central_widget.setContentsMargins(0, 0, 0, 0) + self._log_dock.setWidget(central_widget) + main_layout = QtWidgets.QVBoxLayout() + main_layout.setContentsMargins(0, 0, 0, 0) + central_widget.setLayout(main_layout) + + toolbar_layout = QtWidgets.QHBoxLayout() + toolbar_layout.setContentsMargins(0, 0, 0, 0) + main_layout.addLayout(toolbar_layout) + + self._output_log = QtWidgets.QListWidget() + self._output_log.setProperty("Log", True) + self._output_log.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self._output_log.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self._output_log.customContextMenuRequested.connect(self._show_context_menu) + main_layout.addWidget(self._output_log) + + icon_size_px = 25 + icon_size = QtCore.QSize(icon_size_px, icon_size_px) + btn_size_px = 30 + btn_size = QtCore.QSize(btn_size_px, btn_size_px) + + self._debug_btn = QtWidgets.QPushButton() + self._debug_btn.setIcon(QtGui.QIcon(QtGui.QPixmap("PoseWrangler:debug.png"))) + self._debug_btn.setIconSize(icon_size) + self._debug_btn.setProperty("LogButton", True) + self._debug_btn.setCheckable(True) + self._debug_btn.setChecked(False) + self._debug_btn.setFixedSize(btn_size) + self._debug_btn.clicked.connect(self._refresh_log) + toolbar_layout.addWidget(self._debug_btn) + + self._info_btn = QtWidgets.QPushButton() + self._info_btn.setIcon(QtGui.QIcon(QtGui.QPixmap("PoseWrangler:info.png"))) + self._info_btn.setIconSize(icon_size) + self._info_btn.setProperty("LogButton", True) + self._info_btn.setCheckable(True) + self._info_btn.setChecked(True) + self._info_btn.setFixedSize(btn_size) + self._info_btn.clicked.connect(self._refresh_log) + toolbar_layout.addWidget(self._info_btn) + + self._warning_btn = QtWidgets.QPushButton() + self._warning_btn.setIcon(QtGui.QIcon(QtGui.QPixmap("PoseWrangler:warning.png"))) + self._warning_btn.setIconSize(icon_size) + self._warning_btn.setProperty("LogButton", True) + self._warning_btn.setCheckable(True) + self._warning_btn.setChecked(True) + self._warning_btn.setFixedSize(btn_size) + self._warning_btn.clicked.connect(self._refresh_log) + toolbar_layout.addWidget(self._warning_btn) + + self._error_btn = QtWidgets.QPushButton() + self._error_btn.setIcon(QtGui.QIcon(QtGui.QPixmap("PoseWrangler:error.png"))) + self._error_btn.setIconSize(icon_size) + self._error_btn.setProperty("LogButton", True) + self._error_btn.setCheckable(True) + self._error_btn.setChecked(True) + self._error_btn.setFixedSize(btn_size) + self._error_btn.clicked.connect(self._refresh_log) + toolbar_layout.addWidget(self._error_btn) + toolbar_layout.addStretch(0) + + self._clear_log_btn = QtWidgets.QPushButton() + self._clear_log_btn.setIcon(QtGui.QIcon(QtGui.QPixmap("PoseWrangler:clear.png"))) + self._clear_log_btn.setIconSize(icon_size) + self._clear_log_btn.setProperty("LogButton", True) + self._clear_log_btn.setFixedSize(btn_size) + self._clear_log_btn.clicked.connect(self._output_log.clear) + toolbar_layout.addWidget(self._clear_log_btn) + + @property + def log_dock(self): + return self._log_dock + + def emit(self, record): + level_colour_map = { + logging.DEBUG: QtGui.QColor(91, 192, 222), + logging.INFO: QtGui.QColor(247, 247, 247), + logging.WARNING: QtGui.QColor(240, 173, 78), + logging.ERROR: QtGui.QColor(217, 83, 79) + } + msg = self.format(record) + item = QtWidgets.QListWidgetItem(msg) + item.setData(QtCore.Qt.UserRole, record.levelno) + item.setForeground(QtGui.QBrush(level_colour_map[record.levelno])) + self._output_log.addItem(item) + self._refresh_log() + + def _refresh_log(self): + level_btn_map = { + logging.DEBUG: self._debug_btn, + logging.INFO: self._info_btn, + logging.WARNING: self._warning_btn, + logging.ERROR: self._error_btn + } + for i in range(0, self._output_log.count()): + item = self._output_log.item(i) + data = item.data(QtCore.Qt.UserRole) + item.setHidden(not level_btn_map[data].isChecked()) + + def _copy_to_clipboard(self): + clipboard_text = "" + for item in self._output_log.selectedItems(): + text = item.text() + clipboard_text += text + if text: + clipboard_text += "\n" + clipboard = QtWidgets.QApplication.clipboard() + clipboard.setText(clipboard_text) + + def _show_context_menu(self, pos): + if not self._output_log.selectedItems(): + return + menu = QtWidgets.QMenu(parent=self._output_log) + copy_action = menu.addAction("Copy") + copy_action.triggered.connect(self._copy_to_clipboard) + menu.exec_(QtGui.QCursor.pos()) \ No newline at end of file diff --git a/Scripts/Animation/keyframe_pro/__init__.py b/Scripts/Animation/keyframe_pro/__init__.py new file mode 100644 index 0000000..078c0f7 --- /dev/null +++ b/Scripts/Animation/keyframe_pro/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from . import keyframe_pro +from . import keyframe_pro_maya + diff --git a/Scripts/Animation/keyframe_pro/keyframe_pro/__init__.py b/Scripts/Animation/keyframe_pro/keyframe_pro/__init__.py new file mode 100644 index 0000000..5175b1d --- /dev/null +++ b/Scripts/Animation/keyframe_pro/keyframe_pro/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from . import * diff --git a/Scripts/Animation/keyframe_pro/keyframe_pro/keyframe_pro_client.py b/Scripts/Animation/keyframe_pro/keyframe_pro/keyframe_pro_client.py new file mode 100644 index 0000000..08c057f --- /dev/null +++ b/Scripts/Animation/keyframe_pro/keyframe_pro/keyframe_pro_client.py @@ -0,0 +1,686 @@ +import json +import socket +import time +import traceback + + +class KeyframeProClient(object): + """ + Client API for Keyframe Pro + """ + + API_VERSION = "1.0.1" + + PORT = 18181 + + HEADER_SIZE = 10 + + kpro_socket = None + kpro_initialized = False + + def __init__(self, timeout=2): + """ + """ + self.timeout = timeout + self.show_timeout_errors = True + + def connect(self, port=-1, display_errors=True): + """ + Create a connection with the application. + + :param port: The port Keyframe Pro is listening on. + :return: True if connection was successful (or already exists). False otherwise. + """ + if self.is_connected(): + return True + + if port < 0: + port = self.__class__.PORT + + try: + self.__class__.kpro_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.__class__.kpro_socket.connect(("localhost", port)) + self.__class__.kpro_socket.setblocking(0) + + self.__class__.kpro_initialized = False + + except: + self.__class__.kpro_socket = None + if display_errors: + traceback.print_exc() + return False + + return True + + def disconnect(self): + """ + Disconnect from the application. + + :return: True if the existing connection was disconnect successfully. False otherwise. + """ + result = False + if self.__class__.kpro_socket: + try: + self.__class__.kpro_socket.close() + result = True + except: + traceback.print_exc() + + self.__class__.kpro_socket = None + self.__class__.kpro_initialized = False + + return result + + def is_connected(self): + """ + Test if a connection exists. + + :return: True if a connection exists. False otherwise. + """ + self.show_timeout_errors = False + connected = self.__class__.kpro_socket and self.echo("conn") + self.show_timeout_errors = True + + if connected: + return True + else: + self.disconnect() + return False + + def send(self, cmd): + """ + Send a command to the application and wait for a processed reply. + + :param cmd: Dictionary containing the cmd and args + :return: Variable depending on cmd. + """ + json_cmd = json.dumps(cmd) + + message = [] + message.append("{:10d}".format(len(json_cmd))) # header + message.append(json_cmd) + + try: + self.__class__.kpro_socket.sendall("".join(message)) + except: + traceback.print_exc() + return None + + return self.recv(cmd) + + def recv(self, cmd): + """ + Wait for the application to reply to a previously sent cmd. + + :param cmd: Dictionary containing the cmd and args + :return: Variable depending on cmd. + """ + total_data = [] + data = "" + reply_length = 0 + bytes_remaining = self.__class__.HEADER_SIZE + + begin = time.time() + while time.time() - begin < self.timeout: + + try: + data = self.__class__.kpro_socket.recv(bytes_remaining) + except: + time.sleep(0.01) + continue + + if data: + total_data.append(data) + + bytes_remaining -= len(data) + if(bytes_remaining <= 0): + + if reply_length == 0: + header = "".join(total_data) + reply_length = int(header) + bytes_remaining = reply_length + total_data = [] + else: + reply_json = "".join(total_data) + return json.loads(reply_json) + + if self.show_timeout_errors: + if "cmd" in cmd.keys(): + cmd_name = cmd["cmd"] + print('[KPRO][ERROR] "{0}" timed out.'.format(cmd_name)) + else: + print('[KPRO][ERROR] Unknown cmd timed out.') + + return None + + def is_valid_reply(self, reply): + """ + Test if a reply from the application is valid. Output any messages. + + :param reply: Dictionary containing the response to a cmd + :return: True if valid. False otherwise. + """ + if not reply: + return False + + if not reply["success"]: + print('[KPRO][ERROR] "{0}" failed: {1}'.format(reply["cmd"], reply["msg"])) + return False + + return True + + def initialize(self): + """ + One time initialization required by the application. + + :return: True if successfully initalized. False otherwise. + """ + if self.__class__.kpro_initialized: + return True + + cmd = { + "cmd": "initialize", + "api_version": self.__class__.API_VERSION + } + + reply = self.send(cmd) + if reply and reply["success"] and reply["result"] == 0: + self.__class__.kpro_initialized = True + return True + else: + print('[KPRO][ERROR] Initialization failed: {0}'.format(reply["msg"])) + self.disconnect() + return False + + # ------------------------------------------------------------------ + # COMMANDS + # ------------------------------------------------------------------ + def echo(self, text): + """ + Get an echo response from the application. + + :param text: The string to be sent to the application. + :return: A string containing the text on success. None otherwise. + """ + cmd = { + "cmd": "echo", + "text": text + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["result"] + else: + return None + + def get_config(self): + """ + Get the configuration settings for the application. + + :return: Dictionary containing the config values. + """ + cmd = { + "cmd": "get_config" + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply + else: + return None + + def new_project(self, empty=False): + """ + Create a new project. + + :param empty: Create an empty project. + :return: True if new project created. False otherwise. + """ + cmd = { + "cmd": "new_project", + "empty": empty + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return True + else: + return False + + def open_project(self, file_path): + """ + Open an existing project. + + :param file_path: Path to the project. + :return: True if the project is opened. False otherwise. + """ + cmd = { + "cmd": "open_project", + "file_path": file_path + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return True + else: + return False + + def save_project(self, file_path): + """ + Save the current project. + + :param file_path: Path to the project. + :return: True if the project is saved. False otherwise. + """ + cmd = { + "cmd": "save_project", + "file_path": file_path + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return True + else: + return False + + def get_project_path(self): + """ + Get the path to the current project. + + :return: The path to the project. None if there is an error. + """ + cmd = { + "cmd": "get_project_path" + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["project_path"] + else: + return None + + def import_file(self, file_path, name="", range_start=-1, range_end=-1, parent_id=""): + """ + Import a source file into the project. + + :param file_path: Path to the source + :param name: Name of the source + :param range_start: Range start frame + :param range_end: Range end frame + :param parent_id: Parent folder of the source + :return: Dictionary representing the source object. None on error. + """ + cmd = { + "cmd": "import_file", + "file_path": file_path, + "name": name, + "range_start": range_start, + "range_end": range_end, + "parent_id": parent_id + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["source"] + else: + return None + + def add_folder(self, name="", parent_id=""): + """ + Add a folder to the project. + + :param name: Name of the folder + :param parent_id: Parent folder of the folder + :return: Dictionary representing the folder object. None on error. + """ + cmd = { + "cmd": "add_folder", + "name": name, + "parent_id": parent_id + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["folder"] + else: + return None + + def add_timeline(self, name="", parent_id=""): + """ + Add a timeline to the project. + + :param name: Name of the timeline + :param parent_id: Parent folder of the timeline + :return: Dictionary representing the timeline object. None on error. + """ + cmd = { + "cmd": "add_timeline", + "name": name, + "parent_id": parent_id + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["timeline"] + else: + return None + + def remove(self, id, force=False): + """ + Remove a folder, timeline or source from the project. + + :param id: ID for the object to be removed. + :param force: (Source only) Force removal if the source is in use in one or more timelines. + :return: True on successful removal. False otherwise. + """ + cmd = { + "cmd": "remove", + "id": id, + "force": force + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return True + else: + return False + + def update(self, obj): + """ + Update the folder, timeline or source object with the values contained in the obj dict. + Editable obj values that are different will be modified. + + :param obj: Dictionary representing the object to be updated. + :return: Dictionary representing the updated object. None on error. + """ + cmd = { + "cmd": "update", + "object": obj + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["updated"] + else: + return None + + def insert_element_in_timeline(self, source_id, timeline_id, index=-1): + """ + Insert a source element into a timeline. + + :param source_id: ID of the source to be added to the timeline. + :param timeline_id: ID of the timeline. + :param index: Index to insert source at. Inserted at the end if index is out of range. + :return: True on successful insertion. False otherwise. + """ + cmd = { + "cmd": "insert_element_in_timeline", + "source_id": source_id, + "timeline_id": timeline_id, + "index": index + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return True + else: + return False + + def remove_element_from_timeline(self, timeline_id, index): + """ + Remove a source element from a timeline. + + :param timeline_id: ID of the timeline. + :param index: Index of the element to be removed. + :return: True on successful removal. False otherwise. + """ + cmd = { + "cmd": "remove_element_from_timeline", + "timeline_id": timeline_id, + "index": index + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return True + else: + return False + + def get_timeline_elements(self, timeline_id): + """ + Get an ordered list of the sources in a timeline. + + :param timeline_id: ID of the timeline. + :return: An ordered list of dictionaries representing the sources in a timeline. None on error. + """ + cmd = { + "cmd": "get_timeline_elements", + "timeline_id": timeline_id + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["sources"] + else: + return None + + def get_folders(self): + """ + Get an unordered list of the folders in the project. + + :return: An unordered list of dictionaries representing the folders in the project. None on error. + """ + cmd = { + "cmd": "get_folders" + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["folders"] + else: + return None + + def get_timelines(self): + """ + Get an unordered list of timelines in the project. + + :return: An unordered list of dictionaries representing the timelines in the project. None on error. + """ + cmd = { + "cmd": "get_timelines" + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["timelines"] + else: + return None + + def get_sources(self): + """ + Get an unordered list of sources in the project. + + :return: An unordered list of dictionaries representing the sources in the project. None on error. + """ + cmd = { + "cmd": "get_sources" + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["sources"] + else: + return None + + def get_frame(self): + """ + Get the current frame of the (primary) active timeline. + + :return: The frame of the (primary) active timeline. Zero on error. + """ + cmd = { + "cmd": "get_frame" + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["frame"] + else: + return 0 + + def set_frame(self, frame, audio=False): + """ + Set the current frame of the (primary) active timeline. + + :param frame: Requested frame number + :param audio: Play audio for the frame after setting it. + :return: The frame of the (primary) active timeline. Zero on error. + """ + cmd = { + "cmd": "set_frame", + "frame": frame, + "audio": audio + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["frame"] + else: + return 0 + + def get_range(self): + """ + Get the current range of the (primary) active timeline. + + :return: Tuple containing the range of the (primary) active timeline. None on error. + """ + cmd = { + "cmd": "get_range" + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return (reply["start_frame"], reply["end_frame"]) + else: + return None + + def set_range(self, start_frame, end_frame): + """ + Set the current range of the (primary) active timeline. + + :param start_frame: Requested range start frame number. + :param end_frame: Requested range end frame number. + :return: Tuple containing the range of the (primary) active timeline. None on error. + """ + cmd = { + "cmd": "set_range", + "start_frame": start_frame, + "end_frame": end_frame + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return (reply["start_frame"], reply["end_frame"]) + else: + return None + + def get_default_timeline(self): + """ + Get the project default timeline. + + Imported files (sources) are automatically added to this timeline. + + :return: Dictionary representing the timeline object (may be empty if unassigned). None on error. + """ + cmd = { + "cmd": "get_default_timeline" + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["timeline"] + else: + return None + + def set_default_timeline(self, id): + """ + Set the project default timeline. An empty 'id' string will result unassign the default timeline. + + Imported files (sources) are automatically added to this timeline. + + :return: Dictionary representing the timeline object (may be empty if unassigned). None on error. + """ + cmd = { + "cmd": "set_default_timeline", + "id": id + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return True + else: + return False + + def get_active_in_viewer(self, index): + """ + Get the source/timeline assigned to a viewer. + + :param index: Viewer index. (0 - Viewer A, 1 - Viewer B) + :return: Dictionary representing the timeline object (may be empty if unassigned). None on error. + """ + cmd = { + "cmd": "get_active_in_viewer", + "index": index + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return reply["timeline"] + else: + return None + + def set_active_in_viewer(self, id, index): + """ + Set the source/timeline assigned to a viewer. + + An empty 'id' string will unassign a timeline from the viewer. + + :param index: Viewer index. (0 - Viewer A, 1 - Viewer B) + :return: Dictionary representing the timeline object (may be empty if unassigned). None on error. + """ + cmd = { + "cmd": "set_active_in_viewer", + "id": id, + "index": index + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return True + else: + return False + + def set_viewer_layout(self, layout="single"): + """ + Set the viewer layout to single, split horizontal or split vertical. + + :param layout: Desired layout ("single", "horizontal" or "vertical") + :return: True on success. False otherwise. + """ + cmd = { + "cmd": "set_viewer_layout", + "layout": layout, + } + + reply = self.send(cmd) + if self.is_valid_reply(reply): + return True + else: + return False + + +if __name__ == "__main__": + + kpro = KeyframeProClient() + if kpro.connect(): + print("Connected successfully.") diff --git a/Scripts/Animation/keyframe_pro/keyframe_pro_maya/__init__.py b/Scripts/Animation/keyframe_pro/keyframe_pro_maya/__init__.py new file mode 100644 index 0000000..5175b1d --- /dev/null +++ b/Scripts/Animation/keyframe_pro/keyframe_pro_maya/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from . import * diff --git a/Scripts/Animation/keyframe_pro/keyframe_pro_maya/maya_to_keyframe_pro.py b/Scripts/Animation/keyframe_pro/keyframe_pro_maya/maya_to_keyframe_pro.py new file mode 100644 index 0000000..3d53d4b --- /dev/null +++ b/Scripts/Animation/keyframe_pro/keyframe_pro_maya/maya_to_keyframe_pro.py @@ -0,0 +1,556 @@ +import datetime +import os +import shutil +import subprocess +import sys +import time +import traceback + +import maya.cmds as cmds +import maya.mel as mel +import maya.OpenMaya as om + +from keyframe_pro.keyframe_pro_client import KeyframeProClient + + +class MayaToKeyframePro: + + WINDOW_NAME = "MayaToKeyframeProWindow" + WINDOW_TITLE = "Keyframe Pro" + + VERSION = "1.0.1" + + KEYFRAME_PRO_PATH = "C:/Program Files/Keyframe Pro/bin/KeyframePro.exe" + PORT = 18181 + + SYNC_SCRIPT_NODE_NAME = "MayaToKeyframeProScriptNode" + + CACHED_TEMP_DIR_OPTION_VAR = "MayaToKeyframeProCachedTempDir" + COLLAPSE_STATE_OPTION_VAR = "MayaToKeyframeProCollapseState" + SYNC_OFFSET_OPTION_VAR = "MayaToKeyframeProSyncOffset" + FROM_RANGE_START_OPTION_VAR = "MayaToKeyframeProFromRangeStart" + + WAIT_FOR_OPEN_DURATION = 1 # Seconds to sleep after trying to open the application + + BUTTON_COLOR_01 = (0.5, 0.5, 0.5) + BUTTON_COLOR_02 = (0.361, 0.361, 0.361) + + SYNC_ACTIVE_COLOR = (0.0, 0.5, 0.0) + + kpro_client = None + + main_window = None + sync_layout = None + viewer_layout = None + playblast_layout = None + + sync_from_range_start_cb = None + sync_offset_ifg = None + playblast_viewer_rbg = None + + @classmethod + def open_keyframe_pro(cls, application_path=""): + if not application_path: + application_path = cls.KEYFRAME_PRO_PATH + + if not application_path: + om.MGlobal.displayError("Keyframe Pro application path not set.") + elif not os.path.exists(application_path): + om.MGlobal.displayError("Keyframe Pro application path does not exist: {0}".format(application_path)) + else: + try: + subprocess.Popen(cls.KEYFRAME_PRO_PATH, shell=False, stdin=None, stdout=None, stderr=None) + except: + traceback.print_exc() + om.MGlobal.displayError("Failed to open Keyframe Pro. See script editor for details.") + + @classmethod + def is_initialized(cls, display_errors=True): + if not cls.kpro_client: + cls.kpro_client = KeyframeProClient() + + if cls.kpro_client.connect(port=cls.PORT, display_errors=display_errors): + if cls.kpro_client.initialize(): + return True + else: + if display_errors: + om.MGlobal.displayError("Connection failed. Application may be closed or the port may be in use ({0}).".format(cls.PORT)) + + if display_errors: + om.MGlobal.displayError("Failed to connect to Keyframe Pro. See script editor for details.") + + return False + + @classmethod + def toggle_sync(cls): + if not cls.sync_script_node_exists() and cls.is_initialized(): + cls.create_sync_script_node() + if cls.sync_script_node_exists(): + cls.update_sync_time() + else: + cls.delete_sync_script_node() + + cls.update_sync_state() + + @classmethod + def update_sync_time(cls): + if cls.is_initialized(): + frame = cmds.currentTime(q=True) + cls.get_sync_offset() + + if cls.get_from_range_start(): + range = cls.kpro_client.get_range() + if range: + frame += range[0] - 1 + + cls.kpro_client.set_frame(frame) + + @classmethod + def set_viewer_layout(cls, layout): + if cls.is_initialized(): + cls.kpro_client.set_viewer_layout(layout) + + @classmethod + def swap_timelines(cls): + if cls.is_initialized(): + a = cls.kpro_client.get_active_in_viewer(0) + b = cls.kpro_client.get_active_in_viewer(1) + if b: + cls.kpro_client.set_active_in_viewer(b["id"], 0) + if a: + cls.kpro_client.set_active_in_viewer(a["id"], 1) + + @classmethod + def playblast(cls): + + format = cls.get_option_var("playblastFormat", "avi") + ext = "" + if format == "avi": + ext = "avi" + elif format == "qt": + ext = "mov" + else: + om.MGlobal.displayError("Current playblast format is image. Images are not supported in the current version of Keyframe Pro") + return + + temp_dir = cls.get_temp_dir() + if not temp_dir: + return + + if not os.path.exists(temp_dir): + os.makedirs(temp_dir) + + name = "blast" + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + file_path = "{0}/{1}_{2}.{3}".format(temp_dir, name, timestamp, ext) + + clear_cache = cls.get_option_var("playblastClearCache", True) + show_ornaments = cls.get_option_var("playblastShowOrnaments", False) + compression = cls.get_option_var("playblastCompression", "none") + quality = cls.get_option_var("playblastQuality", 70) + percent = cls.get_option_var("playblastScale", 0.5) * 100 + padding = cls.get_option_var("playblastPadding", 4) + display_source_size = cls.get_option_var("playblastDisplaySizeSource", 1) + playblast_width = cls.get_option_var("playblastWidth", 720) + playblast_height = cls.get_option_var("playblastHeight", 480) + + args = {"format": format, + "clearCache": clear_cache, + "viewer": False, + "showOrnaments": show_ornaments, + "fp": padding, + "percent": percent, + "compression": compression, + "quality": quality, + "filename": file_path + } + + if display_source_size == 2: + args["widthHeight"] = [cmds.getAttr("defaultResolution.w"), cmds.getAttr("defaultResolution.h")] + elif display_source_size == 3: + args["widthHeight"] = [playblast_width, playblast_height] + + playback_slider = mel.eval("$tempVar = $gPlayBackSlider") + if(cmds.timeControl(playback_slider, q=True, rv=True)): + range = cmds.timeControl(playback_slider, q=True, ra=True) + args["startTime"] = range[0] + args["endTime"] = range[1] + + sound = cmds.timeControl(playback_slider, q=True, sound=True) + if sound: + args["sound"] = sound + + file_path = cmds.playblast(**args) + om.MGlobal.displayInfo(file_path) + + # Open in viewer + viewer_index = cmds.radioButtonGrp(cls.playblast_viewer_rbg, q=True, select=True) - 1 + if viewer_index <= 1: + if not cls.is_initialized(False): + cls.open_keyframe_pro() + time.sleep(cls.WAIT_FOR_OPEN_DURATION) + + if not cls.is_initialized(): + om.MGlobal.displayError("Failed to open in viewer. See script editor for details.") + return + + if viewer_index >= 0 and viewer_index <= 1: + # On import, source may be loaded into A. Restore current A if source is to be in B + source_in_a = None + if viewer_index > 0: + source_in_a = cls.kpro_client.get_active_in_viewer(0) + + # Swap + source = cls.kpro_client.import_file(file_path) + if source: + cls.kpro_client.set_active_in_viewer(source["id"], viewer_index) + if source_in_a: + cls.kpro_client.set_active_in_viewer(source_in_a["id"], 0) + + @classmethod + def get_option_var(cls, name, default): + if cmds.optionVar(exists=name): + return cmds.optionVar(q=name) + else: + return default + + @classmethod + def open_temp_dir(cls): + temp_dir = cls.get_temp_dir() + if temp_dir: + if sys.platform == "win32": + os.startfile(temp_dir, 'explore') + else: + om.MGlobal.displayError("Open temp dir is not supported on the current platform ({0})".format(sys.platform)) + + @classmethod + def clear_temp_dir(cls): + result = cmds.confirmDialog(title='Confirm', + message='Clear temporary directory?', + button=['Yes', 'No'], + defaultButton='Yes', + cancelButton='No', + dismissString='No') + if result == "Yes": + temp_dir = cls.get_temp_dir() + if temp_dir: + errors_occurred = False + + for the_file in os.listdir(temp_dir): + file_path = os.path.join(temp_dir, the_file) + try: + if os.path.isfile(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + except: + om.MGlobal.displayWarning("Failed to remove file: {0}".format(file_path)) + om.MGlobal.displayWarning("File may be open in an application") + errors_occurred = True + + if errors_occurred: + om.MGlobal.displayWarning("Unable to remove all files. See script editor for details.") + else: + om.MGlobal.displayInfo("Temporary directory cleared: {0}".format(temp_dir)) + + @classmethod + def get_temp_dir(cls): + if cls.is_initialized(display_errors=False): + config = cls.kpro_client.get_config() + if config: + cmds.optionVar(sv=[cls.CACHED_TEMP_DIR_OPTION_VAR, config["temp_dir"]]) + return config["temp_dir"] + + temp_dir = cls.get_option_var(cls.CACHED_TEMP_DIR_OPTION_VAR, "") + if not temp_dir: + om.MGlobal.displayWarning("Unable to get temporary directory.") + + return temp_dir + + @classmethod + def sync_script_node_exists(cls): + return cmds.objExists(cls.SYNC_SCRIPT_NODE_NAME) + + @classmethod + def create_sync_script_node(cls): + if not cls.sync_script_node_exists(): + cmds.scriptNode(scriptType=7, + beforeScript="try: MayaToKeyframePro.update_sync_time()\nexcept: pass", + name=cls.SYNC_SCRIPT_NODE_NAME, + sourceType="python") + + @classmethod + def delete_sync_script_node(cls): + if cls.sync_script_node_exists(): + cmds.delete(cls.SYNC_SCRIPT_NODE_NAME) + + @classmethod + def get_sync_offset(cls): + if cmds.optionVar(exists=cls.SYNC_OFFSET_OPTION_VAR): + return cmds.optionVar(q=cls.SYNC_OFFSET_OPTION_VAR) + else: + return 0 + + @classmethod + def set_sync_offset(cls, value): + cmds.intFieldGrp(cls.sync_offset_ifg, e=True, value1=value) + cmds.optionVar(iv=[cls.SYNC_OFFSET_OPTION_VAR, value]) + if (cls.sync_script_node_exists()): + cls.update_sync_time() + + @classmethod + def sync_offset_to_current(cls): + cls.set_sync_offset(-cmds.currentTime(q=True) + 1) + + @classmethod + def sync_offset_changed(cls): + cls.set_sync_offset(cmds.intFieldGrp(cls.sync_offset_ifg, q=True, value1=True)) + + @classmethod + def get_from_range_start(cls): + if cmds.optionVar(exists=cls.FROM_RANGE_START_OPTION_VAR): + return cmds.optionVar(q=cls.FROM_RANGE_START_OPTION_VAR) + else: + return 1 + + @classmethod + def update_from_range_start(cls): + value = cmds.checkBox(cls.sync_from_range_start_cb, q=True, value=True) + cmds.optionVar(iv=[cls.FROM_RANGE_START_OPTION_VAR, value]) + + if cls.sync_script_node_exists(): + cls.update_sync_time() + + @classmethod + def get_collapse_state(cls): + if cmds.optionVar(exists=cls.COLLAPSE_STATE_OPTION_VAR): + collapse_state = cmds.optionVar(q=cls.COLLAPSE_STATE_OPTION_VAR) + if len(collapse_state) == 3: + for value in collapse_state: + if value < 0 or value > 1: + return [0, 1, 1] + + return collapse_state + + return [0, 1, 1] + + @classmethod + def update_collapse_state(cls): + cmds.optionVar(clearArray=cls.COLLAPSE_STATE_OPTION_VAR) + layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout] + for layout in layouts: + collapse = cmds.frameLayout(layout, q=True, cl=True) + cmds.optionVar(iva=[cls.COLLAPSE_STATE_OPTION_VAR, collapse]) + + @classmethod + def display(cls): + + if cmds.window(cls.WINDOW_NAME, exists=True): + cmds.deleteUI(cls.WINDOW_NAME, window=True) + + collapse_state = cls.get_collapse_state() + + # --------------------------------------------------------------------- + # Main layout + # --------------------------------------------------------------------- + cls.main_window = cmds.window(cls.WINDOW_NAME, title=cls.WINDOW_TITLE, s=True, tlb=False, rtf=True, mnb=False, mxb=False) + main_layout = cmds.formLayout(parent=cls.main_window) + + cls.sync_layout = cmds.frameLayout(parent=main_layout, + label="Sync", collapsable=True, + cl=collapse_state[0], + cc=lambda *args: cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()"), + ec=lambda *args: cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")) + sync_form_layout = cmds.formLayout(parent=cls.sync_layout) + + cls.viewer_layout = cmds.frameLayout(parent=main_layout, + label="Viewer", + collapsable=True, + cl=collapse_state[1], + cc=lambda *args: cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()"), + ec=lambda *args: cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")) + viewer_form_layout = cmds.formLayout(parent=cls.viewer_layout) + + cls.playblast_layout = cmds.frameLayout(parent=main_layout, + label="Playblast", + collapsable=True, + cl=collapse_state[2], + cc=lambda *args: cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()"), + ec=lambda *args: cmds.evalDeferred("MayaToKeyframePro.on_collapse_changed()")) + playblast_form_layout = cmds.formLayout(parent=cls.playblast_layout) + + cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, "top", 0)) + cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, "left", 0)) + cmds.formLayout(main_layout, e=True, af=(cls.sync_layout, "right", 0)) + + cmds.formLayout(main_layout, e=True, ac=(cls.viewer_layout, "top", 0, cls.sync_layout)) + cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, "left", 0)) + cmds.formLayout(main_layout, e=True, af=(cls.viewer_layout, "right", 0)) + + cmds.formLayout(main_layout, e=True, ac=(cls.playblast_layout, "top", 0, cls.viewer_layout)) + cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout, "left", 0)) + cmds.formLayout(main_layout, e=True, af=(cls.playblast_layout, "right", 0)) + + # --------------------------------------------------------------------- + # Sync layout + # --------------------------------------------------------------------- + cls.sync_offset_ifg = cmds.intFieldGrp(label="Offset: ", + value1=MayaToKeyframePro.get_sync_offset(), + columnWidth2=(40, 48), + cl2=("left", "right"), + cc=lambda *args: MayaToKeyframePro.sync_offset_changed(), + parent=sync_form_layout) + + cls.sync_from_range_start_cb = cmds.checkBox(label="From Range Start", + value=MayaToKeyframePro.get_from_range_start(), + cc=lambda *args: MayaToKeyframePro.update_from_range_start(), + parent=sync_form_layout) + + sync_offset_to_current_btn = cmds.button(label="Current", + bgc=cls.BUTTON_COLOR_01, + c=lambda *args: MayaToKeyframePro.sync_offset_to_current(), + parent=sync_form_layout) + + reset_sync_offset_btn = cmds.button(label=" Reset ", + bgc=cls.BUTTON_COLOR_01, + c=lambda *args: MayaToKeyframePro.set_sync_offset(0), + parent=sync_form_layout) + + cls.sync_btn = cmds.button(label="SYNC", c=lambda *args: MayaToKeyframePro.toggle_sync(), parent=sync_form_layout) + + top_offset = 1 + bottom_offset = 4 + left_position = 1 + right_position = 99 + spacing = 2 + cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_offset_ifg, "top", top_offset)) + cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_offset_ifg, "left", 0, left_position)) + + cmds.formLayout(sync_form_layout, e=True, af=(sync_offset_to_current_btn, "top", top_offset)) + cmds.formLayout(sync_form_layout, e=True, ac=(sync_offset_to_current_btn, "left", 0, cls.sync_offset_ifg)) + + cmds.formLayout(sync_form_layout, e=True, af=(reset_sync_offset_btn, "top", top_offset)) + cmds.formLayout(sync_form_layout, e=True, ac=(reset_sync_offset_btn, "left", spacing, sync_offset_to_current_btn)) + + cmds.formLayout(sync_form_layout, e=True, ac=(cls.sync_from_range_start_cb, "top", top_offset, sync_offset_to_current_btn)) + cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_from_range_start_cb, "left", 0, left_position)) + + cmds.formLayout(sync_form_layout, e=True, ac=(cls.sync_btn, "top", 2 * spacing, cls.sync_from_range_start_cb)) + cmds.formLayout(sync_form_layout, e=True, af=(cls.sync_btn, "bottom", bottom_offset)) + cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, "left", 0, left_position)) + cmds.formLayout(sync_form_layout, e=True, ap=(cls.sync_btn, "right", 0, right_position)) + + # --------------------------------------------------------------------- + # Viewer layout + # --------------------------------------------------------------------- + single_viewer_btn = cmds.button(label="Single", + bgc=cls.BUTTON_COLOR_01, + c=lambda *args: MayaToKeyframePro.set_viewer_layout('single'), + parent=viewer_form_layout) + + hori_viewer_btn = cmds.button(label="Horizontal", + bgc=cls.BUTTON_COLOR_01, + c=lambda *args: MayaToKeyframePro.set_viewer_layout('horizontal'), + parent=viewer_form_layout) + + vert_viewer_btn = cmds.button(label=" Vertical ", + bgc=cls.BUTTON_COLOR_01, + c=lambda *args: MayaToKeyframePro.set_viewer_layout('vertical'), + parent=viewer_form_layout) + + swap_timelines_btn = cmds.button(label="Swap Timelines", + bgc=cls.BUTTON_COLOR_01, + c=lambda *args: MayaToKeyframePro.swap_timelines(), + parent=viewer_form_layout) + + cmds.formLayout(viewer_form_layout, e=True, af=(single_viewer_btn, "top", top_offset)) + cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn, "left", 0, left_position)) + cmds.formLayout(viewer_form_layout, e=True, ap=(single_viewer_btn, "right", 0, 38)) + + cmds.formLayout(viewer_form_layout, e=True, af=(hori_viewer_btn, "top", top_offset)) + cmds.formLayout(viewer_form_layout, e=True, ac=(hori_viewer_btn, "left", spacing, single_viewer_btn)) + cmds.formLayout(viewer_form_layout, e=True, ap=(hori_viewer_btn, "right", 0, 68)) + + cmds.formLayout(viewer_form_layout, e=True, af=(vert_viewer_btn, "top", top_offset)) + cmds.formLayout(viewer_form_layout, e=True, ac=(vert_viewer_btn, "left", spacing, hori_viewer_btn)) + cmds.formLayout(viewer_form_layout, e=True, ap=(vert_viewer_btn, "right", 0, right_position)) + + cmds.formLayout(viewer_form_layout, e=True, ac=(swap_timelines_btn, "top", spacing, single_viewer_btn)) + cmds.formLayout(viewer_form_layout, e=True, af=(swap_timelines_btn, "bottom", bottom_offset)) + cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn, "left", 0, left_position)) + cmds.formLayout(viewer_form_layout, e=True, ap=(swap_timelines_btn, "right", 0, right_position)) + + # --------------------------------------------------------------------- + # Playblast layout + # --------------------------------------------------------------------- + cls.playblast_viewer_rbg = cmds.radioButtonGrp(label='Open in Viewer: ', + labelArray3=['A', 'B', 'None'], + numberOfRadioButtons=3, + select=1, + cw4=(100, 40, 40, 40), + cl4=("left", "left", "left", "left"), + parent=playblast_form_layout) + + playblast_btn = cmds.button(label="PLAYBLAST", + bgc=cls.BUTTON_COLOR_01, + c=lambda *args: MayaToKeyframePro.playblast(), + parent=playblast_form_layout) + + open_temp_dir_btn = cmds.button(label="Open Temp Folder", + bgc=cls.BUTTON_COLOR_01, + c=lambda *args: MayaToKeyframePro.open_temp_dir(), + parent=playblast_form_layout) + + clear_temp_dir_btn = cmds.button(label="Clear Temp Folder", + bgc=cls.BUTTON_COLOR_01, + c=lambda *args: MayaToKeyframePro.clear_temp_dir(), + parent=playblast_form_layout) + + version_label = cmds.text(label="v{0}".format(cls.VERSION), align="right") + + cmds.formLayout(playblast_form_layout, e=True, af=(cls.playblast_viewer_rbg, "top", top_offset)) + cmds.formLayout(playblast_form_layout, e=True, ap=(cls.playblast_viewer_rbg, "left", 0, left_position)) + + cmds.formLayout(playblast_form_layout, e=True, ac=(playblast_btn, "top", spacing, cls.playblast_viewer_rbg)) + cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn, "left", 0, left_position)) + cmds.formLayout(playblast_form_layout, e=True, ap=(playblast_btn, "right", 0, right_position)) + + cmds.formLayout(playblast_form_layout, e=True, ac=(open_temp_dir_btn, "top", spacing, playblast_btn)) + cmds.formLayout(playblast_form_layout, e=True, ap=(open_temp_dir_btn, "left", 0, left_position)) + cmds.formLayout(playblast_form_layout, e=True, ap=(open_temp_dir_btn, "right", 1, 50)) + + cmds.formLayout(playblast_form_layout, e=True, ac=(clear_temp_dir_btn, "top", spacing, playblast_btn)) + cmds.formLayout(playblast_form_layout, e=True, ap=(clear_temp_dir_btn, "left", 1, 50)) + cmds.formLayout(playblast_form_layout, e=True, ap=(clear_temp_dir_btn, "right", 0, right_position)) + + cmds.formLayout(playblast_form_layout, e=True, ac=(version_label, "top", spacing, open_temp_dir_btn)) + cmds.formLayout(playblast_form_layout, e=True, ap=(version_label, "right", 0, right_position)) + + # --------------------------------------------------------------------- + # Update and show + # --------------------------------------------------------------------- + cls.update_sync_state() + cls.on_collapse_changed() + cmds.setFocus(cls.sync_btn) + + cmds.showWindow(cls.main_window) + + @classmethod + def on_collapse_changed(cls): + total_height = 0 + layouts = [cls.sync_layout, cls.viewer_layout, cls.playblast_layout] + for layout in layouts: + total_height += cmds.frameLayout(layout, q=True, h=True) + + cmds.window(MayaToKeyframePro.main_window, e=True, h=total_height) + cls.update_collapse_state() + + @classmethod + def update_sync_state(cls): + if cls.sync_script_node_exists(): + cmds.button(cls.sync_btn, e=True, bgc=cls.SYNC_ACTIVE_COLOR, label="SYNCED") + else: + cmds.button(cls.sync_btn, e=True, bgc=cls.BUTTON_COLOR_01, label="SYNC") + + +if __name__ == "__main__": + MayaToKeyframePro.display() diff --git a/Scripts/Animation/mog_ikFkSwitchFree.py b/Scripts/Animation/mog_ikFkSwitchFree.py new file mode 100644 index 0000000..4bc322f --- /dev/null +++ b/Scripts/Animation/mog_ikFkSwitchFree.py @@ -0,0 +1,1480 @@ +import pymel.core as pm +import maya.OpenMaya as om +import logging + +""" +// Universal IK FK +// version 3.1 +// November 23, 2023 +// Monika Gelbmann +// monikagelbmann@gmail.com +// www.monikagelbmann.com + +Universal IK FK Switch and Match Tool + +DESCRIPTION: +This script lets you switch and match Poses between IK/FK controls in the animation scene. +Works for Riggs that don't have IK/FK match built in and requires only Standard FK controls and IK Pole Vector Setup. +The Controls are defined once and can be stored in Node for easy re use throughout the animation. + +INSTALLATION: +a) Copy the file (mog_ikFkSwitch.py) to your Maya scripts directory. On Windows that is Documents/maya/20xx/scripts/ + +b) Open Maya. In the Script Editor (Python), past the following code: +import pymel.core as pm +import mog_ikFkSwitchFree as mog_ikFkSwitchFree +import imp +imp.reload(mog_ikFkSwitchFree) +mog_ikFkSwitchFree.FkIk_UI() + +c) Hit execute (or Ctrl Enter) + +USAGE: +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> SETUP STORE NODE FOR LIMB MATCHING <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +1. Define Limb to work on +This always needs to be defined bofore loading/storing/switching. Sides are treated seperately + +2. Define Ctrls necessary by selecting them and hitting the << button +<< FK1: Upper arm +<< FK2: Lower arm +<< FK3: FK Hand +<< IK Ctrl: IK Hand +<< IK Pole: IK Ellbow pole vector (rig need to have pole vecotr for ik ellbow control) +<< Switch Ctrl: The ctrl that is used to switch between ik and fk mode +<< Switch Attr: The attribute that is dialed to switch. + It can be highlighted in the channel box and hit << + +3. Define Behavior +>> Knee Bend: Primary bend axis for the knee/ellbow. Bend the knee/ellbow 90 degree. What axis does it bend? +Knee Bend values for common rigs: +Malcolm(c Animschool): : +X Armx, -X Legs +Steward(c Animation Mentor): -Y Arms, +Z Legs +Jack(c Animsquad): -Y Arms, +X Legs +Norman: +Z Arms/Legs +Advanced Skeleton: -Z Arms/Legs + +>> Rotation Offset: + Some Riggs have different orientations in their IK and FK ctrls and joints. + This becomes obvious when running 'Match' from fk to ik and seeing a 90 degree offset in the wrist + Set the offset with that value and run 'Match' again to align them +Rotation Offsets for common rigs (X,Y,Z): +Malcolm(c Animschool): : (0, 0, 180) Right Arm/Leg, (0, 180, 0) Left Arm/Leg +Steward(c Animation Mentor): (180, 0, 0) Right Arm, (-20,-90, 0) Right Leg, (160,-90, 0) Left Leg +Jack(c Animsquad): (-180, 0, 0) Right Side +Norman: (0, -90, -90) Right Arm, (0, 90, -90) Left Arm, (-90,-90,0) Right Leg, (90,-90,0) Left Leg +Advanced Skeleton: (90,0,175) Right Arm, (-90,0,175) Left Arm + +4.Save/Update: Stores the Setup Node for this Limb in the Scene as a Null node + +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> UPDATE A EXISTING STORE NODE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +1. Load: Selected a Ctl in viewport, if a Setup Node exists in the Scene, load it into the Setup fields in the UI\ +2. Make modifications +3. Save/Update and confirm to overwrite existing Store node + +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> SWITCH / MATCH <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +1. Select a Ctrl in the viewport and click "Load Store Node from Selections". + When a Setup Node exists a green bar will light up +2. Match IK >> FK or FK >> IK to match the limb between the modes + +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ADDITONAL BUTTONS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +Switch IK/FK: Simple switches between IK/FK modes (does not do any matching) +Select all IK or FK ctrls: Select all nodes for this mode as stored in the Seup node + +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PRO BUTTONS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +Key all IK/FK: Creates a keyframe for all IK/FK Ctrls on current frame +Bake IK/FK Range: Bakes the entire timeline frame range to ik or fk. Leaves source keys clean +Bake IK/FK AllKeys: Bakes all keyframes in the timeline to ik or fk. Leaves source keys clean. Make sure your frame range is set to include all keys you want to bake. + +Export Store Nodes: Save all store nodes into .ma/.mb Store File saved in the same location as the scene. + The file name is the same as the scene + _IKFKSTORE and located in the same folder as the file + Already existing store nodes in the scene will be overwritten +Import Store Nodes: Opens file dialogue to select the previously exported Store File + +LIMITATIONS: +- Pole Vector Control is required and will not run if controlled with attribute +- Works only on Referenced Riggs + +Future Improvements/Optimzations planned: + - Make script work/not error if there is no polevector + +VERSIONS: +3.1 - November 23, 20234 - Added ability to look for Referenced Store Nodes (Save Store node in Rigg files) +3.0 - Oktober 06, 2021 - Update for Maya 2022 +2.0 - January 27, 2021 - UI update for Maya 2020 +1.12 - September 27, 2020 - Fixed isses with 'Switch' attribute for Baking (PRO) +1.11 - July 21, 2020 - Bug fix FK to IK Match pole Vector computation for locked fk controls +1.10 - July 05, 2020 - Pro version: added option to Bake All Keyframes in Range IK/FK and FK/IK in addtion to Bake every Frame +1.9 - June 03, 2020 Bug fix position snap for fk controls with transformation +1.8 - September 20, 2019 - Added knee/ellbow Bend Attribute to solve pole vector flip issues + Added more Info about Rotation Offset and Bend Angle to Help tab + Evaluation update bug fixes + Pro version: changes save format from .fbx to .ma/.mb +1.7 - May 25, 2018 - Too small Joint orientation on Malcolm fixed +1.6 - August 27, 2017 - Offset Value Bug fix. Storing float values now. +1.5 - August 27, 2017 - Blend Value 1 - 10 bug fix + Pro version: Bake range and Set Keyframe for all IK/FK ctrls + Free version: Select all ik/fk ctrls +1.4 - April 24, 2017 - Beta release. New interface. Auto detect limbs by selecting +1.1 - Jan 12, 2017 - Improvement to interface and bug fixes. +1.0 - Jan 07, 2017 - Initial Release. + +// Questions/comments/bugs/issues to +// monikagelbmann@gmail.com + +""" + +debug = False +debugZero = False +_logger = logging.getLogger(__name__) + +class FkIk_UI: + COLOR_PRESETS = { + "grey": (.5, .5, .5), + "lightgrey": (.7, .7, .7), + "darkgrey": (.25, .25, .25), + "turquoise": (.3, .55, .55)} + + def __init__(self): + global win + win ='ikfkswitchUI_' + + # on off logging + + #logging.basicConfig(level=logging.INFO) + _logger.disabled = not debug + + try: + if pm.window("ikFkSwitch_UI", exists=True): + pm.deleteUI("ikFkSwitch_UI") + except Exception as e: + pass + + windowWidth = 350 + windowHeight = 250 + + window = pm.window("ikFkSwitch_UI", width=windowWidth, height=windowHeight, title="Universal IK FK Free") + topLevelColumn = pm.columnLayout(adjustableColumn=True, columnAlign="center") + + #Setup Tabs #every child creates new tab + tabHeight = 350 + tabWidth = 300 + scrollWidth = tabWidth - 40 + + riggTab = self.initializeTab(tabHeight, tabWidth) + pm.setParent("..") + + #Display window + pm.showWindow("ikFkSwitch_UI") + + def initializeTab(self, tabHeight, tabWidth): + frameWidth = tabWidth - 20 + mainColumnLayout = pm.columnLayout(win+"mainColumnLayout", w=tabWidth,columnAttach=('left', 10)) + pm.setParent(win + "mainColumnLayout") + ## + ####################### SETUP FRAME ############################## + ## + pm.frameLayout(win+"setupFrameLayout", w=tabWidth, label="Setup Store Node",collapsable=True, collapse=True ) + + pm.separator(h=10) + pm.text('1. Choose Limb ') + pm.separator(h=10) + + pm.columnLayout(win + 'ctrlinputColumn', cal='left', columnWidth=20) + pm.rowLayout(win+"switchLimb", numberOfColumns=5) + self.collection2 = pm.radioCollection(win+'limbRadioCollt') + self.rb1 = pm.radioButton(win+'R_arm', w=frameWidth/5, label='R Arm') + self.rb2 = pm.radioButton(win+'L_arm', w=frameWidth/5, label='L Arm') + self.rb3 = pm.radioButton(win+'R_leg', w=frameWidth/5, label='R Leg') + self.rb4 = pm.radioButton(win+'L_leg', w=frameWidth/5, label='L Leg') + pm.button(label=' From Sel ', w=frameWidth/5, command=lambda a:self.autoDetectSideAndLimbWin()) + pm.setParent(win+"setupFrameLayout") + + pm.separator(h=10) + pm.text('2. Define Controls') + pm.separator(h=10) + + pm.textFieldButtonGrp(win+'fkshldrTfb', label='', text='', cw3=(0,200,100), ad3=3, buttonLabel=' < FK Upper Limb', bc=lambda:self.inputSelTfb("fkshldrTfb"), columnAlign3=("right", "left", "left")) + pm.textFieldButtonGrp(win+'fkellbowTfb', label='', text='', cw3=(0,200,100), ad3=3, buttonLabel='< FK Lower Limb', bc=lambda:self.inputSelTfb("fkellbowTfb")) + pm.textFieldButtonGrp(win+'fkwristTfb', label='', text='', cw3=(0,200,100), ad3=3, buttonLabel='< FK Wrist/Foot', bc=lambda:self.inputSelTfb("fkwristTfb")) + pm.textFieldButtonGrp(win+'ikwristTfb', label='', text='',cw3=(0,200,100), ad3=3, buttonLabel='< IK Wrist/Foot', bc=lambda:self.inputSelTfb("ikwristTfb")) + pm.textFieldButtonGrp(win+'ikpvTfb', label='', text='', cw3=(0,200,100), ad3=3, buttonLabel='< Pole Vector', bc=lambda:self.inputSelTfb("ikpvTfb")) + + pm.setParent(win+"setupFrameLayout") + # pm.setParent(self.UIElements["mainColumnLayout"]) + # + + pm.textFieldButtonGrp(win+'switchCtrlTfb', label='', cw3=(0,200,100), ad3=3, text='', buttonLabel='< Switch Ctrl', bc=lambda:self.inputSelTfb("switchCtrlTfb")) + pm.textFieldButtonGrp(win+'switchAttrTfb', label='', cw3=(0,200,100), ad3=3, text='', buttonLabel='< Switch Attr', bc=lambda:self.inputChannelboxSelectionTbf("switchAttrTfb")) + + pm.rowLayout(win+"ikIsValueRow", numberOfColumns=3) + pm.text('Attribute on 0 is', w=frameWidth/3) + collection2 = pm.radioCollection(win+'switch0isfkTfb') + rb1 = pm.radioButton(win+'attr0IsIk', label='IK mode', w=frameWidth/3) + rb2 = pm.radioButton(win+'attr0IsFk', label='FK mode', w=frameWidth/3) + pm.radioCollection(win+"switch0isfkTfb", e=1, select=rb2) + pm.setParent(win+"setupFrameLayout") + + pm.rowLayout(win+"ikIsRangeRow", numberOfColumns=3) + pm.text('Attribute Range', w=frameWidth/3) + collection2 = pm.radioCollection(win+'switchAttrRangeTfb') + rb1 = pm.radioButton(win+'attr1', label='0 to 1', w=frameWidth/3) + rb2 = pm.radioButton(win+'attr10', label='0 to 10', w=frameWidth/3) + pm.radioCollection(win+"switchAttrRangeTfb", e=1, select=rb1) + pm.setParent(win+"setupFrameLayout") + + pm.rowLayout(win+"bendKneeAxisRowTfb", numberOfColumns=7) + pm.text('Knee Bend', w=frameWidth/4) + collection2 = pm.radioCollection(win+'bendKneeAxisTfb') + rb1 = pm.radioButton(win+'pX', label='+X', w=frameWidth/8) + rb2 = pm.radioButton(win+'nX', label='-X', w=frameWidth/8) + rb3 = pm.radioButton(win+'pY', label='+Y', w=frameWidth/8) + rb4 = pm.radioButton(win+'nY', label='-Y', w=frameWidth/8) + rb5 = pm.radioButton(win+'pZ', label='+Z', w=frameWidth/8) + rb6 = pm.radioButton(win+'nZ', label='-Z', w=frameWidth/8) + pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=rb1) + pm.setParent(win+"setupFrameLayout") + + + pm.rowColumnLayout("rotOffsetRow", numberOfColumns=4, columnWidth=[(1,frameWidth/4), (2,frameWidth/4), (3,frameWidth/4)]) + pm.text(l='rotOffset') + pm.textField(win+'rotOffsetX', tx=0) + pm.textField(win+'rotOffsetY', tx=0) + pm.textField(win+'rotOffsetZ', tx=0) + pm.setParent(win + "setupFrameLayout") + + + # store for clearing + self.inputTxtFldBtnGrps = [win+"fkshldrTfb",win+"fkellbowTfb",win+"fkwristTfb", win+"ikwristTfb", + win+"ikpvTfb",win+"switchCtrlTfb",win+"switchAttrTfb"] + self.inputTxtFlds = [ win+"rotOffsetX", win+"rotOffsetY", win+"rotOffsetZ"] + + pm.button(label=' Save/Update ', w=300, bgc=self.COLOR_PRESETS["turquoise"], command=lambda a:self.saveIkFkCtrlsWin()) + pm.button(label='Load', bgc=self.COLOR_PRESETS["darkgrey"], w=300, h=20, command=lambda a: self.loadIkFkCtrlsWin()) + pm.button(label='clear Fields', bgc=self.COLOR_PRESETS["darkgrey"], w=300, h=20,command=lambda a: self.clearInputFields()) + + pm.separator(h=10) + ## + ## + ####################### HELP FRAME ############################## + ## + pm.setParent(win + "mainColumnLayout") + pm.frameLayout(win + "helpFrameLayout", w=300, label="Help", collapsable=True, collapse=True) + pm.columnLayout(win + 'helpColumn', cal='left', columnWidth=300) + pm.scrollField(w=300, wordWrap = True, editable=False, tx= + 'Use <<< Button to Fill the Fields by what you have selected in the Viewport.\n\n' + + 'Switch Ctrl: Choose the Ctrls that sets IK/FK mode\n\n' + + 'Switch Attr: Highlight the "ikfk" Attribute in the Channelbox and hit <<\n\n' + + 'Knee Bend: What axis does the knee/ellbow bend?\n' + + 'Malcolm(Animschool): +X Armx, -X Legs\n' + + 'Steward(AnimationMentor): -Y Arms, +Z Legs \n' + + 'Jack(Animsquad): -Y Arms, +X Legs\n' + + 'Norman: -Y Arms, +X Legs\n' + + 'Advanced Skeleeton: -Z Arms and Legs\n\n' + + 'Rotation Offset: Orientation difference of IK/FK wrist\n' + + 'Malcolm(Animschool): (0,0,180) R Arm/Leg, (0,180,0) L Arm/Leg\n' + + 'Steward(AnimationMentor): (180,0,0) R Arm, (-20,-90,0) R Leg, (160,-90,0) L Leg\n' + + 'Jack(Animsquad): (-180,0,0) R Arm/Leg \n' + + 'Norman: (0,-90,-90) R Arm, (0,90,-90) L Arm, (-90,-90,0) R Leg, (90,-90,0) L Leg\n' + + 'Adanced Skeleton: (90,0,175) R Arm, (-90,0,175) L Arm\n\n' + + 'Match IK/FK: Does the matching between IK/FK\n\n' + + 'Switch IK/FK: Simple switches between IK/FK modes (does not do any matching)\n\n' + + 'Steps:\n' + + '1. Fill Setup Input Fields\n' + + '2. Hit "Save/Update"\n' + + '3. Hit Load \"Store Node from Selection\"\n' + + '4. Hit "Match IK>FK" or "Match FK>IK"\n\n' + + 'Find more details in \"how to install and use.txt\"' + ) + pm.separator(h=10) + ## + ####################### MATCH FRAME ############################## + ## + pm.setParent(win+"mainColumnLayout") + pm.frameLayout(win + "matchFrameLayout", w=300, label="Match and Switch", collapsable=True ) + pm.text('3. Match / Switch') + pm.separator(h=10) + pm.button(label='Load Store Node from Selection', w=300, command=lambda a: self.findStoreNodeFromSelectionWin()) + self.readyText = pm.text(win + 'readyText', label='Not Ready.', align='left', bgc=(.6,.4,.4)) + pm.separator(h=10) + pm.rowColumnLayout(win+"matchIKRow", numberOfColumns=2, columnWidth=[(1,150), (2,150)]) + pm.button(label="Match IK >> FK", bgc=self.COLOR_PRESETS["turquoise"], command=lambda a: self.matchIkFkWin(tofk=1)) + pm.button(label="Match FK >> IK", bgc=self.COLOR_PRESETS["turquoise"], command=lambda a: self.matchIkFkWin(tofk=0)) + pm.setParent(win+"matchFrameLayout") + + pm.rowColumnLayout(win+"switchIKRow", numberOfColumns=2, columnWidth=[(1,150), (2,150)]) + pm.button(label="Switch IK", command=lambda a: self.switchIkFkWin()) + pm.button(label="Switch FK", command=lambda a: self.switchFkIkWin()) + pm.button(label="Select all IK", command=lambda a: self.selectAll(fk=0)) + pm.button(label="Select all FK", command=lambda a: self.selectAll(fk=1)) + pm.setParent(win+"matchFrameLayout") + pm.setParent(win+"mainColumnLayout") + + pm.separator(h=5) + pm.text('Release 3.1 Monika Gelbmann 11/2023') + pm.separator(h=5) + + def inputSelTfb(self, name): + if len(pm.selected()) == 0: + pm.textFieldButtonGrp(win+name, e=1, tx='') + return [] + pm.textFieldButtonGrp(win+name, e=1, tx=pm.selected()[0]) + + + def getAndCheckInputWin(self): + + inputValues = [] + errorFields = [] + + # switch 0 is radio + switch0isfkTfb= pm.radioCollection(win+"switch0isfkTfb", q=1, sl=1) + + if switch0isfkTfb == 'ikfkswitchUI_attr0IsFk': + _logger.info( 'FK switch0isfk is %s'%switch0isfkTfb ) + switch0isfk = 1 + else: + _logger.info('IK switch0isfk is %s'%switch0isfkTfb ) + switch0isfk = 0 + + # switch range radio + switchAttrRangeTfb = pm.radioCollection(win+"switchAttrRangeTfb", q=1, sl=1) + if switchAttrRangeTfb == 'ikfkswitchUI_attr1': + switchAttrRange = 1 + else: + switchAttrRange = 10 + + + + # check empty input text fields + for inputTxtFldBtnGrp in self.inputTxtFldBtnGrps: + input = pm.textFieldButtonGrp(inputTxtFldBtnGrp, q=1, tx=1) + + if len(input) == 0: + errorFields.append(pm.textFieldButtonGrp(inputTxtFldBtnGrp, q=1, buttonLabel=1)) + + if len(errorFields) > 0: + message = 'Empty input field found. Please pick Ctrl to use.\n%s'%errorFields + self.popupWarning(message) + pm.error(message) + return False + + + # check ctrls are valid and do exist + for inputTxtFldBtnGrp in self.inputTxtFldBtnGrps[:-1]: + input = pm.textFieldButtonGrp(inputTxtFldBtnGrp, q=1, tx=1) + if pm.objExists(input) == 0: + errorFields.append(pm.textFieldButtonGrp(inputTxtFldBtnGrp, q=1, buttonLabel=1)) + else: + inputValues.append(input) + if len(errorFields) > 0: + message = 'Non existing ctrls found. Check those names are correct:\n%s'%errorFields + self.popupWarning(message) + pm.error(message) + return False + + # check switch attribute + ctrlInput = pm.textFieldButtonGrp(self.inputTxtFldBtnGrps[-2], q=1, tx=1) + attrInput = pm.textFieldButtonGrp(self.inputTxtFldBtnGrps[-1], q=1, tx=1) + attr = '%s.%s'%(ctrlInput, attrInput) + if pm.objExists(attr) == False: + message = 'Switch Attribute does not exist. Check the naming:\n%s'%attr + pm.warning(message) + self.popupWarning(message) + pm.error(message) + return False + else: + inputValues.append(attrInput) + + + # limb radio box + limbRadio = pm.radioCollection(win+"limbRadioCollt", q=1, sl=1) + _logger.info('raidobuttons: %s' % limbRadio) + if limbRadio == 'NONE': + message = 'Limb choice missing. Please choose R Arm / L Arm / R Leg / L Leg' + self.popupWarning(message=message) + pm.warning(message) + return False + + ###TODO IK PIV can stay empty... + ###TODO how to align with fk if there is no pv in ik + # if pm.objExists(ikpv) == 0: + # pm.error('Input Piv %s does not exist. Aborting'%input) + # return False + + + # validate offset numeric input fields + rotOffsetX = pm.textField(win+'rotOffsetX', q=1, tx=1) + _logger.debug('checking offsets') + try: + rotOffsetX = float(rotOffsetX) + except: + rotOffsetX = 0.0 + pass + rotOffsetY = pm.textField(win+'rotOffsetY', q=1, tx=1) + try: + rotOffsetY = float(rotOffsetY) + except: + rotOffsetY = 0.0 + pass + rotOffsetZ = pm.textField(win+'rotOffsetZ', q=1, tx=1) + try: + rotOffsetZ = float(rotOffsetZ) + except: + rotOffsetZ = 0.0 + pass + rotOffset=[rotOffsetX, rotOffsetY, rotOffsetZ] + + bendKneeAxis = pm.radioCollection(win+"bendKneeAxisTfb", q=1, sl=1) + if bendKneeAxis == 'ikfkswitchUI_pX': + bendKneeAxis = '+X' + elif bendKneeAxis == 'ikfkswitchUI_nX': + bendKneeAxis = '-X' + elif bendKneeAxis == 'ikfkswitchUI_pY': + bendKneeAxis = '+Y' + elif bendKneeAxis == 'ikfkswitchUI_nY': + bendKneeAxis = '-Y' + elif bendKneeAxis == 'ikfkswitchUI_pZ': + bendKneeAxis = '+Z' + elif bendKneeAxis == 'ikfkswitchUI_nZ': + bendKneeAxis = '-Z' + inputValues.append(switch0isfk) + inputValues.append(switchAttrRange) + inputValues.append(rotOffset) + inputValues.append(bendKneeAxis) + + _logger.info('returning %s'%inputValues) + return inputValues + + + def clearInputFields(self): + # query text input fields + for inputTxtFldBtnGrp in self.inputTxtFldBtnGrps: + pm.textFieldButtonGrp(inputTxtFldBtnGrp, e=1, tx='') + for inputTxtFld in self.inputTxtFlds: + pm.textField(inputTxtFld, e=1, tx='') + + + def popupWarning(self, message, title='Input Error'): + + result = pm.confirmDialog( + title=title, + message=message, + button=['OK'], + defaultButton='OK',) + + return result + + + def autoDetectSideAndLimbWin(self): + side, limb = autoDetectSideAndLimb(pm.selected()[0]) + if side and limb: + pm.displayInfo( 'Matching Side and Limb found: %s %s'%(side, limb)) + if side == 'R' and limb == 'arm': pm.radioCollection(win+'limbRadioCollt' , edit=1, select=self.rb1) + elif side == 'L' and limb == 'arm': pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb2) + elif side == 'R' and limb == 'leg': pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb3) + elif side == 'L' and limb == 'leg': pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb4) + self.loadIkFkCtrlsWin() + + def inputChannelboxSelectionTbf(self, name): + channelBox = pm.mel.eval('global string $gChannelBoxName; $temp=$gChannelBoxName;') #fetch maya's main channelbox + attrs = pm.channelBox(channelBox, q=True, sma=True) + + if not attrs: + pm.textFieldButtonGrp(win+name, e=1, tx='') + return [] + if len(attrs) != 1: + pm.warning('Highlight only the IK/FK Switch Attribute in the Channelbox') + return [] + pm.textFieldButtonGrp(win+name, e=1, tx=attrs[0]) + return attrs + + def findStoreNodeFromSelectionWin(self): + store_node = findStoreNodeFromSelection() + _logger.debug( 'store node found is %s'%store_node) + if store_node == []: + message = 'No Storenode found for Selection. Fill out Setup section first and hit SAVE for future detection' + pm.warning(message) + #self.popupWarning(message) + pm.text(self.readyText, e=1, label='No Storenode found. Use Setup. Not Ready.', align='left', bgc=(.6,.4,.4)) + + + else: + ns = store_node.split('__')[0] if len(store_node.split('__'))>0 else '' + side = store_node.split('_')[-3] + limb = store_node.split('_')[-2] + pm.displayInfo( 'Storenode found for %s %s. Loading %s'%(side, limb, store_node)) + if side and limb: + _logger.info( 'Machting Side and Limb found') + if side == 'R' and limb == 'arm': + pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb1) + elif side == 'L' and limb == 'arm': + pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb2) + elif side == 'R' and limb == 'leg': + pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb3) + elif side == 'L' and limb == 'leg': + pm.radioCollection(win + 'limbRadioCollt', edit=1, select=self.rb4) + self.loadIkFkCtrlsWin() + pm.text(self.readyText, e=1, label='Storenode found >> %s %s. Ready.'%(side,limb), align='left', bgc=(.4,.6,.4)) + + + def saveIkFkCtrlsWin(self): + fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, bendKneeAxis = self.getAndCheckInputWin() + limbRadio = pm.radioCollection(win+"limbRadioCollt", q=1, sl=1) + if limbRadio == 'NONE': + pm.warning('Limb choice missing. Please choose form the UI options') + return False + limb = limbRadio.split('_')[-1] + side = limbRadio.split('_')[1] + + storeNode = saveIKFkCtrls(limb, side, fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, bendKneeAxis) + if storeNode: + pm.displayInfo( 'Successfully saved.') + #self.popupWarning(message = 'Successfully saved.', title='Storenode saved') + + def loadIkFkCtrlsWin(self): + limbRadio = pm.radioCollection(win+"limbRadioCollt", q=1, sl=1) + + if limbRadio == 'NONE': + message = 'Limb choice missing. Please choose R Arm / L Arm / R Leg / L Leg' + self.popupWarning(message=message) + pm.warning(message) + return False + + limb = limbRadio.split('_')[-1] # limbRadio = ikfkswitchUI_R_arm + side = limbRadio.split('_')[1] + + if len(pm.selected()) == 0: + pm.warning('Select anything from the rigg') + return False + ns = pm.selected()[0].split(':')[0] if len(pm.selected()[0].split(':')) > 1 else '' + + storedic = loadIkFkCtrl(ns, limb, side) + + if len(storedic) == 0: + pm.warning('No Store Node for %s. Define Limbs and Save Store Node'%limb) + else: + pm.displayInfo( 'Found Store Node for %s. Loading.'%limb ) + + for attrName, value in list(storedic.items()): + if attrName == 'switch0isfk': + _logger.info('load switch0isfk is %s'%value ) + if value == '0': + pm.radioCollection(win+"switch0isfkTfb", e=1, select=win+'attr0IsIk') + else: + pm.radioCollection(win+"switch0isfkTfb", e=1, select=win+'attr0IsFk') + elif attrName == 'attrRange': + _logger.info('load attrRange value is %s'%value ) + if value == '1': + pm.radioCollection(win+"switchAttrRangeTfb", e=1, select=win+'attr1') + else: + pm.radioCollection(win+"switchAttrRangeTfb", e=1, select=win+'attr10') + elif attrName == 'rotOffset': + rotList = eval(value) + _logger.debug( 'rotation list eval is %rotList'%rotList) + pm.textField(win+"rotOffsetX", e=1, tx=rotList[0]) + pm.textField(win+"rotOffsetY", e=1, tx=rotList[1]) + pm.textField(win+"rotOffsetZ", e=1, tx=rotList[2]) + elif attrName == 'side': + continue + elif attrName == 'bendKneeAxis': + _logger.info('load bendKneeAxis value is %s'%value ) + if value == '+X': + pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'pX') + elif value == '-X': + pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'nX') + elif value == '+Y': + pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'pY') + elif value == '-Y': + pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'nY') + elif value == '+Z': + pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'pZ') + elif value == '-Z': + pm.radioCollection(win+"bendKneeAxisTfb", e=1, select=win+'nZ') + else: + pm.textFieldButtonGrp(win+"%sTfb"%attrName, e=1, tx=value) + + + + def matchIkFkWin(self, tofk=1): + try: + fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, bendKneeAxis = self.getAndCheckInputWin() + except: + pm.warning( 'input error matchIkFkWin' ) + return + + rotOffsetX = pm.textField(win+'rotOffsetX', q=1, tx=1) + _logger.debug('rotOffset X is %s'%rotOffsetX ) + + if rotOffsetX == '' : rotOffsetX = 0.0 + else: rotOffsetX = float(rotOffsetX) + rotOffsetY = pm.textField(win+'rotOffsetY', q=1, tx=1) + if rotOffsetY == '' : rotOffsetY = 0.0 + else: rotOffsetY = float(rotOffsetY) + rotOffsetZ = pm.textField(win+'rotOffsetZ', q=1, tx=1) + if rotOffsetZ == '' : rotOffsetZ = 0.0 + else: rotOffsetZ = float(rotOffsetZ) + + limbRadio = pm.radioCollection(win+"limbRadioCollt", q=1, sl=1) + side = limbRadio.split('_')[1] + limb = limbRadio.split('_')[2] + + if tofk == 1: + ikfkMatch(fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk=switch0isfk, switchAttrRange=switchAttrRange, rotOffset=[rotOffsetX, rotOffsetY, rotOffsetZ], side=side, limb=limb, bendKneeAxis=bendKneeAxis) + elif tofk == 0: + fkikMatch(fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk=switch0isfk, switchAttrRange=switchAttrRange, rotOffset=[rotOffsetX, rotOffsetY, rotOffsetZ], side=side, limb=limb) + + pm.select(switchCtrl) + + def switchIkFkWin(self): + fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, kneeBendAxis = self.getAndCheckInputWin() + + + if switch0isfk == 1: setSwitchTo = switchAttrRange + else: setSwitchTo = 0 + + pm.setAttr('%s.%s'%(switchCtrl, switchAttr), setSwitchTo) + pm.displayInfo( 'Done. Switched IK >> FK') + + def switchFkIkWin(self): + fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, kneeBendAxis = self.getAndCheckInputWin() + + if switch0isfk == 1: setSwitchTo = 0 + else: setSwitchTo = switchAttrRange + + pm.setAttr('%s.%s'%(switchCtrl, switchAttr), setSwitchTo) + pm.displayInfo( 'Done. Switched FK >> IK') + + def selectAll(self, fk=1): + + try: + fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, kneeBendAxis = self.getAndCheckInputWin() + except: + pm.warning('Input error selectAll') + return + + if fk == 1: + pm.select(fkshldr, fkellbow, fkwrist) + pm.displayInfo('Done. Select 3 fk Ctrls.') + + elif fk == 0: + pm.select(ikpv,ikwrist) + pm.displayInfo('Done. Select 2 ik Ctrls.') + return + + def keyAll(self, fk=1): + try: + fkshldr, fkellbow, fkwrist, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, bendKneeAxis = self.getAndCheckInputWin() + except: + pm.warning('Input error keyAll') + return + + if fk == 1: + pm.setKeyframe(fkwrist,s=0, rk=1, mr=1) + pm.setKeyframe(fkellbow,s=0, rk=1, mr=1) + pm.setKeyframe(fkshldr,s=0, rk=1, mr=1) + + pm.displayInfo('Done. Set Keyframes on 3 fk Ctrls.') + + elif fk == 0: + pm.setKeyframe(ikpv,s=0, rk=1, mr=1) + pm.setKeyframe(ikwrist,s=0, rk=1, mr=1) + + pm.displayInfo('Done. Set Keyframes on 2 ik Ctrls.') + return + + + +def findStoreNodeFromSelection(): + + selection = pm.selected()[0] + namespace = selection.split(':')[0] + ':' if len(selection.split(':')) > 1 else '' + referenced_node = False + storenode_namespace = namespace.replace(':', '__') + character_storenodes = pm.ls('%s*_IKFKSTORE'%storenode_namespace) + + if character_storenodes == []: # referenced store node EDIT 23.11.23 + character_storenodes = pm.ls('%s*_IKFKSTORE'%namespace) + referenced_node == True + + if character_storenodes == []: + return [] + + for storenode in character_storenodes: + storedic = {'fkwrist': '', 'fkellbow': '', 'fkshldr': '', 'ikwrist': '', 'ikpv': '', 'switchCtrl': ''} + for attrName, value in list(storedic.items()): + _logger.debug(storenode.attr(attrName).get() + ' selection is ' + selection.name()) + if referenced_node: # referenced store node EDIT 23.11.23 + storedic[attrName] = namespace+storenode.attr(attrName).get() + else: + storedic[attrName] = storenode.attr(attrName).get() + if (selection.name() == storedic[attrName]) or (selection.name() == namespace+storedic[attrName]): + _logger.debug( 'Found selection in store node %s'%storedic[attrName] ) + return storenode + + return [] + +def autoDetectSideAndLimb(ctrl=None): + ''' + Need to have one ctrl selecte. This ctrl will determine side, namespace and suffix + From there we list all matching nodes and try to find Limb + From there we filter FK IK + Returns: + + ''' + if ctrl == None: + ctrl = pm.selected()[0] + ctrlname = pm.PyNode(ctrl).nodeName() + namespace = ctrl.split(':')[0]+':' if len(ctrl.split(':')) > 1 else '' + suffix = '_' + ctrlname.split('_')[-1] if 'ctrl' in ctrlname.split('_')[-1] else '' + + side = None + limb = None + + # Detect Side + for search_str in ['rt', 'Rt', 'R_', '_R', 'r_', '_r', 'right']: + if search_str in ctrlname: + side, side_str = 'R', search_str + break + if side == None: + for search_str in ['lf', 'Lf', 'L_', '_L', 'l_', '_l', 'left']: + if search_str in ctrlname: + side, side_str = 'L', search_str + break + + #_logger.debug('Side found: %s from %s' % (side, side_str)) + + # Detect Limb + #side_ctrls = pm.ls('%s*%s*fk*%s' % (namespace, side_str, suffix),exactType='transform') + #for side_ctrl in side_ctrls: + # side_ctrlname = side_ctrl.nodeName().split(namespace)[-1].split(suffix)[0] + for search_str in ['hand', 'Hand', 'arm', 'Arm', 'elbow', 'ellbow', 'Elbow', 'wrist', 'Wrist']: + if search_str in ctrlname: + #_logger.debug(' Arm detected %s'%ctrlname) + limb = 'arm' + break + if limb == None: + for search_str in ['leg', 'Leg', 'knee', 'Knee', 'foot', 'Foot']: + if search_str in ctrlname: + #_logger.debug(' Leg detected %s' % ctrlname ) + limb = 'leg' + break + return side, limb + + + +def fkikMatch(fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk=1, switchAttrRange=1, rotOffset=[0,0,0], side='R', limb='arm'): + ''' + Match fk to ik. Recreate the ik chain + Args: + fkwrist: + fkellbow: + fkshldr: + ikwrist: + ikpv: + switchCtrl: + switchAttr: + switch0isfk: + rotOffset: + + Returns: + + ''' + switch = '%s.%s'%(switchCtrl, switchAttr) + + if pm.objExists('snapGrp'): pm.delete('snapGrp') + snapGrp = pm.createNode('transform', name='snapGrp') + clist=[] + + + # dup controls to constrain + fk_wristDup = pm.duplicate(fkwrist, parentOnly=1, n='fk_wristDup')[0] + unlockAttributes([fk_wristDup]) + pm.parent(fk_wristDup, snapGrp) + + + + # go to fk mode to match correct position + if switch0isfk == 0: pm.setAttr(switch, switchAttrRange) # 0 is fk + else: pm.setAttr(switch, 0) + + + # store fk keyframes on attribute or not: + fkwrist_key, fkellbow_key, fkshldr_key = pm.keyframe(fkwrist, q=1, t=pm.currentTime()),\ + pm.keyframe(fkellbow, q=1, t=pm.currentTime()),\ + pm.keyframe(fkshldr, q=1, t=pm.currentTime()) + + + # get positions from fk + fkwRaw = pm.xform(fkwrist, ws=1, q=1, t=1) + fkwPos = om.MVector(fkwRaw[0], fkwRaw[1], fkwRaw[2]) + fkeRaw = pm.xform(fkellbow, ws=1, q=1, t=1) + fkePos = om.MVector(fkeRaw[0], fkeRaw[1], fkeRaw[2]) + fksRaw = pm.xform(fkshldr, ws=1, q=1, t=1) + fksPos = om.MVector(fksRaw[0], fksRaw[1], fksRaw[2]) + + # store rotation + fkwRotRaw = pm.xform(fkwrist, q=1, ro=1) + fkeRotRaw = pm.xform(fkellbow, q=1, ro=1) + fksRotRaw = pm.xform(fkshldr, q=1, ro=1) + + # zero out fk + pm.xform(fkshldr, ro=(0,0,0)) + pm.xform(fkellbow, ro=(0,0,0)) + pm.xform(fkwrist, ro=(0,0,0)) + snap(fkwrist, fk_wristDup) + + # create orig ik wrist dup to get offset + pm.xform(ikwrist, ro=(0,0,0)) + ik_wristDup = pm.duplicate(ikwrist, parentOnly=1, n='ik_wristDup')[0] + unlockAttributes([ik_wristDup]) + pm.parent(ik_wristDup, fk_wristDup) + snap(fk_wristDup, ik_wristDup, pos=1, rot=1) + #snap(ikwrist, ik_wristDup, pos=0, rot=1) + + ik_wristDupOffset = pm.duplicate(ik_wristDup, parentOnly=1, n='ik_wristDup_offset')[0] + pm.parent(ik_wristDupOffset, ik_wristDup) + + clist.append(pm.parentConstraint(fkwrist,fk_wristDup, mo=0)) + + + # restore fk + pm.xform(fkshldr, ro=fksRotRaw) + pm.xform(fkellbow, ro=fkeRotRaw) + pm.xform(fkwrist, ro=fkwRotRaw) + + #considering rotation offset + pm.setAttr('%s.rx'%ik_wristDupOffset, rotOffset[0]) + pm.setAttr('%s.ry'%ik_wristDupOffset, rotOffset[1]) + pm.setAttr('%s.rz'%ik_wristDupOffset, rotOffset[2]) + + + # pole vector + fkshldr_dup = pm.spaceLocator(n='fkShld_dup') + snap(fkshldr, fkshldr_dup) + pm.parent(fkshldr_dup, snapGrp) + fkellbow_dup = pm.spaceLocator(n='fkEllbow_dup') + snap(fkellbow, fkellbow_dup) + pm.parent(fkellbow_dup, snapGrp) + fkwrist_dup = pm.spaceLocator(n='fkwrist_dup') + snap(fkwrist, fkwrist_dup) + pm.parent(fkwrist_dup, snapGrp) + pvLoc = poleVectorPosition(fkshldr_dup, fkellbow_dup, fkwrist_dup, length=12, createLoc =1) + pm.select([fkshldr, fkellbow, fkwrist]) + pm.parent(pvLoc, snapGrp) + + # snap ik + for ctrl in [ikwrist, ikpv]: + if len(pm.keyframe(ctrl, q=1))>0: + pm.cutKey(ctrl, t=pm.currentTime()) + + snap(ik_wristDupOffset, ikwrist) + snap(pvLoc, ikpv, pos=1, rot=0) + + for ctrl in [ikwrist, ikpv]: + if len(pm.keyframe(ctrl, q=1))>0: + pm.setKeyframe(ctrl, t=pm.currentTime(), s=0) + + if debug == True: + clist.append(pm.parentConstraint(ik_wristDupOffset, ikwrist)) + + # clean up + if debug == False: + pm.delete(clist) + pm.delete(snapGrp) + + #pm.delete(pvLoc) + #if not debug: pm.delete(fkRotLocWs) + + # clean up eventually created keyframe on fk ctrl on switch frame + if len(fkwrist_key) == 0: + try : pm.cutKey(fkwrist, t=pm.currentTime()) + except: pass + if len(fkellbow_key) == 0: + try : pm.cutKey(fkellbow, t=pm.currentTime()) + except: pass + if len(fkshldr_key) == 0: + try : pm.cutKey(fkshldr, t=pm.currentTime()) + except: pass + + + # go to ik mode + if switch0isfk == 0: pm.setAttr(switch, 0) + else: pm.setAttr(switch, switchAttrRange) + + pm.dgdirty([ikwrist, ikpv]) + pm.dgdirty([fkwrist, fkellbow, fkshldr]) + + _logger.info( 'Done matching FK to IK.') + + + +def keyframeAll(fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk=1, rotOffset=[0,0,0]): + for ctrl in [fkwrist, fkellbow, fkshldr, ikwrist, ikpv]: + for attr in ['tx', 'ty', 'tz', 'rx', 'ry', 'rz']: + try: pm.setKeyframe(ctrl, at=attr) + except: pass + + pm.setKeyframe(switchCtrl, at=switchAttr) + + +def ikfkMatch(fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk=1, switchAttrRange=1, rotOffset=[0,0,0], side='R', limb='arm', guessUp=1, bendKneeAxis='+X'): + ''' + Snap fk to ik controls by building ik joint form fk control position and lining up to ik + Args: + Returns: + + ''' + ns = fkwrist.split(':')[0] + switch = '%s.%s'%(switchCtrl, switchAttr) + clist = [] + + if pm.objExists('snapGrp'): pm.delete('snapGrp') + snapGrp = pm.createNode('transform', name='snapGrp') + + # store if keyframe on ik attribute or not: + ikwrist_key, ikpv_key = pm.keyframe(ikwrist, q=1, t=pm.currentTime()),\ + pm.keyframe(ikpv, q=1, t=pm.currentTime()) + + _logger.info( 'matching. switch attr range is %s'%switchAttrRange ) + # go to fk mode to match correct position (some riggs use same foot ctrl for ik and fk) + if switch0isfk == 0: pm.setAttr(switch, switchAttrRange) # 0 is fk + else: pm.setAttr(switch, 0) + + # zero out fk + pm.xform(fkshldr, ro=(0,0,0)) + pm.xform(fkellbow, ro=(0,0,0)) + pm.xform(fkwrist, ro=(0,0,0)) + + try : pm.xform(fkshldr, t=(0,0,0)) + except:pass + try : pm.xform(fkellbow, t=(0,0,0)) + except:pass + try : pm.xform(fkwrist, t=(0,0,0)) + except:pass + + _logger.info('root loc') + pm.dgdirty([fkshldr, fkellbow, fkwrist]) + root_loc = pm.group(empty=1, n='fk_shld_root') + pm.parent(root_loc, snapGrp) + snap(fkshldr, root_loc) + + fkshldr_dup = pm.duplicate(fkshldr, parentOnly=1)[0] + fkellbow_dup = pm.duplicate(fkellbow, parentOnly=1)[0] + fkwrist_dup = pm.duplicate(fkwrist, parentOnly=1)[0] + + #unlock all of duplicate A's arrtibutes + basicTransforms = ['translateX','translateY','translateZ', 'translate', 'rotateX',' rotateY','rotateZ', 'rotate'] + for attr in basicTransforms: + #unlock attr + pm.setAttr((fkshldr_dup + '.' + attr), lock=False, k=True) + pm.setAttr((fkellbow_dup + '.' + attr), lock=False, k=True) + pm.setAttr((fkwrist_dup + '.' + attr), lock=False, k=True) + pm.select([fkshldr_dup, fkellbow_dup, fkwrist_dup]) + _logger.info('line up fk duplicates to fk controlssss %s %s %s'%(fkshldr_dup, fkellbow_dup, fkwrist_dup)) + + # line up fk duplicates to fk controls + pm.parent(fkshldr_dup, snapGrp) + snap(fkshldr, fkshldr_dup, pos=1, rot=1) + pm.parent(fkellbow_dup,fkshldr_dup) + snap(fkellbow, fkellbow_dup, pos=1, rot=1) + pm.parent(fkwrist_dup, fkellbow_dup) + snap(fkwrist, fkwrist_dup, pos=1, rot=1) + pm.select(snapGrp) + _logger.info('snapping fk shoulder to ik') + + root_ikSnap = pm.joint(n='root_ikSnap', p=pm.xform(fkshldr, t=1, q=1, ws=1), orientation=(0, 0, 0)) + pm.parent(root_ikSnap, root_loc) + snap(fkshldr, root_ikSnap, rot=1, pos=1) + ikshldr_jnt = pm.joint(n='ikshldr_jnt', p=pm.xform(fkshldr, t=1, q=1, ws=1), orientation=(0, 0, 0)) + snap(fkellbow, ikshldr_jnt, rot=1, pos=0) + try: snap(fkshldr, ikshldr_jnt, rot=0, pos=1) + except: pass + _logger.info('snapping fk ellbow to ik') + ikellbow_jnt = pm.joint(n='ikellbow_jnt', p=pm.xform(fkellbow, t=1, q=1, ws=1), orientation=(0, 0, 0)) + snap(fkellbow, ikellbow_jnt, rot=1, pos=0) + try: snap(fkellbow, ikellbow_jnt, rot=0, pos=1) + except: pass + _logger.info('snapping fk wrist to ik') + ikwrist_jnt = pm.joint(n='ikwrist_jnt', p=pm.xform(fkwrist, t=1, q=1, ws=1), orientation=(0, 0, 0)) + snap(fkellbow, ikwrist_jnt, rot=1, pos=0) + try: snap(fkwrist, ikwrist_jnt, rot=0, pos=1) + except: pass + #aimaxis = max(pm.getAttr('%s.tx'%ikellbow_jnt), pm.getAttr('%s.tx'%ikellbow_jnt), pm.getAttr('%s.tx'%ikellbow_jnt)) + _logger.info('freeze transform') + pm.makeIdentity(ikshldr_jnt, apply=1) + pm.makeIdentity(ikellbow_jnt, apply=1) + pm.makeIdentity(ikwrist_jnt, apply=1) + + multiplyer = 1 + if bendKneeAxis[0] == '-': + mutliplyer = -1 + if abs(pm.getAttr('%s.jointOrient%s'%(ikellbow_jnt, bendKneeAxis[1]))) < 0.1: + pm.warning('Warning small joint orient. Setting Prefferec Angle to Y ' ) + pm.setAttr('%s.preferredAngle%s'%(ikellbow_jnt, bendKneeAxis[1]), 12.0*multiplyer) + pm.setAttr('%s.jointOrient%s'%(ikellbow_jnt, bendKneeAxis[1]), 0.01*multiplyer) + + # pole vector + pole_ikSnap = pm.spaceLocator(n='pole_ikSnap') + pm.parent(pole_ikSnap, fkellbow_dup) + + _logger.info('snap pole ik to fkellbow knee bend axis is %s'%bendKneeAxis) + # temp pole vector position. use the ellbow could use poleVectorPos as well + snap(fkellbow_dup, pole_ikSnap) + + _logger.info('considering kneebendaxis. %s'%bendKneeAxis) + reverse = 1 + if side == 'L': reverse = -1 + + if bendKneeAxis == '-X': + pole_ikSnap.tz.set(pole_ikSnap.tz.get()+0.5*reverse) + elif bendKneeAxis == '+X': + pole_ikSnap.tz.set(pole_ikSnap.tz.get()-0.5*reverse) + elif bendKneeAxis == '-Y': + pole_ikSnap.tz.set(pole_ikSnap.tz.get()+0.5*reverse) + elif bendKneeAxis == '+Y': + pole_ikSnap.tz.set(pole_ikSnap.tx.get()-0.5*reverse) + elif bendKneeAxis == '-Z': + pole_ikSnap.ty.set(pole_ikSnap.ty.get()-0.5*reverse) + elif bendKneeAxis == '+Z': + pole_ikSnap.ty.set(pole_ikSnap.ty.get()+0.5*reverse) + + pm.parent(pole_ikSnap, snapGrp) + + # ik handle + ikHandle_ikSnap = pm.ikHandle(sj=ikshldr_jnt, ee=ikwrist_jnt, sol='ikRPsolver') + pm.parent(ikHandle_ikSnap[0], snapGrp) + + pm.poleVectorConstraint(pole_ikSnap, ikHandle_ikSnap[0]) + _logger.info( 'done polevector constraint' ) + + # wrist offset locator line up to zero out ikwrist + ikrot = pm.xform(ikwrist, q=1, ro=1) + pm.xform(ikwrist, ro=(0,0,0)) + ikwrist_loc = pm.spaceLocator(n='ikwrist_loc') + pm.setAttr('%s.rotateOrder'%ikwrist_loc, pm.getAttr('%s.rotateOrder'%ikwrist)) + pm.parent(ikwrist_loc, fkwrist_dup) + snap(fkwrist, ikwrist_loc, rot=0, pos=1) + snap(fkwrist, ikwrist_loc, rot=1, pos=0) + + ikwrist_loc_offset = pm.spaceLocator(n='ikwrist_loc_offset') + pm.setAttr('%s.rotateOrder'%ikwrist_loc_offset, pm.getAttr('%s.rotateOrder'%ikwrist)) + pm.parent(ikwrist_loc_offset, ikwrist_loc) + snap(ikwrist_jnt, ikwrist_loc_offset, rot=0, pos=1) + snap(fkwrist, ikwrist_loc_offset, rot=1, pos=0) + + # considering rotation offset (reverse) + _logger.info( 'considering rotation offset' ) + fkwrist_rotOrder = pm.getAttr('%s.rotateOrder'%fkwrist) + ikwrist_rotOrder = pm.getAttr('%s.rotateOrder'%ikwrist) + _logger.debug('rotation order ikwrist: %s. fkwrist: %s'%(fkwrist_rotOrder,ikwrist_rotOrder)) + pm.setAttr('%s.rx'%ikwrist_loc_offset, rotOffset[0] ) + pm.setAttr('%s.ry'%ikwrist_loc_offset, rotOffset[1] ) + pm.setAttr('%s.rz'%ikwrist_loc_offset, rotOffset[2] ) + + + # constrain fk ctrl dups to ikSnap locs + _logger.info( 'constrain fk ctrl dups to ikSnap locs' ) + clist.append(pm.parentConstraint(ikshldr_jnt, fkshldr_dup, skipTranslate = ['x', 'y', 'z'], mo=1) ) + clist.append(pm.parentConstraint(ikellbow_jnt, fkellbow_dup, skipTranslate = ['x', 'y', 'z'], mo=1) ) + clist.append(pm.parentConstraint(ikwrist_jnt, fkwrist_dup, mo=1) ) + + fkwrist_loc = pm.spaceLocator(n='fkwrist_loc') + pm.setAttr('%s.rotateOrder'%fkwrist_loc, pm.getAttr('%s.rotateOrder'%fkwrist)) + pm.parent(fkwrist_loc, ikwrist_loc_offset) + snap(fkwrist, fkwrist_loc) + pm.setAttr('%s.rx'%ikwrist_loc_offset,0) + pm.setAttr('%s.ry'%ikwrist_loc_offset, 0) + pm.setAttr('%s.rz'%ikwrist_loc_offset, 0) + + # rotate back ik + _logger.info( 'rotate back ik' ) + pm.xform(ikwrist, ro=ikrot) + clist.append(pm.parentConstraint(ikwrist, ikwrist_loc, mo=0) ) + + if debugZero: + return + + # switch to ik mode (some riggs use same foot ctrl for ik and fk) + if switch0isfk == 0: pm.setAttr(switch, 0) # 0 is fk + else: pm.setAttr(switch, switchAttrRange) + + # line up to ik wrist and pole + _logger.info( 'line up to ik wrist and pole' ) + clist.append(pm.pointConstraint(ikwrist, ikHandle_ikSnap[0])) + snap(ikpv, pole_ikSnap, rot=0, pos=1) + + # get wrist rotation + #snap(ikwrist, fkwrist_loc, rot=1, pos=0) + # snap(fkshldr_loc, fkshldr, rot=1, pos=0) + # snap(fkellbow_loc, fkellbow, rot=1, pos=0) + # snap(fkwrist_loc, fkwrist, rot=1, pos=0) + _logger.debug('snapping back to original fk') + # snap back to original fk ctlrs + for ctrl in [fkshldr, fkellbow, fkwrist]: + if len(pm.keyframe(ctrl, q=1))>0: + pm.cutKey(ctrl, t=pm.currentTime()) + + _logger.info( 'snap fk shoulder' ) + snap(fkshldr_dup, fkshldr, rot=1, pos=0) + try: snap(fkshldr_dup, fkshldr, pos=1) + except: pass + _logger.info( 'snap fk ellbow' ) + snap(fkellbow_dup, fkellbow, rot=1, pos=0) + try: snap(fkellbow_dup, fkellbow, pos=1) + except: pass + _logger.info( 'snap fk wrist' ) + snap(fkwrist_loc, fkwrist, rot=1, pos=0) + try: snap(fkwrist_loc, fkwrist, pos=1) + except: pass + + for ctrl in [fkshldr, fkellbow, fkwrist]: + if len(pm.keyframe(ctrl, q=1))>0: + pm.setKeyframe(ctrl, t=pm.currentTime(), s=0) + + pm.dgdirty([fkshldr, fkellbow, fkwrist]) + + # debug mode + if debug == True: + pm.parentConstraint(fkwrist_loc, fkwrist, mo=0, st=('x', 'y', 'z')) + + # clean up + if debug == False: + pm.delete(clist) + pm.delete(snapGrp) + + # clean up eventually created keyframe on ik ctrl on switch frame + if len(ikwrist_key) == 0: + try : pm.cutKey(ikwrist, t=pm.currentTime()) + except: pass + if len(ikpv_key) == 0: + try : pm.cutKey(ikpv, t=pm.currentTime()) + except: pass + + # set to ik + if switch0isfk == 0: pm.setAttr(switch, 1) + else: pm.setAttr(switch, 0) + + +def saveIKFkCtrls(limb, side, fkwrist, fkellbow, fkshldr, ikwrist, ikpv, switchCtrl, switchAttr, switch0isfk, switchAttrRange, rotOffset, bendKneeAxis): + ''' + limb = 'arm'/'leg + side = 'R'/'L' + ''' + sel = pm.selected() + ns = fkwrist.split(':')[0] if len(fkwrist.split(':')) > 1 else '' + storenode = ns + '__' + side + '_' + limb + '_IKFKSTORE' + _logger.info('Storenode is %s'%storenode) + if pm.objExists(storenode) == False: + storenode = pm.createNode('transform', n=storenode) + else: + message = 'Do you want to replace existing store node?' + confirm = pm.confirmDialog( title='Replace existing', message=message, button=['Yes','No'], + defaultButton='Yes', cancelButton='No', dismissString='No' ) + if confirm == 'Yes': + _logger.info('deleting existing store node') + pm.delete(storenode) + storenode = pm.createNode('transform', n=storenode) + else: + pm.select(sel) + return + + storenode = pm.PyNode(storenode) + storedic = {'fkwrist': fkwrist, 'fkellbow': fkellbow, 'fkshldr':fkshldr, 'ikwrist':ikwrist, 'ikpv':ikpv, 'switchCtrl':switchCtrl, 'switchAttr':switchAttr, 'switch0isfk':switch0isfk, 'attrRange':switchAttrRange, 'rotOffset':rotOffset, 'side':side, 'bendKneeAxis':bendKneeAxis} + for attrName, value in list(storedic.items()): + pm.addAttr(storenode, ln=attrName, dt='string', k=1) + storenode.attr(attrName).set('%s'%value) + + pm.select(sel) + return storenode + + + +def loadIkFkCtrl(ns, limb, side): + ''' + limb = 'arm'/'leg + side = 'R'/'L' + ''' + + storenodeRegex = ns + '__' + side + '_' + limb + '_IKFKSTORE' + _logger.info('loading %s '%storenodeRegex) + storenode = pm.ls(storenodeRegex) + storenode_referenced = False + if len(storenode) == 0: # referenced store node EDIT 23.11.23 + storenodeRegex = ns + ':__' + side + '_' + limb + '_IKFKSTORE' + storenode = pm.ls(storenodeRegex) + storenode_referenced = True + if len(storenode) == 0: + #_logger.info( 'No storenode found' ) + return {} + else: + storenode = storenode[0] + ns = storenode.split('__')[0] + storenode = ns + '__' + side + '_' + limb + '_IKFKSTORE' + + if pm.objExists(storenode) == False: + return {} + storenode = pm.PyNode(storenode) + + storedic = {'fkwrist': '', 'fkellbow': '', 'fkshldr':'', 'ikwrist':'', 'ikpv':'', 'switchCtrl':'', 'switchAttr':'', 'switch0isfk':'', 'attrRange':'', 'rotOffset':'', 'bendKneeAxis':'+X'} + i=0 + for attrName, value in list(storedic.items()): + try: + if storenode_referenced and i<6: # referenced store node EDIT 23.11.23 + storedic[attrName] = ns+storenode.attr(attrName).get() + else: + storedic[attrName] = storenode.attr(attrName).get() + except: + pm.warning('Missing Attribute %s. Please Save Store Node again.'%attrName) + storedic[attrName] = value + i=i+1 + + _logger.info('StoreNode found is %s'%storedic) + return storedic + + +def get_variable_name(var_value, main_var): + mvar = [key for key, val in list(main_var.items()) if val==var_value][0] + _logger.info( 'var: %s >> %s'%(mvar, var_value)) # 123 {'test_var': 123} test_var + return [mvar, var_value] + + + +def matchTransform(slave, master, rot=1, pos=1): + ''' + Mimicking innate matchTransform of maya 2016.5 and up + Args: + slave: this object will be moved to master + master: target position and rotation + ''' + + if rot == 0: + skipRotAxis=["x","y","z"] + else: + skipRotAxis = [] + if pos == 0: + skipTransAxis=["x","y","z"] + else: + skipTransAxis = [] + + if rot == 1: + target = pm.xform(master, q=1, ro=1, ws=1) + pm.xform(slave, ro=target, ws=1) + + if pos == 1: + + target = pm.xform(master, q=1, t=1, ws=1) + pm.xform(slave, t=target, ws=1) + +# Align with Parent Constrain +def snap(master=None, slave=None, pos=1, rot=1): + ''' + Snap slave to master. Check if attribute locked and skip + ''' + lastSel = pm.selected() + + if master == None: + master = pm.selected()[0] + if slave == None: + slave = pm.selected()[1:] + slaves = pm.ls(slave) + + ptC, ptR = [], [] + + # for each slave, parentconstrain for each position and rotation, skipping locked attributes + for slave in slaves: + + slaveDup = pm.duplicate(slave, parentOnly=True)[0] + _logger.debug('snapping slaveDup') + + #unlock all of duplicate A's arrtibutes + basicTransforms = ['translateX','translateY','translateZ', 'translate','rotateX','rotateY','rotateZ','rotate'] + for attr in basicTransforms: + #unlock attr + pm.setAttr((slaveDup + '.' + attr), lock=False, k=1) + + ptC=pm.parentConstraint(master, slaveDup, mo=False) + + if pos == 1: + for att in ['tx', 'ty', 'tz']: + if pm.getAttr('%s.%s'%(slave,att), l=1) == False: + pm.setAttr((slave + '.' + att), pm.getAttr((slaveDup + '.' + att))) + + _logger.info('Snap Constraining Traslation %s %s. Skiplist is '%(master, slave) ) + + + if rot == 1: + for att in ['rx', 'ry', 'rz']: + if pm.getAttr('%s.%s'%(slave,att), l=1) == False: + pm.setAttr((slave + '.' + att), pm.getAttr((slaveDup + '.' + att))) + + _logger.info('Snap Constraining Rotation %s %s. Skiplist is '%(master, slave)) + + pm.delete(ptC) + pm.delete(slaveDup) + + pm.select(lastSel) + + + +def poleVectorPosition(startJnt, midJnt, endJnt, length=12, createLoc =0): + + import maya.api.OpenMaya as om + + start = pm.xform(startJnt ,q= 1 ,ws = 1,t =1 ) + mid = pm.xform(midJnt ,q= 1 ,ws = 1,t =1 ) + end = pm.xform(endJnt ,q= 1 ,ws = 1,t =1 ) + startV = om.MVector(start[0] ,start[1],start[2]) + midV = om.MVector(mid[0] ,mid[1],mid[2]) + endV = om.MVector(end[0] ,end[1],end[2]) + + + startEnd = endV - startV + startMid = midV - startV + + # projection vector is vecA projected onto vecB + # it is calculated by dot product if one vector normalized + + # proj= vecA * vecB.normalized (dot product result is scalar) + proj = startMid * startEnd.normal() + + + # multiply proj scalar with normalized startEndVector to project it onto vector + startEndN = startEnd.normal() + projV = startEndN * proj + + arrowV = startMid - projV + arrowVN = arrowV.normal() + + # scale up to length and offset to midV + finalV = arrowVN*length + midV + + + if createLoc: + loc = pm.spaceLocator(n='polePos') + pm.xform(loc , ws =1 , t= (finalV.x , finalV.y ,finalV.z)) + return loc + + return finalV + + +def unlockAttributes(objects, attributes=['translateX','translateY','translateZ','rotateX',' rotateY','rotateZ', 'visibility']): + #unlock all of duplicate A's arrtibutes + for obj in objects: + for attr in attributes: + #unlock attr + pm.setAttr((obj + '.' + attr), lock=False, k=True) + pm.setAttr((obj + '.' + attr), lock=False, k=True) + pm.setAttr((obj + '.' + attr), lock=False, k=True) + if attr == 'visibility': + pm.setAttr((obj + '.' + attr), 1) + +def orientJoints(joints, aimAxis, upAxis, upDir, doAuto): + """ + * + * $joints is array of joints to orient + * $aimAxis = is xyz array of what axis of joint does aim + * $upAxis = is xyz array of what axis of joint does up + * $upDir = what vector to use for up direction? + * $doAuto = If possible will try to guess the up axis otherwise + * it will use prev joint up axis or else world upDir. + * + """ + + + nJnt=len(joints) + i = 0 + prevUp=pm.dt.Vector([0, 0, 0]) + # Now orient each joint + for i in range(0,nJnt): + childs=pm.listRelatives(joints[i], type=["transform", "joint"], children=1) + # First we need to unparent everything and then store that, + if len(childs)>0: + childs=pm.parent(childs, w=1) + # unparent and get NEW names in case they changed... + # Find parent for later in case we need it. + + parents=pm.listRelatives(joints[i], parent=1) + parent=parents[0] + # Now if we have a child joint...aim to that. + aimTgt="" + child = "" + for child in childs: + if pm.nodeType(child) == "joint": + aimTgt=str(child) + break + + + if aimTgt != "": + upVec=[0, 0, 0] + # First off...if $doAuto is on, we need to guess the cross axis dir. + # + if doAuto: + posJ=pm.xform(joints[i], q=1, rp=1, ws=1) + # Now since the first joint we want to match the second orientation + # we kind of hack the things passed in if it is the first joint + # ie: If the joint doesn't have a parent...OR if the parent it has + # has the "same" position as itself...then we use the "next" joints + # as the up cross calculations + # + posP=posJ + if parent != "": + posP=pm.xform(parent, q=1, rp=1, ws=1) + + tol=0.0001 + # How close to we consider "same"? + if parent == "" or (abs(posJ[0] - posP[0])<=tol and abs(posJ[1] - posP[1])<=tol and abs(posJ[2] - posP[2])<=tol): + aimChilds=pm.listRelatives(aimTgt, children=1) + aimChild="" + child = "" + for child in aimChilds: + if pm.nodeType(child) == "joint": + aimChild=str(child) + break + + + upVec=pm.mel.cJO_getCrossDir(joints[i], aimTgt, aimChild) + + + else: + upVec=pm.mel.cJO_getCrossDir(parent, joints[i], aimTgt) + + + if not doAuto or (upVec[0] == 0.0 and upVec[1] == 0.0 and upVec[2] == 0.0): + upVec=upDir + # or else use user set up Dir. if needed + + aCons=pm.aimConstraint(aimTgt, joints[i], + aim=(aimAxis[0], aimAxis[1], aimAxis[2]), + worldUpType="vector", + weight=1.0, + upVector=(upAxis[0], upAxis[1], upAxis[2]), + worldUpVector=(upVec[0], upVec[1], upVec[2])) + pm.delete(aCons) + # Now compare the up we used to the prev one. + curUp=pm.dt.Vector([upVec[0], upVec[1], upVec[2]]) + curUp=pm.dt.Vector(pm.mel.unit(curUp)) + dot=float(curUp * prevUp) + # dot product for angle betwen... + prevUp=pm.dt.Vector([upVec[0], upVec[1], upVec[2]]) + # store for later + if i>0 and dot<=0.0: + pm.xform(joints[i], r=1, os=1, ra=((aimAxis[0] * 180.0), (aimAxis[1] * 180.0), (aimAxis[2] * 180.0))) + # Adjust the rotation axis 180 if it looks like we've flopped the wrong way! + prevUp*=pm.dt.Vector(-1.0) + + pm.joint(joints[i], zso=1, e=1) + # And now finish clearing out joint axis... + pm.makeIdentity(joints[i], apply=True) + + + elif parent != "": + oCons=pm.orientConstraint(parent, joints[i], + weight=1.0) + # Otherwise if there is no target, just dup orienation of parent... + pm.delete(oCons) + # And now finish clearing out joint axis... + pm.joint(joints[i], zso=1, e=1) + pm.makeIdentity(joints[i], apply=True) + + if len(childs)>0: + pm.parent(childs, joints[i]) + # Now that we're done... reparent \ No newline at end of file