diff --git a/mods/sdk/kindred_sdk/__init__.py b/mods/sdk/kindred_sdk/__init__.py index 0100622b9d..a0b881c3cf 100644 --- a/mods/sdk/kindred_sdk/__init__.py +++ b/mods/sdk/kindred_sdk/__init__.py @@ -24,7 +24,12 @@ from kindred_sdk.origin import ( set_active_origin, unregister_origin, ) -from kindred_sdk.registry import addon_version, is_addon_loaded, loaded_addons +from kindred_sdk.registry import ( + addon_resource, + addon_version, + is_addon_loaded, + loaded_addons, +) from kindred_sdk.statusbar import register_status_widget from kindred_sdk.theme import get_theme_tokens, load_palette from kindred_sdk.toolbar import register_toolbar @@ -33,6 +38,7 @@ from kindred_sdk.version import SDK_VERSION __all__ = [ "SDK_VERSION", "active_origin", + "addon_resource", "addon_version", "available_contexts", "context_history", diff --git a/mods/sdk/kindred_sdk/registry.py b/mods/sdk/kindred_sdk/registry.py index 6f8f7fc879..e1e22f65ef 100644 --- a/mods/sdk/kindred_sdk/registry.py +++ b/mods/sdk/kindred_sdk/registry.py @@ -3,6 +3,8 @@ # Thin wrappers around FreeCAD.KindredAddons (AddonRegistry) so addons # use a stable SDK import instead of reaching into FreeCAD internals. +import os + import FreeCAD @@ -51,3 +53,47 @@ def loaded_addons() -> list[str]: if registry is None: return [] return [m.name for m in registry.loaded()] + + +def addon_resource(name: str, relative_path: str) -> str: + """Resolve a bundled asset path for an addon. + + Parameters + ---------- + name : str + Addon name as declared in its ``package.xml`` (e.g. ``"silo"``). + relative_path : str + Path relative to the addon's root directory + (e.g. ``"icons/silo_commit.svg"``). + + Returns + ------- + str + Absolute path to the resolved file. + + Raises + ------ + LookupError + If the addon is not registered. + FileNotFoundError + If the resolved path does not exist on disk. + + >>> import kindred_sdk as sdk + >>> sdk.addon_resource("silo", "icons/silo_commit.svg") + '/home/.../mods/silo/freecad/icons/silo_commit.svg' + """ + registry = _get_registry() + if registry is None: + raise LookupError( + f"Addon registry not initialized; cannot resolve resource for '{name}'" + ) + manifest = registry.get(name) + if manifest is None: + raise LookupError(f"Addon '{name}' is not registered") + + resolved = os.path.join(manifest.addon_root, relative_path) + if not os.path.exists(resolved): + raise FileNotFoundError( + f"Resource not found: {resolved} (addon '{name}', path '{relative_path}')" + ) + return resolved