Rigify: implement an optional IK palm pivot control in the arm rig.

The control itself is simply a pivot around the end of the hand
bone, similar to those in the foot and paw. However, the main
point of it is that it allows future finger IK to use the IK
control as the parent, while still allowing the wrist to be moved
relative to it.
This commit is contained in:
Alexander Gavrilov 2019-10-21 18:01:03 +03:00
parent b9a821bf60
commit db3e15be7a
4 changed files with 106 additions and 13 deletions

View File

@ -21,12 +21,15 @@
import bpy
from itertools import count
from mathutils import Matrix
from ...utils.bones import BoneDict, compute_chain_x_axis, align_bone_x_axis, align_bone_z_axis
from ...utils.bones import put_bone, compute_chain_x_axis, align_bone_x_axis, align_bone_z_axis
from ...utils.naming import make_derived_name
from ...utils.misc import map_list
from ...utils.widgets import adjust_widget_transform_mesh
from ..widgets import create_hand_widget
from ...utils.widgets_basic import create_circle_widget
from ...base_rig import stage
@ -42,6 +45,8 @@ class Rig(BaseLimbRig):
super().initialize()
self.make_palm_pivot = self.params.make_ik_palm_pivot
def prepare_bones(self):
orgs = self.bones.org.main
@ -67,11 +72,71 @@ class Rig(BaseLimbRig):
def make_ik_ctrl_widget(self, ctrl):
create_hand_widget(self.obj, ctrl)
####################################################
# Palm Pivot
def get_ik_input_bone(self):
if self.make_palm_pivot:
return self.bones.mch.ik_palm
else:
return self.get_ik_control_output()
def get_extra_ik_controls(self):
controls = super().get_extra_ik_controls()
if self.make_palm_pivot:
controls += [self.bones.ctrl.ik_palm]
return controls
@stage.generate_bones
def make_palm_pivot_control(self):
if self.make_palm_pivot:
org = self.bones.org.main[2]
self.bones.ctrl.ik_palm = self.make_palm_pivot_bone(org)
self.bones.mch.ik_palm = self.copy_bone(org, make_derived_name(org, 'mch'), scale=0.25)
def make_palm_pivot_bone(self, org):
name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_ik_palm'), scale=0.5)
put_bone(self.obj, name, self.get_bone(org).tail)
return name
@stage.parent_bones
def parent_palm_pivot_control(self):
if self.make_palm_pivot:
ctrl = self.bones.ctrl.ik_palm
self.set_bone_parent(ctrl, self.get_ik_control_output())
self.set_bone_parent(self.bones.mch.ik_palm, ctrl)
@stage.generate_widgets
def make_palm_pivot_widget(self):
if self.make_palm_pivot:
ctrl = self.bones.ctrl.ik_palm
if self.main_axis == 'x':
obj = create_circle_widget(self.obj, ctrl, head_tail=-0.3, head_tail_x=0.5)
else:
obj = create_circle_widget(self.obj, ctrl, head_tail=0.5, head_tail_x=-0.3)
if obj:
org_bone = self.get_bone(self.bones.org.main[2])
offset = org_bone.head - self.get_bone(ctrl).head
adjust_widget_transform_mesh(obj, Matrix.Translation(offset))
####################################################
# Settings
@classmethod
def add_parameters(self, params):
super().add_parameters(params)
params.make_ik_palm_pivot = bpy.props.BoolProperty(
name="IK Palm Pivot", default=False,
description="Make an extra IK hand control pivoting around the tip of the hand"
)
@classmethod
def parameters_ui(self, layout, params):
layout.prop(params, "make_ik_palm_pivot")
super().parameters_ui(layout, params, 'Hand')

View File

@ -376,8 +376,8 @@ class BaseLimbRig(BaseRig):
pbuilder.register_parent(self, self.rig_parent_bone)
pbuilder.register_parent(
self, self.get_ik_control_output(), name=self.bones.ctrl.ik,
exclude_self=True, tags={'limb_ik'},
self, self.get_ik_control_output, name=self.bones.ctrl.ik,
exclude_self=True, tags={'limb_ik', 'child'},
)
def build_ik_parent_switch(self, pbuilder):

View File

@ -99,7 +99,8 @@ class BaseSpineRig(TweakChainRig):
org_parent = self.get_bone_parent(self.bones.org[0])
parents = [org_parent] if org_parent else []
pbuilder.register_parent(self, self.get_master_control_output, name='Torso', tags={'torso'})
pbuilder.register_parent(self, self.get_master_control_output, name='Torso', tags={'torso', 'child'})
pbuilder.build_child(
self, master_name, exclude_self=True,
extra_parents=parents, select_parent=org_parent,

View File

@ -96,6 +96,8 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
ignore_global Ignore the is_global flag of potential parents.
exclude_self Ignore parents registered by the rig itself.
context_rig Rig to use for selecting parents.
no_implicit Only use parents listed as extra_parents.
only_selected Like no_implicit, but allow the 'default' selected parent.
prop_bone Name of the bone to add the property to.
prop_id Actual name of the control property.
@ -159,6 +161,7 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
'prop_bone': None, 'prop_id': None, 'prop_name': None, 'controls': None,
'select_parent': None, 'ignore_global': False, 'exclude_self': False,
'context_rig': None, 'select_tags': None,
'no_implicit': False, 'only_selected': False,
'ctrl_bone': None,
'no_fix_location': False, 'no_fix_rotation': False, 'no_fix_scale': False,
'copy_location': None, 'copy_rotation': None, 'copy_scale': None,
@ -255,32 +258,56 @@ class SwitchParentBuilder(GeneratorPlugin, MechanismUtilityMixin):
parent_tags[parent['bone']] |= parent['tags']
last_main_parent_bone = child['parents'][-1]['bone']
num_main_parents = len(parent_map.items())
extra_parents = set()
for parent in force_lazy(child['extra_parents'] or []):
if not isinstance(parent, tuple):
parent = (parent, None)
extra_parents.add(parent[0])
if parent[0] not in parent_map:
parent_map[parent[0]] = parent[1]
for parent in parent_map:
if parent in self.child_map:
parent_tags[parent] |= {'child'}
parent_bones = list(parent_map.items())
child['parent_bones'] = parent_bones
# Find which bone to select
select_bone = force_lazy(child['select_parent']) or last_main_parent_bone
select_tags = force_lazy(child['select_tags']) or []
select_index = num_main_parents
if child['no_implicit']:
assert len(extra_parents) > 0
parent_bones = [ item for item in parent_bones if item[0] in extra_parents ]
if last_main_parent_bone not in extra_parents:
last_main_parent_bone = parent_bones[-1][0]
for tag in select_tags:
tag_set = tag if isinstance(tag, set) else {tag}
matching = [
bone for (bone, _) in parent_bones
if not tag_set.isdisjoint(parent_tags[bone])
]
if len(matching) > 0:
select_bone = matching[-1]
break
if select_bone not in parent_map:
print("RIGIFY ERROR: Can't find bone '%s' to select as default parent of '%s'\n" % (select_bone, bone))
select_bone = last_main_parent_bone
if child['only_selected']:
filter_set = { select_bone, *extra_parents }
parent_bones = [ item for item in parent_bones if item[0] in filter_set ]
try:
select_index = 1 + next(i for i, (bone, _) in enumerate(parent_bones) if bone == select_bone)
except StopIteration:
print("RIGIFY ERROR: Can't find bone '%s' to select as default parent of '%s'\n" % (select_bone, bone))
select_index = len(parent_bones)
print("RIGIFY ERROR: Invalid default parent '%s' of '%s'\n" % (select_bone, bone))
for tag in select_tags:
matching = [ i for i, (bone, _) in enumerate(parent_bones) if tag in parent_tags[bone] ]
if len(matching) > 0:
select_index = 1 + matching[-1]
break
child['parent_bones'] = parent_bones
# Create the controlling property
prop_bone = child['prop_bone'] = force_lazy(child['prop_bone']) or bone