# SPDX-License-Identifier: GPL-2.0-or-later import bpy from ...utils.bones import align_bone_roll, put_bone, copy_bone_position, flip_bone from ...utils.naming import make_derived_name from ...utils.misc import map_list from itertools import count from ...base_rig import stage from .limb_rigs import BaseLimbRig from .paw import Rig as pawRig class Rig(pawRig): """Front paw rig with special IK automation.""" #################################################### # EXTRA BONES # # mch: # ik2_chain[2] # Second IK system (pre-driving heel) # heel_track # Bone tracking IK2 to rotate heel # heel_parent # Parent of the heel control # #################################################### #################################################### # IK controls def get_middle_ik_controls(self): return [self.bones.ctrl.heel] def get_ik_fk_position_chains(self): ik_chain, tail_chain, fk_chain = super().get_ik_fk_position_chains() assert not tail_chain if not self.use_heel2: return [*ik_chain, ik_chain[-1]], [], [*fk_chain, fk_chain[-1]] return ik_chain, tail_chain, fk_chain def get_extra_ik_controls(self): extra = [self.bones.ctrl.heel2] if self.use_heel2 else [] return BaseLimbRig.get_extra_ik_controls(self) + extra def get_ik_pole_parents(self): return [(self.get_ik2_target_bone(), self.bones.ctrl.ik)] #################################################### # Second IK system (pre-driving heel) use_mch_ik_base = True def get_ik2_target_bone(self): return self.bones.mch.ik2_target if self.use_heel2 else self.bones.mch.toe_socket @stage.generate_bones def make_ik2_mch_chain(self): orgs = self.bones.org.main chain = map_list(self.make_ik2_mch_bone, count(0), orgs[0:2]) self.bones.mch.ik2_chain = chain if self.use_heel2: self.bones.mch.ik2_target = self.make_ik2_mch_target_bone(orgs) # Connect the chain end to the target self.get_bone(chain[1]).tail = self.get_bone(orgs[2]).tail align_bone_roll(self.obj, chain[1], orgs[1]) 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) def make_ik2_mch_bone(self, i, org): return self.copy_bone(org, make_derived_name(org, 'mch', '_ik2')) @stage.parent_bones def parent_ik2_mch_chain(self): mch = self.bones.mch if self.use_heel2: self.set_bone_parent(mch.ik2_target, self.bones.ctrl.heel2) 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) @stage.configure_bones def configure_ik2_mch_chain(self): for i, mch in enumerate(self.bones.mch.ik2_chain): self.configure_ik2_mch_bone(i, mch) def configure_ik2_mch_bone(self, i, mch): bone = self.get_bone(mch) bone.ik_stretch = 0.1 if i == 1: bone.lock_ik_x = bone.lock_ik_y = bone.lock_ik_z = True setattr(bone, 'lock_ik_' + self.main_axis, False) @stage.rig_bones def rig_ik2_mch_chain(self): target_bone = self.get_ik2_target_bone() self.rig_ik_mch_end_bone(self.bones.mch.ik2_chain[-1], target_bone, self.bones.ctrl.ik_pole) #################################################### # Heel tracking from IK2 @stage.generate_bones def make_heel_track_bones(self): orgs = self.bones.org.main mch = self.bones.mch mch.heel_track = self.copy_bone(orgs[2], make_derived_name(orgs[2], 'mch', '_track')) mch.heel_parent = self.copy_bone(orgs[2], make_derived_name(orgs[2], 'mch', '_parent')) # This two bone setup is used to move the damped track singularity out # of the way to a forbidden zone of the rig, and thus avoid flipping. # The bones are aligned to the center of the valid transformation zone. self.align_ik_control_bone(mch.heel_track) put_bone(self.obj, mch.heel_track, self.get_bone(orgs[2]).tail, scale=1/3) copy_bone_position(self.obj, mch.heel_track, mch.heel_parent, scale=3/4) @stage.parent_bones def parent_heel_control_bone(self): self.set_bone_parent(self.bones.ctrl.heel, self.bones.mch.heel_parent) @stage.parent_bones def parent_heel_track_bones(self): # Parenting heel_parent deferred to apply_bones. self.set_bone_parent(self.bones.mch.heel_track, self.get_ik2_target_bone()) @stage.configure_bones def prerig_heel_track_bones(self): # Assign the constraint before the apply stage. self.make_constraint( self.bones.mch.heel_track, 'DAMPED_TRACK', self.bones.mch.ik2_chain[1], influence=self.params.front_paw_heel_influence ) @stage.preapply_bones def preapply_heel_track_bones(self): # Assign local transform negating the effect of the constraint at rest. track_bone = self.get_bone(self.bones.mch.heel_track) bone = self.get_bone(self.bones.mch.heel_parent) bone.matrix_basis = track_bone.matrix.inverted() @ bone.matrix @stage.apply_bones def apply_heel_track_bones(self): # Complete the parent chain. self.set_bone_parent(self.bones.mch.heel_parent, self.bones.mch.heel_track) #################################################### # Settings @classmethod def add_parameters(self, params): super().add_parameters(params) params.front_paw_heel_influence = bpy.props.FloatProperty( name = 'Heel IK Influence', default = 0.8, min = 0, max = 1, description = 'Influence of the secondary IK on the heel control rotation' ) @classmethod def parameters_ui(self, layout, params): r = layout.row() r.prop(params, "front_paw_heel_influence", slider=True) super().parameters_ui(layout, params) 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('front_thigh.L') bone.head = 0.0000, 0.0000, 0.6902 bone.tail = 0.0000, 0.0916, 0.4418 bone.roll = 0.0000 bone.use_connect = False bones['front_thigh.L'] = bone.name bone = arm.edit_bones.new('front_shin.L') bone.head = 0.0000, 0.0916, 0.4418 bone.tail = 0.0000, 0.1014, 0.1698 bone.roll = 0.0000 bone.use_connect = True bone.parent = arm.edit_bones[bones['front_thigh.L']] bones['front_shin.L'] = bone.name bone = arm.edit_bones.new('front_foot.L') bone.head = 0.0000, 0.1014, 0.1698 bone.tail = 0.0000, 0.0699, 0.0411 bone.roll = 0.0000 bone.use_connect = True bone.parent = arm.edit_bones[bones['front_shin.L']] bones['front_foot.L'] = bone.name bone = arm.edit_bones.new('front_toe.L') bone.head = 0.0000, 0.0699, 0.0411 bone.tail = 0.0000, -0.0540, 0.0000 bone.roll = 3.1416 bone.use_connect = True bone.parent = arm.edit_bones[bones['front_foot.L']] bones['front_toe.L'] = bone.name bpy.ops.object.mode_set(mode='OBJECT') pbone = obj.pose.bones[bones['front_thigh.L']] pbone.rigify_type = 'limbs.front_paw' 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.limb_type = "paw" except AttributeError: pass try: pbone.rigify_parameters.fk_layers = [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, False, False, False, False, False] except AttributeError: pass try: pbone.rigify_parameters.tweak_layers = [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, False, False, False, False] except AttributeError: pass pbone = obj.pose.bones[bones['front_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['front_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['front_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' try: pbone.rigify_parameters.limb_type = "paw" except AttributeError: pass 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 bone.bbone_x = bone.bbone_z = bone.length * 0.05 arm.edit_bones.active = bone return bones