Setting Panel Added, Debuging Added.

Added the Armature Validation modes now, we have Stritct, Basic and None, it will give a warning to the user in the panel if there have it set to basic or none.
Settings panel added, langauge change has been added back. Did some work on it to slightl improve the system.
Added dubug area, basically everything but autoload will use logging now, you be able to turn it on/off in debug settings.
Did other bits and bobs.
This commit is contained in:
Yusarina
2024-12-04 14:58:34 +00:00
parent 5dcaba381d
commit 9961223548
12 changed files with 322 additions and 91 deletions
+15 -4
View File
@@ -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,8 +50,7 @@ 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
@@ -49,3 +58,5 @@ def get_addon_preferences(context):
# 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)
save_preference("validation_mode", "STRICT") # Set default validation mode
save_preference("enable_logging", False) # Set default logging mode
+8
View File
@@ -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)
+13 -17
View File
@@ -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:
+26
View File
@@ -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)
+48 -1
View File
@@ -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")
+45 -30
View File
@@ -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()
+1 -1
View File
@@ -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
+1 -3
View File
@@ -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
+29 -1
View File
@@ -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"
}
}
+9 -9
View File
@@ -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"""
+51 -24
View File
@@ -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)
if is_valid:
# Create info box for all validation information
info_box: UILayout = col.box()
if is_valid:
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')
+75
View File
@@ -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")