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:
@@ -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,12 +50,13 @@ 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
|
||||||
|
|
||||||
# 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
|
||||||
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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,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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
Reference in New Issue
Block a user