Rigify: rework limb IK stretch limit and add a manual swing structure.

After introduction of the Custom space it is possible to easily
use Limit Distance within rigs while accounting for rig scale.
This allows replacing the Stretch To + Limit Scale mechanism
used for the IK stretch switch in rigify.

Instead, use the freed bone to manually handle limb swing before
allowing the actual IK solver to handle limb contraction. This
improves stability in marginal cases of limbs nearly straight
in the rest pose, because previously the solver could destroy
the slight knee bend in the process of swinging the limb forward,
causing a flip.
This commit is contained in:
Alexander Gavrilov 2021-10-22 22:50:06 +03:00
parent 0573bc7dae
commit 293cc140f5
2 changed files with 31 additions and 28 deletions

View File

@ -162,8 +162,8 @@ class BaseLimbRig(BaseRig):
# FK chain parents (or None)
# ik_pivot
# Custom IK pivot result (optional).
# ik_stretch
# IK stretch switch implementation.
# ik_swing
# Bone that tracks ik_target to manually handle limb swing.
# ik_target
# Corrected target position.
# ik_base
@ -422,7 +422,10 @@ class BaseLimbRig(BaseRig):
@stage.parent_bones
def parent_ik_controls(self):
self.set_bone_parent(self.bones.ctrl.ik_base, self.bones.mch.follow)
if self.use_mch_ik_base:
self.set_bone_parent(self.bones.ctrl.ik_base, self.bones.mch.follow)
else:
self.set_bone_parent(self.bones.ctrl.ik_base, self.bones.mch.ik_swing)
@stage.configure_bones
def configure_ik_controls(self):
@ -515,16 +518,17 @@ class BaseLimbRig(BaseRig):
if self.use_mch_ik_base:
self.bones.mch.ik_base = self.make_ik_mch_base_bone(orgs)
self.bones.mch.ik_stretch = self.make_ik_mch_stretch_bone(orgs)
self.bones.mch.ik_swing = self.make_ik_mch_swing_bone(orgs)
self.bones.mch.ik_target = self.make_ik_mch_target_bone(orgs)
self.bones.mch.ik_end = self.copy_bone(orgs[1], make_derived_name(orgs[1], 'mch', '_ik'))
def make_ik_mch_base_bone(self, orgs):
return self.copy_bone(orgs[0], make_derived_name(orgs[0], 'mch', '_ik'))
def make_ik_mch_stretch_bone(self, orgs):
name = self.copy_bone(orgs[0], make_derived_name(orgs[0], 'mch', '_ik_stretch'))
self.get_bone(name).tail = self.get_bone(orgs[2]).head
def make_ik_mch_swing_bone(self, orgs):
name = self.copy_bone(orgs[0], make_derived_name(orgs[0], 'mch', '_ik_swing'))
bone = self.get_bone(name)
bone.tail = bone.head + (self.get_bone(orgs[2]).head - bone.head).normalized() * bone.length * 0.3
return name
def make_ik_mch_target_bone(self, orgs):
@ -533,8 +537,11 @@ class BaseLimbRig(BaseRig):
@stage.parent_bones
def parent_ik_mch_chain(self):
if self.use_mch_ik_base:
self.set_bone_parent(self.bones.mch.ik_base, self.bones.ctrl.ik_base, inherit_scale='AVERAGE')
self.set_bone_parent(self.bones.mch.ik_stretch, self.bones.mch.follow)
self.set_bone_parent(self.bones.mch.ik_swing, self.bones.ctrl.ik_base, inherit_scale='AVERAGE')
self.set_bone_parent(self.bones.mch.ik_base, self.bones.mch.ik_swing)
else:
self.set_bone_parent(self.bones.mch.ik_swing, self.bones.mch.follow)
self.set_bone_parent(self.bones.mch.ik_target, self.get_ik_input_bone())
self.set_bone_parent(self.bones.mch.ik_end, self.get_ik_chain_base())
@ -608,26 +615,29 @@ class BaseLimbRig(BaseRig):
mch = self.bones.mch
input_bone = self.get_ik_input_bone()
self.rig_ik_mch_stretch_bones(mch.ik_target, mch.ik_stretch, input_bone, self.ik_input_head_tail, 2)
self.make_constraint(mch.ik_swing, 'DAMPED_TRACK', mch.ik_target)
self.rig_ik_mch_stretch_limit(mch.ik_target, mch.follow, input_bone, self.ik_input_head_tail, 2)
self.rig_ik_mch_end_bone(mch.ik_end, mch.ik_target, self.bones.ctrl.ik_pole)
def rig_ik_mch_stretch_bones(self, mch_target, mch_stretch, input_bone, head_tail, org_count, bias=1.035):
def rig_ik_mch_stretch_limit(self, mch_target, base_bone, input_bone, head_tail, org_count, bias=1.035):
# Compute increase in length to fully straighten
orgs = self.bones.org.main[0:org_count]
len_cur = (self.get_bone(orgs[-1]).tail - self.get_bone(orgs[0]).head).length
len_full = sum(self.get_bone(org).length for org in orgs)
len_scale = len_full / len_cur
# Limited stretch on the stretch bone
self.make_constraint(mch_stretch, 'STRETCH_TO', input_bone, head_tail=head_tail, keep_axis='SWING_Y')
# Snap the target to the input position
self.make_constraint(mch_target, 'COPY_LOCATION', input_bone, head_tail=head_tail)
con = self.make_constraint(mch_stretch, 'LIMIT_SCALE', min_y=0.0, max_y=len_scale*bias, owner_space='LOCAL')
# Limit distance from the base of the limb
con = self.make_constraint(
mch_target, 'LIMIT_DISTANCE', base_bone,
limit_mode='LIMITDIST_INSIDE', distance=len_full*bias,
# Use custom space to tolerate rig scaling
space='CUSTOM', space_object=self.obj, space_subtarget=self.bones.mch.follow,
)
self.make_driver(con, "influence", variables=[(self.prop_bone, 'IK_Stretch')], polynomial=[1.0, -1.0])
# Snap the target to the end of the stretch bone
self.make_constraint(mch_target, 'COPY_LOCATION', mch_stretch, head_tail=1.0)
def rig_ik_mch_end_bone(self, mch_ik, mch_target, ctrl_pole, chain=2):
con = self.make_constraint(
mch_ik, 'IK', mch_target, chain_count=chain,

View File

@ -39,7 +39,7 @@ class Rig(pawRig):
# EXTRA BONES
#
# mch:
# ik2_stretch, ik2_target
# ik2_target
# Three bone IK stretch limit
# ik2_chain[2]
# Second IK system (pre-driving thigh and ik3)
@ -88,14 +88,8 @@ class Rig(pawRig):
def make_ik2_mch_stretch(self):
orgs = self.bones.org.main
self.bones.mch.ik2_stretch = self.make_ik2_mch_stretch_bone(orgs)
self.bones.mch.ik2_target = self.make_ik2_mch_target_bone(orgs)
def make_ik2_mch_stretch_bone(self, orgs):
name = self.copy_bone(orgs[0], make_derived_name(orgs[0], 'mch', '_ik2_stretch'))
self.get_bone(name).tail = self.get_bone(orgs[3]).head
return name
def make_ik2_mch_target_bone(self, orgs):
return self.copy_bone(orgs[3], make_derived_name(orgs[0], 'mch', '_ik2_target'), scale=1/2)
@ -120,7 +114,6 @@ class Rig(pawRig):
@stage.parent_bones
def parent_ik2_mch_chain(self):
mch = self.bones.mch
self.set_bone_parent(mch.ik2_stretch, mch.follow)
self.set_bone_parent(mch.ik2_target, self.get_ik2_input_bone())
self.set_bone_parent(mch.ik2_chain[0], self.bones.ctrl.ik_base, inherit_scale='AVERAGE')
self.parent_bone_chain(mch.ik2_chain, use_connect=True)
@ -143,7 +136,7 @@ class Rig(pawRig):
input_bone = self.get_ik2_input_bone()
head_tail = 1 if self.use_heel2 else 0
self.rig_ik_mch_stretch_bones(mch.ik2_target, mch.ik2_stretch, input_bone, head_tail, 3)
self.rig_ik_mch_stretch_limit(mch.ik2_target, mch.follow, input_bone, head_tail, 3)
self.rig_ik_mch_end_bone(mch.ik2_chain[-1], mch.ik2_target, self.bones.ctrl.ik_pole)