1 # SPDX-License-Identifier: GPL-2.0-or-later
7 from collections
import OrderedDict
9 from .utils
.animation
import SCRIPT_REGISTER_BAKE
, SCRIPT_UTILITIES_BAKE
11 from . import base_generate
13 from rna_prop_ui
import rna_idprop_quote_path
22 'from math import pi',
23 'from bpy.props import StringProperty',
24 'from mathutils import Euler, Matrix, Quaternion, Vector',
25 'from rna_prop_ui import rna_idprop_quote_path',
28 UI_BASE_UTILITIES
= '''
32 ############################
33 ## Math utility functions ##
34 ############################
36 def perpendicular_vector(v):
37 """ Returns a vector that is perpendicular to the one given.
38 The returned vector is _not_ guaranteed to be normalized.
40 # Create a vector that is not aligned with v.
41 # It doesn't matter what vector. Just any vector
42 # that's guaranteed to not be pointing in the same
44 if abs(v[0]) < abs(v[1]):
49 # Use cross prouct to generate a vector perpendicular to
50 # both tv and (more importantly) v.
54 def rotation_difference(mat1, mat2):
55 """ Returns the shortest-path rotational difference between two
58 q1 = mat1.to_quaternion()
59 q2 = mat2.to_quaternion()
60 angle = math.acos(min(1,max(-1,q1.dot(q2)))) * 2
62 angle = -angle + (2*pi)
65 def find_min_range(f,start_angle,delta=pi/8):
66 """ finds the range where lies the minimum of function f applied on bone_ik and bone_fk
70 while (angle > (start_angle - 2*pi)) and (angle < (start_angle + 2*pi)):
71 l_dist = f(angle-delta)
73 r_dist = f(angle+delta)
74 if min((l_dist,c_dist,r_dist)) == c_dist:
75 return (angle-delta,angle+delta)
79 def ternarySearch(f, left, right, absolutePrecision):
81 Find minimum of unimodal function f() within [left, right]
82 To find the maximum, revert the if/else statement or revert the comparison.
85 #left and right are the current bounds; the maximum is between them
86 if abs(right - left) < absolutePrecision:
87 return (left + right)/2
89 leftThird = left + (right - left)/3
90 rightThird = right - (right - left)/3
92 if f(leftThird) > f(rightThird):
98 UTILITIES_FUNC_COMMON_IKFK
= ['''
99 #########################################
100 ## "Visual Transform" helper functions ##
101 #########################################
103 def get_pose_matrix_in_other_space(mat, pose_bone):
104 """ Returns the transform matrix relative to pose_bone's current
105 transform space. In other words, presuming that mat is in
106 armature space, slapping the returned matrix onto pose_bone
107 should give it the armature-space transforms of mat.
109 return pose_bone.id_data.convert_space(matrix=mat, pose_bone=pose_bone, from_space='POSE', to_space='LOCAL')
112 def convert_pose_matrix_via_rest_delta(mat, from_bone, to_bone):
113 """Convert pose of one bone to another bone, preserving the rest pose difference between them."""
114 return mat @ from_bone.bone.matrix_local.inverted() @ to_bone.bone.matrix_local
117 def convert_pose_matrix_via_pose_delta(mat, from_bone, to_bone):
118 """Convert pose of one bone to another bone, preserving the current pose difference between them."""
119 return mat @ from_bone.matrix.inverted() @ to_bone.matrix
122 def get_local_pose_matrix(pose_bone):
123 """ Returns the local transform matrix of the given pose bone.
125 return get_pose_matrix_in_other_space(pose_bone.matrix, pose_bone)
128 def set_pose_translation(pose_bone, mat):
129 """ Sets the pose bone's translation to the same translation as the given matrix.
130 Matrix should be given in bone's local space.
132 pose_bone.location = mat.to_translation()
135 def set_pose_rotation(pose_bone, mat):
136 """ Sets the pose bone's rotation to the same rotation as the given matrix.
137 Matrix should be given in bone's local space.
139 q = mat.to_quaternion()
141 if pose_bone.rotation_mode == 'QUATERNION':
142 pose_bone.rotation_quaternion = q
143 elif pose_bone.rotation_mode == 'AXIS_ANGLE':
144 pose_bone.rotation_axis_angle[0] = q.angle
145 pose_bone.rotation_axis_angle[1] = q.axis[0]
146 pose_bone.rotation_axis_angle[2] = q.axis[1]
147 pose_bone.rotation_axis_angle[3] = q.axis[2]
149 pose_bone.rotation_euler = q.to_euler(pose_bone.rotation_mode)
152 def set_pose_scale(pose_bone, mat):
153 """ Sets the pose bone's scale to the same scale as the given matrix.
154 Matrix should be given in bone's local space.
156 pose_bone.scale = mat.to_scale()
159 def match_pose_translation(pose_bone, target_bone):
160 """ Matches pose_bone's visual translation to target_bone's visual
162 This function assumes you are in pose mode on the relevant armature.
164 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
165 set_pose_translation(pose_bone, mat)
168 def match_pose_rotation(pose_bone, target_bone):
169 """ Matches pose_bone's visual rotation to target_bone's visual
171 This function assumes you are in pose mode on the relevant armature.
173 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
174 set_pose_rotation(pose_bone, mat)
177 def match_pose_scale(pose_bone, target_bone):
178 """ Matches pose_bone's visual scale to target_bone's visual
180 This function assumes you are in pose mode on the relevant armature.
182 mat = get_pose_matrix_in_other_space(target_bone.matrix, pose_bone)
183 set_pose_scale(pose_bone, mat)
186 ##############################
187 ## IK/FK snapping functions ##
188 ##############################
190 def correct_rotation(view_layer, bone_ik, target_matrix, *, ctrl_ik=None):
191 """ Corrects the ik rotation in ik2fk snapping functions
194 axis = target_matrix.to_3x3().col[1].normalized()
195 ctrl_ik = ctrl_ik or bone_ik
198 # Rotate the bone and return the actual angle between bones
199 ctrl_ik.rotation_euler[1] = angle
202 return -(bone_ik.vector.normalized().dot(axis))
204 if ctrl_ik.rotation_mode in {'QUATERNION', 'AXIS_ANGLE'}:
205 ctrl_ik.rotation_mode = 'ZXY'
207 start_angle = ctrl_ik.rotation_euler[1]
209 alfarange = find_min_range(distance, start_angle)
210 alfamin = ternarySearch(distance, alfarange[0], alfarange[1], pi / 180)
212 ctrl_ik.rotation_euler[1] = alfamin
216 def correct_scale(view_layer, bone_ik, target_matrix, *, ctrl_ik=None):
217 """ Correct the scale of the base IK bone. """
218 input_scale = target_matrix.to_scale()
219 ctrl_ik = ctrl_ik or bone_ik
222 cur_scale = bone_ik.matrix.to_scale()
225 v * i / c for v, i, c in zip(bone_ik.scale, input_scale, cur_scale)
230 if all(abs((c - i)/i) < 0.01 for i, c in zip(input_scale, cur_scale)):
234 def match_pole_target(view_layer, ik_first, ik_last, pole, match_bone_matrix, length):
235 """ Places an IK chain's pole target to match ik_first's
236 transforms to match_bone. All bones should be given as pose bones.
237 You need to be in pose mode on the relevant armature object.
238 ik_first: first bone in the IK chain
239 ik_last: last bone in the IK chain
240 pole: pole target bone for the IK chain
241 match_bone: bone to match ik_first to (probably first bone in a matching FK chain)
242 length: distance pole target should be placed from the chain center
244 a = ik_first.matrix.to_translation()
245 b = ik_last.matrix.to_translation() + ik_last.vector
247 # Vector from the head of ik_first to the
251 # Get a vector perpendicular to ikv
252 pv = perpendicular_vector(ikv).normalized() * length
255 """ Set pole target's position based on a vector
256 from the arm center line.
258 # Translate pvi into armature space
259 ploc = a + (ikv/2) + pvi
261 # Set pole target to location
262 mat = get_pose_matrix_in_other_space(Matrix.Translation(ploc), pole)
263 set_pose_translation(pole, mat)
269 # Get the rotation difference between ik_first and match_bone
270 angle = rotation_difference(ik_first.matrix, match_bone_matrix)
272 # Try compensating for the rotation difference in both directions
273 pv1 = Matrix.Rotation(angle, 4, ikv) @ pv
275 ang1 = rotation_difference(ik_first.matrix, match_bone_matrix)
277 pv2 = Matrix.Rotation(-angle, 4, ikv) @ pv
279 ang2 = rotation_difference(ik_first.matrix, match_bone_matrix)
281 # Do the one with the smaller angle
289 def parse_bone_names(names_string):
290 if names_string[0] == '[' and names_string[-1] == ']':
291 return eval(names_string)
297 UTILITIES_FUNC_OLD_ARM_FKIK
= ['''
298 ######################
299 ## IK Arm functions ##
300 ######################
302 def fk2ik_arm(obj, fk, ik):
303 """ Matches the fk bones in an arm rig to the ik bones.
305 fk: list of fk bone names
306 ik: list of ik bone names
308 view_layer = bpy.context.view_layer
309 uarm = obj.pose.bones[fk[0]]
310 farm = obj.pose.bones[fk[1]]
311 hand = obj.pose.bones[fk[2]]
312 uarmi = obj.pose.bones[ik[0]]
313 farmi = obj.pose.bones[ik[1]]
314 handi = obj.pose.bones[ik[2]]
316 if 'auto_stretch' in handi.keys():
317 # This is kept for compatibility with legacy rigify Human
319 if handi['auto_stretch'] == 0.0:
320 uarm['stretch_length'] = handi['stretch_length']
322 diff = (uarmi.vector.length + farmi.vector.length) / (uarm.vector.length + farm.vector.length)
323 uarm['stretch_length'] *= diff
326 match_pose_rotation(uarm, uarmi)
327 match_pose_scale(uarm, uarmi)
331 match_pose_rotation(farm, farmi)
332 match_pose_scale(farm, farmi)
336 match_pose_rotation(hand, handi)
337 match_pose_scale(hand, handi)
341 match_pose_translation(uarm, uarmi)
342 match_pose_rotation(uarm, uarmi)
343 match_pose_scale(uarm, uarmi)
347 #match_pose_translation(hand, handi)
348 match_pose_rotation(farm, farmi)
349 match_pose_scale(farm, farmi)
353 match_pose_translation(hand, handi)
354 match_pose_rotation(hand, handi)
355 match_pose_scale(hand, handi)
359 def ik2fk_arm(obj, fk, ik):
360 """ Matches the ik bones in an arm rig to the fk bones.
362 fk: list of fk bone names
363 ik: list of ik bone names
365 view_layer = bpy.context.view_layer
366 uarm = obj.pose.bones[fk[0]]
367 farm = obj.pose.bones[fk[1]]
368 hand = obj.pose.bones[fk[2]]
369 uarmi = obj.pose.bones[ik[0]]
370 farmi = obj.pose.bones[ik[1]]
371 handi = obj.pose.bones[ik[2]]
373 main_parent = obj.pose.bones[ik[4]]
375 if ik[3] != "" and main_parent['pole_vector']:
376 pole = obj.pose.bones[ik[3]]
383 # handi['stretch_length'] = uarm['stretch_length']
386 match_pose_translation(handi, hand)
387 match_pose_rotation(handi, hand)
388 match_pose_scale(handi, hand)
391 # Pole target position
392 match_pole_target(view_layer, uarmi, farmi, pole, uarm.matrix, (uarmi.length + farmi.length))
396 match_pose_translation(handi, hand)
397 match_pose_rotation(handi, hand)
398 match_pose_scale(handi, hand)
402 match_pose_translation(uarmi, uarm)
403 #match_pose_rotation(uarmi, uarm)
404 set_pose_rotation(uarmi, Matrix())
405 match_pose_scale(uarmi, uarm)
408 # Rotation Correction
409 correct_rotation(view_layer, uarmi, uarm.matrix)
411 correct_scale(view_layer, uarmi, uarm.matrix)
414 UTILITIES_FUNC_OLD_LEG_FKIK
= ['''
415 ######################
416 ## IK Leg functions ##
417 ######################
419 def fk2ik_leg(obj, fk, ik):
420 """ Matches the fk bones in a leg rig to the ik bones.
422 fk: list of fk bone names
423 ik: list of ik bone names
425 view_layer = bpy.context.view_layer
426 thigh = obj.pose.bones[fk[0]]
427 shin = obj.pose.bones[fk[1]]
428 foot = obj.pose.bones[fk[2]]
429 mfoot = obj.pose.bones[fk[3]]
430 thighi = obj.pose.bones[ik[0]]
431 shini = obj.pose.bones[ik[1]]
432 footi = obj.pose.bones[ik[2]]
433 mfooti = obj.pose.bones[ik[3]]
435 if 'auto_stretch' in footi.keys():
436 # This is kept for compatibility with legacy rigify Human
438 if footi['auto_stretch'] == 0.0:
439 thigh['stretch_length'] = footi['stretch_length']
441 diff = (thighi.vector.length + shini.vector.length) / (thigh.vector.length + shin.vector.length)
442 thigh['stretch_length'] *= diff
445 match_pose_rotation(thigh, thighi)
446 match_pose_scale(thigh, thighi)
450 match_pose_rotation(shin, shini)
451 match_pose_scale(shin, shini)
455 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot)
456 footmat = convert_pose_matrix_via_rest_delta(footmat, mfoot, foot)
457 set_pose_rotation(foot, footmat)
458 set_pose_scale(foot, footmat)
463 match_pose_translation(thigh, thighi)
464 match_pose_rotation(thigh, thighi)
465 match_pose_scale(thigh, thighi)
469 match_pose_rotation(shin, shini)
470 match_pose_scale(shin, shini)
474 footmat = get_pose_matrix_in_other_space(mfooti.matrix, foot)
475 footmat = convert_pose_matrix_via_rest_delta(footmat, mfoot, foot)
476 set_pose_rotation(foot, footmat)
477 set_pose_scale(foot, footmat)
481 def ik2fk_leg(obj, fk, ik):
482 """ Matches the ik bones in a leg rig to the fk bones.
484 fk: list of fk bone names
485 ik: list of ik bone names
487 view_layer = bpy.context.view_layer
488 thigh = obj.pose.bones[fk[0]]
489 shin = obj.pose.bones[fk[1]]
490 mfoot = obj.pose.bones[fk[2]]
492 foot = obj.pose.bones[fk[3]]
495 thighi = obj.pose.bones[ik[0]]
496 shini = obj.pose.bones[ik[1]]
497 footi = obj.pose.bones[ik[2]]
498 footroll = obj.pose.bones[ik[3]]
500 main_parent = obj.pose.bones[ik[6]]
502 if ik[4] != "" and main_parent['pole_vector']:
503 pole = obj.pose.bones[ik[4]]
506 mfooti = obj.pose.bones[ik[5]]
508 if (not pole) and (foot):
511 set_pose_rotation(footroll, Matrix())
515 footmat = get_pose_matrix_in_other_space(foot.matrix, footi)
516 footmat = convert_pose_matrix_via_rest_delta(footmat, mfooti, footi)
517 set_pose_translation(footi, footmat)
518 set_pose_rotation(footi, footmat)
519 set_pose_scale(footi, footmat)
523 match_pose_translation(thighi, thigh)
524 #match_pose_rotation(thighi, thigh)
525 set_pose_rotation(thighi, Matrix())
526 match_pose_scale(thighi, thigh)
529 # Rotation Correction
530 correct_rotation(view_layer, thighi, thigh.matrix)
534 if 'stretch_length' in footi.keys() and 'stretch_length' in thigh.keys():
535 # Kept for compat with legacy rigify Human
536 footi['stretch_length'] = thigh['stretch_length']
539 set_pose_rotation(footroll, Matrix())
543 footmat = get_pose_matrix_in_other_space(mfoot.matrix, footi)
544 footmat = convert_pose_matrix_via_rest_delta(footmat, mfooti, footi)
545 set_pose_translation(footi, footmat)
546 set_pose_rotation(footi, footmat)
547 set_pose_scale(footi, footmat)
550 # Pole target position
551 match_pole_target(view_layer, thighi, shini, pole, thigh.matrix, (thighi.length + shini.length))
553 correct_scale(view_layer, thighi, thigh.matrix)
556 UTILITIES_FUNC_OLD_POLE
= ['''
557 ################################
558 ## IK Rotation-Pole functions ##
559 ################################
561 def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole):
563 rig_id = rig.data['rig_id']
564 leg_fk2ik = eval('bpy.ops.pose.rigify_leg_fk2ik_' + rig_id)
565 arm_fk2ik = eval('bpy.ops.pose.rigify_arm_fk2ik_' + rig_id)
566 leg_ik2fk = eval('bpy.ops.pose.rigify_leg_ik2fk_' + rig_id)
567 arm_ik2fk = eval('bpy.ops.pose.rigify_arm_ik2fk_' + rig_id)
569 controls = parse_bone_names(controls)
570 ik_ctrl = parse_bone_names(ik_ctrl)
571 fk_ctrl = parse_bone_names(fk_ctrl)
572 parent = parse_bone_names(parent)
573 pole = parse_bone_names(pole)
575 pbones = bpy.context.selected_pose_bones
576 bpy.ops.pose.select_all(action='DESELECT')
580 new_pole_vector_value = not rig.pose.bones[parent]['pole_vector']
582 if b.name in controls or b.name in ik_ctrl:
583 if limb_type == 'arm':
586 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
587 rig.pose.bones[controls[4]].bone.select = not new_pole_vector_value
588 rig.pose.bones[parent].bone.select = not new_pole_vector_value
589 rig.pose.bones[pole].bone.select = new_pole_vector_value
591 kwargs1 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
592 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1],
593 'hand_ik': controls[4]}
594 kwargs2 = {'uarm_fk': controls[1], 'farm_fk': controls[2], 'hand_fk': controls[3],
595 'uarm_ik': controls[0], 'farm_ik': ik_ctrl[1], 'hand_ik': controls[4],
596 'pole': pole, 'main_parent': parent}
600 rig.pose.bones[controls[0]].bone.select = not new_pole_vector_value
601 rig.pose.bones[controls[6]].bone.select = not new_pole_vector_value
602 rig.pose.bones[controls[5]].bone.select = not new_pole_vector_value
603 rig.pose.bones[parent].bone.select = not new_pole_vector_value
604 rig.pose.bones[pole].bone.select = new_pole_vector_value
606 kwargs1 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
607 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
608 'foot_ik': ik_ctrl[2], 'mfoot_ik': ik_ctrl[2]}
609 kwargs2 = {'thigh_fk': controls[1], 'shin_fk': controls[2], 'foot_fk': controls[3],
610 'mfoot_fk': controls[7], 'thigh_ik': controls[0], 'shin_ik': ik_ctrl[1],
611 'foot_ik': controls[6], 'pole': pole, 'footroll': controls[5], 'mfoot_ik': ik_ctrl[2],
612 'main_parent': parent}
615 rig.pose.bones[parent]['pole_vector'] = new_pole_vector_value
618 bpy.ops.pose.select_all(action='DESELECT')
621 REGISTER_OP_OLD_ARM_FKIK
= ['Rigify_Arm_FK2IK', 'Rigify_Arm_IK2FK']
623 UTILITIES_OP_OLD_ARM_FKIK
= ['''
624 ##################################
625 ## IK/FK Arm snapping operators ##
626 ##################################
628 class Rigify_Arm_FK2IK(bpy.types.Operator):
629 """ Snaps an FK arm to an IK arm.
631 bl_idname = "pose.rigify_arm_fk2ik_" + rig_id
632 bl_label = "Rigify Snap FK arm to IK"
633 bl_options = {'UNDO', 'INTERNAL'}
635 uarm_fk: StringProperty(name="Upper Arm FK Name")
636 farm_fk: StringProperty(name="Forerm FK Name")
637 hand_fk: StringProperty(name="Hand FK Name")
639 uarm_ik: StringProperty(name="Upper Arm IK Name")
640 farm_ik: StringProperty(name="Forearm IK Name")
641 hand_ik: StringProperty(name="Hand IK Name")
644 def poll(cls, context):
645 return (context.active_object != None and context.mode == 'POSE')
647 def execute(self, context):
648 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])
652 class Rigify_Arm_IK2FK(bpy.types.Operator):
653 """ Snaps an IK arm to an FK arm.
655 bl_idname = "pose.rigify_arm_ik2fk_" + rig_id
656 bl_label = "Rigify Snap IK arm to FK"
657 bl_options = {'UNDO', 'INTERNAL'}
659 uarm_fk: StringProperty(name="Upper Arm FK Name")
660 farm_fk: StringProperty(name="Forerm FK Name")
661 hand_fk: StringProperty(name="Hand FK Name")
663 uarm_ik: StringProperty(name="Upper Arm IK Name")
664 farm_ik: StringProperty(name="Forearm IK Name")
665 hand_ik: StringProperty(name="Hand IK Name")
666 pole : StringProperty(name="Pole IK Name")
668 main_parent: StringProperty(name="Main Parent", default="")
671 def poll(cls, context):
672 return (context.active_object != None and context.mode == 'POSE')
674 def execute(self, context):
675 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])
679 REGISTER_OP_OLD_LEG_FKIK
= ['Rigify_Leg_FK2IK', 'Rigify_Leg_IK2FK']
681 UTILITIES_OP_OLD_LEG_FKIK
= ['''
682 ##################################
683 ## IK/FK Leg snapping operators ##
684 ##################################
686 class Rigify_Leg_FK2IK(bpy.types.Operator):
687 """ Snaps an FK leg to an IK leg.
689 bl_idname = "pose.rigify_leg_fk2ik_" + rig_id
690 bl_label = "Rigify Snap FK leg to IK"
691 bl_options = {'UNDO', 'INTERNAL'}
693 thigh_fk: StringProperty(name="Thigh FK Name")
694 shin_fk: StringProperty(name="Shin FK Name")
695 foot_fk: StringProperty(name="Foot FK Name")
696 mfoot_fk: StringProperty(name="MFoot FK Name")
698 thigh_ik: StringProperty(name="Thigh IK Name")
699 shin_ik: StringProperty(name="Shin IK Name")
700 foot_ik: StringProperty(name="Foot IK Name")
701 mfoot_ik: StringProperty(name="MFoot IK Name")
704 def poll(cls, context):
705 return (context.active_object != None and context.mode == 'POSE')
707 def execute(self, context):
708 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])
712 class Rigify_Leg_IK2FK(bpy.types.Operator):
713 """ Snaps an IK leg to an FK leg.
715 bl_idname = "pose.rigify_leg_ik2fk_" + rig_id
716 bl_label = "Rigify Snap IK leg to FK"
717 bl_options = {'UNDO', 'INTERNAL'}
719 thigh_fk: StringProperty(name="Thigh FK Name")
720 shin_fk: StringProperty(name="Shin FK Name")
721 mfoot_fk: StringProperty(name="MFoot FK Name")
722 foot_fk: StringProperty(name="Foot FK Name", default="")
723 thigh_ik: StringProperty(name="Thigh IK Name")
724 shin_ik: StringProperty(name="Shin IK Name")
725 foot_ik: StringProperty(name="Foot IK Name")
726 footroll: StringProperty(name="Foot Roll Name")
727 pole: StringProperty(name="Pole IK Name")
728 mfoot_ik: StringProperty(name="MFoot IK Name")
730 main_parent: StringProperty(name="Main Parent", default="")
733 def poll(cls, context):
734 return (context.active_object != None and context.mode == 'POSE')
736 def execute(self, context):
737 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])
741 REGISTER_OP_OLD_POLE
= ['Rigify_Rot2PoleSwitch']
743 UTILITIES_OP_OLD_POLE
= ['''
744 ###########################
745 ## IK Rotation Pole Snap ##
746 ###########################
748 class Rigify_Rot2PoleSwitch(bpy.types.Operator):
749 bl_idname = "pose.rigify_rot2pole_" + rig_id
750 bl_label = "Rotation - Pole toggle"
751 bl_description = "Toggles IK chain between rotation and pole target"
753 bone_name: StringProperty(default='')
754 limb_type: StringProperty(name="Limb Type")
755 controls: StringProperty(name="Controls string")
756 ik_ctrl: StringProperty(name="IK Controls string")
757 fk_ctrl: StringProperty(name="FK Controls string")
758 parent: StringProperty(name="Parent name")
759 pole: StringProperty(name="Pole name")
761 def execute(self, context):
765 bpy.ops.pose.select_all(action='DESELECT')
766 rig.pose.bones[self.bone_name].bone.select = True
768 rotPoleToggle(rig, self.limb_type, self.controls, self.ik_ctrl, self.fk_ctrl, self.parent, self.pole)
772 REGISTER_RIG_OLD_ARM
= REGISTER_OP_OLD_ARM_FKIK
+ REGISTER_OP_OLD_POLE
774 UTILITIES_RIG_OLD_ARM
= [
775 *UTILITIES_FUNC_COMMON_IKFK
,
776 *UTILITIES_FUNC_OLD_ARM_FKIK
,
777 *UTILITIES_FUNC_OLD_POLE
,
778 *UTILITIES_OP_OLD_ARM_FKIK
,
779 *UTILITIES_OP_OLD_POLE
,
782 REGISTER_RIG_OLD_LEG
= REGISTER_OP_OLD_LEG_FKIK
+ REGISTER_OP_OLD_POLE
784 UTILITIES_RIG_OLD_LEG
= [
785 *UTILITIES_FUNC_COMMON_IKFK
,
786 *UTILITIES_FUNC_OLD_LEG_FKIK
,
787 *UTILITIES_FUNC_OLD_POLE
,
788 *UTILITIES_OP_OLD_LEG_FKIK
,
789 *UTILITIES_OP_OLD_POLE
,
792 ##############################
793 ## Default set of utilities ##
794 ##############################
809 class RigUI(bpy.types.Panel):
810 bl_space_type = 'VIEW_3D'
811 bl_region_type = 'UI'
812 bl_label = "Rig Main Properties"
813 bl_idname = "VIEW3D_PT_rig_ui_" + rig_id
817 def poll(self, context):
818 if context.mode != 'POSE':
821 return (context.active_object.data.get("rig_id") == rig_id)
822 except (AttributeError, KeyError, TypeError):
825 def draw(self, context):
827 pose_bones = context.active_object.pose.bones
829 selected_bones = set(bone.name for bone in context.selected_pose_bones)
830 selected_bones.add(context.active_pose_bone.name)
831 except (AttributeError, TypeError):
834 def is_selected(names):
835 # Returns whether any of the named bones are selected.
836 if isinstance(names, list) or isinstance(names, set):
837 return not selected_bones.isdisjoint(names)
838 elif names in selected_bones:
842 num_rig_separators = [-1]
844 def emit_rig_separator():
845 if num_rig_separators[0] >= 0:
847 num_rig_separators[0] += 1
850 UI_REGISTER_BAKE_SETTINGS
= ['RigBakeSettings']
852 UI_BAKE_SETTINGS
= '''
853 class RigBakeSettings(bpy.types.Panel):
854 bl_space_type = 'VIEW_3D'
855 bl_region_type = 'UI'
856 bl_label = "Rig Bake Settings"
857 bl_idname = "VIEW3D_PT_rig_bake_settings_" + rig_id
861 def poll(self, context):
862 return RigUI.poll(context) and find_action(context.active_object) is not None
864 def draw(self, context):
865 RigifyBakeKeyframesMixin.draw_common_bake_ui(context, self.layout)
868 def layers_ui(layers
, layout
):
869 """ Turn a list of booleans + a list of names into a layer UI.
873 class RigLayers(bpy.types.Panel):
874 bl_space_type = 'VIEW_3D'
875 bl_region_type = 'UI'
876 bl_label = "Rig Layers"
877 bl_idname = "VIEW3D_PT_rig_layers_" + rig_id
881 def poll(self, context):
883 return (context.active_object.data.get("rig_id") == rig_id)
884 except (AttributeError, KeyError, TypeError):
887 def draw(self, context):
889 col = layout.column()
894 if layout
[i
][1] not in rows
:
895 rows
[layout
[i
][1]] = []
896 rows
[layout
[i
][1]] += [(layout
[i
][0], i
)]
898 keys
= list(rows
.keys())
902 code
+= "\n row = col.row()\n"
906 code
+= "\n row = col.row()\n"
908 code
+= " row.prop(context.active_object.data, 'layers', index=%s, toggle=True, text='%s')\n" % (str(l
[1]), l
[0])
912 code
+= "\n row = col.row()"
913 code
+= "\n row.separator()"
914 code
+= "\n row = col.row()"
915 code
+= "\n row.separator()\n"
916 code
+= "\n row = col.row()\n"
917 code
+= " row.prop(context.active_object.data, 'layers', index=28, toggle=True, text='Root')\n"
922 def quote_parameters(positional
, named
):
923 """Quote the given positional and named parameters as a code string."""
924 positional_list
= [ repr(v
) for v
in positional
]
925 named_list
= [ "%s=%r" % (k
, v
) for k
, v
in named
.items() ]
926 return ', '.join(positional_list
+ named_list
)
928 def indent_lines(lines
, indent
=4):
930 prefix
= ' ' * indent
931 return [ prefix
+ line
for line
in lines
]
936 class PanelLayout(object):
937 """Utility class that builds code for creating a layout."""
939 def __init__(self
, parent
, index
=0):
940 if isinstance(parent
, PanelLayout
):
942 self
.script
= parent
.script
951 self
.layout
= self
._get
_layout
_var
(index
)
955 def _get_layout_var(index
):
956 return 'layout' if index
== 0 else 'group' + str(index
)
958 def clear_empty(self
):
959 self
.is_empty
= False
962 self
.parent
.clear_empty()
967 for item
in self
.items
:
968 if isinstance(item
, PanelLayout
):
969 lines
+= item
.get_lines()
974 return self
.wrap_lines(lines
)
978 def wrap_lines(self
, lines
):
979 return self
.header
+ indent_lines(lines
, self
.indent
)
981 def add_line(self
, line
):
982 assert isinstance(line
, str)
984 self
.items
.append(line
)
989 def use_bake_settings(self
):
990 """This panel contains operators that need the common Bake settings."""
991 self
.parent
.use_bake_settings()
993 def custom_prop(self
, bone_name
, prop_name
, **params
):
994 """Add a custom property input field to the panel."""
995 param_str
= quote_parameters([ rna_idprop_quote_path(prop_name
) ], params
)
997 "%s.prop(pose_bones[%r], %s)" % (self
.layout
, bone_name
, param_str
)
1000 def operator(self
, operator_name
, *, properties
=None, **params
):
1001 """Add an operator call button to the panel."""
1002 name
= operator_name
.format_map(self
.script
.format_args
)
1003 param_str
= quote_parameters([ name
], params
)
1004 call_str
= "%s.operator(%s)" % (self
.layout
, param_str
)
1006 self
.add_line("props = " + call_str
)
1007 for k
, v
in properties
.items():
1008 self
.add_line("props.%s = %r" % (k
,v
))
1010 self
.add_line(call_str
)
1012 def add_nested_layout(self
, name
, params
):
1013 param_str
= quote_parameters([], params
)
1014 sub_panel
= PanelLayout(self
, self
.index
+ 1)
1015 sub_panel
.header
.append('%s = %s.%s(%s)' % (sub_panel
.layout
, self
.layout
, name
, param_str
))
1016 self
.items
.append(sub_panel
)
1019 def row(self
, **params
):
1020 """Add a nested row layout to the panel."""
1021 return self
.add_nested_layout('row', params
)
1023 def column(self
, **params
):
1024 """Add a nested column layout to the panel."""
1025 return self
.add_nested_layout('column', params
)
1027 def split(self
, **params
):
1028 """Add a split layout to the panel."""
1029 return self
.add_nested_layout('split', params
)
1032 class BoneSetPanelLayout(PanelLayout
):
1033 """Panel restricted to a certain set of bones."""
1035 def __init__(self
, rig_panel
, bones
):
1036 assert isinstance(bones
, frozenset)
1037 super().__init
__(rig_panel
)
1039 self
.show_bake_settings
= False
1041 def clear_empty(self
):
1042 self
.parent
.bones |
= self
.bones
1044 super().clear_empty()
1046 def wrap_lines(self
, lines
):
1047 if self
.bones
!= self
.parent
.bones
:
1048 header
= ["if is_selected(%r):" % (set(self
.bones
))]
1049 return header
+ indent_lines(lines
)
1053 def use_bake_settings(self
):
1054 self
.show_bake_settings
= True
1055 if not self
.script
.use_bake_settings
:
1056 self
.script
.use_bake_settings
= True
1057 self
.script
.add_utilities(SCRIPT_UTILITIES_BAKE
)
1058 self
.script
.register_classes(SCRIPT_REGISTER_BAKE
)
1061 class RigPanelLayout(PanelLayout
):
1062 """Panel owned by a certain rig."""
1064 def __init__(self
, script
, rig
):
1065 super().__init
__(script
)
1067 self
.subpanels
= OrderedDict()
1069 def wrap_lines(self
, lines
):
1070 header
= [ "if is_selected(%r):" % (set(self
.bones
)) ]
1071 prefix
= [ "emit_rig_separator()" ]
1072 return header
+ indent_lines(prefix
+ lines
)
1074 def panel_with_selected_check(self
, control_names
):
1075 selected_set
= frozenset(control_names
)
1077 if selected_set
in self
.subpanels
:
1078 return self
.subpanels
[selected_set
]
1080 panel
= BoneSetPanelLayout(self
, selected_set
)
1081 self
.subpanels
[selected_set
] = panel
1082 self
.items
.append(panel
)
1086 class ScriptGenerator(base_generate
.GeneratorPlugin
):
1087 """Generator plugin that builds the python script attached to the rig."""
1091 def __init__(self
, generator
):
1092 super().__init
__(generator
)
1094 self
.ui_scripts
= []
1095 self
.ui_imports
= UI_IMPORTS
.copy()
1096 self
.ui_utilities
= UI_UTILITIES
.copy()
1097 self
.ui_register
= UI_REGISTER
.copy()
1098 self
.ui_register_drivers
= []
1099 self
.ui_register_props
= []
1101 self
.ui_rig_panels
= OrderedDict()
1103 self
.use_bake_settings
= False
1105 # Structured panel code generation
1106 def panel_with_selected_check(self
, rig
, control_names
):
1107 """Add a panel section with restricted selection."""
1110 if rig_key
in self
.ui_rig_panels
:
1111 panel
= self
.ui_rig_panels
[rig_key
]
1113 panel
= RigPanelLayout(self
, rig
)
1114 self
.ui_rig_panels
[rig_key
] = panel
1116 return panel
.panel_with_selected_check(control_names
)
1119 def add_panel_code(self
, str_list
):
1120 """Add raw code to the panel."""
1121 self
.ui_scripts
+= str_list
1123 def add_imports(self
, str_list
):
1124 self
.ui_imports
+= str_list
1126 def add_utilities(self
, str_list
):
1127 self
.ui_utilities
+= str_list
1129 def register_classes(self
, str_list
):
1130 self
.ui_register
+= str_list
1132 def register_driver_functions(self
, str_list
):
1133 self
.ui_register_drivers
+= str_list
1135 def register_property(self
, name
, definition
):
1136 self
.ui_register_props
.append((name
, definition
))
1138 def initialize(self
):
1139 self
.format_args
= {
1140 'rig_id': self
.generator
.rig_id
,
1144 metarig
= self
.generator
.metarig
1145 rig_id
= self
.generator
.rig_id
1147 vis_layers
= self
.obj
.data
.layers
1149 # Ensure the collection of layer names exists
1150 for i
in range(1 + len(metarig
.data
.rigify_layers
), 29):
1151 metarig
.data
.rigify_layers
.add()
1153 # Create list of layer name/row pairs
1155 for l
in metarig
.data
.rigify_layers
:
1156 layer_layout
+= [(l
.name
, l
.row
)]
1158 # Generate the UI script
1159 script
= metarig
.data
.rigify_rig_ui
1164 script
= bpy
.data
.texts
.new("rig_ui.py")
1165 metarig
.data
.rigify_rig_ui
= script
1167 for s
in OrderedDict
.fromkeys(self
.ui_imports
):
1168 script
.write(s
+ "\n")
1170 script
.write(UI_BASE_UTILITIES
% rig_id
)
1172 for s
in OrderedDict
.fromkeys(self
.ui_utilities
):
1173 script
.write(s
+ "\n")
1175 script
.write(UI_SLIDERS
)
1177 for s
in self
.ui_scripts
:
1178 script
.write("\n " + s
.replace("\n", "\n ") + "\n")
1180 if len(self
.ui_scripts
) > 0:
1181 script
.write("\n num_rig_separators[0] = 0\n")
1183 for panel
in self
.ui_rig_panels
.values():
1184 lines
= panel
.get_lines()
1186 script
.write("\n ".join([''] + lines
) + "\n")
1188 if self
.use_bake_settings
:
1189 self
.ui_register
= UI_REGISTER_BAKE_SETTINGS
+ self
.ui_register
1190 script
.write(UI_BAKE_SETTINGS
)
1192 script
.write(layers_ui(vis_layers
, layer_layout
))
1194 script
.write("\ndef register():\n")
1196 ui_register
= OrderedDict
.fromkeys(self
.ui_register
)
1197 for s
in ui_register
:
1198 script
.write(" bpy.utils.register_class("+s
+")\n")
1200 ui_register_drivers
= OrderedDict
.fromkeys(self
.ui_register_drivers
)
1201 for s
in ui_register_drivers
:
1202 script
.write(" bpy.app.driver_namespace['"+s
+"'] = "+s
+"\n")
1204 ui_register_props
= OrderedDict
.fromkeys(self
.ui_register_props
)
1205 for s
in ui_register_props
:
1206 script
.write(" bpy.types.%s = %s\n " % (*s
,))
1208 script
.write("\ndef unregister():\n")
1210 for s
in ui_register_props
:
1211 script
.write(" del bpy.types.%s\n" % s
[0])
1213 for s
in ui_register
:
1214 script
.write(" bpy.utils.unregister_class("+s
+")\n")
1216 for s
in ui_register_drivers
:
1217 script
.write(" del bpy.app.driver_namespace['"+s
+"']\n")
1219 script
.write("\nregister()\n")
1220 script
.use_module
= True
1223 exec(script
.as_string(), {})
1225 # Attach the script to the rig
1226 self
.obj
['rig_ui'] = script