Files
Avatar-Toolkit/core/properties.py
T
Yusarina 61c77cf756 Translation Service
- Added translation service with 3 services.
- MyMemory (Free no api key needed but 1000 words a day and Skow)
- Deepl (Free with API key, 500000 words a month and fast)
- Libre Translate (Paid unless you host your own server, open source)
- Added caching for Quick Access and the translate service to speed up the UI. Can be fast depending on the service you use/ PC specs and etc).
2025-08-07 13:58:40 +01:00

778 lines
26 KiB
Python

import bpy
from typing import List, Tuple, Optional, Any, Dict, Union, Callable
from bpy.types import PropertyGroup, Material, Scene, Object, Context
from bpy.props import (
StringProperty,
BoolProperty,
EnumProperty,
IntProperty,
FloatProperty,
CollectionProperty,
PointerProperty
)
from .logging_setup import logger
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, SceneMatClass
from ..functions.visemes import VisemePreview
from ..functions.eye_tracking import set_rotation
class ValidationMessageItem(PropertyGroup):
"""Property group for validation message items"""
name: StringProperty(name="Message")
class ZeroWeightBoneItem(PropertyGroup):
"""Property group for zero weight bone list items"""
name: StringProperty(name="Bone Name")
selected: BoolProperty(name="Selected", default=True)
has_children: BoolProperty(name="Has Children", default=False)
is_deform: BoolProperty(name="Is Deform Bone", default=False)
def update_validation_mode(self: PropertyGroup, context: Context) -> None:
"""Updates validation mode and saves preference"""
logger.info(f"Updating validation mode to: {self.validation_mode}")
save_preference("validation_mode", self.validation_mode)
def update_logging_state(self: PropertyGroup, context: Context) -> None:
"""Updates logging state and configures logging"""
logger.info(f"Updating logging state to: {self.enable_logging}")
save_preference("enable_logging", self.enable_logging)
from .logging_setup import configure_logging
configure_logging(self.enable_logging)
def update_log_level(self: PropertyGroup, context: Context) -> None:
"""Updates log level and configures logging"""
logger.info(f"Updating log level to: {self.log_level}")
save_preference("log_level", self.log_level)
from .logging_setup import configure_logging
configure_logging(self.enable_logging, self.log_level)
def update_shape_intensity(self: PropertyGroup, context: Context) -> None:
"""Updates shape key intensity and refreshes preview"""
if self.viseme_preview_mode:
VisemePreview.update_preview(context)
def highlight_problem_bones(self: PropertyGroup, context: Context) -> None:
"""Updates problem bone highlighting state and saves preference"""
logger.info(f"Updating problem bone highlighting to: {self.highlight_problem_bones}")
save_preference("highlight_problem_bones", self.highlight_problem_bones)
def get_mesh_objects(self, context):
meshes = [(obj.name, obj.name, "") for obj in bpy.data.objects if obj.type == 'MESH']
if not meshes:
return [('NONE', t("Visemes.no_meshes"), '')]
return meshes
class AvatarToolkitSceneProperties(PropertyGroup):
"""Property group containing Avatar Toolkit scene-level settings and properties"""
show_found_bones: BoolProperty(
name="Show Found Bones",
default=False
)
show_non_standard: BoolProperty(
name="Show Non-Standard Bones",
default=False
)
show_hierarchy: BoolProperty(
name="Show Hierarchy Issues",
default=False
)
material_search_filter: StringProperty(
name=t("TextureAtlas.search_materials"),
description=t("TextureAtlas.search_materials_desc"),
default=""
)
def get_texture_node_list(self: Material, context: Context) -> list[tuple]:
if self.use_nodes:
Object.Enum = [((i.image.name if i.image else i.name+"_image"),
(i.image.name if i.image else "node with no image..."),
(i.image.name if i.image else i.name), index+1)
for index, i in enumerate(self.node_tree.nodes)
if i.bl_idname == "ShaderNodeTexImage"]
if not len(Object.Enum):
Object.Enum = [(t("TextureAtlas.error.label"),
t("TextureAtlas.no_images_error.desc"),
t("TextureAtlas.error.label"), 0)]
else:
Object.Enum = [(t("TextureAtlas.error.label"),
t("TextureAtlas.no_nodes_error.desc"),
t("TextureAtlas.error.label"), 0)]
Object.Enum.append((t("TextureAtlas.none.label"),
t("TextureAtlas.none.label"),
t("TextureAtlas.none.label"), 0))
return Object.Enum
Material.texture_atlas_albedo = EnumProperty(
name=t("TextureAtlas.albedo"),
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.albedo").lower()),
default=0,
items=get_texture_node_list
)
Material.texture_atlas_normal = EnumProperty(
name=t("TextureAtlas.normal"),
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.normal").lower()),
default=0,
items=get_texture_node_list
)
Material.texture_atlas_emission = EnumProperty(
name=t("TextureAtlas.emission"),
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.emission").lower()),
default=0,
items=get_texture_node_list
)
Material.texture_atlas_ambient_occlusion = EnumProperty(
name=t("TextureAtlas.ambient_occlusion"),
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.ambient_occlusion").lower()),
default=0,
items=get_texture_node_list
)
Material.texture_atlas_height = EnumProperty(
name=t("TextureAtlas.height"),
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.height").lower()),
default=0,
items=get_texture_node_list
)
Material.texture_atlas_roughness = EnumProperty(
name=t("TextureAtlas.roughness"),
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.roughness").lower()),
default=0,
items=get_texture_node_list
)
list_only_mode: BoolProperty(
name=t("Tools.list_only_mode"),
description=t("Tools.list_only_mode_desc"),
default=False
)
Material.include_in_atlas = BoolProperty(
name=t("TextureAtlas.include_in_atlas"),
description=t("TextureAtlas.include_in_atlas_desc"),
default=False
)
Material.material_expanded = BoolProperty(
name=t("TextureAtlas.material_expanded"),
description=t("TextureAtlas.material_expanded_desc"),
default=False
)
texture_atlas_Has_Mat_List_Shown: BoolProperty(
name=t("TextureAtlas.list_shown"),
description=t("TextureAtlas.list_shown_desc"),
default=False
)
texture_atlas_material_index: IntProperty(
default=-1,
get=lambda self: -1,
set=lambda self, context: None
)
materials: CollectionProperty(
type=SceneMatClass
)
avatar_toolkit_updater_version_list: EnumProperty(
items=get_version_list,
name=t("Scene.avatar_toolkit_updater_version_list.name"),
description=t("Scene.avatar_toolkit_updater_version_list.description")
)
active_armature: EnumProperty(
items=get_armature_list,
name=t("QuickAccess.select_armature"),
description=t("QuickAccess.select_armature"),
update=lambda self, context: update_active_armature(self, context)
)
language: EnumProperty(
name=t("Settings.language"),
description=t("Settings.language_desc"),
items=get_languages_list,
update=update_language
)
validation_mode: EnumProperty(
name=t("Settings.validation_mode"),
description=t("Settings.validation_mode_desc"),
items=[
('STRICT', t("Settings.validation_mode.strict"), t("Settings.validation_mode.strict_desc")),
('BASIC', t("Settings.validation_mode.basic"), t("Settings.validation_mode.basic_desc")),
('NONE', t("Settings.validation_mode.none"), t("Settings.validation_mode.none_desc"))
],
default=get_preference("validation_mode", "STRICT"),
update=update_validation_mode
)
enable_logging: BoolProperty(
name=t("Settings.enable_logging"),
description=t("Settings.enable_logging_desc"),
default=False,
update=update_logging_state
)
debug_expand: BoolProperty(
name="Debug Settings Expanded",
default=False
)
remove_doubles_merge_distance: FloatProperty(
name=t("Optimization.merge_distance"),
description=t("Optimization.merge_distance_desc"),
default=0.0001,
min=0.00001,
max=0.1
)
remove_doubles_advanced: BoolProperty(
name=t("Optimization.remove_doubles_advanced"),
description=t("Optimization.remove_doubles_advanced_desc"),
default=False
)
connect_bones_min_distance: FloatProperty(
name=t("Tools.connect_bones_min_distance"),
description=t("Tools.connect_bones_min_distance_desc"),
default=0.001,
min=0.0001,
max=0.1,
precision=4
)
merge_twist_bones: BoolProperty(
name=t("MMD.merge_twist_bones"),
description=t("MMD.merge_twist_bones_desc"),
default=True
)
keep_twist_bones: BoolProperty(
name=t("MMD.keep_twist_bones"),
description=t("MMD.keep_twist_bones_desc"),
default=False
)
keep_upper_chest: BoolProperty(
name=t("MMD.keep_upper_chest"),
description=t("MMD.keep_upper_chest_desc"),
default=True
)
merge_weights_threshold: FloatProperty(
name=t("MMD.merge_weights_threshold"),
description=t("MMD.merge_weights_threshold_desc"),
default=0.01,
min=0.0,
max=1.0
)
viseme_preview_mode: BoolProperty(
name=t("Visemes.preview_mode"),
description=t("Visemes.preview_mode_desc"),
default=False
)
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")
)
viseme_mesh: EnumProperty(
name=t("Visemes.mesh_select"),
description=t("Visemes.mesh_select_desc"),
items=get_mesh_objects
)
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_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(
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")
)
merge_mode: EnumProperty(
name=t('CustomPanel.merge_mode'),
description=t('CustomPanel.merge_mode_desc'),
items=[
('ARMATURE', t('CustomPanel.mode.armature'), t('CustomPanel.mode.armature_desc')),
('MESH', t('CustomPanel.mode.mesh'), t('CustomPanel.mode.mesh_desc'))
],
default='ARMATURE'
)
merge_armature_into: StringProperty(
name=t('MergeArmature.into'),
description=t('MergeArmature.into_desc'),
default=""
)
merge_armature: StringProperty(
name=t('MergeArmature.from'),
description=t('MergeArmature.from_desc'),
default=""
)
attach_mesh: StringProperty(
name=t('AttachMesh.select'),
description=t('AttachMesh.select_desc'),
default=""
)
attach_bone: StringProperty(
name=t('AttachBone.select'),
description=t('AttachBone.select_desc'),
default=""
)
apply_transforms: BoolProperty(
name=t('MergeArmature.apply_transforms'),
description=t('MergeArmature.apply_transforms_desc'),
default=True
)
join_meshes: BoolProperty(
name=t('MergeArmature.join_meshes'),
description=t('MergeArmature.join_meshes_desc'),
default=True
)
remove_zero_weights: BoolProperty(
name=t('MergeArmature.remove_zero_weights'),
description=t('MergeArmature.remove_zero_weights_desc'),
default=True
)
preserve_parent_bones: BoolProperty(
name=t("Tools.preserve_parent_bones"),
description=t("Tools.preserve_parent_bones_desc"),
default=True
)
target_bone_type: EnumProperty(
name=t("Tools.target_bone_type"),
description=t("Tools.target_bone_type_desc"),
items=[
('ALL', t("Tools.target_all_bones"), ""),
('DEFORM', t("Tools.target_deform_bones"), ""),
('NON_DEFORM', t("Tools.target_non_deform_bones"), "")
],
default='ALL'
)
zero_weight_bones: CollectionProperty(
type=ZeroWeightBoneItem,
name="Zero Weight Bones",
description="List of bones with zero weights"
)
zero_weight_bones_index: IntProperty(
name="Zero Weight Bone Index",
default=0
)
list_only_mode: BoolProperty(
name=t("Tools.list_only_mode"),
description=t("Tools.list_only_mode_desc"),
default=False
)
cleanup_shape_keys: BoolProperty(
name=t('MergeArmature.cleanup_shape_keys'),
description=t('MergeArmature.cleanup_shape_keys_desc'),
default=True
)
merge_twist_bones: BoolProperty(
name=t("Tools.merge_twist_bones"),
description=t("Tools.merge_twist_bones_desc"),
default=True
)
highlight_problem_bones: BoolProperty(
name=t("Settings.highlight_problem_bones"),
description=t("Settings.highlight_problem_bones_desc"),
default=get_preference("highlight_problem_bones", True),
update=highlight_problem_bones
)
show_scale_issues: BoolProperty(
name="Show Scale Issues",
default=False
)
tpose_validation_result: BoolProperty(
name="T-Pose Validation Result",
default=True
)
tpose_validation_messages: CollectionProperty(
type=bpy.types.PropertyGroup,
name="T-Pose Validation Messages"
)
show_tpose_validation: BoolProperty(
name="Show T-Pose Validation Results",
default=False
)
standardize_fix_names: BoolProperty(
name=t("Tools.standardize_fix_names"),
description=t("Tools.standardize_fix_names_desc"),
default=True
)
standardize_fix_hierarchy: BoolProperty(
name=t("Tools.standardize_fix_hierarchy"),
description=t("Tools.standardize_fix_hierarchy_desc"),
default=True
)
standardize_fix_scale: BoolProperty(
name=t("Tools.standardize_fix_scale"),
description=t("Tools.standardize_fix_scale_desc"),
default=True
)
log_level: EnumProperty(
name=t("Settings.log_level"),
description=t("Settings.log_level_desc"),
items=[
('DEBUG', t("Settings.log_level.debug"), t("Settings.log_level.debug_desc")),
('INFO', t("Settings.log_level.info"), t("Settings.log_level.info_desc")),
('WARNING', t("Settings.log_level.warning"), t("Settings.log_level.warning_desc")),
('ERROR', t("Settings.log_level.error"), t("Settings.log_level.error_desc")),
],
default=get_preference("log_level", "WARNING"),
update=update_log_level
)
# VRM Conversion Properties
vrm_remove_colliders: BoolProperty(
name=t("VRM.remove_colliders"),
description=t("VRM.remove_colliders_desc"),
default=True
)
vrm_remove_root: BoolProperty(
name=t("VRM.remove_root"),
description=t("VRM.remove_root_desc"),
default=True
)
# Translation System Properties
translation_service: EnumProperty(
name=t("Translation.service"),
description=t("Translation.service_desc"),
items=[
('mymemory', t("Translation.service.mymemory"), t("Translation.service.mymemory_desc")),
('libretranslate', t("Translation.service.libretranslate"), t("Translation.service.libretranslate_desc")),
('deepl', t("Translation.service.deepl"), t("Translation.service.deepl_desc"))
],
default=get_preference("translation_service", "mymemory"),
update=lambda self, context: update_translation_service(self, context)
)
translation_mode: EnumProperty(
name=t("Translation.mode"),
description=t("Translation.mode_desc"),
items=[
('hybrid', t("Translation.mode.hybrid"), t("Translation.mode.hybrid_desc")),
('dictionary_only', t("Translation.mode.dictionary_only"), t("Translation.mode.dictionary_only_desc")),
('api_only', t("Translation.mode.api_only"), t("Translation.mode.api_only_desc"))
],
default=get_preference("translation_mode", "hybrid"),
update=lambda self, context: update_translation_mode(self, context)
)
translation_expand: BoolProperty(
name="Translation Settings Expanded",
default=False
)
translation_target_language: EnumProperty(
name=t("Translation.target_language"),
description=t("Translation.target_language_desc"),
items=[
('en', 'English', 'Translate to English'),
('ja', 'Japanese', 'Translate to Japanese'),
('ko', 'Korean', 'Translate to Korean'),
('zh', 'Chinese', 'Translate to Chinese'),
('es', 'Spanish', 'Translate to Spanish'),
('fr', 'French', 'Translate to French'),
('de', 'German', 'Translate to German')
],
default='en'
)
translation_source_language: EnumProperty(
name=t("Translation.source_language"),
description=t("Translation.source_language_desc"),
items=[
('auto', 'Auto-detect', 'Automatically detect source language'),
('ja', 'Japanese', 'Source is Japanese'),
('en', 'English', 'Source is English'),
('ko', 'Korean', 'Source is Korean'),
('zh', 'Chinese', 'Source is Chinese')
],
default='ja'
)
def update_translation_service(self: PropertyGroup, context: Context) -> None:
"""Update translation service preference"""
logger.info(f"Updating translation service to: {self.translation_service}")
save_preference("translation_service", self.translation_service)
# Clear module-level translation caches when service changes
try:
from ..ui.translation_panel import _ui_cache
_ui_cache['deepl_config'].clear()
_ui_cache['libretranslate_config'].clear()
_ui_cache['translation_status'].clear()
if 'batch_info' in _ui_cache:
del _ui_cache['batch_info'] # Clear batch info cache when service changes
except ImportError:
pass # UI module might not be loaded yet
# Set the primary service
try:
from .translation_manager import get_avatar_translation_manager
manager = get_avatar_translation_manager()
manager.service_manager.set_primary_service(self.translation_service)
except Exception as e:
logger.error(f"Failed to update translation service: {e}")
def update_translation_mode(self: PropertyGroup, context: Context) -> None:
"""Update translation mode preference"""
logger.info(f"Updating translation mode to: {self.translation_mode}")
save_preference("translation_mode", self.translation_mode)
# Clear module-level translation status cache when mode changes
try:
from ..ui.translation_panel import _ui_cache
_ui_cache['translation_status'].clear()
if 'batch_info' in _ui_cache:
del _ui_cache['batch_info'] # Clear batch info cache when mode changes
except ImportError:
pass # UI module might not be loaded yet
try:
from .translation_manager import get_avatar_translation_manager, TranslationMode
manager = get_avatar_translation_manager()
manager.set_translation_mode(TranslationMode(self.translation_mode))
except Exception as e:
logger.error(f"Failed to update translation mode: {e}")
def update_active_armature(self: PropertyGroup, context: Context) -> None:
"""Update the active armature when selection changes"""
if self.active_armature:
logger.info(f"Active armature set to: {self.active_armature}")
# Deselect all objects first
bpy.ops.object.select_all(action='DESELECT')
# Select and make active the chosen armature
self.active_armature.select_set(True)
context.view_layer.objects.active = self.active_armature
logger.info(f"Selected and activated armature: {self.active_armature.name}")
# Clear armature caches when armature changes to ensure fresh validation
try:
from ..ui.quick_access_panel import clear_armature_caches
clear_armature_caches()
except ImportError:
pass # UI module might not be loaded yet
else:
logger.info("No armature selected")
def register() -> None:
"""Register the Avatar Toolkit property group"""
logger.info("Registering Avatar Toolkit properties")
# Only register the property, not the classes (auto_load will handle that)
bpy.types.Scene.avatar_toolkit = PointerProperty(type=AvatarToolkitSceneProperties)
logger.debug("Properties registered successfully")
def unregister() -> None:
"""Unregister the Avatar Toolkit property group"""
logger.info("Unregistering Avatar Toolkit properties")
# Remove the property
if hasattr(bpy.types.Scene, "avatar_toolkit"):
try:
del bpy.types.Scene.avatar_toolkit
logger.debug("Removed avatar_toolkit property")
except Exception as e:
logger.warning(f"Failed to remove avatar_toolkit property: {e}")
# Not fatal - continue