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):
100 def flatten_children(iterable):
101 """Enumerate the iterator items as well as their children in the tree order."""
102 for item in iterable:
104 yield from flatten_children(item.children)
108 UTILITIES_FUNC_COMMON_IK_FK
= ['''
109 #########################################
110 ## "Visual Transform" helper functions ##
111 #########################################
113 def get_pose_matrix_in_other_space(mat, pose_bone):
114 """ Returns the transform matrix relative to pose_bone's current
115 transform space. In other words, presuming that mat is in
116 armature space, slapping the returned matrix onto pose_bone
117 should give it the armature-space transforms of mat.
119 return pose_bone.id_data.convert_space(matrix=mat, pose_bone=pose_bone, from_space='POSE', to_space='LOCAL')
122 def convert_pose_matrix_via_rest_delta(mat, from_bone, to_bone):
123 """Convert pose of one bone to another bone, preserving the rest pose difference between them."""
124 return mat @ from_bone.bone.matrix_local.inverted() @ to_bone.bone.matrix_local
127 def convert_pose_matrix_via_pose_delta(mat, from_bone, to_bone):
128 """Convert pose of one bone to another bone, preserving the current pose difference between them."""
129 return mat @ from_bone.matrix.inverted() @ to_bone.matrix
132 def get_local_pose_matrix(pose_bone):
133 """ Returns the local transform matrix of the given pose bone.
135 return get_pose_matrix_in_other_space(pose_bone.matrix, pose_bone)
138 def set_pose_translation(pose_bone, mat):
139 """ Sets the pose bone's translation to the same translation as the given matrix.
140 Matrix should be given in bone's local space.
142 pose_bone.location = mat.to_translation()
145 def set_pose_rotation(pose_bone, mat):
146 """ Sets the pose bone's rotation to the same rotation as the given matrix.
147 Matrix should be given in bone's local space.
149 q = mat.to_quaternion()
151 if pose_bone.rotation_mode == 'QUATERNION':
152 pose_bone.rotation_quaternion = q
153 elif pose_bone.rotation_mode == 'AXIS_ANGLE':
154 pose_bone.rotation_axis_angle[0] = q.angle
155 pose_bone.rotation_axis_angle[1] = q.axis[0]
156 pose_bone.rotation_axis_angle[2] = q.axis[1]
157 pose_bone.rotation_axis_angle[3] = q.axis[2]
159 pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode)
162 def set_pose_scale(pose_bone, mat):
163 """ Sets the pose bone's scale to the same scale as the given matrix.
164 Matrix should be given in bone's local space.
166 pose_bone.scale = mat.to_scale()
169 def match_pose_translation(pose_bone, target_bone):
170 """ Matches pose_bone's visual translation to target_bone's visual
172 This function assumes you are in pose mode on the relevant armature.
174 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
175 set_pose_translation(pose_bone, mat)
178 def match_pose_rotation(pose_bone, target_bone):
179 """ Matches pose_bone's visual rotation to target_bone's visual
181 This function assumes you are in pose mode on the relevant armature.
183 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
184 set_pose_rotation(pose_bone, mat)
187 def match_pose_scale(pose_bone, target_bone):
188 """ Matches pose_bone's visual scale to target_bone's visual
190 This function assumes you are in pose mode on the relevant armature.
192 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
193 set_pose_scale(pose_bone, mat)
196 ##############################
197 ## IK/FK snapping functions ##
198 ##############################
200 def correct_rotation(view_layer, bone_ik, target_matrix, *, ctrl_ik=None):
201 """ Corrects the ik rotation in ik2fk snapping functions
204 axis = target_matrix.to_3x3().col[1].normalized()
205 ctrl_ik = ctrl_ik or bone_ik
208 # Rotate the bone and return the actual angle between bones
209 ctrl_ik.rotation_euler[1] = angle
212 return -(bone_ik.vector.normalized().dot(axis))
214 if ctrl_ik.rotation_mode in {'QUATERNION', 'AXIS_ANGLE'}:
215 ctrl_ik.rotation_mode = 'ZXY'
217 start_angle = ctrl_ik.rotation_euler[1]
219 alpha_range = find_min_range(distance, start_angle)
220 alpha_min = ternarySearch(distance, alpha_range[0], alpha_range[1], pi / 180)
222 ctrl_ik.rotation_euler[1] = alpha_min
226 def correct_scale(view_layer, bone_ik, target_matrix, *, ctrl_ik=None):
227 """ Correct the scale of the base IK bone. """
228 input_scale = target_matrix.to_scale()
229 ctrl_ik = ctrl_ik or bone_ik
232 cur_scale = bone_ik.matrix.to_scale()
235 v * i / c for v, i, c in zip(bone_ik.scale, input_scale, cur_scale)
240 if all(abs((c - i)/i) < 0.01 for i, c in zip(input_scale, cur_scale)):
244 def match_pole_target(view_layer, ik_first, ik_last, pole, match_bone_matrix, length):
245 """ Places an IK chain's pole target to match ik_first's
246 transforms to match_bone. All bones should be given as pose bones.
247 You need to be in pose mode on the relevant armature object.
248 ik_first: first bone in the IK chain
249 ik_last: last bone in the IK chain
250 pole: pole target bone for the IK chain
251 match_bone: bone to match ik_first to (probably first bone in a matching FK chain)
252 length: distance pole target should be placed from the chain center
254 a = ik_first.matrix.to_translation()
255 b = ik_last.matrix.to_translation() + ik_last.vector
257 # Vector from the head of ik_first to the
261 # Get a vector perpendicular to ikv
262 pv = perpendicular_vector(ikv).normalized() * length
265 """ Set pole target's position based on a vector
266 from the arm center line.
268 # Translate pvi into armature space
269 pole_loc = a + (ikv/2) + pvi
271 # Set pole target to location
272 mat = get_pose_matrix_in_other_space(Matrix.Translation(pole_loc), pole)
273 set_pose_translation(pole, mat)
279 # Get the rotation difference between ik_first and match_bone
280 angle = rotation_difference(ik_first.matrix, match_bone_matrix)
282 # Try compensating for the rotation difference in both directions
283 pv1 = Matrix.Rotation(angle, 4, ikv) @ pv
285 ang1 = rotation_difference(ik_first.matrix, match_bone_matrix)
287 pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv
289 ang2 = rotation_difference(ik_first.matrix, match_bone_matrix)
291 # Do the one with the smaller angle
299 def parse_bone_names(names_string):
300 if names_string[0] == '[' and names_string[-1] == ']':
301 return eval(names_string)
307 # noinspection SpellCheckingInspection
308 UTILITIES_FUNC_OLD_ARM_FKIK
= ['''
309 ######################
310 ## IK Arm functions ##
311 ######################
313 def fk2ik_arm(obj, fk, ik):
314 """ Matches the fk bones in an arm rig to the ik bones.
316 fk: list of fk bone names
317 ik: list of ik bone names
319 view_layer = bpy.context.view_layer
320 uarm = obj.pose.bones[fk[0]]
321 farm = obj.pose.bones[fk[1]]
322 hand = obj.pose.bones[fk[2]]
323 uarmi = obj.pose.bones[ik[0]]
324 farmi = obj.pose.bones[ik[1]]
325 handi = obj.pose.bones[ik[2]]
327 if 'auto_stretch' in handi.keys():
328 # This is kept for compatibility with legacy rigify Human
330 if handi['auto_stretch'] == 0.0:
331 uarm['stretch_length'] = handi['stretch_length']
333 diff = (uarmi.vector.length + farmi.vector.length) / (uarm.vector.length + farm.vector.length)
334 uarm['stretch_length'] *= diff
337 match_pose_rotation(uarm, uarmi)
338 match_pose_scale(uarm, uarmi)
342 match_pose_rotation(farm, farmi)
343 match_pose_scale(farm, farmi)
347 match_pose_rotation(hand, handi)
348 match_pose_scale(hand, handi)
352 match_pose_translation(uarm, uarmi)
353 match_pose_rotation(uarm, uarmi)
354 match_pose_scale(uarm, uarmi)
358 #match_pose_translation(hand, handi)
359 match_pose_rotation(farm, farmi)
360 match_pose_scale(farm, farmi)
364 match_pose_translation(hand, handi)
365 match_pose_rotation(hand, handi)
366 match_pose_scale(hand, handi)
370 def ik2fk_arm(obj, fk, ik):
371 """ Matches the ik bones in an arm rig to the fk bones.
373 fk: list of fk bone names
374 ik: list of ik bone names
376 view_layer = bpy.context.view_layer
377 uarm = obj.pose.bones[fk[0]]
378 farm = obj.pose.bones[fk[1]]
379 hand = obj.pose.bones[fk[2]]
380 uarmi = obj.pose.bones[ik[0]]
381 farmi = obj.pose.bones[ik[1]]
382 handi = obj.pose.bones[ik[2]]
384 main_parent = obj.pose.bones[ik[4]]
386 if ik[3] != "" and main_parent['pole_vector']:
387 pole = obj.pose.bones[ik[3]]
394 # handi['stretch_length'] = uarm['stretch_length']
397 match_pose_translation(handi, hand)
398 match_pose_rotation(handi, hand)
399 match_pose_scale(handi, hand)
402 # Pole target position
403 match_pole_target(view_layer, uarmi, farmi, pole, uarm.matrix, (uarmi.length + farmi.length))
407 match_pose_translation(handi, hand)
408 match_pose_rotation(handi, hand)
409 match_pose_scale(handi, hand)
413 match_pose_translation(uarmi, uarm)
414 #match_pose_rotation(uarmi, uarm)
415 set_pose_rotation(uarmi, Matrix())
416 match_pose_scale(uarmi, uarm)
419 # Rotation Correction
420 correct_rotation(view_layer, uarmi, uarm.matrix)
422 correct_scale(view_layer, uarmi, uarm.matrix)
425 # noinspection SpellCheckingInspection
426 UTILITIES_FUNC_OLD_LEG_FKIK
= ['''
427 ######################
428 ## IK Leg functions ##
429 ######################
431 def fk2ik_leg(obj, fk, ik):
432 """ Matches the fk bones in a leg rig to the ik bones.
434 fk: list of fk bone names
435 ik: list of ik bone names
437 view_layer = bpy.context.view_layer
438 thigh = obj.pose.bones[fk[0]]
439 shin = obj.pose.bones[fk[1]]
440 foot = obj.pose.bones[fk[2]]
441 mfoot = obj.pose.bones[fk[3]]
442 thighi = obj.pose.bones[ik[0]]
443 shini = obj.pose.bones[ik[1]]
444 footi = obj.pose.bones[ik[2]]
445 mfooti = obj.pose.bones[ik[3]]
447 if 'auto_stretch' in footi.keys():
448 # This is kept for compatibility with legacy rigify Human
450 if footi['auto_stretch'] == 0.0:
451 thigh['stretch_length'] = footi['stretch_length']
453 diff = (thighi.vector.length + shini.vector.length) / (thigh.vector.length + shin.vector.length)
454 thigh['stretch_length'] *= diff
457 match_pose_rotation(thigh, thighi)
458 match_pose_scale(thigh, thighi)
462 match_pose_rotation(shin, shini)
463 match_pose_scale(shin, shini)
467 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot)
468 footmat = convert_pose_matrix_via_rest_delta(footmat, mfoot, foot)
469 set_pose_rotation(foot, footmat)
470 set_pose_scale(foot, footmat)
475 match_pose_translation(thigh, thighi)
476 match_pose_rotation(thigh, thighi)
477 match_pose_scale(thigh, thighi)
481 match_pose_rotation(shin, shini)
482 match_pose_scale(shin, shini)
486 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot)
487 footmat = convert_pose_matrix_via_rest_delta(footmat, mfoot, foot)
488 set_pose_rotation(foot, footmat)
489 set_pose_scale(foot, footmat)
493 def ik2fk_leg(obj, fk, ik):
494 """ Matches the ik bones in a leg rig to the fk bones.
496 fk: list of fk bone names
497 ik: list of ik bone names
499 view_layer = bpy.context.view_layer
500 thigh = obj.pose.bones[fk[0]]
501 shin = obj.pose.bones[fk[1]]
502 mfoot = obj.pose.bones[fk[2]]
504 foot = obj.pose.bones[fk[3]]
507 thighi = obj.pose.bones[ik[0]]
508 shini = obj.pose.bones[ik[1]]
509 footi = obj.pose.bones[ik[2]]
510 footroll = obj.pose.bones[ik[3]]
512 main_parent = obj.pose.bones[ik[6]]
514 if ik[4] != "" and main_parent['pole_vector']:
515 pole = obj.pose.bones[ik[4]]
518 mfooti = obj.pose.bones[ik[5]]
520 if (not pole) and (foot):
523 set_pose_rotation(footroll, Matrix())
527 footmat = get_pose_matrix_in_other_space(foot.matrix, footi)
528 footmat = convert_pose_matrix_via_rest_delta(footmat, mfooti, footi)
529 set_pose_translation(footi, footmat)
530 set_pose_rotation(footi, footmat)
531 set_pose_scale(footi, footmat)
535 match_pose_translation(thighi, thigh)
536 #match_pose_rotation(thighi, thigh)
537 set_pose_rotation(thighi, Matrix())
538 match_pose_scale(thighi, thigh)
541 # Rotation Correction
542 correct_rotation(view_layer, thighi, thigh.matrix)
546 if 'stretch_length' in footi.keys() and 'stretch_length' in thigh.keys():
547 # Kept for compat with legacy rigify Human
548 footi['stretch_length'] = thigh['stretch_length']
551 set_pose_rotation(footroll, Matrix())
555 footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi)
556 footmat = convert_pose_matrix_via_rest_delta(footmat, mfooti, footi)
557 set_pose_translation(footi, footmat)
558 set_pose_rotation(footi, footmat)
559 set_pose_scale(footi, footmat)
562 # Pole target position
563 match_pole_target(view_layer, thighi, shini, pole, thigh.matrix, (thighi.length + shini.length))
565 correct_scale(view_layer, thighi, thigh.matrix)
568 # noinspection SpellCheckingInspection
569 UTILITIES_FUNC_OLD_POLE
= ['''
570 ################################
571 ## IK Rotation-Pole functions ##
572 ################################
574 def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole):
576 rig_id = rig.data['rig_id']
577 leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
578 arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
579 leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
580 arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
582 controls = parse_bone_names(controls)
583 ik_ctrl = parse_bone_names(ik_ctrl)
584 fk_ctrl = parse_bone_names(fk_ctrl)
585 parent = parse_bone_names(parent)
586 pole = parse_bone_names(pole)
588 pbones = bpy.context.selected_pose_bones
589 bpy.ops.pose.select_all(action='DESELECT')
593 new_pole_vector_value = not rig.pose.bones[parent]['pole_vector']
595 if b.name in controls or b.name in ik_ctrl:
596 if limb_type == 'arm':
599 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
600 rig.pose.bones[controls[4]].bone.select = not new_pole_vector_value
601 rig.pose.bones[parent].bone.select = not new_pole_vector_value
602 rig.pose.bones[pole].bone.select = new_pole_vector_value
604 kwargs1 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
605 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
606 'hand_ik': controls[4]}
607 kwargs2 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
608 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
609 'pole': pole, 'main_parent': parent}
613 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
614 rig.pose.bones[controls[6]].bone.select = not new_pole_vector_value
615 rig.pose.bones[controls[5]].bone.select = not new_pole_vector_value
616 rig.pose.bones[parent].bone.select = not new_pole_vector_value
617 rig.pose.bones[pole].bone.select = new_pole_vector_value
619 kwargs1 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
620 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
621 'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
622 kwargs2 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
623 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
624 'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5],
625 'mfoot_ik': ik_ctrl[2], 'main_parent': parent}
628 rig.pose.bones[parent]['pole_vector'] = new_pole_vector_value
631 bpy.ops.pose.select_all(action='DESELECT')
634 # noinspection SpellCheckingInspection
635 REGISTER_OP_OLD_ARM_FKIK
= ['Rigify_Arm_FK2IK', 'Rigify_Arm_IK2FK']
637 # noinspection SpellCheckingInspection
638 UTILITIES_OP_OLD_ARM_FKIK
= ['''
639 ##################################
640 ## IK/FK Arm snapping operators ##
641 ##################################
643 class Rigify_Arm_FK2IK(bpy.types.Operator):
644 """ Snaps an FK arm to an IK arm.
646 bl_idname = "pose.rigify_arm_fk2ik_" + rig_id
647 bl_label = "Rigify Snap FK arm to IK"
648 bl_options = {'UNDO', 'INTERNAL'}
650 uarm_fk: StringProperty(name="Upper Arm FK Name")
651 farm_fk: StringProperty(name="Forerm FK Name")
652 hand_fk: StringProperty(name="Hand FK Name")
654 uarm_ik: StringProperty(name="Upper Arm IK Name")
655 farm_ik: StringProperty(name="Forearm IK Name")
656 hand_ik: StringProperty(name="Hand IK Name")
659 def poll(cls, context):
660 return (context.active_object != None and context.mode == 'POSE')
662 def execute(self, context):
663 fk2ik_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk],
664 ik=[self.uarm_ik, self.farm_ik, self.hand_ik])
668 class Rigify_Arm_IK2FK(bpy.types.Operator):
669 """ Snaps an IK arm to an FK arm.
671 bl_idname = "pose.rigify_arm_ik2fk_" + rig_id
672 bl_label = "Rigify Snap IK arm to FK"
673 bl_options = {'UNDO', 'INTERNAL'}
675 uarm_fk: StringProperty(name="Upper Arm FK Name")
676 farm_fk: StringProperty(name="Forerm FK Name")
677 hand_fk: StringProperty(name="Hand FK Name")
679 uarm_ik: StringProperty(name="Upper Arm IK Name")
680 farm_ik: StringProperty(name="Forearm IK Name")
681 hand_ik: StringProperty(name="Hand IK Name")
682 pole : StringProperty(name="Pole IK Name")
684 main_parent: StringProperty(name="Main Parent", default="")
687 def poll(cls, context):
688 return (context.active_object != None and context.mode == 'POSE')
690 def execute(self, context):
691 ik2fk_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk],
692 ik=[self.uarm_ik, self.farm_ik, self.hand_ik, self.pole, self.main_parent])
696 # noinspection SpellCheckingInspection
697 REGISTER_OP_OLD_LEG_FKIK
= ['Rigify_Leg_FK2IK', 'Rigify_Leg_IK2FK']
699 # noinspection SpellCheckingInspection
700 UTILITIES_OP_OLD_LEG_FKIK
= ['''
701 ##################################
702 ## IK/FK Leg snapping operators ##
703 ##################################
705 class Rigify_Leg_FK2IK(bpy.types.Operator):
706 """ Snaps an FK leg to an IK leg.
708 bl_idname = "pose.rigify_leg_fk2ik_" + rig_id
709 bl_label = "Rigify Snap FK leg to IK"
710 bl_options = {'UNDO', 'INTERNAL'}
712 thigh_fk: StringProperty(name="Thigh FK Name")
713 shin_fk: StringProperty(name="Shin FK Name")
714 foot_fk: StringProperty(name="Foot FK Name")
715 mfoot_fk: StringProperty(name="MFoot FK Name")
717 thigh_ik: StringProperty(name="Thigh IK Name")
718 shin_ik: StringProperty(name="Shin IK Name")
719 foot_ik: StringProperty(name="Foot IK Name")
720 mfoot_ik: StringProperty(name="MFoot IK Name")
723 def poll(cls, context):
724 return (context.active_object != None and context.mode == 'POSE')
726 def execute(self, context):
727 fk2ik_leg(context.active_object,
728 fk=[self.thigh_fk, self.shin_fk, self.foot_fk, self.mfoot_fk],
729 ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.mfoot_ik])
733 class Rigify_Leg_IK2FK(bpy.types.Operator):
734 """ Snaps an IK leg to an FK leg.
736 bl_idname = "pose.rigify_leg_ik2fk_" + rig_id
737 bl_label = "Rigify Snap IK leg to FK"
738 bl_options = {'UNDO', 'INTERNAL'}
740 thigh_fk: StringProperty(name="Thigh FK Name")
741 shin_fk: StringProperty(name="Shin FK Name")
742 mfoot_fk: StringProperty(name="MFoot FK Name")
743 foot_fk: StringProperty(name="Foot FK Name", default="")
744 thigh_ik: StringProperty(name="Thigh IK Name")
745 shin_ik: StringProperty(name="Shin IK Name")
746 foot_ik: StringProperty(name="Foot IK Name")
747 footroll: StringProperty(name="Foot Roll Name")
748 pole: StringProperty(name="Pole IK Name")
749 mfoot_ik: StringProperty(name="MFoot IK Name")
751 main_parent: StringProperty(name="Main Parent", default="")
754 def poll(cls, context):
755 return (context.active_object != None and context.mode == 'POSE')
757 def execute(self, context):
758 ik2fk_leg(context.active_object,
759 fk=[self.thigh_fk, self.shin_fk, self.mfoot_fk, self.foot_fk],
760 ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.footroll, self.pole,
761 self.mfoot_ik, self.main_parent])
765 REGISTER_OP_OLD_POLE
= ['Rigify_Rot2PoleSwitch']
767 UTILITIES_OP_OLD_POLE
= ['''
768 ###########################
769 ## IK Rotation Pole Snap ##
770 ###########################
772 class Rigify_Rot2PoleSwitch(bpy.types.Operator):
773 bl_idname = "pose.rigify_rot2pole_" + rig_id
774 bl_label = "Rotation - Pole toggle"
775 bl_description = "Toggles IK chain between rotation and pole target"
777 bone_name: StringProperty(default='')
778 limb_type: StringProperty(name="Limb Type")
779 controls: StringProperty(name="Controls string")
780 ik_ctrl: StringProperty(name="IK Controls string")
781 fk_ctrl: StringProperty(name="FK Controls string")
782 parent: StringProperty(name="Parent name")
783 pole: StringProperty(name="Pole name")
785 def execute(self, context):
789 bpy.ops.pose.select_all(action='DESELECT')
790 rig.pose.bones[self.bone_name].bone.select = True
792 rotPoleToggle(rig, self.limb_type, self.controls, self.ik_ctrl, self.fk_ctrl,
793 self.parent, self.pole)
797 REGISTER_RIG_OLD_ARM
= REGISTER_OP_OLD_ARM_FKIK
+ REGISTER_OP_OLD_POLE
799 UTILITIES_RIG_OLD_ARM
= [
800 *UTILITIES_FUNC_COMMON_IK_FK
,
801 *UTILITIES_FUNC_OLD_ARM_FKIK
,
802 *UTILITIES_FUNC_OLD_POLE
,
803 *UTILITIES_OP_OLD_ARM_FKIK
,
804 *UTILITIES_OP_OLD_POLE
,
807 REGISTER_RIG_OLD_LEG
= REGISTER_OP_OLD_LEG_FKIK
+ REGISTER_OP_OLD_POLE
809 UTILITIES_RIG_OLD_LEG
= [
810 *UTILITIES_FUNC_COMMON_IK_FK
,
811 *UTILITIES_FUNC_OLD_LEG_FKIK
,
812 *UTILITIES_FUNC_OLD_POLE
,
813 *UTILITIES_OP_OLD_LEG_FKIK
,
814 *UTILITIES_OP_OLD_POLE
,
817 ############################
818 # Default set of utilities #
819 ############################
834 class RigUI(bpy.types.Panel):
835 bl_space_type = 'VIEW_3D'
836 bl_region_type = 'UI'
837 bl_label = "Rig Main Properties"
838 bl_idname = "VIEW3D_PT_rig_ui_" + rig_id
842 def poll(self, context):
843 if context.mode != 'POSE':
846 return (context.active_object.data.get("rig_id") == rig_id)
847 except (AttributeError, KeyError, TypeError):
850 def draw(self, context):
852 pose_bones = context.active_object.pose.bones
854 selected_bones = set(bone.name for bone in context.selected_pose_bones)
855 selected_bones.add(context.active_pose_bone.name)
856 except (AttributeError, TypeError):
859 def is_selected(names):
860 # Returns whether any of the named bones are selected.
861 if isinstance(names, list) or isinstance(names, set):
862 return not selected_bones.isdisjoint(names)
863 elif names in selected_bones:
867 num_rig_separators = [-1]
869 def emit_rig_separator():
870 if num_rig_separators[0] >= 0:
872 num_rig_separators[0] += 1
875 UI_REGISTER_BAKE_SETTINGS
= ['RigBakeSettings']
877 UI_BAKE_SETTINGS
= '''
878 class RigBakeSettings(bpy.types.Panel):
879 bl_space_type = 'VIEW_3D'
880 bl_region_type = 'UI'
881 bl_label = "Rig Bake Settings"
882 bl_idname = "VIEW3D_PT_rig_bake_settings_" + rig_id
886 def poll(self, context):
887 return RigUI.poll(context) and find_action(context.active_object) is not None
889 def draw(self, context):
890 RigifyBakeKeyframesMixin.draw_common_bake_ui(context, self.layout)
894 UI_LAYERS_PANEL
= '''
895 class RigLayers(bpy.types.Panel):
896 bl_space_type = 'VIEW_3D'
897 bl_region_type = 'UI'
898 bl_label = "Rig Layers"
899 bl_idname = "VIEW3D_PT_rig_layers_" + rig_id
903 def poll(self, context):
905 return (context.active_object.data.get("rig_id") == rig_id)
906 except (AttributeError, KeyError, TypeError):
909 def draw(self, context):
911 row_table = collections.defaultdict(list)
912 for coll in flatten_children(context.active_object.data.collections):
913 row_id = coll.get('rigify_ui_row', 0)
915 row_table[row_id].append(coll)
916 col = layout.column()
917 for row_id in range(min(row_table.keys()), 1 + max(row_table.keys())):
919 row_buttons = row_table[row_id]
921 for coll in row_buttons:
922 title = coll.get('rigify_ui_title') or coll.name
924 row2.active = coll.is_visible_ancestors
925 row2.prop(coll, 'is_visible', toggle=True, text=title)
931 class PanelExpression(object):
932 """A runtime expression involving bone properties"""
936 def __init__(self
, expr
: str):
937 self
._rigify
_expr
= expr
940 return self
._rigify
_expr
942 def __add__(self
, other
):
943 return PanelExpression(f
"({self._rigify_expr} + {repr(other)})")
945 def __sub__(self
, other
):
946 return PanelExpression(f
"({self._rigify_expr} - {repr(other)})")
948 def __mul__(self
, other
):
949 return PanelExpression(f
"({self._rigify_expr} * {repr(other)})")
951 def __matmul__(self
, other
):
952 return PanelExpression(f
"({self._rigify_expr} @ {repr(other)})")
954 def __truediv__(self
, other
):
955 return PanelExpression(f
"({self._rigify_expr} / {repr(other)})")
957 def __floordiv__(self
, other
):
958 return PanelExpression(f
"({self._rigify_expr} // {repr(other)})")
960 def __mod__(self
, other
):
961 return PanelExpression(f
"({self._rigify_expr} % {repr(other)})")
963 def __lshift__(self
, other
):
964 return PanelExpression(f
"({self._rigify_expr} << {repr(other)})")
966 def __rshift__(self
, other
):
967 return PanelExpression(f
"({self._rigify_expr} >> {repr(other)})")
969 def __and__(self
, other
):
970 return PanelExpression(f
"({self._rigify_expr} & {repr(other)})")
972 def __xor__(self
, other
):
973 return PanelExpression(f
"({self._rigify_expr} ^ {repr(other)})")
975 def __or__(self
, other
):
976 return PanelExpression(f
"({self._rigify_expr} | {repr(other)})")
978 def __radd__(self
, other
):
979 return PanelExpression(f
"({repr(other)} + {self._rigify_expr})")
981 def __rsub__(self
, other
):
982 return PanelExpression(f
"({repr(other)} - {self._rigify_expr})")
984 def __rmul__(self
, other
):
985 return PanelExpression(f
"({repr(other)} * {self._rigify_expr})")
987 def __rmatmul__(self
, other
):
988 return PanelExpression(f
"({repr(other)} @ {self._rigify_expr})")
990 def __rtruediv__(self
, other
):
991 return PanelExpression(f
"({repr(other)} / {self._rigify_expr})")
993 def __rfloordiv__(self
, other
):
994 return PanelExpression(f
"({repr(other)} // {self._rigify_expr})")
996 def __rmod__(self
, other
):
997 return PanelExpression(f
"({repr(other)} % {self._rigify_expr})")
999 def __rlshift__(self
, other
):
1000 return PanelExpression(f
"({repr(other)} << {self._rigify_expr})")
1002 def __rrshift__(self
, other
):
1003 return PanelExpression(f
"({repr(other)} >> {self._rigify_expr})")
1005 def __rand__(self
, other
):
1006 return PanelExpression(f
"({repr(other)} & {self._rigify_expr})")
1008 def __rxor__(self
, other
):
1009 return PanelExpression(f
"({repr(other)} ^ {self._rigify_expr})")
1011 def __ror__(self
, other
):
1012 return PanelExpression(f
"({repr(other)} | {self._rigify_expr})")
1015 return PanelExpression(f
"-{self._rigify_expr}")
1018 return PanelExpression(f
"+{self._rigify_expr}")
1021 return PanelExpression(f
"abs({self._rigify_expr})")
1023 def __invert__(self
):
1024 return PanelExpression(f
"~{self._rigify_expr}")
1026 def __round__(self
, digits
=None):
1027 return PanelExpression(f
"round({self._rigify_expr}, {digits})")
1029 def __trunc__(self
):
1030 return PanelExpression(f
"trunc({self._rigify_expr})")
1032 def __floor__(self
):
1033 return PanelExpression(f
"floor({self._rigify_expr})")
1036 return PanelExpression(f
"ceil({self._rigify_expr})")
1038 def __lt__(self
, other
):
1039 return PanelExpression(f
"({self._rigify_expr} < {repr(other)})")
1041 def __le__(self
, other
):
1042 return PanelExpression(f
"({self._rigify_expr} <= {repr(other)})")
1044 def __eq__(self
, other
):
1045 return PanelExpression(f
"({self._rigify_expr} == {repr(other)})")
1047 def __ne__(self
, other
):
1048 return PanelExpression(f
"({self._rigify_expr} != {repr(other)})")
1050 def __gt__(self
, other
):
1051 return PanelExpression(f
"({self._rigify_expr} > {repr(other)})")
1053 def __ge__(self
, other
):
1054 return PanelExpression(f
"({self._rigify_expr} >= {repr(other)})")
1057 raise NotImplementedError("This object wraps an expression, not a value; casting to boolean is meaningless")
1060 class PanelReferenceExpression(PanelExpression
):
1062 A runtime expression referencing an object.
1066 def __getitem__(self
, item
):
1067 return PanelReferenceExpression(f
"{self._rigify_expr}[{repr(item)}]")
1069 def __getattr__(self
, item
):
1070 return PanelReferenceExpression(f
"{self._rigify_expr}.{item}")
1072 def get(self
, item
, default
=None):
1073 return PanelReferenceExpression(f
"{self._rigify_expr}.get({repr(item)}, {repr(default)})")
1076 def quote_parameters(positional
: list[Any
], named
: dict[str, Any
]):
1077 """Quote the given positional and named parameters as a code string."""
1078 positional_list
= [repr(v
) for v
in positional
]
1079 named_list
= ["%s=%r" % (k
, v
) for k
, v
in named
.items()]
1080 return ', '.join(positional_list
+ named_list
)
1083 def indent_lines(lines
: list[str], indent
=4):
1085 prefix
= ' ' * indent
1086 return [prefix
+ line
for line
in lines
]
1091 class PanelLayout(object):
1092 """Utility class that builds code for creating a layout."""
1094 parent
: Optional
['PanelLayout']
1095 script
: 'ScriptGenerator'
1098 items
: list[Union
[str, 'PanelLayout']]
1100 def __init__(self
, parent
: Union
['PanelLayout', 'ScriptGenerator'], index
=0):
1101 if isinstance(parent
, PanelLayout
):
1102 self
.parent
= parent
1103 self
.script
= parent
.script
1106 self
.script
= parent
1112 self
.layout
= self
._get
_layout
_var
(index
)
1113 self
.is_empty
= True
1116 def _get_layout_var(index
):
1117 return 'layout' if index
== 0 else 'group' + str(index
)
1119 def clear_empty(self
):
1120 self
.is_empty
= False
1123 self
.parent
.clear_empty()
1125 def get_lines(self
) -> list[str]:
1128 for item
in self
.items
:
1129 if isinstance(item
, PanelLayout
):
1130 lines
+= item
.get_lines()
1135 return self
.wrap_lines(lines
)
1139 def wrap_lines(self
, lines
):
1140 return self
.header
+ indent_lines(lines
, self
.indent
)
1142 def add_line(self
, line
: str):
1143 assert isinstance(line
, str)
1145 self
.items
.append(line
)
1150 def use_bake_settings(self
):
1151 """This panel contains operators that need the common Bake settings."""
1152 self
.parent
.use_bake_settings()
1154 def custom_prop(self
, bone_name
: str, prop_name
: str, **params
):
1155 """Add a custom property input field to the panel."""
1156 param_str
= quote_parameters([rna_idprop_quote_path(prop_name
)], params
)
1158 "%s.prop(pose_bones[%r], %s)" % (self
.layout
, bone_name
, param_str
)
1161 def operator(self
, operator_name
: str, *,
1162 properties
: Optional
[dict[str, Any
]] = None,
1164 """Add an operator call button to the panel."""
1165 name
= operator_name
.format_map(self
.script
.format_args
)
1166 param_str
= quote_parameters([name
], params
)
1167 call_str
= "%s.operator(%s)" % (self
.layout
, param_str
)
1169 self
.add_line("props = " + call_str
)
1170 for k
, v
in properties
.items():
1171 self
.add_line("props.%s = %r" % (k
, v
))
1173 self
.add_line(call_str
)
1175 def add_nested_layout(self
, method_name
: str, params
: dict[str, Any
]) -> 'PanelLayout':
1176 param_str
= quote_parameters([], params
)
1177 sub_panel
= PanelLayout(self
, self
.index
+ 1)
1178 sub_panel
.header
.append(f
'{sub_panel.layout} = {self.layout}.{method_name}({param_str})')
1179 self
.items
.append(sub_panel
)
1182 def row(self
, **params
):
1183 """Add a nested row layout to the panel."""
1184 return self
.add_nested_layout('row', params
)
1186 def column(self
, **params
):
1187 """Add a nested column layout to the panel."""
1188 return self
.add_nested_layout('column', params
)
1190 def split(self
, **params
):
1191 """Add a split layout to the panel."""
1192 return self
.add_nested_layout('split', params
)
1195 def expr_bone(bone_name
):
1196 """Returns an expression referencing the specified pose bone."""
1197 return PanelReferenceExpression(f
"pose_bones[%r]" % bone_name
)
1200 def expr_and(*expressions
):
1201 """Returns a boolean and expression of its parameters."""
1202 return PanelExpression("(" + " and ".join(repr(e
) for e
in expressions
) + ")")
1205 def expr_or(*expressions
):
1206 """Returns a boolean or expression of its parameters."""
1207 return PanelExpression("(" + " or ".join(repr(e
) for e
in expressions
) + ")")
1210 def expr_if_else(condition
, true_expr
, false_expr
):
1211 """Returns a conditional expression."""
1212 return PanelExpression(f
"({repr(true_expr)} if {repr(condition)} else {repr(false_expr)})")
1215 def expr_call(func
: str, *expressions
):
1216 """Returns an expression calling the specified function with given parameters."""
1217 return PanelExpression(func
+ "(" + ", ".join(repr(e
) for e
in expressions
) + ")")
1219 def set_layout_property(self
, prop_name
: str, prop_value
: Any
):
1220 assert self
.index
> 0 # Don't change properties on the root layout
1221 self
.add_line("%s.%s = %r" % (self
.layout
, prop_name
, prop_value
))
1225 raise NotImplementedError("This is a write only property")
1228 def active(self
, value
):
1229 self
.set_layout_property('active', value
)
1233 raise NotImplementedError("This is a write only property")
1236 def enabled(self
, value
):
1237 self
.set_layout_property('enabled', value
)
1240 class BoneSetPanelLayout(PanelLayout
):
1241 """Panel restricted to a certain set of bones."""
1243 parent
: 'RigPanelLayout'
1245 def __init__(self
, rig_panel
: 'RigPanelLayout', bones
: frozenset[str]):
1246 assert isinstance(bones
, frozenset)
1247 super().__init
__(rig_panel
)
1249 self
.show_bake_settings
= False
1251 def clear_empty(self
):
1252 self
.parent
.bones |
= self
.bones
1254 super().clear_empty()
1256 def wrap_lines(self
, lines
):
1257 if self
.bones
!= self
.parent
.bones
:
1258 header
= ["if is_selected(%r):" % (set(self
.bones
))]
1259 return header
+ indent_lines(lines
)
1263 def use_bake_settings(self
):
1264 self
.show_bake_settings
= True
1265 if not self
.script
.use_bake_settings
:
1266 self
.script
.use_bake_settings
= True
1267 self
.script
.add_utilities(SCRIPT_UTILITIES_BAKE
)
1268 self
.script
.register_classes(SCRIPT_REGISTER_BAKE
)
1271 class RigPanelLayout(PanelLayout
):
1272 """Panel owned by a certain rig."""
1274 def __init__(self
, script
: 'ScriptGenerator', _rig
):
1275 super().__init
__(script
)
1277 self
.sub_panels
= OrderedDict()
1279 def wrap_lines(self
, lines
):
1280 header
= ["if is_selected(%r):" % (set(self
.bones
))]
1281 prefix
= ["emit_rig_separator()"]
1282 return header
+ indent_lines(prefix
+ lines
)
1284 def panel_with_selected_check(self
, control_names
):
1285 selected_set
= frozenset(control_names
)
1287 if selected_set
in self
.sub_panels
:
1288 return self
.sub_panels
[selected_set
]
1290 panel
= BoneSetPanelLayout(self
, selected_set
)
1291 self
.sub_panels
[selected_set
] = panel
1292 self
.items
.append(panel
)
1296 class ScriptGenerator(base_generate
.GeneratorPlugin
):
1297 """Generator plugin that builds the python script attached to the rig."""
1301 format_args
: dict[str, str]
1303 def __init__(self
, generator
):
1304 super().__init
__(generator
)
1306 self
.ui_scripts
= []
1307 self
.ui_imports
= UI_IMPORTS
.copy()
1308 self
.ui_utilities
= UI_UTILITIES
.copy()
1309 self
.ui_register
= UI_REGISTER
.copy()
1310 self
.ui_register_drivers
= []
1311 self
.ui_register_props
= []
1313 self
.ui_rig_panels
= OrderedDict()
1315 self
.use_bake_settings
= False
1317 # Structured panel code generation
1318 def panel_with_selected_check(self
, rig
, control_names
):
1319 """Add a panel section with restricted selection."""
1322 if rig_key
in self
.ui_rig_panels
:
1323 panel
= self
.ui_rig_panels
[rig_key
]
1325 panel
= RigPanelLayout(self
, rig
)
1326 self
.ui_rig_panels
[rig_key
] = panel
1328 return panel
.panel_with_selected_check(control_names
)
1331 def add_panel_code(self
, str_list
: list[str]):
1332 """Add raw code to the panel."""
1333 self
.ui_scripts
+= str_list
1335 def add_imports(self
, str_list
: list[str]):
1336 self
.ui_imports
+= str_list
1338 def add_utilities(self
, str_list
: list[str]):
1339 self
.ui_utilities
+= str_list
1341 def register_classes(self
, str_list
: list[str]):
1342 self
.ui_register
+= str_list
1344 def register_driver_functions(self
, str_list
: list[str]):
1345 self
.ui_register_drivers
+= str_list
1347 def register_property(self
, name
: str, definition
):
1348 self
.ui_register_props
.append((name
, definition
))
1350 def initialize(self
):
1351 self
.format_args
= {
1352 'rig_id': self
.generator
.rig_id
,
1356 metarig
= self
.generator
.metarig
1357 rig_id
= self
.generator
.rig_id
1359 # Generate the UI script
1360 script
= metarig
.data
.rigify_rig_ui
1365 script_name
= self
.generator
.obj
.name
+ "_ui.py"
1366 script
= bpy
.data
.texts
.new(script_name
)
1367 metarig
.data
.rigify_rig_ui
= script
1369 for s
in OrderedDict
.fromkeys(self
.ui_imports
):
1370 script
.write(s
+ "\n")
1372 script
.write(UI_BASE_UTILITIES
% rig_id
)
1374 for s
in OrderedDict
.fromkeys(self
.ui_utilities
):
1375 script
.write(s
+ "\n")
1377 script
.write(UI_SLIDERS
)
1379 for s
in self
.ui_scripts
:
1380 script
.write("\n " + s
.replace("\n", "\n ") + "\n")
1382 if len(self
.ui_scripts
) > 0:
1383 script
.write("\n num_rig_separators[0] = 0\n")
1385 for panel
in self
.ui_rig_panels
.values():
1386 lines
= panel
.get_lines()
1388 script
.write("\n ".join([''] + lines
) + "\n")
1390 if self
.use_bake_settings
:
1391 self
.ui_register
= UI_REGISTER_BAKE_SETTINGS
+ self
.ui_register
1392 script
.write(UI_BAKE_SETTINGS
)
1394 script
.write(UI_LAYERS_PANEL
)
1396 script
.write("\ndef register():\n")
1398 ui_register
= OrderedDict
.fromkeys(self
.ui_register
)
1399 for s
in ui_register
:
1400 script
.write(" bpy.utils.register_class("+s
+")\n")
1402 ui_register_drivers
= OrderedDict
.fromkeys(self
.ui_register_drivers
)
1403 for s
in ui_register_drivers
:
1404 script
.write(" bpy.app.driver_namespace['"+s
+"'] = "+s
+"\n")
1406 ui_register_props
= OrderedDict
.fromkeys(self
.ui_register_props
)
1407 for classname
, text
in ui_register_props
:
1408 script
.write(f
" bpy.types.{classname} = {text}\n ")
1410 script
.write("\ndef unregister():\n")
1412 for s
in ui_register_props
:
1413 script
.write(" del bpy.types.%s\n" % s
[0])
1415 for s
in ui_register
:
1416 script
.write(" bpy.utils.unregister_class("+s
+")\n")
1418 for s
in ui_register_drivers
:
1419 script
.write(" del bpy.app.driver_namespace['"+s
+"']\n")
1421 script
.write("\nregister()\n")
1422 script
.use_module
= True
1425 exec(script
.as_string(), {})
1427 # Attach the script to the rig
1428 self
.obj
['rig_ui'] = script