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 os
import tomllib import tomllib
import json import json
from ..core.logging_setup import logger
from bpy.types import AddonPreferences from bpy.types import AddonPreferences
from typing import Any, Dict from typing import Any, Dict
@@ -12,22 +13,31 @@ PREFERENCES_FILE = os.path.join(PREFERENCES_DIR, "preferences.json")
def get_current_version(): def get_current_version():
main_dir = os.path.dirname(os.path.dirname(__file__)) main_dir = os.path.dirname(os.path.dirname(__file__))
manifest_path = os.path.join(main_dir, "blender_manifest.toml") 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: with open(manifest_path, 'rb') as f:
manifest_data = tomllib.load(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: def save_preference(key: str, value: Any) -> None:
"""Save a single preference to the JSON file.""" """Save a single preference to the JSON file."""
logger.debug(f"Saving preference: {key} = {value}")
prefs = load_preferences() prefs = load_preferences()
prefs[key] = value prefs[key] = value
with open(PREFERENCES_FILE, 'w') as f: with open(PREFERENCES_FILE, 'w') as f:
json.dump(prefs, f, indent=4) json.dump(prefs, f, indent=4)
logger.info(f"Preference saved: {key}")
def load_preferences() -> Dict[str, Any]: def load_preferences() -> Dict[str, Any]:
"""Load all preferences from the JSON file.""" """Load all preferences from the JSON file."""
logger.debug(f"Loading preferences from: {PREFERENCES_FILE}")
if os.path.exists(PREFERENCES_FILE): if os.path.exists(PREFERENCES_FILE):
with open(PREFERENCES_FILE, 'r') as f: 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 {} return {}
def get_preference(key: str, default: Any = None) -> Any: def get_preference(key: str, default: Any = None) -> Any:
@@ -40,8 +50,7 @@ class AvatarToolkitPreferences(AddonPreferences):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.label(text="Preferences are managed internally.") layout.label(text=f"Version: {get_current_version()}")
# You can add more UI elements here if needed
def get_addon_preferences(context): def get_addon_preferences(context):
return context.preferences.addons[AvatarToolkitPreferences.bl_idname].preferences 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 # Initialize preferences if the file doesn't exist
if not os.path.exists(PREFERENCES_FILE): if not os.path.exists(PREFERENCES_FILE):
save_preference("language", 0) # Set default language to 0 (auto) 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""" """Initialize the auto-loader by discovering modules and classes"""
global modules global modules
global ordered_classes 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") print("Auto-load init starting")
modules = get_all_submodules(Path(__file__).parent.parent) modules = get_all_submodules(Path(__file__).parent.parent)
ordered_classes = get_ordered_classes_to_register(modules) ordered_classes = get_ordered_classes_to_register(modules)
+13 -17
View File
@@ -1,22 +1,11 @@
import bpy import bpy
import numpy as np import numpy as np
import logging
from bpy.types import Context, Object, Modifier from bpy.types import Context, Object, Modifier
from typing import Optional, Tuple, List, Set, Dict, Any, Generator, Callable from typing import Optional, Tuple, List, Set, Dict, Any, Generator, Callable
from ..core.logging_setup import logger
from ..core.translations import t from ..core.translations import t
from ..core.dictionaries import bone_names 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: class ProgressTracker:
"""Universal progress tracking for Avatar Toolkit operations""" """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 [('NONE', t("Armature.validation.no_armature"), '')]
return armatures return armatures
def validate_armature(armature: bpy.types.Object, validation_level: str = 'standard') -> Tuple[bool, List[str]]: def validate_armature(armature: bpy.types.Object) -> Tuple[bool, List[str]]:
"""Enhanced armature validation with multiple checks and validation levels""" """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 = [] messages = []
# Basic checks # Basic checks always run if not NONE
if not armature or armature.type != 'ARMATURE' or not armature.data.bones: if not armature or armature.type != 'ARMATURE' or not armature.data.bones:
return False, [t("Armature.validation.basic_check_failed")] return False, [t("Armature.validation.basic_check_failed")]
found_bones = {bone.name.lower(): bone for bone in armature.data.bones} 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'} essential_bones = {'hips', 'spine', 'chest', 'neck', 'head'}
missing_bones = [] missing_bones = []
for bone in essential_bones: for bone in essential_bones:
@@ -83,7 +78,8 @@ def validate_armature(armature: bpy.types.Object, validation_level: str = 'stand
if missing_bones: if missing_bones:
messages.append(t("Armature.validation.missing_bones", bones=", ".join(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 validation
hierarchy = [('hips', 'spine'), ('spine', 'chest'), ('chest', 'neck'), ('neck', 'head')] hierarchy = [('hips', 'spine'), ('spine', 'chest'), ('chest', 'neck'), ('neck', 'head')]
for parent, child in hierarchy: 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, CollectionProperty,
PointerProperty PointerProperty
) )
from .logging_setup import logger
from .translations import t, get_languages_list, update_language 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 .updater import get_version_list
from .common import get_armature_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): class AvatarToolkitSceneProperties(PropertyGroup):
"""Property group containing Avatar Toolkit scene-level settings and properties""" """Property group containing Avatar Toolkit scene-level settings and properties"""
@@ -30,10 +41,46 @@ class AvatarToolkitSceneProperties(PropertyGroup):
description=t("QuickAccess.select_armature") 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: def register() -> None:
"""Register the Avatar Toolkit property group""" """Register the Avatar Toolkit property group"""
logger.info("Registering Avatar Toolkit properties")
bpy.types.Scene.avatar_toolkit = PointerProperty(type=AvatarToolkitSceneProperties) bpy.types.Scene.avatar_toolkit = PointerProperty(type=AvatarToolkitSceneProperties)
logger.debug("Properties registered successfully")
def unregister() -> None: def unregister() -> None:
"""Unregister the Avatar Toolkit property group""" """Unregister the Avatar Toolkit property group"""
logger.info("Unregistering Avatar Toolkit properties")
del bpy.types.Scene.avatar_toolkit del bpy.types.Scene.avatar_toolkit
logger.debug("Properties unregistered successfully")
+45 -30
View File
@@ -1,10 +1,15 @@
import os import os
import json import json
import bpy import bpy
import logging
from bpy.app.translations import locale 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 from .addon_preferences import save_preference, get_preference
# Set up logging
logger = logging.getLogger(__name__)
# Use __file__ to get the current file's directory # Use __file__ to get the current file's directory
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
main_dir = os.path.dirname(current_dir) main_dir = os.path.dirname(current_dir)
@@ -13,9 +18,15 @@ translations_dir = os.path.join(resources_dir, "translations")
dictionary: Dict[str, str] = dict() dictionary: Dict[str, str] = dict()
languages: List[str] = [] languages: List[str] = []
_translation_cache: Dict[str, Dict[str, str]] = {}
verbose: bool = True verbose: bool = True
def get_fallback_language() -> str:
"""Return the default fallback language"""
return "en_US"
def load_translations() -> bool: def load_translations() -> bool:
"""Load translations for the selected language"""
global dictionary, languages global dictionary, languages
old_dictionary = dictionary.copy() old_dictionary = dictionary.copy()
@@ -29,69 +40,73 @@ def load_translations() -> bool:
if lang != "auto": if lang != "auto":
languages.append(lang) languages.append(lang)
language_index = get_preference("language", 0) language_index: int = get_preference("language", 0)
# print(f"Loading translations for language index: {language_index}") # Debug print logger.debug(f"Loading translations for language index: {language_index}")
if language_index == 0: # "auto" if language_index == 0: # "auto"
language = bpy.context.preferences.view.language language: str = bpy.context.preferences.view.language
else: else:
try: try:
language = languages[language_index] language = languages[language_index]
except IndexError: except IndexError:
language = bpy.context.preferences.view.language 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") translation_file: str = os.path.join(translations_dir, language + ".json")
if os.path.exists(translation_file): if os.path.exists(translation_file):
with open(translation_file, 'r', encoding='utf-8') as file: dictionary = _load_translation_file(translation_file)
dictionary = json.load(file)["messages"]
# print(f"Loaded translations: {dictionary}") # Debug print
else: else:
custom_language: str = language.split("_")[0] custom_language: str = language.split("_")[0]
custom_translation_file: str = os.path.join(translations_dir, custom_language + ".json") custom_translation_file: str = os.path.join(translations_dir, custom_language + ".json")
if os.path.exists(custom_translation_file): if os.path.exists(custom_translation_file):
with open(custom_translation_file, 'r', encoding='utf-8') as file: dictionary = _load_translation_file(custom_translation_file)
dictionary = json.load(file)["messages"]
# print(f"Loaded custom translations: {dictionary}") # Debug print
else: else:
print(f"Translation file not found for language: {language}") logger.warning(f"Translation file not found for language: {language}")
default_file: str = os.path.join(translations_dir, "en_US.json") default_file: str = os.path.join(translations_dir, get_fallback_language() + ".json")
if os.path.exists(default_file): if os.path.exists(default_file):
with open(default_file, 'r', encoding='utf-8') as file: dictionary = _load_translation_file(default_file)
dictionary = json.load(file)["messages"]
# print(f"Loaded default translations: {dictionary}") # Debug print
else: 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 return dictionary != old_dictionary
def t(phrase: str, default: str = None, **kwargs) -> str: def _load_translation_file(file_path: str) -> Dict[str, str]:
output: str = dictionary.get(phrase) """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 output is None:
if verbose: if verbose:
print(f'Warning: Unknown phrase: {phrase}') logger.warning(f'Unknown phrase: {phrase}')
return default if default is not None else 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 return output.format(**kwargs) if kwargs else output
def get_language_display_name(lang: str) -> str: def get_language_display_name(lang: str) -> str:
if lang == "auto": """Get the display name for a language code"""
return t("Language.auto", "Automatic")
return t(f"Language.{lang}", lang) return t(f"Language.{lang}", lang)
def get_languages_list(self, context) -> List[Tuple[str, str, str]]: def get_languages_list(self: Any, context: Any) -> List[Tuple[str, str, str]]:
return [(str(i), get_language_display_name(lang), f"Use {lang} language") for i, lang in enumerate(languages)] """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): def update_language(self: Any, context: Any) -> None:
print(f"Updating language to: {self.language}") # Debug print """Handle language update and UI refresh"""
logger.info(f"Updating language to: {self.language}")
save_preference("language", int(self.language)) save_preference("language", int(self.language))
load_translations() load_translations()
# Set a flag to indicate that a language change has occurred
context.scene.avatar_toolkit.language_changed = True context.scene.avatar_toolkit.language_changed = True
# Show popup after language change
bpy.ops.avatar_toolkit.translation_restart_popup('INVOKE_DEFAULT') bpy.ops.avatar_toolkit.translation_restart_popup('INVOKE_DEFAULT')
# Initial load of translations # Initial load of translations
# print("Performing initial load of translations") # Debug print
load_translations() load_translations()
+1 -1
View File
@@ -76,7 +76,7 @@ class AvatarToolkit_PT_UpdaterPanel(bpy.types.Panel):
bl_region_type = 'UI' bl_region_type = 'UI'
bl_category = CATEGORY_NAME bl_category = CATEGORY_NAME
bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
bl_order = 9 bl_order = 1
def draw(self, context: bpy.types.Context) -> None: def draw(self, context: bpy.types.Context) -> None:
layout = self.layout layout = self.layout
+1 -3
View File
@@ -1,8 +1,8 @@
import bpy import bpy
import logging
from typing import Set, Dict, List, Tuple, Optional, Any from typing import Set, Dict, List, Tuple, Optional, Any
from bpy.props import StringProperty from bpy.props import StringProperty
from bpy.types import Operator, Context, Object, Event, Modifier from bpy.types import Operator, Context, Object, Event, Modifier
from ..core.logging_setup import logger
from ..core.translations import t from ..core.translations import t
from ..core.common import ( from ..core.common import (
get_active_armature, get_active_armature,
@@ -16,8 +16,6 @@ from ..core.common import (
ProgressTracker ProgressTracker
) )
logger = logging.getLogger('avatar_toolkit.pose')
class BatchPoseOperationMixin: class BatchPoseOperationMixin:
"""Base class for batch pose operations""" """Base class for batch pose operations"""
@classmethod @classmethod
+29 -1
View File
@@ -46,6 +46,10 @@
"QuickAccess.apply_pose_as_rest.label": "Apply Pose as Rest", "QuickAccess.apply_pose_as_rest.label": "Apply Pose as Rest",
"QuickAccess.apply_pose_as_rest.desc": "Apply current pose as rest pose", "QuickAccess.apply_pose_as_rest.desc": "Apply current pose as rest pose",
"QuickAccess.apply_armature_failed": "Failed to apply armature modifications", "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.start": "Failed to start pose mode: {error}",
"PoseMode.error.stop": "Failed to stop pose mode: {error}", "PoseMode.error.stop": "Failed to stop pose mode: {error}",
@@ -74,6 +78,30 @@
"Operation.pose_applied": "Pose applied successfully", "Operation.pose_applied": "Pose applied successfully",
"Scene.avatar_toolkit_updater_version_list.name": "Version List", "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 import bpy
from typing import Optional from typing import Optional, Set
from bpy.types import Panel, Context, UILayout from bpy.types import Panel, Context, UILayout
from ..core.translations import t from ..core.translations import t
@@ -13,12 +13,12 @@ def draw_title(self: Panel) -> None:
# Add a nice header # Add a nice header
row: UILayout = col.row() row: UILayout = col.row()
row.scale_y = 1.2 row.scale_y: float = 1.2
row.label(text=t("AvatarToolkit.label"), icon='ARMATURE_DATA') row.label(text=t("AvatarToolkit.label"), icon='ARMATURE_DATA')
# Description as a flowing paragraph # Description as a flowing paragraph
desc_col: UILayout = col.column() 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.desc1"))
desc_col.label(text=t("AvatarToolkit.desc2")) desc_col.label(text=t("AvatarToolkit.desc2"))
desc_col.label(text=t("AvatarToolkit.desc3")) desc_col.label(text=t("AvatarToolkit.desc3"))
@@ -26,12 +26,12 @@ def draw_title(self: Panel) -> None:
class AvatarToolKit_PT_AvatarToolkitPanel(Panel): class AvatarToolKit_PT_AvatarToolkitPanel(Panel):
"""Main panel for Avatar Toolkit containing general information and settings""" """Main panel for Avatar Toolkit containing general information and settings"""
bl_label = t("AvatarToolkit.label") bl_label: str = t("AvatarToolkit.label")
bl_idname = "OBJECT_PT_avatar_toolkit" bl_idname: str = "OBJECT_PT_avatar_toolkit"
bl_space_type = 'VIEW_3D' bl_space_type: str = 'VIEW_3D'
bl_region_type = 'UI' bl_region_type: str = 'UI'
bl_category = CATEGORY_NAME bl_category: str = CATEGORY_NAME
bl_options = {'DEFAULT_CLOSED'} bl_options: Set[str] = {'DEFAULT_CLOSED'}
def draw(self, context: Context) -> None: def draw(self, context: Context) -> None:
"""Draw the main panel layout""" """Draw the main panel layout"""
+51 -24
View File
@@ -1,6 +1,14 @@
import bpy import bpy
from typing import Set, Optional, List, Tuple from typing import Set, Dict, List, Optional, Tuple
from bpy.types import Operator, Panel, Menu, Context, UILayout from bpy.types import (
Operator,
Panel,
Menu,
Context,
UILayout,
WindowManager,
Object
)
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
from ..core.translations import t from ..core.translations import t
from ..core.common import ( from ..core.common import (
@@ -20,8 +28,8 @@ from ..functions.pose_mode import (
class AvatarToolKit_OT_Import(Operator): class AvatarToolKit_OT_Import(Operator):
"""Import FBX files into Blender with Avatar Toolkit settings""" """Import FBX files into Blender with Avatar Toolkit settings"""
bl_idname = "avatar_toolkit.import" bl_idname: str = "avatar_toolkit.import"
bl_label = t("QuickAccess.import") bl_label: str = t("QuickAccess.import")
def execute(self, context: Context) -> Set[str]: def execute(self, context: Context) -> Set[str]:
clear_default_objects() clear_default_objects()
@@ -30,8 +38,8 @@ class AvatarToolKit_OT_Import(Operator):
class AvatarToolKit_OT_ExportFBX(Operator): class AvatarToolKit_OT_ExportFBX(Operator):
"""Export selected objects as FBX""" """Export selected objects as FBX"""
bl_idname = "avatar_toolkit.export_fbx" bl_idname: str = "avatar_toolkit.export_fbx"
bl_label = t("QuickAccess.export_fbx") bl_label: str = t("QuickAccess.export_fbx")
def execute(self, context: Context) -> Set[str]: def execute(self, context: Context) -> Set[str]:
bpy.ops.export_scene.fbx('INVOKE_DEFAULT') bpy.ops.export_scene.fbx('INVOKE_DEFAULT')
@@ -39,8 +47,8 @@ class AvatarToolKit_OT_ExportFBX(Operator):
class AvatarToolKit_MT_ExportMenu(Menu): class AvatarToolKit_MT_ExportMenu(Menu):
"""Export menu containing various export options""" """Export menu containing various export options"""
bl_idname = "AVATAR_TOOLKIT_MT_export_menu" bl_idname: str = "AVATAR_TOOLKIT_MT_export_menu"
bl_label = t("QuickAccess.export") bl_label: str = t("QuickAccess.export")
def draw(self, context: Context) -> None: def draw(self, context: Context) -> None:
layout: UILayout = self.layout layout: UILayout = self.layout
@@ -49,22 +57,23 @@ class AvatarToolKit_MT_ExportMenu(Menu):
class AvatarToolKit_OT_ExportMenu(Operator): class AvatarToolKit_OT_ExportMenu(Operator):
"""Open the export menu""" """Open the export menu"""
bl_idname = "avatar_toolkit.export" bl_idname: str = "avatar_toolkit.export"
bl_label = t("QuickAccess.export") bl_label: str = t("QuickAccess.export")
def execute(self, context: Context) -> Set[str]: 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'} return {'FINISHED'}
class AvatarToolKit_PT_QuickAccessPanel(Panel): class AvatarToolKit_PT_QuickAccessPanel(Panel):
"""Quick access panel for common Avatar Toolkit operations""" """Quick access panel for common Avatar Toolkit operations"""
bl_label = t("QuickAccess.label") bl_label: str = t("QuickAccess.label")
bl_idname = "OBJECT_PT_avatar_toolkit_quick_access" bl_idname: str = "OBJECT_PT_avatar_toolkit_quick_access"
bl_space_type = 'VIEW_3D' bl_space_type: str = 'VIEW_3D'
bl_region_type = 'UI' bl_region_type: str = 'UI'
bl_category = CATEGORY_NAME bl_category: str = CATEGORY_NAME
bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
bl_order = 0 bl_order: int = 0
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
@@ -85,25 +94,41 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
col.prop(context.scene.avatar_toolkit, "active_armature", text="") col.prop(context.scene.avatar_toolkit, "active_armature", text="")
# Armature Validation # Armature Validation
active_armature = get_active_armature(context) active_armature: Optional[Object] = get_active_armature(context)
if active_armature: if active_armature:
is_valid: bool
messages: List[str]
is_valid, messages = validate_armature(active_armature) is_valid, messages = validate_armature(active_armature)
# Create info box for all validation information
info_box: UILayout = col.box()
if is_valid: if is_valid:
info_box: UILayout = col.box()
row: UILayout = info_box.row() row: UILayout = info_box.row()
split: UILayout = row.split(factor=0.6) split: UILayout = row.split(factor=0.6)
split.label(text=t("QuickAccess.valid_armature"), icon='CHECKMARK') 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'])) split.label(text=t("QuickAccess.bones_count", count=stats['bone_count']))
if stats['has_pose']: if stats['has_pose']:
info_box.label(text=t("QuickAccess.pose_bones_available"), icon='POSE_HLT') info_box.label(text=t("QuickAccess.pose_bones_available"), icon='POSE_HLT')
else: else:
col.separator(factor=0.5) # Display validation failure messages
# Display each validation message
for message in 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 Mode Controls
pose_box: UILayout = layout.box() pose_box: UILayout = layout.box()
@@ -130,3 +155,5 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
button_row.scale_y = 1.5 button_row.scale_y = 1.5
button_row.operator("avatar_toolkit.import", text=t("QuickAccess.import"), icon='IMPORT') button_row.operator("avatar_toolkit.import", text=t("QuickAccess.import"), icon='IMPORT')
button_row.operator("avatar_toolkit.export", text=t("QuickAccess.export"), icon='EXPORT') 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")