chore: migrate submodules to public repos, rework docs and CI/CD
Some checks failed
Build and Test / build (push) Has been cancelled

- Update .gitmodules: ztools, silo, and OndselSolver now reference
  public git.kindred-systems.com URLs instead of internal Gitea
- Merge OndselSolver numerical solver with ML solver scaffolding
  into unified kindred/solver repository
- Rewrite README.md for conciseness
- Add docs/CI_CD.md with full pipeline documentation
- Rework CI/CD workflows for public dockerized runners
- Add multi-platform release builds (Linux, macOS, Windows)
- Release workflow triggers on v* tags only
- Update docs/REPOSITORY_STATE.md and docs/INTEGRATION_PLAN.md
This commit is contained in:
2026-02-03 10:54:47 -06:00
parent 0ef9ffcf51
commit 1b3f780aa1
8 changed files with 877 additions and 1103 deletions

View File

@@ -1,460 +1,159 @@
# Kindred Create Integration Plan
# Integration Plan
This document outlines the strategy for integrating ztools and Silo workbenches as built-in addons in Kindred Create while maintaining clear boundaries with FreeCAD core.
Strategy for integrating ztools and Silo as built-in addons while maintaining clear boundaries with FreeCAD core.
## Goals
1. **Native feel** - ztools and Silo should feel like first-class citizens, not bolted-on addons
2. **Clean boundaries** - Clear separation between FreeCAD core, Kindred extensions, and addon code
3. **Minimal core modifications** - Preserve FreeCAD's container models (Part, Body, Assembly)
4. **Maintainability** - Easy to pull upstream FreeCAD changes without merge conflicts
5. **Extensibility** - Architecture that supports future Kindred-specific features
1. **Native feel** -- ztools and Silo behave as first-class citizens, not bolted-on addons
2. **Clean boundaries** -- Clear separation between FreeCAD core, Kindred extensions, and addon code
3. **Minimal core modifications** -- Preserve FreeCAD's container models (Part, Body, Assembly)
4. **Maintainability** -- Easy to pull upstream FreeCAD changes without merge conflicts
5. **Extensibility** -- Architecture supports future Kindred-specific features
## Current State
### Repository Structure
```
kindred-create/
├── src/Mod/ # FreeCAD core modules (PartDesign, Sketcher, Assembly, etc.)
├── mods/ # Kindred addons (git submodules)
│ ├── ztools/ # Part design extensions, theme
│ └── silo/ # Parts database integration
└── resources/ # Branding, default preferences
```
### Integration Points Today
- **ztools**: Pure Python addon wrapping FreeCAD commands with enhanced UX
- **Silo**: Pure Python addon with REST API integration
- **Theme**: Preference pack + runtime stylesheet application
## Architecture Layers
## Architecture layers
```
┌─────────────────────────────────────────────────────────────────
Kindred Create Application
├─────────────────────────────────────────────────────────────────
│ Layer 4: Kindred Workbenches (mods/)
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ ztools │ │ Silo
│ │ - Datum Creator │ │ - Open/Save/Commit │ │
│ │ - Enhanced Pocket │ │ - Part numbering │ │
│ │ - Assembly Patterns│ │ - Revision control │ │
│ │ - Spreadsheet fmt │ │ - BOM management │ │
│ └─────────────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────────
│ Layer 3: Kindred Core Extensions (src/Mod/Kindred/)
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ - KindredFeatures: Flip-side pocket, custom geometry ops ││
│ │ - KindredGui: Shared UI components, selection helpers ││
│ - Theme integration hooks │
│ └─────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────┤
Layer 2: FreeCAD Python API
┌─────────────────────────────────────────────────────────────┐
│ │ FreeCAD, FreeCADGui, Part, PartDesign, Sketcher, Assembly ││
│ └─────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────
│ Layer 1: FreeCAD Core (C++)
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ App::Document, Part::Feature, PartDesign::Body, etc. ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────
┌─────────────────────────────────────────────────────────────┐
│ Kindred Create Application │
├─────────────────────────────────────────────────────────────┤
│ Layer 4: Kindred Workbenches (mods/) │
│ ┌──────────────────┐ ┌──────────────────┐
│ │ ztools │ │ Silo
│ │ Datum Creator │ │ Open/Save/Commit│
│ │ Enhanced Pocket │ │ Part numbering │
│ │ Assembly Patterns│ │ Revision control│
│ │ Spreadsheet fmt │ │ BOM management │
│ └──────────────────┘ └──────────────────┘
├─────────────────────────────────────────────────────────────┤
│ Layer 3: Kindred Bootstrap (src/Mod/Create/)
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Addon loading, theme application, global menu/toolbar ││
│ │ injection via WorkbenchManipulator API ││
└─────────────────────────────────────────────────────────┘
─────────────────────────────────────────────────────────────
│ Layer 2: FreeCAD Python API │
┌─────────────────────────────────────────────────────────┐
│ FreeCAD, FreeCADGui, Part, PartDesign, Sketcher, │
│ │ Assembly, WorkbenchManipulator ││
│ └─────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────┤
│ Layer 1: FreeCAD Core (C++) │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ App::Document, Part::Feature, PartDesign::Body, etc. ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
```
## Boundary Definitions
## Boundary definitions
### Layer 1: FreeCAD Core (DO NOT MODIFY)
**Location**: `src/Mod/PartDesign/App/`, `src/Mod/Part/App/`, etc.
### Layer 1: FreeCAD Core -- do not modify
These are FreeCAD's fundamental data structures and should remain untouched:
- `PartDesign::Body` - Feature container
- `PartDesign::Pocket`, `PartDesign::Pad` - Additive/subtractive features
- `Part::Feature` - Base geometric feature
- `App::Document` - Document container
- `Sketcher::SketchObject` - 2D constraint system
FreeCAD's fundamental data structures: `PartDesign::Body`, `PartDesign::Pocket`, `Part::Feature`, `App::Document`, `Sketcher::SketchObject`. Modifying these creates merge conflicts with upstream and risks breaking FCStd file compatibility.
**Rationale**: Modifying these creates merge conflicts with upstream FreeCAD and risks breaking compatibility with existing FCStd files.
### Layer 2: FreeCAD Python API -- use as-is
### Layer 2: FreeCAD Python API (USE AS-IS)
**Access via**: `import FreeCAD`, `import FreeCADGui`, `import Part`, etc.
The Python API provides everything needed for feature creation, command registration, and geometry access. ZTools and Silo operate entirely through this layer.
The Python API provides everything needed for feature creation:
- Create objects: `body.newObject("PartDesign::Pocket", "Pocket")`
- Set properties: `pocket.Length = 10.0`
- Register commands: `FreeCADGui.addCommand("Name", CommandClass())`
- Access geometry: `shape.Faces`, `shape.Edges`, `shape.Vertexes`
### Layer 3: Kindred Bootstrap -- `src/Mod/Create/`
### Layer 3: Kindred Core Extensions (NEW - MINIMAL)
**Location**: `src/Mod/Kindred/` (to be created)
The Create module is a thin Python loader that:
- Adds `mods/` addon paths to `sys.path` and executes their `Init.py`/`InitGui.py` files
- Installs `SiloMenuManipulator` for global File menu/toolbar injection
- Sets up deferred Silo dock panels (auth, activity) via `QTimer`
- Handles first-start configuration
A thin C++ module providing capabilities that cannot be achieved in pure Python:
This layer does not contain C++ code. It uses FreeCAD's `WorkbenchManipulator` API for menu/toolbar injection.
| Component | Purpose | Justification |
|-----------|---------|---------------|
| `CreateFeatures` | Custom PartDesign-like features | Python feature objects have performance limitations for complex boolean operations |
| `CreateGui` | Shared UI utilities | Common selection helpers, task panel base classes |
| `ThemeHooks` | Theme application entry points | Ensure theme applies before any workbench loads |
### Layer 4: Kindred Workbenches -- `mods/`
**Namespace**: All Kindred Create features use the `Create::` prefix (e.g., `Create::FlipPocket`).
Pure Python workbenches following FreeCAD's addon pattern. Self-contained with `InitGui.py`, `Init.py`, and `package.xml`. Developed and versioned independently as git submodules.
**Design principle**: Only add C++ code when Python cannot achieve the requirement. Document why each component exists.
---
### Layer 4: Kindred Workbenches (ADDON PATTERN)
**Location**: `mods/ztools/`, `mods/silo/`
## Phase status
Pure Python workbenches following FreeCAD's addon pattern:
- Self-contained with `InitGui.py`, `Init.py`, `package.xml`
- Register commands via `FreeCADGui.addCommand()`
- Define toolbars/menus via `Workbench.appendToolbar()`
- Can be developed/tested independently
### Phase 1: Addon auto-loading -- DONE
## Detailed Integration Plan
**Implementation:** `src/Mod/Create/Init.py` and `InitGui.py` load workbenches from `mods/` at startup using `exec()`. Addons degrade gracefully if submodule is absent.
### Phase 1: Addon Auto-Loading
**Default workbench:** `ZToolsWorkbench` (set in `resources/preferences/KindredCreate/KindredCreate.cfg`).
**Goal**: ztools and Silo load automatically without user intervention.
### Phase 2: Enhanced Pocket as C++ feature -- NOT STARTED
**Implementation**:
**Goal:** Replace the Python boolean-operation workaround in `ZTools_EnhancedPocket` with a proper `Create::FlipPocket` C++ feature inheriting from `PartDesign::ProfileBased`.
1. **Create addon manifest** (`src/Mod/Kindred/addons.json`):
```json
{
"builtin_addons": [
{
"name": "ztools",
"path": "mods/ztools/ztools",
"autoload": true,
"workbench": "ZToolsWorkbench"
},
{
"name": "silo",
"path": "mods/silo/pkg/freecad",
"autoload": true,
"workbench": "SiloWorkbench"
}
]
}
```
**Rationale:** The current Python implementation creates a standard Pocket then applies a boolean Common. A native feature would integrate properly with the feature tree, support undo/redo correctly, and perform better on complex geometry.
2. **Modify addon path discovery** (`src/Mod/Kindred/Init.py`):
```python
# Add mods/ directory to FreeCAD's module search path
import FreeCAD
import os
**Scope:**
- `src/Mod/Create/App/FeatureFlipPocket.cpp` -- Feature implementation
- `src/Mod/Create/Gui/TaskFlipPocket.cpp` -- Task panel
- `src/Mod/Create/Gui/ViewProviderFlipPocket.cpp` -- View provider
- Update `ZTools_EnhancedPocket` to create `Create::FlipPocket` instead of the workaround
mods_dir = os.path.join(FreeCAD.getHomePath(), "mods")
if os.path.isdir(mods_dir):
for addon in os.listdir(mods_dir):
addon_path = os.path.join(mods_dir, addon)
if os.path.isdir(addon_path) and addon_path not in sys.path:
sys.path.insert(0, addon_path)
```
**Decision:** Deferred. The Python approach is functional for current needs. Revisit when performance or feature-tree integration becomes a problem.
3. **Set default workbench** in preferences:
```xml
<!-- resources/preferences/KindredCreate/KindredCreate.cfg -->
<FCText Name="StartUpModule">ZToolsWorkbench</FCText>
```
### Phase 3: Datum C++ helpers -- NOT STARTED (SUPERSEDED)
**Files to create/modify**:
- Create: `src/Mod/Kindred/Init.py`
- Create: `src/Mod/Kindred/InitGui.py`
- Create: `src/Mod/Kindred/CMakeLists.txt`
- Modify: `src/Mod/CMakeLists.txt` (add Kindred subdirectory)
- Modify: `resources/preferences/KindredCreate/KindredCreate.cfg`
**Original goal:** Create C++ geometry helper functions for datum calculations.
### Phase 2: Enhanced Pocket as Separate Feature
**Current state:** The Python implementation now uses FreeCAD's built-in `Part::AttachExtension` for automatic parametric updates. Each datum type maps to a native MapMode (`FlatFace`, `ThreePointsPlane`, `NormalToEdge`, etc.) with appropriate `AttachmentSupport` and `AttachmentOffset`. This eliminates the need for custom geometry calculations.
**Goal**: "Flip Side to Cut" becomes a proper feature, not a command wrapper.
**Decision:** Superseded by the AttachExtension approach. Only `tangent_to_cylinder` still uses manual placement (requires a vertex reference not currently collected by the UI).
**Current state** (in ztools):
```python
# ztools/commands/pocket_commands.py
class ZTools_EnhancedPocket:
def Activated(self):
# Creates standard Pocket, then applies boolean Common
# Workaround using existing features
```
### Phase 4: Theme system -- PARTIAL
**Proposed architecture**:
**Goal:** Theme applies consistently at startup regardless of active workbench.
```
src/Mod/Create/
├── App/
│ ├── CreateFeatures.cpp # Feature implementations
│ ├── FeatureFlipPocket.cpp # Flip-side pocket feature
│ └── FeatureFlipPocket.h
├── Gui/
│ ├── Command.cpp # Command registrations
│ ├── TaskFlipPocket.cpp # Task panel
│ └── ViewProviderFlipPocket.cpp
└── CMakeLists.txt
```
**Current state:** The Catppuccin Mocha theme is set as the default via the KindredCreate preference pack. Four copies of the QSS file exist and must be kept in sync manually:
1. `resources/preferences/KindredCreate/KindredCreate.qss` (canonical)
2. `src/Gui/Stylesheets/KindredCreate.qss`
3. `src/Gui/PreferencePacks/KindredCreate/KindredCreate.qss`
4. `mods/ztools/CatppuccinMocha/CatppuccinMocha.qss`
**Feature design** (`Create::FlipPocket`):
**Remaining work:** Eliminate QSS duplication via build-time copy or symlinks. Move theme responsibility out of ztools and into the Create module.
```cpp
// Inherits from PartDesign::ProfileBased (same base as Pocket)
class FeatureFlipPocket : public PartDesign::ProfileBased {
public:
// Properties (same as Pocket, plus flip flag)
App::PropertyLength Length;
App::PropertyEnumeration Type; // Dimension, ThroughAll, ToFirst, UpToFace
App::PropertyBool Symmetric;
App::PropertyBool FlipSide; // NEW: Cut outside instead of inside
// Implementation uses Boolean Common instead of Cut when FlipSide=true
App::DocumentObjectExecReturn* execute();
};
```
### Phase 5: Silo deep integration -- DONE
**Separation of concerns**:
- **FreeCAD Core** (`PartDesign::Pocket`): Standard inside-cut behavior, unchanged
- **Create Extension** (`Create::FlipPocket`): Outside-cut using boolean common
- **ztools Workbench**: Provides UI command that creates `Create::FlipPocket`
**Goal:** Silo commands available globally, not just in the Silo workbench.
**Files to create**:
- `src/Mod/Create/App/FeatureFlipPocket.cpp`
- `src/Mod/Create/App/FeatureFlipPocket.h`
- `src/Mod/Create/Gui/TaskFlipPocket.cpp`
- `src/Mod/Create/Gui/ViewProviderFlipPocket.cpp`
**Implementation:** `SiloMenuManipulator` in `src/Mod/Create/InitGui.py` uses `FreeCADGui.addWorkbenchManipulator()` to inject Silo commands into the File menu and toolbar across all workbenches. `Silo_ToggleMode` provides a one-click swap of Ctrl+O/S/N between standard FreeCAD and Silo file commands.
**ztools update**:
```python
# mods/ztools/ztools/commands/pocket_commands.py
class ZTools_EnhancedPocket:
def Activated(self):
# Now creates Create::FlipPocket instead of workaround
body = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("pdbody")
pocket = body.newObject("Create::FlipPocket", "FlipPocket")
pocket.Profile = sketch
# Show task panel...
```
**Dock panels:** Database Auth (1500ms) and Database Activity (4000ms) panels are created via deferred QTimers and docked in the right panel area.
### Phase 3: Datum System Integration
### Phase 6: Build system integration -- PARTIAL
**Goal**: ztools datum creation uses stable, efficient C++ geometry calculations.
**Goal:** CMake install rules for `mods/` submodules so packages include ztools and Silo automatically.
**Current state**: Pure Python geometry calculations in `ztools/datums/core.py`.
**CI/CD status:** Release workflows (`.gitea/workflows/release.yml`) now build for Linux (AppImage + .deb), macOS (DMG for Intel + Apple Silicon), and Windows (.exe NSIS installer + .7z archive). Builds run on public runners in dockerized mode. Releases are triggered by `v*` tags. See `docs/CI_CD.md` for details.
**Issue**: Python geometry operations can be slow and less precise for complex cases.
**Proposed solution**: Create C++ helper functions, expose via Python.
```cpp
// src/Mod/Create/App/DatumHelpers.cpp
namespace Create {
// Calculate midplane between two parallel faces
gp_Pln computeMidplane(const TopoDS_Face& face1, const TopoDS_Face& face2);
// Calculate plane through three points
gp_Pln computePlaneFrom3Points(gp_Pnt p1, gp_Pnt p2, gp_Pnt p3);
// Calculate axis at cylinder center
gp_Ax1 computeCylinderAxis(const TopoDS_Face& cylinderFace);
// ... other datum calculations ...
}
```
**Python binding**:
```python
# In ztools after Create module is available
from Create import DatumHelpers
plane = DatumHelpers.computeMidplane(face1, face2)
```
**Separation of concerns**:
- **FreeCAD Core** (`PartDesign::DatumPlane`): Data structure, unchanged
- **Create Extension** (`DatumHelpers`): Geometry calculation utilities
- **ztools Workbench**: UI, selection handling, property storage
### Phase 4: Theme System Refinement
**Goal**: Theme applies consistently at startup, no workbench dependency.
**Current state**: Theme applied when ztools workbench activates.
**Issue**: If user opens FreeCAD and doesn't activate ztools, theme isn't applied.
**Proposed solution**:
1. **Move theme to Create module** (`src/Mod/Create/InitGui.py`):
```python
# This runs at GUI startup, before any workbench
def applyKindredTheme():
from PySide import QtWidgets
qss_path = os.path.join(FreeCAD.getResourceDir(),
"preferences", "KindredCreate", "KindredCreate.qss")
with open(qss_path) as f:
QtWidgets.QApplication.instance().setStyleSheet(f.read())
# Apply spreadsheet colors
applySpreadsheetColors()
# Run at import time
applyKindredTheme()
```
2. **Remove theme code from ztools**: ztools focuses on commands, not theming.
3. **Ensure load order**: Create module loads before other workbenches via `src/Mod/CMakeLists.txt` ordering.
### Phase 5: Silo Deep Integration
**Goal**: Silo commands available globally, not just in Silo workbench.
**Current state**: Must switch to Silo workbench to access commands.
**Proposed solution**:
1. **Global menu registration** (`src/Mod/Create/InitGui.py`):
```python
def setupSiloMenu():
# Add Silo menu to menu bar regardless of active workbench
import silo_commands
mw = FreeCADGui.getMainWindow()
menuBar = mw.menuBar()
siloMenu = QtWidgets.QMenu("Silo", mw)
menuBar.addMenu(siloMenu)
for cmd in ["Silo_Open", "Silo_Save", "Silo_Commit", "Silo_Pull", "Silo_Push"]:
action = FreeCADGui.Command.get(cmd).getAction()
siloMenu.addAction(action[0])
```
2. **Keyboard shortcuts** (global):
```python
# Ctrl+Shift+O: Silo Open
# Ctrl+Shift+S: Silo Save
# Ctrl+Shift+C: Silo Commit
```
3. **Status bar integration**: Show current Silo item info in status bar.
### Phase 6: Build System Integration
**Goal**: mods/ submodules installed correctly during build.
**Implementation** (`src/Mod/Create/CMakeLists.txt`):
**Remaining work:** CMake install rules should be formalized in `src/Mod/Create/CMakeLists.txt` so that `cmake --install` includes mods/ submodules without relying on the packaging scripts to copy them:
```cmake
# Install ztools workbench
install(DIRECTORY ${CMAKE_SOURCE_DIR}/mods/ztools/ztools
DESTINATION ${CMAKE_INSTALL_DATADIR}/Mod/ztools)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/mods/ztools/CatppuccinMocha
DESTINATION ${CMAKE_INSTALL_DATADIR}/Mod/ztools)
install(FILES ${CMAKE_SOURCE_DIR}/mods/ztools/package.xml
DESTINATION ${CMAKE_INSTALL_DATADIR}/Mod/ztools)
# Install Silo workbench
install(DIRECTORY ${CMAKE_SOURCE_DIR}/mods/silo/pkg/freecad/
DESTINATION ${CMAKE_INSTALL_DATADIR}/Mod/Silo)
```
## File Organization Summary
---
### New files to create
## Design decisions
```
src/Mod/Create/ # NEW: Kindred Create core extensions
├── CMakeLists.txt
├── Init.py # Addon path setup
├── InitGui.py # Theme application, global menus
├── App/
│ ├── CMakeLists.txt
│ ├── CreateModule.cpp # Module registration
│ ├── FeatureFlipPocket.cpp/h # Flip-side pocket feature
│ └── DatumHelpers.cpp/h # Datum geometry utilities
└── Gui/
├── CMakeLists.txt
├── CreateGuiModule.cpp
├── Command.cpp # Create-specific commands
├── TaskFlipPocket.cpp/h
└── ViewProviderFlipPocket.cpp/h
```
1. **`Create::` namespace prefix.** All Kindred Create features use this prefix to distinguish them from FreeCAD core.
### Files to modify
2. **No upstream contribution.** Kindred Create is a standalone product. This allows divergent design decisions without upstream coordination.
| File | Change |
|------|--------|
| `src/Mod/CMakeLists.txt` | Add `add_subdirectory(Create)` |
| `resources/preferences/KindredCreate/KindredCreate.cfg` | Set default workbench |
| `mods/ztools/ztools/commands/pocket_commands.py` | Use `Create::FlipPocket` |
| `mods/ztools/ztools/datums/core.py` | Use `Create.DatumHelpers` when available |
3. **Silo server distributed separately.** Users deploy the Silo server independently. Setup instructions live in `mods/silo/README.md`.
### Files to remove/deprecate from ztools
| File | Reason |
|------|--------|
| `ztools/resources/theme.py` | Moved to Create module |
| Theme application in `InitGui.py` | Handled globally |
## Implementation Priority
| Priority | Phase | Effort | Impact |
|----------|-------|--------|--------|
| 1 | Phase 1: Addon Auto-Loading | Low | High - Seamless user experience |
| 2 | Phase 4: Theme System | Low | High - Consistent appearance |
| 3 | Phase 5: Silo Global Menu | Medium | High - Always-available database access |
| 4 | Phase 2: Enhanced Pocket | High | Medium - Proper feature architecture |
| 5 | Phase 3: Datum Helpers | Medium | Medium - Performance improvement |
| 6 | Phase 6: Build System | Low | High - Clean distribution |
## Testing Strategy
### Unit Tests
- Create feature creation and execution
- Datum helper calculations
- Theme application verification
### Integration Tests
- Addon auto-loading on fresh install
- Feature creation via ztools commands
- Silo operations with mock server
- Theme persistence across sessions
### Compatibility Tests
- Open existing FCStd files (no regressions)
- Export to STEP/IGES (geometry unchanged)
- Upstream FreeCAD file compatibility
## Migration Notes
### For existing ztools/silo users
- No changes required - workbenches continue to function
- Enhanced features available automatically when Create module present
- Theme applies globally instead of per-workbench
### For developers
- ztools can check for Create module availability:
```python
try:
import Create
HAS_CREATE = True
except ImportError:
HAS_CREATE = False
# Use C++ implementation if available, fall back to Python
if HAS_CREATE:
plane = Create.DatumHelpers.computeMidplane(f1, f2)
else:
plane = compute_midplane_python(f1, f2)
```
## Design Decisions
1. **Naming convention**: Kindred Create features use the `Create::` prefix (e.g., `Create::FlipPocket`, `Create::DatumHelpers`) to clearly identify them as Kindred Create extensions separate from FreeCAD core.
2. **Upstream contribution**: Kindred Create is a standalone product and does not plan to contribute features upstream to FreeCAD. This allows for divergent design decisions optimized for Kindred Create's target use cases.
3. **Silo server distribution**: Silo server is distributed separately from Kindred Create. Users download and deploy the Silo server independently. Setup instructions are documented in `mods/silo/README.md`.
4. **Version synchronization**: ztools and Silo versions are determined by pinned git submodule commits. Updates are deliberate and tested before each Kindred Create release. To update:
4. **Version synchronization via submodule pins.** ztools and Silo versions are pinned git submodule commits. Updates are deliberate:
```bash
cd mods/ztools && git pull origin main && cd ../..
git add mods/ztools && git commit -m "Update ztools to <version>"
git add mods/ztools && git commit -m "Update ztools submodule"
```
5. **Python-first approach.** C++ extensions are deferred until Python cannot achieve the requirement. The AttachExtension approach for datums validated this strategy.
6. **Graceful degradation.** The Create module loads successfully even if submodules are absent. Each addon load is wrapped in try/except with console logging.