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:
Yusarina
2024-12-02 01:52:11 +00:00
parent ac6e98c27e
commit fe8f5f69d5
40 changed files with 581 additions and 580 deletions
-20
View File
@@ -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)
+172
View File
@@ -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
-1
View File
@@ -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")
-3
View File
@@ -1,3 +0,0 @@
{
"language": 0
}
+153 -142
View File
@@ -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
-105
View File
@@ -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
+97
View File
@@ -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
View File
@@ -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")