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