Files
create/docs/src/reference/create-module-bootstrap.md
forbes b3a58a6d92
Some checks failed
Build and Test / build (pull_request) Has been cancelled
docs: Create module bootstrap sequence (#148)
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:30:52 -06:00

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 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:

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()