import bpy
import math
from bpy.props import (StringProperty, FloatProperty, BoolProperty, EnumProperty, PointerProperty)
from . import (Base_Functions, Pose_Functions_Rigging, Pose_Properties_Rigging)
class JK_OT_Add_Head_Controls(bpy.types.Operator):
"""Adds head tracking controls"""
bl_idname = "jk.add_head_controls"
bl_label = "Add Head Tracking"
Props: PointerProperty(type=Pose_Properties_Rigging.JK_MMT_Head_Tracking_Props)
def execute(self, context):
self.Props.Stretch = "HT_STRETCH" + self.Props.name[2:]
self.Props.Target = "HT" + self.Props.name[2:]
axes = (True, False, False) if "X" in self.Props.Axis else (False, True, False) if "Y" in self.Props.Axis else (False, False, True)
distance = (0 - self.Props.Distance) if "NEGATIVE" in self.Props.Axis else self.Props.Distance
vector = (distance, 0, 0 ) if "X" in self.Props.Axis else (0, distance, 0) if "Y" in self.Props.Axis else (0, 0, distance)
rig = bpy.context.object
# deselect any selected pose bones...
bpy.ops.pose.select_all(action='DESELECT')
# loop through all the bones to track and select them...
for bone in [self.Props.name, self.Props.Neck, self.Props.Spine]:
p_bone = rig.pose.bones[bone]
p_bone.bone.select = True
# go to edit mode and duplicate them...
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.armature.duplicate(do_flip_names=False)
# rename the duplicates...
for e_bone in bpy.context.selected_bones:
e_bone.name = "GB" + e_bone.name[2:-4]
e_bone.layers, e_bone.use_deform = [False]*23+[True]+[False]*8, False
# get a reference to the head bone...
h_bone = rig.data.edit_bones["GB" + self.Props.name[2:]]
h_bone.use_inherit_scale = False
# add and create the target bone...
t_bone = rig.data.edit_bones.new(self.Props.Target)
t_bone.head, t_bone.tail, t_bone.roll = h_bone.head, h_bone.tail, h_bone.roll
bpy.ops.armature.select_all(action='DESELECT')
t_bone.select_tail, t_bone.select_head = True, True
bpy.ops.transform.translate(value=vector, orient_type='NORMAL', constraint_axis=(axes[0], axes[1], axes[2]))
if h_bone.parent.parent.parent != None:
t_bone.parent = h_bone.parent.parent.parent #if h_bone.parent.parent.parent != None else None
else:
t_bone.parent = None
t_bone.roll, t_bone.layers, t_bone.use_deform = 0, [False]*1+[True]+[False]*30, False
# add and set the stretch bone...
s_bone = rig.data.edit_bones.new(self.Props.Stretch)
s_bone.head, s_bone.tail, s_bone.roll = h_bone.head, t_bone.head, 0
s_bone.parent = rig.data.edit_bones["GB" + self.Props.Neck[2:]]
s_bone.layers, s_bone.use_deform = [False]*1+[True]+[False]*30, False
# re-parent the head gizmo bone...
h_bone.parent = s_bone
# back to pose mode to set up constraints and stuff...
bpy.ops.object.mode_set(mode='POSE')
# target just needs group and shape...
p_bone = rig.pose.bones[self.Props.Target]
p_bone.custom_shape = bpy.data.objects["B_Shape_Sphere"]
p_bone.custom_shape_scale = 0.5
p_bone.bone_group = rig.pose.bone_groups["IK Targets"]
# stretch bone hase IK contraint...
p_bone = rig.pose.bones[self.Props.Stretch]
p_bone.custom_shape, p_bone.custom_shape_scale = bpy.data.objects["B_Shape_Circle_Limbs"], 1
p_bone.bone_group, p_bone.ik_stretch = rig.pose.bone_groups["IK Targets"], 1
ik = p_bone.constraints.new('IK')
ik.name, ik.show_expanded = "Head Tracking - IK", False
ik.target, ik.subtarget, ik.chain_count = rig, "HT" + self.Props.name[2:], 3
# set up the control bones...
for bone in [self.Props.name, self.Props.Neck, self.Props.Spine]:
# set the gizmo bone...
p_bone = rig.pose.bones["GB" + bone[2:]]
p_bone.custom_shape, p_bone.bone_group = None, rig.pose.bone_groups["Gizmo Bones"]
p_bone.ik_stretch, p_bone.ik_stiffness_x, p_bone.ik_stiffness_y, p_bone.ik_stiffness_z = 0.5, 0, 0, 0
# set the control bone...
p_bone = rig.pose.bones[bone]
ik = p_bone.constraints.new('IK')
ik.name, ik.show_expanded, ik.target, ik.chain_count = "Head Tracking - IK", False, rig, 1
# set the right target bone...
ik.subtarget = "GB" + self.Props.Neck[2:] if bone == self.Props.Spine else "GB" + self.Props.name[2:]
# head just uses rotation based IK... (could be a damped track with a limit rotation but i prefer IK)
if bone == self.Props.name:
ik.use_location, ik.use_rotation = False, True
# neck and spine also have copy Y rotation constraints...
else:
copy_rot = p_bone.constraints.new('COPY_ROTATION')
copy_rot.name, copy_rot.show_expanded = "Head Tracking - Copy Rotation", False
copy_rot.target, copy_rot.subtarget = rig, "GB" + bone[2:]
copy_rot.use_x, copy_rot.use_z = False, False
copy_rot.target_space, copy_rot.owner_space, copy_rot.mix_mode = 'LOCAL', 'LOCAL', 'BEFORE'
# turn on the IK limits for all three...
p_bone.use_ik_limit_x, p_bone.use_ik_limit_y, p_bone.use_ik_limit_z = True, True, True
# set the data entry...
if self.Props.name in rig.JK_MMT.Head_tracking_data:
data = rig.JK_MMT.Head_tracking_data[self.Props.name]
else:
data = rig.JK_MMT.Head_tracking_data.add()
data.name = self.Props.name
data.Target, data.Stretch, data.Neck, data.Spine = self.Props.Target, self.Props.Stretch, self.Props.Neck, self.Props.Spine
data.Forward_axis, data.Target_distance = self.Props.Axis, self.Props.Distance
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
if bpy.context.active_pose_bone != None:
active_bone = bpy.context.active_pose_bone
self.Props.name = active_bone.name
self.Props.Neck = active_bone.parent.name
self.Props.Spine = active_bone.parent.parent.name
return wm.invoke_props_dialog(self)
def draw(self, context):
armature = bpy.context.object
layout = self.layout
layout.ui_units_x = 20
row = layout.row()
label_col = row.column()
label_col.ui_units_x = 7
label_col.label(text="Head Bone:")
label_col.label(text="Neck Bone:")
label_col.label(text="Spine Bone:")
label_col.label(text="Target Orientation:")
prop_col = row.column()
prop_col.prop_search(self.Props, "name", armature.pose, "bones", text="")
prop_col.prop_search(self.Props, "Neck", armature.pose, "bones", text="")
prop_col.prop_search(self.Props, "Spine", armature.pose, "bones", text="")
row = prop_col.row()
row.prop(self.Props, "Axis", text="")
row.prop(self.Props, "Distance", text="Distance")
class JK_OT_Remove_Head_Controls(bpy.types.Operator):
"""Removes head tracking controls"""
bl_idname = "jk.remove_head_controls"
bl_label = "Remove Head Tracking"
Name: StringProperty(
name="Name",
description="The name of the head tracking to be removed",
default="",
maxlen=1024,
)
def execute(self, context):
armature = bpy.context.object
data = armature.JK_MMT.Head_tracking_data
props = data[self.Name]
for bone in [props.name, props.Neck, props.Spine]:
p_bone = armature.pose.bones[bone]
p_bone.constraints["Head Tra