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.
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"language": 0
|
||||
}
|
||||
+153
-142
@@ -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
|
||||
|
||||
@@ -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,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()
|
||||
+7
-8
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user