Merge branch 'main' into pr/82
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
# core/__init__.py
|
||||
|
||||
from .register import register_wrap
|
||||
|
||||
#to reload all things in this directory and import them properly - @989onan
|
||||
if "bpy" not in locals():
|
||||
import bpy
|
||||
import glob
|
||||
import os
|
||||
from os.path import dirname, basename, isfile, join
|
||||
modules = glob.glob(join(dirname(__file__), "*.py"))
|
||||
for module_name in [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]:
|
||||
exec("from . import "+module_name)
|
||||
print("importing " +module_name)
|
||||
else:
|
||||
import importlib
|
||||
modules = glob.glob(join(dirname(__file__), "*.py"))
|
||||
for module_name in [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]:
|
||||
exec("importlib.reload("+module_name+")")
|
||||
print("reloading " +module_name)
|
||||
|
||||
@@ -2,6 +2,7 @@ import bpy
|
||||
import os
|
||||
import tomllib
|
||||
import json
|
||||
from ..core.logging_setup import logger
|
||||
from bpy.types import AddonPreferences
|
||||
from typing import Any, Dict
|
||||
|
||||
@@ -12,22 +13,31 @@ PREFERENCES_FILE = os.path.join(PREFERENCES_DIR, "preferences.json")
|
||||
def get_current_version():
|
||||
main_dir = os.path.dirname(os.path.dirname(__file__))
|
||||
manifest_path = os.path.join(main_dir, "blender_manifest.toml")
|
||||
logger.debug(f"Reading version from manifest: {manifest_path}")
|
||||
with open(manifest_path, 'rb') as f:
|
||||
manifest_data = tomllib.load(f)
|
||||
return manifest_data.get('version', 'Unknown')
|
||||
version = manifest_data.get('version', 'Unknown')
|
||||
logger.info(f"Current addon version: {version}")
|
||||
return version
|
||||
|
||||
def save_preference(key: str, value: Any) -> None:
|
||||
"""Save a single preference to the JSON file."""
|
||||
logger.debug(f"Saving preference: {key} = {value}")
|
||||
prefs = load_preferences()
|
||||
prefs[key] = value
|
||||
with open(PREFERENCES_FILE, 'w') as f:
|
||||
json.dump(prefs, f, indent=4)
|
||||
logger.info(f"Preference saved: {key}")
|
||||
|
||||
def load_preferences() -> Dict[str, Any]:
|
||||
"""Load all preferences from the JSON file."""
|
||||
logger.debug(f"Loading preferences from: {PREFERENCES_FILE}")
|
||||
if os.path.exists(PREFERENCES_FILE):
|
||||
with open(PREFERENCES_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
prefs = json.load(f)
|
||||
logger.debug(f"Loaded preferences: {prefs}")
|
||||
return prefs
|
||||
logger.info("No preferences file found, using defaults")
|
||||
return {}
|
||||
|
||||
def get_preference(key: str, default: Any = None) -> Any:
|
||||
@@ -40,12 +50,13 @@ class AvatarToolkitPreferences(AddonPreferences):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Preferences are managed internally.")
|
||||
# You can add more UI elements here if needed
|
||||
layout.label(text=f"Version: {get_current_version()}")
|
||||
|
||||
def get_addon_preferences(context):
|
||||
return context.preferences.addons[AvatarToolkitPreferences.bl_idname].preferences
|
||||
|
||||
# Initialize preferences if the file doesn't exist
|
||||
if not os.path.exists(PREFERENCES_FILE):
|
||||
save_preference("language", 0) # Set default language to 0 (auto)
|
||||
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
|
||||
@@ -0,0 +1,193 @@
|
||||
import os
|
||||
import bpy
|
||||
import sys
|
||||
import typing
|
||||
import inspect
|
||||
import pkgutil
|
||||
import tomllib
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Set, Optional, Any, Type, Tuple, Generator, TypeVar
|
||||
|
||||
__all__ = (
|
||||
"init",
|
||||
"register",
|
||||
"unregister",
|
||||
)
|
||||
|
||||
T = TypeVar('T')
|
||||
modules: Optional[List[Any]] = None
|
||||
ordered_classes: Optional[List[Type]] = None
|
||||
|
||||
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)
|
||||
print(f"Found modules: {modules}")
|
||||
print(f"Found classes: {ordered_classes}")
|
||||
|
||||
def register() -> None:
|
||||
"""Register all discovered classes and modules"""
|
||||
print("Registering classes")
|
||||
for cls in ordered_classes:
|
||||
print(f"Registering: {cls}")
|
||||
try:
|
||||
bpy.utils.register_class(cls)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
for module in modules:
|
||||
if module.__name__ == __name__:
|
||||
continue
|
||||
if hasattr(module, "register"):
|
||||
module.register()
|
||||
|
||||
def unregister() -> None:
|
||||
"""Unregister all classes and modules in reverse order"""
|
||||
for cls in reversed(ordered_classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
for module in modules:
|
||||
if module.__name__ == __name__:
|
||||
continue
|
||||
if hasattr(module, "unregister"):
|
||||
module.unregister()
|
||||
|
||||
def get_manifest_id() -> str:
|
||||
"""Get the addon ID from the manifest file"""
|
||||
manifest_path = Path(__file__).parent.parent / "blender_manifest.toml"
|
||||
with open(manifest_path, "rb") as f:
|
||||
manifest = tomllib.load(f)
|
||||
return manifest["id"]
|
||||
|
||||
def get_all_submodules(directory: Path) -> List[Any]:
|
||||
"""Discover and import all submodules in the given directory"""
|
||||
modules = []
|
||||
addon_id = get_manifest_id()
|
||||
for root, dirs, files in os.walk(directory):
|
||||
if "__pycache__" in root:
|
||||
continue
|
||||
path = Path(root)
|
||||
if path == directory:
|
||||
package_name = f"bl_ext.user_default.{addon_id}"
|
||||
else:
|
||||
relative_path = path.relative_to(directory).as_posix().replace('/', '.')
|
||||
package_name = f"bl_ext.user_default.{addon_id}.{relative_path}"
|
||||
for name in sorted(iter_module_names(path)):
|
||||
modules.append(importlib.import_module(f".{name}", package_name))
|
||||
return modules
|
||||
|
||||
def iter_submodules(path: Path, package_name: str) -> Generator[Any, None, None]:
|
||||
"""Iterate through submodules in a package"""
|
||||
for name in sorted(iter_module_names(path)):
|
||||
yield importlib.import_module("." + name, package_name)
|
||||
|
||||
def iter_module_names(path: Path) -> Generator[str, None, None]:
|
||||
"""Iterate through module names in a directory"""
|
||||
print(f"Scanning path: {path}")
|
||||
modules_list = list(pkgutil.iter_modules([str(path)]))
|
||||
print(f"Found these modules: {modules_list}")
|
||||
for _, module_name, is_pkg in modules_list:
|
||||
if not is_pkg:
|
||||
print(f"Found module: {module_name}")
|
||||
yield module_name
|
||||
|
||||
def get_ordered_classes_to_register(modules: List[Any]) -> List[Type]:
|
||||
"""Get a topologically sorted list of classes to register"""
|
||||
return toposort(get_register_deps_dict(modules))
|
||||
|
||||
def get_register_deps_dict(modules: List[Any]) -> Dict[Type, Set[Type]]:
|
||||
"""Get dependencies dictionary for class registration"""
|
||||
deps_dict = {}
|
||||
classes_to_register = set(iter_classes_to_register(modules))
|
||||
for cls in classes_to_register:
|
||||
deps_dict[cls] = set(iter_own_register_deps(cls, classes_to_register))
|
||||
return deps_dict
|
||||
|
||||
def iter_own_register_deps(cls: Type, classes_to_register: Set[Type]) -> Generator[Type, None, None]:
|
||||
"""Iterate through a class's own registration dependencies"""
|
||||
yield from (dep for dep in iter_register_deps(cls) if dep in classes_to_register)
|
||||
|
||||
def iter_register_deps(cls: Type) -> Generator[Type, None, None]:
|
||||
"""Iterate through all registration dependencies of a class"""
|
||||
for value in typing.get_type_hints(cls, {}, {}).values():
|
||||
dependency = get_dependency_from_annotation(value)
|
||||
if dependency is not None:
|
||||
yield dependency
|
||||
|
||||
def get_dependency_from_annotation(value: Any) -> Optional[Type]:
|
||||
"""Get dependency type from a type annotation"""
|
||||
if isinstance(value, tuple) and len(value) == 2:
|
||||
if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty):
|
||||
return value[1]["type"]
|
||||
return None
|
||||
|
||||
def iter_classes_to_register(modules: List[Any]) -> Generator[Type, None, None]:
|
||||
"""Iterate through classes that need to be registered"""
|
||||
base_types = get_register_base_types()
|
||||
for cls in get_classes_in_modules(modules):
|
||||
if any(base in base_types for base in cls.__bases__):
|
||||
if not getattr(cls, "_is_registered", False):
|
||||
yield cls
|
||||
|
||||
def get_classes_in_modules(modules: List[Any]) -> Set[Type]:
|
||||
"""Get all classes defined in the modules"""
|
||||
classes = set()
|
||||
for module in modules:
|
||||
for cls in iter_classes_in_module(module):
|
||||
classes.add(cls)
|
||||
return classes
|
||||
|
||||
def iter_classes_in_module(module: Any) -> Generator[Type, None, None]:
|
||||
"""Iterate through classes defined in a module"""
|
||||
for value in module.__dict__.values():
|
||||
if inspect.isclass(value):
|
||||
yield value
|
||||
|
||||
def get_register_base_types() -> Set[Type]:
|
||||
"""Get set of base types that need registration"""
|
||||
return set(getattr(bpy.types, name) for name in [
|
||||
"Panel", "Operator", "PropertyGroup",
|
||||
"AddonPreferences", "Header", "Menu",
|
||||
"Node", "NodeSocket", "NodeTree",
|
||||
"UIList", "RenderEngine"
|
||||
])
|
||||
|
||||
def toposort(deps_dict: Dict[Type, Set[Type]]) -> List[Type]:
|
||||
"""Topologically sort classes based on their dependencies"""
|
||||
sorted_list = []
|
||||
sorted_values = set()
|
||||
|
||||
panels_to_sort = [(value, deps) for value, deps in deps_dict.items()
|
||||
if hasattr(value, 'bl_parent_id')]
|
||||
|
||||
base_panels = [(value, deps) for value, deps in deps_dict.items()
|
||||
if not hasattr(value, 'bl_parent_id')]
|
||||
|
||||
for value, deps in base_panels:
|
||||
if len(deps) == 0:
|
||||
sorted_list.append(value)
|
||||
sorted_values.add(value)
|
||||
|
||||
while len(deps_dict) > len(sorted_values):
|
||||
unsorted = []
|
||||
for value, deps in deps_dict.items():
|
||||
if value not in sorted_values:
|
||||
if len(deps - sorted_values) == 0:
|
||||
sorted_list.append(value)
|
||||
sorted_values.add(value)
|
||||
else:
|
||||
unsorted.append(value)
|
||||
|
||||
return sorted_list
|
||||
+532
-404
File diff suppressed because it is too large
Load Diff
+324
-80
@@ -5,89 +5,277 @@
|
||||
# Note from @989onan: Please make sure to make your names are lowercase in this array. I banged my head metaphorically till I figured that out...
|
||||
# Taken from Tuxedo/Cats
|
||||
bone_names = {
|
||||
"right_shoulder": ["rightshoulder", "shoulderr", "rshoulder", "valvebipedbip01rclavicle"],
|
||||
"right_arm": ["rightarm", "armr", "rarm", "upperarmr", "rupperarm", "rightupperarm", "uparmr", "ruparm", "valvebipedbip01rupperarm"],
|
||||
"right_elbow": ["rightelbow", "elbowr", "relbow", "lowerarmr", "rightlowerarm", "lowerarmr","rlowerarm", "lowarmr", "rlowarm", "forearmr","rforearm", "valvebipedbip01rforearm"],
|
||||
"right_wrist": ["rightwrist", "wristr", "rwrist", "handr", "righthand", "rhand", "valvebipedbip01rhand"],
|
||||
# Right side bones
|
||||
"right_shoulder": [
|
||||
"rightshoulder", "shoulderr", "rshoulder", "valvebipedbip01rclavicle",
|
||||
"右肩", "肩.r", "肩+.r", "右肩+", "右肩", "右肩+", "肩+r", "肩+右", "ik_肩.r"
|
||||
],
|
||||
"right_arm": [
|
||||
"rightarm", "armr", "rarm", "upperarmr", "rupperarm", "rightupperarm",
|
||||
"uparmr", "ruparm", "valvebipedbip01rupperarm", "右腕", "腕.r", "右腕", "ik_腕.r"
|
||||
],
|
||||
"right_elbow": [
|
||||
"rightelbow", "elbowr", "relbow", "lowerarmr", "rightlowerarm",
|
||||
"rlowerarm", "lowarmr", "rlowarm", "forearmr", "rforearm",
|
||||
"valvebipedbip01rforearm", "右ひじ", "ひじ.r", "ik_ひじ.r"
|
||||
],
|
||||
"right_wrist": [
|
||||
"rightwrist", "wristr", "rwrist", "handr", "righthand", "rhand",
|
||||
"valvebipedbip01rhand", "右手首", "手首.r", "ik_手首.r"
|
||||
],
|
||||
"pinkie_0_r": [
|
||||
"littlefinger0r", "pinkie0r", "rpinkie0", "pinkiemetacarpalr", "右小指0"
|
||||
],
|
||||
"pinkie_1_r": [
|
||||
"littlefinger1r", "pinkie1r", "rpinkie1", "pinkieproximalr",
|
||||
"valvebipedbip01rfinger4", "右小指1"
|
||||
],
|
||||
"pinkie_2_r": [
|
||||
"littlefinger2r", "pinkie2r", "rpinkie2", "pinkieintermediater",
|
||||
"valvebipedbip01rfinger41", "右小指2"
|
||||
],
|
||||
"pinkie_3_r": [
|
||||
"littlefinger3r", "pinkie3r", "rpinkie3", "pinkiedistalr",
|
||||
"valvebipedbip01rfinger42", "右小指3"
|
||||
],
|
||||
"ring_0_r": [
|
||||
"ringfinger0r", "ring0r", "rring0", "ringmetacarpalr", "右薬指0"
|
||||
],
|
||||
"ring_1_r": [
|
||||
"ringfinger1r", "ring1r", "rring1", "ringproximalr",
|
||||
"valvebipedbip01rfinger3", "右薬指1"
|
||||
],
|
||||
"ring_2_r": [
|
||||
"ringfinger2r", "ring2r", "rring2", "ringintermediater",
|
||||
"valvebipedbip01rfinger31", "右薬指2"
|
||||
],
|
||||
"ring_3_r": [
|
||||
"ringfinger3r", "ring3r", "rring3", "ringdistalr",
|
||||
"valvebipedbip01rfinger32", "右薬指3"
|
||||
],
|
||||
"middle_0_r": [
|
||||
"middlefinger0r", "middle0r", "rmiddle0", "middlemetacarpalr", "右中指0"
|
||||
],
|
||||
"middle_1_r": [
|
||||
"middlefinger1r", "middle1r", "rmiddle1", "middleproximalr",
|
||||
"valvebipedbip01rfinger2", "右中指1"
|
||||
],
|
||||
"middle_2_r": [
|
||||
"middlefinger2r", "middle2r", "rmiddle2", "middleintermediater",
|
||||
"valvebipedbip01rfinger21", "右中指2"
|
||||
],
|
||||
"middle_3_r": [
|
||||
"middlefinger3r", "middle3r", "rmiddle3", "middledistalr",
|
||||
"valvebipedbip01rfinger22", "右中指3"
|
||||
],
|
||||
"index_0_r": [
|
||||
"indexfinger0r", "index0r", "rindex0", "indexmetacarpalr", "右人差指0"
|
||||
],
|
||||
"index_1_r": [
|
||||
"indexfinger1r", "index1r", "rindex1", "indexproximalr",
|
||||
"valvebipedbip01rfinger1", "右人差指1"
|
||||
],
|
||||
"index_2_r": [
|
||||
"indexfinger2r", "index2r", "rindex2", "indexintermediater",
|
||||
"valvebipedbip01rfinger11", "右人差指2"
|
||||
],
|
||||
"index_3_r": [
|
||||
"indexfinger3r", "index3r", "rindex3", "indexdistalr",
|
||||
"valvebipedbip01rfinger12", "右人差指3"
|
||||
],
|
||||
"thumb_0_r": [
|
||||
"thumb0r", "rthumb0", "thumbmetacarpalr", "右親指0"
|
||||
],
|
||||
"thumb_1_r": [
|
||||
"thumb1r", "rthumb1", "thumbproximalr", "valvebipedbip01rfinger0", "右親指1"
|
||||
],
|
||||
"thumb_2_r": [
|
||||
"thumb2r", "rthumb2", "thumbintermediater", "valvebipedbip01rfinger01", "右親指2"
|
||||
],
|
||||
"thumb_3_r": [
|
||||
"thumb3r", "rthumb3", "thumbdistalr", "valvebipedbip01rfinger02", "右親指3"
|
||||
],
|
||||
"right_leg": [
|
||||
"rightleg", "legr", "rleg", "upperlegr", "rupperleg", "thighr",
|
||||
"rightupperleg", "uplegr", "rupleg", "valvebipedbip01rthigh",
|
||||
"右足", "足.r", "ik_足.r"
|
||||
],
|
||||
"right_knee": [
|
||||
"rightknee", "kneer", "rknee", "lowerlegr", "rightlowerleg",
|
||||
"rlowerleg", "lowlegr", "rlowleg", "calfr", "rcalf",
|
||||
"valvebipedbip01rcalf", "右ひざ", "ひざ.r", "すね.r", "ik_ひざ.r"
|
||||
],
|
||||
"right_ankle": [
|
||||
"rightankle", "ankler", "rankle", "rightfoot", "footr", "rfoot",
|
||||
"rightfeet", "feetright", "rfeet", "feetr", "valvebipedbip01rfoot",
|
||||
"右足首", "足首.r", "ik_足首.r"
|
||||
],
|
||||
"right_toe": [
|
||||
"righttoe", "toeright", "toer", "rtoe", "toesr", "rtoes",
|
||||
"valvebipedbip01rtoe0", "右つま先", "つま先.r", "ik_つま先.r"
|
||||
],
|
||||
|
||||
#hand l fingers
|
||||
"pinkie_0_r": ["littlefinger0r","pinkie0r","rpinkie0","pinkiemetacarpalr"],
|
||||
"pinkie_1_r": ["littlefinger1r","pinkie1r","rpinkie1","pinkieproximalr", "valvebipedbip01rfinger4"],
|
||||
"pinkie_2_r": ["littlefinger2r","pinkie2r","rpinkie2","pinkieintermediater", "valvebipedbip01rfinger41"],
|
||||
"pinkie_3_r": ["littlefinger3r","pinkie3r","rpinkie3","pinkiedistalr", "valvebipedbip01rfinger42"],
|
||||
# Left side bones
|
||||
"left_shoulder": [
|
||||
"leftshoulder", "shoulderl", "lshoulder", "valvebipedbip01lclavicle",
|
||||
"左肩", "肩.l", "肩+.l", "左肩+", "左肩", "左肩+", "肩+l", "肩+左", "ik_肩.l"
|
||||
],
|
||||
"left_arm": [
|
||||
"leftarm", "arml", "larm", "upperarml", "lupperarm", "leftupperarm",
|
||||
"uparml", "luparm", "valvebipedbip01lupperarm", "左腕", "腕.l", "左腕", "ik_腕.l"
|
||||
],
|
||||
"left_elbow": [
|
||||
"leftelbow", "elbowl", "lelbow", "lowerarml", "leftlowerarm",
|
||||
"llowerarm", "lowarml", "llowarm", "forearml", "lforearm",
|
||||
"valvebipedbip01lforearm", "左ひじ", "ひじ.l", "すね.l", "ik_ひじ.l"
|
||||
],
|
||||
"left_wrist": [
|
||||
"leftwrist", "wristl", "lwrist", "handl", "lefthand", "lhand",
|
||||
"valvebipedbip01lhand", "左手首", "手首.l", "ik_手首.l"
|
||||
],
|
||||
"pinkie_0_l": [
|
||||
"pinkiefinger0l", "pinkie0l", "lpinkie0", "pinkiemetacarpall", "左小指0"
|
||||
],
|
||||
"pinkie_1_l": [
|
||||
"littlefinger1l", "pinkie1l", "lpinkie1", "pinkieproximall",
|
||||
"valvebipedbip01lfinger4", "左小指1"
|
||||
],
|
||||
"pinkie_2_l": [
|
||||
"littlefinger2l", "pinkie2l", "lpinkie2", "pinkieintermediatel",
|
||||
"valvebipedbip01lfinger41", "左小指2"
|
||||
],
|
||||
"pinkie_3_l": [
|
||||
"littlefinger3l", "pinkie3l", "lpinkie3", "pinkiedistall",
|
||||
"valvebipedbip01lfinger42", "左小指3"
|
||||
],
|
||||
"ring_0_l": [
|
||||
"ringfinger0l", "ring0l", "lring0", "ringmetacarpall", "左薬指0"
|
||||
],
|
||||
"ring_1_l": [
|
||||
"ringfinger1l", "ring1l", "lring1", "ringproximall",
|
||||
"valvebipedbip01lfinger3", "左薬指1"
|
||||
],
|
||||
"ring_2_l": [
|
||||
"ringfinger2l", "ring2l", "lring2", "ringintermediatel",
|
||||
"valvebipedbip01lfinger31", "左薬指2"
|
||||
],
|
||||
"ring_3_l": [
|
||||
"ringfinger3l", "ring3l", "lring3", "ringdistall",
|
||||
"valvebipedbip01lfinger32", "左薬指3"
|
||||
],
|
||||
"middle_0_l": [
|
||||
"middlefinger0l", "middle_0l", "lmiddle0", "middlemetacarpall", "左中指0"
|
||||
],
|
||||
"middle_1_l": [
|
||||
"middlefinger1l", "middle_1l", "lmiddle1", "middleproximall",
|
||||
"valvebipedbip01lfinger2", "左中指1"
|
||||
],
|
||||
"middle_2_l": [
|
||||
"middlefinger2l", "middle_2l", "lmiddle2", "middleintermediatel",
|
||||
"valvebipedbip01lfinger21", "左中指2"
|
||||
],
|
||||
"middle_3_l": [
|
||||
"middlefinger3l", "middle_3l", "lmiddle3", "middledistall",
|
||||
"valvebipedbip01lfinger22", "左中指3"
|
||||
],
|
||||
"index_0_l": [
|
||||
"indexfinger0l", "index0l", "lindex0", "indexmetacarpall", "左人差指0"
|
||||
],
|
||||
"index_1_l": [
|
||||
"indexfinger1l", "index1l", "lindex1", "indexproximall",
|
||||
"valvebipedbip01lfinger1", "左人差指1"
|
||||
],
|
||||
"index_2_l": [
|
||||
"indexfinger2l", "index2l", "lindex2", "indexintermediatel",
|
||||
"valvebipedbip01lfinger11", "左人差指2"
|
||||
],
|
||||
"index_3_l": [
|
||||
"indexfinger3l", "index3l", "lindex3", "indexdistall",
|
||||
"valvebipedbip01lfinger12", "左人差指3"
|
||||
],
|
||||
"thumb_0_l": [
|
||||
"thumb0l", "lthumb0", "thumbmetacarpall", "左親指0"
|
||||
],
|
||||
"thumb_1_l": [
|
||||
"thumb1l", "lthumb1", "thumbproximall", "valvebipedbip01lfinger0", "左親指1"
|
||||
],
|
||||
"thumb_2_l": [
|
||||
"thumb2l", "lthumb2", "thumbintermediatel", "valvebipedbip01lfinger01", "左親指2"
|
||||
],
|
||||
"thumb_3_l": [
|
||||
"thumb3l", "lthumb3", "thumbdistall", "valvebipedbip01lfinger02", "左親指3"
|
||||
],
|
||||
"left_leg": [
|
||||
"leftleg", "legl", "lleg", "upperlegl", "lupperleg", "thighl",
|
||||
"leftupperleg", "uplegl", "lupleg", "valvebipedbip01lthigh",
|
||||
"左足", "足.l", "ik_足.l"
|
||||
],
|
||||
"left_knee": [
|
||||
"leftknee", "kneel", "lknee", "lowerlegl", "leftlowerleg",
|
||||
"llowerleg", "lowlegl", "llowleg", "calfl", "lcalf",
|
||||
"valvebipedbip01lcalf", "左ひざ", "ひざ.l", "すね.l", "ik_ひざ.l"
|
||||
],
|
||||
"left_ankle": [
|
||||
"leftankle", "anklel", "lankle", "leftfoot", "footl", "lfoot",
|
||||
"leftfeet", "feetleft", "lfeet", "feetl", "valvebipedbip01lfoot",
|
||||
"左足首", "足首.l", "ik_足首.l"
|
||||
],
|
||||
"left_toe": [
|
||||
"lefttoe", "toeleft", "toel", "ltoe", "toesl", "ltoes",
|
||||
"valvebipedbip01ltoe0", "左つま先", "つま先.l", "ik_つま先.l"
|
||||
],
|
||||
|
||||
"ring_0_r": ["ringfinger0r","ring0r","rring0","ringmetacarpalr"],
|
||||
"ring_1_r": ["ringfinger1r","ring1r","rring1","ringproximalr", "valvebipedbip01rfinger3"],
|
||||
"ring_2_r": ["ringfinger2r","ring2r","rring2","ringintermediater", "valvebipedbip01rfinger31"],
|
||||
"ring_3_r": ["ringfinger3r","ring3r","rring3","ringdistalr", "valvebipedbip01rfinger32"],
|
||||
|
||||
"middle_0_r": ["middlefinger0r","middle0r","rmiddle0","middlemetacarpalr"],
|
||||
"middle_1_r": ["middlefinger1r","middle1r","rmiddle1","middleproximalr", "valvebipedbip01rfinger2"],
|
||||
"middle_2_r": ["middlefinger2r","middle2r","rmiddle2","middleintermediater", "valvebipedbip01rfinger21"],
|
||||
"middle_3_r": ["middlefinger3r","middle3r","rmiddle3","middledistalr", "valvebipedbip01rfinger22"],
|
||||
|
||||
"index_0_r": ["indexfinger0r","index0r","rindex0","indexmetacarpalr"],
|
||||
"index_1_r": ["indexfinger1r","index1r","rindex1","indexproximalr", "valvebipedbip01rfinger1"],
|
||||
"index_2_r": ["indexfinger2r","index2r","rindex2","indexintermediater", "valvebipedbip01rfinger11"],
|
||||
"index_3_r": ["indexfinger3r","index3r","rindex3","indexdistalr", "valvebipedbip01rfinger12"],
|
||||
|
||||
"thumb_0_r": ["thumb0r","rthumb0","thumbmetacarpalr"],
|
||||
"thumb_1_r": ['thumb1r',"rthumb1","thumbproximalr", "valvebipedbip01rfinger0"],
|
||||
"thumb_2_r": ['thumb2r',"rthumb2","thumbintermediater", "valvebipedbip01rfinger01"],
|
||||
"thumb_3_r": ['thumb3r',"rthumb3","thumbdistalr", "valvebipedbip01rfinger02"],
|
||||
|
||||
"right_leg": ["rightleg", "legr", "rleg", "upperlegr", "rupperleg", "thighr", "rightupperleg", "uplegr", "rupleg", "valvebipedbip01rthigh"],
|
||||
"right_knee": ["rightknee", "kneer", "rknee", "lowerlegr", "calfr", "rlowerleg", "rcalf", "rightlowerleg", "lowlegr", "rlowleg", "valvebipedbip01rcalf"],
|
||||
"right_ankle": ["rightankle", "ankler", "rankle", "rightfoot", "footr", "rfoot", "rightfoot", "rightfeet", "feetright", "rfeet", "feetr", "valvebipedbip01rfoot"],
|
||||
"right_toe": ["righttoe", "toeright", "toer", "rtoe", "toesr", "rtoes", "valvebipedbip01rtoe0"],
|
||||
|
||||
"left_shoulder": ["leftshoulder", "shoulderl", "lshoulder", "valvebipedbip01lclavicle"],
|
||||
"left_arm": ["leftarm", "arml", "rarm", "upperarml", "lupperarm", "leftupperarm", "uparml", "luparm", "valvebipedbip01lupperarm"],
|
||||
"left_elbow": ["leftelbow", "elbowl", "lelbow", "lowerarml", "leftlowerarm", "lowerarml", "llowerarm", "lowarml", "llowarm", "forearml","lforearm", "valvebipedbip01lforearm"],
|
||||
"left_wrist": ["leftwrist", "wristl", "lwrist", "handl", "lefthand", "lhand", "valvebipedbip01lhand"],
|
||||
|
||||
#hand l fingers
|
||||
|
||||
"pinkie_0_l": ["pinkiefinger0l","pinkie0l","lpinkie0","pinkiemetacarpall"],
|
||||
"pinkie_1_l": ["littlefinger1l","pinkie1l","lpinkie1","pinkieproximall", "valvebipedbip01lfinger4"],
|
||||
"pinkie_2_l": ["littlefinger2l","pinkie2l","lpinkie2","pinkieintermediatel", "valvebipedbip01lfinger41"],
|
||||
"pinkie_3_l": ["littlefinger3l","pinkie3l","lpinkie3","pinkiedistall", "valvebipedbip01lfinger42"],
|
||||
|
||||
"ring_0_l": ["ringfinger0l","ring0l","lring0","ringmetacarpall"],
|
||||
"ring_1_l": ["ringfinger1l","ring1l","lring1","ringproximall", "valvebipedbip01lfinger3"],
|
||||
"ring_2_l": ["ringfinger2l","ring2l","lring2","ringintermediatel", "valvebipedbip01lfinger31"],
|
||||
"ring_3_l": ["ringfinger3l","ring3l","lring3","ringdistall", "valvebipedbip01lfinger32"],
|
||||
|
||||
"middle_0_l": ["middlefinger0l","middle_0l","lmiddle0","middlemetacarpall"],
|
||||
"middle_1_l": ["middlefinger1l","middle_1l","lmiddle1","middleproximall", "valvebipedbip01lfinger2"],
|
||||
"middle_2_l": ["middlefinger2l","middle_2l","lmiddle2","middleintermediatel", "valvebipedbip01lfinger21"],
|
||||
"middle_3_l": ["middlefinger3l","middle_3l","lmiddle3","middledistall", "valvebipedbip01lfinger22"],
|
||||
|
||||
"index_0_l": ["indexfinger0l","index0l","lindex0","indexmetacarpall"],
|
||||
"index_1_l": ["indexfinger1l","index1l","lindex1","indexproximall", "valvebipedbip01lfinger1"],
|
||||
"index_2_l": ["indexfinger2l","index2l","lindex2","indexintermediatel", "valvebipedbip01lfinger11"],
|
||||
"index_3_l": ["indexfinger3l","index3l","lindex3","indexdistall", "valvebipedbip01lfinger12"],
|
||||
|
||||
"thumb_0_l": ["thumb0l","lthumb0","thumbmetacarpall"],
|
||||
"thumb_1_l": ['thumb1l',"lthumb1","thumbproximall", "valvebipedbip01lfinger0"],
|
||||
"thumb_2_l": ['thumb2l',"lthumb2","thumbintermediatel", "valvebipedbip01lfinger01"],
|
||||
"thumb_3_l": ['thumb3l',"lthumb3","thumbdistall", "valvebipedbip01lfinger02"],
|
||||
|
||||
"left_leg": ["leftleg", "legl", "lleg", "upperlegl", "lupperleg", "thighl", "leftupperleg", "uplegl", "lupleg", "valvebipedbip01lthigh"],
|
||||
"left_knee": ["leftknee", "kneel", "lknee", "lowerlegl", "llowerleg", "calfl", "lcalf", "leftlowerleg", 'lowlegl', 'llowleg', "valvebipedbip01lcalf"],
|
||||
"left_ankle": ["leftankle", "anklel", "rankle", "leftfoot", "footl", "lfoot", "leftfoot", "leftfeet", "feetleft", "lfeet", "feetl", "valvebipedbip01lfoot"],
|
||||
"left_toe": ["lefttoe", "toeleft", "toel", "ltoe", "toesl", "ltoes", "valvebipedbip01ltoe0"],
|
||||
|
||||
"hips": ["pelvis", "hips", "hip", "valvebipedbip01pelvis"],
|
||||
"spine": ["torso", "spine", "valvebipedbip01spine"],
|
||||
"chest": ["chest", "valvebipedbip01spine1"],
|
||||
"upper_chest": ["upperchest", "valvebipedbip01spine4"],
|
||||
"neck": ["neck", "valvebipedbip01neck1"],
|
||||
"head": ["head", "valvebipedbip01head1"],
|
||||
"left_eye": ["eyeleft", "lefteye", "eyel", "leye"],
|
||||
"right_eye": ["eyeright", "righteye", "eyer", "reye"],
|
||||
# Central bones
|
||||
"hips": [
|
||||
"pelvis", "hips", "hip", "valvebipedbip01pelvis", "腰", "ik_腰"
|
||||
],
|
||||
"spine": [
|
||||
"torso", "spine", "valvebipedbip01spine", "脊椎", "ik_脊椎"
|
||||
],
|
||||
"chest": [
|
||||
"chest", "valvebipedbip01spine1", "胸", "ik_胸"
|
||||
],
|
||||
"upper_chest": [
|
||||
"upperchest", "valvebipedbip01spine4", "上胸", "ik_上胸"
|
||||
],
|
||||
"neck": [
|
||||
"neck", "valvebipedbip01neck1", "首", "ik_首"
|
||||
],
|
||||
"head": [
|
||||
"head", "valvebipedbip01head1", "頭", "ik_頭"
|
||||
],
|
||||
"left_eye": [
|
||||
"eyeleft", "lefteye", "eyel", "leye", "左目", "ik_左目"
|
||||
],
|
||||
"right_eye": [
|
||||
"eyeright", "righteye", "eyer", "reye", "右目", "ik_右目"
|
||||
],
|
||||
}
|
||||
|
||||
# Add VRM bone name variations
|
||||
bone_names.update({
|
||||
'hips': bone_names['hips'] + ['j_bip_c_hips', 'j_hips', 'vrm_hips'],
|
||||
'spine': bone_names['spine'] + ['j_bip_c_spine', 'j_spine', 'vrm_spine'],
|
||||
'chest': bone_names['chest'] + ['j_bip_c_chest', 'j_chest', 'vrm_chest'],
|
||||
'upper_chest': bone_names['upper_chest'] + ['j_bip_c_upper_chest', 'j_upper_chest', 'vrm_upperchest'],
|
||||
'neck': bone_names['neck'] + ['j_bip_c_neck', 'j_neck', 'vrm_neck'],
|
||||
'head': bone_names['head'] + ['j_bip_c_head', 'j_head', 'vrm_head'],
|
||||
|
||||
# VRM specific finger naming
|
||||
'thumb_0_l': bone_names['thumb_0_l'] + ['thumb_metacarpal_l', 'j_thumb1_l'],
|
||||
'index_0_l': bone_names['index_0_l'] + ['index_metacarpal_l', 'j_index1_l'],
|
||||
'middle_0_l': bone_names['middle_0_l'] + ['middle_metacarpal_l', 'j_middle1_l'],
|
||||
'ring_0_l': bone_names['ring_0_l'] + ['ring_metacarpal_l', 'j_ring1_l'],
|
||||
'pinkie_0_l': bone_names['pinkie_0_l'] + ['little_metacarpal_l', 'j_little1_l'],
|
||||
|
||||
# Mirror for right side
|
||||
'thumb_0_r': bone_names['thumb_0_r'] + ['thumb_metacarpal_r', 'j_thumb1_r'],
|
||||
'index_0_r': bone_names['index_0_r'] + ['index_metacarpal_r', 'j_index1_r'],
|
||||
'middle_0_r': bone_names['middle_0_r'] + ['middle_metacarpal_r', 'j_middle1_r'],
|
||||
'ring_0_r': bone_names['ring_0_r'] + ['ring_metacarpal_r', 'j_ring1_r'],
|
||||
'pinkie_0_r': bone_names['pinkie_0_r'] + ['little_metacarpal_r', 'j_little1_r']
|
||||
})
|
||||
|
||||
# array taken from cats
|
||||
dont_delete_these_main_bones = [
|
||||
'Hips', 'Spine', 'Chest', 'Upper Chest', 'Neck', 'Head',
|
||||
@@ -109,4 +297,60 @@ dont_delete_these_main_bones = [
|
||||
'MiddleFinger1_R', 'MiddleFinger2_R', 'MiddleFinger3_R',
|
||||
'RingFinger1_R', 'RingFinger2_R', 'RingFinger3_R',
|
||||
'LittleFinger1_R', 'LittleFinger2_R', 'LittleFinger3_R',
|
||||
]
|
||||
]
|
||||
|
||||
resonite_translations = {
|
||||
'hips': "Hips",
|
||||
'spine': "Spine",
|
||||
'chest': "Chest",
|
||||
'neck': "Neck",
|
||||
'head': "Head",
|
||||
'left_eye': "Eye.L",
|
||||
'right_eye': "Eye.R",
|
||||
'right_leg': "UpperLeg.R",
|
||||
'right_knee': "Calf.R",
|
||||
'right_ankle': "Foot.R",
|
||||
'right_toe': 'Toes.R',
|
||||
'right_shoulder': "Shoulder.R",
|
||||
'right_arm': "UpperArm.R",
|
||||
'right_elbow': "ForeArm.R",
|
||||
'right_wrist': "Hand.R",
|
||||
'left_leg': "UpperLeg.L",
|
||||
'left_knee': "Calf.L",
|
||||
'left_ankle': "Foot.L",
|
||||
'left_toe': "Toes.L",
|
||||
'left_shoulder': "Shoulder.L",
|
||||
'left_arm': "UpperArm.L",
|
||||
'left_elbow': "ForeArm.L",
|
||||
'left_wrist': "Hand.L",
|
||||
'pinkie_1_l': "pinkie1.L",
|
||||
'pinkie_2_l': "pinkie2.L",
|
||||
'pinkie_3_l': "pinkie3.L",
|
||||
'ring_1_l': "ring1.L",
|
||||
'ring_2_l': "ring2.L",
|
||||
'ring_3_l': "ring3.L",
|
||||
'middle_1_l': "middle1.L",
|
||||
'middle_2_l': "middle2.L",
|
||||
'middle_3_l': "middle3.L",
|
||||
'index_1_l': "index1.L",
|
||||
'index_2_l': "index2.L",
|
||||
'index_3_l': "index3.L",
|
||||
'thumb_1_l': "thumb1.L",
|
||||
'thumb_2_l': "thumb2.L",
|
||||
'thumb_3_l': "thumb3.L",
|
||||
'pinkie_1_r': "pinkie1.R",
|
||||
'pinkie_2_r': "pinkie2.R",
|
||||
'pinkie_3_r': "pinkie3.R",
|
||||
'ring_1_r': "ring1.R",
|
||||
'ring_2_r': "ring2.R",
|
||||
'ring_3_r': "ring3.R",
|
||||
'middle_1_r': "middle1.R",
|
||||
'middle_2_r': "middle2.R",
|
||||
'middle_3_r': "middle3.R",
|
||||
'index_1_r': "index1.R",
|
||||
'index_2_r': "index2.R",
|
||||
'index_3_r': "index3.R",
|
||||
'thumb_1_r': "thumb1.R",
|
||||
'thumb_2_r': "thumb2.R",
|
||||
'thumb_3_r': "thumb3.R"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import bpy
|
||||
import logging
|
||||
import os
|
||||
import typing
|
||||
from typing import Optional, Callable, Dict, List, Union, Set
|
||||
from ..common import clear_default_objects
|
||||
from .import_pmx import import_pmx
|
||||
from .import_pmd import import_pmd
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
import importlib.util
|
||||
|
||||
if importlib.util.find_spec("io_scene_valvesource") is not None:
|
||||
from io_scene_valvesource.import_smd import SmdImporter
|
||||
|
||||
class ImportProgress:
|
||||
"""Tracks and logs the progress of multi-file imports"""
|
||||
def __init__(self, total_files: int):
|
||||
self.total: int = total_files
|
||||
self.current: int = 0
|
||||
|
||||
def update(self, filename: str) -> None:
|
||||
"""Update import progress and log current file"""
|
||||
self.current += 1
|
||||
logger.info(f"Importing {filename} ({self.current}/{self.total})")
|
||||
|
||||
def validate_file(filepath: str) -> bool:
|
||||
"""
|
||||
Validate if a file exists and is accessible
|
||||
Returns: True if file is valid, False otherwise
|
||||
"""
|
||||
if not os.path.exists(filepath):
|
||||
logger.error(f"File not found: {filepath}")
|
||||
return False
|
||||
if not os.path.isfile(filepath):
|
||||
logger.error(f"Not a file: {filepath}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def import_multi_files(
|
||||
method: Optional[Callable] = None,
|
||||
directory: Optional[str] = None,
|
||||
files: Optional[List[Dict[str, str]]] = None,
|
||||
filepath: str = "",
|
||||
progress_callback: Optional[Callable[[str], None]] = None
|
||||
) -> None:
|
||||
"""
|
||||
Import multiple files using the specified import method
|
||||
|
||||
Args:
|
||||
method: Import method to use
|
||||
directory: Directory containing files
|
||||
files: List of files to import
|
||||
filepath: Single file path to import
|
||||
progress_callback: Callback for progress updates
|
||||
"""
|
||||
try:
|
||||
if not method:
|
||||
raise ValueError("Import method not specified")
|
||||
|
||||
if not files:
|
||||
if not validate_file(filepath):
|
||||
return
|
||||
method(directory, filepath)
|
||||
if progress_callback:
|
||||
progress_callback(filepath)
|
||||
else:
|
||||
progress = ImportProgress(len(files))
|
||||
for file in files:
|
||||
fullpath: str = os.path.join(directory, os.path.basename(file["name"]))
|
||||
if not validate_file(fullpath):
|
||||
continue
|
||||
|
||||
logger.info(f"Importing file: {fullpath}")
|
||||
method(directory, fullpath)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(fullpath)
|
||||
progress.update(file["name"])
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Import failed: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
||||
ImportMethod = Callable[[str, List[Dict[str, str]], str], None]
|
||||
|
||||
import_types: Dict[str, ImportMethod] = {
|
||||
"fbx": lambda directory, files, filepath: bpy.ops.import_scene.fbx(
|
||||
files=files, directory=directory, filepath=filepath,
|
||||
automatic_bone_orientation=False, use_prepost_rot=False, use_anim=False
|
||||
),
|
||||
"smd": lambda directory, files, filepath: eval("bpy."+SmdImporter.bl_idname+".(files=files, directory=directory, filepath=filepath)"),
|
||||
"dmx": lambda directory, files, filepath: eval("bpy."+SmdImporter.bl_idname+".(files=files, directory=directory, filepath=filepath)"),
|
||||
"gltf": lambda directory, files, filepath: bpy.ops.import_scene.gltf(files=files, filepath=filepath),
|
||||
"glb": lambda directory, files, filepath: bpy.ops.import_scene.gltf(files=files, filepath=filepath),
|
||||
"qc": lambda directory, files, filepath: eval("bpy."+SmdImporter.bl_idname+".(files=files, directory=directory, filepath=filepath)"),
|
||||
"obj": lambda directory, files, filepath: bpy.ops.wm.obj_import(files=files, directory=directory, filepath=filepath),
|
||||
"dae": lambda directory, files, filepath: import_multi_files(
|
||||
directory=directory,
|
||||
files=files,
|
||||
filepath=filepath,
|
||||
method=lambda directory, filepath: bpy.ops.wm.collada_import(
|
||||
filepath=filepath, auto_connect=True, find_chains=True, fix_orientation=True
|
||||
)
|
||||
),
|
||||
"3ds": lambda directory, files, filepath: bpy.ops.import_scene.max3ds(files=files, directory=directory, filepath=filepath),
|
||||
"stl": lambda directory, files, filepath: bpy.ops.import_mesh.stl(files=files, directory=directory, filepath=filepath),
|
||||
"mtl": lambda directory, files, filepath: bpy.ops.wm.obj_import(files=files, directory=directory, filepath=filepath),
|
||||
"x3d": lambda directory, files, filepath: bpy.ops.import_scene.x3d(files=files, directory=directory, filepath=filepath),
|
||||
"wrl": lambda directory, files, filepath: bpy.ops.import_scene.x3d(files=files, directory=directory, filepath=filepath),
|
||||
"vmd": lambda directory, files, filepath: import_multi_files(
|
||||
directory=directory,
|
||||
files=files,
|
||||
filepath=filepath,
|
||||
method=lambda directory, filepath: bpy.ops.tuxedo.import_mmd_animation(directory=directory, filepath=filepath)
|
||||
),
|
||||
"vrm": lambda directory, files, filepath: bpy.ops.import_scene.vrm(filepath=filepath),
|
||||
"pmx": lambda directory, files, filepath: import_pmx(filepath),
|
||||
"pmd": lambda directory, files, filepath: import_pmd(filepath),
|
||||
}
|
||||
|
||||
def concat_imports_filter(imports: Dict[str, ImportMethod]) -> str:
|
||||
"""Create a file filter string from import types"""
|
||||
return "".join(f"*.{importer};" for importer in imports.keys())
|
||||
|
||||
imports: str = concat_imports_filter(import_types)
|
||||
@@ -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)
|
||||
@@ -1,152 +0,0 @@
|
||||
# thank you https://stackoverflow.com/a/71432759
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
from typing import Optional
|
||||
from bpy.types import Image, Material
|
||||
|
||||
|
||||
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jake Gordon and contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
class Rectangle_Obj:
|
||||
x: int = 0
|
||||
y: int = 0
|
||||
w: int = 0
|
||||
h: int = 0
|
||||
down: Rectangle_Obj = None
|
||||
used: bool = False
|
||||
right: Rectangle_Obj = None
|
||||
|
||||
def __init__(self, x:int, y:int, w:int, h:int, down=None, used =False, right=None):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.w = w
|
||||
self.h = h
|
||||
self.down = down
|
||||
self.used = used
|
||||
self.right = right
|
||||
|
||||
def split(self, w, h) -> Rectangle_Obj:
|
||||
self.used = True
|
||||
self.down = Rectangle_Obj(x=self.x, y=self.y + h, w=self.w, h=self.h - h)
|
||||
self.right = Rectangle_Obj(x=self.x + w, y=self.y, w=self.w - w, h=h)
|
||||
return self
|
||||
|
||||
def find(self, w, h) -> Optional[Rectangle_Obj]:
|
||||
if self.used:
|
||||
return self.right.find(w, h) or self.down.find(w, h)
|
||||
elif (w <= self.w) and (h <= self.h):
|
||||
return self
|
||||
return None
|
||||
|
||||
class MaterialImageList:
|
||||
albedo: Image
|
||||
normal: Image
|
||||
emission: Image
|
||||
ambient_occlusion: Image
|
||||
height: Image
|
||||
roughness: Image
|
||||
fit: Rectangle_Obj
|
||||
material: Material
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
x: int = 0
|
||||
y: int = 0
|
||||
w: int = 0
|
||||
h: int = 0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class BinPacker(object):
|
||||
root: Rectangle_Obj
|
||||
bin: list[MaterialImageList] = []
|
||||
def __init__(self, structure: list[MaterialImageList]):
|
||||
self.root = None
|
||||
self.bin = structure
|
||||
|
||||
def fit(self):
|
||||
structure = self.bin
|
||||
structure_len = len(self.bin)
|
||||
w: int = 0
|
||||
h: int = 0
|
||||
if structure_len > 0:
|
||||
w = structure[0].w
|
||||
h = structure[0].h
|
||||
self.root = Rectangle_Obj(x=0, y=0, w=w, h=h)
|
||||
for img in structure:
|
||||
w = img.w
|
||||
h = img.h
|
||||
node = self.root.find(w, h)
|
||||
if node:
|
||||
img.fit = node.split(w, h)
|
||||
else:
|
||||
img.fit = self.grow_node(w, h)
|
||||
return structure
|
||||
|
||||
def grow_node(self, w, h) -> Optional[Rectangle_Obj]:
|
||||
can_grow_right = (h <= self.root.h)
|
||||
can_grow_down = (w <= self.root.w)
|
||||
|
||||
should_grow_right = can_grow_right and (self.root.h >= (self.root.w + w))
|
||||
should_grow_down = can_grow_down and (self.root.w >= (self.root.h + h))
|
||||
|
||||
if should_grow_right:
|
||||
return self.grow_right(w, h)
|
||||
elif should_grow_down:
|
||||
return self.grow_down(w, h)
|
||||
elif can_grow_right:
|
||||
return self.grow_right(w, h)
|
||||
elif can_grow_down:
|
||||
return self.grow_down(w, h)
|
||||
return None
|
||||
|
||||
def grow_right(self, w, h) -> Optional[Rectangle_Obj]:
|
||||
self.root = Rectangle_Obj(
|
||||
used=True,
|
||||
x=0,
|
||||
y=0,
|
||||
w=self.root.w + w,
|
||||
h=self.root.h,
|
||||
down=self.root,
|
||||
right=Rectangle_Obj(x=self.root.w, y=0, w=w, h=self.root.h))
|
||||
node = self.root.find(w, h)
|
||||
if node:
|
||||
return node.split(w, h)
|
||||
return None
|
||||
|
||||
def grow_down(self, w, h) -> Optional[Rectangle_Obj]:
|
||||
self.root = Rectangle_Obj(
|
||||
used=True,
|
||||
x=0,
|
||||
y=0,
|
||||
w=self.root.w,
|
||||
h=self.root.h + h,
|
||||
down=Rectangle_Obj(x=0, y=self.root.h, w=self.root.w, h=h),
|
||||
right=self.root
|
||||
)
|
||||
node = self.root.find(w, h)
|
||||
if node:
|
||||
return node.split(w, h)
|
||||
return None
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"language": 0,
|
||||
"last_update_check": 1734295375.2681296
|
||||
}
|
||||
+370
-150
@@ -1,169 +1,389 @@
|
||||
import bpy
|
||||
from ..functions.translations import t, get_languages_list, update_language
|
||||
from ..core.register import register_property
|
||||
from bpy.types import Scene, Object, Material, Context
|
||||
from bpy.props import BoolProperty, EnumProperty, IntProperty, CollectionProperty, StringProperty, FloatVectorProperty, PointerProperty
|
||||
from ..core.addon_preferences import get_preference
|
||||
from ..core.common import SceneMatClass, MaterialListBool, get_armatures, get_mesh_items, get_armatures_that_are_not_selected
|
||||
from typing import List, Tuple, Optional
|
||||
from bpy.types import PropertyGroup, Material, Scene, Object, Context
|
||||
from bpy.props import (
|
||||
StringProperty,
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
CollectionProperty,
|
||||
PointerProperty
|
||||
)
|
||||
from .logging_setup import logger
|
||||
from .translations import t, get_languages_list, update_language
|
||||
from .addon_preferences import get_preference, save_preference
|
||||
from .updater import get_version_list
|
||||
from .common import get_armature_list, get_active_armature, get_all_meshes
|
||||
from ..functions.visemes import VisemePreview
|
||||
from ..functions.eye_tracking import set_rotation
|
||||
|
||||
def register() -> None:
|
||||
default_language = get_preference("language", 0)
|
||||
register_property((bpy.types.Scene, "avatar_toolkit_language", bpy.props.EnumProperty(
|
||||
name=t("Settings.language.label", "Language"),
|
||||
description=t("Settings.language.desc", "Select the language for the addon"),
|
||||
items=get_languages_list,
|
||||
default=default_language,
|
||||
update=update_language
|
||||
)))
|
||||
def update_validation_mode(self, context):
|
||||
logger.info(f"Updating validation mode to: {self.validation_mode}")
|
||||
save_preference("validation_mode", self.validation_mode)
|
||||
|
||||
register_property((bpy.types.Scene, "selected_mesh", bpy.props.EnumProperty(
|
||||
items=get_mesh_items,
|
||||
name=t("VisemePanel.selected_mesh.label"),
|
||||
description=t("VisemePanel.selected_mesh.desc")
|
||||
)))
|
||||
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)
|
||||
|
||||
register_property((bpy.types.Object, "material_group_expanded", bpy.props.BoolProperty(
|
||||
name="Expand Material Group",
|
||||
description="Show/hide materials for this mesh",
|
||||
default=False
|
||||
)))
|
||||
def update_shape_intensity(self, context):
|
||||
if self.viseme_preview_mode:
|
||||
from ..functions.visemes import VisemePreview
|
||||
VisemePreview.update_preview(context)
|
||||
|
||||
register_property((bpy.types.Material, "material_expanded", bpy.props.BoolProperty(
|
||||
name="Expand Material",
|
||||
description="Show/hide material properties",
|
||||
default=False
|
||||
)))
|
||||
|
||||
register_property((bpy.types.Scene, "material_search_filter", bpy.props.StringProperty(
|
||||
name="Search Materials",
|
||||
description="Filter materials by name",
|
||||
default=""
|
||||
)))
|
||||
|
||||
register_property((bpy.types.Material, "include_in_atlas", bpy.props.BoolProperty(
|
||||
name=t("TextureAtlas.include_in_atlas"),
|
||||
description=t("TextureAtlas.include_in_atlas_desc"),
|
||||
default=True
|
||||
)))
|
||||
|
||||
register_property((bpy.types.Scene, "merge_armature_apply_transforms", bpy.props.BoolProperty(
|
||||
default=False,
|
||||
name=t("MergeArmature.merge_armatures.apply_transforms.label"),
|
||||
description=t("MergeArmature.merge_armatures.apply_transforms.desc")
|
||||
)))
|
||||
register_property((bpy.types.Scene, "merge_armature_align_bones", bpy.props.BoolProperty(
|
||||
default=False,
|
||||
name=t("MergeArmature.merge_armatures.align_bones.label"),
|
||||
description=t("MergeArmature.merge_armatures.align_bones.desc")
|
||||
)))
|
||||
class AvatarToolkitSceneProperties(PropertyGroup):
|
||||
"""Property group containing Avatar Toolkit scene-level settings and properties"""
|
||||
|
||||
register_property((bpy.types.Scene, "avatar_toolkit_language_changed", bpy.props.BoolProperty(default=False)))
|
||||
avatar_toolkit_updater_version_list: EnumProperty(
|
||||
items=get_version_list,
|
||||
name=t("Scene.avatar_toolkit_updater_version_list.name"),
|
||||
description=t("Scene.avatar_toolkit_updater_version_list.description")
|
||||
)
|
||||
|
||||
register_property((bpy.types.Scene, "avatar_toolkit_progress_steps", bpy.props.IntProperty(default=0)))
|
||||
register_property((bpy.types.Scene, "avatar_toolkit_progress_current", bpy.props.IntProperty(default=0)))
|
||||
active_armature: EnumProperty(
|
||||
items=get_armature_list,
|
||||
name=t("QuickAccess.select_armature"),
|
||||
description=t("QuickAccess.select_armature"),
|
||||
)
|
||||
|
||||
register_property((bpy.types.Scene, "avatar_toolkit_mouth_a", bpy.props.StringProperty(
|
||||
name=t("VisemePanel.mouth_a.label"),
|
||||
description=t("VisemePanel.mouth_a.desc")
|
||||
)))
|
||||
register_property((bpy.types.Scene, "avatar_toolkit_mouth_o", bpy.props.StringProperty(
|
||||
name=t("VisemePanel.mouth_o.label"),
|
||||
description=t("VisemePanel.mouth_o.desc")
|
||||
)))
|
||||
register_property((bpy.types.Scene, "avatar_toolkit_mouth_ch", bpy.props.StringProperty(
|
||||
name=t("VisemePanel.mouth_ch.label"),
|
||||
description=t("VisemePanel.mouth_ch.desc")
|
||||
)))
|
||||
register_property((bpy.types.Scene, "avatar_toolkit_shape_intensity", bpy.props.FloatProperty(
|
||||
name=t("VisemePanel.shape_intensity"),
|
||||
description=t("VisemePanel.shape_intensity_desc"),
|
||||
language: EnumProperty(
|
||||
name=t("Settings.language"),
|
||||
description=t("Settings.language_desc"),
|
||||
items=get_languages_list,
|
||||
update=update_language
|
||||
)
|
||||
|
||||
validation_mode: EnumProperty(
|
||||
name=t("Settings.validation_mode"),
|
||||
description=t("Settings.validation_mode_desc"),
|
||||
items=[
|
||||
('STRICT', t("Settings.validation_mode.strict"), t("Settings.validation_mode.strict_desc")),
|
||||
('BASIC', t("Settings.validation_mode.basic"), t("Settings.validation_mode.basic_desc")),
|
||||
('NONE', t("Settings.validation_mode.none"), t("Settings.validation_mode.none_desc"))
|
||||
],
|
||||
default=get_preference("validation_mode", "STRICT"),
|
||||
update=update_validation_mode
|
||||
)
|
||||
|
||||
enable_logging: BoolProperty(
|
||||
name=t("Settings.enable_logging"),
|
||||
description=t("Settings.enable_logging_desc"),
|
||||
default=False,
|
||||
update=update_logging_state
|
||||
)
|
||||
|
||||
debug_expand: BoolProperty(
|
||||
name="Debug Settings Expanded",
|
||||
default=False
|
||||
)
|
||||
|
||||
remove_doubles_merge_distance: FloatProperty(
|
||||
name=t("Optimization.merge_distance"),
|
||||
description=t("Optimization.merge_distance_desc"),
|
||||
default=0.0001,
|
||||
min=0.00001,
|
||||
max=0.1
|
||||
)
|
||||
|
||||
remove_doubles_advanced: BoolProperty(
|
||||
name=t("Optimization.remove_doubles_advanced"),
|
||||
description=t("Optimization.remove_doubles_advanced_desc"),
|
||||
default=False
|
||||
)
|
||||
|
||||
connect_bones_min_distance: FloatProperty(
|
||||
name=t("Tools.connect_bones_min_distance"),
|
||||
description=t("Tools.connect_bones_min_distance_desc"),
|
||||
default=0.001,
|
||||
min=0.0001,
|
||||
max=0.1,
|
||||
precision=4
|
||||
)
|
||||
|
||||
merge_twist_bones: BoolProperty(
|
||||
name=t("MMD.merge_twist_bones"),
|
||||
description=t("MMD.merge_twist_bones_desc"),
|
||||
default=True
|
||||
)
|
||||
|
||||
keep_twist_bones: BoolProperty(
|
||||
name=t("MMD.keep_twist_bones"),
|
||||
description=t("MMD.keep_twist_bones_desc"),
|
||||
default=False
|
||||
)
|
||||
|
||||
keep_upper_chest: BoolProperty(
|
||||
name=t("MMD.keep_upper_chest"),
|
||||
description=t("MMD.keep_upper_chest_desc"),
|
||||
default=True
|
||||
)
|
||||
|
||||
merge_weights_threshold: FloatProperty(
|
||||
name=t("MMD.merge_weights_threshold"),
|
||||
description=t("MMD.merge_weights_threshold_desc"),
|
||||
default=0.01,
|
||||
min=0.0,
|
||||
max=1.0
|
||||
)
|
||||
|
||||
viseme_preview_mode: BoolProperty(
|
||||
name=t("Visemes.preview_mode"),
|
||||
description=t("Visemes.preview_mode_desc"),
|
||||
default=False
|
||||
)
|
||||
|
||||
viseme_preview_selection: StringProperty(
|
||||
name=t("Visemes.preview_selection"),
|
||||
description=t("Visemes.preview_selection_desc"),
|
||||
default="vrc.v_aa"
|
||||
)
|
||||
|
||||
mouth_a: StringProperty(
|
||||
name=t("Visemes.mouth_a"),
|
||||
description=t("Visemes.mouth_a_desc")
|
||||
)
|
||||
|
||||
mouth_o: StringProperty(
|
||||
name=t("Visemes.mouth_o"),
|
||||
description=t("Visemes.mouth_o_desc")
|
||||
)
|
||||
|
||||
mouth_ch: StringProperty(
|
||||
name=t("Visemes.mouth_ch"),
|
||||
description=t("Visemes.mouth_ch_desc")
|
||||
)
|
||||
|
||||
shape_intensity: FloatProperty(
|
||||
name=t("Visemes.shape_intensity"),
|
||||
description=t("Visemes.shape_intensity_desc"),
|
||||
default=1.0,
|
||||
min=0.0,
|
||||
max=2.0
|
||||
)))
|
||||
register_property((bpy.types.Scene, "merge_twist_bones", bpy.props.BoolProperty(
|
||||
name=t("Tools.merge_twist_bones.label"),
|
||||
description=t("Tools.merge_twist_bones.desc"),
|
||||
max=2.0,
|
||||
precision=3,
|
||||
update=update_shape_intensity
|
||||
)
|
||||
|
||||
viseme_preview_selection: EnumProperty(
|
||||
name=t("Visemes.preview_selection"),
|
||||
description=t("Visemes.preview_selection_desc"),
|
||||
items=[
|
||||
('vrc.v_aa', 'AA', 'A as in "bat"'),
|
||||
('vrc.v_ch', 'CH', 'Ch as in "choose"'),
|
||||
('vrc.v_dd', 'DD', 'D as in "dog"'),
|
||||
('vrc.v_ih', 'IH', 'I as in "bit"'),
|
||||
('vrc.v_ff', 'FF', 'F as in "fox"'),
|
||||
('vrc.v_e', 'E', 'E as in "bet"'),
|
||||
('vrc.v_kk', 'KK', 'K as in "cat"'),
|
||||
('vrc.v_nn', 'NN', 'N as in "net"'),
|
||||
('vrc.v_oh', 'OH', 'O as in "hot"'),
|
||||
('vrc.v_ou', 'OU', 'O as in "go"'),
|
||||
('vrc.v_pp', 'PP', 'P as in "pat"'),
|
||||
('vrc.v_rr', 'RR', 'R as in "red"'),
|
||||
('vrc.v_sil', 'SIL', 'Silence'),
|
||||
('vrc.v_ss', 'SS', 'S as in "sit"'),
|
||||
('vrc.v_th', 'TH', 'Th as in "think"')
|
||||
],
|
||||
update=lambda s, c: VisemePreview.update_preview(c)
|
||||
|
||||
)
|
||||
|
||||
eye_tracking_type: EnumProperty(
|
||||
name=t("EyeTracking.type"),
|
||||
description=t("EyeTracking.type_desc"),
|
||||
items=[
|
||||
('AV3', t("EyeTracking.type.av3"), t("EyeTracking.type.av3_desc")),
|
||||
('SDK2', t("EyeTracking.type.sdk2"), t("EyeTracking.type.sdk2_desc"))
|
||||
],
|
||||
default='AV3'
|
||||
)
|
||||
|
||||
eye_mode: EnumProperty(
|
||||
name=t("EyeTracking.mode"),
|
||||
items=[
|
||||
('CREATION', t("EyeTracking.mode.creation"), ""),
|
||||
('TESTING', t("EyeTracking.mode.testing"), "")
|
||||
],
|
||||
default='CREATION'
|
||||
)
|
||||
|
||||
eye_rotation_x: FloatProperty(
|
||||
name=t("EyeTracking.rotation.x"),
|
||||
update=set_rotation
|
||||
)
|
||||
|
||||
eye_rotation_y: FloatProperty(
|
||||
name=t("EyeTracking.rotation.y"),
|
||||
update=set_rotation
|
||||
)
|
||||
|
||||
mesh_name_eye: StringProperty(
|
||||
name=t("EyeTracking.mesh_name"),
|
||||
description=t("EyeTracking.mesh_name_desc")
|
||||
)
|
||||
|
||||
head: StringProperty(
|
||||
name=t("EyeTracking.head_bone"),
|
||||
description=t("EyeTracking.head_bone_desc")
|
||||
)
|
||||
|
||||
eye_left: StringProperty(
|
||||
name=t("EyeTracking.eye_left"),
|
||||
description=t("EyeTracking.eye_left_desc")
|
||||
)
|
||||
|
||||
eye_right: StringProperty(
|
||||
name=t("EyeTracking.eye_right"),
|
||||
description=t("EyeTracking.eye_right_desc")
|
||||
)
|
||||
|
||||
disable_eye_movement: BoolProperty(
|
||||
name=t("EyeTracking.disable_movement"),
|
||||
description=t("EyeTracking.disable_movement_desc"),
|
||||
default=False
|
||||
)
|
||||
|
||||
disable_eye_blinking: BoolProperty(
|
||||
name=t("EyeTracking.disable_blinking"),
|
||||
description=t("EyeTracking.disable_blinking_desc"),
|
||||
default=False
|
||||
)
|
||||
|
||||
eye_distance: FloatProperty(
|
||||
name=t("EyeTracking.distance"),
|
||||
description=t("EyeTracking.distance_desc"),
|
||||
default=0.0,
|
||||
min=-1.0,
|
||||
max=1.0
|
||||
)
|
||||
|
||||
iris_height: FloatProperty(
|
||||
name=t("EyeTracking.iris_height"),
|
||||
description=t("EyeTracking.iris_height_desc"),
|
||||
default=0.0,
|
||||
min=-1.0,
|
||||
max=1.0
|
||||
)
|
||||
|
||||
eye_blink_shape: FloatProperty(
|
||||
name=t("EyeTracking.blink_shape"),
|
||||
description=t("EyeTracking.blink_shape_desc"),
|
||||
default=1.0,
|
||||
min=0.0,
|
||||
max=1.0
|
||||
)
|
||||
|
||||
eye_lowerlid_shape: FloatProperty(
|
||||
name=t("EyeTracking.lowerlid_shape"),
|
||||
description=t("EyeTracking.lowerlid_shape_desc"),
|
||||
default=1.0,
|
||||
min=0.0,
|
||||
max=1.0
|
||||
)
|
||||
|
||||
wink_left: StringProperty(
|
||||
name=t("EyeTracking.wink_left"),
|
||||
description=t("EyeTracking.wink_left_desc")
|
||||
)
|
||||
|
||||
wink_right: StringProperty(
|
||||
name=t("EyeTracking.wink_right"),
|
||||
description=t("EyeTracking.wink_right_desc")
|
||||
)
|
||||
|
||||
lowerlid_left: StringProperty(
|
||||
name=t("EyeTracking.lowerlid_left"),
|
||||
description=t("EyeTracking.lowerlid_left_desc")
|
||||
)
|
||||
|
||||
lowerlid_right: StringProperty(
|
||||
name=t("EyeTracking.lowerlid_right"),
|
||||
description=t("EyeTracking.lowerlid_right_desc")
|
||||
)
|
||||
|
||||
merge_mode: EnumProperty(
|
||||
name=t('CustomPanel.merge_mode'),
|
||||
description=t('CustomPanel.merge_mode_desc'),
|
||||
items=[
|
||||
('ARMATURE', t('CustomPanel.mode.armature'), t('CustomPanel.mode.armature_desc')),
|
||||
('MESH', t('CustomPanel.mode.mesh'), t('CustomPanel.mode.mesh_desc'))
|
||||
],
|
||||
default='ARMATURE'
|
||||
)
|
||||
|
||||
merge_armature_into: StringProperty(
|
||||
name=t('MergeArmature.into'),
|
||||
description=t('MergeArmature.into_desc'),
|
||||
default=""
|
||||
)
|
||||
|
||||
merge_armature: StringProperty(
|
||||
name=t('MergeArmature.from'),
|
||||
description=t('MergeArmature.from_desc'),
|
||||
default=""
|
||||
)
|
||||
|
||||
attach_mesh: StringProperty(
|
||||
name=t('AttachMesh.select'),
|
||||
description=t('AttachMesh.select_desc'),
|
||||
default=""
|
||||
)
|
||||
|
||||
attach_bone: StringProperty(
|
||||
name=t('AttachBone.select'),
|
||||
description=t('AttachBone.select_desc'),
|
||||
default=""
|
||||
)
|
||||
|
||||
merge_all_bones: BoolProperty(
|
||||
name=t('MergeArmature.merge_all'),
|
||||
description=t('MergeArmature.merge_all_desc'),
|
||||
default=True
|
||||
)))
|
||||
)
|
||||
|
||||
register_property((bpy.types.Scene, "selected_armature", bpy.props.EnumProperty(
|
||||
items=get_armatures,
|
||||
name=t("Quick_Access.selected_armature.label"),
|
||||
description=t("Quick_Access.selected_armature.desc"),
|
||||
default=0
|
||||
)))
|
||||
apply_transforms: BoolProperty(
|
||||
name=t('MergeArmature.apply_transforms'),
|
||||
description=t('MergeArmature.apply_transforms_desc'),
|
||||
default=True
|
||||
)
|
||||
|
||||
register_property((bpy.types.Scene, "merge_armature_source", bpy.props.EnumProperty(
|
||||
items=get_armatures_that_are_not_selected,
|
||||
name=t("MergeArmatures.selected_armature.label"),
|
||||
description=t("MergeArmatures.selected_armature.label"),
|
||||
default=0
|
||||
)))
|
||||
join_meshes: BoolProperty(
|
||||
name=t('MergeArmature.join_meshes'),
|
||||
description=t('MergeArmature.join_meshes_desc'),
|
||||
default=True
|
||||
)
|
||||
|
||||
register_property((bpy.types.Scene, "avatar_toolkit_updater_version_list", bpy.props.EnumProperty(
|
||||
name=t('Scene.avatar_toolkit_updater_version_list.name'),
|
||||
description=t('Scene.avatar_toolkit_updater_version_list.description'),
|
||||
items=get_version_list
|
||||
)))
|
||||
|
||||
#happy with how compressed this get_texture_node_list method is - @989onan
|
||||
def get_texture_node_list(self: Material, context: Context) -> list[set[3]]:
|
||||
|
||||
if self.use_nodes:
|
||||
|
||||
Object.Enum = [((i.image.name if i.image else i.name+"_image"),(i.image.name if i.image else "node with no image..."),(i.image.name if i.image else i.name),index+1) for index,i in enumerate(self.node_tree.nodes) if i.bl_idname == "ShaderNodeTexImage"]
|
||||
if not len(Object.Enum):
|
||||
Object.Enum = [(t("TextureAtlas.error.label"), t("TextureAtlas.no_images_error.desc") , t("TextureAtlas.error.label"), 0)]
|
||||
else:
|
||||
Object.Enum = [(t("TextureAtlas.error.label"), t("TextureAtlas.no_nodes_error.desc"), t("TextureAtlas.error.label"), 0)]
|
||||
Object.Enum.append((t("TextureAtlas.none.label"), t("TextureAtlas.none.label"), t("TextureAtlas.none.label"), 0))
|
||||
return Object.Enum
|
||||
|
||||
register_property((Material, "texture_atlas_albedo", EnumProperty(
|
||||
name=t("TextureAtlas.albedo"),
|
||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.albedo").lower()),
|
||||
default=0,
|
||||
items=get_texture_node_list)))
|
||||
register_property((Material, "texture_atlas_normal", EnumProperty(
|
||||
name=t("TextureAtlas.normal"),
|
||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.normal").lower()),
|
||||
default=0,
|
||||
items=get_texture_node_list)))
|
||||
register_property((Material, "texture_atlas_emission", EnumProperty(
|
||||
name=t("TextureAtlas.emission"),
|
||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.emission").lower()),
|
||||
default=0,
|
||||
items=get_texture_node_list)))
|
||||
register_property((Material, "texture_atlas_ambient_occlusion", EnumProperty(
|
||||
name=t("TextureAtlas.ambient_occlusion"),
|
||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.ambient_occlusion").lower()),
|
||||
default=0,
|
||||
items=get_texture_node_list)))
|
||||
register_property((Material, "texture_atlas_height", EnumProperty(
|
||||
name=t("TextureAtlas.height"),
|
||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.height").lower()),
|
||||
default=0,
|
||||
items=get_texture_node_list)))
|
||||
register_property((Material, "texture_atlas_roughness", EnumProperty(
|
||||
name=t("TextureAtlas.roughness"),
|
||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.roughness").lower()),
|
||||
default=0,
|
||||
items=get_texture_node_list)))
|
||||
|
||||
register_property((Scene, "texture_atlas_material_index", IntProperty(
|
||||
default=-1,
|
||||
get=(lambda self : -1),
|
||||
set=(lambda self,context : None))))
|
||||
remove_zero_weights: BoolProperty(
|
||||
name=t('MergeArmature.remove_zero_weights'),
|
||||
description=t('MergeArmature.remove_zero_weights_desc'),
|
||||
default=True
|
||||
)
|
||||
|
||||
register_property((Scene, "materials", CollectionProperty(type=SceneMatClass)))
|
||||
cleanup_shape_keys: BoolProperty(
|
||||
name=t('MergeArmature.cleanup_shape_keys'),
|
||||
description=t('MergeArmature.cleanup_shape_keys_desc'),
|
||||
default=True
|
||||
)
|
||||
|
||||
register_property((Scene, "texture_atlas_Has_Mat_List_Shown", BoolProperty(
|
||||
default=False,
|
||||
get=MaterialListBool.get_bool,
|
||||
set=MaterialListBool.set_bool)))
|
||||
attach_mesh: StringProperty(
|
||||
name=t("Tools.attach_mesh_select"),
|
||||
description=t("Tools.attach_mesh_select_desc")
|
||||
)
|
||||
|
||||
attach_bone: StringProperty(
|
||||
name=t("Tools.attach_bone_select"),
|
||||
description=t("Tools.attach_bone_select_desc")
|
||||
)
|
||||
|
||||
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:
|
||||
#if you register properties with register_property then you shouldn't need this function.
|
||||
pass
|
||||
"""Unregister the Avatar Toolkit property group"""
|
||||
logger.info("Unregistering Avatar Toolkit properties")
|
||||
del bpy.types.Scene.avatar_toolkit
|
||||
logger.debug("Properties unregistered successfully")
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import bpy
|
||||
import typing
|
||||
from typing import List, Type
|
||||
|
||||
# List to store the classes to register
|
||||
__bl_classes = []
|
||||
# List to store the ordered classes for registration
|
||||
__bl_ordered_classes = []
|
||||
# List to store props to register
|
||||
__bl_props = []
|
||||
|
||||
def register_wrap(cls):
|
||||
# Check if the class has a 'bl_rna' attribute (indicating it's a Blender class)
|
||||
if hasattr(cls, 'bl_rna'):
|
||||
# Add the class to the list of classes to register
|
||||
__bl_classes.append(cls)
|
||||
return cls
|
||||
|
||||
# Register all properties
|
||||
def register_property(prop):
|
||||
__bl_props.append(prop)
|
||||
|
||||
def register_properties():
|
||||
for prop in __bl_props:
|
||||
if isinstance(prop[2], bpy.props._PropertyDeferred):
|
||||
setattr(prop[0], prop[1], prop[2])
|
||||
else:
|
||||
prop()
|
||||
|
||||
def clear_registration():
|
||||
__bl_classes.clear()
|
||||
__bl_ordered_classes.clear()
|
||||
__bl_props.clear()
|
||||
|
||||
def unregister_properties():
|
||||
for prop in reversed(__bl_props):
|
||||
try:
|
||||
delattr(prop[0], prop[1])
|
||||
except AttributeError:
|
||||
continue
|
||||
clear_registration()
|
||||
|
||||
#- @989onan had to add this from Cats. This is extremely important else you will be screamed at by register order issues!
|
||||
# Find order to register to solve dependencies
|
||||
|
||||
#################################################
|
||||
|
||||
def toposort(deps_dict):
|
||||
sorted_list = []
|
||||
sorted_values = set()
|
||||
while len(deps_dict) > 0:
|
||||
unsorted = []
|
||||
for value, deps in deps_dict.items():
|
||||
if len(deps) == 0:
|
||||
sorted_list.append(value)
|
||||
sorted_values.add(value)
|
||||
else:
|
||||
unsorted.append(value)
|
||||
deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted}
|
||||
|
||||
#sort_order(sorted_list) #to sort by 'bl_order' so we can choose how things may appear in the ui
|
||||
return sorted_list
|
||||
|
||||
|
||||
|
||||
def order_classes():
|
||||
deps_dict = {}
|
||||
classes_to_register = set(iter_classes_to_register())
|
||||
for class_obj in classes_to_register:
|
||||
deps_dict[class_obj] = set(iter_own_register_deps(class_obj, classes_to_register))
|
||||
|
||||
__bl_ordered_classes.clear()
|
||||
# Then put everything else sorted into the list
|
||||
for class_obj in toposort(deps_dict):
|
||||
__bl_ordered_classes.append(class_obj)
|
||||
|
||||
print(__bl_ordered_classes)
|
||||
__bl_classes.clear()
|
||||
|
||||
|
||||
def iter_classes_to_register():
|
||||
for class_obj in __bl_classes:
|
||||
yield class_obj
|
||||
|
||||
|
||||
def iter_own_register_deps(class_obj, own_classes):
|
||||
yield from (dep for dep in iter_register_deps(class_obj) if dep in own_classes)
|
||||
|
||||
|
||||
def iter_register_deps(class_obj):
|
||||
for value in typing.get_type_hints(class_obj, {}, {}, True).values():
|
||||
dependency = get_dependency_from_annotation(value)
|
||||
if dependency is not None:
|
||||
yield dependency
|
||||
if hasattr(class_obj, "bl_parent_id"):
|
||||
if class_obj.bl_parent_id != "":
|
||||
for dependency in __bl_classes:
|
||||
if dependency.bl_idname == class_obj.bl_parent_id:
|
||||
yield dependency
|
||||
|
||||
def get_dependency_from_annotation(value):
|
||||
if isinstance(value, tuple) and len(value) == 2:
|
||||
if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty):
|
||||
return value[1]["type"]
|
||||
return None
|
||||
@@ -0,0 +1,112 @@
|
||||
import os
|
||||
import json
|
||||
import bpy
|
||||
import logging
|
||||
from bpy.app.translations import locale
|
||||
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)
|
||||
resources_dir = os.path.join(main_dir, "resources")
|
||||
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()
|
||||
|
||||
dictionary = dict()
|
||||
languages = ["auto"]
|
||||
|
||||
# Populate languages list
|
||||
for i in os.listdir(translations_dir):
|
||||
lang = i.split(".")[0]
|
||||
if lang != "auto":
|
||||
languages.append(lang)
|
||||
|
||||
language_index: int = get_preference("language", 0)
|
||||
logger.debug(f"Loading translations for language index: {language_index}")
|
||||
|
||||
if language_index == 0: # "auto"
|
||||
language: str = bpy.context.preferences.view.language
|
||||
else:
|
||||
try:
|
||||
language = languages[language_index]
|
||||
except IndexError:
|
||||
language = bpy.context.preferences.view.language
|
||||
|
||||
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):
|
||||
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):
|
||||
dictionary = _load_translation_file(custom_translation_file)
|
||||
else:
|
||||
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):
|
||||
dictionary = _load_translation_file(default_file)
|
||||
else:
|
||||
logger.error("Default translation file not found")
|
||||
|
||||
_translation_cache[language] = dictionary
|
||||
return dictionary != old_dictionary
|
||||
|
||||
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:
|
||||
logger.warning(f'Unknown phrase: {phrase}')
|
||||
return default if default is not None else phrase
|
||||
return output.format(**kwargs) if kwargs else output
|
||||
|
||||
def get_language_display_name(lang: str) -> str:
|
||||
"""Get the display name for a language code"""
|
||||
return t(f"Language.{lang}", lang)
|
||||
|
||||
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: 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()
|
||||
context.scene.avatar_toolkit.language_changed = True
|
||||
bpy.ops.avatar_toolkit.translation_restart_popup('INVOKE_DEFAULT')
|
||||
|
||||
# Initial load of translations
|
||||
load_translations()
|
||||
+44
-18
@@ -10,10 +10,9 @@ import time
|
||||
from urllib import request, error
|
||||
from threading import Thread
|
||||
from bpy.app.handlers import persistent
|
||||
from ..functions.translations import t
|
||||
from .translations import t
|
||||
from .addon_preferences import get_preference, get_current_version, save_preference
|
||||
from .register import register_wrap
|
||||
from ..ui.panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||
from ..ui.main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||
from typing import Dict, List, Tuple, Optional, Set, Any
|
||||
|
||||
GITHUB_REPO = "teamneoneko/Avatar-Toolkit"
|
||||
@@ -27,7 +26,7 @@ version_list: Optional[Dict[str, List[str]]] = None
|
||||
main_dir: str = os.path.dirname(os.path.dirname(__file__))
|
||||
downloads_dir: str = os.path.join(main_dir, "downloads")
|
||||
|
||||
@register_wrap
|
||||
|
||||
class AvatarToolkit_OT_CheckForUpdate(bpy.types.Operator):
|
||||
bl_idname = 'avatar_toolkit.check_for_update'
|
||||
bl_label = t('CheckForUpdateButton.label')
|
||||
@@ -38,7 +37,7 @@ class AvatarToolkit_OT_CheckForUpdate(bpy.types.Operator):
|
||||
check_for_update_background()
|
||||
return {'FINISHED'}
|
||||
|
||||
@register_wrap
|
||||
|
||||
class AvatarToolkit_OT_UpdateToLatest(bpy.types.Operator):
|
||||
bl_idname = 'avatar_toolkit.update_latest'
|
||||
bl_label = t('UpdateToLatestButton.label')
|
||||
@@ -49,7 +48,7 @@ class AvatarToolkit_OT_UpdateToLatest(bpy.types.Operator):
|
||||
update_now(latest=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
@register_wrap
|
||||
|
||||
class AvatarToolkit_OT_UpdateNotificationPopup(bpy.types.Operator):
|
||||
bl_idname = "avatar_toolkit.update_notification_popup"
|
||||
bl_label = t('UpdateNotificationPopup.label')
|
||||
@@ -69,7 +68,7 @@ class AvatarToolkit_OT_UpdateNotificationPopup(bpy.types.Operator):
|
||||
col = layout.column(align=True)
|
||||
col.label(text=t('UpdateNotificationPopup.newUpdate', default="New update available: {version}").format(version=latest_version_str))
|
||||
|
||||
@register_wrap
|
||||
|
||||
class AvatarToolkit_PT_UpdaterPanel(bpy.types.Panel):
|
||||
bl_label = t("Updater.label")
|
||||
bl_idname = "OBJECT_PT_avatar_toolkit_updater"
|
||||
@@ -77,13 +76,14 @@ 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 = 8
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
layout = self.layout
|
||||
draw_updater_panel(context, layout)
|
||||
|
||||
@register_wrap
|
||||
|
||||
class AvatarToolkit_OT_RestartBlenderPopup(bpy.types.Operator):
|
||||
bl_idname = "avatar_toolkit.restart_blender_popup"
|
||||
bl_label = t('RestartBlenderPopup.label', default="Restart Blender")
|
||||
@@ -277,22 +277,48 @@ def get_version_list(self, context: bpy.types.Context) -> List[Tuple[str, str, s
|
||||
return [(v, v, '') for v in version_list.keys()] if version_list else []
|
||||
|
||||
def draw_updater_panel(context: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||
col = layout.column(align=True)
|
||||
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
|
||||
# Header
|
||||
row = col.row()
|
||||
row.scale_y = 1.2
|
||||
row.label(text=t('Updater.label'), icon='DOWNARROW_HLT')
|
||||
|
||||
col.separator()
|
||||
|
||||
# Update check/status section
|
||||
if is_checking_for_update:
|
||||
col.operator(AvatarToolkit_OT_CheckForUpdate.bl_idname, text=t('Updater.CheckForUpdateButton.label'))
|
||||
col.operator(AvatarToolkit_OT_CheckForUpdate.bl_idname,
|
||||
text=t('Updater.CheckForUpdateButton.label'),
|
||||
icon='SORTTIME')
|
||||
elif update_needed:
|
||||
col.operator(AvatarToolkit_OT_UpdateToLatest.bl_idname, text=t('Updater.UpdateToLatestButton.label', name=latest_version_str))
|
||||
update_row = col.row(align=True)
|
||||
update_row.scale_y = 1.5
|
||||
update_row.alert = True
|
||||
update_row.operator(AvatarToolkit_OT_UpdateToLatest.bl_idname,
|
||||
text=t('Updater.UpdateToLatestButton.label', name=latest_version_str),
|
||||
icon='IMPORT')
|
||||
else:
|
||||
col.operator(AvatarToolkit_OT_CheckForUpdate.bl_idname, text=t('Updater.CheckForUpdateButton.label_alt'))
|
||||
col.operator(AvatarToolkit_OT_CheckForUpdate.bl_idname,
|
||||
text=t('Updater.CheckForUpdateButton.label_alt'),
|
||||
icon='FILE_REFRESH')
|
||||
|
||||
# Version selection section
|
||||
col.separator()
|
||||
row = col.row(align=True)
|
||||
row.prop(context.scene, 'avatar_toolkit_updater_version_list', text='')
|
||||
row.operator(AvatarToolkit_OT_UpdateToLatest.bl_idname, text=t('Updater.UpdateToSelectedButton.label'))
|
||||
box_inner = col.box()
|
||||
box_inner.label(text=t('Updater.selectVersion'), icon='SETTINGS')
|
||||
row = box_inner.row(align=True)
|
||||
row.prop(context.scene.avatar_toolkit, 'avatar_toolkit_updater_version_list', text='')
|
||||
row.operator(AvatarToolkit_OT_UpdateToLatest.bl_idname,
|
||||
text=t('Updater.UpdateToSelectedButton.label'),
|
||||
icon='IMPORT')
|
||||
|
||||
# Current version info
|
||||
col.separator()
|
||||
col.label(text=t('Updater.currentVersion').format(name=get_current_version()))
|
||||
curr_ver_row = col.row()
|
||||
curr_ver_row.label(text=t('Updater.currentVersion').format(name=get_current_version()),
|
||||
icon='CHECKMARK')
|
||||
|
||||
def ui_refresh() -> None:
|
||||
for windowManager in bpy.data.window_managers:
|
||||
|
||||
Reference in New Issue
Block a user