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.
5.8 KiB
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 loadingsrc/Mod/Create/InitGui.py-- GUI-phase workbench loading and deferred timerssrc/Mod/Create/update_checker.py-- Gitea releases API pollingsrc/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:
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:
- Build the path from
FreeCAD.getHomePath() + "mods/" + addon_path - Add to
sys.pathif not already present - Find
Init.pyin the addon directory - 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)
- Query
https://git.kindred-systems.com/api/v1/repos/kindred/create/releases?limit=10(5 s timeout) - Filter out drafts, pre-releases, and the "latest" tag
- Parse version tags (
v0.1.3becomes tuple(0, 1, 3)) - Compare against
version.VERSIONfrom the Create module - If a newer version exists, log it to the console
- 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()