Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Gavrilov <angavrilov@gmail.com>2019-11-02 11:57:25 +0300
committerAlexander Gavrilov <angavrilov@gmail.com>2019-12-16 18:53:14 +0300
commita41ceb3ac8d48fcf8f47360376d009ea08dbb894 (patch)
treed5d24b37de1db587b30ccc04d8ca8ee4b8396b56 /rigify/rigs/limbs/super_finger.py
parentbf89c1eb28007fdd491b5a0b7cb3709b84d88b85 (diff)
Rigify: add optional primitive IK in limbs.super_finger.
Basic IK support in fingers could be useful for easily avoiding fingertips sliding when animating minor movement between the hand and an object it is holding. As there are 10 fingers, to limit the performance impact the IK itself is implemented using just one extra control, one constraint, and one driver. The parent switch adds one more bone, constraint and driver. This simple implementation requires applying IK as a correction on top of the FK shape to share the FK controls for precisely defining the shape, which means that stretch can't be implemented without giving up on exact IK<->FK snapping. This also means that unlike limbs this IK is not indended for independent use, and must always be used as a local anti-slide fix on top of primarily FK animation. The parent switch is designed to work with the extra wrist control and/or a held object pivot, demonstrating the tag feature of SwitchParentBuilder.
Diffstat (limited to 'rigify/rigs/limbs/super_finger.py')
-rw-r--r--rigify/rigs/limbs/super_finger.py310
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