Rigify: support foot IK control bone pivoting around the ankle.

Allow switching the foot IK control between pivoting around
the base of the toe as before, around the ankle, or ankle with
additional toe pivot. Also rewrite the foot roll mechanism to
use correct Euler orders that match the actual bone hierarchy.
This commit is contained in:
Alexander Gavrilov 2019-10-16 18:29:11 +03:00
parent 4225323a26
commit c871246f8d
2 changed files with 120 additions and 68 deletions

View File

@ -22,13 +22,14 @@ import bpy
import math
from itertools import count
from mathutils import Vector
from mathutils import Vector, Matrix
from ...utils.rig import is_rig_base_bone
from ...utils.bones import align_chain_x_axis, align_bone_x_axis, align_bone_y_axis, align_bone_z_axis
from ...utils.bones import align_bone_to_axis, flip_bone, put_bone, align_bone_orientation
from ...utils.naming import make_derived_name
from ...utils.misc import map_list
from ...utils.misc import map_list, matrix_from_axis_roll, matrix_from_axis_pair
from ...utils.widgets import adjust_widget_transform_mesh
from ..widgets import create_foot_widget, create_ballsocket_widget
@ -62,10 +63,18 @@ class Rig(BaseLimbRig):
super().initialize()
self.pivot_type = self.params.foot_pivot_type
self.heel_euler_order = 'ZXY' if self.main_axis == 'x' else 'XZY'
assert self.pivot_type in {'ANKLE', 'TOE', 'ANKLE_TOE'}
def prepare_bones(self):
orgs = self.bones.org.main
foot = self.get_bone(orgs[2])
foot_x = self.vector_without_z(self.get_bone(orgs[2]).y_axis).cross((0, 0, -1))
ik_y_axis = (0, 1, 0)
foot_y_axis = -self.vector_without_z(foot.y_axis)
foot_x = foot_y_axis.cross((0, 0, 1))
if self.params.rotation_axis == 'automatic':
align_chain_x_axis(self.obj, orgs[0:2])
@ -84,6 +93,12 @@ class Rig(BaseLimbRig):
align_bone_z_axis(self.obj, orgs[2], foot_x)
align_bone_z_axis(self.obj, orgs[3], -foot_x)
else:
ik_y_axis = foot_y_axis
# Orientation of the IK main and roll control bones
self.ik_matrix = matrix_from_axis_roll(ik_y_axis, 0)
self.roll_matrix = matrix_from_axis_pair(ik_y_axis, foot_x, self.main_axis)
####################################################
# EXTRA BONES
@ -92,6 +107,8 @@ class Rig(BaseLimbRig):
# heel:
# Heel location marker bone
# ctrl:
# ik_spin:
# Toe spin control.
# heel:
# Foot roll control
# mch:
@ -104,21 +121,17 @@ class Rig(BaseLimbRig):
# IK controls
def get_extra_ik_controls(self):
return [self.bones.ctrl.heel]
if self.pivot_type == 'ANKLE_TOE':
return [self.bones.ctrl.heel, self.bones.ctrl.ik_spin]
else:
return [self.bones.ctrl.heel]
def make_ik_control_bone(self, orgs):
name = self.copy_bone(orgs[2], make_derived_name(orgs[2], 'ctrl', '_ik'))
if self.params.rotation_axis == 'automatic' or self.params.auto_align_extremity:
align_bone_to_axis(self.obj, name, 'y', flip=True)
if self.pivot_type == 'TOE':
put_bone(self.obj, name, self.get_bone(name).tail, matrix=self.ik_matrix)
else:
flip_bone(self.obj, name)
bone = self.get_bone(name)
bone.tail[2] = bone.head[2]
bone.roll = 0
put_bone(self.obj, name, None, matrix=self.ik_matrix)
return name
def build_ik_pivot(self, ik_name, **args):
@ -135,8 +148,42 @@ class Rig(BaseLimbRig):
pbuilder.register_parent(self, self.bones.org.main[2], exclude_self=True)
def make_ik_ctrl_widget(self, ctrl):
create_foot_widget(self.obj, ctrl)
obj = create_foot_widget(self.obj, ctrl)
if self.pivot_type != 'TOE':
org = self.get_bone(self.bones.org.main[2])
adjust_widget_transform_mesh(obj, Matrix.Translation(org.vector))
####################################################
# IK pivot controls
def get_ik_pivot_output(self):
if self.pivot_type == 'ANKLE_TOE':
return self.bones.ctrl.ik_spin
else:
return self.get_ik_control_output()
@stage.generate_bones
def make_ik_pivot_controls(self):
if self.pivot_type == 'ANKLE_TOE':
self.bones.ctrl.ik_spin = self.make_ik_spin_bone(self.bones.org.main)
def make_ik_spin_bone(self, orgs):
name = self.copy_bone(orgs[2], make_derived_name(orgs[2], 'ctrl', '_spin_ik'))
put_bone(self.obj, name, self.get_bone(orgs[3]).head, matrix=self.ik_matrix, scale=0.5)
return name
@stage.parent_bones
def parent_ik_pivot_controls(self):
if self.pivot_type == 'ANKLE_TOE':
self.set_bone_parent(self.bones.ctrl.ik_spin, self.get_ik_control_output())
@stage.generate_widgets
def make_ik_spin_control_widget(self):
if self.pivot_type == 'ANKLE_TOE':
obj = create_ballsocket_widget(self.obj, self.bones.ctrl.ik_spin, size=0.75)
rotfix = Matrix.Rotation(math.pi/2, 4, self.main_axis.upper())
adjust_widget_transform_mesh(obj, rotfix, local=True)
####################################################
# Heel control
@ -144,25 +191,19 @@ class Rig(BaseLimbRig):
@stage.generate_bones
def make_heel_control_bone(self):
org = self.bones.org.main[2]
name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_heel_ik'), scale=1/2)
name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_heel_ik'))
put_bone(self.obj, name, None, matrix=self.roll_matrix, scale=0.5)
self.bones.ctrl.heel = name
self.align_roll_bone(org, name, -self.vector_without_z(self.get_bone(org).vector))
@stage.parent_bones
def parent_heel_control_bone(self):
self.set_bone_parent(self.bones.ctrl.heel, self.get_ik_control_output())
self.set_bone_parent(self.bones.ctrl.heel, self.get_ik_pivot_output(), inherit_scale='AVERAGE')
@stage.configure_bones
def configure_heel_control_bone(self):
bone = self.get_bone(self.bones.ctrl.heel)
bone.lock_location = True, True, True
if self.main_axis == 'x':
bone.lock_rotation = False, False, True
else:
bone.lock_rotation = True, False, False
bone.rotation_mode = self.heel_euler_order
bone.lock_scale = True, True, True
@stage.generate_widgets
@ -181,34 +222,27 @@ class Rig(BaseLimbRig):
def make_roll_mch_bones(self, foot, toe, heel):
foot_bone = self.get_bone(foot)
heel_bone = self.get_bone(heel)
axis = -self.vector_without_z(foot_bone.vector)
roll1 = self.copy_bone(foot, make_derived_name(heel, 'mch', '_roll1'))
heel_middle = (heel_bone.head + heel_bone.tail) / 2
flip_bone(self.obj, roll1)
self.align_roll_bone(foot, roll1, -foot_bone.vector)
roll2 = self.copy_bone(toe, make_derived_name(heel, 'mch', '_roll2'), scale=1/4)
put_bone(self.obj, roll2, (heel_bone.head + heel_bone.tail) / 2)
self.align_roll_bone(foot, roll2, -axis)
result = self.copy_bone(foot, make_derived_name(foot, 'mch', '_roll'), scale=0.25)
roll1 = self.copy_bone(toe, make_derived_name(heel, 'mch', '_roll1'), scale=0.3)
roll2 = self.copy_bone(toe, make_derived_name(heel, 'mch', '_roll2'), scale=0.3)
rock1 = self.copy_bone(heel, make_derived_name(heel, 'mch', '_rock1'))
align_bone_to_axis(self.obj, rock1, 'y', roll=0, length=heel_bone.length/2, flip=True)
align_bone_y_axis(self.obj, rock1, axis)
rock2 = self.copy_bone(heel, make_derived_name(heel, 'mch', '_rock2'))
align_bone_to_axis(self.obj, rock2, 'y', roll=0, length=heel_bone.length/2)
align_bone_y_axis(self.obj, rock2, axis)
put_bone(self.obj, roll1, None, matrix=self.roll_matrix)
put_bone(self.obj, roll2, heel_middle, matrix=self.roll_matrix)
put_bone(self.obj, rock1, heel_bone.tail, matrix=self.roll_matrix, scale=0.5)
put_bone(self.obj, rock2, heel_bone.head, matrix=self.roll_matrix, scale=0.5)
return [ rock2, rock1, roll2, roll1 ]
return [ rock2, rock1, roll2, roll1, result ]
@stage.parent_bones
def parent_roll_mch_chain(self):
chain = self.bones.mch.heel
self.set_bone_parent(chain[0], self.get_ik_control_output())
self.set_bone_parent(chain[0], self.get_ik_pivot_output())
self.parent_bone_chain(chain)
@stage.rig_bones
@ -216,28 +250,37 @@ class Rig(BaseLimbRig):
self.rig_roll_mch_bones(self.bones.mch.heel, self.bones.ctrl.heel, self.bones.org.heel)
def rig_roll_mch_bones(self, chain, heel, org_heel):
rock2, rock1, roll2, roll1 = chain
rock2, rock1, roll2, roll1, result = chain
self.make_constraint(roll1, 'COPY_ROTATION', heel, space='LOCAL')
self.make_constraint(roll2, 'COPY_ROTATION', heel, space='LOCAL')
# This order is required for correct working of the constraints
for bone in chain:
self.get_bone(bone).rotation_mode = self.heel_euler_order
self.make_constraint(roll1, 'COPY_ROTATION', heel, space='POSE')
if self.main_axis == 'x':
self.make_constraint(roll1, 'LIMIT_ROTATION', use_limit_x=True, max_x=DEG_360, space='LOCAL')
self.make_constraint(roll2, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, min_x=-DEG_360, space='LOCAL')
self.make_constraint(roll2, 'COPY_ROTATION', heel, space='LOCAL', use_xyz=(True, False, False))
self.make_constraint(roll2, 'LIMIT_ROTATION', min_x=-DEG_360, space='LOCAL')
else:
self.make_constraint(roll1, 'LIMIT_ROTATION', use_limit_z=True, max_z=DEG_360, space='LOCAL')
self.make_constraint(roll2, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, min_z=-DEG_360, space='LOCAL')
self.make_constraint(roll2, 'COPY_ROTATION', heel, space='LOCAL', use_xyz=(False, False, True))
self.make_constraint(roll2, 'LIMIT_ROTATION', min_z=-DEG_360, space='LOCAL')
direction = self.get_main_axis(self.get_bone(heel)).dot(self.get_bone(org_heel).vector)
if direction < 0:
rock2, rock1 = rock1, rock2
self.make_constraint(rock1, 'COPY_ROTATION', heel, space='LOCAL')
self.make_constraint(rock2, 'COPY_ROTATION', heel, space='LOCAL')
self.make_constraint(
rock1, 'COPY_ROTATION', heel, space='LOCAL',
use_xyz=(False, True, False),
)
self.make_constraint(
rock2, 'COPY_ROTATION', heel, space='LOCAL',
use_xyz=(False, True, False),
)
self.make_constraint(rock1, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, max_y=DEG_360, space='LOCAL')
self.make_constraint(rock2, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, min_y=-DEG_360, space='LOCAL')
self.make_constraint(rock1, 'LIMIT_ROTATION', max_y=DEG_360, space='LOCAL')
self.make_constraint(rock2, 'LIMIT_ROTATION', min_y=-DEG_360, space='LOCAL')
####################################################
@ -245,7 +288,7 @@ class Rig(BaseLimbRig):
def parent_fk_parent_bone(self, i, parent_mch, prev_ctrl, org, prev_org):
if i == 3:
align_bone_orientation(self.obj, parent_mch, self.bones.mch.heel[-2])
align_bone_orientation(self.obj, parent_mch, self.bones.mch.heel[2])
self.set_bone_parent(parent_mch, prev_org, use_connect=True)
@ -254,7 +297,7 @@ class Rig(BaseLimbRig):
def rig_fk_parent_bone(self, i, parent_mch, org):
if i == 3:
con = self.make_constraint(parent_mch, 'COPY_TRANSFORMS', self.bones.mch.heel[-2])
con = self.make_constraint(parent_mch, 'COPY_TRANSFORMS', self.bones.mch.heel[2])
self.make_driver(con, 'influence', variables=[(self.prop_bone, 'IK_FK')], polynomial=[1.0, -1.0])
@ -265,8 +308,6 @@ class Rig(BaseLimbRig):
####################################################
# IK system MCH
ik_input_head_tail = 1.0
def get_ik_input_bone(self):
return self.bones.mch.heel[-1]
@ -274,14 +315,35 @@ class Rig(BaseLimbRig):
def parent_ik_mch_chain(self):
super().parent_ik_mch_chain()
self.set_bone_parent(self.bones.mch.ik_target, self.bones.ctrl.heel)
self.set_bone_parent(self.bones.mch.ik_target, self.bones.mch.heel[-1])
####################################################
# Settings
@classmethod
def add_parameters(self, params):
super().add_parameters(params)
items = [
('ANKLE', 'Ankle',
'The foots pivots at the ankle'),
('TOE', 'Toe',
'The foot pivots around the base of the toe'),
('ANKLE_TOE', 'Ankle and Toe',
'The foots pivots at the ankle, with extra toe pivot'),
]
params.foot_pivot_type = bpy.props.EnumProperty(
items = items,
name = "Foot Pivot",
default = 'ANKLE_TOE'
)
@classmethod
def parameters_ui(self, layout, params):
layout.prop(params, 'foot_pivot_type')
super().parameters_ui(layout, params, 'Foot')

View File

@ -24,7 +24,6 @@ import json
from ...utils.animation import add_generic_snap_fk_to_ik, add_fk_ik_snap_buttons
from ...utils.rig import connected_children_names
from ...utils.bones import BoneDict, put_bone, compute_chain_x_axis, align_bone_orientation
from ...utils.bones import align_bone_x_axis, align_bone_y_axis, align_bone_z_axis
from ...utils.naming import strip_org, make_derived_name
from ...utils.layers import ControlLayersOption
from ...utils.misc import pairwise_nozip, padnone, map_list
@ -126,15 +125,6 @@ class BaseLimbRig(BaseRig):
bone = self.get_bone(org)
return bone.head + bone.vector * (seg / self.segments)
def align_roll_bone(self, org, name, y_axis):
if y_axis:
align_bone_y_axis(self.obj, name, y_axis)
if self.main_axis == 'x':
align_bone_x_axis(self.obj, name, self.get_bone(org).x_axis)
else:
align_bone_z_axis(self.obj, name, self.get_bone(org).z_axis)
@staticmethod
def vector_without_z(vector):
return Vector((vector[0], vector[1], 0))