From fe8f5f69d5b10158d467e76191ca5dfff7e22608 Mon Sep 17 00:00:00 2001 From: Yusarina Date: Mon, 2 Dec 2024 01:52:11 +0000 Subject: [PATCH] Plugin Registration Changes - Re-wrote how the plugin registers itself. - No longer need @register_wrapper classes get auto detected and added. - The new Auto loader is much better then the old way, no longer need "if "bpy" not in locals():" this was an old way of doing things and wasn't really efficient. using auto_load.py provides several advantages: - It automatically discovers and loads all modules in the addon. - It handles dependencies between classes correctly through topological sorting. - It manages registration order automatically. - It properly handles unregistration in the correct order. This approach is much less error prone and I not had any issues so far. However it still needs testing fully. I have also start to re-organise files into folders as well, this is going to be needed so we don't have a long list of files as Avatar Toolkit is getting larger then i originally planned. --- __init__.py | 60 +---- core/__init__.py | 20 -- core/auto_load.py | 172 ++++++++++++++ core/common.py | 1 - core/{ => exporters}/export_resonite.py | 8 +- core/{ => importers}/import_pmd.py | 0 core/{ => importers}/import_pmx.py | 0 core/{ => importers}/importer.py | 0 core/preferences.json | 3 - core/properties.py | 295 ++++++++++++------------ core/register.py | 105 --------- core/translations.py | 97 ++++++++ core/updater.py | 15 +- functions/__init__.py | 18 -- functions/additional_tools.py | 13 +- functions/armature_modifying.py | 19 +- functions/atlas_materials.py | 5 +- functions/combine_materials.py | 5 +- functions/digitigrade_legs.py | 5 +- functions/import_anything.py | 11 +- functions/mesh_tools.py | 11 +- functions/mmd_functions.py | 11 +- functions/remove_doubles_safely.py | 7 +- functions/resonite_functions.py | 5 +- functions/rigify_functions.py | 11 +- functions/uv_tools.py | 5 +- functions/viseme.py | 5 +- ui/__init__.py | 15 -- ui/atlas_materials.py | 93 ++++---- ui/credits_support.py | 6 +- ui/{panel.py => main_panel.py} | 9 +- ui/merge_armatures.py | 15 +- ui/mmd_options.py | 7 +- ui/optimization.py | 10 +- ui/quick_access.py | 23 +- ui/settings.py | 14 +- ui/tools.py | 24 +- ui/uv_panel.py | 9 +- ui/uv_tools.py | 10 +- ui/viseme.py | 19 +- 40 files changed, 581 insertions(+), 580 deletions(-) create mode 100644 core/auto_load.py rename core/{ => exporters}/export_resonite.py (88%) rename core/{ => importers}/import_pmd.py (100%) rename core/{ => importers}/import_pmx.py (100%) rename core/{ => importers}/importer.py (100%) delete mode 100644 core/preferences.json delete mode 100644 core/register.py create mode 100644 core/translations.py rename ui/{panel.py => main_panel.py} (84%) diff --git a/__init__.py b/__init__.py index c628dc6..4016289 100644 --- a/__init__.py +++ b/__init__.py @@ -1,55 +1,13 @@ -if "bpy" not in locals(): - import bpy - from . import ui - from . import core - from . import functions - from .core import register - from .core.register import __bl_ordered_classes - from .core import properties - from .core import addon_preferences - from .core.updater import check_for_update_on_start -else: - import importlib - importlib.reload(ui) - importlib.reload(core) - importlib.reload(functions) - importlib.reload(properties) - importlib.reload(addon_preferences) +modules = None +ordered_classes = None def register(): - print("Registering Avatar Toolkit") - # Register the addon properties - properties.register() - - # Load the translations - functions.translations.load_translations() - - # Order the classes before registration - core.register.order_classes() - # Register the UI classes - for cls in core.register.__bl_ordered_classes: - print("registering " + str(cls)) - bpy.utils.register_class(cls) - - #finally register properties that may use some classes. - core.register.register_properties() - - bpy.app.handlers.load_post.append(check_for_update_on_start) - - from .functions.mesh_tools import AvatarToolkit_OT_ApplyShapeKey - - bpy.types.MESH_MT_shape_key_context_menu.append((lambda self, context: self.layout.separator())) - bpy.types.MESH_MT_shape_key_context_menu.append((lambda self, context: self.layout.operator(AvatarToolkit_OT_ApplyShapeKey.bl_idname, icon="KEY_HLT"))) + from .core import auto_load + print("Starting registration") + auto_load.init() + auto_load.register() + print("Registration complete") def unregister(): - print("Unregistering Avatar Toolkit") - # Unregister the UI classes - if check_for_update_on_start in bpy.app.handlers.load_post: - bpy.app.handlers.load_post.remove(check_for_update_on_start) - - # Iterate over the classes to unregister in reverse order and unregister them - for cls in reversed(list(__bl_ordered_classes)): - bpy.utils.unregister_class(cls) - print("unregistering " + str(cls)) - core.register.unregister_properties() - properties.unregister() + from .core import auto_load + auto_load.unregister() diff --git a/core/__init__.py b/core/__init__.py index 50c92e7..e69de29 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -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) diff --git a/core/auto_load.py b/core/auto_load.py new file mode 100644 index 0000000..7ca78ab --- /dev/null +++ b/core/auto_load.py @@ -0,0 +1,172 @@ +import os +import bpy +import sys +import typing +import inspect +import pkgutil +import tomllib +import importlib +from pathlib import Path + +__all__ = ( + "init", + "register", + "unregister", +) + +modules = None +ordered_classes = None + +def init(): + global modules + global ordered_classes + 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(): + 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(): + 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(): + 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): + 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, package_name): + for name in sorted(iter_module_names(path)): + yield importlib.import_module("." + name, package_name) + +def iter_module_names(path): + print(f"Scanning path: {path}") # Debug path + modules_list = list(pkgutil.iter_modules([str(path)])) + print(f"Found these modules: {modules_list}") # Debug modules + 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): + return toposort(get_register_deps_dict(modules)) + +def get_register_deps_dict(modules): + 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, classes_to_register): + yield from (dep for dep in iter_register_deps(cls) if dep in classes_to_register) + +def iter_register_deps(cls): + 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): + 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): + 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): + 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): + for value in module.__dict__.values(): + if inspect.isclass(value): + yield value + +def get_register_base_types(): + return set(getattr(bpy.types, name) for name in [ + "Panel", "Operator", "PropertyGroup", + "AddonPreferences", "Header", "Menu", + "Node", "NodeSocket", "NodeTree", + "UIList", "RenderEngine" + ]) + +def toposort(deps_dict): + sorted_list = [] + sorted_values = set() + + # First pass: Register panels without parents + 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')] + + # Add base panels first + for value, deps in base_panels: + if len(deps) == 0: + sorted_list.append(value) + sorted_values.add(value) + + # Then add child panels + 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 + diff --git a/core/common.py b/core/common.py index 30cd235..b1f3644 100644 --- a/core/common.py +++ b/core/common.py @@ -6,7 +6,6 @@ import time import webbrowser import typing -from ..core.register import register_wrap from typing import List, Optional, Tuple from bpy.types import Object, ShapeKey, Mesh, Context, Material, PropertyGroup from functools import lru_cache diff --git a/core/export_resonite.py b/core/exporters/export_resonite.py similarity index 88% rename from core/export_resonite.py rename to core/exporters/export_resonite.py index c5a668f..9de6448 100644 --- a/core/export_resonite.py +++ b/core/exporters/export_resonite.py @@ -1,14 +1,10 @@ import bpy - from typing import List, Optional -from .common import get_armature +from ...core.common import get_armature from bpy.types import Object, ShapeKey, Mesh, Context, Operator from functools import lru_cache -from ..core.register import register_wrap -from ..functions.translations import t +from ...core.translations import t - -@register_wrap class AvatarToolKit_OT_ExportResonite(Operator): bl_idname = 'avatar_toolkit.export_resonite' bl_label = t("Importer.export_resonite.label") diff --git a/core/import_pmd.py b/core/importers/import_pmd.py similarity index 100% rename from core/import_pmd.py rename to core/importers/import_pmd.py diff --git a/core/import_pmx.py b/core/importers/import_pmx.py similarity index 100% rename from core/import_pmx.py rename to core/importers/import_pmx.py diff --git a/core/importer.py b/core/importers/importer.py similarity index 100% rename from core/importer.py rename to core/importers/importer.py diff --git a/core/preferences.json b/core/preferences.json deleted file mode 100644 index b0ca7bf..0000000 --- a/core/preferences.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "language": 0 -} \ No newline at end of file diff --git a/core/properties.py b/core/properties.py index 58cd396..5d5c755 100644 --- a/core/properties.py +++ b/core/properties.py @@ -1,169 +1,180 @@ 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 .translations import t, get_languages_list, update_language +from bpy.types import PropertyGroup, Material, Scene, Object, Context +from bpy.props import (StringProperty, BoolProperty, EnumProperty, + IntProperty, FloatProperty, CollectionProperty, + PointerProperty) +from .addon_preferences import get_preference +from .common import SceneMatClass, MaterialListBool, get_armatures, get_mesh_items, get_armatures_that_are_not_selected from .updater import get_version_list -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"), +class AvatarToolkitSceneProperties(PropertyGroup): + language: EnumProperty( + name="Language", + description="Select the language for the addon", items=get_languages_list, - default=default_language, update=update_language - ))) - - register_property((bpy.types.Scene, "selected_mesh", bpy.props.EnumProperty( + ) + + selected_mesh: EnumProperty( items=get_mesh_items, - name=t("VisemePanel.selected_mesh.label"), - description=t("VisemePanel.selected_mesh.desc") - ))) + name="Selected Mesh", + description="Select mesh to modify" + ) - register_property((bpy.types.Object, "material_group_expanded", bpy.props.BoolProperty( - name="Expand Material Group", - description="Show/hide materials for this mesh", - default=False - ))) - - 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( + material_search_filter: 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( + merge_armature_apply_transforms: 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( + name="Apply Transforms", + description="Apply transforms when merging armatures" + ) + + merge_armature_align_bones: BoolProperty( default=False, - name=t("MergeArmature.merge_armatures.align_bones.label"), - description=t("MergeArmature.merge_armatures.align_bones.desc") - ))) - - register_property((bpy.types.Scene, "avatar_toolkit_language_changed", bpy.props.BoolProperty(default=False))) + name="Align Bones", + description="Align bones when merging armatures" + ) - 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))) + progress_steps: IntProperty(default=0) + progress_current: IntProperty(default=0) + language_changed: BoolProperty(default=False) - 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"), + mouth_a: StringProperty( + name="Mouth A", + description="Shape key for A sound" + ) + + mouth_o: StringProperty( + name="Mouth O", + description="Shape key for O sound" + ) + + mouth_ch: StringProperty( + name="Mouth CH", + description="Shape key for CH sound" + ) + + shape_intensity: FloatProperty( + name="Shape Intensity", + description="Intensity of shape key modifications", 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"), + ) + + merge_twist_bones: BoolProperty( + name="Merge Twist Bones", + description="Merge twist bones during processing", default=True - ))) + ) - register_property((bpy.types.Scene, "selected_armature", bpy.props.EnumProperty( + selected_armature: EnumProperty( items=get_armatures, - name=t("Quick_Access.selected_armature.label"), - description=t("Quick_Access.selected_armature.desc"), - default=0 - ))) + name="Selected Armature", + description="Select the armature to work with" + ) - register_property((bpy.types.Scene, "merge_armature_source", bpy.props.EnumProperty( + merge_armature_source: EnumProperty( items=get_armatures_that_are_not_selected, - name=t("MergeArmatures.selected_armature.label"), - description=t("MergeArmatures.selected_armature.label"), - default=0 - ))) + name="Source Armature", + description="Select the source armature for merging" + ) - 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)))) + texture_atlas_material_index: IntProperty( + default=-1, + get=lambda self: -1, + set=lambda self, context: None + ) - register_property((Scene, "materials", CollectionProperty(type=SceneMatClass))) - - register_property((Scene, "texture_atlas_Has_Mat_List_Shown", BoolProperty( + materials: CollectionProperty(type=SceneMatClass) + + texture_atlas_Has_Mat_List_Shown: BoolProperty( default=False, - get=MaterialListBool.get_bool, - set=MaterialListBool.set_bool))) + get=MaterialListBool.get_bool, + set=MaterialListBool.set_bool + ) -def unregister() -> None: - #if you register properties with register_property then you shouldn't need this function. - pass +class AvatarToolkitMaterialProperties(PropertyGroup): + material_expanded: BoolProperty( + name="Expand Material", + description="Show/hide material properties", + default=False + ) + + include_in_atlas: BoolProperty( + name="Include in Atlas", + description="Include this material in texture atlas", + default=True + ) + + def get_texture_node_list(self, context): + if self.use_nodes: + nodes = [(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 nodes: + nodes = [("Error", "No images found", "Error", 0)] + else: + nodes = [("Error", "No node tree found", "Error", 0)] + nodes.append(("None", "None", "None", 0)) + return nodes + + texture_atlas_albedo: EnumProperty( + name="Albedo", + description="Albedo texture for atlas", + items=get_texture_node_list + ) + + texture_atlas_normal: EnumProperty( + name="Normal", + description="Normal map for atlas", + items=get_texture_node_list + ) + + texture_atlas_emission: EnumProperty( + name="Emission", + description="Emission texture for atlas", + items=get_texture_node_list + ) + + texture_atlas_ambient_occlusion: EnumProperty( + name="Ambient Occlusion", + description="AO texture for atlas", + items=get_texture_node_list + ) + + texture_atlas_height: EnumProperty( + name="Height", + description="Height map for atlas", + items=get_texture_node_list + ) + + texture_atlas_roughness: EnumProperty( + name="Roughness", + description="Roughness map for atlas", + items=get_texture_node_list + ) + +class AvatarToolkitObjectProperties(PropertyGroup): + material_group_expanded: BoolProperty( + name="Expand Material Group", + description="Show/hide materials for this mesh", + default=False + ) + +def register(): + bpy.types.Scene.avatar_toolkit = PointerProperty(type=AvatarToolkitSceneProperties) + bpy.types.Material.avatar_toolkit = PointerProperty(type=AvatarToolkitMaterialProperties) + bpy.types.Object.avatar_toolkit = PointerProperty(type=AvatarToolkitObjectProperties) + +def unregister(): + del bpy.types.Scene.avatar_toolkit + del bpy.types.Material.avatar_toolkit + del bpy.types.Object.avatar_toolkit diff --git a/core/register.py b/core/register.py deleted file mode 100644 index 915c372..0000000 --- a/core/register.py +++ /dev/null @@ -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 diff --git a/core/translations.py b/core/translations.py new file mode 100644 index 0000000..c123ad7 --- /dev/null +++ b/core/translations.py @@ -0,0 +1,97 @@ +import os +import json +import bpy +from bpy.app.translations import locale +from typing import Dict, List, Tuple +from .addon_preferences import save_preference, get_preference + +# 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] = [] +verbose: bool = True + +def load_translations() -> bool: + 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 = get_preference("language", 0) + # print(f"Loading translations for language index: {language_index}") # Debug print + + if language_index == 0: # "auto" + language = bpy.context.preferences.view.language + else: + try: + language = languages[language_index] + except IndexError: + language = bpy.context.preferences.view.language + + # print(f"Selected language: {language}") # Debug print + + translation_file: str = os.path.join(translations_dir, language + ".json") + if os.path.exists(translation_file): + with open(translation_file, 'r', encoding='utf-8') as file: + dictionary = json.load(file)["messages"] + # print(f"Loaded translations: {dictionary}") # Debug print + else: + custom_language: str = language.split("_")[0] + custom_translation_file: str = os.path.join(translations_dir, custom_language + ".json") + if os.path.exists(custom_translation_file): + with open(custom_translation_file, 'r', encoding='utf-8') as file: + dictionary = json.load(file)["messages"] + # print(f"Loaded custom translations: {dictionary}") # Debug print + else: + print(f"Translation file not found for language: {language}") + default_file: str = os.path.join(translations_dir, "en_US.json") + if os.path.exists(default_file): + with open(default_file, 'r', encoding='utf-8') as file: + dictionary = json.load(file)["messages"] + # print(f"Loaded default translations: {dictionary}") # Debug print + else: + print("Default translation file 'en_US.json' not found.") + + return dictionary != old_dictionary + +def t(phrase: str, default: str = None, **kwargs) -> str: + output: str = dictionary.get(phrase) + if output is None: + if verbose: + print(f'Warning: Unknown phrase: {phrase}') + return default if default is not None else phrase + # print(f"Translating '{phrase}' to '{output}'") # Debug print + return output.format(**kwargs) if kwargs else output + +def get_language_display_name(lang: str) -> str: + if lang == "auto": + return t("Language.auto", "Automatic") + return t(f"Language.{lang}", lang) + +def get_languages_list(self, context) -> List[Tuple[str, str, str]]: + return [(str(i), get_language_display_name(lang), f"Use {lang} language") for i, lang in enumerate(languages)] + +def update_language(self, context): + print(f"Updating language to: {self.avatar_toolkit_language}") # Debug print + save_preference("language", int(self.avatar_toolkit_language)) + load_translations() + # Set a flag to indicate that a language change has occurred + context.scene.avatar_toolkit_language_changed = True + # Show popup after language change + bpy.ops.avatar_toolkit.translation_restart_popup('INVOKE_DEFAULT') + +# Initial load of translations +# print("Performing initial load of translations") # Debug print +load_translations() diff --git a/core/updater.py b/core/updater.py index 96bc017..53eb248 100644 --- a/core/updater.py +++ b/core/updater.py @@ -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" @@ -83,7 +82,7 @@ class AvatarToolkit_PT_UpdaterPanel(bpy.types.Panel): 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") diff --git a/functions/__init__.py b/functions/__init__.py index afece68..e69de29 100644 --- a/functions/__init__.py +++ b/functions/__init__.py @@ -1,18 +0,0 @@ -from ..core.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) diff --git a/functions/additional_tools.py b/functions/additional_tools.py index 0979207..57f8f3d 100644 --- a/functions/additional_tools.py +++ b/functions/additional_tools.py @@ -1,11 +1,10 @@ import bpy import math from bpy.types import Context, Operator -from ..core.register import register_wrap from ..core.common import get_selected_armature, is_valid_armature, get_all_meshes -from ..functions.translations import t +from ..core.translations import t + -@register_wrap class AvatarToolKit_OT_ApplyTransforms(Operator): bl_idname = "avatar_toolkit.apply_transforms" bl_label = t("Tools.apply_transforms.label") @@ -37,7 +36,7 @@ class AvatarToolKit_OT_ApplyTransforms(Operator): self.report({'INFO'}, t("Tools.apply_transforms.success")) return {'FINISHED'} -@register_wrap + class AvatarToolKit_OT_ConnectBones(Operator): bl_idname = "avatar_toolkit.connect_bones" bl_label = t("Tools.connect_bones.label") @@ -90,7 +89,7 @@ class AvatarToolKit_OT_ConnectBones(Operator): layout = self.layout layout.prop(self, "min_distance") -@register_wrap + class AvatarToolKit_OT_DeleteBoneConstraints(Operator): bl_idname = "avatar_toolkit.delete_bone_constraints" bl_label = t("Tools.delete_bone_constraints.label") @@ -120,7 +119,7 @@ class AvatarToolKit_OT_DeleteBoneConstraints(Operator): self.report({'INFO'}, t("Tools.delete_bone_constraints.success").format(constraints_removed=constraints_removed)) return {'FINISHED'} -@register_wrap + class AvatarToolKit_OT_SeparateByMaterials(Operator): bl_idname = "avatar_toolkit.separate_by_materials" bl_label = t("Tools.separate_by_materials.label") @@ -140,7 +139,7 @@ class AvatarToolKit_OT_SeparateByMaterials(Operator): self.report({'INFO'}, t("Tools.separate_by_materials.success")) return {'FINISHED'} -@register_wrap + class AvatarToolKit_OT_SeparateByLooseParts(Operator): bl_idname = "avatar_toolkit.separate_by_loose_parts" bl_label = t("Tools.separate_by_loose_parts.label") diff --git a/functions/armature_modifying.py b/functions/armature_modifying.py index efc109f..f8c1acd 100644 --- a/functions/armature_modifying.py +++ b/functions/armature_modifying.py @@ -1,13 +1,12 @@ import bpy -from ..core.register import register_wrap from bpy.types import Context, Mesh, Panel, Operator, Armature, EditBone -from ..functions.translations import t +from ..core.translations import t from ..core.common import get_selected_armature, get_all_meshes from ..core import common from ..core.dictionaries import bone_names from mathutils import Matrix -@register_wrap + class AvatarToolkit_OT_StartPoseMode(Operator): bl_idname = 'avatar_toolkit.start_pose_mode' bl_label = t("Quick_Access.start_pose_mode.label") @@ -33,7 +32,7 @@ class AvatarToolkit_OT_StartPoseMode(Operator): return {'FINISHED'} -@register_wrap + class AvatarToolkit_OT_StopPoseMode(Operator): bl_idname = 'avatar_toolkit.stop_pose_mode' bl_label = t("Quick_Access.stop_pose_mode.label") @@ -55,7 +54,7 @@ class AvatarToolkit_OT_StopPoseMode(Operator): return {'FINISHED'} -@register_wrap + class AvatarToolkit_OT_ApplyPoseAsShapekey(Operator): bl_idname = 'avatar_toolkit.apply_pose_as_shapekey' bl_label = t("Quick_Access.apply_pose_as_shapekey.label") @@ -98,7 +97,7 @@ class AvatarToolkit_OT_ApplyPoseAsShapekey(Operator): self.report({'INFO'}, t('Tools.apply_pose_as_rest.success')) return {'FINISHED'} -@register_wrap + class AvatarToolkit_OT_ApplyPoseAsRest(Operator): bl_idname = 'avatar_toolkit.apply_pose_as_rest' bl_label = t("Quick_Access.apply_pose_as_rest.label") @@ -117,7 +116,7 @@ class AvatarToolkit_OT_ApplyPoseAsRest(Operator): return {'CANCELLED'} return {'FINISHED'} -@register_wrap + class AvatarToolkit_OT_RemoveZeroWeightBones(Operator): bl_idname = "avatar_toolkit.remove_zero_weight_bones" bl_label = t("Tools.remove_zero_weight_bones.label") @@ -218,7 +217,7 @@ class AvatarToolkit_OT_RemoveZeroWeightBones(Operator): return {'FINISHED'} -@register_wrap + class AvatarToolkit_OT_MergeBonesToActive(Operator): bl_idname = "avatar_toolkit.merge_bones_to_active" bl_label = t("Tools.merge_bones_to_active.label") @@ -267,7 +266,7 @@ class AvatarToolkit_OT_MergeBonesToActive(Operator): bpy.ops.object.mode_set(mode=prev_mode) return {'FINISHED'} -@register_wrap + class AvatarToolkit_OT_MergeBonesToParents(Operator): bl_idname = "avatar_toolkit.merge_bones_to_parents" bl_label = t("Tools.merge_bones_to_parents.label") @@ -342,7 +341,7 @@ class AvatarToolkit_OT_MergeBonesToParents(Operator): bpy.ops.object.mode_set(mode=prev_mode) return {'FINISHED'} -@register_wrap + class AvatarToolkit_OT_MergeArmatures(Operator): bl_idname = "avatar_toolkit.merge_armatures" bl_label = t("MergeArmature.merge_armatures.label") diff --git a/functions/atlas_materials.py b/functions/atlas_materials.py index 4b925c1..48f5c40 100644 --- a/functions/atlas_materials.py +++ b/functions/atlas_materials.py @@ -5,10 +5,9 @@ import bpy import os from typing import List, Tuple, Optional from bpy.types import Material, Operator, Context, Object, Image, Mesh, MeshUVLoopLayer, Float2AttributeValue, ShaderNodeTexImage, ShaderNodeBsdfPrincipled, ShaderNodeNormalMap -from ..core.register import register_wrap from ..core.common import SceneMatClass, MaterialListBool from ..core.packer.rectangle_packer import MaterialImageList, BinPacker -from ..functions.translations import t +from ..core.translations import t class MaterialImageList: def __init__(self): @@ -134,7 +133,7 @@ def prep_images_in_scene(context: Context) -> list[MaterialImageList]: -@register_wrap + class AvatarToolKit_OT_AtlasMaterials(Operator): bl_idname = "avatar_toolkit.atlas_materials" diff --git a/functions/combine_materials.py b/functions/combine_materials.py index 48ec63f..8e0bcaf 100644 --- a/functions/combine_materials.py +++ b/functions/combine_materials.py @@ -3,8 +3,7 @@ import re from typing import List, Tuple, Optional, Set, Dict from bpy.types import Material, Operator, Context, Object, NodeTree from ..core.common import clean_material_names, get_selected_armature, is_valid_armature, get_all_meshes, init_progress, update_progress, finish_progress -from ..core.register import register_wrap -from ..functions.translations import t +from ..core.translations import t def textures_match(tex1: bpy.types.ImageTexture, tex2: bpy.types.ImageTexture) -> bool: return tex1.image == tex2.image and tex1.extension == tex2.extension @@ -52,7 +51,7 @@ def get_base_name(name: str) -> str: mat_match = re.match(r"^(.*)\.\d{3}$", name) return mat_match.group(1) if mat_match else name -@register_wrap + class AvatarToolKit_OT_CombineMaterials(Operator): bl_idname = "avatar_toolkit.combine_materials" bl_label = t("Optimization.combine_materials.label") diff --git a/functions/digitigrade_legs.py b/functions/digitigrade_legs.py index 3b5dc56..7553e01 100644 --- a/functions/digitigrade_legs.py +++ b/functions/digitigrade_legs.py @@ -1,11 +1,10 @@ import bpy from ..core import common -from ..core import register_wrap -from .translations import t +from ..core.translations import t import re -@register_wrap + class AvatarToolKit_OT_CreateDigitigradeLegs(bpy.types.Operator): bl_idname = "avatar_toolkit.create_digitigrade_legs" bl_label = t('Tools.create_digitigrade_legs.label') diff --git a/functions/import_anything.py b/functions/import_anything.py index 0e0fd49..86a5c3a 100644 --- a/functions/import_anything.py +++ b/functions/import_anything.py @@ -1,16 +1,15 @@ import bpy from bpy.types import Operator from bpy_extras.io_utils import ImportHelper -from ..core.register import register_wrap -from ..core.importer import imports, import_types +from ..core.importers.importer import imports, import_types from ..core.common import remove_default_objects -from ..functions.translations import t +from ..core.translations import t import pathlib import os VRM_IMPORTER_URL = "https://github.com/saturday06/VRM_Addon_for_Blender" -@register_wrap + class AvatarToolKit_OT_ImportAnyModel(Operator, ImportHelper): bl_idname = 'avatar_toolkit.import_any_model' bl_label = t('Tools.import_any_model.label') @@ -67,7 +66,7 @@ class AvatarToolKit_OT_ImportAnyModel(Operator, ImportHelper): self.report({'INFO'}, t('Quick_Access.import_success')) return {'FINISHED'} -@register_wrap + class VRMImporterPopup(Operator): bl_idname = "wm.vrm_importer_popup" bl_label = "VRM Importer Not Installed" @@ -87,7 +86,7 @@ class VRMImporterPopup(Operator): #TODO: This needs to be done with our own MMD importer. """ #stolen from cats. Oh wait I made this code riiiiiiight - @989onan -@register_wrap + class ImportMMDAnimation(bpy.types.Operator, ImportHelper): bl_idname = 'avatar_toolkit.import_mmd_animation' bl_label = t('Importer.mmd_anim_importer.label') diff --git a/functions/mesh_tools.py b/functions/mesh_tools.py index e814932..d3c917f 100644 --- a/functions/mesh_tools.py +++ b/functions/mesh_tools.py @@ -3,10 +3,9 @@ import bpy from typing import List, Optional, Set from bpy.types import Operator, Context, Object from ..core.common import fix_uv_coordinates, get_selected_armature, get_all_meshes, is_valid_armature, apply_shapekey_to_basis, has_shapekeys, select_current_armature, init_progress, update_progress, finish_progress -from ..functions.translations import t -from ..core.register import register_wrap +from ..core.translations import t + -@register_wrap class AvatarToolkit_OT_RemoveUnusedShapekeys(bpy.types.Operator): tolerance: bpy.props.FloatProperty(name=t("Tools.remove_unused_shapekeys.tolerance.label"), default=0.001, description=t("Tools.remove_unused_shapekeys.tolerance.desc")) bl_idname = "avatar_toolkit.remove_unused_shapekeys" @@ -56,7 +55,7 @@ class AvatarToolkit_OT_RemoveUnusedShapekeys(bpy.types.Operator): continue ob.shape_key_remove(ob.data.shape_keys.key_blocks[kb_name]) -@register_wrap + class AvatarToolkit_OT_ApplyShapeKey(bpy.types.Operator): bl_idname = "avatar_toolkit.apply_shape_key" bl_label = t("Tools.apply_shape_key.label") @@ -79,7 +78,7 @@ class AvatarToolkit_OT_ApplyShapeKey(bpy.types.Operator): self.report({'ERROR'}, t("Tools.apply_shape_key.error")) return {'FINISHED'} -@register_wrap + class AvatarToolKit_OT_JoinAllMeshes(Operator): bl_idname = "avatar_toolkit.join_all_meshes" bl_label = t("Optimization.join_all_meshes.label") @@ -150,7 +149,7 @@ class AvatarToolKit_OT_JoinAllMeshes(Operator): finish_progress(context) -@register_wrap + class AvatarToolKit_OT_JoinSelectedMeshes(Operator): bl_idname = "avatar_toolkit.join_selected_meshes" bl_label = t("Optimization.join_selected_meshes.label") diff --git a/functions/mmd_functions.py b/functions/mmd_functions.py index daaca13..76ba124 100644 --- a/functions/mmd_functions.py +++ b/functions/mmd_functions.py @@ -2,13 +2,12 @@ import bpy import numpy as np import re from bpy.types import Operator, Context, Material, ShaderNodeTexImage, ShaderNodeGroup, Object -from ..core.register import register_wrap -from ..functions.translations import t +from ..core.translations import t from ..core.common import get_selected_armature, is_valid_armature, get_all_meshes, init_progress, update_progress, finish_progress from ..functions.additional_tools import AvatarToolKit_OT_ConnectBones, AvatarToolKit_OT_DeleteBoneConstraints from ..functions.armature_modifying import AvatarToolkit_OT_RemoveZeroWeightBones, AvatarToolkit_OT_MergeBonesToParents -@register_wrap + class AvatarToolKit_OT_CleanupMesh(Operator): bl_idname = "avatar_toolkit.cleanup_mesh" bl_label = t("MMDOptions.cleanup_mesh.label") @@ -61,7 +60,7 @@ class AvatarToolKit_OT_CleanupMesh(Operator): if key.name != 'Basis' and all(abs(key.data[i].co[j] - obj.data.shape_keys.reference_key.data[i].co[j]) < 0.0001 for i in range(len(key.data)) for j in range(3)): obj.shape_key_remove(key) -@register_wrap + class AvatarToolKit_OT_OptimizeWeights(Operator): bl_idname = "avatar_toolkit.optimize_weights" bl_label = t("MMDOptions.optimize_weights.label") @@ -108,7 +107,7 @@ class AvatarToolKit_OT_OptimizeWeights(Operator): for g in sorted_groups[self.max_weights:]: obj.vertex_groups[g.group].remove([v.index]) -@register_wrap + class AvatarToolKit_OT_OptimizeArmature(Operator): bl_idname = "avatar_toolkit.optimize_armature" bl_label = t("MMDOptions.optimize_armature.label") @@ -339,7 +338,7 @@ def fix_vrm_shader(material: Material): material.node_tree.links = [link for link in material.node_tree.links if not (link.from_node == node or link.to_node == node)] -@register_wrap + class AvatarToolKit_OT_ConvertMaterials(Operator): bl_idname = "avatar_toolkit.convert_materials" bl_label = t("MMDOptions.convert_materials.label") diff --git a/functions/remove_doubles_safely.py b/functions/remove_doubles_safely.py index ea635f7..3e3ed42 100644 --- a/functions/remove_doubles_safely.py +++ b/functions/remove_doubles_safely.py @@ -1,9 +1,8 @@ import bpy from typing import List, TypedDict, Any from bpy.types import Operator, Context, Object -from ..core.register import register_wrap from ..core.common import get_selected_armature, is_valid_armature, select_current_armature, get_all_meshes -from ..functions.translations import t +from ..core.translations import t class meshEntry(TypedDict): mesh: Object @@ -11,7 +10,7 @@ class meshEntry(TypedDict): vertices: int cur_vertex_pass: int -@register_wrap + class AvatarToolKit_OT_RemoveDoublesSafelyAdvanced(Operator): bl_idname = "avatar_toolkit.remove_doubles_safely_advanced" bl_label = t("Optimization.remove_doubles_safely_advanced.label") @@ -39,7 +38,7 @@ class AvatarToolKit_OT_RemoveDoublesSafelyAdvanced(Operator): return {'RUNNING_MODAL'} -@register_wrap + class AvatarToolKit_OT_RemoveDoublesSafely(Operator): bl_idname = "avatar_toolkit.remove_doubles_safely" bl_label = t("Optimization.remove_doubles_safely.label") diff --git a/functions/resonite_functions.py b/functions/resonite_functions.py index 1f32e2e..8033e7f 100644 --- a/functions/resonite_functions.py +++ b/functions/resonite_functions.py @@ -1,13 +1,12 @@ import bpy -from ..core.register import register_wrap from typing import List, Optional import re from bpy.types import Operator, Context, Object from ..core.dictionaries import bone_names from ..core.common import get_selected_armature, simplify_bonename, is_valid_armature -from ..functions.translations import t +from ..core.translations import t + -@register_wrap class AvatarToolKit_OT_ConvertToResonite(Operator): bl_idname = 'avatar_toolkit.convert_to_resonite' bl_label = t('Tools.convert_to_resonite.label') diff --git a/functions/rigify_functions.py b/functions/rigify_functions.py index 8ca932f..1a23b24 100644 --- a/functions/rigify_functions.py +++ b/functions/rigify_functions.py @@ -1,18 +1,9 @@ # This code is heavily based on the Rigify-Move-DEF by NyankoNyan (https://github.com/NyankoNyan/Rigify-Move-DEF), which is licensed under the MIT License. We just heavily improve the code and add some new features. - import bpy -from ..core.register import register_wrap from ..core.common import get_selected_armature, is_valid_armature -from ..functions.translations import t +from ..core.translations import t from bpy.types import Operator, Context -import bpy -from ..core.register import register_wrap -from ..core.common import get_selected_armature, is_valid_armature -from ..functions.translations import t -from bpy.types import Operator, Context - -@register_wrap class AvatarToolKit_OT_ConvertRigifyToUnity(Operator): bl_idname = "avatar_toolkit.convert_rigify_to_unity" bl_label = t("Tools.convert_rigify_to_unity.label") diff --git a/functions/uv_tools.py b/functions/uv_tools.py index 504fc84..9273d2f 100644 --- a/functions/uv_tools.py +++ b/functions/uv_tools.py @@ -4,15 +4,14 @@ from bpy.types import Operator, Object, Context, Mesh, MeshUVLoopLayer import bmesh import numpy as np import math -from ..functions.translations import t -from ..core.register import register_wrap +from ..core.translations import t class GenerateLoopTreeResult(TypedDict): tree: dict[str, set[str]] selected_loops: dict[str,list[int]] selected_verts: dict[str,int] -@register_wrap + class AvatarToolkit_OT_AlignUVEdgesToTarget(Operator): bl_idname = "avatar_toolkit.align_uv_edges_to_target" bl_label = t("avatar_toolkit.align_uv_edges_to_target.label") diff --git a/functions/viseme.py b/functions/viseme.py index 7f20e73..6793997 100644 --- a/functions/viseme.py +++ b/functions/viseme.py @@ -1,11 +1,10 @@ import bpy from ..core import common -from ..core.register import register_wrap -from ..functions.translations import t +from ..core.translations import t from typing import List, Tuple from ..core.common import get_selected_armature, is_valid_armature, get_all_meshes, init_progress, update_progress, finish_progress -@register_wrap + class AvatarToolKit_OT_AutoVisemeButton(bpy.types.Operator): bl_idname = 'avatar_toolkit.create_visemes' bl_label = t('AutoVisemeButton.label') diff --git a/ui/__init__.py b/ui/__init__.py index 7db3379..e69de29 100644 --- a/ui/__init__.py +++ b/ui/__init__.py @@ -1,15 +0,0 @@ -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')]: - print("reloading " +module_name) - exec("importlib.reload("+module_name+")") diff --git a/ui/atlas_materials.py b/ui/atlas_materials.py index 7d5a666..7ab0206 100644 --- a/ui/atlas_materials.py +++ b/ui/atlas_materials.py @@ -1,57 +1,51 @@ -from bpy.types import UIList, Panel, UILayout, Object, Context,Material, Operator +from bpy.types import UIList, Panel, UILayout, Object, Context, Material, Operator import bpy from math import sqrt -from ..core.register import register_wrap -from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from ..core.common import SceneMatClass, MaterialListBool, get_selected_armature from ..functions.atlas_materials import AvatarToolKit_OT_AtlasMaterials -from ..functions.translations import t +from ..core.translations import t -@register_wrap class AvatarToolKit_OT_SelectAllMaterials(Operator): bl_idname = 'avatar_toolkit.select_all_materials' bl_label = "Select All" bl_description = "Select all materials for atlas" def execute(self, context): - for item in context.scene.materials: - item.mat.include_in_atlas = True + for item in context.scene.avatar_toolkit.materials: + item.mat.avatar_toolkit.include_in_atlas = True return {'FINISHED'} -@register_wrap class AvatarToolKit_OT_SelectNoneMaterials(Operator): bl_idname = 'avatar_toolkit.select_none_materials' bl_label = "Select None" bl_description = "Deselect all materials" def execute(self, context): - for item in context.scene.materials: - item.mat.include_in_atlas = False + for item in context.scene.avatar_toolkit.materials: + item.mat.avatar_toolkit.include_in_atlas = False return {'FINISHED'} -@register_wrap class AvatarToolKit_OT_ExpandAllMaterials(Operator): bl_idname = 'avatar_toolkit.expand_all_materials' bl_label = "Expand All" bl_description = "Expand all material settings" def execute(self, context): - for item in context.scene.materials: - item.mat.material_expanded = True + for item in context.scene.avatar_toolkit.materials: + item.mat.avatar_toolkit.material_expanded = True return {'FINISHED'} -@register_wrap class AvatarToolKit_OT_CollapseAllMaterials(Operator): bl_idname = 'avatar_toolkit.collapse_all_materials' bl_label = "Collapse All" bl_description = "Collapse all material settings" def execute(self, context): - for item in context.scene.materials: - item.mat.material_expanded = False + for item in context.scene.avatar_toolkit.materials: + item.mat.avatar_toolkit.material_expanded = False return {'FINISHED'} -@register_wrap class AvatarToolKit_OT_ExpandSectionMaterials(Operator): bl_idname = 'avatar_toolkit.expand_section_materials' bl_label = "" @@ -62,23 +56,22 @@ class AvatarToolKit_OT_ExpandSectionMaterials(Operator): return True def execute(self, context: Context) -> set: - if not context.scene.texture_atlas_Has_Mat_List_Shown: - context.scene.materials.clear() + if not context.scene.avatar_toolkit.texture_atlas_Has_Mat_List_Shown: + context.scene.avatar_toolkit.materials.clear() newlist: list[Material] = [] - for obj in bpy.context.scene.objects: + for obj in context.scene.objects: if len(obj.material_slots)>0: for mat_slot in obj.material_slots: if mat_slot.material: if mat_slot.material not in newlist: newlist.append(mat_slot.material) - newitem: SceneMatClass = context.scene.materials.add() + newitem: SceneMatClass = context.scene.avatar_toolkit.materials.add() newitem.mat = mat_slot.material MaterialListBool.old_list[context.scene.name] = newlist else: - context.scene.texture_atlas_Has_Mat_List_Shown = False + context.scene.avatar_toolkit.texture_atlas_Has_Mat_List_Shown = False return {'FINISHED'} -@register_wrap class AvatarToolKit_UL_MaterialTextureAtlasProperties(UIList): bl_label = t("TextureAtlas.material_list_label") bl_idname = "Material_UL_avatar_toolkit_texture_atlas_mat_list_mat" @@ -93,38 +86,35 @@ class AvatarToolKit_UL_MaterialTextureAtlasProperties(UIList): row.operator("avatar_toolkit.select_none_materials", text="", icon='CHECKBOX_DEHLT') row.operator("avatar_toolkit.expand_all_materials", text="", icon='DISCLOSURE_TRI_DOWN') row.operator("avatar_toolkit.collapse_all_materials", text="", icon='DISCLOSURE_TRI_RIGHT') - row.prop(context.scene, "material_search_filter", text="", icon='VIEWZOOM') + row.prop(context.scene.avatar_toolkit, "material_search_filter", text="", icon='VIEWZOOM') box = layout.box() row = box.row() row.label(text=f"Estimated Atlas Size: {self.calculate_atlas_size(context)}px") def draw_item(self, context: Context, layout: UILayout, data: Object, item: SceneMatClass, icon, active_data, active_propname, index): - if context.scene.texture_atlas_Has_Mat_List_Shown: - if context.scene.material_search_filter and context.scene.material_search_filter.lower() not in item.mat.name.lower(): + if context.scene.avatar_toolkit.texture_atlas_Has_Mat_List_Shown: + if context.scene.avatar_toolkit.material_search_filter and context.scene.avatar_toolkit.material_search_filter.lower() not in item.mat.name.lower(): return row = layout.row() - # Add a clear checkbox for material selection - row.prop(item.mat, "include_in_atlas", text="", icon='CHECKBOX_HLT' if item.mat.include_in_atlas else 'CHECKBOX_DEHLT') + row.prop(item.mat.avatar_toolkit, "include_in_atlas", text="", icon='CHECKBOX_HLT' if item.mat.avatar_toolkit.include_in_atlas else 'CHECKBOX_DEHLT') - # Material name and expansion toggle - row.prop(item.mat, "material_expanded", + row.prop(item.mat.avatar_toolkit, "material_expanded", text=item.mat.name, - icon='DOWNARROW_HLT' if item.mat.material_expanded else 'RIGHTARROW', + icon='DOWNARROW_HLT' if item.mat.avatar_toolkit.material_expanded else 'RIGHTARROW', emboss=False) - # Show texture settings if expanded - if item.mat.material_expanded and item.mat.include_in_atlas: + if item.mat.avatar_toolkit.material_expanded and item.mat.avatar_toolkit.include_in_atlas: box = layout.box() col = box.column(align=True) - self.draw_texture_row(col, item.mat, "texture_atlas_albedo", "IMAGE_RGB") - self.draw_texture_row(col, item.mat, "texture_atlas_normal", "NORMALS_FACE") - self.draw_texture_row(col, item.mat, "texture_atlas_emission", "LIGHT") - self.draw_texture_row(col, item.mat, "texture_atlas_ambient_occlusion", "SHADING_SOLID") - self.draw_texture_row(col, item.mat, "texture_atlas_height", "IMAGE_ZDEPTH") - self.draw_texture_row(col, item.mat, "texture_atlas_roughness", "MATERIAL") + self.draw_texture_row(col, item.mat.avatar_toolkit, "texture_atlas_albedo", "IMAGE_RGB") + self.draw_texture_row(col, item.mat.avatar_toolkit, "texture_atlas_normal", "NORMALS_FACE") + self.draw_texture_row(col, item.mat.avatar_toolkit, "texture_atlas_emission", "LIGHT") + self.draw_texture_row(col, item.mat.avatar_toolkit, "texture_atlas_ambient_occlusion", "SHADING_SOLID") + self.draw_texture_row(col, item.mat.avatar_toolkit, "texture_atlas_height", "IMAGE_ZDEPTH") + self.draw_texture_row(col, item.mat.avatar_toolkit, "texture_atlas_roughness", "MATERIAL") col.separator(factor=0.5) @@ -136,21 +126,15 @@ class AvatarToolKit_UL_MaterialTextureAtlasProperties(UIList): else: row.label(text="", icon='X') - def is_material_ready(self, material): - return bool(material.texture_atlas_albedo or - material.texture_atlas_normal or - material.texture_atlas_emission) - def calculate_atlas_size(self, context): total_size = 0 - for mat in context.scene.materials: - if mat.mat.include_in_atlas: - if mat.mat.texture_atlas_albedo: - img = bpy.data.images[mat.mat.texture_atlas_albedo] + for mat in context.scene.avatar_toolkit.materials: + if mat.mat.avatar_toolkit.include_in_atlas: + if mat.mat.avatar_toolkit.texture_atlas_albedo: + img = bpy.data.images[mat.mat.avatar_toolkit.texture_atlas_albedo] total_size += img.size[0] * img.size[1] return f"{int(sqrt(total_size))}x{int(sqrt(total_size))}" -@register_wrap class AvatarToolKit_PT_TextureAtlasPanel(Panel): bl_label = t("TextureAtlas.label") bl_idname = "OBJECT_PT_avatar_toolkit_texture_atlas" @@ -170,18 +154,18 @@ class AvatarToolKit_PT_TextureAtlasPanel(Panel): box = layout.box() row = box.row() - direction_icon = 'RIGHTARROW' if not context.scene.texture_atlas_Has_Mat_List_Shown else 'DOWNARROW_HLT' + direction_icon = 'RIGHTARROW' if not context.scene.avatar_toolkit.texture_atlas_Has_Mat_List_Shown else 'DOWNARROW_HLT' row.operator(AvatarToolKit_OT_ExpandSectionMaterials.bl_idname, - text=(t("TextureAtlas.reload_list") if not context.scene.texture_atlas_Has_Mat_List_Shown else t("TextureAtlas.loaded_list")), + text=(t("TextureAtlas.reload_list") if not context.scene.avatar_toolkit.texture_atlas_Has_Mat_List_Shown else t("TextureAtlas.loaded_list")), icon=direction_icon) - if context.scene.texture_atlas_Has_Mat_List_Shown: + if context.scene.avatar_toolkit.texture_atlas_Has_Mat_List_Shown: row = box.row() row.template_list(AvatarToolKit_UL_MaterialTextureAtlasProperties.bl_idname, 'material_list', - context.scene, + context.scene.avatar_toolkit, 'materials', - context.scene, + context.scene.avatar_toolkit, 'texture_atlas_material_index', rows=12, type='DEFAULT') @@ -195,3 +179,4 @@ class AvatarToolKit_PT_TextureAtlasPanel(Panel): icon='NODE_TEXTURE') else: layout.label(text=t("Tools.select_armature"), icon='ERROR') + diff --git a/ui/credits_support.py b/ui/credits_support.py index 90c9f61..e650d9a 100644 --- a/ui/credits_support.py +++ b/ui/credits_support.py @@ -1,10 +1,8 @@ import bpy -from ..core.register import register_wrap -from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME -from ..functions.translations import t +from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from ..core.translations import t from ..core.common import open_web_after_delay_multi_threaded -@register_wrap class AvatarToolkit_PT_CreditsSupport(bpy.types.Panel): bl_label = t("CreditsSupport.label") bl_idname = "OBJECT_PT_avatar_toolkit_credits_support" diff --git a/ui/panel.py b/ui/main_panel.py similarity index 84% rename from ui/panel.py rename to ui/main_panel.py index 66e9496..6463bed 100644 --- a/ui/panel.py +++ b/ui/main_panel.py @@ -1,6 +1,7 @@ import bpy -from ..core.register import register_wrap -from ..functions.translations import t +from ..core.translations import t + +CATEGORY_NAME = "Avatar Toolkit" def draw_title(self: bpy.types.Panel): layout = self.layout @@ -8,9 +9,6 @@ def draw_title(self: bpy.types.Panel): layout.label(text=t("AvatarToolkit.desc2")) layout.label(text=t("AvatarToolkit.desc3")) -CATEGORY_NAME = "Avatar Toolkit" - -@register_wrap class AvatarToolKit_PT_AvatarToolkitPanel(bpy.types.Panel): bl_label = t("AvatarToolkit.label") bl_idname = "OBJECT_PT_avatar_toolkit" @@ -21,4 +19,3 @@ class AvatarToolKit_PT_AvatarToolkitPanel(bpy.types.Panel): def draw(self: bpy.types.Panel, context: bpy.types.Context): draw_title(self) - diff --git a/ui/merge_armatures.py b/ui/merge_armatures.py index 8f00458..494c5a9 100644 --- a/ui/merge_armatures.py +++ b/ui/merge_armatures.py @@ -1,13 +1,10 @@ - import bpy -from ..core.register import register_wrap -from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from bpy.types import Panel, Context from ..core.common import get_selected_armature -from ..functions.translations import t +from ..core.translations import t from ..functions.armature_modifying import AvatarToolkit_OT_MergeArmatures -@register_wrap class AvatarToolkit_PT_MergeArmaturesPanel(Panel): bl_label = t("MergeArmatures.label") bl_idname = "OBJECT_PT_avatar_toolkit_merge_armatures" @@ -29,14 +26,14 @@ class AvatarToolkit_PT_MergeArmaturesPanel(Panel): box = layout.box() col = box.column(align=True) - col.prop(context.scene, property="selected_armature", text=t("MergeArmatures.target_armature.label"), icon="ARMATURE_DATA") - col.prop(context.scene, property="merge_armature_source", icon="OUTLINER_OB_ARMATURE") + col.prop(context.scene.avatar_toolkit, "selected_armature", text=t("MergeArmatures.target_armature.label"), icon="ARMATURE_DATA") + col.prop(context.scene.avatar_toolkit, "merge_armature_source", icon="OUTLINER_OB_ARMATURE") layout.separator(factor=0.5) col = layout.column(align=True) - col.prop(context.scene, property="merge_armature_align_bones", icon="BONE_DATA") - col.prop(context.scene, property="merge_armature_apply_transforms", icon="OBJECT_ORIGIN") + col.prop(context.scene.avatar_toolkit, "merge_armature_align_bones", icon="BONE_DATA") + col.prop(context.scene.avatar_toolkit, "merge_armature_apply_transforms", icon="OBJECT_ORIGIN") layout.separator(factor=1.0) diff --git a/ui/mmd_options.py b/ui/mmd_options.py index fe2c04e..55b1839 100644 --- a/ui/mmd_options.py +++ b/ui/mmd_options.py @@ -1,13 +1,11 @@ import bpy -from ..core.register import register_wrap -from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME -from ..functions.translations import t +from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from ..core.translations import t from ..functions.mmd_functions import * from ..functions.mesh_tools import AvatarToolKit_OT_JoinAllMeshes from ..functions.combine_materials import AvatarToolKit_OT_CombineMaterials from ..functions.additional_tools import AvatarToolKit_OT_ApplyTransforms -@register_wrap class AvatarToolkit_PT_MMDOptionsPanel(bpy.types.Panel): bl_label = t("MMDOptions.label") bl_idname = "OBJECT_PT_avatar_toolkit_mmd_options" @@ -48,4 +46,3 @@ class AvatarToolkit_PT_MMDOptionsPanel(bpy.types.Panel): row = layout.row() row.scale_y = 1.2 row.operator(AvatarToolKit_OT_ConvertMaterials.bl_idname, icon='SHADING_TEXTURE') - diff --git a/ui/optimization.py b/ui/optimization.py index a84abdb..0c2877e 100644 --- a/ui/optimization.py +++ b/ui/optimization.py @@ -1,13 +1,11 @@ import bpy -from ..core.register import register_wrap -from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME -from ..functions.translations import t +from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from ..core.translations import t from ..functions.remove_doubles_safely import AvatarToolKit_OT_RemoveDoublesSafely, AvatarToolKit_OT_RemoveDoublesSafelyAdvanced from ..core.common import get_selected_armature from ..functions.mesh_tools import AvatarToolKit_OT_JoinAllMeshes, AvatarToolKit_OT_JoinSelectedMeshes from ..functions.combine_materials import AvatarToolKit_OT_CombineMaterials -@register_wrap class AvatarToolkit_PT_OptimizationPanel(bpy.types.Panel): bl_label = t("Optimization.label") bl_idname = "OBJECT_PT_avatar_toolkit_optimization" @@ -17,7 +15,7 @@ class AvatarToolkit_PT_OptimizationPanel(bpy.types.Panel): bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname bl_order = 2 - def draw(self: bpy.types.Panel, context: bpy.types.Context): + def draw(self, context: bpy.types.Context): layout = self.layout armature = get_selected_armature(context) @@ -46,5 +44,3 @@ class AvatarToolkit_PT_OptimizationPanel(bpy.types.Panel): else: layout.label(text=t("Optimization.select_armature"), icon='ERROR') - - diff --git a/ui/quick_access.py b/ui/quick_access.py index 26a33ef..cd87e32 100644 --- a/ui/quick_access.py +++ b/ui/quick_access.py @@ -1,17 +1,15 @@ import bpy -from ..core.register import register_wrap -from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME -from ..core.export_resonite import AvatarToolKit_OT_ExportResonite +from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from ..core.exporters.export_resonite import AvatarToolKit_OT_ExportResonite from bpy.types import Context, Mesh, Panel, Operator -from ..functions.translations import t - -from ..core.import_pmx import import_pmx -from ..core.import_pmd import import_pmd +from ..core.translations import t +from ..core.common import get_selected_armature from ..functions.import_anything import AvatarToolKit_OT_ImportAnyModel -from ..functions.armature_modifying import AvatarToolkit_OT_StartPoseMode, AvatarToolkit_OT_StopPoseMode, AvatarToolkit_OT_ApplyPoseAsRest, AvatarToolkit_OT_ApplyPoseAsShapekey -from ..core.common import get_selected_armature, set_selected_armature, get_all_meshes +from ..functions.armature_modifying import (AvatarToolkit_OT_StartPoseMode, + AvatarToolkit_OT_StopPoseMode, + AvatarToolkit_OT_ApplyPoseAsRest, + AvatarToolkit_OT_ApplyPoseAsShapekey) -@register_wrap class AvatarToolkitQuickAccessPanel(Panel): bl_label = t("Quick_Access.label") bl_idname = "OBJECT_PT_avatar_toolkit_quick_access" @@ -28,7 +26,7 @@ class AvatarToolkitQuickAccessPanel(Panel): layout.separator(factor=1.0) layout.label(text=t("Quick_Access.select_armature"), icon='ARMATURE_DATA') - layout.prop(context.scene, "selected_armature", text="") + layout.prop(context.scene.avatar_toolkit, "selected_armature", text="") layout.separator(factor=1.0) @@ -58,8 +56,6 @@ class AvatarToolkitQuickAccessPanel(Panel): row.scale_y = 1.2 row.operator(AvatarToolkit_OT_StartPoseMode.bl_idname, text=t("Quick_Access.start_pose_mode.label"), icon='POSE_HLT') - -@register_wrap class AVATAR_TOOLKIT_OT_ExportMenu(bpy.types.Operator): bl_idname = "avatar_toolkit.export_menu" bl_label = t("Quick_Access.export_menu.label") @@ -82,7 +78,6 @@ class AVATAR_TOOLKIT_OT_ExportMenu(bpy.types.Operator): layout.operator(AvatarToolKit_OT_ExportResonite.bl_idname, text=t("Quick_Access.select_export_resonite.label"), icon='SCENE_DATA') layout.operator(AVATAR_TOOLKIT_OT_ExportFbx.bl_idname, text=t("Quick_Access.export_fbx.label"), icon='OBJECT_DATA') -@register_wrap class AVATAR_TOOLKIT_OT_ExportFbx(bpy.types.Operator): bl_idname = 'avatar_toolkit.export_fbx' bl_label = t("Quick_Access.export_fbx.label") diff --git a/ui/settings.py b/ui/settings.py index 997d933..fbb6c6c 100644 --- a/ui/settings.py +++ b/ui/settings.py @@ -1,9 +1,7 @@ import bpy -from ..core.register import register_wrap -from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME -from ..functions.translations import t +from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from ..core.translations import t -@register_wrap class AvatarToolkitSettingsPanel(bpy.types.Panel): bl_label = t("Settings.label") bl_idname = "OBJECT_PT_avatar_toolkit_settings" @@ -17,9 +15,8 @@ class AvatarToolkitSettingsPanel(bpy.types.Panel): layout = self.layout layout.label(text=t("Settings.language.label")) - layout.prop(context.scene, "avatar_toolkit_language", text="", icon='WORLD') + layout.prop(context.scene.avatar_toolkit, "language", text="", icon='WORLD') -@register_wrap class AVATAR_TOOLKIT_OT_translation_restart_popup(bpy.types.Operator): bl_idname = "avatar_toolkit.translation_restart_popup" bl_label = t("Settings.translation_restart_popup.label") @@ -27,9 +24,9 @@ class AVATAR_TOOLKIT_OT_translation_restart_popup(bpy.types.Operator): bl_options = {'INTERNAL'} def execute(self, context): - if context.scene.avatar_toolkit_language_changed: + if context.scene.avatar_toolkit.language_changed: bpy.ops.script.reload() - context.scene.avatar_toolkit_language_changed = False + context.scene.avatar_toolkit.language_changed = False return {'FINISHED'} def invoke(self, context, event): @@ -39,4 +36,3 @@ class AVATAR_TOOLKIT_OT_translation_restart_popup(bpy.types.Operator): layout = self.layout layout.label(text=t("Settings.translation_restart_popup.message1"), icon='INFO') layout.label(text=t("Settings.translation_restart_popup.message2"), icon='FILE_REFRESH') - diff --git a/ui/tools.py b/ui/tools.py index df218c7..d07a380 100644 --- a/ui/tools.py +++ b/ui/tools.py @@ -1,17 +1,20 @@ import bpy -from ..core.register import register_wrap -from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from bpy.types import Context from ..functions.digitigrade_legs import AvatarToolKit_OT_CreateDigitigradeLegs from ..functions.resonite_functions import AvatarToolKit_OT_ConvertToResonite -from ..functions.translations import t +from ..core.translations import t from ..core.common import get_selected_armature from ..functions.mesh_tools import AvatarToolkit_OT_RemoveUnusedShapekeys -from ..functions.additional_tools import AvatarToolKit_OT_ApplyTransforms, AvatarToolKit_OT_ConnectBones, AvatarToolKit_OT_DeleteBoneConstraints, AvatarToolKit_OT_SeparateByMaterials, AvatarToolKit_OT_SeparateByLooseParts -from ..functions.armature_modifying import AvatarToolkit_OT_RemoveZeroWeightBones, AvatarToolkit_OT_MergeBonesToActive, AvatarToolkit_OT_MergeBonesToParents -from ..functions.rigify_functions import AvatarToolKit_OT_ConvertRigifyToUnity +from ..functions.additional_tools import (AvatarToolKit_OT_ApplyTransforms, + AvatarToolKit_OT_ConnectBones, + AvatarToolKit_OT_DeleteBoneConstraints, + AvatarToolKit_OT_SeparateByMaterials, + AvatarToolKit_OT_SeparateByLooseParts) +from ..functions.armature_modifying import (AvatarToolkit_OT_RemoveZeroWeightBones, + AvatarToolkit_OT_MergeBonesToActive, + AvatarToolkit_OT_MergeBonesToParents) -@register_wrap class AvatarToolkit_PT_ToolsPanel(bpy.types.Panel): bl_label = t("Tools.label") bl_idname = "OBJECT_PT_avatar_toolkit_tools" @@ -33,10 +36,6 @@ class AvatarToolkit_PT_ToolsPanel(bpy.types.Panel): row.scale_y = 1.5 row.operator(AvatarToolKit_OT_ConvertToResonite.bl_idname, text=t("Tools.convert_to_resonite.label"), icon='SCENE_DATA') - row = layout.row(align=True) - row.scale_y = 1.5 - row.operator(AvatarToolKit_OT_ConvertRigifyToUnity.bl_idname, text=t("Tools.convert_rigify_to_unity.label"), icon='ARMATURE_DATA') - layout.separator(factor=1.0) layout.label(text=t("Tools.separate_by.label"), icon='MESH_DATA') @@ -62,7 +61,7 @@ class AvatarToolkit_PT_ToolsPanel(bpy.types.Panel): row.operator(AvatarToolKit_OT_DeleteBoneConstraints.bl_idname, text=t("Tools.delete_bone_constraints.label"), icon='CONSTRAINT_BONE') row = layout.row() - row.prop(context.scene, "merge_twist_bones") + row.prop(context.scene.avatar_toolkit, "merge_twist_bones") layout.separator(factor=1.0) @@ -74,3 +73,4 @@ class AvatarToolkit_PT_ToolsPanel(bpy.types.Panel): layout.separator(factor=1.0) else: layout.label(text=t("Tools.select_armature"), icon='ERROR') + diff --git a/ui/uv_panel.py b/ui/uv_panel.py index 8221e31..50e4c7b 100644 --- a/ui/uv_panel.py +++ b/ui/uv_panel.py @@ -1,9 +1,7 @@ import bpy -from ..core.register import register_wrap -from ..functions.translations import t -from .panel import draw_title +from ..core.translations import t +from .main_panel import draw_title -@register_wrap class UVTools_PT_MainPanel(bpy.types.Panel): bl_label = t("AvatarToolkit.label") bl_idname = "OBJECT_PT_avatar_toolkit_uv" @@ -13,5 +11,4 @@ class UVTools_PT_MainPanel(bpy.types.Panel): def draw(self: bpy.types.Panel, context: bpy.types.Context): layout = self.layout - - draw_title(self) \ No newline at end of file + draw_title(self) diff --git a/ui/uv_tools.py b/ui/uv_tools.py index e4b5244..d05489e 100644 --- a/ui/uv_tools.py +++ b/ui/uv_tools.py @@ -1,12 +1,9 @@ - import bpy -from ..core.register import register_wrap -from ..functions.translations import t +from ..core.translations import t from ..functions.uv_tools import AvatarToolkit_OT_AlignUVEdgesToTarget -from .panel import draw_title +from .main_panel import draw_title from .uv_panel import UVTools_PT_MainPanel -@register_wrap class UVTools_PT_Tools(bpy.types.Panel): bl_label = t("Tools.label") bl_idname = "OBJECT_PT_avatar_toolkit_uv_tools" @@ -18,6 +15,5 @@ class UVTools_PT_Tools(bpy.types.Panel): def draw(self, context: bpy.types.Context): layout = self.layout - row = layout.row(align=True) - row.operator(AvatarToolkit_OT_AlignUVEdgesToTarget.bl_idname, text=t("avatar_toolkit.align_uv_edges_to_target.label"), icon='GP_MULTIFRAME_EDITING') \ No newline at end of file + row.operator(AvatarToolkit_OT_AlignUVEdgesToTarget.bl_idname, text=t("avatar_toolkit.align_uv_edges_to_target.label"), icon='GP_MULTIFRAME_EDITING') diff --git a/ui/viseme.py b/ui/viseme.py index 8e0ef26..32e8e43 100644 --- a/ui/viseme.py +++ b/ui/viseme.py @@ -1,11 +1,9 @@ import bpy -from ..core.register import register_wrap -from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME +from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from ..functions.viseme import AvatarToolKit_OT_AutoVisemeButton -from ..functions.translations import t +from ..core.translations import t from ..core.common import get_selected_armature -@register_wrap class AvatarToolkitVisemePanel(bpy.types.Panel): bl_label = t("VisemePanel.label") bl_idname = "OBJECT_PT_avatar_toolkit_viseme" @@ -24,20 +22,20 @@ class AvatarToolkitVisemePanel(bpy.types.Panel): layout.separator(factor=0.5) - layout.prop(context.scene, "selected_mesh", text=t("VisemePanel.select_mesh"), icon='OUTLINER_OB_MESH') + layout.prop(context.scene.avatar_toolkit, "selected_mesh", text=t("VisemePanel.select_mesh"), icon='OUTLINER_OB_MESH') - mesh = bpy.data.objects.get(context.scene.selected_mesh) + mesh = bpy.data.objects.get(context.scene.avatar_toolkit.selected_mesh) if mesh and mesh.type == 'MESH': if mesh.data.shape_keys: box = layout.box() col = box.column(align=True) - col.prop_search(context.scene, "avatar_toolkit_mouth_a", mesh.data.shape_keys, "key_blocks", text=t('VisemePanel.mouth_a.label'), icon='SHAPEKEY_DATA') - col.prop_search(context.scene, "avatar_toolkit_mouth_o", mesh.data.shape_keys, "key_blocks", text=t('VisemePanel.mouth_o.label'), icon='SHAPEKEY_DATA') - col.prop_search(context.scene, "avatar_toolkit_mouth_ch", mesh.data.shape_keys, "key_blocks", text=t('VisemePanel.mouth_ch.label'), icon='SHAPEKEY_DATA') + col.prop_search(context.scene.avatar_toolkit, "mouth_a", mesh.data.shape_keys, "key_blocks", text=t('VisemePanel.mouth_a.label'), icon='SHAPEKEY_DATA') + col.prop_search(context.scene.avatar_toolkit, "mouth_o", mesh.data.shape_keys, "key_blocks", text=t('VisemePanel.mouth_o.label'), icon='SHAPEKEY_DATA') + col.prop_search(context.scene.avatar_toolkit, "mouth_ch", mesh.data.shape_keys, "key_blocks", text=t('VisemePanel.mouth_ch.label'), icon='SHAPEKEY_DATA') layout.separator(factor=0.5) - layout.prop(context.scene, 'avatar_toolkit_shape_intensity', text=t('VisemePanel.shape_intensity'), icon='FORCE_LENNARDJONES') + layout.prop(context.scene.avatar_toolkit, 'shape_intensity', text=t('VisemePanel.shape_intensity'), icon='FORCE_LENNARDJONES') layout.separator(factor=1.0) @@ -53,4 +51,3 @@ class AvatarToolkitVisemePanel(bpy.types.Panel): layout.separator(factor=1.0) layout.label(text=t('VisemePanel.info.selectMesh'), icon='HELP') -