1 # SPDX-License-Identifier: GPL-2.0-or-later
5 from collections
import OrderedDict
6 from typing
import Union
, Optional
, Any
8 from .utils
.animation
import SCRIPT_REGISTER_BAKE
, SCRIPT_UTILITIES_BAKE
10 from . import base_generate
12 from rna_prop_ui
import rna_idprop_quote_path
14 from .utils
.rig
import get_rigify_layers
23 'from math import pi',
24 'from bpy.props import StringProperty',
25 'from mathutils import Euler, Matrix, Quaternion, Vector',
26 'from rna_prop_ui import rna_idprop_quote_path',
30 UI_BASE_UTILITIES
= '''
34 ############################
35 ## Math utility functions ##
36 ############################
38 def perpendicular_vector(v):
39 """ Returns a vector that is perpendicular to the one given.
40 The returned vector is _not_ guaranteed to be normalized.
42 # Create a vector that is not aligned with v.
43 # It doesn't matter what vector. Just any vector
44 # that's guaranteed to not be pointing in the same
46 if abs(v[0]) < abs(v[1]):
51 # Use cross product to generate a vector perpendicular to
52 # both tv and (more importantly) v.
56 def rotation_difference(mat1, mat2):
57 """ Returns the shortest-path rotational difference between two
60 q1 = mat1.to_quaternion()
61 q2 = mat2.to_quaternion()
62 angle = math.acos(min(1,max(-1,q1.dot(q2)))) * 2
64 angle = -angle + (2*pi)
67 def find_min_range(f,start_angle,delta=pi/8):
68 """ finds the range where lies the minimum of function f applied on bone_ik and bone_fk
72 while (angle > (start_angle - 2*pi)) and (angle < (start_angle + 2*pi)):
73 l_dist = f(angle-delta)
75 r_dist = f(angle+delta)
76 if min((l_dist,c_dist,r_dist)) == c_dist:
77 return (angle-delta,angle+delta)
81 def ternarySearch(f, left, right, absolutePrecision):
83 Find minimum of uni-modal function f() within [left, right]
84 To find the maximum, revert the if/else statement or revert the comparison.
87 #left and right are the current bounds; the maximum is between them
88 if abs(right - left) < absolutePrecision:
89 return (left + right)/2
91 leftThird = left + (right - left)/3
92 rightThird = right - (right - left)/3
94 if f(leftThird) > f(rightThird):
100 UTILITIES_FUNC_COMMON_IK_FK
= ['''
101 #########################################
102 ## "Visual Transform" helper functions ##
103 #########################################
105 def get_pose_matrix_in_other_space(mat, pose_bone):
106 """ Returns the transform matrix relative to pose_bone's current
107 transform space. In other words, presuming that mat is in
108 armature space, slapping the returned matrix onto pose_bone
109 should give it the armature-space transforms of mat.
111 return pose_bone.id_data.convert_space(matrix=mat, pose_bone=pose_bone, from_space='POSE', to_space='LOCAL')
114 def convert_pose_matrix_via_rest_delta(mat, from_bone, to_bone):
115 """Convert pose of one bone to another bone, preserving the rest pose difference between them."""
116 return mat @ from_bone.bone.matrix_local.inverted() @ to_bone.bone.matrix_local
119 def convert_pose_matrix_via_pose_delta(mat, from_bone, to_bone):
120 """Convert pose of one bone to another bone, preserving the current pose difference between them."""
121 return mat @ from_bone.matrix.inverted() @ to_bone.matrix
124 def get_local_pose_matrix(pose_bone):
125 """ Returns the local transform matrix of the given pose bone.
127 return get_pose_matrix_in_other_space(pose_bone.matrix, pose_bone)
130 def set_pose_translation(pose_bone, mat):
131 """ Sets the pose bone's translation to the same translation as the given matrix.
132 Matrix should be given in bone's local space.
134 pose_bone.location = mat.to_translation()
137 def set_pose_rotation(pose_bone, mat):
138 """ Sets the pose bone's rotation to the same rotation as the given matrix.
139 Matrix should be given in bone's local space.
141 q = mat.to_quaternion()
143 if pose_bone.rotation_mode == 'QUATERNION':
144 pose_bone.rotation_quaternion = q
145 elif pose_bone.rotation_mode == 'AXIS_ANGLE':
146 pose_bone.rotation_axis_angle[0] = q.angle
147 pose_bone.rotation_axis_angle[1] = q.axis[0]
148 pose_bone.rotation_axis_angle[2] = q.axis[1]
149 pose_bone.rotation_axis_angle[3] = q.axis[2]
151 pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode)
154 def set_pose_scale(pose_bone, mat):
155 """ Sets the pose bone's scale to the same scale as the given matrix.
156 Matrix should be given in bone's local space.
158 pose_bone.scale = mat.to_scale()
161 def match_pose_translation(pose_bone, target_bone):
162 """ Matches pose_bone's visual translation to target_bone's visual
164 This function assumes you are in pose mode on the relevant armature.
166 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
167 set_pose_translation(pose_bone, mat)
170 def match_pose_rotation(pose_bone, target_bone):
171 """ Matches pose_bone's visual rotation to target_bone's visual
173 This function assumes you are in pose mode on the relevant armature.
175 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
176 set_pose_rotation(pose_bone, mat)
179 def match_pose_scale(pose_bone, target_bone):
180 """ Matches pose_bone's visual scale to target_bone's visual
182 This function assumes you are in pose mode on the relevant armature.
184 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
185 set_pose_scale(pose_bone, mat)
188 ##############################
189 ## IK/FK snapping functions ##
190 ##############################
192 def correct_rotation(view_layer, bone_ik, target_matrix, *, ctrl_ik=None):
193 """ Corrects the ik rotation in ik2fk snapping functions
196 axis = target_matrix.to_3x3().col[1].normalized()
197 ctrl_ik = ctrl_ik or bone_ik
200 # Rotate the bone and return the actual angle between bones
201 ctrl_ik.rotation_euler[1] = angle
204 return -(bone_ik.vector.normalized().dot(axis))
206 if ctrl_ik.rotation_mode in {'QUATERNION', 'AXIS_ANGLE'}:
207 ctrl_ik.rotation_mode = 'ZXY'
209 start_angle = ctrl_ik.rotation_euler[1]
211 alpha_range = find_min_range(distance, start_angle)
212 alpha_min = ternarySearch(distance, alpha_range[0], alpha_range[1], pi / 180)
214 ctrl_ik.rotation_euler[1] = alpha_min
218 def correct_scale(view_layer, bone_ik, target_matrix, *, ctrl_ik=None):
219 """ Correct the scale of the base IK bone. """
220 input_scale = target_matrix.to_scale()
221 ctrl_ik = ctrl_ik or bone_ik
224 cur_scale = bone_ik.matrix.to_scale()
227 v * i / c for v, i, c in zip(bone_ik.scale, input_scale, cur_scale)
232 if all(abs((c - i)/i) < 0.01 for i, c in zip(input_scale, cur_scale)):
236 def match_pole_target(view_layer, ik_first, ik_last, pole, match_bone_matrix, length):
237 """ Places an IK chain's pole target to match ik_first's
238 transforms to match_bone. All bones should be given as pose bones.
239 You need to be in pose mode on the relevant armature object.
240 ik_first: first bone in the IK chain
241 ik_last: last bone in the IK chain
242 pole: pole target bone for the IK chain
243 match_bone: bone to match ik_first to (probably first bone in a matching FK chain)
244 length: distance pole target should be placed from the chain center
246 a = ik_first.matrix.to_translation()
247 b = ik_last.matrix.to_translation() + ik_last.vector
249 # Vector from the head of ik_first to the
253 # Get a vector perpendicular to ikv
254 pv = perpendicular_vector(ikv).normalized() * length
257 """ Set pole target's position based on a vector
258 from the arm center line.
260 # Translate pvi into armature space
261 pole_loc = a + (ikv/2) + pvi
263 # Set pole target to location
264 mat = get_pose_matrix_in_other_space(Matrix.Translation(pole_loc), pole)
265 set_pose_translation(pole, mat)
271 # Get the rotation difference between ik_first and match_bone
272 angle = rotation_difference(ik_first.matrix, match_bone_matrix)
274 # Try compensating for the rotation difference in both directions
275 pv1 = Matrix.Rotation(angle, 4, ikv) @ pv
277 ang1 = rotation_difference(ik_first.matrix, match_bone_matrix)
279 pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv
281 ang2 = rotation_difference(ik_first.matrix, match_bone_matrix)
283 # Do the one with the smaller angle
291 def parse_bone_names(names_string):
292 if names_string[0] == '[' and names_string[-1] == ']':
293 return eval(names_string)
299 # noinspection SpellCheckingInspection
300 UTILITIES_FUNC_OLD_ARM_FKIK
= ['''
301 ######################
302 ## IK Arm functions ##
303 ######################
305 def fk2ik_arm(obj, fk, ik):
306 """ Matches the fk bones in an arm rig to the ik bones.
308 fk: list of fk bone names
309 ik: list of ik bone names
311 view_layer = bpy.context.view_layer
312 uarm = obj.pose.bones[fk[0]]
313 farm = obj.pose.bones[fk[1]]
314 hand = obj.pose.bones[fk[2]]
315 uarmi = obj.pose.bones[ik[0]]
316 farmi = obj.pose.bones[ik[1]]
317 handi = obj.pose.bones[ik[2]]
319 if 'auto_stretch' in handi.keys():
320 # This is kept for compatibility with legacy rigify Human
322 if handi['auto_stretch'] == 0.0:
323 uarm['stretch_length'] = handi['stretch_length']
325 diff = (uarmi.vector.length + farmi.vector.length) / (uarm.vector.length + farm.vector.length)
326 uarm['stretch_length'] *= diff
329 match_pose_rotation(uarm, uarmi)
330 match_pose_scale(uarm, uarmi)
334 match_pose_rotation(farm, farmi)
335 match_pose_scale(farm, farmi)
339 match_pose_rotation(hand, handi)
340 match_pose_scale(hand, handi)
344 match_pose_translation(uarm, uarmi)
345 match_pose_rotation(uarm, uarmi)
346 match_pose_scale(uarm, uarmi)
350 #match_pose_translation(hand, handi)
351 match_pose_rotation(farm, farmi)
352 match_pose_scale(farm, farmi)
356 match_pose_translation(hand, handi)
357 match_pose_rotation(hand, handi)
358 match_pose_scale(hand, handi)
362 def ik2fk_arm(obj, fk, ik):
363 """ Matches the ik bones in an arm rig to the fk bones.
365 fk: list of fk bone names
366 ik: list of ik bone names
368 view_layer = bpy.context.view_layer
369 uarm = obj.pose.bones[fk[0]]
370 farm = obj.pose.bones[fk[1]]
371 hand = obj.pose.bones[fk[2]]
372 uarmi = obj.pose.bones[ik[0]]
373 farmi = obj.pose.bones[ik[1]]
374 handi = obj.pose.bones[ik[2]]
376 main_parent = obj.pose.bones[ik[4]]
378 if ik[3] != "" and main_parent['pole_vector']:
379 pole = obj.pose.bones[ik[3]]
386 # handi['stretch_length'] = uarm['stretch_length']
389 match_pose_translation(handi, hand)
390 match_pose_rotation(handi, hand)
391 match_pose_scale(handi, hand)
394 # Pole target position
395 match_pole_target(view_layer, uarmi, farmi, pole, uarm.matrix, (uarmi.length + farmi.length))
399 match_pose_translation(handi, hand)
400 match_pose_rotation(handi, hand)
401 match_pose_scale(handi, hand)
405 match_pose_translation(uarmi, uarm)
406 #match_pose_rotation(uarmi, uarm)
407 set_pose_rotation(uarmi, Matrix())
408 match_pose_scale(uarmi, uarm)
411 # Rotation Correction
412 correct_rotation(view_layer, uarmi, uarm.matrix)
414 correct_scale(view_layer, uarmi, uarm.matrix)
417 # noinspection SpellCheckingInspection
418 UTILITIES_FUNC_OLD_LEG_FKIK
= ['''
419 ######################
420 ## IK Leg functions ##
421 ######################
423 def fk2ik_leg(obj, fk, ik):
424 """ Matches the fk bones in a leg rig to the ik bones.
426 fk: list of fk bone names
427 ik: list of ik bone names
429 view_layer = bpy.context.view_layer
430 thigh = obj.pose.bones[fk[0]]
431 shin = obj.pose.bones[fk[1]]
432 foot = obj.pose.bones[fk[2]]
433 mfoot = obj.pose.bones[fk[3]]
434 thighi = obj.pose.bones[ik[0]]
435 shini = obj.pose.bones[ik[1]]
436 footi = obj.pose.bones[ik[2]]
437 mfooti = obj.pose.bones[ik[3]]
439 if 'auto_stretch' in footi.keys():
440 # This is kept for compatibility with legacy rigify Human
442 if footi['auto_stretch'] == 0.0:
443 thigh['stretch_length'] = footi['stretch_length']
445 diff = (thighi.vector.length + shini.vector.length) / (thigh.vector.length + shin.vector.length)
446 thigh['stretch_length'] *= diff
449 match_pose_rotation(thigh, thighi)
450 match_pose_scale(thigh, thighi)
454 match_pose_rotation(shin, shini)
455 match_pose_scale(shin, shini)
459 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot)
460 footmat = convert_pose_matrix_via_rest_delta(footmat, mfoot, foot)
461 set_pose_rotation(foot, footmat)
462 set_pose_scale(foot, footmat)
467 match_pose_translation(thigh, thighi)
468 match_pose_rotation(thigh, thighi)
469 match_pose_scale(thigh, thighi)
473 match_pose_rotation(shin, shini)
474 match_pose_scale(shin, shini)
478 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot)
479 footmat = convert_pose_matrix_via_rest_delta(footmat, mfoot, foot)
480 set_pose_rotation(foot, footmat)
481 set_pose_scale(foot, footmat)
485 def ik2fk_leg(obj, fk, ik):
486 """ Matches the ik bones in a leg rig to the fk bones.
488 fk: list of fk bone names
489 ik: list of ik bone names
491 view_layer = bpy.context.view_layer
492 thigh = obj.pose.bones[fk[0]]
493 shin = obj.pose.bones[fk[1]]
494 mfoot = obj.pose.bones[fk[2]]
496 foot = obj.pose.bones[fk[3]]
499 thighi = obj.pose.bones[ik[0]]
500 shini = obj.pose.bones[ik[1]]
501 footi = obj.pose.bones[ik[2]]
502 footroll = obj.pose.bones[ik[3]]
504 main_parent = obj.pose.bones[ik[6]]
506 if ik[4] != "" and main_parent['pole_vector']:
507 pole = obj.pose.bones[ik[4]]
510 mfooti = obj.pose.bones[ik[5]]
512 if (not pole) and (foot):
515 set_pose_rotation(footroll, Matrix())
519 footmat = get_pose_matrix_in_other_space(foot.matrix, footi)
520 footmat = convert_pose_matrix_via_rest_delta(footmat, mfooti, footi)
521 set_pose_translation(footi, footmat)
522 set_pose_rotation(footi, footmat)
523 set_pose_scale(footi, footmat)
527 match_pose_translation(thighi, thigh)
528 #match_pose_rotation(thighi, thigh)
529 set_pose_rotation(thighi, Matrix())
530 match_pose_scale(thighi, thigh)
533 # Rotation Correction
534 correct_rotation(view_layer, thighi, thigh.matrix)
538 if 'stretch_length' in footi.keys() and 'stretch_length' in thigh.keys():
539 # Kept for compat with legacy rigify Human
540 footi['stretch_length'] = thigh['stretch_length']
543 set_pose_rotation(footroll, Matrix())
547 footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi)
548 footmat = convert_pose_matrix_via_rest_delta(footmat, mfooti, footi)
549 set_pose_translation(footi, footmat)
550 set_pose_rotation(footi, footmat)
551 set_pose_scale(footi, footmat)
554 # Pole target position
555 match_pole_target(view_layer, thighi, shini, pole, thigh.matrix, (thighi.length + shini.length))
557 correct_scale(view_layer, thighi, thigh.matrix)
560 # noinspection SpellCheckingInspection
561 UTILITIES_FUNC_OLD_POLE
= ['''
562 ################################
563 ## IK Rotation-Pole functions ##
564 ################################
566 def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole):
568 rig_id = rig.data['rig_id']
569 leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
570 arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
571 leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
572 arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
574 controls = parse_bone_names(controls)
575 ik_ctrl = parse_bone_names(ik_ctrl)
576 fk_ctrl = parse_bone_names(fk_ctrl)
577 parent = parse_bone_names(parent)
578 pole = parse_bone_names(pole)
580 pbones = bpy.context.selected_pose_bones
581 bpy.ops.pose.select_all(action='DESELECT')
585 new_pole_vector_value = not rig.pose.bones[parent]['pole_vector']
587 if b.name in controls or b.name in ik_ctrl:
588 if limb_type == 'arm':
591 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
592 rig.pose.bones[controls[4]].bone.select = not new_pole_vector_value
593 rig.pose.bones[parent].bone.select = not new_pole_vector_value
594 rig.pose.bones[pole].bone.select = new_pole_vector_value
596 kwargs1 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
597 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
598 'hand_ik': controls[4]}
599 kwargs2 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
600 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
601 'pole': pole, 'main_parent': parent}
605 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
606 rig.pose.bones[controls[6]].bone.select = not new_pole_vector_value
607 rig.pose.bones[controls[5]].bone.select = not new_pole_vector_value
608 rig.pose.bones[parent].bone.select = not new_pole_vector_value
609 rig.pose.bones[pole].bone.select = new_pole_vector_value
611 kwargs1 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
612 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
613 'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
614 kwargs2 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
615 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
616 'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5],
617 'mfoot_ik': ik_ctrl[2], 'main_parent': parent}
620 rig.pose.bones[parent]['pole_vector'] = new_pole_vector_value
623 bpy.ops.pose.select_all(action='DESELECT')
626 # noinspection SpellCheckingInspection
627 REGISTER_OP_OLD_ARM_FKIK
= ['Rigify_Arm_FK2IK', 'Rigify_Arm_IK2FK']
629 # noinspection SpellCheckingInspection
630 UTILITIES_OP_OLD_ARM_FKIK
= ['''
631 ##################################
632 ## IK/FK Arm snapping operators ##
633 ##################################
635 class Rigify_Arm_FK2IK(bpy.types.Operator):
636 """ Snaps an FK arm to an IK arm.
638 bl_idname = "pose.rigify_arm_fk2ik_" + rig_id
639 bl_label = "Rigify Snap FK arm to IK"
640 bl_options = {'UNDO', 'INTERNAL'}
642 uarm_fk: StringProperty(name="Upper Arm FK Name")
643 farm_fk: StringProperty(name="Forerm FK Name")
644 hand_fk: StringProperty(name="Hand FK Name")
646 uarm_ik: StringProperty(name="Upper Arm IK Name")
647 farm_ik: StringProperty(name="Forearm IK Name")
648 hand_ik: StringProperty(name="Hand IK Name")
651 def poll(cls, context):
652 return (context.active_object != None and context.mode == 'POSE')
654 def execute(self, context):
655 fk2ik_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk],
656 ik=[self.uarm_ik, self.farm_ik, self.hand_ik])
660 class Rigify_Arm_IK2FK(bpy.types.Operator):
661 """ Snaps an IK arm to an FK arm.
663 bl_idname = "pose.rigify_arm_ik2fk_" + rig_id
664 bl_label = "Rigify Snap IK arm to FK"
665 bl_options = {'UNDO', 'INTERNAL'}
667 uarm_fk: StringProperty(name="Upper Arm FK Name")
668 farm_fk: StringProperty(name="Forerm FK Name")
669 hand_fk: StringProperty(name="Hand FK Name")
671 uarm_ik: StringProperty(name="Upper Arm IK Name")
672 farm_ik: StringProperty(name="Forearm IK Name")
673 hand_ik: StringProperty(name="Hand IK Name")
674 pole : StringProperty(name="Pole IK Name")
676 main_parent: StringProperty(name="Main Parent", default="")
679 def poll(cls, context):
680 return (context.active_object != None and context.mode == 'POSE')
682 def execute(self, context):
683 ik2fk_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk],
684 ik=[self.uarm_ik, self.farm_ik, self.hand_ik, self.pole, self.main_parent])
688 # noinspection SpellCheckingInspection
689 REGISTER_OP_OLD_LEG_FKIK
= ['Rigify_Leg_FK2IK', 'Rigify_Leg_IK2FK']
691 # noinspection SpellCheckingInspection
692 UTILITIES_OP_OLD_LEG_FKIK
= ['''
693 ##################################
694 ## IK/FK Leg snapping operators ##
695 ##################################
697 class Rigify_Leg_FK2IK(bpy.types.Operator):
698 """ Snaps an FK leg to an IK leg.
700 bl_idname = "pose.rigify_leg_fk2ik_" + rig_id
701 bl_label = "Rigify Snap FK leg to IK"
702 bl_options = {'UNDO', 'INTERNAL'}
704 thigh_fk: StringProperty(name="Thigh FK Name")
705 shin_fk: StringProperty(name="Shin FK Name")
706 foot_fk: StringProperty(name="Foot FK Name")
707 mfoot_fk: StringProperty(name="MFoot FK Name")
709 thigh_ik: StringProperty(name="Thigh IK Name")
710 shin_ik: StringProperty(name="Shin IK Name")
711 foot_ik: StringProperty(name="Foot IK Name")
712 mfoot_ik: StringProperty(name="MFoot IK Name")
715 def poll(cls, context):
716 return (context.active_object != None and context.mode == 'POSE')
718 def execute(self, context):
719 fk2ik_leg(context.active_object,
720 fk=[self.thigh_fk, self.shin_fk, self.foot_fk, self.mfoot_fk],
721 ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.mfoot_ik])
725 class Rigify_Leg_IK2FK(bpy.types.Operator):
726 """ Snaps an IK leg to an FK leg.
728 bl_idname = "pose.rigify_leg_ik2fk_" + rig_id
729 bl_label = "Rigify Snap IK leg to FK"
730 bl_options = {'UNDO', 'INTERNAL'}
732 thigh_fk: StringProperty(name="Thigh FK Name")
733 shin_fk: StringProperty(name="Shin FK Name")
734 mfoot_fk: StringProperty(name="MFoot FK Name")
735 foot_fk: StringProperty(name="Foot FK Name", default="")
736 thigh_ik: StringProperty(name="Thigh IK Name")
737 shin_ik: StringProperty(name="Shin IK Name")
738 foot_ik: StringProperty(name="Foot IK Name")
739 footroll: StringProperty(name="Foot Roll Name")
740 pole: StringProperty(name="Pole IK Name")
741 mfoot_ik: StringProperty(name="MFoot IK Name")
743 main_parent: StringProperty(name="Main Parent", default="")
746 def poll(cls, context):
747 return (context.active_object != None and context.mode == 'POSE')
749 def execute(self, context):
750 ik2fk_leg(context.active_object,
751 fk=[self.thigh_fk, self.shin_fk, self.mfoot_fk, self.foot_fk],
752 ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.footroll, self.pole,
753 self.mfoot_ik, self.main_parent])
757 REGISTER_OP_OLD_POLE
= ['Rigify_Rot2PoleSwitch']
759 UTILITIES_OP_OLD_POLE
= ['''
760 ###########################
761 ## IK Rotation Pole Snap ##
762 ###########################
764 class Rigify_Rot2PoleSwitch(bpy.types.Operator):
765 bl_idname = "pose.rigify_rot2pole_" + rig_id
766 bl_label = "Rotation - Pole toggle"
767 bl_description = "Toggles IK chain between rotation and pole target"
769 bone_name: StringProperty(default='')
770 limb_type: StringProperty(name="Limb Type")
771 controls: StringProperty(name="Controls string")
772 ik_ctrl: StringProperty(name="IK Controls string")
773 fk_ctrl: StringProperty(name="FK Controls string")
774 parent: StringProperty(name="Parent name")
775 pole: StringProperty(name="Pole name")
777 def execute(self, context):
781 bpy.ops.pose.select_all(action='DESELECT')
782 rig.pose.bones[self.bone_name].bone.select = True
784 rotPoleToggle(rig, self.limb_type, self.controls, self.ik_ctrl, self.fk_ctrl,
785 self.parent, self.pole)
789 REGISTER_RIG_OLD_ARM
= REGISTER_OP_OLD_ARM_FKIK
+ REGISTER_OP_OLD_POLE
791 UTILITIES_RIG_OLD_ARM
= [
792 *UTILITIES_FUNC_COMMON_IK_FK
,
793 *UTILITIES_FUNC_OLD_ARM_FKIK
,
794 *UTILITIES_FUNC_OLD_POLE
,
795 *UTILITIES_OP_OLD_ARM_FKIK
,
796 *UTILITIES_OP_OLD_POLE
,
799 REGISTER_RIG_OLD_LEG
= REGISTER_OP_OLD_LEG_FKIK
+ REGISTER_OP_OLD_POLE
801 UTILITIES_RIG_OLD_LEG
= [
802 *UTILITIES_FUNC_COMMON_IK_FK
,
803 *UTILITIES_FUNC_OLD_LEG_FKIK
,
804 *UTILITIES_FUNC_OLD_POLE
,
805 *UTILITIES_OP_OLD_LEG_FKIK
,
806 *UTILITIES_OP_OLD_POLE
,
809 ############################
810 # Default set of utilities #
811 ############################
826 class RigUI(bpy.types.Panel):
827 bl_space_type = 'VIEW_3D'
828 bl_region_type = 'UI'
829 bl_label = "Rig Main Properties"
830 bl_idname = "VIEW3D_PT_rig_ui_" + rig_id
834 def poll(self, context):
835 if context.mode != 'POSE':
838 return (context.active_object.data.get("rig_id") == rig_id)
839 except (AttributeError, KeyError, TypeError):
842 def draw(self, context):
844 pose_bones = context.active_object.pose.bones
846 selected_bones = set(bone.name for bone in context.selected_pose_bones)
847 selected_bones.add(context.active_pose_bone.name)
848 except (AttributeError, TypeError):
851 def is_selected(names):
852 # Returns whether any of the named bones are selected.
853 if isinstance(names, list) or isinstance(names, set):
854 return not selected_bones.isdisjoint(names)
855 elif names in selected_bones:
859 num_rig_separators = [-1]
861 def emit_rig_separator():
862 if num_rig_separators[0] >= 0:
864 num_rig_separators[0] += 1
867 UI_REGISTER_BAKE_SETTINGS
= ['RigBakeSettings']
869 UI_BAKE_SETTINGS
= '''
870 class RigBakeSettings(bpy.types.Panel):
871 bl_space_type = 'VIEW_3D'
872 bl_region_type = 'UI'
873 bl_label = "Rig Bake Settings"
874 bl_idname = "VIEW3D_PT_rig_bake_settings_" + rig_id
878 def poll(self, context):
879 return RigUI.poll(context) and find_action(context.active_object) is not None
881 def draw(self, context):
882 RigifyBakeKeyframesMixin.draw_common_bake_ui(context, self.layout)
886 def layers_ui(layers
, layout
):
887 """ Turn a list of booleans + a list of names into a layer UI.
891 class RigLayers(bpy.types.Panel):
892 bl_space_type = 'VIEW_3D'
893 bl_region_type = 'UI'
894 bl_label = "Rig Layers"
895 bl_idname = "VIEW3D_PT_rig_layers_" + rig_id
899 def poll(self, context):
901 return (context.active_object.data.get("rig_id") == rig_id)
902 except (AttributeError, KeyError, TypeError):
905 def draw(self, context):
907 col = layout.column()
912 if layout
[i
][1] not in rows
:
913 rows
[layout
[i
][1]] = []
914 rows
[layout
[i
][1]] += [(layout
[i
][0], i
)]
916 keys
= list(rows
.keys())
920 code
+= "\n row = col.row()\n"
922 for layer
in rows
[key
]:
924 code
+= "\n row = col.row()\n"
926 code
+= f
" row.prop(context.active_object.data, 'layers', "\
927 f
"index={layer[1]}, toggle=True, text='{layer[0]}')\n"
931 code
+= "\n row = col.row()"
932 code
+= "\n row.separator()"
933 code
+= "\n row = col.row()"
934 code
+= "\n row.separator()\n"
935 code
+= "\n row = col.row()\n"
936 code
+= " row.prop(context.active_object.data, 'layers', "\
937 "index=28, toggle=True, text='Root')\n"
942 def quote_parameters(positional
: list[Any
], named
: dict[str, Any
]):
943 """Quote the given positional and named parameters as a code string."""
944 positional_list
= [repr(v
) for v
in positional
]
945 named_list
= ["%s=%r" % (k
, v
) for k
, v
in named
.items()]
946 return ', '.join(positional_list
+ named_list
)
949 def indent_lines(lines
: list[str], indent
=4):
951 prefix
= ' ' * indent
952 return [prefix
+ line
for line
in lines
]
957 class PanelLayout(object):
958 """Utility class that builds code for creating a layout."""
960 parent
: Optional
['PanelLayout']
961 script
: 'ScriptGenerator'
964 items
: list[Union
[str, 'PanelLayout']]
966 def __init__(self
, parent
: Union
['PanelLayout', 'ScriptGenerator'], index
=0):
967 if isinstance(parent
, PanelLayout
):
969 self
.script
= parent
.script
978 self
.layout
= self
._get
_layout
_var
(index
)
982 def _get_layout_var(index
):
983 return 'layout' if index
== 0 else 'group' + str(index
)
985 def clear_empty(self
):
986 self
.is_empty
= False
989 self
.parent
.clear_empty()
991 def get_lines(self
) -> list[str]:
994 for item
in self
.items
:
995 if isinstance(item
, PanelLayout
):
996 lines
+= item
.get_lines()
1001 return self
.wrap_lines(lines
)
1005 def wrap_lines(self
, lines
):
1006 return self
.header
+ indent_lines(lines
, self
.indent
)
1008 def add_line(self
, line
: str):
1009 assert isinstance(line
, str)
1011 self
.items
.append(line
)
1016 def use_bake_settings(self
):
1017 """This panel contains operators that need the common Bake settings."""
1018 self
.parent
.use_bake_settings()
1020 def custom_prop(self
, bone_name
: str, prop_name
: str, **params
):
1021 """Add a custom property input field to the panel."""
1022 param_str
= quote_parameters([rna_idprop_quote_path(prop_name
)], params
)
1024 "%s.prop(pose_bones[%r], %s)" % (self
.layout
, bone_name
, param_str
)
1027 def operator(self
, operator_name
: str, *,
1028 properties
: Optional
[dict[str, Any
]] = None,
1030 """Add an operator call button to the panel."""
1031 name
= operator_name
.format_map(self
.script
.format_args
)
1032 param_str
= quote_parameters([name
], params
)
1033 call_str
= "%s.operator(%s)" % (self
.layout
, param_str
)
1035 self
.add_line("props = " + call_str
)
1036 for k
, v
in properties
.items():
1037 self
.add_line("props.%s = %r" % (k
, v
))
1039 self
.add_line(call_str
)
1041 def add_nested_layout(self
, method_name
: str, params
: dict[str, Any
]) -> 'PanelLayout':
1042 param_str
= quote_parameters([], params
)
1043 sub_panel
= PanelLayout(self
, self
.index
+ 1)
1044 sub_panel
.header
.append(f
'{sub_panel.layout} = {self.layout}.{method_name}({param_str})')
1045 self
.items
.append(sub_panel
)
1048 def row(self
, **params
):
1049 """Add a nested row layout to the panel."""
1050 return self
.add_nested_layout('row', params
)
1052 def column(self
, **params
):
1053 """Add a nested column layout to the panel."""
1054 return self
.add_nested_layout('column', params
)
1056 def split(self
, **params
):
1057 """Add a split layout to the panel."""
1058 return self
.add_nested_layout('split', params
)
1061 class BoneSetPanelLayout(PanelLayout
):
1062 """Panel restricted to a certain set of bones."""
1064 parent
: 'RigPanelLayout'
1066 def __init__(self
, rig_panel
: 'RigPanelLayout', bones
: frozenset[str]):
1067 assert isinstance(bones
, frozenset)
1068 super().__init
__(rig_panel
)
1070 self
.show_bake_settings
= False
1072 def clear_empty(self
):
1073 self
.parent
.bones |
= self
.bones
1075 super().clear_empty()
1077 def wrap_lines(self
, lines
):
1078 if self
.bones
!= self
.parent
.bones
:
1079 header
= ["if is_selected(%r):" % (set(self
.bones
))]
1080 return header
+ indent_lines(lines
)
1084 def use_bake_settings(self
):
1085 self
.show_bake_settings
= True
1086 if not self
.script
.use_bake_settings
:
1087 self
.script
.use_bake_settings
= True
1088 self
.script
.add_utilities(SCRIPT_UTILITIES_BAKE
)
1089 self
.script
.register_classes(SCRIPT_REGISTER_BAKE
)
1092 class RigPanelLayout(PanelLayout
):
1093 """Panel owned by a certain rig."""
1095 def __init__(self
, script
: 'ScriptGenerator', _rig
):
1096 super().__init
__(script
)
1098 self
.sub_panels
= OrderedDict()
1100 def wrap_lines(self
, lines
):
1101 header
= ["if is_selected(%r):" % (set(self
.bones
))]
1102 prefix
= ["emit_rig_separator()"]
1103 return header
+ indent_lines(prefix
+ lines
)
1105 def panel_with_selected_check(self
, control_names
):
1106 selected_set
= frozenset(control_names
)
1108 if selected_set
in self
.sub_panels
:
1109 return self
.sub_panels
[selected_set
]
1111 panel
= BoneSetPanelLayout(self
, selected_set
)
1112 self
.sub_panels
[selected_set
] = panel
1113 self
.items
.append(panel
)
1117 class ScriptGenerator(base_generate
.GeneratorPlugin
):
1118 """Generator plugin that builds the python script attached to the rig."""
1122 format_args
: dict[str, str]
1124 def __init__(self
, generator
):
1125 super().__init
__(generator
)
1127 self
.ui_scripts
= []
1128 self
.ui_imports
= UI_IMPORTS
.copy()
1129 self
.ui_utilities
= UI_UTILITIES
.copy()
1130 self
.ui_register
= UI_REGISTER
.copy()
1131 self
.ui_register_drivers
= []
1132 self
.ui_register_props
= []
1134 self
.ui_rig_panels
= OrderedDict()
1136 self
.use_bake_settings
= False
1138 # Structured panel code generation
1139 def panel_with_selected_check(self
, rig
, control_names
):
1140 """Add a panel section with restricted selection."""
1143 if rig_key
in self
.ui_rig_panels
:
1144 panel
= self
.ui_rig_panels
[rig_key
]
1146 panel
= RigPanelLayout(self
, rig
)
1147 self
.ui_rig_panels
[rig_key
] = panel
1149 return panel
.panel_with_selected_check(control_names
)
1152 def add_panel_code(self
, str_list
: list[str]):
1153 """Add raw code to the panel."""
1154 self
.ui_scripts
+= str_list
1156 def add_imports(self
, str_list
: list[str]):
1157 self
.ui_imports
+= str_list
1159 def add_utilities(self
, str_list
: list[str]):
1160 self
.ui_utilities
+= str_list
1162 def register_classes(self
, str_list
: list[str]):
1163 self
.ui_register
+= str_list
1165 def register_driver_functions(self
, str_list
: list[str]):
1166 self
.ui_register_drivers
+= str_list
1168 def register_property(self
, name
: str, definition
):
1169 self
.ui_register_props
.append((name
, definition
))
1171 def initialize(self
):
1172 self
.format_args
= {
1173 'rig_id': self
.generator
.rig_id
,
1177 metarig
= self
.generator
.metarig
1178 rig_id
= self
.generator
.rig_id
1180 vis_layers
= self
.obj
.data
.layers
1182 # Ensure the collection of layer names exists
1183 rigify_layers
= get_rigify_layers(metarig
.data
)
1185 for i
in range(1 + len(rigify_layers
), 29):
1188 # Create list of layer name/row pairs
1190 for layer
in rigify_layers
:
1191 layer_layout
+= [(layer
.name
, layer
.row
)]
1193 # Generate the UI script
1194 script
= metarig
.data
.rigify_rig_ui
1199 script_name
= self
.generator
.obj
.name
+ "_ui.py"
1200 script
= bpy
.data
.texts
.new(script_name
)
1201 metarig
.data
.rigify_rig_ui
= script
1203 for s
in OrderedDict
.fromkeys(self
.ui_imports
):
1204 script
.write(s
+ "\n")
1206 script
.write(UI_BASE_UTILITIES
% rig_id
)
1208 for s
in OrderedDict
.fromkeys(self
.ui_utilities
):
1209 script
.write(s
+ "\n")
1211 script
.write(UI_SLIDERS
)
1213 for s
in self
.ui_scripts
:
1214 script
.write("\n " + s
.replace("\n", "\n ") + "\n")
1216 if len(self
.ui_scripts
) > 0:
1217 script
.write("\n num_rig_separators[0] = 0\n")
1219 for panel
in self
.ui_rig_panels
.values():
1220 lines
= panel
.get_lines()
1222 script
.write("\n ".join([''] + lines
) + "\n")
1224 if self
.use_bake_settings
:
1225 self
.ui_register
= UI_REGISTER_BAKE_SETTINGS
+ self
.ui_register
1226 script
.write(UI_BAKE_SETTINGS
)
1228 script
.write(layers_ui(vis_layers
, layer_layout
))
1230 script
.write("\ndef register():\n")
1232 ui_register
= OrderedDict
.fromkeys(self
.ui_register
)
1233 for s
in ui_register
:
1234 script
.write(" bpy.utils.register_class("+s
+")\n")
1236 ui_register_drivers
= OrderedDict
.fromkeys(self
.ui_register_drivers
)
1237 for s
in ui_register_drivers
:
1238 script
.write(" bpy.app.driver_namespace['"+s
+"'] = "+s
+"\n")
1240 ui_register_props
= OrderedDict
.fromkeys(self
.ui_register_props
)
1241 for classname
, text
in ui_register_props
:
1242 script
.write(f
" bpy.types.{classname} = {text}\n ")
1244 script
.write("\ndef unregister():\n")
1246 for s
in ui_register_props
:
1247 script
.write(" del bpy.types.%s\n" % s
[0])
1249 for s
in ui_register
:
1250 script
.write(" bpy.utils.unregister_class("+s
+")\n")
1252 for s
in ui_register_drivers
:
1253 script
.write(" del bpy.app.driver_namespace['"+s
+"']\n")
1255 script
.write("\nregister()\n")
1256 script
.use_module
= True
1259 exec(script
.as_string(), {})
1261 # Attach the script to the rig
1262 self
.obj
['rig_ui'] = script