Merge pull request 'docs: update addon loader pipeline and origin API documentation' (#407) from docs/update-addon-loader-origin into main
Reviewed-on: #407
This commit was merged in pull request #407.
This commit is contained in:
13
CLAUDE.md
13
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 `<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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|---|---|
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user