Added Eye tracking and Visemes

This commit is contained in:
Yusarina
2024-12-15 20:14:26 +00:00
parent f4dc74d091
commit 87a351cea4
13 changed files with 1807 additions and 39 deletions
+91
View File
@@ -485,3 +485,94 @@ def remove_unused_shapekeys(mesh_obj: Object, tolerance: float = 0.001) -> int:
removed_count += 1
return removed_count
def has_shapekeys(mesh_obj: Object) -> bool:
return mesh_obj.data.shape_keys is not None
# Identifier to indicate that an EnumProperty is empty
# This is the default identifier used when a wrapped items function returns an empty list
# This identifier needs to be something that should never normally be used, so as to avoid the possibility of
# conflicting with an enum value that exists.
_empty_enum_identifier = 'Cats_empty_enum_identifier'
# names - The first object will be the first one in the list. So the first one has to be the one that exists in the most models
# no_basis - If this is true the Basis will not be available in the list
def get_shapekeys(context, names, is_mouth, no_basis, return_list):
choices = []
choices_simple = []
meshes_list = get_meshes_objects(check=False)
if meshes_list:
if is_mouth:
meshes = [get_objects().get(context.scene.mesh_name_viseme)]
else:
meshes = [get_objects().get(context.scene.mesh_name_eye)]
else:
return choices
for mesh in meshes:
if not mesh or not has_shapekeys(mesh):
return choices
for shapekey in mesh.data.shape_keys.key_blocks:
name = shapekey.name
if name in choices_simple:
continue
if no_basis and name == 'Basis':
continue
# 1. Will be returned by context.scene
# 2. Will be shown in lists
# 3. will be shown in the hover description (below description)
choices.append((name, name, name))
choices_simple.append(name)
_sort_enum_choices_by_identifier_lower(choices)
choices2 = []
for name in names:
if name in choices_simple and len(choices) > 1 and choices[0][0] != name:
continue
choices2.append((name, name, name))
choices2.extend(choices)
if return_list:
shape_list = []
for choice in choices2:
shape_list.append(choice[0])
return shape_list
return choices2
# Default sorting for dynamic EnumProperty items
def _sort_enum_choices_by_identifier_lower(choices, in_place=True):
"""Sort a list of enum choices (items) by the lowercase of their identifier.
Sorting is performed in-place by default, but can be changed by setting in_place=False.
Returns the sorted list of enum choices."""
def identifier_lower(choice):
return choice[0].lower()
if in_place:
choices.sort(key=identifier_lower)
else:
choices = sorted(choices, key=identifier_lower)
return choices
def is_enum_empty(string):
"""Returns True only if the tested string is the string that signifies that an EnumProperty is empty.
Returns False in all other cases."""
return _empty_enum_identifier == string
# This function isn't needed since you can 'not is_enum_empty(string)', but is included for code clarity and readability
def is_enum_non_empty(string):
"""Returns False only if the tested string is not the string that signifies that an EnumProperty is empty.
Returns True in all other cases."""
return _empty_enum_identifier != string
+169
View File
@@ -15,6 +15,8 @@ from .translations import t, get_languages_list, update_language
from .addon_preferences import get_preference, save_preference
from .updater import get_version_list
from .common import get_armature_list, get_active_armature, get_all_meshes
from ..functions.visemes import VisemePreview
from ..functions.eye_tracking import set_rotation
def update_validation_mode(self, context):
logger.info(f"Updating validation mode to: {self.validation_mode}")
@@ -26,6 +28,11 @@ def update_logging_state(self, context):
from .logging_setup import configure_logging
configure_logging(self.enable_logging)
def update_shape_intensity(self, context):
if self.viseme_preview_mode:
from ..functions.visemes import VisemePreview
VisemePreview.update_preview(context)
class AvatarToolkitSceneProperties(PropertyGroup):
"""Property group containing Avatar Toolkit scene-level settings and properties"""
@@ -112,6 +119,168 @@ class AvatarToolkitSceneProperties(PropertyGroup):
max=1.0
)
viseme_preview_mode: BoolProperty(
name=t("Visemes.preview_mode"),
description=t("Visemes.preview_mode_desc"),
default=False
)
viseme_preview_selection: StringProperty(
name=t("Visemes.preview_selection"),
description=t("Visemes.preview_selection_desc"),
default="vrc.v_aa"
)
mouth_a: StringProperty(
name=t("Visemes.mouth_a"),
description=t("Visemes.mouth_a_desc")
)
mouth_o: StringProperty(
name=t("Visemes.mouth_o"),
description=t("Visemes.mouth_o_desc")
)
mouth_ch: StringProperty(
name=t("Visemes.mouth_ch"),
description=t("Visemes.mouth_ch_desc")
)
shape_intensity: FloatProperty(
name=t("Visemes.shape_intensity"),
description=t("Visemes.shape_intensity_desc"),
default=1.0,
min=0.0,
max=2.0,
precision=3,
update=update_shape_intensity
)
viseme_preview_selection: EnumProperty(
name=t("Visemes.preview_selection"),
description=t("Visemes.preview_selection_desc"),
items=[
('vrc.v_aa', 'AA', 'A as in "bat"'),
('vrc.v_ch', 'CH', 'Ch as in "choose"'),
('vrc.v_dd', 'DD', 'D as in "dog"'),
('vrc.v_ih', 'IH', 'I as in "bit"'),
('vrc.v_ff', 'FF', 'F as in "fox"'),
('vrc.v_e', 'E', 'E as in "bet"'),
('vrc.v_kk', 'KK', 'K as in "cat"'),
('vrc.v_nn', 'NN', 'N as in "net"'),
('vrc.v_oh', 'OH', 'O as in "hot"'),
('vrc.v_ou', 'OU', 'O as in "go"'),
('vrc.v_pp', 'PP', 'P as in "pat"'),
('vrc.v_rr', 'RR', 'R as in "red"'),
('vrc.v_sil', 'SIL', 'Silence'),
('vrc.v_ss', 'SS', 'S as in "sit"'),
('vrc.v_th', 'TH', 'Th as in "think"')
],
update=lambda s, c: VisemePreview.update_preview(c)
)
eye_mode: EnumProperty(
name=t("EyeTracking.mode"),
items=[
('CREATION', t("EyeTracking.mode.creation"), ""),
('TESTING', t("EyeTracking.mode.testing"), "")
],
default='CREATION'
)
eye_rotation_x: FloatProperty(
name=t("EyeTracking.rotation.x"),
update=set_rotation
)
eye_rotation_y: FloatProperty(
name=t("EyeTracking.rotation.y"),
update=set_rotation
)
mesh_name_eye: StringProperty(
name=t("EyeTracking.mesh_name"),
description=t("EyeTracking.mesh_name_desc")
)
head: StringProperty(
name=t("EyeTracking.head_bone"),
description=t("EyeTracking.head_bone_desc")
)
eye_left: StringProperty(
name=t("EyeTracking.eye_left"),
description=t("EyeTracking.eye_left_desc")
)
eye_right: StringProperty(
name=t("EyeTracking.eye_right"),
description=t("EyeTracking.eye_right_desc")
)
disable_eye_movement: BoolProperty(
name=t("EyeTracking.disable_movement"),
description=t("EyeTracking.disable_movement_desc"),
default=False
)
disable_eye_blinking: BoolProperty(
name=t("EyeTracking.disable_blinking"),
description=t("EyeTracking.disable_blinking_desc"),
default=False
)
eye_distance: FloatProperty(
name=t("EyeTracking.distance"),
description=t("EyeTracking.distance_desc"),
default=0.0,
min=-1.0,
max=1.0
)
iris_height: FloatProperty(
name=t("EyeTracking.iris_height"),
description=t("EyeTracking.iris_height_desc"),
default=0.0,
min=-1.0,
max=1.0
)
eye_blink_shape: FloatProperty(
name=t("EyeTracking.blink_shape"),
description=t("EyeTracking.blink_shape_desc"),
default=1.0,
min=0.0,
max=1.0
)
eye_lowerlid_shape: FloatProperty(
name=t("EyeTracking.lowerlid_shape"),
description=t("EyeTracking.lowerlid_shape_desc"),
default=1.0,
min=0.0,
max=1.0
)
wink_left: StringProperty(
name=t("EyeTracking.wink_left"),
description=t("EyeTracking.wink_left_desc")
)
wink_right: StringProperty(
name=t("EyeTracking.wink_right"),
description=t("EyeTracking.wink_right_desc")
)
lowerlid_left: StringProperty(
name=t("EyeTracking.lowerlid_left"),
description=t("EyeTracking.lowerlid_left_desc")
)
lowerlid_right: StringProperty(
name=t("EyeTracking.lowerlid_right"),
description=t("EyeTracking.lowerlid_right_desc")
)
def register() -> None:
"""Register the Avatar Toolkit property group"""
logger.info("Registering Avatar Toolkit properties")
+1
View File
@@ -77,6 +77,7 @@ class AvatarToolkit_PT_UpdaterPanel(bpy.types.Panel):
bl_category = CATEGORY_NAME
bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
bl_order = 4
bl_options = {'DEFAULT_CLOSED'}
def draw(self, context: bpy.types.Context) -> None:
layout = self.layout