# SPDX-License-Identifier: GPL-2.0-or-later import bpy from itertools import count from ...utils.naming import make_derived_name from ...utils.bones import align_bone_orientation from ...utils.widgets_basic import create_circle_widget from ...utils.widgets_special import create_neck_bend_widget, create_neck_tweak_widget from ...utils.switch_parent import SwitchParentBuilder from ...utils.misc import map_list from ...base_rig import stage from .spine_rigs import BaseHeadTailRig class Rig(BaseHeadTailRig): """ Head rig with long neck support and connect option. """ use_connect_reverse = False min_chain_length = 1 def initialize(self): super().initialize() self.long_neck = len(self.bones.org) > 3 self.has_neck = len(self.bones.org) > 1 #################################################### # BONES # # org[]: # ORG bones # ctrl: # neck, head, neck_bend: # Main controls. # tweak[]: # Tweak control chain. # mch: # rot_neck, rot_head: # Main control parents, implement FK follow. # stretch # Long neck stretch behavior. # ik[] # Long neck IK behavior. # chain[] # Tweak parents. # deform[]: # DEF bones # #################################################### #################################################### # Main control bones @stage.generate_bones def make_control_chain(self): orgs = self.bones.org ctrl = self.bones.ctrl if self.has_neck: ctrl.neck = self.make_neck_control_bone(orgs[0], 'neck', orgs[-1]) ctrl.head = self.make_head_control_bone(orgs[-1], 'head') if self.long_neck: ctrl.neck_bend = self.make_neck_bend_control_bone(orgs[0], 'neck_bend', ctrl.neck) self.default_prop_bone = ctrl.head def make_neck_control_bone(self, org, name, org_head): name = self.copy_bone(org, name, parent=False) # Neck spans all neck bones (except head) self.get_bone(name).tail = self.get_bone(org_head).head return name def make_neck_bend_control_bone(self, org, name, neck): name = self.copy_bone(org, name, parent=False) neck_bend_eb = self.get_bone(name) # Neck pivot position neck_bones = self.bones.org if (len(neck_bones)-1) % 2: # odd num of neck bones (head excluded) center_bone = self.get_bone(neck_bones[int((len(neck_bones))/2) - 1]) neck_bend_eb.head = (center_bone.head + center_bone.tail)/2 else: center_bone = self.get_bone(neck_bones[int((len(neck_bones)-1)/2) - 1]) neck_bend_eb.head = center_bone.tail align_bone_orientation(self.obj, name, neck) neck_bend_eb.length = self.get_bone(neck).length / 2 return name def make_head_control_bone(self, org, name): return self.copy_bone(org, name, parent=False) @stage.parent_bones def parent_control_chain(self): ctrl = self.bones.ctrl mch = self.bones.mch if self.has_neck: self.set_bone_parent(ctrl.neck, mch.rot_neck) self.set_bone_parent(ctrl.head, mch.rot_head) if self.long_neck: self.set_bone_parent(ctrl.neck_bend, mch.stretch) @stage.configure_bones def configure_control_chain(self): if self.has_neck: self.configure_control_bone(0, self.bones.ctrl.neck, self.bones.org[0]) self.configure_control_bone(2, self.bones.ctrl.head, self.bones.org[-1]) if self.long_neck: self.configure_neck_bend_bone(self.bones.ctrl.neck_bend, self.bones.org[0]) def configure_neck_bend_bone(self, ctrl, org): bone = self.get_bone(ctrl) bone.lock_rotation = (True, True, True) bone.lock_rotation_w = True bone.lock_scale = (True, True, True) @stage.generate_widgets def make_control_widgets(self): ctrl = self.bones.ctrl if self.has_neck: self.make_neck_widget(ctrl.neck) self.make_head_widget(ctrl.head) if self.long_neck: self.make_neck_bend_widget(ctrl.neck_bend) def make_neck_widget(self, ctrl): radius = 1/max(1, len(self.bones.mch.chain)) create_circle_widget( self.obj, ctrl, radius=radius, head_tail=0.5, ) def make_neck_bend_widget(self, ctrl): radius = 1/max(1, len(self.bones.mch.chain)) create_neck_bend_widget( self.obj, ctrl, radius=radius/2, head_tail=0.0, ) def make_head_widget(self, ctrl): # place wgt @ middle of head bone for long necks if self.long_neck: head_tail = 0.5 else: head_tail = 1.0 create_circle_widget( self.obj, ctrl, radius = 0.5, head_tail = head_tail, with_line = False, ) #################################################### # MCH bones associated with main controls @stage.generate_bones def make_mch_control_bones(self): orgs = self.bones.org mch = self.bones.mch if self.has_neck: mch.rot_neck = self.make_mch_follow_bone(orgs[0], 'neck', 0.5, copy_scale=True) mch.stretch = self.make_mch_stretch_bone(orgs[0], 'STR-neck', orgs[-1]) mch.rot_head = self.make_mch_follow_bone(orgs[-1], 'head', 0.0, copy_scale=True) def make_mch_stretch_bone(self, org, name, org_head): name = self.copy_bone(org, make_derived_name(name, 'mch'), parent=False) self.get_bone(name).tail = self.get_bone(org_head).head return name @stage.parent_bones def parent_mch_control_bones(self): if self.has_neck: self.set_bone_parent(self.bones.mch.rot_neck, self.rig_parent_bone) self.set_bone_parent(self.bones.mch.rot_head, self.bones.ctrl.neck) self.set_bone_parent(self.bones.mch.stretch, self.bones.ctrl.neck) else: self.set_bone_parent(self.bones.mch.rot_head, self.rig_parent_bone) @stage.rig_bones def rig_mch_control_bones(self): if self.has_neck: self.rig_mch_stretch_bone(self.bones.mch.stretch, self.bones.ctrl.head) def rig_mch_stretch_bone(self, mch, head): self.make_constraint(mch, 'STRETCH_TO', head, keep_axis='SWING_Y') #################################################### # MCH IK chain for the long neck @stage.generate_bones def make_mch_ik_chain(self): orgs = self.bones.org if self.long_neck: self.bones.mch.ik = map_list(self.make_mch_ik_bone, orgs[0:-1]) def make_mch_ik_bone(self, org): return self.copy_bone(org, make_derived_name(org, 'mch', '_ik'), parent=False) @stage.parent_bones def parent_mch_ik_chain(self): if self.long_neck: ik = self.bones.mch.ik self.set_bone_parent(ik[0], self.bones.ctrl.tweak[0]) self.parent_bone_chain(ik, use_connect=True) @stage.rig_bones def rig_mch_ik_chain(self): if self.long_neck: ik = self.bones.mch.ik head = self.bones.ctrl.head for args in zip(count(0), ik): self.rig_mch_ik_bone(*args, len(ik), head) def rig_mch_ik_bone(self, i, mch, ik_len, head): if i == ik_len - 1: self.make_constraint(mch, 'IK', head, chain_count=ik_len) self.get_bone(mch).ik_stretch = 0.1 #################################################### # MCH chain for the middle of the neck @stage.generate_bones def make_mch_chain(self): orgs = self.bones.org self.bones.mch.chain = map_list(self.make_mch_bone, orgs[1:-1]) def make_mch_bone(self, org): return self.copy_bone(org, make_derived_name(org, 'mch'), parent=False, scale=1/4) @stage.parent_bones def align_mch_chain(self): for mch in self.bones.mch.chain: align_bone_orientation(self.obj, mch, self.bones.ctrl.neck) @stage.parent_bones def parent_mch_chain(self): mch = self.bones.mch for bone in mch.chain: self.set_bone_parent(bone, mch.stretch, inherit_scale='NONE') @stage.rig_bones def rig_mch_chain(self): chain = self.bones.mch.chain if self.long_neck: ik = self.bones.mch.ik for args in zip(count(0), chain, ik[1:]): self.rig_mch_bone_long(*args, len(chain)) else: for args in zip(count(0), chain): self.rig_mch_bone(*args, len(chain)) def rig_mch_bone_long(self, i, mch, ik, len_mch): ctrl = self.bones.ctrl self.make_constraint(mch, 'COPY_LOCATION', ik) step = 2/(len_mch+1) xval = (i+1)*step influence = 2*xval - xval**2 #parabolic influence of pivot self.make_constraint( mch, 'COPY_LOCATION', ctrl.neck_bend, influence=influence, use_offset=True, space='LOCAL' ) self.make_constraint(mch, 'COPY_SCALE', ctrl.neck) def rig_mch_bone(self, i, mch, len_mch): ctrl = self.bones.ctrl nfactor = float((i + 1) / (len_mch + 1)) self.make_constraint( mch, 'COPY_ROTATION', ctrl.head, influence=nfactor, space='LOCAL' ) self.make_constraint(mch, 'COPY_SCALE', ctrl.neck) #################################################### # Tweak bones @stage.generate_bones def make_tweak_chain(self): orgs = self.bones.org self.bones.ctrl.tweak = map_list(self.make_tweak_bone, count(0), orgs[0:-1]) if not self.has_neck: self.check_connect_tweak(orgs[0]) @stage.parent_bones def parent_tweak_chain(self): ctrl = self.bones.ctrl mch = self.bones.mch if self.has_neck: for args in zip(ctrl.tweak, [ctrl.neck, *mch.chain]): self.set_bone_parent(*args) elif self.connected_tweak: self.set_bone_parent(self.connected_tweak, ctrl.head) @stage.rig_bones def generate_neck_tweak_widget(self): # Generate the widget early to override connected parent if self.long_neck: bone = self.bones.ctrl.tweak[0] create_neck_tweak_widget(self.obj, bone, size=1.0) #################################################### # ORG and DEF bones @stage.generate_bones def register_parent_bones(self): rig = self.rigify_parent or self builder = SwitchParentBuilder(self.generator) builder.register_parent( self, self.bones.org[-1], name='Head', inject_into=rig, exclude_self=True, tags={'head'}, ) @stage.configure_bones def configure_bbone_chain(self): self.get_bone(self.bones.deform[-1]).bone.bbone_segments = 1 @stage.rig_bones def rig_org_chain(self): if self.has_neck: tweaks = self.bones.ctrl.tweak + [self.bones.ctrl.head] else: tweaks = [self.connected_tweak or self.bones.ctrl.head] for args in zip(count(0), self.bones.org, tweaks, tweaks[1:] + [None]): self.rig_org_bone(*args) def create_sample(obj, *, parent=None): # generated by rigify.utils.write_metarig bpy.ops.object.mode_set(mode='EDIT') arm = obj.data bones = {} bone = arm.edit_bones.new('neck') bone.head[:] = 0.0000, 0.0114, 1.6582 bone.tail[:] = 0.0000, -0.0130, 1.7197 bone.roll = 0.0000 bone.use_connect = False if parent: bone.parent = arm.edit_bones[parent] bones['neck'] = bone.name bone = arm.edit_bones.new('neck.001') bone.head[:] = 0.0000, -0.0130, 1.7197 bone.tail[:] = 0.0000, -0.0247, 1.7813 bone.roll = 0.0000 bone.use_connect = True bone.parent = arm.edit_bones[bones['neck']] bones['neck.001'] = bone.name bone = arm.edit_bones.new('head') bone.head[:] = 0.0000, -0.0247, 1.7813 bone.tail[:] = 0.0000, -0.0247, 1.9796 bone.roll = 0.0000 bone.use_connect = True bone.parent = arm.edit_bones[bones['neck.001']] bones['head'] = bone.name bpy.ops.object.mode_set(mode='OBJECT') pbone = obj.pose.bones[bones['neck']] pbone.rigify_type = 'spines.super_head' 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.connect_chain = bool(parent) except AttributeError: pass try: pbone.rigify_parameters.tweak_layers = [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, False, False, False, False] except AttributeError: pass pbone = obj.pose.bones[bones['neck.001']] 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['head']] 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