Compare commits

...

1 Commits

Author SHA1 Message Date
forbes
06911d27aa docs: Create module bootstrap sequence (#148)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Document the two-phase bootstrap: console-phase addon loading via
exec(), GUI-phase workbench registration, six deferred QTimer
callbacks (kc_format, silo origin, auth panel, first-start check,
activity panel, update checker), and the Gitea releases API polling
with skip/interval logic.
2026-02-14 13:28:08 -06:00
2 changed files with 134 additions and 0 deletions

View File

@@ -50,6 +50,8 @@
# Reference
- [Configuration](./reference/configuration.md)
- [Create Module Bootstrap](./reference/create-module-bootstrap.md)
- [Datum Creator](./reference/datum-creator.md)
- [Glossary](./reference/glossary.md)
# C++ API Reference

View File

@@ -0,0 +1,132 @@
# Create Module Bootstrap Sequence
The Create module (`src/Mod/Create/`) is the integration layer that bootstraps Kindred Create's addons and deferred services. It runs in two phases: console (headless) and GUI.
**Source files:**
- `src/Mod/Create/Init.py` -- console-phase addon loading
- `src/Mod/Create/InitGui.py` -- GUI-phase workbench loading and deferred timers
- `src/Mod/Create/update_checker.py` -- Gitea releases API polling
- `src/Mod/Create/kc_format.py` -- .kc file format round-trip preservation
## Loading Mechanism
FreeCAD loads module `Init.py` and `InitGui.py` files using **direct `exec()`**, not Python's import system. The C++ startup code scans `Mod/` directories, reads each file as text, compiles it, and executes it in an isolated namespace:
```python
source = init_py.read_text(encoding="utf-8")
code = compile(source, init_py, "exec")
exec(code)
```
Modules loaded this way are **not** added to `sys.modules`. Each execution gets a fresh globals dict with no caching or dependency resolution.
## Phase 1: Console (`Init.py`)
Runs immediately at application startup, before any GUI is available.
`setup_kindred_addons()` discovers and loads two built-in addons from `mods/`:
| Addon | Path | Purpose |
|-------|------|---------|
| ztools | `mods/ztools/ztools/` | Part design, assembly, and sketcher tools |
| silo | `mods/silo/freecad/` | Database integration and version control |
For each addon:
1. Build the path from `FreeCAD.getHomePath() + "mods/" + addon_path`
2. Add to `sys.path` if not already present
3. Find `Init.py` in the addon directory
4. Execute it with `exec(compile(source, init_file, "exec"), exec_globals)`
Failures are logged to `FreeCAD.Console.PrintWarning()` and do not prevent other addons from loading.
## Phase 2: GUI (`InitGui.py`)
Runs after the console phase, when `FreeCADGui` is available.
### Synchronous
`setup_kindred_workbenches()` loads `InitGui.py` from the same two addons (ztools, silo), registering their workbenches and GUI commands.
### Deferred Timers
Six `QTimer.singleShot` calls stagger initialization to let the GUI event loop settle:
| Delay | Function | Purpose |
|-------|----------|---------|
| 500 ms | `_register_kc_format()` | Register .kc file format observer |
| 1500 ms | `_register_silo_origin()` | Register Silo as a file origin |
| 2000 ms | `_setup_silo_auth_panel()` | Dock the Database Auth panel |
| 3000 ms | `_check_silo_first_start()` | Show settings dialog on first run |
| 4000 ms | `_setup_silo_activity_panel()` | Dock the Database Activity panel |
| 10000 ms | `_check_for_updates()` | Poll Gitea for new releases |
Every timer callback is wrapped in `try/except`. Failures log to `FreeCAD.Console.PrintLog()` and do not crash the application.
### Timer Details
**_register_kc_format (500 ms)** -- Imports `kc_format` and calls `kc_format.register()`, which installs a `DocumentObserver` with `slotStartSaveDocument` / `slotFinishSaveDocument` hooks. These cache `silo/` ZIP entries before FreeCAD's C++ save rewrites the archive, then re-inject them afterwards. Only processes `.kc` files.
**_register_silo_origin (1500 ms)** -- Imports `silo_origin` and calls `silo_origin.register_silo_origin()`, making Silo available in the origin selector dropdown alongside LocalFileOrigin.
**_setup_silo_auth_panel (2000 ms)** -- Creates a `QDockWidget` titled "Database Auth" containing a `SiloAuthDockWidget` from `silo_commands`. Docked on the right side of the main window. Guards against duplicates by checking if the widget already exists.
**_check_silo_first_start (3000 ms)** -- Reads `User parameter:BaseApp/Preferences/Mod/KindredSilo`. On the very first launch (when `FirstStartChecked` is not set), runs the `Silo_Settings` command if no API URL is configured.
**_setup_silo_activity_panel (4000 ms)** -- Creates a "Database Activity" dock widget showing the 20 most recent Silo items (part number, description, date). Falls back to "(Unable to connect to Silo database)" on connection failure.
**_check_for_updates (10000 ms)** -- Calls `update_checker._run_update_check()` in the background.
## Update Checker
`update_checker.py` polls the Gitea releases API for newer versions.
### Check Decision (`_should_check`)
Reads preferences from `User parameter:BaseApp/Preferences/Mod/KindredCreate/Update`:
| Parameter | Type | Default | Purpose |
|-----------|------|---------|---------|
| `CheckEnabled` | bool | true | Master enable/disable |
| `CheckIntervalDays` | int | 1 | Minimum days between checks |
| `LastCheckTimestamp` | string | (none) | ISO timestamp of last check |
| `SkippedVersion` | string | (none) | Version the user chose to skip |
A check runs only if enabled, the interval has elapsed, and the current version is not the skipped version.
### Check Execution (`check_for_update`)
1. Query `https://git.kindred-systems.com/api/v1/repos/kindred/create/releases?limit=10` (5 s timeout)
2. Filter out drafts, pre-releases, and the "latest" tag
3. Parse version tags (`v0.1.3` becomes tuple `(0, 1, 3)`)
4. Compare against `version.VERSION` from the Create module
5. If a newer version exists, log it to the console
6. Record `LastCheckTimestamp`
The checker never shows a dialog -- it only logs to `FreeCAD.Console`.
## Dependency Chain
```
FreeCAD startup
|
Init.py (exec'd, immediate)
+-- setup_kindred_addons()
| +-- ztools/Init.py (exec'd)
| +-- silo/freecad/Init.py (exec'd)
|
[GUI startup]
|
InitGui.py (exec'd, immediate)
+-- setup_kindred_workbenches()
| +-- ztools/InitGui.py (exec'd)
| +-- silo/freecad/InitGui.py (exec'd)
|
+-- QTimer 500ms --> kc_format.register()
+-- QTimer 1500ms --> silo_origin.register_silo_origin()
+-- QTimer 2000ms --> SiloAuthDockWidget
+-- QTimer 3000ms --> Silo first-start check
+-- QTimer 4000ms --> Database Activity panel
+-- QTimer 10000ms --> update_checker._run_update_check()
```