diff --git a/src/Mod/Create/Init.py b/src/Mod/Create/Init.py index 1769b66306..1c6c3331af 100644 --- a/src/Mod/Create/Init.py +++ b/src/Mod/Create/Init.py @@ -4,9 +4,10 @@ import FreeCAD try: - from addon_loader import load_addons + from addon_loader import getAddonRegistry, load_addons load_addons(gui=False) + FreeCAD.getAddonRegistry = getAddonRegistry except Exception as e: FreeCAD.Console.PrintWarning(f"Create: Addon loader failed: {e}\n") diff --git a/src/Mod/Create/addon_loader.py b/src/Mod/Create/addon_loader.py index f229b991ad..5a73f19370 100644 --- a/src/Mod/Create/addon_loader.py +++ b/src/Mod/Create/addon_loader.py @@ -14,7 +14,6 @@ from typing import Optional import FreeCAD - # --------------------------------------------------------------------------- # Data structures # --------------------------------------------------------------------------- @@ -53,11 +52,10 @@ class AddonManifest: state: AddonState = AddonState.DISCOVERED error: str = "" load_time_ms: float = 0.0 + contexts: list[str] = field(default_factory=list) def __repr__(self): - return ( - f"AddonManifest(name={self.name!r}, version={self.version!r}, state={self.state.value})" - ) + return f"AddonManifest(name={self.name!r}, version={self.version!r}, state={self.state.value})" # --------------------------------------------------------------------------- @@ -97,6 +95,20 @@ class AddonRegistry: m = self._addons.get(name) return m is not None and m.state == AddonState.LOADED + def register_context(self, addon_name: str, context_id: str): + """Register a context ID as provided by an addon.""" + m = self._addons.get(addon_name) + if m is not None and context_id not in m.contexts: + m.contexts.append(context_id) + + def contexts(self) -> dict[str, list[str]]: + """Return a mapping of context IDs to the addon names that provide them.""" + result: dict[str, list[str]] = {} + for m in self._addons.values(): + for ctx in m.contexts: + result.setdefault(ctx, []).append(m.name) + return result + def _by_load_order(self) -> list[AddonManifest]: ordered = [] for name in self._load_order: @@ -213,7 +225,9 @@ def parse_manifest(manifest: AddonManifest): except ET.ParseError as e: manifest.state = AddonState.FAILED manifest.error = f"XML parse error: {e}" - FreeCAD.Console.PrintWarning(f"Create: Failed to parse {manifest.package_xml_path}: {e}\n") + FreeCAD.Console.PrintWarning( + f"Create: Failed to parse {manifest.package_xml_path}: {e}\n" + ) return # Standard fields @@ -256,6 +270,11 @@ def parse_manifest(manifest: AddonManifest): for dep in _findall(deps, "dependency"): if dep.text and dep.text.strip(): manifest.dependencies.append(dep.text.strip()) + ctxs = _find(kindred, "contexts") + if ctxs is not None: + for ctx in _findall(ctxs, "context"): + if ctx.text and ctx.text.strip(): + manifest.contexts.append(ctx.text.strip()) FreeCAD.Console.PrintLog( f"Create: Parsed {manifest.name} v{manifest.version} from {manifest.package_xml_path}\n" @@ -285,25 +304,27 @@ def validate_manifest(manifest: AddonManifest, create_version: str) -> bool: if manifest.min_create_version: if cv < _parse_version(manifest.min_create_version): manifest.state = AddonState.SKIPPED - manifest.error = ( - f"Requires Create >= {manifest.min_create_version}, running {create_version}" + manifest.error = f"Requires Create >= {manifest.min_create_version}, running {create_version}" + FreeCAD.Console.PrintWarning( + f"Create: Skipping {manifest.name}: {manifest.error}\n" ) - FreeCAD.Console.PrintWarning(f"Create: Skipping {manifest.name}: {manifest.error}\n") return False if manifest.max_create_version: if cv > _parse_version(manifest.max_create_version): manifest.state = AddonState.SKIPPED - manifest.error = ( - f"Requires Create <= {manifest.max_create_version}, running {create_version}" + manifest.error = f"Requires Create <= {manifest.max_create_version}, running {create_version}" + FreeCAD.Console.PrintWarning( + f"Create: Skipping {manifest.name}: {manifest.error}\n" ) - FreeCAD.Console.PrintWarning(f"Create: Skipping {manifest.name}: {manifest.error}\n") return False if not os.path.isdir(manifest.workbench_path): manifest.state = AddonState.SKIPPED manifest.error = f"Workbench path not found: {manifest.workbench_path}" - FreeCAD.Console.PrintWarning(f"Create: Skipping {manifest.name}: {manifest.error}\n") + FreeCAD.Console.PrintWarning( + f"Create: Skipping {manifest.name}: {manifest.error}\n" + ) return False # At least one of Init.py or InitGui.py must exist @@ -312,7 +333,9 @@ def validate_manifest(manifest: AddonManifest, create_version: str) -> bool: if not has_init and not has_gui: manifest.state = AddonState.SKIPPED manifest.error = f"No Init.py or InitGui.py in {manifest.workbench_path}" - FreeCAD.Console.PrintWarning(f"Create: Skipping {manifest.name}: {manifest.error}\n") + FreeCAD.Console.PrintWarning( + f"Create: Skipping {manifest.name}: {manifest.error}\n" + ) return False manifest.state = AddonState.VALIDATED @@ -324,7 +347,9 @@ def validate_manifest(manifest: AddonManifest, create_version: str) -> bool: # --------------------------------------------------------------------------- -def resolve_load_order(manifests: list[AddonManifest], mods_dir: str) -> list[AddonManifest]: +def resolve_load_order( + manifests: list[AddonManifest], mods_dir: str +) -> list[AddonManifest]: """Sort addons by dependencies, then by (load_priority, name). If no addons declare a element, fall back to the legacy @@ -428,7 +453,9 @@ def _load_addon(manifest: AddonManifest, gui: bool = False): else: manifest.load_time_ms += elapsed manifest.state = AddonState.LOADED - FreeCAD.Console.PrintLog(f"Create: Loaded {manifest.name} {init_file} ({elapsed:.0f}ms)\n") + FreeCAD.Console.PrintLog( + f"Create: Loaded {manifest.name} {init_file} ({elapsed:.0f}ms)\n" + ) except Exception as e: manifest.state = AddonState.FAILED manifest.error = str(e) @@ -491,3 +518,11 @@ def load_addons(gui: bool = False): for m in _registry.loaded(): _load_addon(m, gui=True) + + +def getAddonRegistry() -> Optional[AddonRegistry]: + """Return the addon registry singleton, or None if not yet initialized. + + Exposed as FreeCAD.getAddonRegistry() for runtime introspection. + """ + return _registry