@@ -957,28 +957,52 @@ def _get_mth(name):
957957
958958 # if is IK then snap FK
959959 if val == ik_val :
960-
961960 for target , ctl in zip (fk_targets , fk_ctrls ):
962961 transform .matchWorldTransform (target , ctl )
963962 pm .setAttr (o_attr , fk_val )
964963
965964 # if is FK then snap IK
966965 elif val == fk_val :
967- transform .matchWorldTransform (ik_targets ["ik_control" ], ik_ctrl ["ik_control" ])
968- transform .matchWorldTransform (ik_targets ["pole_vector" ], ik_ctrl ["pole_vector" ])
966+ transform .matchWorldTransform (
967+ ik_targets ["ik_control" ], ik_ctrl ["ik_control" ]
968+ )
969+ transform .matchWorldTransform (
970+ ik_targets ["pole_vector" ], ik_ctrl ["pole_vector" ]
971+ )
972+
969973 try :
970- transform .matchWorldTransform (ik_targets ["toes_ik" ], ik_ctrl ["toes_ik" ])
971- transform .matchWorldTransform (ik_targets ["toeRollIk" ], ik_ctrl ["toeRollIk" ])
972- transform .matchWorldTransform (ik_targets ["heelIk" ], ik_ctrl ["heelIk" ])
973974 transform .matchWorldTransform (
974- ik_targets ["reverse_ankle_ik" ], ik_ctrl ["reverse_ankle_ik" ]
975+ ik_targets ["toes_ik" ], ik_ctrl ["toes_ik" ]
976+ )
977+ transform .matchWorldTransform (
978+ ik_targets ["toeRollIk" ], ik_ctrl ["toeRollIk" ]
979+ )
980+ transform .matchWorldTransform (
981+ ik_targets ["heelIk" ], ik_ctrl ["heelIk" ]
982+ )
983+ transform .matchWorldTransform (
984+ ik_targets ["reverse_ankle_ik" ],
985+ ik_ctrl ["reverse_ankle_ik" ],
986+ )
987+ match_fk_to_ik_arbitrary_lengths (
988+ fk_controls , ui_node , ikfk_attr ,
989+ ik_ctrl ["pole_vector" ],
975990 )
976- match_fk_to_ik_arbitrary_lengths (fk_controls , ui_node ,
977- ikfk_attr , ik_ctrl ["pole_vector" ])
978991 except KeyError :
979992 pass
993+
994+ # Set the blend attr to IK BEFORE matching roll, because
995+ # the rig needs to evaluate in IK mode first so the roll
996+ # control's parent hierarchy is in its final state.
980997 pm .setAttr (o_attr , ik_val )
981998
999+ # Match roll control after blend switch
1000+ if "roll" in ik_ctrl and ik_ctrl ["roll" ] is not None :
1001+ if "roll" in ik_targets and ik_targets ["roll" ] is not None :
1002+ transform .matchWorldTransform (
1003+ ik_targets ["roll" ], ik_ctrl ["roll" ]
1004+ )
1005+
9821006 # sets keyframes
9831007 if keyframe :
9841008 [
@@ -1234,10 +1258,20 @@ def _get_mth(name):
12341258 roll_att = ik_ctrl .attr (roll_att_name )
12351259 roll_att .set (0.0 )
12361260
1237- # reset roll ctl if exist
1261+ # match roll ctl to its reference if it exists,
1262+ # otherwise reset rotateX (arm components have no roll_mth)
12381263 if ik_controls and "roll" in ik_controls .keys ():
1239- roll_ctl = _get_node (ik_controls ["roll" ])
1240- roll_ctl .rotateX .set (0 )
1264+ roll_ctl_node = _get_node (ik_controls ["roll" ])
1265+ if roll_ctl_node .hasAttr ("match_ref" ):
1266+ match_nodes = roll_ctl_node .match_ref .listConnections ()
1267+ if match_nodes :
1268+ transform .matchWorldTransform (
1269+ match_nodes [0 ], roll_ctl_node
1270+ )
1271+ else :
1272+ roll_ctl_node .rotateX .set (0 )
1273+ else :
1274+ roll_ctl_node .rotateX .set (0 )
12411275
12421276 # reset IK foot ctls
12431277 if foot_cnx :
@@ -2614,99 +2648,86 @@ def bakeAnimation(
26142648
26152649# Functions to support arbitraty limb length for FK to IK
26162650
2617- def match_fk_to_ik_scale_slide (arm_ctl , forearm_ctl , hand_ctl ,
2618- ui_host , scale_attr = ' scale' ,
2619- slide_attr = ' slide' ):
2651+ def match_fk_to_ik_scale_slide (fk_controls , ui_host ,
2652+ scale_attr = " scale" ,
2653+ slide_attr = " slide" ):
26202654 """Match FK limb to IK using scale and slide on a uiHost node.
26212655
2656+ Supports chains with 2 or more segments (3+ FK controls).
2657+
26222658 Args:
2623- arm_ctl (str): Arm or upper leg FK control.
2624- forearm_ctl (str): Forearm or lower leg FK control.
2625- hand_ctl (str): Hand or foot FK control.
2659+ fk_controls (list): FK control names in chain order.
26262660 ui_host (str): Node where scale/slide attrs live.
26272661 scale_attr (str): Name of scale attribute.
26282662 slide_attr (str): Name of slide attribute.
26292663
26302664 Raises:
2631- RuntimeError: On missing nodes or zero‐ length setup.
2665+ RuntimeError: On missing nodes or zero- length setup.
26322666 """
2633- # get default max values and rest values
26342667 s_path = "{}.{}" .format (ui_host , scale_attr )
26352668 sl_path = "{}.{}" .format (ui_host , slide_attr )
26362669 s_path_info = attribute .get_attr_info (ui_host , scale_attr )
26372670 sl_path_info = attribute .get_attr_info (ui_host , slide_attr )
26382671 s_default_val = s_path_info ["default" ]
2639- s_min_val = s_path_info ["min" ]
2640- s_max_val = s_path_info ["max" ]
2641-
2642- sl_default_val = sl_path_info ["default" ]
26432672 sl_min_val = sl_path_info ["min" ]
26442673 sl_max_val = sl_path_info ["max" ]
26452674
2646- # verify controls & parents
26472675 def parent_of (obj ):
2648- p = cmds .listRelatives (obj , parent = True , f = True )
2676+ p = cmds .listRelatives (obj , parent = True , fullPath = True )
26492677 if not p :
26502678 raise RuntimeError ("No parent for {}" .format (obj ))
26512679 return p [0 ]
26522680
2653- for ctl in ( arm_ctl , forearm_ctl , hand_ctl ) :
2681+ for ctl in fk_controls :
26542682 if not cmds .objExists (ctl ):
26552683 raise RuntimeError ("Control not found: {}" .format (ctl ))
26562684
2657- # arm_p = parent_of(arm_ctl)
2658- # arm_p_p = parent_of(arm_p)
2659- fore_p = parent_of (forearm_ctl )
2660- fore_p_p = parent_of (fore_p )
2661- hand_p = parent_of (hand_ctl )
2662- hand_p_p = parent_of (hand_p )
2663-
2664- # rest lengths
2665- rest_upper = vector .getDistance2 (fore_p_p , fore_p )
2666- rest_lower = vector .getDistance2 (hand_p_p , hand_p )
2667- # rest_lower = 2.0
2668- rest_total = rest_upper + rest_lower
2669- # print("Rest lengths: upper={:.3f}, lower={:.3f}, total={:.3f}"
2670- # .format(rest_upper, rest_lower, rest_total))
2685+ # Rest lengths from parent hierarchy for each segment
2686+ rest_segments = []
2687+ for ctl in fk_controls [1 :]:
2688+ ctl_p = parent_of (ctl )
2689+ ctl_pp = parent_of (ctl_p )
2690+ rest_segments .append (vector .getDistance2 (ctl_pp , ctl_p ))
2691+ rest_total = sum (rest_segments )
26712692
26722693 if rest_total == 0 :
26732694 raise RuntimeError ("Rest pose total length is zero." )
26742695
2675- # current lengths
2676- cur_upper = vector .getDistance2 (arm_ctl , forearm_ctl )
2677- cur_lower = vector .getDistance2 (forearm_ctl , hand_ctl )
2678- cur_total = cur_upper + cur_lower
2679- # print("Cur lengths: upper={:.3f}, lower={:.3f}, total={:.3f}"
2680- # .format(cur_upper, cur_lower, cur_total))
2696+ # Current lengths between consecutive FK controls
2697+ cur_segments = []
2698+ for i in range (len (fk_controls ) - 1 ):
2699+ cur_segments .append (
2700+ vector .getDistance2 (fk_controls [i ], fk_controls [i + 1 ])
2701+ )
2702+ cur_total = sum (cur_segments )
26812703
2682- # scale
2704+ # Scale
26832705 scale_val = cur_total / rest_total
2684- # print("Scale value: {:.3f}".format(scale_val))
26852706
2686- # slide: piecewise around rest ratio
2707+ # Slide: ratio of upper portion vs total
2708+ mid_idx = len (fk_controls ) // 2
2709+ rest_upper = sum (rest_segments [:mid_idx ])
2710+ cur_upper = sum (cur_segments [:mid_idx ])
26872711 rest_ratio = rest_upper / rest_total
2688- cur_ratio = cur_upper / cur_total
2689- # print("Ratios: rest_ratio={:.3f}, cur_ratio={:.3f}"
2690- # .format(rest_ratio, cur_ratio))
2712+ cur_ratio = cur_upper / cur_total if cur_total > 0 else rest_ratio
26912713
26922714 if cur_ratio <= rest_ratio :
26932715 slide_val = (cur_ratio / rest_ratio ) * 0.5
26942716 else :
2695- slide_val = 0.5 + ((cur_ratio - rest_ratio ) / (1 - rest_ratio )) \
2696- * 0.5
2717+ slide_val = (
2718+ 0.5
2719+ + ((cur_ratio - rest_ratio ) / (1 - rest_ratio )) * 0.5
2720+ )
26972721
26982722 slide_val = max (0.0 , min (1.0 , slide_val ))
2699- # print("Slide value: {:.3f}".format(slide_val))
27002723
2701- # map normalized values to attribute ranges
2724+ # Map normalized values to attribute ranges
27022725 if s_default_val is not None :
27032726 scale_val = scale_val * s_default_val
27042727
27052728 if sl_min_val is not None and sl_max_val is not None :
27062729 slide_val = (sl_max_val - sl_min_val ) * slide_val + sl_min_val
27072730
2708- # set attrs
2709-
27102731 for p in (s_path , sl_path ):
27112732 if not cmds .objExists (p ):
27122733 raise RuntimeError ("Missing attribute: {}" .format (p ))
@@ -2716,33 +2737,35 @@ def parent_of(obj):
27162737
27172738
27182739
2719- def place_upv_from_fk (arm_ctl , forearm_ctl , hand_ctl ,
2720- upv_ctl , distance_multiplier = 2.0 ):
2740+ def place_upv_from_fk (fk_controls , upv_ctl , distance_multiplier = 2.0 ):
27212741 """Place up vector control based on the FK plane.
27222742
2723- Calculates the pole vector (up vector) position defined by the FK
2724- controls and places the upv_ctl at that position.
2743+ Uses the first, middle, and last FK controls to define the limb
2744+ plane and places the pole vector perpendicular to it.
2745+ Supports chains with 3 or more FK controls.
27252746
27262747 Args:
2727- arm_ctl (str): Arm or upper leg FK control.
2728- forearm_ctl (str): Forearm or lower leg FK control.
2729- hand_ctl (str): Hand or foot FK control.
2748+ fk_controls (list): FK control names in chain order.
27302749 upv_ctl (str): Up vector control to be moved.
27312750 distance_multiplier (float): Distance scale factor.
27322751
27332752 Raises:
27342753 RuntimeError: If any control does not exist.
27352754 """
2736- for ctl in [ arm_ctl , forearm_ctl , hand_ctl , upv_ctl ]:
2755+ for ctl in list ( fk_controls ) + [ upv_ctl ]:
27372756 if not cmds .objExists (ctl ):
27382757 raise RuntimeError ("Control not found: {}" .format (ctl ))
27392758
2740- v1 = vector . get_mvector ( arm_ctl )
2741- v2 = vector . get_mvector ( forearm_ctl )
2742- v3 = vector . get_mvector ( hand_ctl )
2759+ first = fk_controls [ 0 ]
2760+ mid = fk_controls [ len ( fk_controls ) // 2 ]
2761+ last = fk_controls [ - 1 ]
27432762
2744- a = v2 - v1 # vector from arm to elbow
2745- b = v3 - v1 # vector from arm to wrist
2763+ v1 = vector .get_mvector (first )
2764+ v2 = vector .get_mvector (mid )
2765+ v3 = vector .get_mvector (last )
2766+
2767+ a = v2 - v1
2768+ b = v3 - v1
27462769
27472770 b_normalized = b .normal ()
27482771 proj = a * b_normalized
@@ -2761,23 +2784,26 @@ def match_fk_to_ik_arbitrary_lengths(fk_controls, ui_host,
27612784 blend_attr , upv_ctl ):
27622785 """Match FK to IK for arbitrary limb lengths.
27632786
2787+ Supports chains with 3 or more FK controls (2jnt, 3jnt, etc.).
2788+
27642789 Args:
2765- fk_controls (list[str or PyNode] ): [arm_ctl, forearm_ctl, hand_ctl] .
2790+ fk_controls (list): FK controls as strings or PyNodes .
27662791 ui_host (str or PyNode): Node with blend attr.
27672792 blend_attr (str): Name of blend or switch attr.
27682793 upv_ctl (str or PyNode): Up-vector control.
27692794
27702795 Returns:
27712796 bool: True if match ran, False if attrs missing.
27722797 """
2773- arm_str , fore_str , hand_str = [], [], []
27742798 names = []
27752799 for c in fk_controls :
27762800 if not isinstance (c , str ):
27772801 names .append (c .name ())
27782802 else :
27792803 names .append (c )
2780- arm_str , fore_str , hand_str = names
2804+
2805+ if len (names ) < 3 :
2806+ return False
27812807
27822808 if not isinstance (ui_host , str ):
27832809 ui_node = ui_host
@@ -2791,45 +2817,46 @@ def match_fk_to_ik_arbitrary_lengths(fk_controls, ui_host,
27912817 else :
27922818 upv_str = upv_ctl
27932819
2794- # Derive scale/slide attr names
2795- if blend_attr .endswith ('_blend' ):
2820+ if blend_attr .endswith ("_blend" ):
27962821 base = blend_attr [:- 6 ]
2797- elif blend_attr .endswith (' _Switch' ):
2822+ elif blend_attr .endswith (" _Switch" ):
27982823 base = blend_attr [:- 7 ]
27992824 else :
28002825 base = blend_attr
2801- scale_attr = base + ' _ikscale'
2802- slide_attr = base + ' _slide'
2826+ scale_attr = base + " _ikscale"
2827+ slide_attr = base + " _slide"
28032828
2804- # Check required attrs on PyNode
28052829 if not ui_node .hasAttr (scale_attr ) or not ui_node .hasAttr (
2806- slide_attr ):
2830+ slide_attr
2831+ ):
28072832 return False
28082833
2809- keyframe = pm .keyframe (f"{ ui_str } .{ blend_attr } " ,
2810- query = True ,
2811- keyframeCount = True )
2812-
2813- # if keyframe:
2814- # cmds.setKeyframe(f"{ui_str}.{scale_attr}", time=(cmds.currentTime(query=True) - 1.0))
2815- # cmds.setKeyframe(f"{ui_str}.{slide_attr}", time=(cmds.currentTime(query=True) - 1.0))
2834+ keyframe = pm .keyframe (
2835+ "{}.{}" .format (ui_str , blend_attr ),
2836+ query = True ,
2837+ keyframeCount = True ,
2838+ )
28162839
2817- # Run FK to IK match
28182840 match_fk_to_ik_scale_slide (
2819- arm_ctl = arm_str , forearm_ctl = fore_str ,
2820- hand_ctl = hand_str , ui_host = ui_str ,
2821- scale_attr = scale_attr , slide_attr = slide_attr
2841+ fk_controls = names ,
2842+ ui_host = ui_str ,
2843+ scale_attr = scale_attr ,
2844+ slide_attr = slide_attr ,
28222845 )
28232846
2824- # Place up-vector
28252847 place_upv_from_fk (
2826- arm_ctl = arm_str , forearm_ctl = fore_str ,
2827- hand_ctl = hand_str , upv_ctl = upv_str ,
2828- distance_multiplier = 1.0
2848+ fk_controls = names ,
2849+ upv_ctl = upv_str ,
2850+ distance_multiplier = 1.0 ,
28292851 )
28302852
28312853 if keyframe :
2832- cmds .setKeyframe (f"{ ui_str } .{ scale_attr } " , time = (cmds .currentTime (query = True )))
2833- cmds .setKeyframe (f"{ ui_str } .{ slide_attr } " , time = (cmds .currentTime (query = True )))
2854+ cur_time = cmds .currentTime (query = True )
2855+ cmds .setKeyframe (
2856+ "{}.{}" .format (ui_str , scale_attr ), time = cur_time
2857+ )
2858+ cmds .setKeyframe (
2859+ "{}.{}" .format (ui_str , slide_attr ), time = cur_time
2860+ )
28342861
28352862 return True
0 commit comments