Merge pull request 'docs: update addon loader pipeline and origin API documentation' (#407) from docs/update-addon-loader-origin into main
Some checks failed
Deploy Docs / build-and-deploy (push) Failing after 58s
Build and Test / build (push) Successful in 29m52s

Reviewed-on: #407
This commit was merged in pull request #407.
This commit is contained in:
2026-03-05 14:46:56 +00:00
5 changed files with 43 additions and 27 deletions

View File

@@ -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 `<kindred>` 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 `<kindred>` extensions declaring version bounds, load priority, and dependencies. The loader pipeline is:
1. **Scan** — discover `package.xml` manifests in `mods/`
2. **Parse** — extract `<kindred>` 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)

View File

@@ -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 <kindred> extensions
├─ validate_manifest() — check min/max_create_version
├─ parse_manifest() — extract <kindred> 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 <dependency>
└─ 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 `<kindred>` exten
The loader (`addon_loader.py`) processes addons in this order:
1. **Scan** — find all `mods/*/package.xml` files
2. **Parse** — extract `<kindred>` metadata (version bounds, priority, dependencies)
3. **Validate** — reject addons incompatible with the current Create version
4. **Resolve** — topological sort by `<dependency>` declarations, breaking ties by `<load_priority>`
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 `<kindred>` 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 `<dependency>` declarations, breaking ties by `<load_priority>`; 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)

View File

@@ -8,15 +8,15 @@ The `<kindred>` 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 `<name>` 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 `<name>` 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 `<kindred/>` 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 `<dependency>` names another addon by its `<name>` 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 `<dependency>` names another addon by its `<name>` 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 `<contexts>` element documents which editing contexts the addon interacts with. The `action` attribute describes the type of interaction:
The `<contexts>` 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 |
|---|---|

View File

@@ -54,6 +54,8 @@ Every addon needs a `package.xml` with a `<kindred>` extension block. The `<work
| 50-99 | Standard addons (silo) |
| 100+ | Optional/user addons |
The loader validates manifests at parse time: `load_priority` must be a valid integer, version strings must be dotted-numeric (e.g. `0.1.5`), context IDs must be alphanumeric with dots/underscores, and dependency names are cross-checked against all discovered addons. All errors are accumulated and reported together.
See [Package.xml Schema Extensions](./package-xml-schema.md) for the full schema.
## Step 2: Console bootstrap (Init.py)

View File

@@ -30,15 +30,15 @@ Runs immediately at application startup, before any GUI is available.
| datums | 45 | Unified datum creator |
| silo | 60 | PLM workbench |
For each addon:
Pipeline steps:
1. Discover `package.xml` manifest (depth 1 or 2)
2. Parse `<kindred>` 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** `<kindred>` 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