Skip to content

Commit 513e208

Browse files
committed
Implement an IK control in limbs.super_finger with snapping.
BlenRig has this feature, so Rigify was behind in this aspect. Since this is a feature for specific situations, which applies to 10 fingers if enabled, it is implemented in a lightweight way using the minimal number of new bones; specifically, IK is applied to the result of regular FK transformation, so existing FK bones can be used to tweak the shape. Another effect of that is that when IK is off, FK->IK snap simply normalizes the finger by absorbing as much of rotation as possible into the master control. This makes the operation useful even if IK is not used or even generated. For IK follow parent, it makes zero sense to follow the palm bones, so the rig has an option for explicitly specifying the parent bone. Natural choices are the hand, or a separate utility bone for something held in the hand, if the metarig has it.
1 parent add38f3 commit 513e208

File tree

2 files changed

+360
-10
lines changed

2 files changed

+360
-10
lines changed

rig_ui_template.py

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,112 @@ def ik2fk_leg(obj, fk, ik):
520520
match_pole_target(thighi, shini, pole, thigh, (thighi.length + shini.length))
521521
522522
523+
def normalize_finger(obj, fk_master, fk_master_drv, fk_controls, axis):
524+
""" Absorbs rotation from tweaks to master control bone.
525+
obj: armature object
526+
fk_master: master FK control
527+
fk_master_drv: proxy bones of the master
528+
fk_controls: list of tweak FK controls
529+
axis: main transform axis
530+
"""
531+
532+
fk_master_drv = parse_bone_names(fk_master_drv)
533+
fk_controls = parse_bone_names(fk_controls)
534+
535+
axis_types = {
536+
"automatic": [0, 1, 'XYZ'],
537+
"X": [0, 1, 'XYZ'],
538+
"-X": [0, -1, 'XYZ'],
539+
"Y": [1, 1, 'YXZ'],
540+
"-Y": [1, -1, 'YXZ'],
541+
"Z": [2, 1, 'ZXY'],
542+
"-Z": [2, -1, 'ZXY'],
543+
}
544+
545+
axis_id, axis_sign, axis_order = axis_types[axis]
546+
547+
# Scan controls to collect data
548+
ctl_matrix_list = [obj.pose.bones[name].matrix.copy() for name in fk_controls]
549+
550+
rotations = []
551+
552+
for i, (drv_name, ctl_name, ctl_mat) in enumerate(zip(fk_master_drv, fk_controls, ctl_matrix_list)):
553+
drv = obj.pose.bones[drv_name]
554+
ctl = obj.pose.bones[ctl_name]
555+
556+
drv_mat = get_pose_matrix_in_other_space(ctl_mat, drv)
557+
558+
if i == 0:
559+
# Absorb first tweak control rotation into the master control
560+
master = obj.pose.bones[fk_master]
561+
562+
master_mat = get_pose_matrix_in_other_space(ctl_mat, master)
563+
564+
set_pose_translation(master, master_mat)
565+
set_pose_rotation(master, drv_mat)
566+
set_pose_translation(ctl, Matrix())
567+
set_pose_rotation(ctl, Matrix())
568+
569+
bpy.ops.object.mode_set(mode='OBJECT')
570+
bpy.ops.object.mode_set(mode='POSE')
571+
else:
572+
# Collect single axis rotations
573+
rotations.append(drv_mat.to_quaternion().to_euler(axis_order)[axis_id])
574+
575+
# Apply average rotation to the master control scale
576+
avg_rotation = sum(rotations) / len(rotations)
577+
obj.pose.bones[fk_master].scale.y = 1 - axis_sign * avg_rotation / pi;
578+
579+
# Correct tweak control rotations
580+
for i, (ctl_name, ctl_mat) in enumerate(zip(fk_controls, ctl_matrix_list)):
581+
if i > 0:
582+
ctl = obj.pose.bones[ctl_name]
583+
mat = get_pose_matrix_in_other_space(ctl_mat, ctl)
584+
set_pose_translation(ctl, mat)
585+
set_pose_rotation(ctl, mat)
586+
587+
bpy.ops.object.mode_set(mode='OBJECT')
588+
bpy.ops.object.mode_set(mode='POSE')
589+
590+
591+
def fk2ik_finger(obj, fk_controls, fk_chain, ik_chain):
592+
""" Matches the fk bones in a finger rig to the ik bones.
593+
obj: armature object
594+
fk_controls: list of tweak FK controls
595+
fk_chain: list of bones with final FK shape
596+
ik_chain: list of bones with final IK shape
597+
"""
598+
599+
fk_controls = parse_bone_names(fk_controls)
600+
fk_chain = parse_bone_names(fk_chain)
601+
ik_chain = parse_bone_names(ik_chain)
602+
603+
ik_matrix_list = [obj.pose.bones[name].matrix.copy() for name in ik_chain]
604+
605+
for i, (ctl_name, fk_name, ik_matrix) in enumerate(zip(fk_controls, fk_chain, ik_matrix_list)):
606+
fk = obj.pose.bones[fk_name]
607+
ctl = obj.pose.bones[ctl_name]
608+
609+
newmat = ik_matrix * fk.matrix.inverted() * ctl.matrix
610+
newmat_local = get_pose_matrix_in_other_space(newmat, ctl)
611+
set_pose_rotation(ctl, newmat_local)
612+
613+
bpy.ops.object.mode_set(mode='OBJECT')
614+
bpy.ops.object.mode_set(mode='POSE')
615+
616+
617+
def ik2fk_finger(obj, fk, ik):
618+
""" Matches the ik bone in a finger rig to the fk bones.
619+
obj: armature object
620+
fk: FK fingertip control
621+
ik: IK control
622+
"""
623+
fk_bone = obj.pose.bones[fk]
624+
ik_bone = obj.pose.bones[ik]
625+
626+
match_pose_translation(ik_bone, fk_bone)
627+
628+
523629
################################
524630
## IK Rotation-Pole functions ##
525631
################################
@@ -718,6 +824,59 @@ def execute(self, context):
718824
context.user_preferences.edit.use_global_undo = use_global_undo
719825
return {'FINISHED'}
720826
827+
828+
class Rigify_Finger_FK2IK(bpy.types.Operator):
829+
"""Snaps an FK finger to the actual shape, and normalizes the master control"""
830+
bl_idname = "pose.rigify_finger_fk2ik_" + rig_id
831+
bl_label = "Rigify Snap FK finger to IK"
832+
bl_options = {'UNDO'}
833+
834+
fk_master = bpy.props.StringProperty(name="FK Master Name")
835+
fk_master_drv = bpy.props.StringProperty(name="FK Master Proxy Name")
836+
fk_controls = bpy.props.StringProperty(name="FK Control Names")
837+
fk_chain = bpy.props.StringProperty(name="FK Shape Bone Names")
838+
ik_chain = bpy.props.StringProperty(name="IK Shape Bone Names")
839+
axis = bpy.props.StringProperty(name="Main Rotation Axis")
840+
841+
@classmethod
842+
def poll(cls, context):
843+
return (context.active_object != None and context.mode == 'POSE')
844+
845+
def execute(self, context):
846+
use_global_undo = context.user_preferences.edit.use_global_undo
847+
context.user_preferences.edit.use_global_undo = False
848+
try:
849+
if self.ik_chain != '':
850+
fk2ik_finger(context.active_object, self.fk_controls, self.fk_chain, self.ik_chain)
851+
if self.fk_master != '':
852+
normalize_finger(context.active_object, self.fk_master, self.fk_master_drv, self.fk_controls, self.axis)
853+
finally:
854+
context.user_preferences.edit.use_global_undo = use_global_undo
855+
return {'FINISHED'}
856+
857+
858+
class Rigify_Finger_IK2FK(bpy.types.Operator):
859+
"""Snaps the IK finger control to the FK finger"""
860+
bl_idname = "pose.rigify_finger_ik2fk_" + rig_id
861+
bl_label = "Rigify Snap IK finger to FK"
862+
bl_options = {'UNDO'}
863+
864+
fk_ctl = bpy.props.StringProperty(name="FK Name")
865+
ik_ctl = bpy.props.StringProperty(name="IK Name")
866+
867+
@classmethod
868+
def poll(cls, context):
869+
return (context.active_object != None and context.mode == 'POSE')
870+
871+
def execute(self, context):
872+
use_global_undo = context.user_preferences.edit.use_global_undo
873+
context.user_preferences.edit.use_global_undo = False
874+
try:
875+
ik2fk_finger(context.active_object, self.fk_ctl, self.ik_ctl)
876+
finally:
877+
context.user_preferences.edit.use_global_undo = use_global_undo
878+
return {'FINISHED'}
879+
721880
###########################
722881
## IK Rotation Pole Snap ##
723882
###########################
@@ -846,6 +1005,8 @@ def register():
8461005
bpy.utils.register_class(Rigify_Arm_IK2FK)
8471006
bpy.utils.register_class(Rigify_Leg_FK2IK)
8481007
bpy.utils.register_class(Rigify_Leg_IK2FK)
1008+
bpy.utils.register_class(Rigify_Finger_FK2IK)
1009+
bpy.utils.register_class(Rigify_Finger_IK2FK)
8491010
bpy.utils.register_class(Rigify_Rot2PoleSwitch)
8501011
bpy.utils.register_class(RigUI)
8511012
bpy.utils.register_class(RigLayers)
@@ -855,7 +1016,9 @@ def unregister():
8551016
bpy.utils.unregister_class(Rigify_Arm_IK2FK)
8561017
bpy.utils.unregister_class(Rigify_Leg_FK2IK)
8571018
bpy.utils.unregister_class(Rigify_Leg_IK2FK)
858-
bpy.utils.register_class(Rigify_Rot2PoleSwitch)
1019+
bpy.utils.unregister_class(Rigify_Finger_FK2IK)
1020+
bpy.utils.unregister_class(Rigify_Finger_IK2FK)
1021+
bpy.utils.unregister_class(Rigify_Rot2PoleSwitch)
8591022
bpy.utils.unregister_class(RigUI)
8601023
bpy.utils.unregister_class(RigLayers)
8611024

0 commit comments

Comments
 (0)