531 lines
19 KiB
Python
531 lines
19 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import bpy
|
|
import math
|
|
|
|
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_z_axis
|
|
from ...utils.bones import put_bone, align_bone_orientation
|
|
from ...utils.naming import make_derived_name
|
|
from ...utils.misc import 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
|
|
|
|
from ...base_rig import stage
|
|
|
|
from .limb_rigs import BaseLimbRig
|
|
|
|
|
|
DEG_360 = math.pi * 2
|
|
ALL_TRUE = (True, True, True)
|
|
|
|
|
|
class Rig(BaseLimbRig):
|
|
"""Human leg rig."""
|
|
|
|
min_valid_orgs = max_valid_orgs = 4
|
|
|
|
def find_org_bones(self, bone):
|
|
bones = super().find_org_bones(bone)
|
|
|
|
for b in self.get_bone(bones.main[2]).bone.children:
|
|
if not b.use_connect and not b.children and not is_rig_base_bone(self.obj, b.name):
|
|
bones.heel = b.name
|
|
break
|
|
else:
|
|
self.raise_error("Heel bone not found.")
|
|
|
|
return bones
|
|
|
|
def initialize(self):
|
|
super().initialize()
|
|
|
|
self.pivot_type = self.params.foot_pivot_type
|
|
self.heel_euler_order = 'ZXY' if self.main_axis == 'x' else 'XZY'
|
|
self.use_ik_toe = self.params.extra_ik_toe
|
|
|
|
if self.use_ik_toe:
|
|
self.fk_name_suffix_cutoff = 3
|
|
self.fk_ik_layer_cutoff = 4
|
|
|
|
assert self.pivot_type in {'ANKLE', 'TOE', 'ANKLE_TOE'}
|
|
|
|
def prepare_bones(self):
|
|
orgs = self.bones.org.main
|
|
foot = self.get_bone(orgs[2])
|
|
|
|
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])
|
|
|
|
# Orient foot and toe
|
|
align_bone_x_axis(self.obj, orgs[2], foot_x)
|
|
align_bone_x_axis(self.obj, orgs[3], -foot_x)
|
|
|
|
align_bone_x_axis(self.obj, self.bones.org.heel, Vector((0, 0, 1)))
|
|
|
|
elif self.params.auto_align_extremity:
|
|
if self.main_axis == 'x':
|
|
align_bone_x_axis(self.obj, orgs[2], foot_x)
|
|
align_bone_x_axis(self.obj, orgs[3], -foot_x)
|
|
else:
|
|
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
|
|
#
|
|
# org:
|
|
# heel:
|
|
# Heel location marker bone
|
|
# ctrl:
|
|
# ik_spin:
|
|
# Toe spin control.
|
|
# heel:
|
|
# Foot roll control
|
|
# ik_toe:
|
|
# If enabled, toe control for IK chain.
|
|
# mch:
|
|
# heel[]:
|
|
# Chain of bones implementing foot roll.
|
|
# ik_toe_parent:
|
|
# If using split IK toe, parent of the IK toe control.
|
|
#
|
|
####################################################
|
|
|
|
####################################################
|
|
# IK controls
|
|
|
|
def get_tail_ik_controls(self):
|
|
return [self.bones.ctrl.ik_toe] if self.use_ik_toe else []
|
|
|
|
def get_extra_ik_controls(self):
|
|
controls = super().get_extra_ik_controls() + [self.bones.ctrl.heel]
|
|
if self.pivot_type == 'ANKLE_TOE':
|
|
controls += [self.bones.ctrl.ik_spin]
|
|
return controls
|
|
|
|
def make_ik_control_bone(self, orgs):
|
|
name = self.copy_bone(orgs[2], make_derived_name(orgs[2], 'ctrl', '_ik'))
|
|
if self.pivot_type == 'TOE':
|
|
put_bone(self.obj, name, self.get_bone(name).tail, matrix=self.ik_matrix)
|
|
else:
|
|
put_bone(self.obj, name, None, matrix=self.ik_matrix)
|
|
return name
|
|
|
|
def build_ik_pivot(self, ik_name, **args):
|
|
heel_bone = self.get_bone(self.bones.org.heel)
|
|
args = {
|
|
'position': (heel_bone.head + heel_bone.tail)/2,
|
|
**args
|
|
}
|
|
return super().build_ik_pivot(ik_name, **args)
|
|
|
|
def register_switch_parents(self, pbuilder):
|
|
super().register_switch_parents(pbuilder)
|
|
|
|
pbuilder.register_parent(self, self.bones.org.main[2], exclude_self=True, tags={'limb_end'})
|
|
|
|
def make_ik_ctrl_widget(self, ctrl):
|
|
obj = create_foot_widget(self.obj, ctrl)
|
|
|
|
if self.pivot_type != 'TOE':
|
|
ctrl = self.get_bone(ctrl)
|
|
org = self.get_bone(self.bones.org.main[2])
|
|
offset = org.tail - (ctrl.custom_shape_transform or ctrl).head
|
|
adjust_widget_transform_mesh(obj, Matrix.Translation(offset))
|
|
|
|
####################################################
|
|
# 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
|
|
|
|
@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'))
|
|
put_bone(self.obj, name, None, matrix=self.roll_matrix, scale=0.5)
|
|
self.bones.ctrl.heel = name
|
|
|
|
@stage.parent_bones
|
|
def parent_heel_control_bone(self):
|
|
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
|
|
bone.rotation_mode = self.heel_euler_order
|
|
bone.lock_scale = True, True, True
|
|
|
|
@stage.generate_widgets
|
|
def generate_heel_control_widget(self):
|
|
create_ballsocket_widget(self.obj, self.bones.ctrl.heel)
|
|
|
|
####################################################
|
|
# IK toe control
|
|
|
|
@stage.generate_bones
|
|
def make_ik_toe_control(self):
|
|
if self.use_ik_toe:
|
|
toe = self.bones.org.main[3]
|
|
self.bones.ctrl.ik_toe = self.make_ik_toe_control_bone(toe)
|
|
self.bones.mch.ik_toe_parent = self.make_ik_toe_parent_mch_bone(toe)
|
|
|
|
def make_ik_toe_control_bone(self, org):
|
|
return self.copy_bone(org, make_derived_name(org, 'ctrl', '_ik'))
|
|
|
|
def make_ik_toe_parent_mch_bone(self, org):
|
|
return self.copy_bone(org, make_derived_name(org, 'mch', '_ik_parent'), scale=1/3)
|
|
|
|
@stage.parent_bones
|
|
def parent_ik_toe_control(self):
|
|
if self.use_ik_toe:
|
|
mch = self.bones.mch
|
|
align_bone_orientation(self.obj, mch.ik_toe_parent, self.get_mch_heel_toe_output())
|
|
|
|
self.set_bone_parent(mch.ik_toe_parent, mch.ik_target, use_connect=True)
|
|
self.set_bone_parent(self.bones.ctrl.ik_toe, mch.ik_toe_parent)
|
|
|
|
@stage.configure_bones
|
|
def configure_ik_toe_control(self):
|
|
if self.use_ik_toe:
|
|
self.copy_bone_properties(self.bones.org.main[3], self.bones.ctrl.ik_toe, props=False)
|
|
|
|
@stage.rig_bones
|
|
def rig_ik_toe_control(self):
|
|
if self.use_ik_toe:
|
|
self.make_constraint(self.bones.mch.ik_toe_parent, 'COPY_TRANSFORMS', self.get_mch_heel_toe_output())
|
|
|
|
@stage.generate_widgets
|
|
def make_ik_toe_control_widget(self):
|
|
if self.use_ik_toe:
|
|
self.make_fk_control_widget(3, self.bones.ctrl.ik_toe)
|
|
|
|
####################################################
|
|
# Heel roll MCH
|
|
|
|
def get_mch_heel_toe_output(self):
|
|
return self.bones.mch.heel[-3]
|
|
|
|
@stage.generate_bones
|
|
def make_roll_mch_chain(self):
|
|
orgs = self.bones.org.main
|
|
self.bones.mch.heel = self.make_roll_mch_bones(orgs[2], orgs[3], self.bones.org.heel)
|
|
|
|
def make_roll_mch_bones(self, foot, toe, heel):
|
|
foot_bone = self.get_bone(foot)
|
|
heel_bone = self.get_bone(heel)
|
|
|
|
heel_middle = (heel_bone.head + heel_bone.tail) / 2
|
|
|
|
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'))
|
|
rock2 = self.copy_bone(heel, make_derived_name(heel, 'mch', '_rock2'))
|
|
|
|
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, result ]
|
|
|
|
@stage.parent_bones
|
|
def parent_roll_mch_chain(self):
|
|
chain = self.bones.mch.heel
|
|
self.set_bone_parent(chain[0], self.get_ik_pivot_output())
|
|
self.parent_bone_chain(chain)
|
|
|
|
@stage.rig_bones
|
|
def rig_roll_mch_chain(self):
|
|
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, result = chain
|
|
|
|
# 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(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(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',
|
|
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', max_y=DEG_360, space='LOCAL')
|
|
self.make_constraint(rock2, 'LIMIT_ROTATION', min_y=-DEG_360, space='LOCAL')
|
|
|
|
|
|
####################################################
|
|
# FK parents MCH chain
|
|
|
|
def parent_fk_parent_bone(self, i, parent_mch, prev_ctrl, org, prev_org):
|
|
if i == 3:
|
|
if not self.use_ik_toe:
|
|
align_bone_orientation(self.obj, parent_mch, self.get_mch_heel_toe_output())
|
|
|
|
self.set_bone_parent(parent_mch, prev_org, use_connect=True)
|
|
else:
|
|
self.set_bone_parent(parent_mch, prev_ctrl, use_connect=True, inherit_scale='ALIGNED')
|
|
|
|
else:
|
|
super().parent_fk_parent_bone(i, parent_mch, prev_ctrl, org, prev_org)
|
|
|
|
def rig_fk_parent_bone(self, i, parent_mch, org):
|
|
if i == 3:
|
|
if not self.use_ik_toe:
|
|
con = self.make_constraint(parent_mch, 'COPY_TRANSFORMS', self.get_mch_heel_toe_output())
|
|
|
|
self.make_driver(con, 'influence', variables=[(self.prop_bone, 'IK_FK')], polynomial=[1.0, -1.0])
|
|
|
|
else:
|
|
super().rig_fk_parent_bone(i, parent_mch, org)
|
|
|
|
####################################################
|
|
# IK system MCH
|
|
|
|
def get_ik_input_bone(self):
|
|
return self.bones.mch.heel[-1]
|
|
|
|
@stage.parent_bones
|
|
def parent_ik_mch_chain(self):
|
|
super().parent_ik_mch_chain()
|
|
|
|
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'
|
|
)
|
|
|
|
params.extra_ik_toe = bpy.props.BoolProperty(
|
|
name='Separate IK Toe',
|
|
default=False,
|
|
description="Generate a separate IK toe control for better IK/FK snapping"
|
|
)
|
|
|
|
|
|
@classmethod
|
|
def parameters_ui(self, layout, params):
|
|
layout.prop(params, 'foot_pivot_type')
|
|
layout.prop(params, 'extra_ik_toe')
|
|
|
|
super().parameters_ui(layout, params, 'Foot')
|
|
|
|
|
|
def create_sample(obj):
|
|
# generated by rigify.utils.write_metarig
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
arm = obj.data
|
|
|
|
bones = {}
|
|
|
|
bone = arm.edit_bones.new('thigh.L')
|
|
bone.head[:] = 0.0980, 0.0124, 1.0720
|
|
bone.tail[:] = 0.0980, -0.0286, 0.5372
|
|
bone.roll = 0.0000
|
|
bone.use_connect = False
|
|
bones['thigh.L'] = bone.name
|
|
bone = arm.edit_bones.new('shin.L')
|
|
bone.head[:] = 0.0980, -0.0286, 0.5372
|
|
bone.tail[:] = 0.0980, 0.0162, 0.0852
|
|
bone.roll = 0.0000
|
|
bone.use_connect = True
|
|
bone.parent = arm.edit_bones[bones['thigh.L']]
|
|
bones['shin.L'] = bone.name
|
|
bone = arm.edit_bones.new('foot.L')
|
|
bone.head[:] = 0.0980, 0.0162, 0.0852
|
|
bone.tail[:] = 0.0980, -0.0934, 0.0167
|
|
bone.roll = 0.0000
|
|
bone.use_connect = True
|
|
bone.parent = arm.edit_bones[bones['shin.L']]
|
|
bones['foot.L'] = bone.name
|
|
bone = arm.edit_bones.new('toe.L')
|
|
bone.head[:] = 0.0980, -0.0934, 0.0167
|
|
bone.tail[:] = 0.0980, -0.1606, 0.0167
|
|
bone.roll = -0.0000
|
|
bone.use_connect = True
|
|
bone.parent = arm.edit_bones[bones['foot.L']]
|
|
bones['toe.L'] = bone.name
|
|
bone = arm.edit_bones.new('heel.02.L')
|
|
bone.head[:] = 0.0600, 0.0459, 0.0000
|
|
bone.tail[:] = 0.1400, 0.0459, 0.0000
|
|
bone.roll = 0.0000
|
|
bone.use_connect = False
|
|
bone.parent = arm.edit_bones[bones['foot.L']]
|
|
bones['heel.02.L'] = bone.name
|
|
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
pbone = obj.pose.bones[bones['thigh.L']]
|
|
pbone.rigify_type = 'limbs.leg'
|
|
pbone.lock_location = (False, False, False)
|
|
pbone.lock_rotation = (False, False, False)
|
|
pbone.lock_rotation_w = False
|
|
pbone.lock_scale = (False, False, False)
|
|
pbone.rotation_mode = 'QUATERNION'
|
|
try:
|
|
pbone.rigify_parameters.separate_ik_layers = True
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
pbone.rigify_parameters.ik_layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
pbone.rigify_parameters.separate_hose_layers = True
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
pbone.rigify_parameters.hose_layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
pbone.rigify_parameters.limb_type = "leg"
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
pbone.rigify_parameters.fk_layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
pbone.rigify_parameters.tweak_layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
pbone.rigify_parameters.extra_ik_toe = True
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
pbone.rigify_parameters.ik_local_location = False
|
|
except AttributeError:
|
|
pass
|
|
pbone = obj.pose.bones[bones['shin.L']]
|
|
pbone.rigify_type = ''
|
|
pbone.lock_location = (False, False, False)
|
|
pbone.lock_rotation = (False, False, False)
|
|
pbone.lock_rotation_w = False
|
|
pbone.lock_scale = (False, False, False)
|
|
pbone.rotation_mode = 'QUATERNION'
|
|
pbone = obj.pose.bones[bones['foot.L']]
|
|
pbone.rigify_type = ''
|
|
pbone.lock_location = (False, False, False)
|
|
pbone.lock_rotation = (False, False, False)
|
|
pbone.lock_rotation_w = False
|
|
pbone.lock_scale = (False, False, False)
|
|
pbone.rotation_mode = 'QUATERNION'
|
|
pbone = obj.pose.bones[bones['toe.L']]
|
|
pbone.rigify_type = ''
|
|
pbone.lock_location = (False, False, False)
|
|
pbone.lock_rotation = (False, False, False)
|
|
pbone.lock_rotation_w = False
|
|
pbone.lock_scale = (False, False, False)
|
|
pbone.rotation_mode = 'QUATERNION'
|
|
pbone = obj.pose.bones[bones['heel.02.L']]
|
|
pbone.rigify_type = ''
|
|
pbone.lock_location = (False, False, False)
|
|
pbone.lock_rotation = (False, False, False)
|
|
pbone.lock_rotation_w = False
|
|
pbone.lock_scale = (False, False, False)
|
|
pbone.rotation_mode = 'QUATERNION'
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
for bone in arm.edit_bones:
|
|
bone.select = False
|
|
bone.select_head = False
|
|
bone.select_tail = False
|
|
for b in bones:
|
|
bone = arm.edit_bones[bones[b]]
|
|
bone.select = True
|
|
bone.select_head = True
|
|
bone.select_tail = True
|
|
arm.edit_bones.active = bone
|
|
|
|
for eb in arm.edit_bones:
|
|
eb.layers = (False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False)
|
|
|
|
arm.layers = (False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False)
|
|
|
|
return bones
|