Added Eye tracking and Visemes
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user