diff --git a/core/addon_preferences.py b/core/addon_preferences.py index 6b0184b..b9fdd63 100644 --- a/core/addon_preferences.py +++ b/core/addon_preferences.py @@ -2,6 +2,7 @@ import bpy import os import tomllib import json +from ..core.logging_setup import logger from bpy.types import AddonPreferences from typing import Any, Dict @@ -12,22 +13,31 @@ PREFERENCES_FILE = os.path.join(PREFERENCES_DIR, "preferences.json") def get_current_version(): main_dir = os.path.dirname(os.path.dirname(__file__)) manifest_path = os.path.join(main_dir, "blender_manifest.toml") + logger.debug(f"Reading version from manifest: {manifest_path}") with open(manifest_path, 'rb') as f: manifest_data = tomllib.load(f) - return manifest_data.get('version', 'Unknown') + version = manifest_data.get('version', 'Unknown') + logger.info(f"Current addon version: {version}") + return version def save_preference(key: str, value: Any) -> None: """Save a single preference to the JSON file.""" + logger.debug(f"Saving preference: {key} = {value}") prefs = load_preferences() prefs[key] = value with open(PREFERENCES_FILE, 'w') as f: json.dump(prefs, f, indent=4) + logger.info(f"Preference saved: {key}") def load_preferences() -> Dict[str, Any]: """Load all preferences from the JSON file.""" + logger.debug(f"Loading preferences from: {PREFERENCES_FILE}") if os.path.exists(PREFERENCES_FILE): with open(PREFERENCES_FILE, 'r') as f: - return json.load(f) + prefs = json.load(f) + logger.debug(f"Loaded preferences: {prefs}") + return prefs + logger.info("No preferences file found, using defaults") return {} def get_preference(key: str, default: Any = None) -> Any: @@ -40,12 +50,13 @@ class AvatarToolkitPreferences(AddonPreferences): def draw(self, context): layout = self.layout - layout.label(text="Preferences are managed internally.") - # You can add more UI elements here if needed + layout.label(text=f"Version: {get_current_version()}") def get_addon_preferences(context): return context.preferences.addons[AvatarToolkitPreferences.bl_idname].preferences # Initialize preferences if the file doesn't exist if not os.path.exists(PREFERENCES_FILE): - save_preference("language", 0) # Set default language to 0 (auto) \ No newline at end of file + save_preference("language", 0) # Set default language to 0 (auto) + save_preference("validation_mode", "STRICT") # Set default validation mode + save_preference("enable_logging", False) # Set default logging mode \ No newline at end of file diff --git a/core/auto_load.py b/core/auto_load.py index f0c50a5..ea4f86f 100644 --- a/core/auto_load.py +++ b/core/auto_load.py @@ -23,6 +23,14 @@ def init() -> None: """Initialize the auto-loader by discovering modules and classes""" global modules global ordered_classes + + # Configure logging first + from .logging_setup import configure_logging + configure_logging(False) + + from .addon_preferences import get_preference + configure_logging(get_preference("enable_logging", False)) + print("Auto-load init starting") modules = get_all_submodules(Path(__file__).parent.parent) ordered_classes = get_ordered_classes_to_register(modules) diff --git a/core/common.py b/core/common.py index ee3b694..bec7810 100644 --- a/core/common.py +++ b/core/common.py @@ -1,22 +1,11 @@ import bpy import numpy as np -import logging from bpy.types import Context, Object, Modifier from typing import Optional, Tuple, List, Set, Dict, Any, Generator, Callable +from ..core.logging_setup import logger from ..core.translations import t from ..core.dictionaries import bone_names -logger = logging.getLogger('avatar_toolkit') -logger.setLevel(logging.DEBUG) - -def setup_logging() -> None: - """Configure logging for Avatar Toolkit""" - handler = logging.StreamHandler() - handler.setLevel(logging.DEBUG) - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') - handler.setFormatter(formatter) - logger.addHandler(handler) - class ProgressTracker: """Universal progress tracking for Avatar Toolkit operations""" @@ -63,17 +52,23 @@ def get_armature_list(self=None, context: bpy.types.Context = None) -> List[Tupl return [('NONE', t("Armature.validation.no_armature"), '')] return armatures -def validate_armature(armature: bpy.types.Object, validation_level: str = 'standard') -> Tuple[bool, List[str]]: - """Enhanced armature validation with multiple checks and validation levels""" +def validate_armature(armature: bpy.types.Object) -> Tuple[bool, List[str]]: + """Enhanced armature validation with multiple validation modes""" + validation_mode = bpy.context.scene.avatar_toolkit.validation_mode + + # Skip validation if mode is NONE + if validation_mode == 'NONE': + return True, [] + messages = [] - # Basic checks + # Basic checks always run if not NONE if not armature or armature.type != 'ARMATURE' or not armature.data.bones: return False, [t("Armature.validation.basic_check_failed")] found_bones = {bone.name.lower(): bone for bone in armature.data.bones} - # Essential bones check + # Essential bones check (BASIC and STRICT) essential_bones = {'hips', 'spine', 'chest', 'neck', 'head'} missing_bones = [] for bone in essential_bones: @@ -83,7 +78,8 @@ def validate_armature(armature: bpy.types.Object, validation_level: str = 'stand if missing_bones: messages.append(t("Armature.validation.missing_bones", bones=", ".join(missing_bones))) - if validation_level in ['standard', 'strict']: + # Additional checks for STRICT mode only + if validation_mode == 'STRICT': # Hierarchy validation hierarchy = [('hips', 'spine'), ('spine', 'chest'), ('chest', 'neck'), ('neck', 'head')] for parent, child in hierarchy: diff --git a/core/logging_setup.py b/core/logging_setup.py new file mode 100644 index 0000000..f921d5d --- /dev/null +++ b/core/logging_setup.py @@ -0,0 +1,26 @@ +import logging +from typing import Optional + +logger = logging.getLogger('avatar_toolkit') + +def configure_logging(enabled: bool = False) -> None: + """Configure logging for Avatar Toolkit""" + logger.setLevel(logging.DEBUG if enabled else logging.WARNING) + + # Remove existing handlers + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + if enabled: + handler = logging.StreamHandler() + handler.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + +def update_logging_state(self, context) -> None: + """Update logging state based on user preference""" + from .addon_preferences import save_preference + enabled = self.enable_logging + save_preference("enable_logging", enabled) + configure_logging(enabled) diff --git a/core/properties.py b/core/properties.py index d35eda0..0b2e0a6 100644 --- a/core/properties.py +++ b/core/properties.py @@ -10,11 +10,22 @@ from bpy.props import ( CollectionProperty, PointerProperty ) +from .logging_setup import logger from .translations import t, get_languages_list, update_language -from .addon_preferences import get_preference +from .addon_preferences import get_preference, save_preference from .updater import get_version_list from .common import get_armature_list +def update_validation_mode(self, context): + logger.info(f"Updating validation mode to: {self.validation_mode}") + save_preference("validation_mode", self.validation_mode) + +def update_logging_state(self, context): + 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) + class AvatarToolkitSceneProperties(PropertyGroup): """Property group containing Avatar Toolkit scene-level settings and properties""" @@ -30,10 +41,46 @@ class AvatarToolkitSceneProperties(PropertyGroup): description=t("QuickAccess.select_armature") ) + 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 + ) + def register() -> None: """Register the Avatar Toolkit property group""" + logger.info("Registering Avatar Toolkit properties") 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") del bpy.types.Scene.avatar_toolkit + logger.debug("Properties unregistered successfully") + diff --git a/core/translations.py b/core/translations.py index f047964..fc530d8 100644 --- a/core/translations.py +++ b/core/translations.py @@ -1,10 +1,15 @@ import os import json import bpy +import logging from bpy.app.translations import locale -from typing import Dict, List, Tuple +from typing import Dict, List, Tuple, Optional, Any +from ..core.logging_setup import logger from .addon_preferences import save_preference, get_preference +# Set up logging +logger = logging.getLogger(__name__) + # Use __file__ to get the current file's directory current_dir = os.path.dirname(os.path.abspath(__file__)) main_dir = os.path.dirname(current_dir) @@ -13,9 +18,15 @@ translations_dir = os.path.join(resources_dir, "translations") dictionary: Dict[str, str] = dict() languages: List[str] = [] +_translation_cache: Dict[str, Dict[str, str]] = {} verbose: bool = True +def get_fallback_language() -> str: + """Return the default fallback language""" + return "en_US" + def load_translations() -> bool: + """Load translations for the selected language""" global dictionary, languages old_dictionary = dictionary.copy() @@ -29,69 +40,73 @@ def load_translations() -> bool: if lang != "auto": languages.append(lang) - language_index = get_preference("language", 0) - # print(f"Loading translations for language index: {language_index}") # Debug print + language_index: int = get_preference("language", 0) + logger.debug(f"Loading translations for language index: {language_index}") if language_index == 0: # "auto" - language = bpy.context.preferences.view.language + language: str = bpy.context.preferences.view.language else: try: language = languages[language_index] except IndexError: language = bpy.context.preferences.view.language - # print(f"Selected language: {language}") # Debug print + logger.debug(f"Selected language: {language}") + + # Check cache first + if language in _translation_cache: + dictionary = _translation_cache[language] + return dictionary != old_dictionary translation_file: str = os.path.join(translations_dir, language + ".json") if os.path.exists(translation_file): - with open(translation_file, 'r', encoding='utf-8') as file: - dictionary = json.load(file)["messages"] - # print(f"Loaded translations: {dictionary}") # Debug print + dictionary = _load_translation_file(translation_file) else: custom_language: str = language.split("_")[0] custom_translation_file: str = os.path.join(translations_dir, custom_language + ".json") if os.path.exists(custom_translation_file): - with open(custom_translation_file, 'r', encoding='utf-8') as file: - dictionary = json.load(file)["messages"] - # print(f"Loaded custom translations: {dictionary}") # Debug print + dictionary = _load_translation_file(custom_translation_file) else: - print(f"Translation file not found for language: {language}") - default_file: str = os.path.join(translations_dir, "en_US.json") + logger.warning(f"Translation file not found for language: {language}") + default_file: str = os.path.join(translations_dir, get_fallback_language() + ".json") if os.path.exists(default_file): - with open(default_file, 'r', encoding='utf-8') as file: - dictionary = json.load(file)["messages"] - # print(f"Loaded default translations: {dictionary}") # Debug print + dictionary = _load_translation_file(default_file) else: - print("Default translation file 'en_US.json' not found.") + logger.error("Default translation file not found") + _translation_cache[language] = dictionary return dictionary != old_dictionary -def t(phrase: str, default: str = None, **kwargs) -> str: - output: str = dictionary.get(phrase) +def _load_translation_file(file_path: str) -> Dict[str, str]: + """Load and parse a translation file""" + with open(file_path, 'r', encoding='utf-8') as file: + return json.load(file)["messages"] + +def t(phrase: str, default: Optional[str] = None, **kwargs) -> str: + """Get translation for a phrase with optional formatting""" + output: Optional[str] = dictionary.get(phrase) if output is None: if verbose: - print(f'Warning: Unknown phrase: {phrase}') + logger.warning(f'Unknown phrase: {phrase}') return default if default is not None else phrase - # print(f"Translating '{phrase}' to '{output}'") # Debug print return output.format(**kwargs) if kwargs else output def get_language_display_name(lang: str) -> str: - if lang == "auto": - return t("Language.auto", "Automatic") + """Get the display name for a language code""" return t(f"Language.{lang}", lang) -def get_languages_list(self, context) -> List[Tuple[str, str, str]]: - return [(str(i), get_language_display_name(lang), f"Use {lang} language") for i, lang in enumerate(languages)] +def get_languages_list(self: Any, context: Any) -> List[Tuple[str, str, str]]: + """Get list of available languages for UI""" + return [(str(i), get_language_display_name(lang), f"Use {lang} language") + for i, lang in enumerate(languages)] -def update_language(self, context): - print(f"Updating language to: {self.language}") # Debug print +def update_language(self: Any, context: Any) -> None: + """Handle language update and UI refresh""" + logger.info(f"Updating language to: {self.language}") save_preference("language", int(self.language)) load_translations() - # Set a flag to indicate that a language change has occurred context.scene.avatar_toolkit.language_changed = True - # Show popup after language change bpy.ops.avatar_toolkit.translation_restart_popup('INVOKE_DEFAULT') # Initial load of translations -# print("Performing initial load of translations") # Debug print load_translations() diff --git a/core/updater.py b/core/updater.py index 437bf15..66a9f5c 100644 --- a/core/updater.py +++ b/core/updater.py @@ -76,7 +76,7 @@ class AvatarToolkit_PT_UpdaterPanel(bpy.types.Panel): bl_region_type = 'UI' bl_category = CATEGORY_NAME bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname - bl_order = 9 + bl_order = 1 def draw(self, context: bpy.types.Context) -> None: layout = self.layout diff --git a/functions/pose_mode.py b/functions/pose_mode.py index 5c3dc50..c8fbc15 100644 --- a/functions/pose_mode.py +++ b/functions/pose_mode.py @@ -1,8 +1,8 @@ import bpy -import logging from typing import Set, Dict, List, Tuple, Optional, Any from bpy.props import StringProperty from bpy.types import Operator, Context, Object, Event, Modifier +from ..core.logging_setup import logger from ..core.translations import t from ..core.common import ( get_active_armature, @@ -16,8 +16,6 @@ from ..core.common import ( ProgressTracker ) -logger = logging.getLogger('avatar_toolkit.pose') - class BatchPoseOperationMixin: """Base class for batch pose operations""" @classmethod diff --git a/resources/translations/en_US.json b/resources/translations/en_US.json index ba503a3..f445e83 100644 --- a/resources/translations/en_US.json +++ b/resources/translations/en_US.json @@ -46,6 +46,10 @@ "QuickAccess.apply_pose_as_rest.label": "Apply Pose as Rest", "QuickAccess.apply_pose_as_rest.desc": "Apply current pose as rest pose", "QuickAccess.apply_armature_failed": "Failed to apply armature modifications", + "QuickAccess.validation_basic_warning": "Limited Validation Active", + "QuickAccess.validation_basic_details": "Only essential bone structure is being validated", + "QuickAccess.validation_none_warning": "Validation Disabled", + "QuickAccess.validation_none_details": "No armature validation checks are being performed", "PoseMode.error.start": "Failed to start pose mode: {error}", "PoseMode.error.stop": "Failed to stop pose mode: {error}", @@ -74,6 +78,30 @@ "Operation.pose_applied": "Pose applied successfully", "Scene.avatar_toolkit_updater_version_list.name": "Version List", - "Scene.avatar_toolkit_updater_version_list.description": "List of available versions" + "Scene.avatar_toolkit_updater_version_list.description": "List of available versions", + + "Settings.label": "Settings", + "Settings.language": "Language", + "Settings.language_desc": "Select interface language", + "Settings.validation_mode": "Validation Mode", + "Settings.validation_mode_desc": "Choose how strictly to validate armatures", + "Settings.validation_mode.strict": "Strict", + "Settings.validation_mode.strict_desc": "Full validation including bone hierarchy and symmetry", + "Settings.validation_mode.basic": "Basic", + "Settings.validation_mode.basic_desc": "Essential bones check only", + "Settings.validation_mode.none": "None", + "Settings.validation_mode.none_desc": "No armature validation", + "Settings.debug": "Debug Settings", + "Settings.logging": "Logging", + "Settings.enable_logging": "Enable Debug Logging", + "Settings.enable_logging_desc": "Enable detailed debug logging for troubleshooting", + "Settings.logging_enabled": "Debug logging enabled", + "Settings.logging_disabled": "Debug logging disabled", + "Language.auto": "Automatic", + "Language.en_US": "English", + "Language.ja_JP": "Japanese", + "Language.changed.title": "Language Changed", + "Language.changed.success": "Language changed successfully!", + "Language.changed.restart": "Some UI elements may require restarting Blender" } } diff --git a/ui/main_panel.py b/ui/main_panel.py index 7a802c8..6ae130d 100644 --- a/ui/main_panel.py +++ b/ui/main_panel.py @@ -1,5 +1,5 @@ import bpy -from typing import Optional +from typing import Optional, Set from bpy.types import Panel, Context, UILayout from ..core.translations import t @@ -13,12 +13,12 @@ def draw_title(self: Panel) -> None: # Add a nice header row: UILayout = col.row() - row.scale_y = 1.2 + row.scale_y: float = 1.2 row.label(text=t("AvatarToolkit.label"), icon='ARMATURE_DATA') # Description as a flowing paragraph desc_col: UILayout = col.column() - desc_col.scale_y = 0.6 + desc_col.scale_y: float = 0.6 desc_col.label(text=t("AvatarToolkit.desc1")) desc_col.label(text=t("AvatarToolkit.desc2")) desc_col.label(text=t("AvatarToolkit.desc3")) @@ -26,12 +26,12 @@ def draw_title(self: Panel) -> None: class AvatarToolKit_PT_AvatarToolkitPanel(Panel): """Main panel for Avatar Toolkit containing general information and settings""" - bl_label = t("AvatarToolkit.label") - bl_idname = "OBJECT_PT_avatar_toolkit" - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = CATEGORY_NAME - bl_options = {'DEFAULT_CLOSED'} + bl_label: str = t("AvatarToolkit.label") + bl_idname: str = "OBJECT_PT_avatar_toolkit" + bl_space_type: str = 'VIEW_3D' + bl_region_type: str = 'UI' + bl_category: str = CATEGORY_NAME + bl_options: Set[str] = {'DEFAULT_CLOSED'} def draw(self, context: Context) -> None: """Draw the main panel layout""" diff --git a/ui/quick_access_panel.py b/ui/quick_access_panel.py index 66454c2..0b02111 100644 --- a/ui/quick_access_panel.py +++ b/ui/quick_access_panel.py @@ -1,6 +1,14 @@ import bpy -from typing import Set, Optional, List, Tuple -from bpy.types import Operator, Panel, Menu, Context, UILayout +from typing import Set, Dict, List, Optional, Tuple +from bpy.types import ( + Operator, + Panel, + Menu, + Context, + UILayout, + WindowManager, + Object +) from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from ..core.translations import t from ..core.common import ( @@ -20,8 +28,8 @@ from ..functions.pose_mode import ( class AvatarToolKit_OT_Import(Operator): """Import FBX files into Blender with Avatar Toolkit settings""" - bl_idname = "avatar_toolkit.import" - bl_label = t("QuickAccess.import") + bl_idname: str = "avatar_toolkit.import" + bl_label: str = t("QuickAccess.import") def execute(self, context: Context) -> Set[str]: clear_default_objects() @@ -30,8 +38,8 @@ class AvatarToolKit_OT_Import(Operator): class AvatarToolKit_OT_ExportFBX(Operator): """Export selected objects as FBX""" - bl_idname = "avatar_toolkit.export_fbx" - bl_label = t("QuickAccess.export_fbx") + bl_idname: str = "avatar_toolkit.export_fbx" + bl_label: str = t("QuickAccess.export_fbx") def execute(self, context: Context) -> Set[str]: bpy.ops.export_scene.fbx('INVOKE_DEFAULT') @@ -39,8 +47,8 @@ class AvatarToolKit_OT_ExportFBX(Operator): class AvatarToolKit_MT_ExportMenu(Menu): """Export menu containing various export options""" - bl_idname = "AVATAR_TOOLKIT_MT_export_menu" - bl_label = t("QuickAccess.export") + bl_idname: str = "AVATAR_TOOLKIT_MT_export_menu" + bl_label: str = t("QuickAccess.export") def draw(self, context: Context) -> None: layout: UILayout = self.layout @@ -49,22 +57,23 @@ class AvatarToolKit_MT_ExportMenu(Menu): class AvatarToolKit_OT_ExportMenu(Operator): """Open the export menu""" - bl_idname = "avatar_toolkit.export" - bl_label = t("QuickAccess.export") + bl_idname: str = "avatar_toolkit.export" + bl_label: str = t("QuickAccess.export") def execute(self, context: Context) -> Set[str]: - bpy.ops.wm.call_menu(name=AvatarToolKit_MT_ExportMenu.bl_idname) + wm: WindowManager = context.window_manager + wm.call_menu(name=AvatarToolKit_MT_ExportMenu.bl_idname) return {'FINISHED'} class AvatarToolKit_PT_QuickAccessPanel(Panel): """Quick access panel for common Avatar Toolkit operations""" - bl_label = t("QuickAccess.label") - bl_idname = "OBJECT_PT_avatar_toolkit_quick_access" - bl_space_type = 'VIEW_3D' - bl_region_type = 'UI' - bl_category = CATEGORY_NAME - bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname - bl_order = 0 + bl_label: str = t("QuickAccess.label") + bl_idname: str = "OBJECT_PT_avatar_toolkit_quick_access" + bl_space_type: str = 'VIEW_3D' + bl_region_type: str = 'UI' + bl_category: str = CATEGORY_NAME + bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname + bl_order: int = 0 @classmethod def poll(cls, context: Context) -> bool: @@ -85,25 +94,41 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel): col.prop(context.scene.avatar_toolkit, "active_armature", text="") # Armature Validation - active_armature = get_active_armature(context) + active_armature: Optional[Object] = get_active_armature(context) if active_armature: + is_valid: bool + messages: List[str] is_valid, messages = validate_armature(active_armature) + # Create info box for all validation information + info_box: UILayout = col.box() + if is_valid: - info_box: UILayout = col.box() row: UILayout = info_box.row() split: UILayout = row.split(factor=0.6) split.label(text=t("QuickAccess.valid_armature"), icon='CHECKMARK') - stats: dict = get_armature_stats(active_armature) + stats: Dict[str, int] = get_armature_stats(active_armature) split.label(text=t("QuickAccess.bones_count", count=stats['bone_count'])) if stats['has_pose']: info_box.label(text=t("QuickAccess.pose_bones_available"), icon='POSE_HLT') else: - col.separator(factor=0.5) - # Display each validation message + # Display validation failure messages for message in messages: - col.label(text=message, icon='ERROR') + info_box.label(text=message, icon='ERROR') + + # Validation Mode Warnings - always show in info box + validation_mode = context.scene.avatar_toolkit.validation_mode + if validation_mode == 'BASIC': + warning_row = info_box.box() + warning_row.alert = True + warning_row.label(text=t("QuickAccess.validation_basic_warning"), icon='INFO') + warning_row.label(text=t("QuickAccess.validation_basic_details")) + elif validation_mode == 'NONE': + warning_row = info_box.box() + warning_row.alert = True + warning_row.label(text=t("QuickAccess.validation_none_warning"), icon='ERROR') + warning_row.label(text=t("QuickAccess.validation_none_details")) # Pose Mode Controls pose_box: UILayout = layout.box() @@ -130,3 +155,5 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel): button_row.scale_y = 1.5 button_row.operator("avatar_toolkit.import", text=t("QuickAccess.import"), icon='IMPORT') button_row.operator("avatar_toolkit.export", text=t("QuickAccess.export"), icon='EXPORT') + + diff --git a/ui/settings_panel.py b/ui/settings_panel.py new file mode 100644 index 0000000..a948ef3 --- /dev/null +++ b/ui/settings_panel.py @@ -0,0 +1,75 @@ +import bpy +from typing import Set, Dict, List, Optional +from bpy.types import ( + Operator, + Panel, + Context, + UILayout, + WindowManager, + Event +) +from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from ..core.translations import t, get_languages_list + +class AvatarToolkit_OT_TranslationRestartPopup(Operator): + """Popup dialog shown after language change to inform about restart requirement""" + bl_idname: str = "avatar_toolkit.translation_restart_popup" + bl_label: str = t("Language.changed.title") + + def execute(self, context: Context) -> Set[str]: + return {'FINISHED'} + + def invoke(self, context: Context, event: Event) -> Set[str]: + wm: WindowManager = context.window_manager + return wm.invoke_props_dialog(self) + + def draw(self, context: Context) -> None: + layout: UILayout = self.layout + layout.label(text=t("Language.changed.success")) + layout.label(text=t("Language.changed.restart")) + +class AvatarToolKit_PT_SettingsPanel(Panel): + """Settings panel for Avatar Toolkit containing language preferences""" + bl_label: str = t("Settings.label") + bl_idname: str = "OBJECT_PT_avatar_toolkit_settings" + bl_space_type: str = 'VIEW_3D' + bl_region_type: str = 'UI' + bl_category: str = CATEGORY_NAME + bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname + bl_order: int = 2 + + def draw(self, context: Context) -> None: + """Draw the settings panel layout with language selection""" + layout: UILayout = self.layout + + # Language Settings + lang_box: UILayout = layout.box() + col: UILayout = lang_box.column(align=True) + row: UILayout = col.row() + row.scale_y = 1.2 + row.label(text=t("Settings.language"), icon='WORLD') + col.separator() + col.prop(context.scene.avatar_toolkit, "language", text="") + + # Validation Settings + val_box: UILayout = layout.box() + col = val_box.column(align=True) + row = col.row() + row.scale_y = 1.2 + row.label(text=t("Settings.validation_mode"), icon='CHECKMARK') + col.separator() + col.prop(context.scene.avatar_toolkit, "validation_mode", text="") + + # Debug Settings + debug_box = layout.box() + col = debug_box.column() + row = col.row(align=True) + row.prop(context.scene.avatar_toolkit, "debug_expand", + icon="TRIA_DOWN" if context.scene.avatar_toolkit.debug_expand + else "TRIA_RIGHT", + icon_only=True, emboss=False) + row.label(text=t("Settings.debug"), icon='CONSOLE') + + if context.scene.avatar_toolkit.debug_expand: + col = debug_box.column(align=True) + col.prop(context.scene.avatar_toolkit, "enable_logging")