1 # SPDX-License-Identifier: GPL-2.0-or-later
5 from collections
import OrderedDict
7 from .utils
.animation
import SCRIPT_REGISTER_BAKE
, SCRIPT_UTILITIES_BAKE
9 from . import base_generate
11 from rna_prop_ui
import rna_idprop_quote_path
20 'from math import pi',
21 'from bpy.props import StringProperty',
22 'from mathutils import Euler, Matrix, Quaternion, Vector',
23 'from rna_prop_ui import rna_idprop_quote_path',
26 UI_BASE_UTILITIES
= '''
30 ############################
31 ## Math utility functions ##
32 ############################
34 def perpendicular_vector(v):
35 """ Returns a vector that is perpendicular to the one given.
36 The returned vector is _not_ guaranteed to be normalized.
38 # Create a vector that is not aligned with v.
39 # It doesn't matter what vector. Just any vector
40 # that's guaranteed to not be pointing in the same
42 if abs(v[0]) < abs(v[1]):
47 # Use cross prouct to generate a vector perpendicular to
48 # both tv and (more importantly) v.
52 def rotation_difference(mat1, mat2):
53 """ Returns the shortest-path rotational difference between two
56 q1 = mat1.to_quaternion()
57 q2 = mat2.to_quaternion()
58 angle = math.acos(min(1,max(-1,q1.dot(q2)))) * 2
60 angle = -angle + (2*pi)
63 def find_min_range(f,start_angle,delta=pi/8):
64 """ finds the range where lies the minimum of function f applied on bone_ik and bone_fk
68 while (angle > (start_angle - 2*pi)) and (angle < (start_angle + 2*pi)):
69 l_dist = f(angle-delta)
71 r_dist = f(angle+delta)
72 if min((l_dist,c_dist,r_dist)) == c_dist:
73 return (angle-delta,angle+delta)
77 def ternarySearch(f, left, right, absolutePrecision):
79 Find minimum of unimodal function f() within [left, right]
80 To find the maximum, revert the if/else statement or revert the comparison.
83 #left and right are the current bounds; the maximum is between them
84 if abs(right - left) < absolutePrecision:
85 return (left + right)/2
87 leftThird = left + (right - left)/3
88 rightThird = right - (right - left)/3
90 if f(leftThird) > f(rightThird):
96 UTILITIES_FUNC_COMMON_IKFK
= ['''
97 #########################################
98 ## "Visual Transform" helper functions ##
99 #########################################
101 def get_pose_matrix_in_other_space(mat, pose_bone):
102 """ Returns the transform matrix relative to pose_bone's current
103 transform space. In other words, presuming that mat is in
104 armature space, slapping the returned matrix onto pose_bone
105 should give it the armature-space transforms of mat.
107 return pose_bone.id_data.convert_space(matrix=mat, pose_bone=pose_bone, from_space='POSE', to_space='LOCAL')
110 def convert_pose_matrix_via_rest_delta(mat, from_bone, to_bone):
111 """Convert pose of one bone to another bone, preserving the rest pose difference between them."""
112 return mat @ from_bone.bone.matrix_local.inverted() @ to_bone.bone.matrix_local
115 def convert_pose_matrix_via_pose_delta(mat, from_bone, to_bone):
116 """Convert pose of one bone to another bone, preserving the current pose difference between them."""
117 return mat @ from_bone.matrix.inverted() @ to_bone.matrix
120 def get_local_pose_matrix(pose_bone):
121 """ Returns the local transform matrix of the given pose bone.
123 return get_pose_matrix_in_other_space(pose_bone.matrix, pose_bone)
126 def set_pose_translation(pose_bone, mat):
127 """ Sets the pose bone's translation to the same translation as the given matrix.
128 Matrix should be given in bone's local space.
130 pose_bone.location = mat.to_translation()
133 def set_pose_rotation(pose_bone, mat):
134 """ Sets the pose bone's rotation to the same rotation as the given matrix.
135 Matrix should be given in bone's local space.
137 q = mat.to_quaternion()
139 if pose_bone.rotation_mode == 'QUATERNION':
140 pose_bone.rotation_quaternion = q
141 elif pose_bone.rotation_mode == 'AXIS_ANGLE':
142 pose_bone.rotation_axis_angle[0] = q.angle
143 pose_bone.rotation_axis_angle[1] = q.axis[0]
144 pose_bone.rotation_axis_angle[2] = q.axis[1]
145 pose_bone.rotation_axis_angle[3] = q.axis[2]
147 pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode)
150 def set_pose_scale(pose_bone, mat):
151 """ Sets the pose bone's scale to the same scale as the given matrix.
152 Matrix should be given in bone's local space.
154 pose_bone.scale = mat.to_scale()
157 def match_pose_translation(pose_bone, target_bone):
158 """ Matches pose_bone's visual translation to target_bone's visual
160 This function assumes you are in pose mode on the relevant armature.
162 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
163 set_pose_translation(pose_bone, mat)
166 def match_pose_rotation(pose_bone, target_bone):
167 """ Matches pose_bone's visual rotation to target_bone's visual
169 This function assumes you are in pose mode on the relevant armature.
171 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
172 set_pose_rotation(pose_bone, mat)
175 def match_pose_scale(pose_bone, target_bone):
176 """ Matches pose_bone's visual scale to target_bone's visual
178 This function assumes you are in pose mode on the relevant armature.
180 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
181 set_pose_scale(pose_bone, mat)
184 ##############################
185 ## IK/FK snapping functions ##
186 ##############################
188 def correct_rotation(view_layer, bone_ik, target_matrix, *, ctrl_ik=None):
189 """ Corrects the ik rotation in ik2fk snapping functions
192 axis = target_matrix.to_3x3().col[1].normalized()
193 ctrl_ik = ctrl_ik or bone_ik
196 # Rotate the bone and return the actual angle between bones
197 ctrl_ik.rotation_euler[1] = angle
200 return -(bone_ik.vector.normalized().dot(axis))
202 if ctrl_ik.rotation_mode in {'QUATERNION', 'AXIS_ANGLE'}:
203 ctrl_ik.rotation_mode = 'ZXY'
205 start_angle = ctrl_ik.rotation_euler[1]
207 alfarange = find_min_range(distance, start_angle)
208 alfamin = ternarySearch(distance, alfarange[0], alfarange[1], pi / 180)
210 ctrl_ik.rotation_euler[1] = alfamin
214 def correct_scale(view_layer, bone_ik, target_matrix, *, ctrl_ik=None):
215 """ Correct the scale of the base IK bone. """
216 input_scale = target_matrix.to_scale()
217 ctrl_ik = ctrl_ik or bone_ik
220 cur_scale = bone_ik.matrix.to_scale()
223 v * i / c for v, i, c in zip(bone_ik.scale, input_scale, cur_scale)
228 if all(abs((c - i)/i) < 0.01 for i, c in zip(input_scale, cur_scale)):
232 def match_pole_target(view_layer, ik_first, ik_last, pole, match_bone_matrix, length):
233 """ Places an IK chain's pole target to match ik_first's
234 transforms to match_bone. All bones should be given as pose bones.
235 You need to be in pose mode on the relevant armature object.
236 ik_first: first bone in the IK chain
237 ik_last: last bone in the IK chain
238 pole: pole target bone for the IK chain
239 match_bone: bone to match ik_first to (probably first bone in a matching FK chain)
240 length: distance pole target should be placed from the chain center
242 a = ik_first.matrix.to_translation()
243 b = ik_last.matrix.to_translation() + ik_last.vector
245 # Vector from the head of ik_first to the
249 # Get a vector perpendicular to ikv
250 pv = perpendicular_vector(ikv).normalized() * length
253 """ Set pole target's position based on a vector
254 from the arm center line.
256 # Translate pvi into armature space
257 ploc = a + (ikv/2) + pvi
259 # Set pole target to location
260 mat = get_pose_matrix_in_other_space(Matrix.Translation(ploc), pole)
261 set_pose_translation(pole, mat)
267 # Get the rotation difference between ik_first and match_bone
268 angle = rotation_difference(ik_first.matrix, match_bone_matrix)
270 # Try compensating for the rotation difference in both directions
271 pv1 = Matrix.Rotation(angle, 4, ikv) @ pv
273 ang1 = rotation_difference(ik_first.matrix, match_bone_matrix)
275 pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv
277 ang2 = rotation_difference(ik_first.matrix, match_bone_matrix)
279 # Do the one with the smaller angle
287 def parse_bone_names(names_string):
288 if names_string[0] == '[' and names_string[-1] == ']':
289 return eval(names_string)
295 UTILITIES_FUNC_OLD_ARM_FKIK
= ['''
296 ######################
297 ## IK Arm functions ##
298 ######################
300 def fk2ik_arm(obj, fk, ik):
301 """ Matches the fk bones in an arm rig to the ik bones.
303 fk: list of fk bone names
304 ik: list of ik bone names
306 view_layer = bpy.context.view_layer
307 uarm = obj.pose.bones[fk[0]]
308 farm = obj.pose.bones[fk[1]]
309 hand = obj.pose.bones[fk[2]]
310 uarmi = obj.pose.bones[ik[0]]
311 farmi = obj.pose.bones[ik[1]]
312 handi = obj.pose.bones[ik[2]]
314 if 'auto_stretch' in handi.keys():
315 # This is kept for compatibility with legacy rigify Human
317 if handi['auto_stretch'] == 0.0:
318 uarm['stretch_length'] = handi['stretch_length']
320 diff = (uarmi.vector.length + farmi.vector.length) / (uarm.vector.length + farm.vector.length)
321 uarm['stretch_length'] *= diff
324 match_pose_rotation(uarm, uarmi)
325 match_pose_scale(uarm, uarmi)
329 match_pose_rotation(farm, farmi)
330 match_pose_scale(farm, farmi)
334 match_pose_rotation(hand, handi)
335 match_pose_scale(hand, handi)
339 match_pose_translation(uarm, uarmi)
340 match_pose_rotation(uarm, uarmi)
341 match_pose_scale(uarm, uarmi)
345 #match_pose_translation(hand, handi)
346 match_pose_rotation(farm, farmi)
347 match_pose_scale(farm, farmi)
351 match_pose_translation(hand, handi)
352 match_pose_rotation(hand, handi)
353 match_pose_scale(hand, handi)
357 def ik2fk_arm(obj, fk, ik):
358 """ Matches the ik bones in an arm rig to the fk bones.
360 fk: list of fk bone names
361 ik: list of ik bone names
363 view_layer = bpy.context.view_layer
364 uarm = obj.pose.bones[fk[0]]
365 farm = obj.pose.bones[fk[1]]
366 hand = obj.pose.bones[fk[2]]
367 uarmi = obj.pose.bones[ik[0]]
368 farmi = obj.pose.bones[ik[1]]
369 handi = obj.pose.bones[ik[2]]
371 main_parent = obj.pose.bones[ik[4]]
373 if ik[3] != "" and main_parent['pole_vector']:
374 pole = obj.pose.bones[ik[3]]
381 # handi['stretch_length'] = uarm['stretch_length']
384 match_pose_translation(handi, hand)
385 match_pose_rotation(handi, hand)
386 match_pose_scale(handi, hand)
389 # Pole target position
390 match_pole_target(view_layer, uarmi, farmi, pole, uarm.matrix, (uarmi.length + farmi.length))
394 match_pose_translation(handi, hand)
395 match_pose_rotation(handi, hand)
396 match_pose_scale(handi, hand)
400 match_pose_translation(uarmi, uarm)
401 #match_pose_rotation(uarmi, uarm)
402 set_pose_rotation(uarmi, Matrix())
403 match_pose_scale(uarmi, uarm)
406 # Rotation Correction
407 correct_rotation(view_layer, uarmi, uarm.matrix)
409 correct_scale(view_layer, uarmi, uarm.matrix)
412 UTILITIES_FUNC_OLD_LEG_FKIK
= ['''
413 ######################
414 ## IK Leg functions ##
415 ######################
417 def fk2ik_leg(obj, fk, ik):
418 """ Matches the fk bones in a leg rig to the ik bones.
420 fk: list of fk bone names
421 ik: list of ik bone names
423 view_layer = bpy.context.view_layer
424 thigh = obj.pose.bones[fk[0]]
425 shin = obj.pose.bones[fk[1]]
426 foot = obj.pose.bones[fk[2]]
427 mfoot = obj.pose.bones[fk[3]]
428 thighi = obj.pose.bones[ik[0]]
429 shini = obj.pose.bones[ik[1]]
430 footi = obj.pose.bones[ik[2]]
431 mfooti = obj.pose.bones[ik[3]]
433 if 'auto_stretch' in footi.keys():
434 # This is kept for compatibility with legacy rigify Human
436 if footi['auto_stretch'] == 0.0:
437 thigh['stretch_length'] = footi['stretch_length']
439 diff = (thighi.vector.length + shini.vector.length) / (thigh.vector.length + shin.vector.length)
440 thigh['stretch_length'] *= diff
443 match_pose_rotation(thigh, thighi)
444 match_pose_scale(thigh, thighi)
448 match_pose_rotation(shin, shini)
449 match_pose_scale(shin, shini)
453 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot)
454 footmat = convert_pose_matrix_via_rest_delta(footmat, mfoot, foot)
455 set_pose_rotation(foot, footmat)
456 set_pose_scale(foot, footmat)
461 match_pose_translation(thigh, thighi)
462 match_pose_rotation(thigh, thighi)
463 match_pose_scale(thigh, thighi)
467 match_pose_rotation(shin, shini)
468 match_pose_scale(shin, shini)
472 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot)
473 footmat = convert_pose_matrix_via_rest_delta(footmat, mfoot, foot)
474 set_pose_rotation(foot, footmat)
475 set_pose_scale(foot, footmat)
479 def ik2fk_leg(obj, fk, ik):
480 """ Matches the ik bones in a leg rig to the fk bones.
482 fk: list of fk bone names
483 ik: list of ik bone names
485 view_layer = bpy.context.view_layer
486 thigh = obj.pose.bones[fk[0]]
487 shin = obj.pose.bones[fk[1]]
488 mfoot = obj.pose.bones[fk[2]]
490 foot = obj.pose.bones[fk[3]]
493 thighi = obj.pose.bones[ik[0]]
494 shini = obj.pose.bones[ik[1]]
495 footi = obj.pose.bones[ik[2]]
496 footroll = obj.pose.bones[ik[3]]
498 main_parent = obj.pose.bones[ik[6]]
500 if ik[4] != "" and main_parent['pole_vector']:
501 pole = obj.pose.bones[ik[4]]
504 mfooti = obj.pose.bones[ik[5]]
506 if (not pole) and (foot):
509 set_pose_rotation(footroll, Matrix())
513 footmat = get_pose_matrix_in_other_space(foot.matrix, footi)
514 footmat = convert_pose_matrix_via_rest_delta(footmat, mfooti, footi)
515 set_pose_translation(footi, footmat)
516 set_pose_rotation(footi, footmat)
517 set_pose_scale(footi, footmat)
521 match_pose_translation(thighi, thigh)
522 #match_pose_rotation(thighi, thigh)
523 set_pose_rotation(thighi, Matrix())
524 match_pose_scale(thighi, thigh)
527 # Rotation Correction
528 correct_rotation(view_layer, thighi, thigh.matrix)
532 if 'stretch_length' in footi.keys() and 'stretch_length' in thigh.keys():
533 # Kept for compat with legacy rigify Human
534 footi['stretch_length'] = thigh['stretch_length']
537 set_pose_rotation(footroll, Matrix())
541 footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi)
542 footmat = convert_pose_matrix_via_rest_delta(footmat, mfooti, footi)
543 set_pose_translation(footi, footmat)
544 set_pose_rotation(footi, footmat)
545 set_pose_scale(footi, footmat)
548 # Pole target position
549 match_pole_target(view_layer, thighi, shini, pole, thigh.matrix, (thighi.length + shini.length))
551 correct_scale(view_layer, thighi, thigh.matrix)
554 UTILITIES_FUNC_OLD_POLE
= ['''
555 ################################
556 ## IK Rotation-Pole functions ##
557 ################################
559 def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole):
561 rig_id = rig.data['rig_id']
562 leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
563 arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
564 leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
565 arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
567 controls = parse_bone_names(controls)
568 ik_ctrl = parse_bone_names(ik_ctrl)
569 fk_ctrl = parse_bone_names(fk_ctrl)
570 parent = parse_bone_names(parent)
571 pole = parse_bone_names(pole)
573 pbones = bpy.context.selected_pose_bones
574 bpy.ops.pose.select_all(action='DESELECT')
578 new_pole_vector_value = not rig.pose.bones[parent]['pole_vector']
580 if b.name in controls or b.name in ik_ctrl:
581 if limb_type == 'arm':
584 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
585 rig.pose.bones[controls[4]].bone.select = not new_pole_vector_value
586 rig.pose.bones[parent].bone.select = not new_pole_vector_value
587 rig.pose.bones[pole].bone.select = new_pole_vector_value
589 kwargs1 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
590 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
591 'hand_ik': controls[4]}
592 kwargs2 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
593 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
594 'pole': pole, 'main_parent': parent}
598 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
599 rig.pose.bones[controls[6]].bone.select = not new_pole_vector_value
600 rig.pose.bones[controls[5]].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 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
605 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
606 'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
607 kwargs2 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
608 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
609 'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5], 'mfoot_ik': ik_ctrl[2],
610 'main_parent': parent}
613 rig.pose.bones[parent]['pole_vector'] = new_pole_vector_value
616 bpy.ops.pose.select_all(action='DESELECT')
619 REGISTER_OP_OLD_ARM_FKIK
= ['Rigify_Arm_FK2IK', 'Rigify_Arm_IK2FK']
621 UTILITIES_OP_OLD_ARM_FKIK
= ['''
622 ##################################
623 ## IK/FK Arm snapping operators ##
624 ##################################
626 class Rigify_Arm_FK2IK(bpy.types.Operator):
627 """ Snaps an FK arm to an IK arm.
629 bl_idname = "pose.rigify_arm_fk2ik_" + rig_id
630 bl_label = "Rigify Snap FK arm to IK"
631 bl_options = {'UNDO', 'INTERNAL'}
633 uarm_fk: StringProperty(name="Upper Arm FK Name")
634 farm_fk: StringProperty(name="Forerm FK Name")
635 hand_fk: StringProperty(name="Hand FK Name")
637 uarm_ik: StringProperty(name="Upper Arm IK Name")
638 farm_ik: StringProperty(name="Forearm IK Name")
639 hand_ik: StringProperty(name="Hand IK Name")
642 def poll(cls, context):
643 return (context.active_object != None and context.mode == 'POSE')
645 def execute(self, context):
646 fk2ik_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk], ik=[self.uarm_ik, self.farm_ik, self.hand_ik])
650 class Rigify_Arm_IK2FK(bpy.types.Operator):
651 """ Snaps an IK arm to an FK arm.
653 bl_idname = "pose.rigify_arm_ik2fk_" + rig_id
654 bl_label = "Rigify Snap IK arm to FK"
655 bl_options = {'UNDO', 'INTERNAL'}
657 uarm_fk: StringProperty(name="Upper Arm FK Name")
658 farm_fk: StringProperty(name="Forerm FK Name")
659 hand_fk: StringProperty(name="Hand FK Name")
661 uarm_ik: StringProperty(name="Upper Arm IK Name")
662 farm_ik: StringProperty(name="Forearm IK Name")
663 hand_ik: StringProperty(name="Hand IK Name")
664 pole : StringProperty(name="Pole IK Name")
666 main_parent: StringProperty(name="Main Parent", default="")
669 def poll(cls, context):
670 return (context.active_object != None and context.mode == 'POSE')
672 def execute(self, context):
673 ik2fk_arm(context.active_object, fk=[self.uarm_fk, self.farm_fk, self.hand_fk], ik=[self.uarm_ik, self.farm_ik, self.hand_ik, self.pole, self.main_parent])
677 REGISTER_OP_OLD_LEG_FKIK
= ['Rigify_Leg_FK2IK', 'Rigify_Leg_IK2FK']
679 UTILITIES_OP_OLD_LEG_FKIK
= ['''
680 ##################################
681 ## IK/FK Leg snapping operators ##
682 ##################################
684 class Rigify_Leg_FK2IK(bpy.types.Operator):
685 """ Snaps an FK leg to an IK leg.
687 bl_idname = "pose.rigify_leg_fk2ik_" + rig_id
688 bl_label = "Rigify Snap FK leg to IK"
689 bl_options = {'UNDO', 'INTERNAL'}
691 thigh_fk: StringProperty(name="Thigh FK Name")
692 shin_fk: StringProperty(name="Shin FK Name")
693 foot_fk: StringProperty(name="Foot FK Name")
694 mfoot_fk: StringProperty(name="MFoot FK Name")
696 thigh_ik: StringProperty(name="Thigh IK Name")
697 shin_ik: StringProperty(name="Shin IK Name")
698 foot_ik: StringProperty(name="Foot IK Name")
699 mfoot_ik: StringProperty(name="MFoot IK Name")
702 def poll(cls, context):
703 return (context.active_object != None and context.mode == 'POSE')
705 def execute(self, context):
706 fk2ik_leg(context.active_object, fk=[self.thigh_fk, self.shin_fk, self.foot_fk, self.mfoot_fk], ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.mfoot_ik])
710 class Rigify_Leg_IK2FK(bpy.types.Operator):
711 """ Snaps an IK leg to an FK leg.
713 bl_idname = "pose.rigify_leg_ik2fk_" + rig_id
714 bl_label = "Rigify Snap IK leg to FK"
715 bl_options = {'UNDO', 'INTERNAL'}
717 thigh_fk: StringProperty(name="Thigh FK Name")
718 shin_fk: StringProperty(name="Shin FK Name")
719 mfoot_fk: StringProperty(name="MFoot FK Name")
720 foot_fk: StringProperty(name="Foot FK Name", default="")
721 thigh_ik: StringProperty(name="Thigh IK Name")
722 shin_ik: StringProperty(name="Shin IK Name")
723 foot_ik: StringProperty(name="Foot IK Name")
724 footroll: StringProperty(name="Foot Roll Name")
725 pole: StringProperty(name="Pole IK Name")
726 mfoot_ik: StringProperty(name="MFoot IK Name")
728 main_parent: StringProperty(name="Main Parent", default="")
731 def poll(cls, context):
732 return (context.active_object != None and context.mode == 'POSE')
734 def execute(self, context):
735 ik2fk_leg(context.active_object, fk=[self.thigh_fk, self.shin_fk, self.mfoot_fk, self.foot_fk], ik=[self.thigh_ik, self.shin_ik, self.foot_ik, self.footroll, self.pole, self.mfoot_ik, self.main_parent])
739 REGISTER_OP_OLD_POLE
= ['Rigify_Rot2PoleSwitch']
741 UTILITIES_OP_OLD_POLE
= ['''
742 ###########################
743 ## IK Rotation Pole Snap ##
744 ###########################
746 class Rigify_Rot2PoleSwitch(bpy.types.Operator):
747 bl_idname = "pose.rigify_rot2pole_" + rig_id
748 bl_label = "Rotation - Pole toggle"
749 bl_description = "Toggles IK chain between rotation and pole target"
751 bone_name: StringProperty(default='')
752 limb_type: StringProperty(name="Limb Type")
753 controls: StringProperty(name="Controls string")
754 ik_ctrl: StringProperty(name="IK Controls string")
755 fk_ctrl: StringProperty(name="FK Controls string")
756 parent: StringProperty(name="Parent name")
757 pole: StringProperty(name="Pole name")
759 def execute(self, context):
763 bpy.ops.pose.select_all(action='DESELECT')
764 rig.pose.bones[self.bone_name].bone.select = True
766 rotPoleToggle(rig, self.limb_type, self.controls, self.ik_ctrl, self.fk_ctrl, self.parent, self.pole)
770 REGISTER_RIG_OLD_ARM
= REGISTER_OP_OLD_ARM_FKIK
+ REGISTER_OP_OLD_POLE
772 UTILITIES_RIG_OLD_ARM
= [
773 *UTILITIES_FUNC_COMMON_IKFK
,
774 *UTILITIES_FUNC_OLD_ARM_FKIK
,
775 *UTILITIES_FUNC_OLD_POLE
,
776 *UTILITIES_OP_OLD_ARM_FKIK
,
777 *UTILITIES_OP_OLD_POLE
,
780 REGISTER_RIG_OLD_LEG
= REGISTER_OP_OLD_LEG_FKIK
+ REGISTER_OP_OLD_POLE
782 UTILITIES_RIG_OLD_LEG
= [
783 *UTILITIES_FUNC_COMMON_IKFK
,
784 *UTILITIES_FUNC_OLD_LEG_FKIK
,
785 *UTILITIES_FUNC_OLD_POLE
,
786 *UTILITIES_OP_OLD_LEG_FKIK
,
787 *UTILITIES_OP_OLD_POLE
,
790 ##############################
791 ## Default set of utilities ##
792 ##############################
807 class RigUI(bpy.types.Panel):
808 bl_space_type = 'VIEW_3D'
809 bl_region_type = 'UI'
810 bl_label = "Rig Main Properties"
811 bl_idname = "VIEW3D_PT_rig_ui_" + rig_id
815 def poll(self, context):
816 if context.mode != 'POSE':
819 return (context.active_object.data.get("rig_id") == rig_id)
820 except (AttributeError, KeyError, TypeError):
823 def draw(self, context):
825 pose_bones = context.active_object.pose.bones
827 selected_bones = set(bone.name for bone in context.selected_pose_bones)
828 selected_bones.add(context.active_pose_bone.name)
829 except (AttributeError, TypeError):
832 def is_selected(names):
833 # Returns whether any of the named bones are selected.
834 if isinstance(names, list) or isinstance(names, set):
835 return not selected_bones.isdisjoint(names)
836 elif names in selected_bones:
840 num_rig_separators = [-1]
842 def emit_rig_separator():
843 if num_rig_separators[0] >= 0:
845 num_rig_separators[0] += 1
848 UI_REGISTER_BAKE_SETTINGS
= ['RigBakeSettings']
850 UI_BAKE_SETTINGS
= '''
851 class RigBakeSettings(bpy.types.Panel):
852 bl_space_type = 'VIEW_3D'
853 bl_region_type = 'UI'
854 bl_label = "Rig Bake Settings"
855 bl_idname = "VIEW3D_PT_rig_bake_settings_" + rig_id
859 def poll(self, context):
860 return RigUI.poll(context) and find_action(context.active_object) is not None
862 def draw(self, context):
863 RigifyBakeKeyframesMixin.draw_common_bake_ui(context, self.layout)
866 def layers_ui(layers
, layout
):
867 """ Turn a list of booleans + a list of names into a layer UI.
871 class RigLayers(bpy.types.Panel):
872 bl_space_type = 'VIEW_3D'
873 bl_region_type = 'UI'
874 bl_label = "Rig Layers"
875 bl_idname = "VIEW3D_PT_rig_layers_" + rig_id
879 def poll(self, context):
881 return (context.active_object.data.get("rig_id") == rig_id)
882 except (AttributeError, KeyError, TypeError):
885 def draw(self, context):
887 col = layout.column()
892 if layout
[i
][1] not in rows
:
893 rows
[layout
[i
][1]] = []
894 rows
[layout
[i
][1]] += [(layout
[i
][0], i
)]
896 keys
= list(rows
.keys())
900 code
+= "\n row = col.row()\n"
904 code
+= "\n row = col.row()\n"
906 code
+= " row.prop(context.active_object.data, 'layers', index=%s, toggle=True, text='%s')\n" % (str(l
[1]), l
[0])
910 code
+= "\n row = col.row()"
911 code
+= "\n row.separator()"
912 code
+= "\n row = col.row()"
913 code
+= "\n row.separator()\n"
914 code
+= "\n row = col.row()\n"
915 code
+= " row.prop(context.active_object.data, 'layers', index=28, toggle=True, text='Root')\n"
920 def quote_parameters(positional
, named
):
921 """Quote the given positional and named parameters as a code string."""
922 positional_list
= [ repr(v
) for v
in positional
]
923 named_list
= [ "%s=%r" % (k
, v
) for k
, v
in named
.items() ]
924 return ', '.join(positional_list
+ named_list
)
926 def indent_lines(lines
, indent
=4):
928 prefix
= ' ' * indent
929 return [ prefix
+ line
for line
in lines
]
934 class PanelLayout(object):
935 """Utility class that builds code for creating a layout."""
937 def __init__(self
, parent
, index
=0):
938 if isinstance(parent
, PanelLayout
):
940 self
.script
= parent
.script
949 self
.layout
= self
._get
_layout
_var
(index
)
953 def _get_layout_var(index
):
954 return 'layout' if index
== 0 else 'group' + str(index
)
956 def clear_empty(self
):
957 self
.is_empty
= False
960 self
.parent
.clear_empty()
965 for item
in self
.items
:
966 if isinstance(item
, PanelLayout
):
967 lines
+= item
.get_lines()
972 return self
.wrap_lines(lines
)
976 def wrap_lines(self
, lines
):
977 return self
.header
+ indent_lines(lines
, self
.indent
)
979 def add_line(self
, line
):
980 assert isinstance(line
, str)
982 self
.items
.append(line
)
987 def use_bake_settings(self
):
988 """This panel contains operators that need the common Bake settings."""
989 self
.parent
.use_bake_settings()
991 def custom_prop(self
, bone_name
, prop_name
, **params
):
992 """Add a custom property input field to the panel."""
993 param_str
= quote_parameters([ rna_idprop_quote_path(prop_name
) ], params
)
995 "%s.prop(pose_bones[%r], %s)" % (self
.layout
, bone_name
, param_str
)
998 def operator(self
, operator_name
, *, properties
=None, **params
):
999 """Add an operator call button to the panel."""
1000 name
= operator_name
.format_map(self
.script
.format_args
)
1001 param_str
= quote_parameters([ name
], params
)
1002 call_str
= "%s.operator(%s)" % (self
.layout
, param_str
)
1004 self
.add_line("props = " + call_str
)
1005 for k
, v
in properties
.items():
1006 self
.add_line("props.%s = %r" % (k
,v
))
1008 self
.add_line(call_str
)
1010 def add_nested_layout(self
, name
, params
):
1011 param_str
= quote_parameters([], params
)
1012 sub_panel
= PanelLayout(self
, self
.index
+ 1)
1013 sub_panel
.header
.append('%s = %s.%s(%s)' % (sub_panel
.layout
, self
.layout
, name
, param_str
))
1014 self
.items
.append(sub_panel
)
1017 def row(self
, **params
):
1018 """Add a nested row layout to the panel."""
1019 return self
.add_nested_layout('row', params
)
1021 def column(self
, **params
):
1022 """Add a nested column layout to the panel."""
1023 return self
.add_nested_layout('column', params
)
1025 def split(self
, **params
):
1026 """Add a split layout to the panel."""
1027 return self
.add_nested_layout('split', params
)
1030 class BoneSetPanelLayout(PanelLayout
):
1031 """Panel restricted to a certain set of bones."""
1033 def __init__(self
, rig_panel
, bones
):
1034 assert isinstance(bones
, frozenset)
1035 super().__init
__(rig_panel
)
1037 self
.show_bake_settings
= False
1039 def clear_empty(self
):
1040 self
.parent
.bones |
= self
.bones
1042 super().clear_empty()
1044 def wrap_lines(self
, lines
):
1045 if self
.bones
!= self
.parent
.bones
:
1046 header
= ["if is_selected(%r):" % (set(self
.bones
))]
1047 return header
+ indent_lines(lines
)
1051 def use_bake_settings(self
):
1052 self
.show_bake_settings
= True
1053 if not self
.script
.use_bake_settings
:
1054 self
.script
.use_bake_settings
= True
1055 self
.script
.add_utilities(SCRIPT_UTILITIES_BAKE
)
1056 self
.script
.register_classes(SCRIPT_REGISTER_BAKE
)
1059 class RigPanelLayout(PanelLayout
):
1060 """Panel owned by a certain rig."""
1062 def __init__(self
, script
, rig
):
1063 super().__init
__(script
)
1065 self
.subpanels
= OrderedDict()
1067 def wrap_lines(self
, lines
):
1068 header
= [ "if is_selected(%r):" % (set(self
.bones
)) ]
1069 prefix
= [ "emit_rig_separator()" ]
1070 return header
+ indent_lines(prefix
+ lines
)
1072 def panel_with_selected_check(self
, control_names
):
1073 selected_set
= frozenset(control_names
)
1075 if selected_set
in self
.subpanels
:
1076 return self
.subpanels
[selected_set
]
1078 panel
= BoneSetPanelLayout(self
, selected_set
)
1079 self
.subpanels
[selected_set
] = panel
1080 self
.items
.append(panel
)
1084 class ScriptGenerator(base_generate
.GeneratorPlugin
):
1085 """Generator plugin that builds the python script attached to the rig."""
1089 def __init__(self
, generator
):
1090 super().__init
__(generator
)
1092 self
.ui_scripts
= []
1093 self
.ui_imports
= UI_IMPORTS
.copy()
1094 self
.ui_utilities
= UI_UTILITIES
.copy()
1095 self
.ui_register
= UI_REGISTER
.copy()
1096 self
.ui_register_drivers
= []
1097 self
.ui_register_props
= []
1099 self
.ui_rig_panels
= OrderedDict()
1101 self
.use_bake_settings
= False
1103 # Structured panel code generation
1104 def panel_with_selected_check(self
, rig
, control_names
):
1105 """Add a panel section with restricted selection."""
1108 if rig_key
in self
.ui_rig_panels
:
1109 panel
= self
.ui_rig_panels
[rig_key
]
1111 panel
= RigPanelLayout(self
, rig
)
1112 self
.ui_rig_panels
[rig_key
] = panel
1114 return panel
.panel_with_selected_check(control_names
)
1117 def add_panel_code(self
, str_list
):
1118 """Add raw code to the panel."""
1119 self
.ui_scripts
+= str_list
1121 def add_imports(self
, str_list
):
1122 self
.ui_imports
+= str_list
1124 def add_utilities(self
, str_list
):
1125 self
.ui_utilities
+= str_list
1127 def register_classes(self
, str_list
):
1128 self
.ui_register
+= str_list
1130 def register_driver_functions(self
, str_list
):
1131 self
.ui_register_drivers
+= str_list
1133 def register_property(self
, name
, definition
):
1134 self
.ui_register_props
.append((name
, definition
))
1136 def initialize(self
):
1137 self
.format_args
= {
1138 'rig_id': self
.generator
.rig_id
,
1142 metarig
= self
.generator
.metarig
1143 rig_id
= self
.generator
.rig_id
1145 vis_layers
= self
.obj
.data
.layers
1147 # Ensure the collection of layer names exists
1148 for i
in range(1 + len(metarig
.data
.rigify_layers
), 29):
1149 metarig
.data
.rigify_layers
.add()
1151 # Create list of layer name/row pairs
1153 for l
in metarig
.data
.rigify_layers
:
1154 layer_layout
+= [(l
.name
, l
.row
)]
1156 # Generate the UI script
1157 script
= metarig
.data
.rigify_rig_ui
1162 script_name
= self
.generator
.obj
.name
+ "_ui.py"
1163 script
= bpy
.data
.texts
.new(script_name
)
1164 metarig
.data
.rigify_rig_ui
= script
1166 for s
in OrderedDict
.fromkeys(self
.ui_imports
):
1167 script
.write(s
+ "\n")
1169 script
.write(UI_BASE_UTILITIES
% rig_id
)
1171 for s
in OrderedDict
.fromkeys(self
.ui_utilities
):
1172 script
.write(s
+ "\n")
1174 script
.write(UI_SLIDERS
)
1176 for s
in self
.ui_scripts
:
1177 script
.write("\n " + s
.replace("\n", "\n ") + "\n")
1179 if len(self
.ui_scripts
) > 0:
1180 script
.write("\n num_rig_separators[0] = 0\n")
1182 for panel
in self
.ui_rig_panels
.values():
1183 lines
= panel
.get_lines()
1185 script
.write("\n ".join([''] + lines
) + "\n")
1187 if self
.use_bake_settings
:
1188 self
.ui_register
= UI_REGISTER_BAKE_SETTINGS
+ self
.ui_register
1189 script
.write(UI_BAKE_SETTINGS
)
1191 script
.write(layers_ui(vis_layers
, layer_layout
))
1193 script
.write("\ndef register():\n")
1195 ui_register
= OrderedDict
.fromkeys(self
.ui_register
)
1196 for s
in ui_register
:
1197 script
.write(" bpy.utils.register_class("+s
+")\n")
1199 ui_register_drivers
= OrderedDict
.fromkeys(self
.ui_register_drivers
)
1200 for s
in ui_register_drivers
:
1201 script
.write(" bpy.app.driver_namespace['"+s
+"'] = "+s
+"\n")
1203 ui_register_props
= OrderedDict
.fromkeys(self
.ui_register_props
)
1204 for s
in ui_register_props
:
1205 script
.write(" bpy.types.%s = %s\n " % (*s
,))
1207 script
.write("\ndef unregister():\n")
1209 for s
in ui_register_props
:
1210 script
.write(" del bpy.types.%s\n" % s
[0])
1212 for s
in ui_register
:
1213 script
.write(" bpy.utils.unregister_class("+s
+")\n")
1215 for s
in ui_register_drivers
:
1216 script
.write(" del bpy.app.driver_namespace['"+s
+"']\n")
1218 script
.write("\nregister()\n")
1219 script
.use_module
= True
1222 exec(script
.as_string(), {})
1224 # Attach the script to the rig
1225 self
.obj
['rig_ui'] = script