Eye tracking fixes
This commit is contained in:
@@ -177,7 +177,19 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
|||||||
('vrc.v_th', 'TH', 'Th as in "think"')
|
('vrc.v_th', 'TH', 'Th as in "think"')
|
||||||
],
|
],
|
||||||
update=lambda s, c: VisemePreview.update_preview(c)
|
update=lambda s, c: VisemePreview.update_preview(c)
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
eye_tracking_type: EnumProperty(
|
||||||
|
name=t("EyeTracking.type"),
|
||||||
|
description=t("EyeTracking.type_desc"),
|
||||||
|
items=[
|
||||||
|
('AV3', t("EyeTracking.type.av3"), t("EyeTracking.type.av3_desc")),
|
||||||
|
('SDK2', t("EyeTracking.type.sdk2"), t("EyeTracking.type.sdk2_desc"))
|
||||||
|
],
|
||||||
|
default='AV3'
|
||||||
|
)
|
||||||
|
|
||||||
eye_mode: EnumProperty(
|
eye_mode: EnumProperty(
|
||||||
name=t("EyeTracking.mode"),
|
name=t("EyeTracking.mode"),
|
||||||
items=[
|
items=[
|
||||||
|
|||||||
+180
-102
@@ -29,6 +29,177 @@ VALID_EYE_NAMES = {
|
|||||||
'right': ['RightEye', 'Eye_R', 'eye_R', 'eye.R', 'EyeRight', 'right_eye', 'r_eye']
|
'right': ['RightEye', 'Eye_R', 'eye_R', 'eye.R', 'EyeRight', 'right_eye', 'r_eye']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CreateEyesAV3Button(bpy.types.Operator):
|
||||||
|
"""Create eye tracking setup for VRChat Avatar 3.0"""
|
||||||
|
bl_idname = 'avatar_toolkit.create_eye_tracking_av3'
|
||||||
|
bl_label = t('EyeTracking.create.av3.label')
|
||||||
|
bl_description = t('EyeTracking.create.av3.desc')
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
mesh = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
if not toolkit.head or not toolkit.eye_left or not toolkit.eye_right:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
armature = get_active_armature(context)
|
||||||
|
|
||||||
|
with ProgressTracker(context, 100, "Creating AV3 Eye Tracking") as progress:
|
||||||
|
try:
|
||||||
|
context.view_layer.objects.active = armature
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
progress.step("Setting up bones")
|
||||||
|
|
||||||
|
# Set up bones
|
||||||
|
head = armature.data.edit_bones.get(toolkit.head)
|
||||||
|
old_eye_left = armature.data.edit_bones.get(toolkit.eye_left)
|
||||||
|
old_eye_right = armature.data.edit_bones.get(toolkit.eye_right)
|
||||||
|
|
||||||
|
# Store original names and transformations
|
||||||
|
left_name = old_eye_left.name
|
||||||
|
right_name = old_eye_right.name
|
||||||
|
left_matrix = old_eye_left.matrix.copy()
|
||||||
|
right_matrix = old_eye_right.matrix.copy()
|
||||||
|
left_length = old_eye_left.length
|
||||||
|
right_length = old_eye_right.length
|
||||||
|
|
||||||
|
# Unparent and remove original bones
|
||||||
|
old_eye_left.parent = None
|
||||||
|
old_eye_right.parent = None
|
||||||
|
armature.data.edit_bones.remove(old_eye_left)
|
||||||
|
armature.data.edit_bones.remove(old_eye_right)
|
||||||
|
|
||||||
|
# Create new eye bones with original names
|
||||||
|
new_left_eye = armature.data.edit_bones.new(left_name)
|
||||||
|
new_right_eye = armature.data.edit_bones.new(right_name)
|
||||||
|
|
||||||
|
# Parent them
|
||||||
|
new_left_eye.parent = head
|
||||||
|
new_right_eye.parent = head
|
||||||
|
|
||||||
|
# Calculate straight up orientation matrix
|
||||||
|
straight_up_matrix = mathutils.Matrix.Rotation(math.pi/2, 3, 'X')
|
||||||
|
|
||||||
|
# Apply rotation while preserving position
|
||||||
|
for eye_data in [(new_left_eye, left_matrix, left_length),
|
||||||
|
(new_right_eye, right_matrix, right_length)]:
|
||||||
|
new_eye, orig_matrix, length = eye_data
|
||||||
|
new_matrix = straight_up_matrix.to_4x4()
|
||||||
|
new_matrix.translation = orig_matrix.translation
|
||||||
|
new_eye.matrix = new_matrix
|
||||||
|
new_eye.length = length
|
||||||
|
|
||||||
|
# Disable mirroring to prevent unwanted behavior
|
||||||
|
armature.data.use_mirror_x = False
|
||||||
|
|
||||||
|
|
||||||
|
progress.step("Finalizing setup")
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
|
self.report({'INFO'}, t('EyeTracking.success'))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Eye tracking setup failed: {str(e)}")
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
class CreateEyesSDK2Button(bpy.types.Operator):
|
||||||
|
"""Create eye tracking setup for VRChat SDK2"""
|
||||||
|
bl_idname = 'avatar_toolkit.create_eye_tracking_sdk2'
|
||||||
|
bl_label = t('EyeTracking.create.sdk2.label')
|
||||||
|
bl_description = t('EyeTracking.create.sdk2.desc')
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
mesh = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
if not get_all_meshes(context):
|
||||||
|
return False
|
||||||
|
|
||||||
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
if not toolkit.head or not toolkit.eye_left or not toolkit.eye_right:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if toolkit.disable_eye_blinking and toolkit.disable_eye_movement:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
armature = get_active_armature(context)
|
||||||
|
|
||||||
|
with ProgressTracker(context, 100, "Creating SDK2 Eye Tracking") as progress:
|
||||||
|
# Validate setup
|
||||||
|
validator = EyeTrackingValidator()
|
||||||
|
is_valid, message = validator.validate_setup(context, toolkit.mesh_name_eye)
|
||||||
|
if not is_valid:
|
||||||
|
self.report({'ERROR'}, message)
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
try:
|
||||||
|
context.view_layer.objects.active = armature
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
progress.step("Setting up bones")
|
||||||
|
|
||||||
|
self.mesh = bpy.data.objects.get(toolkit.mesh_name_eye)
|
||||||
|
|
||||||
|
# Set up bones
|
||||||
|
head = armature.data.edit_bones.get(toolkit.head)
|
||||||
|
old_eye_left = armature.data.edit_bones.get(toolkit.eye_left)
|
||||||
|
old_eye_right = armature.data.edit_bones.get(toolkit.eye_right)
|
||||||
|
|
||||||
|
# Create new eye bones
|
||||||
|
new_left_eye = armature.data.edit_bones.new('LeftEye')
|
||||||
|
new_right_eye = armature.data.edit_bones.new('RightEye')
|
||||||
|
|
||||||
|
# Parent them
|
||||||
|
new_left_eye.parent = head
|
||||||
|
new_right_eye.parent = head
|
||||||
|
|
||||||
|
# Calculate positions for SDK2 style
|
||||||
|
fix_eye_position(context, old_eye_left, new_left_eye, head, False)
|
||||||
|
fix_eye_position(context, old_eye_right, new_right_eye, head, True)
|
||||||
|
|
||||||
|
progress.step("Processing vertex groups")
|
||||||
|
if not toolkit.disable_eye_movement:
|
||||||
|
# Switch to object mode for vertex group operations
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
self.mesh.select_set(True)
|
||||||
|
context.view_layer.objects.active = self.mesh
|
||||||
|
|
||||||
|
copy_vertex_group(self, old_eye_left.name, 'LeftEye')
|
||||||
|
copy_vertex_group(self, old_eye_right.name, 'RightEye')
|
||||||
|
|
||||||
|
# Return to armature edit mode
|
||||||
|
context.view_layer.objects.active = armature
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
|
||||||
|
progress.step("Processing shape keys")
|
||||||
|
if not toolkit.disable_eye_blinking:
|
||||||
|
shapes = [toolkit.wink_left, toolkit.wink_right,
|
||||||
|
toolkit.lowerlid_left, toolkit.lowerlid_right]
|
||||||
|
new_shapes = ['vrc.blink_left', 'vrc.blink_right',
|
||||||
|
'vrc.lowerlid_left', 'vrc.lowerlid_right']
|
||||||
|
|
||||||
|
progress.step("Finalizing setup")
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
toolkit.eye_mode = 'TESTING'
|
||||||
|
|
||||||
|
self.report({'INFO'}, t('EyeTracking.success'))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Eye tracking setup failed: {str(e)}")
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
class EyeTrackingBackup:
|
class EyeTrackingBackup:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.backup_path = os.path.join(bpy.app.tempdir, "eye_tracking_backup.json")
|
self.backup_path = os.path.join(bpy.app.tempdir, "eye_tracking_backup.json")
|
||||||
@@ -126,108 +297,6 @@ class EyeTrackingValidator:
|
|||||||
return False, t('EyeTracking.validation.missingBones', bones=', '.join(missing_bones))
|
return False, t('EyeTracking.validation.missingBones', bones=', '.join(missing_bones))
|
||||||
|
|
||||||
return True, t('EyeTracking.validation.success')
|
return True, t('EyeTracking.validation.success')
|
||||||
|
|
||||||
class CreateEyesButton(bpy.types.Operator):
|
|
||||||
bl_idname = 'avatar_toolkit.create_eye_tracking'
|
|
||||||
bl_label = t('EyeTracking.create.label')
|
|
||||||
bl_description = t('EyeTracking.create.desc')
|
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
|
||||||
|
|
||||||
mesh = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
if not get_all_meshes(context):
|
|
||||||
return False
|
|
||||||
|
|
||||||
toolkit = context.scene.avatar_toolkit
|
|
||||||
if not toolkit.head or not toolkit.eye_left or not toolkit.eye_right:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if toolkit.disable_eye_blinking and toolkit.disable_eye_movement:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
toolkit = context.scene.avatar_toolkit
|
|
||||||
armature = get_active_armature(context)
|
|
||||||
|
|
||||||
with ProgressTracker(context, 100, "Creating Eye Tracking") as progress:
|
|
||||||
# Validate setup
|
|
||||||
validator = EyeTrackingValidator()
|
|
||||||
is_valid, message = validator.validate_setup(context, toolkit.mesh_name_eye)
|
|
||||||
if not is_valid:
|
|
||||||
self.report({'ERROR'}, message)
|
|
||||||
return {'CANCELLED'}
|
|
||||||
|
|
||||||
# Create backup
|
|
||||||
backup = EyeTrackingBackup()
|
|
||||||
if not backup.store_bone_positions(armature):
|
|
||||||
logger.warning("Failed to create backup")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Set active object and mode
|
|
||||||
context.view_layer.objects.active = armature
|
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
|
||||||
progress.step("Setting up bones")
|
|
||||||
|
|
||||||
self.mesh = bpy.data.objects.get(toolkit.mesh_name_eye)
|
|
||||||
|
|
||||||
# Set up bones
|
|
||||||
head = armature.data.edit_bones.get(toolkit.head)
|
|
||||||
old_eye_left = armature.data.edit_bones.get(toolkit.eye_left)
|
|
||||||
old_eye_right = armature.data.edit_bones.get(toolkit.eye_right)
|
|
||||||
|
|
||||||
if not toolkit.disable_eye_blinking:
|
|
||||||
if not all([toolkit.wink_left, toolkit.wink_right,
|
|
||||||
toolkit.lowerlid_left, toolkit.lowerlid_right]):
|
|
||||||
self.report({'ERROR'}, t('EyeTracking.error.noShapeSelected'))
|
|
||||||
return {'CANCELLED'}
|
|
||||||
|
|
||||||
progress.step("Processing vertex groups")
|
|
||||||
|
|
||||||
# Create new eye bones
|
|
||||||
new_left_eye = armature.data.edit_bones.new('LeftEye')
|
|
||||||
new_right_eye = armature.data.edit_bones.new('RightEye')
|
|
||||||
|
|
||||||
# Parent them
|
|
||||||
new_left_eye.parent = head
|
|
||||||
new_right_eye.parent = head
|
|
||||||
|
|
||||||
# Calculate positions
|
|
||||||
fix_eye_position(context, old_eye_left, new_left_eye, head, False)
|
|
||||||
fix_eye_position(context, old_eye_right, new_right_eye, head, True)
|
|
||||||
|
|
||||||
progress.step("Processing shape keys")
|
|
||||||
|
|
||||||
# Process shape keys
|
|
||||||
if not toolkit.disable_eye_movement:
|
|
||||||
self.copy_vertex_group(old_eye_left.name, 'LeftEye')
|
|
||||||
self.copy_vertex_group(old_eye_right.name, 'RightEye')
|
|
||||||
|
|
||||||
# Handle shape keys
|
|
||||||
shapes = [toolkit.wink_left, toolkit.wink_right,
|
|
||||||
toolkit.lowerlid_left, toolkit.lowerlid_right]
|
|
||||||
new_shapes = ['vrc.blink_left', 'vrc.blink_right',
|
|
||||||
'vrc.lowerlid_left', 'vrc.lowerlid_right']
|
|
||||||
|
|
||||||
progress.step("Finalizing setup")
|
|
||||||
|
|
||||||
# Reset modes and cleanup
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
|
||||||
|
|
||||||
# Update scene properties
|
|
||||||
toolkit.eye_mode = 'TESTING'
|
|
||||||
|
|
||||||
self.report({'INFO'}, t('EyeTracking.success'))
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Eye tracking setup failed: {str(e)}")
|
|
||||||
if backup.restore_bone_positions(get_active_armature(context)):
|
|
||||||
logger.info("Restored from backup")
|
|
||||||
return {'CANCELLED'}
|
|
||||||
|
|
||||||
class StartTestingButton(bpy.types.Operator):
|
class StartTestingButton(bpy.types.Operator):
|
||||||
bl_idname = 'avatar_toolkit.start_eye_testing'
|
bl_idname = 'avatar_toolkit.start_eye_testing'
|
||||||
@@ -318,6 +387,8 @@ class StopTestingButton(bpy.types.Operator):
|
|||||||
eye_left_rot = []
|
eye_left_rot = []
|
||||||
eye_right_rot = []
|
eye_right_rot = []
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def set_rotation(self, context):
|
def set_rotation(self, context):
|
||||||
@@ -695,6 +766,12 @@ def vertex_group_exists(mesh_obj, group_name):
|
|||||||
def copy_vertex_group(self, vertex_group, rename_to):
|
def copy_vertex_group(self, vertex_group, rename_to):
|
||||||
"""Copy vertex group with new name"""
|
"""Copy vertex group with new name"""
|
||||||
vertex_group_index = 0
|
vertex_group_index = 0
|
||||||
|
# Select and make mesh active
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
self.mesh.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = self.mesh
|
||||||
|
|
||||||
for group in self.mesh.vertex_groups:
|
for group in self.mesh.vertex_groups:
|
||||||
if group.name == vertex_group:
|
if group.name == vertex_group:
|
||||||
self.mesh.vertex_groups.active_index = vertex_group_index
|
self.mesh.vertex_groups.active_index = vertex_group_index
|
||||||
@@ -703,6 +780,7 @@ def copy_vertex_group(self, vertex_group, rename_to):
|
|||||||
break
|
break
|
||||||
vertex_group_index += 1
|
vertex_group_index += 1
|
||||||
|
|
||||||
|
|
||||||
def copy_shape_key(self, context, from_shape, new_names, new_index):
|
def copy_shape_key(self, context, from_shape, new_names, new_index):
|
||||||
"""Copy shape key with new name"""
|
"""Copy shape key with new name"""
|
||||||
blinking = not context.scene.avatar_toolkit.disable_eye_blinking
|
blinking = not context.scene.avatar_toolkit.disable_eye_blinking
|
||||||
|
|||||||
@@ -303,6 +303,17 @@
|
|||||||
"EyeTracking.head_bone_desc": "Select head bone",
|
"EyeTracking.head_bone_desc": "Select head bone",
|
||||||
"EyeTracking.eye_left_desc": "Select left eye bone",
|
"EyeTracking.eye_left_desc": "Select left eye bone",
|
||||||
"EyeTracking.eye_right_desc": "Select right eye bone",
|
"EyeTracking.eye_right_desc": "Select right eye bone",
|
||||||
|
"EyeTracking.type": "Eye Tracking Type",
|
||||||
|
"EyeTracking.type_desc": "Select the type of eye tracking setup to create",
|
||||||
|
"EyeTracking.create.av3.label": "Create AV3 Eye Tracking",
|
||||||
|
"EyeTracking.create.av3.desc": "Set up eye tracking for VRChat Avatar 3.0",
|
||||||
|
"EyeTracking.create.sdk2.label": "Create SDK2 Eye Tracking",
|
||||||
|
"EyeTracking.create.sdk2.desc": "Set up eye tracking for VRChat SDK2",
|
||||||
|
"EyeTracking.sdk_version": "SDK Version",
|
||||||
|
"EyeTracking.type.av3": "Avatar 3.0",
|
||||||
|
"EyeTracking.type.av3_desc": "VRChat Avatar 3.0 eye tracking setup",
|
||||||
|
"EyeTracking.type.sdk2": "SDK2 (Legacy)",
|
||||||
|
"EyeTracking.type.sdk2_desc": "VRChat SDK2 eye tracking setup",
|
||||||
|
|
||||||
"Settings.label": "Settings",
|
"Settings.label": "Settings",
|
||||||
"Settings.language": "Language",
|
"Settings.language": "Language",
|
||||||
|
|||||||
+138
-94
@@ -5,7 +5,8 @@ from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
|||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from ..core.common import get_active_armature, get_all_meshes
|
from ..core.common import get_active_armature, get_all_meshes
|
||||||
from ..functions.eye_tracking import (
|
from ..functions.eye_tracking import (
|
||||||
CreateEyesButton,
|
CreateEyesAV3Button,
|
||||||
|
CreateEyesSDK2Button,
|
||||||
StartTestingButton,
|
StartTestingButton,
|
||||||
StopTestingButton,
|
StopTestingButton,
|
||||||
ResetRotationButton,
|
ResetRotationButton,
|
||||||
@@ -33,110 +34,153 @@ class AvatarToolKit_PT_EyeTrackingPanel(Panel):
|
|||||||
layout = self.layout
|
layout = self.layout
|
||||||
toolkit = context.scene.avatar_toolkit
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
|
||||||
# Mode Selection Box
|
# SDK Version Selection Box
|
||||||
mode_box = layout.box()
|
sdk_box = layout.box()
|
||||||
col = mode_box.column(align=True)
|
col = sdk_box.column(align=True)
|
||||||
col.label(text=t("EyeTracking.mode_select"), icon='TOOL_SETTINGS')
|
col.label(text=t("EyeTracking.sdk_version"), icon='PRESET')
|
||||||
col.separator(factor=0.5)
|
col.separator(factor=0.5)
|
||||||
col.prop(toolkit, "eye_mode", expand=True)
|
row = col.row(align=True)
|
||||||
|
row.prop(toolkit, "eye_tracking_type", expand=True)
|
||||||
|
|
||||||
if toolkit.eye_mode == 'CREATION':
|
if toolkit.eye_tracking_type == 'SDK2':
|
||||||
# Mesh Setup Box
|
# Mode Selection Box
|
||||||
mesh_box = layout.box()
|
mode_box = layout.box()
|
||||||
col = mesh_box.column(align=True)
|
col = mode_box.column(align=True)
|
||||||
col.label(text=t("EyeTracking.mesh_setup"), icon='MESH_DATA')
|
col.label(text=t("EyeTracking.setup"), icon='TOOL_SETTINGS')
|
||||||
col.separator(factor=0.5)
|
col.separator(factor=0.5)
|
||||||
col.prop(toolkit, "mesh_name_eye", text="")
|
col.prop(toolkit, "eye_mode", expand=True)
|
||||||
|
|
||||||
# Bone Setup Box
|
if toolkit.eye_mode == 'CREATION':
|
||||||
bone_box = layout.box()
|
self.draw_creation_mode(context, layout)
|
||||||
col = bone_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.bone_setup"), icon='BONE_DATA')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
|
|
||||||
armature = get_active_armature(context)
|
|
||||||
if armature:
|
|
||||||
col.prop_search(toolkit, "head", armature.data, "bones", text=t("EyeTracking.head_bone"))
|
|
||||||
col.prop_search(toolkit, "eye_left", armature.data, "bones", text=t("EyeTracking.eye_left"))
|
|
||||||
col.prop_search(toolkit, "eye_right", armature.data, "bones", text=t("EyeTracking.eye_right"))
|
|
||||||
else:
|
else:
|
||||||
col.label(text=t("EyeTracking.no_armature"), icon='ERROR')
|
self.draw_testing_mode(context, layout)
|
||||||
|
else:
|
||||||
|
# AV3 bone setup only
|
||||||
|
self.draw_av3_setup(context, layout)
|
||||||
|
|
||||||
# Shapekey Setup Box
|
def draw_av3_setup(self, context: Context, layout: UILayout) -> None:
|
||||||
shape_box = layout.box()
|
toolkit = context.scene.avatar_toolkit
|
||||||
col = shape_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.shapekey_setup"), icon='SHAPEKEY_DATA')
|
# Bone Setup Box
|
||||||
|
bone_box = layout.box()
|
||||||
|
col = bone_box.column(align=True)
|
||||||
|
col.label(text=t("EyeTracking.bone_setup"), icon='BONE_DATA')
|
||||||
|
col.separator(factor=0.5)
|
||||||
|
|
||||||
|
armature = get_active_armature(context)
|
||||||
|
if armature:
|
||||||
|
col.prop_search(toolkit, "head", armature.data, "bones", text=t("EyeTracking.head_bone"))
|
||||||
|
col.prop_search(toolkit, "eye_left", armature.data, "bones", text=t("EyeTracking.eye_left"))
|
||||||
|
col.prop_search(toolkit, "eye_right", armature.data, "bones", text=t("EyeTracking.eye_right"))
|
||||||
|
else:
|
||||||
|
col.label(text=t("EyeTracking.no_armature"), icon='ERROR')
|
||||||
|
|
||||||
|
# Create Button
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.scale_y = 1.5
|
||||||
|
row.operator(CreateEyesAV3Button.bl_idname, icon='PLAY')
|
||||||
|
|
||||||
|
def draw_creation_mode(self, context: Context, layout: UILayout) -> None:
|
||||||
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
|
||||||
|
# Bone Setup Box
|
||||||
|
bone_box = layout.box()
|
||||||
|
col = bone_box.column(align=True)
|
||||||
|
col.label(text=t("EyeTracking.bone_setup"), icon='BONE_DATA')
|
||||||
|
col.separator(factor=0.5)
|
||||||
|
|
||||||
|
armature = get_active_armature(context)
|
||||||
|
if armature:
|
||||||
|
col.prop_search(toolkit, "head", armature.data, "bones", text=t("EyeTracking.head_bone"))
|
||||||
|
col.prop_search(toolkit, "eye_left", armature.data, "bones", text=t("EyeTracking.eye_left"))
|
||||||
|
col.prop_search(toolkit, "eye_right", armature.data, "bones", text=t("EyeTracking.eye_right"))
|
||||||
|
else:
|
||||||
|
col.label(text=t("EyeTracking.no_armature"), icon='ERROR')
|
||||||
|
|
||||||
|
# Mesh Setup Box
|
||||||
|
mesh_box = layout.box()
|
||||||
|
col = mesh_box.column(align=True)
|
||||||
|
col.label(text=t("EyeTracking.mesh_setup"), icon='MESH_DATA')
|
||||||
|
col.separator(factor=0.5)
|
||||||
|
col.prop_search(toolkit, "mesh_name_eye", bpy.data, "objects", text="")
|
||||||
|
|
||||||
|
# Shape Key Setup Box
|
||||||
|
shape_box = layout.box()
|
||||||
|
col = shape_box.column(align=True)
|
||||||
|
col.label(text=t("EyeTracking.shapekey_setup"), icon='SHAPEKEY_DATA')
|
||||||
|
col.separator(factor=0.5)
|
||||||
|
|
||||||
|
mesh = bpy.data.objects.get(toolkit.mesh_name_eye)
|
||||||
|
if mesh and mesh.data.shape_keys:
|
||||||
|
col.prop_search(toolkit, "wink_left", mesh.data.shape_keys, "key_blocks", text=t("EyeTracking.wink_left"))
|
||||||
|
col.prop_search(toolkit, "wink_right", mesh.data.shape_keys, "key_blocks", text=t("EyeTracking.wink_right"))
|
||||||
|
col.prop_search(toolkit, "lowerlid_left", mesh.data.shape_keys, "key_blocks", text=t("EyeTracking.lowerlid_left"))
|
||||||
|
col.prop_search(toolkit, "lowerlid_right", mesh.data.shape_keys, "key_blocks", text=t("EyeTracking.lowerlid_right"))
|
||||||
|
else:
|
||||||
|
col.label(text=t("EyeTracking.no_shapekeys"), icon='ERROR')
|
||||||
|
|
||||||
|
# Options Box
|
||||||
|
options_box = layout.box()
|
||||||
|
col = options_box.column(align=True)
|
||||||
|
col.label(text=t("EyeTracking.options"), icon='SETTINGS')
|
||||||
|
col.separator(factor=0.5)
|
||||||
|
col.prop(toolkit, "disable_eye_blinking")
|
||||||
|
col.prop(toolkit, "disable_eye_movement")
|
||||||
|
if not toolkit.disable_eye_movement:
|
||||||
|
col.prop(toolkit, "eye_distance")
|
||||||
|
|
||||||
|
# Create Button
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.scale_y = 1.5
|
||||||
|
row.operator(CreateEyesSDK2Button.bl_idname, icon='PLAY')
|
||||||
|
|
||||||
|
def draw_testing_mode(self, context: Context, layout: UILayout) -> None:
|
||||||
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
|
||||||
|
if context.mode != 'POSE':
|
||||||
|
# Testing Start Box
|
||||||
|
test_box = layout.box()
|
||||||
|
col = test_box.column(align=True)
|
||||||
|
col.label(text=t("EyeTracking.testing"), icon='PLAY')
|
||||||
col.separator(factor=0.5)
|
col.separator(factor=0.5)
|
||||||
|
row = col.row(align=True)
|
||||||
mesh = bpy.data.objects.get(toolkit.mesh_name_eye)
|
row.scale_y = 1.5
|
||||||
if mesh and mesh.data.shape_keys:
|
row.operator(StartTestingButton.bl_idname, icon='PLAY')
|
||||||
col.prop_search(toolkit, "wink_left", mesh.data.shape_keys, "key_blocks", text=t("EyeTracking.wink_left"))
|
else:
|
||||||
col.prop_search(toolkit, "wink_right", mesh.data.shape_keys, "key_blocks", text=t("EyeTracking.wink_right"))
|
# Eye Rotation Box
|
||||||
col.prop_search(toolkit, "lowerlid_left", mesh.data.shape_keys, "key_blocks", text=t("EyeTracking.lowerlid_left"))
|
rotation_box = layout.box()
|
||||||
col.prop_search(toolkit, "lowerlid_right", mesh.data.shape_keys, "key_blocks", text=t("EyeTracking.lowerlid_right"))
|
col = rotation_box.column(align=True)
|
||||||
else:
|
col.label(text=t("EyeTracking.rotation_controls"), icon='DRIVER_ROTATIONAL_DIFFERENCE')
|
||||||
col.label(text=t("EyeTracking.no_shapekeys"), icon='ERROR')
|
|
||||||
|
|
||||||
# Options Box
|
|
||||||
options_box = layout.box()
|
|
||||||
col = options_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.options"), icon='SETTINGS')
|
|
||||||
col.separator(factor=0.5)
|
col.separator(factor=0.5)
|
||||||
col.prop(toolkit, "disable_eye_blinking")
|
col.prop(toolkit, "eye_rotation_x", text=t("EyeTracking.rotation.x"))
|
||||||
col.prop(toolkit, "disable_eye_movement")
|
col.prop(toolkit, "eye_rotation_y", text=t("EyeTracking.rotation.y"))
|
||||||
if not toolkit.disable_eye_movement:
|
col.operator(ResetRotationButton.bl_idname, icon='LOOP_BACK')
|
||||||
col.prop(toolkit, "eye_distance")
|
|
||||||
|
|
||||||
# Create Button
|
# Eye Adjustment Box
|
||||||
|
adjust_box = layout.box()
|
||||||
|
col = adjust_box.column(align=True)
|
||||||
|
col.label(text=t("EyeTracking.adjustments"), icon='MODIFIER')
|
||||||
|
col.separator(factor=0.5)
|
||||||
|
col.prop(toolkit, "eye_distance")
|
||||||
|
col.operator(AdjustEyesButton.bl_idname, icon='CON_TRACKTO')
|
||||||
|
|
||||||
|
# Blinking Test Box
|
||||||
|
blink_box = layout.box()
|
||||||
|
col = blink_box.column(align=True)
|
||||||
|
col.label(text=t("EyeTracking.blink_testing"), icon='HIDE_OFF')
|
||||||
|
col.separator(factor=0.5)
|
||||||
|
row = col.row(align=True)
|
||||||
|
row.prop(toolkit, "eye_blink_shape")
|
||||||
|
row.operator(TestBlinking.bl_idname, icon='RESTRICT_VIEW_OFF')
|
||||||
|
row = col.row(align=True)
|
||||||
|
row.prop(toolkit, "eye_lowerlid_shape")
|
||||||
|
row.operator(TestLowerlid.bl_idname, icon='RESTRICT_VIEW_OFF')
|
||||||
|
col.operator(ResetBlinkTest.bl_idname, icon='LOOP_BACK')
|
||||||
|
|
||||||
|
# Stop Testing Button
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
row.scale_y = 1.5
|
row.scale_y = 1.5
|
||||||
row.operator(CreateEyesButton.bl_idname, icon='PLAY')
|
row.operator(StopTestingButton.bl_idname, icon='PAUSE')
|
||||||
|
|
||||||
else:
|
|
||||||
if context.mode != 'POSE':
|
|
||||||
# Testing Start Box
|
|
||||||
test_box = layout.box()
|
|
||||||
col = test_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.testing"), icon='PLAY')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
row = col.row(align=True)
|
|
||||||
row.scale_y = 1.5
|
|
||||||
row.operator(StartTestingButton.bl_idname, icon='PLAY')
|
|
||||||
else:
|
|
||||||
# Eye Rotation Box
|
|
||||||
rotation_box = layout.box()
|
|
||||||
col = rotation_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.rotation_controls"), icon='DRIVER_ROTATIONAL_DIFFERENCE')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.prop(toolkit, "eye_rotation_x", text=t("EyeTracking.rotation.x"))
|
|
||||||
col.prop(toolkit, "eye_rotation_y", text=t("EyeTracking.rotation.y"))
|
|
||||||
col.operator(ResetRotationButton.bl_idname, icon='LOOP_BACK')
|
|
||||||
|
|
||||||
# Eye Adjustment Box
|
|
||||||
adjust_box = layout.box()
|
|
||||||
col = adjust_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.adjustments"), icon='MODIFIER')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.prop(toolkit, "eye_distance")
|
|
||||||
col.operator(AdjustEyesButton.bl_idname, icon='CON_TRACKTO')
|
|
||||||
|
|
||||||
# Blinking Test Box
|
|
||||||
blink_box = layout.box()
|
|
||||||
col = blink_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.blink_testing"), icon='HIDE_OFF')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
row = col.row(align=True)
|
|
||||||
row.prop(toolkit, "eye_blink_shape")
|
|
||||||
row.operator(TestBlinking.bl_idname, icon='RESTRICT_VIEW_OFF')
|
|
||||||
row = col.row(align=True)
|
|
||||||
row.prop(toolkit, "eye_lowerlid_shape")
|
|
||||||
row.operator(TestLowerlid.bl_idname, icon='RESTRICT_VIEW_OFF')
|
|
||||||
col.operator(ResetBlinkTest.bl_idname, icon='LOOP_BACK')
|
|
||||||
|
|
||||||
# Stop Testing Button
|
|
||||||
row = layout.row(align=True)
|
|
||||||
row.scale_y = 1.5
|
|
||||||
row.operator(StopTestingButton.bl_idname, icon='PAUSE')
|
|
||||||
|
|
||||||
# Reset Button
|
# Reset Button
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user