@@ -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