diff options
-rw-r--r-- | rigify/rigs/limbs/super_finger.py | 310 |
1 files changed, 298 insertions, 12 deletions
diff --git a/rigify/rigs/limbs/super_finger.py b/rigify/rigs/limbs/super_finger.py index 0b3fcd8a..978c0e09 100644 --- a/rigify/rigs/limbs/super_finger.py +++ b/rigify/rigs/limbs/super_finger.py @@ -20,6 +20,7 @@ import bpy import re +import json from itertools import count @@ -27,9 +28,11 @@ from ...utils.errors import MetarigError from ...utils.bones import put_bone, flip_bone, align_chain_x_axis, set_bone_widget_transform from ...utils.naming import make_derived_name from ...utils.widgets import create_widget -from ...utils.widgets_basic import create_circle_widget +from ...utils.widgets_basic import create_circle_widget, create_sphere_widget from ...utils.misc import map_list from ...utils.layers import ControlLayersOption +from ...utils.switch_parent import SwitchParentBuilder +from ...utils.animation import add_generic_snap, add_fk_ik_snap_buttons from ...base_rig import stage @@ -42,12 +45,15 @@ class Rig(SimpleChainRig): super().initialize() self.bbone_segments = self.params.bbones - self.first_parent = self.get_bone_parent(self.bones.org[0]) + self.make_ik = self.params.make_extra_ik_control def prepare_bones(self): if self.params.primary_rotation_axis == 'automatic': align_chain_x_axis(self.obj, self.bones.org) + def parent_bones(self): + self.rig_parent_bone = self.get_bone_parent(self.bones.org[0]) + ############################## # Master Control @@ -138,6 +144,76 @@ class Rig(SimpleChainRig): create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5) ############################## + # IK Control + + @stage.generate_bones + def make_ik_control(self): + if self.make_ik: + self.bones.ctrl.ik = self.make_ik_control_bone(self.bones.org) + + self.build_ik_parent_switch(SwitchParentBuilder(self.generator)) + + def make_ik_control_bone(self, orgs): + name = self.copy_bone(orgs[-1], make_derived_name(orgs[0], 'ctrl', '_ik'), scale=0.7) + put_bone(self.obj, name, self.get_bone(orgs[-1]).tail) + return name + + def build_ik_parent_switch(self, pbuilder): + ctrl = self.bones.ctrl + + pbuilder.build_child( + self, ctrl.ik, prop_bone=ctrl.ik, + select_tags=['held_object', 'limb_ik', {'child', 'limb_end'}], only_selected=True, + prop_id='IK_parent', prop_name='IK Parent', controls=[ ctrl.ik ], + no_fix_rotation=True, no_fix_scale=True, + ) + + @stage.configure_bones + def configure_ik_control(self): + if self.make_ik: + bone = self.get_bone(self.bones.ctrl.ik) + bone.lock_rotation = True, True, True + bone.lock_rotation_w = True + bone.lock_scale = True, True, True + + @stage.configure_bones + def configure_ik_control_properties(self): + if self.make_ik: + ctrl = self.bones.ctrl + rig_name = ctrl.fk[0] + + panel = self.script.panel_with_selected_check(self, self.bones.ctrl.flatten()) + + self.make_property( + ctrl.ik, 'FK_IK', 0.0, + description="Enable simple IK correction on top of FK posing" + ) + panel.custom_prop(ctrl.ik, 'FK_IK', text="Finger IK ({})".format(rig_name), slider=True) + + axis = self.params.primary_rotation_axis + + add_finger_snap_fk_to_ik( + panel, master=ctrl.master, fk_bones=ctrl.fk, + ik_bones=self.bones.org, ik_control=ctrl.ik, + ik_constraint_bone=self.bones.org[-1], + axis=self.axis_options[axis]['id'], + rig_name=rig_name, compact=True, + ) + + add_generic_snap( + panel, output_bones=[ctrl.ik], input_bones=ctrl.fk[-1:], + input_ctrl_bones=[ctrl.master, *ctrl.fk], + label='IK->FK', rig_name=rig_name, tooltip='IK to FK', + compact=True, locks=(False,True,True), + ) + + + @stage.generate_widgets + def make_ik_control_widget(self): + if self.make_ik: + create_sphere_widget(self.obj, self.bones.ctrl.ik) + + ############################## # MCH bend chain @stage.generate_bones @@ -145,29 +221,29 @@ class Rig(SimpleChainRig): self.bones.mch.bend = map_list(self.make_mch_bend_bone, self.bones.org) def make_mch_bend_bone(self, org): - return self.copy_bone(org, make_derived_name(org, 'mch', '_drv'), parent=False) + return self.copy_bone(org, make_derived_name(org, 'mch', '_drv'), parent=False, scale=0.3) @stage.parent_bones def parent_mch_bend_chain(self): ctrls = self.bones.ctrl.fk - for args in zip(self.bones.mch.bend, [self.first_parent] + ctrls): + for args in zip(self.bones.mch.bend, [self.rig_parent_bone] + ctrls): self.set_bone_parent(*args) # Match axis to expression axis_options = { - "automatic": {"axis": 0, + "automatic": {"axis": 0, "id": '+X', "expr": '(1-sy)*pi'}, - "X": {"axis": 0, + "X": {"axis": 0, "id": '+X', "expr": '(1-sy)*pi'}, - "-X": {"axis": 0, + "-X": {"axis": 0, "id": '-X', "expr": '-((1-sy)*pi)'}, - "Y": {"axis": 1, + "Y": {"axis": 1, "id": '+Y', "expr": '(1-sy)*pi'}, - "-Y": {"axis": 1, + "-Y": {"axis": 1, "id": '-Y', "expr": '-((1-sy)*pi)'}, - "Z": {"axis": 2, + "Z": {"axis": 2, "id": '+Z', "expr": '(1-sy)*pi'}, - "-Z": {"axis": 2, + "-Z": {"axis": 2, "id": '-Z', "expr": '-((1-sy)*pi)'} } @@ -207,7 +283,7 @@ class Rig(SimpleChainRig): @stage.parent_bones def parent_mch_stretch_chain(self): ctrls = self.bones.ctrl.fk - for args in zip(self.bones.mch.stretch, [self.first_parent] + ctrls[1:]): + for args in zip(self.bones.mch.stretch, [self.rig_parent_bone] + ctrls[1:]): self.set_bone_parent(*args) @stage.rig_bones @@ -232,6 +308,30 @@ class Rig(SimpleChainRig): for args in zip(count(0), self.bones.org, self.bones.mch.stretch): self.rig_org_bone(*args) + if self.make_ik: + self.rig_org_ik(self.bones.org, self.bones.ctrl.ik) + + def rig_org_ik(self, orgs, ik_ctrl): + axis = self.params.primary_rotation_axis + options = self.axis_options[axis] + + # Lock IK axis on child bones, using stiffness to preserve + # original rotation rather than zeroing it out. + stiffness = [1.0, 1.0, 1.0] + stiffness[options['axis']] = 0.0 + + for org in orgs[1:]: + bone = self.get_bone(org) + bone.ik_stiffness_x, bone.ik_stiffness_y, bone.ik_stiffness_z = stiffness + + # Add the constraint + con = self.make_constraint( + orgs[-1], 'IK', ik_ctrl, name='FingerIK', + chain_count=len(orgs), use_stretch=False, + ) + + self.make_driver(con, "influence", variables=[(ik_ctrl, 'FK_IK')]) + ############################## # Deform chain @@ -275,6 +375,12 @@ class Rig(SimpleChainRig): description = 'Number of B-Bone segments' ) + params.make_extra_ik_control = bpy.props.BoolProperty( + name = "Extra IK Control", + default = False, + description = "Create an optional IK control" + ) + ControlLayersOption.TWEAK.add_parameters(params) @classmethod @@ -286,9 +392,189 @@ class Rig(SimpleChainRig): r.prop(params, "primary_rotation_axis", text="") layout.prop(params, 'bbones') + layout.prop(params, 'make_extra_ik_control', text='IK Control') ControlLayersOption.TWEAK.parameters_ui(layout, params) +############################# +# Finger FK to IK operator ## +############################# + +SCRIPT_REGISTER_OP_SNAP_FK_IK = ['POSE_OT_rigify_finger_fk2ik', 'POSE_OT_rigify_finger_fk2ik_bake'] + +SCRIPT_UTILITIES_OP_SNAP_FK_IK = [''' +######################## +## Limb Snap IK to FK ## +######################## + +class RigifyFingerFk2IkBase: + ik_control: StringProperty(name="IK Control") + ik_chain: StringProperty(name="IK output chain") + constraint_bone: StringProperty(name="Bone With the IK Constraint") + fk_master: StringProperty(name="FK Master Control") + fk_chain: StringProperty(name="FK Bone Chain") + axis: StringProperty(name="Main Rotation Axis", default="+X") + + def init_execute(self, context): + self.ik_chain_list = json.loads(self.ik_chain) + self.fk_chain_list = json.loads(self.fk_chain) + + # Extracting the IK state - requires forcing IK on temporarily + def find_constraint_drivers(self, obj): + self.driver_fcurves = {} + self.ik_constraint = None + + if self.constraint_bone: + bone = obj.pose.bones[self.constraint_bone] + self.ik_constraint = con = bone.constraints['FingerIK'] + self.driver_fcurves = DriverCurveTable(obj).get_prop_curves(con, 'influence') + + def before_save_state(self, context, obj): + self.find_constraint_drivers(obj) + + if self.ik_constraint: + for fcu in self.driver_fcurves.values(): + fcu.mute = True + + self.ik_constraint.influence = 1 + + context.view_layer.update() + + def get_fk_axis_angles(self, obj): + options = self.axis_options[self.axis] + angles = [] + + for bone in self.fk_chain_list[1:-1]: + matrix = obj.pose.bones[bone].matrix_basis + eulers = matrix.to_euler(options['order']) + angles.append(eulers[options['axis']]) + + return angles + + def get_ik_original_matrix(self, obj): + bone = obj.pose.bones[self.ik_chain_list[0]] + + if len(bone.constraints) == 1 and bone.constraints[0].type == 'COPY_TRANSFORMS': + target = bone.constraints[0].subtarget + return obj.pose.bones[target].matrix + + def save_frame_state(self, context, obj): + matrices = get_chain_transform_matrices(obj, self.ik_chain_list) + fk_matrices = get_chain_transform_matrices(obj, self.fk_chain_list) + angles = self.get_fk_axis_angles(obj) + ik_original = self.get_ik_original_matrix(obj) + return (matrices, fk_matrices, angles, ik_original) + + def after_save_state(self, context, obj): + if self.ik_constraint: + for fcu in self.driver_fcurves.values(): + fcu.mute = False + + context.view_layer.update() + + # Applying the state + axis_options = { + "+X": {"axis": 0, "sign": 1, "order": 'ZYX'}, + "-X": {"axis": 0, "sign": -1, "order": 'ZYX'}, + "+Y": {"axis": 1, "sign": 1, "order": 'ZXY'}, + "-Y": {"axis": 1, "sign": -1, "order": 'ZXY'}, + "+Z": {"axis": 2, "sign": 1, "order": 'XYZ'}, + "-Z": {"axis": 2, "sign": -1, "order": 'XYZ'}, + } + + def apply_frame_state(self, context, obj, state): + matrices, fk_matrices, old_angles, ik_original = state + + fk_master = obj.pose.bones[self.fk_master] + fk_chain = [ obj.pose.bones[k] for k in self.fk_chain_list ] + + # Set the master control position and rotation. + master_mat = matrices[0] + + if ik_original: + master_mat = master_mat @ ik_original.inverted() @ fk_matrices[0] + + master_mat = obj.convert_space( + pose_bone=fk_chain[0].parent, matrix=master_mat, + from_space='POSE', to_space='LOCAL' + ) + master_mat = obj.convert_space( + pose_bone=fk_master, matrix=master_mat, + from_space='LOCAL', to_space='POSE' + ) + master_mat.translation = matrices[0].translation + + set_transform_from_matrix(obj, self.fk_master, master_mat) + + fk_master.scale = (1, 1, 1) + + if self.keyflags is not None: + keyframe_transform_properties(obj, self.fk_master, self.keyflags) + + context.view_layer.update() + + # Apply the detail controls + set_chain_transforms_from_matrices( + context, obj, self.fk_chain_list[:-1], matrices, keyflags=self.keyflags, + ) + + set_transform_from_matrix( + obj, self.fk_chain_list[-1], Matrix.Identity(4), space='LOCAL', keyflags=self.keyflags + ) + + # Compute the master scale from average control angle, biased by original + options = self.axis_options[self.axis] + + angles = self.get_fk_axis_angles(obj) + avg_angle = sum(a - b for a, b in zip(angles, old_angles)) / len(angles) + + fk_master.scale[1] = 1 - avg_angle * options['sign'] / pi + + if self.keyflags is not None: + keyframe_transform_properties(obj, self.fk_master, self.keyflags) + + context.view_layer.update() + + # Re-apply the rest of the detail controls + set_chain_transforms_from_matrices( + context, obj, self.fk_chain_list[1:-1], matrices[1:], keyflags=self.keyflags, + ) + +class POSE_OT_rigify_finger_fk2ik(RigifyFingerFk2IkBase, RigifySingleUpdateMixin, bpy.types.Operator): + bl_idname = "pose.rigify_finger_fk2ik_" + rig_id + bl_label = "Snap FK->IK" + bl_description = "Snap the FK chain to IK result" + +class POSE_OT_rigify_finger_fk2ik_bake(RigifyFingerFk2IkBase, RigifyBakeKeyframesMixin, bpy.types.Operator): + bl_idname = "pose.rigify_finger_fk2ik_bake_" + rig_id + bl_label = "Apply Snap FK->IK To Keyframes" + bl_description = "Snap the FK chain keyframes to IK result" + + def execute_scan_curves(self, context, obj): + fk_bones = [self.fk_master, *self.fk_chain_list] + self.bake_add_bone_frames(fk_bones + [self.ik_control], TRANSFORM_PROPS_ALL) + return self.bake_get_all_bone_curves(fk_bones, TRANSFORM_PROPS_ALL) +'''] + +def add_finger_snap_fk_to_ik(panel, *, master=None, fk_bones=[], ik_bones=[], ik_control=None, ik_constraint_bone=None, axis='+X', rig_name='', compact=None): + panel.use_bake_settings() + panel.script.add_utilities(SCRIPT_UTILITIES_OP_SNAP_FK_IK) + panel.script.register_classes(SCRIPT_REGISTER_OP_SNAP_FK_IK) + + op_props = { + 'fk_master': master, + 'fk_chain': json.dumps(fk_bones), + 'ik_chain': json.dumps(ik_bones), + 'ik_control': ik_control, + 'constraint_bone': ik_constraint_bone, + 'axis': axis, + } + + add_fk_ik_snap_buttons( + panel, 'pose.rigify_finger_fk2ik_{rig_id}', 'pose.rigify_finger_fk2ik_bake_{rig_id}', + label='FK->IK', rig_name=rig_name, properties=op_props, + clear_bones=[master, *fk_bones], compact=compact, + ) def create_sample(obj): # generated by rigify.utils.write_metarig |