1 # SPDX-FileCopyrightText: 2010-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
7 from collections
import OrderedDict
8 from typing
import Union
, Optional
, Any
10 from .utils
.animation
import SCRIPT_REGISTER_BAKE
, SCRIPT_UTILITIES_BAKE
11 from .utils
.mechanism
import quote_property
13 from . import base_generate
15 from rna_prop_ui
import rna_idprop_quote_path
24 'from math import pi',
25 'from bpy.props import StringProperty',
26 'from mathutils import Euler, Matrix, Quaternion, Vector',
27 'from rna_prop_ui import rna_idprop_quote_path',
31 UI_BASE_UTILITIES
= '''
35 ############################
36 ## Math utility functions ##
37 ############################
39 def perpendicular_vector(v):
40 """ Returns a vector that is perpendicular to the one given.
41 The returned vector is _not_ guaranteed to be normalized.
43 # Create a vector that is not aligned with v.
44 # It doesn't matter what vector. Just any vector
45 # that's guaranteed to not be pointing in the same
47 if abs(v[0]) < abs(v[1]):
52 # Use cross product to generate a vector perpendicular to
53 # both tv and (more importantly) v.
57 def rotation_difference(mat1, mat2):
58 """ Returns the shortest-path rotational difference between two
61 q1 = mat1.to_quaternion()
62 q2 = mat2.to_quaternion()
63 angle = math.acos(min(1,max(-1,q1.dot(q2)))) * 2
65 angle = -angle + (2*pi)
68 def find_min_range(f,start_angle,delta=pi/8):
69 """ finds the range where lies the minimum of function f applied on bone_ik and bone_fk
73 while (angle > (start_angle - 2*pi)) and (angle < (start_angle + 2*pi)):
74 l_dist = f(angle-delta)
76 r_dist = f(angle+delta)
77 if min((l_dist,c_dist,r_dist)) == c_dist:
78 return (angle-delta,angle+delta)
82 def ternarySearch(f, left, right, absolutePrecision):
84 Find minimum of uni-modal function f() within [left, right]
85 To find the maximum, revert the if/else statement or revert the comparison.
88 #left and right are the current bounds; the maximum is between them
89 if abs(right - left) < absolutePrecision:
90 return (left + right)/2
92 leftThird = left + (right - left)/3
93 rightThird = right - (right - left)/3
95 if f(leftThird) > f(rightThird):
101 UTILITIES_FUNC_COMMON_IK_FK
= ['''
102 #########################################
103 ## "Visual Transform" helper functions ##
104 #########################################
106 def get_pose_matrix_in_other_space(mat, pose_bone):
107 """ Returns the transform matrix relative to pose_bone's current
108 transform space. In other words, presuming that mat is in
109 armature space, slapping the returned matrix onto pose_bone
110 should give it the armature-space transforms of mat.
112 return pose_bone.id_data.convert_space(matrix=mat, pose_bone=pose_bone, from_space='POSE', to_space='LOCAL')
115 def convert_pose_matrix_via_rest_delta(mat, from_bone, to_bone):
116 """Convert pose of one bone to another bone, preserving the rest pose difference between them."""
117 return mat @ from_bone.bone.matrix_local.inverted() @ to_bone.bone.matrix_local
120 def convert_pose_matrix_via_pose_delta(mat, from_bone, to_bone):
121 """Convert pose of one bone to another bone, preserving the current pose difference between them."""
122 return mat @ from_bone.matrix.inverted() @ to_bone.matrix
125 def get_local_pose_matrix(pose_bone):
126 """ Returns the local transform matrix of the given pose bone.
128 return get_pose_matrix_in_other_space(pose_bone.matrix, pose_bone)
131 def set_pose_translation(pose_bone, mat):
132 """ Sets the pose bone's translation to the same translation as the given matrix.
133 Matrix should be given in bone's local space.
135 pose_bone.location = mat.to_translation()
138 def set_pose_rotation(pose_bone, mat):
139 """ Sets the pose bone's rotation to the same rotation as the given matrix.
140 Matrix should be given in bone's local space.
142 q = mat.to_quaternion()
144 if pose_bone.rotation_mode == 'QUATERNION':
145 pose_bone.rotation_quaternion = q
146 elif pose_bone.rotation_mode == 'AXIS_ANGLE':
147 pose_bone.rotation_axis_angle[0] = q.angle
148 pose_bone.rotation_axis_angle[1] = q.axis[0]
149 pose_bone.rotation_axis_angle[2] = q.axis[1]
150 pose_bone.rotation_axis_angle[3] = q.axis[2]
152 pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode)
155 def set_pose_scale(pose_bone, mat):
156 """ Sets the pose bone's scale to the same scale as the given matrix.
157 Matrix should be given in bone's local space.
159 pose_bone.scale = mat.to_scale()
162 def match_pose_translation(pose_bone, target_bone):
163 """ Matches pose_bone's visual translation to target_bone's visual
165 This function assumes you are in pose mode on the relevant armature.
167 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
168 set_pose_translation(pose_bone, mat)
171 def match_pose_rotation(pose_bone, target_bone):
172 """ Matches pose_bone's visual rotation to target_bone's visual
174 This function assumes you are in pose mode on the relevant armature.
176 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
177 set_pose_rotation(pose_bone, mat)
180 def match_pose_scale(pose_bone, target_bone):
181 """ Matches pose_bone's visual scale to target_bone's visual
183 This function assumes you are in pose mode on the relevant armature.
185 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
186 set_pose_scale(pose_bone, mat)
189 ##############################
190 ## IK/FK snapping functions ##
191 ##############################
193 def correct_rotation(view_layer, bone_ik, target_matrix, *, ctrl_ik=None):
194 """ Corrects the ik rotation in ik2fk snapping functions
197 axis = target_matrix.to_3x3().col[1].normalized()
198 ctrl_ik = ctrl_ik or bone_ik
201 # Rotate the bone and return the actual angle between bones
202 ctrl_ik.rotation_euler[1] = angle
205 return -(bone_ik.vector.normalized().dot(axis))
207 if ctrl_ik.rotation_mode in {'QUATERNION', 'AXIS_ANGLE'}:
208 ctrl_ik.rotation_mode = 'ZXY'
210 start_angle = ctrl_ik.rotation_euler[1]
212 alpha_range = find_min_range(distance, start_angle)
213 alpha_min = ternarySearch(distance, alpha_range[0], alpha_range[1], pi / 180)
215 ctrl_ik.rotation_euler[1] = alpha_min
219 def correct_scale(view_layer, bone_ik, target_matrix, *, ctrl_ik=None):
220 """ Correct the scale of the base IK bone. """
221 input_scale = target_matrix.to_scale()
222 ctrl_ik = ctrl_ik or bone_ik
225 cur_scale = bone_ik.matrix.to_scale()
228 v * i / c for v, i, c in zip(bone_ik.scale, input_scale, cur_scale)
233 if all(abs((c - i)/i) < 0.01 for i, c in zip(input_scale, cur_scale)):
237 def match_pole_target(view_layer, ik_first, ik_last, pole, match_bone_matrix, length):
238 """ Places an IK chain's pole target to match ik_first's
239 transforms to match_bone. All bones should be given as pose bones.
240 You need to be in pose mode on the relevant armature object.
241 ik_first: first bone in the IK chain
242 ik_last: last bone in the IK chain
243 pole: pole target bone for the IK chain
244 match_bone: bone to match ik_first to (probably first bone in a matching FK chain)
245 length: distance pole target should be placed from the chain center
247 a = ik_first.matrix.to_translation()
248 b = ik_last.matrix.to_translation() + ik_last.vector
250 # Vector from the head of ik_first to the
254 # Get a vector perpendicular to ikv
255 pv = perpendicular_vector(ikv).normalized() * length
258 """ Set pole target's position based on a vector
259 from the arm center line.
261 # Translate pvi into armature space
262 pole_loc = a + (ikv/2) + pvi
264 # Set pole target to location
265 mat = get_pose_matrix_in_other_space(Matrix.Translation(pole_loc), pole)
266 set_pose_translation(pole, mat)
272 # Get the rotation difference between ik_first and match_bone
273 angle = rotation_difference(ik_first.matrix, match_bone_matrix)
275 # Try compensating for the rotation difference in both directions
276 pv1 = Matrix.Rotation(angle, 4, ikv) @ pv
278 ang1 = rotation_difference(ik_first.matrix, match_bone_matrix)
280 pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv
282 ang2 = rotation_difference(ik_first.matrix, match_bone_matrix)
284 # Do the one with the smaller angle
292 def parse_bone_names(names_string):
293 if names_string[0] == '[' and names_string[-1] == ']':
294 return eval(names_string)
300 # noinspection SpellCheckingInspection
301 UTILITIES_FUNC_OLD_ARM_FKIK
= ['''
302 ######################
303 ## IK Arm functions ##
304 ######################
306 def fk2ik_arm(obj, fk, ik):
307 """ Matches the fk bones in an arm rig to the ik bones.
309 fk: list of fk bone names
310 ik: list of ik bone names
312 view_layer = bpy.context.view_layer
313 uarm = obj.pose.bones[fk[0]]
314 farm = obj.pose.bones[fk[1]]
315 hand = obj.pose.bones[fk[2]]
316 uarmi = obj.pose.bones[ik[0]]
317 farmi = obj.pose.bones[ik[1]]
318 handi = obj.pose.bones[ik[2]]
320 if 'auto_stretch' in handi.keys():
321 # This is kept for compatibility with legacy rigify Human
323 if handi['auto_stretch'] == 0.0:
324 uarm['stretch_length'] = handi['stretch_length']
326 diff = (uarmi.vector.length + farmi.vector.length) / (uarm.vector.length + farm.vector.length)
327 uarm['stretch_length'] *= diff
330 match_pose_rotation(uarm, uarmi)
331 match_pose_scale(uarm, uarmi)
335 match_pose_rotation(farm, farmi)
336 match_pose_scale(farm, farmi)
340 match_pose_rotation(hand, handi)
341 match_pose_scale(hand, handi)
345 match_pose_translation(uarm, uarmi)
346 match_pose_rotation(uarm, uarmi)
347 match_pose_scale(uarm, uarmi)
351 #match_pose_translation(hand, handi)
352 match_pose_rotation(farm, farmi)
353 match_pose_scale(farm, farmi)
357 match_pose_translation(hand, handi)
358 match_pose_rotation(hand, handi)
359 match_pose_scale(hand, handi)
363 def ik2fk_arm(obj, fk, ik):
364 """ Matches the ik bones in an arm rig to the fk bones.
366 fk: list of fk bone names
367 ik: list of ik bone names
369 view_layer = bpy.context.view_layer
370 uarm = obj.pose.bones[fk[0]]
371 farm = obj.pose.bones[fk[1]]
372 hand = obj.pose.bones[fk[2]]
373 uarmi = obj.pose.bones[ik[0]]
374 farmi = obj.pose.bones[ik[1]]
375 handi = obj.pose.bones[ik[2]]
377 main_parent = obj.pose.bones[ik[4]]
379 if ik[3] != "" and main_parent['pole_vector']:
380 pole = obj.pose.bones[ik[3]]
387 # handi['stretch_length'] = uarm['stretch_length']
390 match_pose_translation(handi, hand)
391 match_pose_rotation(handi, hand)
392 match_pose_scale(handi, hand)
395 # Pole target position
396 match_pole_target(view_layer, uarmi, farmi, pole, uarm.matrix, (uarmi.length + farmi.length))
400 match_pose_translation(handi, hand)
401 match_pose_rotation(handi, hand)
402 match_pose_scale(handi, hand)
406 match_pose_translation(uarmi, uarm)
407 #match_pose_rotation(uarmi, uarm)
408 set_pose_rotation(uarmi, Matrix())
409 match_pose_scale(uarmi, uarm)
412 # Rotation Correction
413 correct_rotation(view_layer, uarmi, uarm.matrix)
415 correct_scale(view_layer, uarmi, uarm.matrix)
418 # noinspection SpellCheckingInspection
419 UTILITIES_FUNC_OLD_LEG_FKIK
= ['''
420 ######################
421 ## IK Leg functions ##
422 ######################
424 def fk2ik_leg(obj, fk, ik):
425 """ Matches the fk bones in a leg rig to the ik bones.
427 fk: list of fk bone names
428 ik: list of ik bone names
430 view_layer = bpy.context.view_layer
431 thigh = obj.pose.bones[fk[0]]
432 shin = obj.pose.bones[fk[1]]
433 foot = obj.pose.bones[fk[2]]
434 mfoot = obj.pose.bones[fk[3]]
435 thighi = obj.pose.bones[ik[0]]
436 shini = obj.pose.bones[ik[1]]
437 footi = obj.pose.bones[ik[2]]
438 mfooti = obj.pose.bones[ik[3]]
440 if 'auto_stretch' in footi.keys():
441 # This is kept for compatibility with legacy rigify Human
443 if footi['auto_stretch'] == 0.0:
444 thigh['stretch_length'] = footi['stretch_length']
446 diff = (thighi.vector.length + shini.vector.length) / (thigh.vector.length + shin.vector.length)
447 thigh['stretch_length'] *= diff
450 match_pose_rotation(thigh, thighi)
451 match_pose_scale(thigh, thighi)
455 match_pose_rotation(shin, shini)
456 match_pose_scale(shin, shini)
460 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot)
461 footmat = convert_pose_matrix_via_rest_delta(footmat, mfoot, foot)
462 set_pose_rotation(foot, footmat)
463 set_pose_scale(foot, footmat)
468 match_pose_translation(thigh, thighi)
469 match_pose_rotation(thigh, thighi)
470 match_pose_scale(thigh, thighi)
474 match_pose_rotation(shin, shini)
475 match_pose_scale(shin, shini)
479 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot)
480 footmat = convert_pose_matrix_via_rest_delta(footmat, mfoot, foot)
481 set_pose_rotation(foot, footmat)
482 set_pose_scale(foot, footmat)
486 def ik2fk_leg(obj, fk, ik):
487 """ Matches the ik bones in a leg rig to the fk bones.
489 fk: list of fk bone names
490 ik: list of ik bone names
492 view_layer = bpy.context.view_layer
493 thigh = obj.pose.bones[fk[0]]
494 shin = obj.pose.bones[fk[1]]
495 mfoot = obj.pose.bones[fk[2]]
497 foot = obj.pose.bones[fk[3]]
500 thighi = obj.pose.bones[ik[0]]
501 shini = obj.pose.bones[ik[1]]
502 footi = obj.pose.bones[ik[2]]
503 footroll = obj.pose.bones[ik[3]]
505 main_parent = obj.pose.bones[ik[6]]
507 if ik[4] != "" and main_parent['pole_vector']:
508 pole = obj.pose.bones[ik[4]]
511 mfooti = obj.pose.bones[ik[5]]
513 if (not pole) and (foot):
516 set_pose_rotation(footroll, Matrix())
520 footmat = get_pose_matrix_in_other_space(foot.matrix, footi)
521 footmat = convert_pose_matrix_via_rest_delta(footmat, mfooti, footi)
522 set_pose_translation(footi, footmat)
523 set_pose_rotation(footi, footmat)
524 set_pose_scale(footi, footmat)
528 match_pose_translation(thighi, thigh)
529 #match_pose_rotation(thighi, thigh)
530 set_pose_rotation(thighi, Matrix())
531 match_pose_scale(thighi, thigh)
534 # Rotation Correction
535 correct_rotation(view_layer, thighi, thigh.matrix)
539 if 'stretch_length' in footi.keys() and 'stretch_length' in thigh.keys():
540 # Kept for compat with legacy rigify Human
541 footi['stretch_length'] = thigh['stretch_length']
544 set_pose_rotation(footroll, Matrix())
548 footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi)
549 footmat = convert_pose_matrix_via_rest_delta(footmat, mfooti, footi)
550 set_pose_translation(footi, footmat)
551 set_pose_rotation(footi, footmat)
552 set_pose_scale(footi, footmat)
555 # Pole target position
556 match_pole_target(view_layer, thighi, shini, pole, thigh.matrix, (thighi.length + shini.length))
558 correct_scale(view_layer, thighi, thigh.matrix)
561 # noinspection SpellCheckingInspection
562 UTILITIES_FUNC_OLD_POLE
= ['''
563 ################################
564 ## IK Rotation-Pole functions ##
565 ################################
567 def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole):
569 rig_id = rig.data['rig_id']
570 leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
571 arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
572 leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
573 arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
575 controls = parse_bone_names(controls)
576 ik_ctrl = parse_bone_names(ik_ctrl)
577 fk_ctrl = parse_bone_names(fk_ctrl)
578 parent = parse_bone_names(parent)
579 pole = parse_bone_names(pole)
581 pbones = bpy.context.selected_pose_bones
582 bpy.ops.pose.select_all(action='DESELECT')
586 new_pole_vector_value = not rig.pose.bones[parent]['pole_vector']
588 if b.name in controls or b.name in ik_ctrl:
589 if limb_type == 'arm':
592 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
593 rig.pose.bones[controls[4]].bone.select = not new_pole_vector_value
594 rig.pose.bones[parent].bone.select = not new_pole_vector_value
595 rig.pose.bones[pole].bone.select = new_pole_vector_value
597 kwargs1 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
598 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
599 'hand_ik': controls[4]}
600 kwargs2 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
601 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
602 'pole': pole, 'main_parent': parent}
606 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
607 rig.pose.bones[controls[6]].bone.select = not new_pole_vector_value
608 rig.pose.bones[controls[5]].bone.select = not new_pole_vector_value
609 rig.pose.bones[parent].bone.select = not new_pole_vector_value
610 rig.pose.bones[pole].bone.select = new_pole_vector_value
612 kwargs1 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
613 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
614 'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
615 kwargs2 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
616 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
617 'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5],
618 'mfoot_ik': ik_ctrl[2], 'main_parent': parent}
621 rig.pose.bones[parent]['pole_vector'] = new_pole_vector_value
624 bpy.ops.pose.select_all(action='DESELECT')
627 # noinspection SpellCheckingInspection
628 REGISTER_OP_OLD_ARM_FKIK
= ['Rigify_Arm_FK2IK', 'Rigify_Arm_IK2FK']
630 # noinspection SpellCheckingInspection
631 UTILITIES_OP_OLD_ARM_FKIK
= ['''
632 ##################################
633 ## IK/FK Arm snapping operators ##
634 ##################################
636 class Rigify_Arm_FK2IK(bpy.types.Operator):
637 """ Snaps an FK arm to an IK arm.
639 bl_idname = "pose.rigify_arm_fk2ik_" + rig_id
640 bl_label = "Rigify Snap FK arm to IK"
641 bl_options = {'UNDO', 'INTERNAL'}
643 uarm_fk: StringProperty(name="Upper Arm FK Name")
644 farm_fk: StringProperty(name="Forerm FK Name")
645 hand_fk: StringProperty(name="Hand FK Name")
647 uarm_ik: StringProperty(name="Upper Arm IK Name")
648 farm_ik: StringProperty(name="Forearm IK Name")
649 hand_ik: StringProperty(name="Hand IK Name")
652 def poll(cls, context):
653 return (context.active_object != None and context.mode == 'POSE')
655 def execute(self, context):
656 fk2ik_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk],
657 ik=[self.uarm_ik, self.farm_ik, self.hand_ik])
661 class Rigify_Arm_IK2FK(bpy.types.Operator):
662 """ Snaps an IK arm to an FK arm.
664 bl_idname = "pose.rigify_arm_ik2fk_" + rig_id
665 bl_label = "Rigify Snap IK arm to FK"
666 bl_options = {'UNDO', 'INTERNAL'}
668 uarm_fk: StringProperty(name="Upper Arm FK Name")
669 farm_fk: StringProperty(name="Forerm FK Name")
670 hand_fk: StringProperty(name="Hand FK Name")
672 uarm_ik: StringProperty(name="Upper Arm IK Name")
673 farm_ik: StringProperty(name="Forearm IK Name")
674 hand_ik: StringProperty(name="Hand IK Name")
675 pole : StringProperty(name="Pole IK Name")
677 main_parent: StringProperty(name="Main Parent", default="")
680 def poll(cls, context):
681 return (context.active_object != None and context.mode == 'POSE')
683 def execute(self, context):
684 ik2fk_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk],
685 ik=[self.uarm_ik, self.farm_ik, self.hand_ik, self.pole, self.main_parent])
689 # noinspection SpellCheckingInspection
690 REGISTER_OP_OLD_LEG_FKIK
= ['Rigify_Leg_FK2IK', 'Rigify_Leg_IK2FK']
692 # noinspection SpellCheckingInspection
693 UTILITIES_OP_OLD_LEG_FKIK
= ['''
694 ##################################
695 ## IK/FK Leg snapping operators ##
696 ##################################
698 class Rigify_Leg_FK2IK(bpy.types.Operator):
699 """ Snaps an FK leg to an IK leg.
701 bl_idname = "pose.rigify_leg_fk2ik_" + rig_id
702 bl_label = "Rigify Snap FK leg to IK"
703 bl_options = {'UNDO', 'INTERNAL'}
705 thigh_fk: StringProperty(name="Thigh FK Name")
706 shin_fk: StringProperty(name="Shin FK Name")
707 foot_fk: StringProperty(name="Foot FK Name")
708 mfoot_fk: StringProperty(name="MFoot FK Name")
710 thigh_ik: StringProperty(name="Thigh IK Name")
711 shin_ik: StringProperty(name="Shin IK Name")
712 foot_ik: StringProperty(name="Foot IK Name")
713 mfoot_ik: StringProperty(name="MFoot IK Name")
716 def poll(cls, context):
717 return (context.active_object != None and context.mode == 'POSE')
719 def execute(self, context):
720 fk2ik_leg(context.active_object,
721 fk=[self.thigh_fk, self.shin_fk, self.foot_fk, self.mfoot_fk],
722 ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.mfoot_ik])
726 class Rigify_Leg_IK2FK(bpy.types.Operator):
727 """ Snaps an IK leg to an FK leg.
729 bl_idname = "pose.rigify_leg_ik2fk_" + rig_id
730 bl_label = "Rigify Snap IK leg to FK"
731 bl_options = {'UNDO', 'INTERNAL'}
733 thigh_fk: StringProperty(name="Thigh FK Name")
734 shin_fk: StringProperty(name="Shin FK Name")
735 mfoot_fk: StringProperty(name="MFoot FK Name")
736 foot_fk: StringProperty(name="Foot FK Name", default="")
737 thigh_ik: StringProperty(name="Thigh IK Name")
738 shin_ik: StringProperty(name="Shin IK Name")
739 foot_ik: StringProperty(name="Foot IK Name")
740 footroll: StringProperty(name="Foot Roll Name")
741 pole: StringProperty(name="Pole IK Name")
742 mfoot_ik: StringProperty(name="MFoot IK Name")
744 main_parent: StringProperty(name="Main Parent", default="")
747 def poll(cls, context):
748 return (context.active_object != None and context.mode == 'POSE')
750 def execute(self, context):
751 ik2fk_leg(context.active_object,
752 fk=[self.thigh_fk, self.shin_fk, self.mfoot_fk, self.foot_fk],
753 ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.footroll, self.pole,
754 self.mfoot_ik, self.main_parent])
758 REGISTER_OP_OLD_POLE
= ['Rigify_Rot2PoleSwitch']
760 UTILITIES_OP_OLD_POLE
= ['''
761 ###########################
762 ## IK Rotation Pole Snap ##
763 ###########################
765 class Rigify_Rot2PoleSwitch(bpy.types.Operator):
766 bl_idname = "pose.rigify_rot2pole_" + rig_id
767 bl_label = "Rotation - Pole toggle"
768 bl_description = "Toggles IK chain between rotation and pole target"
770 bone_name: StringProperty(default='')
771 limb_type: StringProperty(name="Limb Type")
772 controls: StringProperty(name="Controls string")
773 ik_ctrl: StringProperty(name="IK Controls string")
774 fk_ctrl: StringProperty(name="FK Controls string")
775 parent: StringProperty(name="Parent name")
776 pole: StringProperty(name="Pole name")
778 def execute(self, context):
782 bpy.ops.pose.select_all(action='DESELECT')
783 rig.pose.bones[self.bone_name].bone.select = True
785 rotPoleToggle(rig, self.limb_type, self.controls, self.ik_ctrl, self.fk_ctrl,
786 self.parent, self.pole)
790 REGISTER_RIG_OLD_ARM
= REGISTER_OP_OLD_ARM_FKIK
+ REGISTER_OP_OLD_POLE
792 UTILITIES_RIG_OLD_ARM
= [
793 *UTILITIES_FUNC_COMMON_IK_FK
,
794 *UTILITIES_FUNC_OLD_ARM_FKIK
,
795 *UTILITIES_FUNC_OLD_POLE
,
796 *UTILITIES_OP_OLD_ARM_FKIK
,
797 *UTILITIES_OP_OLD_POLE
,
800 REGISTER_RIG_OLD_LEG
= REGISTER_OP_OLD_LEG_FKIK
+ REGISTER_OP_OLD_POLE
802 UTILITIES_RIG_OLD_LEG
= [
803 *UTILITIES_FUNC_COMMON_IK_FK
,
804 *UTILITIES_FUNC_OLD_LEG_FKIK
,
805 *UTILITIES_FUNC_OLD_POLE
,
806 *UTILITIES_OP_OLD_LEG_FKIK
,
807 *UTILITIES_OP_OLD_POLE
,
810 ############################
811 # Default set of utilities #
812 ############################
827 class RigUI(bpy.types.Panel):
828 bl_space_type = 'VIEW_3D'
829 bl_region_type = 'UI'
830 bl_label = "Rig Main Properties"
831 bl_idname = "VIEW3D_PT_rig_ui_" + rig_id
835 def poll(self, context):
836 if context.mode != 'POSE':
839 return (context.active_object.data.get("rig_id") == rig_id)
840 except (AttributeError, KeyError, TypeError):
843 def draw(self, context):
845 pose_bones = context.active_object.pose.bones
847 selected_bones = set(bone.name for bone in context.selected_pose_bones)
848 selected_bones.add(context.active_pose_bone.name)
849 except (AttributeError, TypeError):
852 def is_selected(names):
853 # Returns whether any of the named bones are selected.
854 if isinstance(names, list) or isinstance(names, set):
855 return not selected_bones.isdisjoint(names)
856 elif names in selected_bones:
860 num_rig_separators = [-1]
862 def emit_rig_separator():
863 if num_rig_separators[0] >= 0:
865 num_rig_separators[0] += 1
868 UI_REGISTER_BAKE_SETTINGS
= ['RigBakeSettings']
870 UI_BAKE_SETTINGS
= '''
871 class RigBakeSettings(bpy.types.Panel):
872 bl_space_type = 'VIEW_3D'
873 bl_region_type = 'UI'
874 bl_label = "Rig Bake Settings"
875 bl_idname = "VIEW3D_PT_rig_bake_settings_" + rig_id
879 def poll(self, context):
880 return RigUI.poll(context) and find_action(context.active_object) is not None
882 def draw(self, context):
883 RigifyBakeKeyframesMixin.draw_common_bake_ui(context, self.layout)
887 UI_LAYERS_PANEL
= '''
888 class RigLayers(bpy.types.Panel):
889 bl_space_type = 'VIEW_3D'
890 bl_region_type = 'UI'
891 bl_label = "Rig Layers"
892 bl_idname = "VIEW3D_PT_rig_layers_" + rig_id
896 def poll(self, context):
898 return (context.active_object.data.get("rig_id") == rig_id)
899 except (AttributeError, KeyError, TypeError):
902 def draw(self, context):
904 row_table = collections.defaultdict(list)
905 for coll in context.active_object.data.collections:
906 row_id = coll.get('rigify_ui_row', 0)
908 row_table[row_id].append(coll)
909 col = layout.column()
910 for row_id in range(min(row_table.keys()), 1 + max(row_table.keys())):
912 row_buttons = row_table[row_id]
914 for coll in row_buttons:
915 title = coll.get('rigify_ui_title') or coll.name
916 row.prop(coll, 'is_visible', toggle=True, text=title)
922 class PanelExpression(object):
923 """A runtime expression involving bone properties"""
927 def __init__(self
, expr
: str):
928 self
._rigify
_expr
= expr
931 return self
._rigify
_expr
933 def __add__(self
, other
):
934 return PanelExpression(f
"({self._rigify_expr} + {repr(other)})")
936 def __sub__(self
, other
):
937 return PanelExpression(f
"({self._rigify_expr} - {repr(other)})")
939 def __mul__(self
, other
):
940 return PanelExpression(f
"({self._rigify_expr} * {repr(other)})")
942 def __matmul__(self
, other
):
943 return PanelExpression(f
"({self._rigify_expr} @ {repr(other)})")
945 def __truediv__(self
, other
):
946 return PanelExpression(f
"({self._rigify_expr} / {repr(other)})")
948 def __floordiv__(self
, other
):
949 return PanelExpression(f
"({self._rigify_expr} // {repr(other)})")
951 def __mod__(self
, other
):
952 return PanelExpression(f
"({self._rigify_expr} % {repr(other)})")
954 def __lshift__(self
, other
):
955 return PanelExpression(f
"({self._rigify_expr} << {repr(other)})")
957 def __rshift__(self
, other
):
958 return PanelExpression(f
"({self._rigify_expr} >> {repr(other)})")
960 def __and__(self
, other
):
961 return PanelExpression(f
"({self._rigify_expr} & {repr(other)})")
963 def __xor__(self
, other
):
964 return PanelExpression(f
"({self._rigify_expr} ^ {repr(other)})")
966 def __or__(self
, other
):
967 return PanelExpression(f
"({self._rigify_expr} | {repr(other)})")
969 def __radd__(self
, other
):
970 return PanelExpression(f
"({repr(other)} + {self._rigify_expr})")
972 def __rsub__(self
, other
):
973 return PanelExpression(f
"({repr(other)} - {self._rigify_expr})")
975 def __rmul__(self
, other
):
976 return PanelExpression(f
"({repr(other)} * {self._rigify_expr})")
978 def __rmatmul__(self
, other
):
979 return PanelExpression(f
"({repr(other)} @ {self._rigify_expr})")
981 def __rtruediv__(self
, other
):
982 return PanelExpression(f
"({repr(other)} / {self._rigify_expr})")
984 def __rfloordiv__(self
, other
):
985 return PanelExpression(f
"({repr(other)} // {self._rigify_expr})")
987 def __rmod__(self
, other
):
988 return PanelExpression(f
"({repr(other)} % {self._rigify_expr})")
990 def __rlshift__(self
, other
):
991 return PanelExpression(f
"({repr(other)} << {self._rigify_expr})")
993 def __rrshift__(self
, other
):
994 return PanelExpression(f
"({repr(other)} >> {self._rigify_expr})")
996 def __rand__(self
, other
):
997 return PanelExpression(f
"({repr(other)} & {self._rigify_expr})")
999 def __rxor__(self
, other
):
1000 return PanelExpression(f
"({repr(other)} ^ {self._rigify_expr})")
1002 def __ror__(self
, other
):
1003 return PanelExpression(f
"({repr(other)} | {self._rigify_expr})")
1006 return PanelExpression(f
"-{self._rigify_expr}")
1009 return PanelExpression(f
"+{self._rigify_expr}")
1012 return PanelExpression(f
"abs({self._rigify_expr})")
1014 def __invert__(self
):
1015 return PanelExpression(f
"~{self._rigify_expr}")
1017 def __round__(self
, digits
=None):
1018 return PanelExpression(f
"round({self._rigify_expr}, {digits})")
1020 def __trunc__(self
):
1021 return PanelExpression(f
"trunc({self._rigify_expr})")
1023 def __floor__(self
):
1024 return PanelExpression(f
"floor({self._rigify_expr})")
1027 return PanelExpression(f
"ceil({self._rigify_expr})")
1029 def __lt__(self
, other
):
1030 return PanelExpression(f
"({self._rigify_expr} < {repr(other)})")
1032 def __le__(self
, other
):
1033 return PanelExpression(f
"({self._rigify_expr} <= {repr(other)})")
1035 def __eq__(self
, other
):
1036 return PanelExpression(f
"({self._rigify_expr} == {repr(other)})")
1038 def __ne__(self
, other
):
1039 return PanelExpression(f
"({self._rigify_expr} != {repr(other)})")
1041 def __gt__(self
, other
):
1042 return PanelExpression(f
"({self._rigify_expr} > {repr(other)})")
1044 def __ge__(self
, other
):
1045 return PanelExpression(f
"({self._rigify_expr} >= {repr(other)})")
1048 raise NotImplementedError("This object wraps an expression, not a value; casting to boolean is meaningless")
1051 class PanelReferenceExpression(PanelExpression
):
1053 A runtime expression referencing an object.
1057 def __getitem__(self
, item
):
1058 return PanelReferenceExpression(f
"{self._rigify_expr}[{repr(item)}]")
1060 def __getattr__(self
, item
):
1061 return PanelReferenceExpression(f
"{self._rigify_expr}.{item}")
1063 def get(self
, item
, default
=None):
1064 return PanelReferenceExpression(f
"{self._rigify_expr}.get({repr(item)}, {repr(default)})")
1067 def quote_parameters(positional
: list[Any
], named
: dict[str, Any
]):
1068 """Quote the given positional and named parameters as a code string."""
1069 positional_list
= [repr(v
) for v
in positional
]
1070 named_list
= ["%s=%r" % (k
, v
) for k
, v
in named
.items()]
1071 return ', '.join(positional_list
+ named_list
)
1074 def indent_lines(lines
: list[str], indent
=4):
1076 prefix
= ' ' * indent
1077 return [prefix
+ line
for line
in lines
]
1082 class PanelLayout(object):
1083 """Utility class that builds code for creating a layout."""
1085 parent
: Optional
['PanelLayout']
1086 script
: 'ScriptGenerator'
1089 items
: list[Union
[str, 'PanelLayout']]
1091 def __init__(self
, parent
: Union
['PanelLayout', 'ScriptGenerator'], index
=0):
1092 if isinstance(parent
, PanelLayout
):
1093 self
.parent
= parent
1094 self
.script
= parent
.script
1097 self
.script
= parent
1103 self
.layout
= self
._get
_layout
_var
(index
)
1104 self
.is_empty
= True
1107 def _get_layout_var(index
):
1108 return 'layout' if index
== 0 else 'group' + str(index
)
1110 def clear_empty(self
):
1111 self
.is_empty
= False
1114 self
.parent
.clear_empty()
1116 def get_lines(self
) -> list[str]:
1119 for item
in self
.items
:
1120 if isinstance(item
, PanelLayout
):
1121 lines
+= item
.get_lines()
1126 return self
.wrap_lines(lines
)
1130 def wrap_lines(self
, lines
):
1131 return self
.header
+ indent_lines(lines
, self
.indent
)
1133 def add_line(self
, line
: str):
1134 assert isinstance(line
, str)
1136 self
.items
.append(line
)
1141 def use_bake_settings(self
):
1142 """This panel contains operators that need the common Bake settings."""
1143 self
.parent
.use_bake_settings()
1145 def custom_prop(self
, bone_name
: str, prop_name
: str, **params
):
1146 """Add a custom property input field to the panel."""
1147 param_str
= quote_parameters([rna_idprop_quote_path(prop_name
)], params
)
1149 "%s.prop(pose_bones[%r], %s)" % (self
.layout
, bone_name
, param_str
)
1152 def operator(self
, operator_name
: str, *,
1153 properties
: Optional
[dict[str, Any
]] = None,
1155 """Add an operator call button to the panel."""
1156 name
= operator_name
.format_map(self
.script
.format_args
)
1157 param_str
= quote_parameters([name
], params
)
1158 call_str
= "%s.operator(%s)" % (self
.layout
, param_str
)
1160 self
.add_line("props = " + call_str
)
1161 for k
, v
in properties
.items():
1162 self
.add_line("props.%s = %r" % (k
, v
))
1164 self
.add_line(call_str
)
1166 def add_nested_layout(self
, method_name
: str, params
: dict[str, Any
]) -> 'PanelLayout':
1167 param_str
= quote_parameters([], params
)
1168 sub_panel
= PanelLayout(self
, self
.index
+ 1)
1169 sub_panel
.header
.append(f
'{sub_panel.layout} = {self.layout}.{method_name}({param_str})')
1170 self
.items
.append(sub_panel
)
1173 def row(self
, **params
):
1174 """Add a nested row layout to the panel."""
1175 return self
.add_nested_layout('row', params
)
1177 def column(self
, **params
):
1178 """Add a nested column layout to the panel."""
1179 return self
.add_nested_layout('column', params
)
1181 def split(self
, **params
):
1182 """Add a split layout to the panel."""
1183 return self
.add_nested_layout('split', params
)
1186 def expr_bone(bone_name
):
1187 """Returns an expression referencing the specified pose bone."""
1188 return PanelReferenceExpression(f
"pose_bones[%r]" % bone_name
)
1191 def expr_and(*expressions
):
1192 """Returns a boolean and expression of its parameters."""
1193 return PanelExpression("(" + " and ".join(repr(e
) for e
in expressions
) + ")")
1196 def expr_or(*expressions
):
1197 """Returns a boolean or expression of its parameters."""
1198 return PanelExpression("(" + " or ".join(repr(e
) for e
in expressions
) + ")")
1201 def expr_if_else(condition
, true_expr
, false_expr
):
1202 """Returns a conditional expression."""
1203 return PanelExpression(f
"({repr(true_expr)} if {repr(condition)} else {repr(false_expr)})")
1206 def expr_call(func
: str, *expressions
):
1207 """Returns an expression calling the specified function with given parameters."""
1208 return PanelExpression(func
+ "(" + ", ".join(repr(e
) for e
in expressions
) + ")")
1210 def set_layout_property(self
, prop_name
: str, prop_value
: Any
):
1211 assert self
.index
> 0 # Don't change properties on the root layout
1212 self
.add_line("%s.%s = %r" % (self
.layout
, prop_name
, prop_value
))
1216 raise NotImplementedError("This is a write only property")
1219 def active(self
, value
):
1220 self
.set_layout_property('active', value
)
1224 raise NotImplementedError("This is a write only property")
1227 def enabled(self
, value
):
1228 self
.set_layout_property('enabled', value
)
1231 class BoneSetPanelLayout(PanelLayout
):
1232 """Panel restricted to a certain set of bones."""
1234 parent
: 'RigPanelLayout'
1236 def __init__(self
, rig_panel
: 'RigPanelLayout', bones
: frozenset[str]):
1237 assert isinstance(bones
, frozenset)
1238 super().__init
__(rig_panel
)
1240 self
.show_bake_settings
= False
1242 def clear_empty(self
):
1243 self
.parent
.bones |
= self
.bones
1245 super().clear_empty()
1247 def wrap_lines(self
, lines
):
1248 if self
.bones
!= self
.parent
.bones
:
1249 header
= ["if is_selected(%r):" % (set(self
.bones
))]
1250 return header
+ indent_lines(lines
)
1254 def use_bake_settings(self
):
1255 self
.show_bake_settings
= True
1256 if not self
.script
.use_bake_settings
:
1257 self
.script
.use_bake_settings
= True
1258 self
.script
.add_utilities(SCRIPT_UTILITIES_BAKE
)
1259 self
.script
.register_classes(SCRIPT_REGISTER_BAKE
)
1262 class RigPanelLayout(PanelLayout
):
1263 """Panel owned by a certain rig."""
1265 def __init__(self
, script
: 'ScriptGenerator', _rig
):
1266 super().__init
__(script
)
1268 self
.sub_panels
= OrderedDict()
1270 def wrap_lines(self
, lines
):
1271 header
= ["if is_selected(%r):" % (set(self
.bones
))]
1272 prefix
= ["emit_rig_separator()"]
1273 return header
+ indent_lines(prefix
+ lines
)
1275 def panel_with_selected_check(self
, control_names
):
1276 selected_set
= frozenset(control_names
)
1278 if selected_set
in self
.sub_panels
:
1279 return self
.sub_panels
[selected_set
]
1281 panel
= BoneSetPanelLayout(self
, selected_set
)
1282 self
.sub_panels
[selected_set
] = panel
1283 self
.items
.append(panel
)
1287 class ScriptGenerator(base_generate
.GeneratorPlugin
):
1288 """Generator plugin that builds the python script attached to the rig."""
1292 format_args
: dict[str, str]
1294 def __init__(self
, generator
):
1295 super().__init
__(generator
)
1297 self
.ui_scripts
= []
1298 self
.ui_imports
= UI_IMPORTS
.copy()
1299 self
.ui_utilities
= UI_UTILITIES
.copy()
1300 self
.ui_register
= UI_REGISTER
.copy()
1301 self
.ui_register_drivers
= []
1302 self
.ui_register_props
= []
1304 self
.ui_rig_panels
= OrderedDict()
1306 self
.use_bake_settings
= False
1308 # Structured panel code generation
1309 def panel_with_selected_check(self
, rig
, control_names
):
1310 """Add a panel section with restricted selection."""
1313 if rig_key
in self
.ui_rig_panels
:
1314 panel
= self
.ui_rig_panels
[rig_key
]
1316 panel
= RigPanelLayout(self
, rig
)
1317 self
.ui_rig_panels
[rig_key
] = panel
1319 return panel
.panel_with_selected_check(control_names
)
1322 def add_panel_code(self
, str_list
: list[str]):
1323 """Add raw code to the panel."""
1324 self
.ui_scripts
+= str_list
1326 def add_imports(self
, str_list
: list[str]):
1327 self
.ui_imports
+= str_list
1329 def add_utilities(self
, str_list
: list[str]):
1330 self
.ui_utilities
+= str_list
1332 def register_classes(self
, str_list
: list[str]):
1333 self
.ui_register
+= str_list
1335 def register_driver_functions(self
, str_list
: list[str]):
1336 self
.ui_register_drivers
+= str_list
1338 def register_property(self
, name
: str, definition
):
1339 self
.ui_register_props
.append((name
, definition
))
1341 def initialize(self
):
1342 self
.format_args
= {
1343 'rig_id': self
.generator
.rig_id
,
1347 metarig
= self
.generator
.metarig
1348 rig_id
= self
.generator
.rig_id
1350 # Generate the UI script
1351 script
= metarig
.data
.rigify_rig_ui
1356 script_name
= self
.generator
.obj
.name
+ "_ui.py"
1357 script
= bpy
.data
.texts
.new(script_name
)
1358 metarig
.data
.rigify_rig_ui
= script
1360 for s
in OrderedDict
.fromkeys(self
.ui_imports
):
1361 script
.write(s
+ "\n")
1363 script
.write(UI_BASE_UTILITIES
% rig_id
)
1365 for s
in OrderedDict
.fromkeys(self
.ui_utilities
):
1366 script
.write(s
+ "\n")
1368 script
.write(UI_SLIDERS
)
1370 for s
in self
.ui_scripts
:
1371 script
.write("\n " + s
.replace("\n", "\n ") + "\n")
1373 if len(self
.ui_scripts
) > 0:
1374 script
.write("\n num_rig_separators[0] = 0\n")
1376 for panel
in self
.ui_rig_panels
.values():
1377 lines
= panel
.get_lines()
1379 script
.write("\n ".join([''] + lines
) + "\n")
1381 if self
.use_bake_settings
:
1382 self
.ui_register
= UI_REGISTER_BAKE_SETTINGS
+ self
.ui_register
1383 script
.write(UI_BAKE_SETTINGS
)
1385 script
.write(UI_LAYERS_PANEL
)
1387 script
.write("\ndef register():\n")
1389 ui_register
= OrderedDict
.fromkeys(self
.ui_register
)
1390 for s
in ui_register
:
1391 script
.write(" bpy.utils.register_class("+s
+")\n")
1393 ui_register_drivers
= OrderedDict
.fromkeys(self
.ui_register_drivers
)
1394 for s
in ui_register_drivers
:
1395 script
.write(" bpy.app.driver_namespace['"+s
+"'] = "+s
+"\n")
1397 ui_register_props
= OrderedDict
.fromkeys(self
.ui_register_props
)
1398 for classname
, text
in ui_register_props
:
1399 script
.write(f
" bpy.types.{classname} = {text}\n ")
1401 script
.write("\ndef unregister():\n")
1403 for s
in ui_register_props
:
1404 script
.write(" del bpy.types.%s\n" % s
[0])
1406 for s
in ui_register
:
1407 script
.write(" bpy.utils.unregister_class("+s
+")\n")
1409 for s
in ui_register_drivers
:
1410 script
.write(" del bpy.app.driver_namespace['"+s
+"']\n")
1412 script
.write("\nregister()\n")
1413 script
.use_module
= True
1416 exec(script
.as_string(), {})
1418 # Attach the script to the rig
1419 self
.obj
['rig_ui'] = script