From 8f27083e45e62ba64441fe815abc996dda024750 Mon Sep 17 00:00:00 2001 From: forbes-0023 Date: Thu, 5 Mar 2026 08:22:22 -0600 Subject: [PATCH] docs: update addon loader pipeline and origin API documentation Update documentation across 5 files to reflect changes from #388 (manifest validation) and #391 (per-document origin bindings): - CLAUDE.md: expand addon loading section with 6-step pipeline, add per-document origin functions to SDK API list - ARCHITECTURE.md: update bootstrap flow diagram with validate_dependencies() step, expand lifecycle to 7 steps - create-module-bootstrap.md: rewrite pipeline steps with validation detail, add pipeline functions to dependency chain diagram - package-xml-schema.md: add parse-time validation rules to field reference table, update version/dependency/context sections - writing-an-addon.md: add validation summary after priority table Refs #388, #391 --- CLAUDE.md | 13 +++++++++--- docs/ARCHITECTURE.md | 16 +++++++------- docs/src/development/package-xml-schema.md | 18 ++++++++-------- docs/src/development/writing-an-addon.md | 2 ++ docs/src/reference/create-module-bootstrap.md | 21 ++++++++++++------- 5 files changed, 43 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3607076450..f5afa54de9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -162,7 +162,14 @@ Python API (prefer `kindred_sdk` wrappers over direct `FreeCADGui` calls): ### Addon Loading -Addons in `mods/` are loaded by `src/Mod/Create/addon_loader.py`. Each addon provides a `package.xml` at the mod root with `` extensions declaring version bounds, load priority, and dependencies. The loader discovers manifests, validates version bounds, and resolves load order via topological sort on dependencies, breaking ties by `(load_priority, name)`. +Addons in `mods/` are loaded by `src/Mod/Create/addon_loader.py`. Each addon provides a `package.xml` at the mod root with `` extensions declaring version bounds, load priority, and dependencies. The loader pipeline is: + +1. **Scan** — discover `package.xml` manifests in `mods/` +2. **Parse** — extract `` metadata; validate field types/formats (load_priority must be int, version strings must be dotted-numeric, context IDs must be alphanumeric/dots/underscores) +3. **Validate dependencies** — cross-check declared dependency names against all discovered addons +4. **Validate manifests** — check version bounds, workbench path, Init.py presence (all errors accumulated per-addon in `AddonManifest.errors`) +5. **Resolve load order** — topological sort by dependencies, breaking ties by `(load_priority, name)` +6. **Load** — execute Init.py / InitGui.py for each validated addon Current load order: **sdk** (0) → **solver** (10) → **gears** (40) → **datums** (45) → **silo** (60). @@ -186,7 +193,7 @@ Each addon manages its own deferred setup in its `InitGui.py`. For example, Silo File operations (New, Open, Save, Commit, Pull, Push) are abstracted behind `FileOrigin` (`src/Gui/FileOrigin.h`). `LocalFileOrigin` handles local files; `SiloOrigin` (`mods/silo/freecad/silo_origin.py`) backs Silo-tracked documents. The active origin is selected automatically based on document properties (`SiloItemId`, `SiloPartNumber`). -Origins are registered via `kindred_sdk.register_origin()`. Query functions (`list_origins`, `active_origin`, `get_origin`, `set_active_origin`) route through the `kcsdk` C++ module. +Origins are registered via `kindred_sdk.register_origin()`. Query functions (`list_origins`, `active_origin`, `get_origin`, `set_active_origin`) route through the `kcsdk` C++ module. Per-document origin associations are managed via `kindred_sdk.document_origin()`, `set_document_origin()`, `clear_document_origin()`, and `find_owning_origin()`. ## Submodules @@ -218,7 +225,7 @@ Initialize all submodules: `git submodule update --init --recursive` Stable API contract for addons. Python package `kindred_sdk` wraps the KCSDK C++ module, providing: - **Editing contexts:** `register_context()`, `register_overlay()`, `inject_commands()`, `refresh_context()` -- **Origins:** `register_origin()`, `unregister_origin()`, `list_origins()`, `active_origin()` +- **Origins:** `register_origin()`, `unregister_origin()`, `list_origins()`, `active_origin()`, `document_origin()`, `set_document_origin()`, `clear_document_origin()`, `find_owning_origin()` - **Dock panels:** `register_dock_panel(object_name, title, factory, area, delay_ms)` - **Commands:** `register_command(cmd_id, classname, pixmap, tooltip)` - **Theme:** `get_theme_tokens()`, `load_palette()` (Catppuccin Mocha YAML palette) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index efb74f6b30..b96b258bd8 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -7,8 +7,9 @@ FreeCAD startup └─ src/Mod/Create/Init.py └─ addon_loader.load_addons(gui=False) ├─ scan_addons("mods/") — find package.xml manifests - ├─ parse_manifest() — extract extensions - ├─ validate_manifest() — check min/max_create_version + ├─ parse_manifest() — extract extensions, validate types/formats + ├─ validate_dependencies() — cross-check deps against discovered addons + ├─ validate_manifest() — check version bounds, paths (errors accumulated) ├─ resolve_load_order() — topological sort by └─ for each addon in order: ├─ add addon dir to sys.path @@ -60,11 +61,12 @@ Each addon in `mods/` provides a `package.xml` manifest with a `` exten The loader (`addon_loader.py`) processes addons in this order: 1. **Scan** — find all `mods/*/package.xml` files -2. **Parse** — extract `` metadata (version bounds, priority, dependencies) -3. **Validate** — reject addons incompatible with the current Create version -4. **Resolve** — topological sort by `` declarations, breaking ties by `` -5. **Load** — execute `Init.py` (console) then `InitGui.py` (GUI) for each addon -6. **Register** — populate `FreeCAD.KindredAddons` registry with addon state +2. **Parse** — extract `` metadata (version bounds, priority, dependencies); validate field types and formats (load_priority must be int, version strings must be dotted-numeric, context IDs must be alphanumeric/dots/underscores) +3. **Validate dependencies** — cross-check all declared dependency names against discovered addon names +4. **Validate manifests** — reject addons incompatible with the current Create version, missing workbench paths, or lacking Init files; all errors accumulated per-addon in `AddonManifest.errors` +5. **Resolve** — topological sort by `` declarations, breaking ties by ``; addons with errors are excluded +6. **Load** — execute `Init.py` (console) then `InitGui.py` (GUI) for each addon +7. **Register** — populate `FreeCAD.KindredAddons` registry with addon state Current load order: **sdk** (0) → **solver** (10) → **gears** (40) → **datums** (45) → **silo** (60) diff --git a/docs/src/development/package-xml-schema.md b/docs/src/development/package-xml-schema.md index f562cd96bb..f944141df0 100644 --- a/docs/src/development/package-xml-schema.md +++ b/docs/src/development/package-xml-schema.md @@ -8,15 +8,15 @@ The `` element is ignored by FreeCAD's AddonManager and stock module lo | Field | Parsed by loader | Required | Default | Description | |---|---|---|---|---| -| `min_create_version` | Yes | No | *(none)* | Minimum Kindred Create version. Addon is skipped if the running version is lower. | -| `max_create_version` | Yes | No | *(none)* | Maximum Kindred Create version. Addon is skipped if the running version is higher. | -| `load_priority` | Yes | No | `100` | Integer. Lower values load first. Used as a secondary sort after dependency resolution. | -| `dependencies` | Yes | No | *(none)* | List of addon names (by `` in their `package.xml`) that must load before this one. | +| `min_create_version` | Yes | No | *(none)* | Minimum Kindred Create version. Addon is skipped if the running version is lower. Validated at parse time: must be dotted-numeric (e.g. `0.1.0`). | +| `max_create_version` | Yes | No | *(none)* | Maximum Kindred Create version. Addon is skipped if the running version is higher. Validated at parse time: must be dotted-numeric. | +| `load_priority` | Yes | No | `100` | Integer. Lower values load first. Used as a secondary sort after dependency resolution. Validated at parse time: must be a valid integer. | +| `dependencies` | Yes | No | *(none)* | List of addon names (by `` in their `package.xml`) that must load before this one. Cross-validated against all discovered addons after parsing. | | `sdk_version` | No | No | *(none)* | Required kindred-addon-sdk version. Reserved for future use when the SDK is available. | | `pure_python` | No | No | `true` | If `false`, the addon requires compiled C++ components. Informational. | -| `contexts` | No | No | *(none)* | Editing contexts this addon registers or injects into. Informational. | +| `contexts` | Yes | No | *(none)* | Editing contexts this addon registers or injects into. Validated at parse time: IDs must match `[a-zA-Z0-9][a-zA-Z0-9_.]*`. | -Fields marked "Parsed by loader" are read by `addon_loader.py` and affect load behavior. Other fields are informational metadata for tooling and documentation. +Fields marked "Parsed by loader" are read by `addon_loader.py` and affect load behavior. Validation errors are accumulated in `AddonManifest.errors` — all problems are reported in a single pass rather than stopping at the first failure. Other fields are informational metadata for tooling and documentation. ## Schema @@ -43,7 +43,7 @@ All child elements are optional. An empty `` element is valid and sign ### Version fields -`min_create_version` and `max_create_version` are compared against the running Kindred Create version using semantic versioning (major.minor.patch). If the running version falls outside the declared range, the addon is skipped with a warning in the report view. +`min_create_version` and `max_create_version` are compared against the running Kindred Create version using semantic versioning (major.minor.patch). Values are validated at parse time against the pattern `^\d+(\.\d+)*$` — non-conforming strings produce an error. If the running version falls outside the declared range, the addon is skipped with a warning in the report view. ### Load priority @@ -58,11 +58,11 @@ When multiple addons have no dependency relationship, `load_priority` determines ### Dependencies -Each `` names another addon by its `` element in `package.xml`. The loader resolves load order using topological sort. If a dependency is not found among discovered addons, the dependent addon is skipped. +Each `` names another addon by its `` element in `package.xml`. The loader resolves load order using topological sort. After all manifests are parsed, `validate_dependencies()` cross-checks every declared dependency name against the set of discovered addons. If a dependency is not found, the error is accumulated on the addon's manifest and the addon is skipped. ### Contexts -The `` element documents which editing contexts the addon interacts with. The `action` attribute describes the type of interaction: +The `` element declares which editing contexts the addon interacts with. Context IDs are validated at parse time — they must match the pattern `[a-zA-Z0-9][a-zA-Z0-9_.]*` (start with alphanumeric, then alphanumeric, dots, or underscores). Invalid IDs produce an error and are not registered. The `action` attribute describes the type of interaction: | Action | Meaning | |---|---| diff --git a/docs/src/development/writing-an-addon.md b/docs/src/development/writing-an-addon.md index 03967be25d..3c1b32f4f5 100644 --- a/docs/src/development/writing-an-addon.md +++ b/docs/src/development/writing-an-addon.md @@ -54,6 +54,8 @@ Every addon needs a `package.xml` with a `` extension block. The `` extensions (version bounds, priority, dependencies) -3. Validate version compatibility -4. Resolve load order via topological sort on dependencies -5. Add addon dir to `sys.path` -6. Execute `Init.py` -7. Register in `FreeCAD.KindredAddons` +1. **Discover** `package.xml` manifests (depth 1 or 2 under `mods/`) +2. **Parse** `` extensions (version bounds, priority, dependencies); validate field types/formats at parse time (load_priority must be int, version strings must be dotted-numeric, context IDs must be alphanumeric/dots/underscores) +3. **Validate dependencies** — cross-check all declared dependency names against discovered addons +4. **Validate manifests** — check version compatibility, workbench path, Init.py presence; all errors accumulated per-addon in `AddonManifest.errors` +5. **Resolve** load order via topological sort on dependencies (addons with errors excluded) +6. For each validated addon: add addon dir to `sys.path`, execute `Init.py` +7. Register all addons in `FreeCAD.KindredAddons` Failures are logged to `FreeCAD.Console` and do not prevent other addons from loading. @@ -115,6 +115,11 @@ FreeCAD startup | Init.py (exec'd, immediate) +-- addon_loader.load_addons(gui=False) + | +-- scan_addons() (discover manifests) + | +-- parse_manifest() (extract + validate types/formats) + | +-- validate_dependencies() (cross-check dep names) + | +-- validate_manifest() (version bounds, paths, accumulate errors) + | +-- resolve_load_order() (topo sort, skip errored addons) | +-- sdk/Init.py | +-- solver/Init.py | +-- gears/Init.py