diff --git a/__init__.py b/__init__.py index ee17c15..dedf6b3 100644 --- a/__init__.py +++ b/__init__.py @@ -13,38 +13,23 @@ def show_version_error_popup(): bpy.context.window_manager.popup_menu(draw, title="Avatar Toolkit Version Error", icon='ERROR') def register(): - # Check Blender version first import bpy version = bpy.app.version if version[0] > 4 or (version[0] == 4 and version[1] >= 5): show_version_error_popup() return - - # Add wheel installation check - try: - import lz4 - except ImportError: - import sys - import os - import site - import pip - wheels_dir = os.path.join(os.path.dirname(__file__), "wheels") - for wheel in os.listdir(wheels_dir): - if wheel.endswith(".whl"): - pip.main(['install', os.path.join(wheels_dir, wheel)]) - site.addsitedir(site.getsitepackages()[0]) - - from .core import auto_load + print("Starting registration") - # Make sure to initialize logging first + # Import modules using relative imports + from . import core + from .core import auto_load from .core.logging_setup import configure_logging + + # Initialize logging configure_logging(False) - # Then initialize the addon auto_load.init() - - # Register classes in proper order auto_load.register() # Verify property registration diff --git a/core/auto_load.py b/core/auto_load.py index 730daf1..dc326e6 100644 --- a/core/auto_load.py +++ b/core/auto_load.py @@ -1,6 +1,5 @@ import os import bpy -import sys import typing import inspect import pkgutil @@ -24,7 +23,6 @@ def init() -> None: global modules global ordered_classes - # Configure logging first from .logging_setup import configure_logging configure_logging(False) @@ -32,14 +30,24 @@ def init() -> None: configure_logging(get_preference("enable_logging", False)) print("Auto-load init starting") - modules = get_all_submodules(Path(__file__).parent.parent) + + package_name = __package__.rsplit('.', 1)[0] + directory = Path(__file__).parent.parent + modules = get_all_submodules(directory, package_name) ordered_classes = get_ordered_classes_to_register(modules) print(f"Found modules: {modules}") print(f"Found classes: {ordered_classes}") def register() -> None: """Register all discovered classes and modules""" + global modules, ordered_classes + print("Registering classes") + + if not ordered_classes: + print("Warning: No classes to register") + ordered_classes = [] + for cls in ordered_classes: print(f"Registering: {cls}") try: @@ -47,6 +55,10 @@ def register() -> None: except ValueError: continue + if not modules: + print("Warning: No modules to register") + modules = [] + for module in modules: if module.__name__ == __name__: continue @@ -67,44 +79,29 @@ def unregister() -> None: if hasattr(module, "unregister"): module.unregister() -def get_manifest_id() -> str: - """Get the addon ID from the manifest file""" - manifest_path = Path(__file__).parent.parent / "blender_manifest.toml" - with open(manifest_path, "rb") as f: - manifest = tomllib.load(f) - return manifest["id"] - -def get_all_submodules(directory: Path) -> List[Any]: +def get_all_submodules(directory: Path, package_name: str) -> List[Any]: """Discover and import all submodules in the given directory""" - modules = [] - addon_id = get_manifest_id() - for root, dirs, files in os.walk(directory): - if "__pycache__" in root: - continue - path = Path(root) - if path == directory: - package_name = f"bl_ext.user_default.{addon_id}" - else: - relative_path = path.relative_to(directory).as_posix().replace('/', '.') - package_name = f"bl_ext.user_default.{addon_id}.{relative_path}" - for name in sorted(iter_module_names(path)): - modules.append(importlib.import_module(f".{name}", package_name)) - return modules + return list(iter_submodules(directory, package_name)) -def iter_submodules(path: Path, package_name: str) -> Generator[Any, None, None]: +def iter_submodules(directory: Path, package_name: str) -> Generator[Any, None, None]: """Iterate through submodules in a package""" - for name in sorted(iter_module_names(path)): - yield importlib.import_module("." + name, package_name) + for name in sorted(iter_submodule_names(directory)): + try: + yield importlib.import_module("." + name, package_name) + print(f"Successfully imported {name} from {package_name}") + except ImportError as e: + print(f"Error importing {name} from {package_name}: {e}") -def iter_module_names(path: Path) -> Generator[str, None, None]: +def iter_submodule_names(path: Path, root: str = "") -> Generator[str, None, None]: """Iterate through module names in a directory""" print(f"Scanning path: {path}") - modules_list = list(pkgutil.iter_modules([str(path)])) - print(f"Found these modules: {modules_list}") - for _, module_name, is_pkg in modules_list: - if not is_pkg: - print(f"Found module: {module_name}") - yield module_name + for _, module_name, is_package in pkgutil.iter_modules([str(path)]): + if is_package: + sub_path = path / module_name + sub_root = root + module_name + "." + yield from iter_submodule_names(sub_path, sub_root) + else: + yield root + module_name def get_ordered_classes_to_register(modules: List[Any]) -> List[Type]: """Get a topologically sorted list of classes to register""" @@ -112,28 +109,37 @@ def get_ordered_classes_to_register(modules: List[Any]) -> List[Type]: def get_register_deps_dict(modules: List[Any]) -> Dict[Type, Set[Type]]: """Get dependencies dictionary for class registration""" + my_classes = set(iter_classes_to_register(modules)) + my_classes_by_idname = {cls.bl_idname: cls for cls in my_classes if hasattr(cls, "bl_idname")} + 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)) + for cls in my_classes: + deps_dict[cls] = set() + deps_dict[cls].update(iter_deps_from_annotations(cls, my_classes)) + deps_dict[cls].update(iter_deps_from_parent_id(cls, my_classes_by_idname)) + return deps_dict -def iter_own_register_deps(cls: Type, classes_to_register: Set[Type]) -> Generator[Type, None, None]: - """Iterate through a class's own registration dependencies""" - yield from (dep for dep in iter_register_deps(cls) if dep in classes_to_register) - -def iter_register_deps(cls: Type) -> Generator[Type, None, None]: - """Iterate through all registration dependencies of a class""" +def iter_deps_from_annotations(cls: Type, my_classes: Set[Type]) -> Generator[Type, None, None]: + """Iterate through dependencies from class annotations""" for value in typing.get_type_hints(cls, {}, {}).values(): dependency = get_dependency_from_annotation(value) - if dependency is not None: + if dependency is not None and dependency in my_classes: yield dependency +def iter_deps_from_parent_id(cls: Type, my_classes_by_idname: Dict[str, Type]) -> Generator[Type, None, None]: + """Iterate through dependencies from panel parent IDs""" + if bpy.types.Panel in cls.__bases__: + parent_idname = getattr(cls, "bl_parent_id", None) + if parent_idname is not None: + parent_cls = my_classes_by_idname.get(parent_idname) + if parent_cls is not None: + yield parent_cls + def get_dependency_from_annotation(value: Any) -> Optional[Type]: """Get dependency type from a type annotation""" - if isinstance(value, tuple) and len(value) == 2: - if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty): - return value[1]["type"] + if isinstance(value, bpy.props._PropertyDeferred): + return value.keywords.get("type") return None def iter_classes_to_register(modules: List[Any]) -> Generator[Type, None, None]: @@ -164,7 +170,8 @@ def get_register_base_types() -> Set[Type]: "Panel", "Operator", "PropertyGroup", "AddonPreferences", "Header", "Menu", "Node", "NodeSocket", "NodeTree", - "UIList", "RenderEngine" + "UIList", "RenderEngine", + "Gizmo", "GizmoGroup", ]) def toposort(deps_dict: Dict[Type, Set[Type]]) -> List[Type]: @@ -172,25 +179,15 @@ def toposort(deps_dict: Dict[Type, Set[Type]]) -> List[Type]: sorted_list = [] sorted_values = set() - panels_to_sort = [(value, deps) for value, deps in deps_dict.items() - if hasattr(value, 'bl_parent_id')] - - base_panels = [(value, deps) for value, deps in deps_dict.items() - if not hasattr(value, 'bl_parent_id')] - - for value, deps in base_panels: - if len(deps) == 0: - sorted_list.append(value) - sorted_values.add(value) - - while len(deps_dict) > len(sorted_values): + while len(deps_dict) > 0: 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) + 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} return sorted_list