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

@@ -10,7 +10,10 @@ on:
jobs:
build:
runs-on: app-builder
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
options: --security-opt seccomp=unconfined
env:
CCACHE_DIR: /tmp/ccache-kindred-create
@@ -19,35 +22,16 @@ jobs:
CCACHE_MAXSIZE: "4G"
CCACHE_SLOPPINESS: "include_file_ctime,include_file_mtime,pch_defines,time_macros"
CCACHE_BASEDIR: ${{ github.workspace }}
DEBIAN_FRONTEND: noninteractive
steps:
- name: Trust FreeIPA CA certificate
shell: bash
- name: Install system prerequisites
run: |
# Download and install the FreeIPA CA certificate for SSL verification
echo "Downloading FreeIPA CA certificate..."
curl -fsSL -o /tmp/ipa-ca.crt https://ipa.kindred.internal/ipa/config/ca.crt
# Verify the certificate is valid (just show subject and dates)
echo "Verifying certificate..."
openssl x509 -in /tmp/ipa-ca.crt -subject -dates -noout
# Set NODE_EXTRA_CA_CERTS for Node.js-based actions (used by upload-artifact)
echo "NODE_EXTRA_CA_CERTS=/tmp/ipa-ca.crt" >> $GITHUB_ENV
# Verify SSL connection to Gitea works with the CA cert
echo "Testing SSL connection to Gitea..."
curl -fsSL --cacert /tmp/ipa-ca.crt https://gitea.kindred.internal/api/v1/version
echo ""
echo "SSL certificate setup complete"
- name: Install node if needed
shell: bash
run: |
if ! command -v node &> /dev/null; then
curl -fsSL https://nodejs.org/dist/v20.18.0/node-v20.18.0-linux-x64.tar.xz | sudo tar -xJ -C /usr/local --strip-components=1
fi
node --version
apt-get update -qq
apt-get install -y --no-install-recommends \
ca-certificates curl git xvfb xauth openssl \
libgl1-mesa-dev libglu1-mesa-dev libx11-dev libxkbcommon-dev \
libxcb-xkb-dev libfontconfig1-dev
- name: Checkout repository
uses: https://github.com/actions/checkout@v4
@@ -55,14 +39,11 @@ jobs:
submodules: recursive
fetch-depth: 0
- name: Install pixi if needed
shell: bash
- name: Install pixi
run: |
if ! command -v pixi &> /dev/null; then
curl -fsSL https://pixi.sh/install.sh | bash
echo "$HOME/.pixi/bin" >> $GITHUB_PATH
export PATH="$HOME/.pixi/bin:$PATH"
fi
curl -fsSL https://pixi.sh/install.sh | bash
echo "$HOME/.pixi/bin" >> $GITHUB_PATH
export PATH="$HOME/.pixi/bin:$PATH"
pixi --version
- name: Restore ccache
@@ -76,32 +57,18 @@ jobs:
ccache-build-main-
- name: Prepare ccache
shell: bash
run: |
mkdir -p $CCACHE_DIR
pixi run ccache -z
echo "=== ccache configuration ==="
pixi run ccache -p
echo ""
echo "=== ccache status before build ==="
pixi run ccache -s
- name: Configure (CMake)
shell: bash
run: |
pixi run cmake --preset conda-linux-release
run: pixi run cmake --preset conda-linux-release
- name: Build
run: pixi run cmake --build build/release -j$(nproc)
- name: Show ccache statistics
shell: bash
run: |
echo "=== ccache statistics after build ==="
pixi run ccache -s
echo ""
echo "=== ccache cache directory size ==="
du -sh $CCACHE_DIR
run: pixi run ccache -s
- name: Save ccache
if: always()
@@ -111,30 +78,18 @@ jobs:
key: ccache-build-${{ github.ref_name }}-${{ github.sha }}
- name: Run C++ unit tests
shell: bash
continue-on-error: true
timeout-minutes: 15
run: |
# Set discovery timeout to prevent hangs during test enumeration
export CTEST_DISCOVERY_TIMEOUT=60
# Use xvfb for tests that require a display (Qt GUI tests)
# Exclude Assembly tests which hang during discovery
pixi run xvfb-run -a ctest --test-dir build/release \
--output-on-failure \
--timeout 120 \
--exclude-regex "Assembly_tests" \
2>&1 || true
echo ""
echo "=== Test Summary ==="
echo "Note: Assembly_tests excluded due to discovery timeout issues"
cat build/release/Testing/Temporary/LastTestsFailed.log 2>/dev/null || echo "All included tests passed (or no failures logged)"
- name: Install
run: |
# Install to a custom prefix for packaging
pixi run cmake --install build/release --prefix build/release/install
run: pixi run cmake --install build/release --prefix build/release/install
- name: Run Python CLI tests
timeout-minutes: 10
@@ -145,79 +100,13 @@ jobs:
run: pixi run timeout 300 xvfb-run -a build/release/bin/FreeCAD -t 0 || true
- name: Package build artifacts
shell: bash
run: |
# Create a distributable tarball from the installed build
ARTIFACT_NAME="kindred-create-$(git describe --tags --always)-linux-x86_64"
echo "Creating artifact: ${ARTIFACT_NAME}"
# The install step puts files in build/release/install
cd build/release
if [ -d "install" ]; then
cp -a install "${ARTIFACT_NAME}"
tar -cJf "${ARTIFACT_NAME}.tar.xz" "${ARTIFACT_NAME}"
sha256sum "${ARTIFACT_NAME}.tar.xz" > "${ARTIFACT_NAME}.tar.xz.sha256"
echo "ARTIFACT_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV
ls -lh "${ARTIFACT_NAME}.tar.xz"
else
echo "ERROR: install directory not found"
ls -la
exit 1
fi
- name: Prepare bundled environment for .deb
shell: bash
run: |
# Create a bundled environment with all dependencies (like AppImage)
# The .deb needs all libraries bundled since we can't rely on system packages
# We need BOTH the pixi environment (dependencies) AND the built FreeCAD
BUNDLE_DIR="build/release/bundle"
mkdir -p "${BUNDLE_DIR}"
echo "Copying pixi environment (dependencies)..."
cp -a .pixi/envs/default/* "${BUNDLE_DIR}/"
echo "After pixi copy: $(du -sh ${BUNDLE_DIR} | cut -f1)"
echo "Copying built FreeCAD into bundle..."
# Copy the built FreeCAD (bin, lib, Mod, share, etc.) over the pixi env
cp -a build/release/install/* "${BUNDLE_DIR}/"
echo "After FreeCAD copy: $(du -sh ${BUNDLE_DIR} | cut -f1)"
# Remove unnecessary files to reduce size significantly
echo "Removing unnecessary files..."
rm -rf "${BUNDLE_DIR}/include"
rm -rf "${BUNDLE_DIR}/conda-meta"
rm -rf "${BUNDLE_DIR}/doc"
rm -rf "${BUNDLE_DIR}/man"
rm -rf "${BUNDLE_DIR}/share/doc"
rm -rf "${BUNDLE_DIR}/share/man"
rm -rf "${BUNDLE_DIR}/share/gtk-doc"
rm -rf "${BUNDLE_DIR}/share/info"
rm -rf "${BUNDLE_DIR}/lib/cmake"
rm -rf "${BUNDLE_DIR}/lib/pkgconfig"
rm -rf "${BUNDLE_DIR}/share/pkgconfig"
# Remove static libraries and dev files
find "${BUNDLE_DIR}" -name "*.a" -delete 2>/dev/null || true
find "${BUNDLE_DIR}" -name "*.la" -delete 2>/dev/null || true
find "${BUNDLE_DIR}" -name "*.cmake" -delete 2>/dev/null || true
find "${BUNDLE_DIR}" -path "*/__pycache__/*" -delete 2>/dev/null || true
find "${BUNDLE_DIR}" -name "*.pyc" -delete 2>/dev/null || true
find "${BUNDLE_DIR}" -name "*.pyo" -delete 2>/dev/null || true
# List key binaries to verify FreeCAD is there
echo "Key binaries in bundle:"
ls -la "${BUNDLE_DIR}/bin/FreeCAD" "${BUNDLE_DIR}/bin/FreeCADCmd" 2>/dev/null || echo "FreeCAD binaries not found!"
echo "Final bundle size: $(du -sh ${BUNDLE_DIR} | cut -f1)"
- name: Build .deb package
shell: bash
run: |
# Build .deb package from bundled environment
# The build-deb.sh script handles version conversion to Debian format
# (e.g., weekly-2025.01.28 -> 0~weekly.2025.01.28)
./package/debian/build-deb.sh build/release/bundle build/release
cp -a install "${ARTIFACT_NAME}"
tar -cJf "${ARTIFACT_NAME}.tar.xz" "${ARTIFACT_NAME}"
sha256sum "${ARTIFACT_NAME}.tar.xz" > "${ARTIFACT_NAME}.tar.xz.sha256"
echo "ARTIFACT_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV
- name: Upload build artifact
uses: https://github.com/actions/upload-artifact@v3
@@ -225,6 +114,5 @@ jobs:
name: ${{ env.ARTIFACT_NAME }}
path: |
build/release/*.tar.xz
build/release/*.deb
build/release/*.sha256
retention-days: 30
retention-days: 14

View File

@@ -3,17 +3,23 @@ name: Release Build
on:
push:
tags: ["v*", "weekly-*"]
tags: ["v*"]
workflow_dispatch:
inputs:
tag:
description: "Release tag (e.g., v1.0.0)"
description: "Release tag (e.g., v0.1.0)"
required: true
type: string
jobs:
build-linux-appimage:
runs-on: app-builder
# ---------------------------------------------------------------------------
# Linux: AppImage + .deb
# ---------------------------------------------------------------------------
build-linux:
runs-on: ubuntu-latest
container:
image: ubuntu:24.04
options: --security-opt seccomp=unconfined
env:
CCACHE_DIR: /tmp/ccache-kindred-create
@@ -23,38 +29,18 @@ jobs:
CCACHE_SLOPPINESS: "include_file_ctime,include_file_mtime,pch_defines,time_macros"
CCACHE_BASEDIR: ${{ github.workspace }}
BUILD_TAG: ${{ github.ref_name || inputs.tag }}
MAKE_INSTALLER: "true"
CFLAGS: "-O3"
CXXFLAGS: "-O3"
DEBIAN_FRONTEND: noninteractive
steps:
- name: Trust FreeIPA CA certificate
shell: bash
- name: Install system prerequisites
run: |
# Download and install the FreeIPA CA certificate for SSL verification
echo "Downloading FreeIPA CA certificate..."
curl -fsSL -o /tmp/ipa-ca.crt https://ipa.kindred.internal/ipa/config/ca.crt
# Verify the certificate is valid (just show subject and dates)
echo "Verifying certificate..."
openssl x509 -in /tmp/ipa-ca.crt -subject -dates -noout
# Set NODE_EXTRA_CA_CERTS for Node.js-based actions (used by upload-artifact)
echo "NODE_EXTRA_CA_CERTS=/tmp/ipa-ca.crt" >> $GITHUB_ENV
# Verify SSL connection to Gitea works with the CA cert
echo "Testing SSL connection to Gitea..."
curl -fsSL --cacert /tmp/ipa-ca.crt https://gitea.kindred.internal/api/v1/version
echo ""
echo "SSL certificate setup complete"
- name: Install node if needed
shell: bash
run: |
if ! command -v node &> /dev/null; then
curl -fsSL https://nodejs.org/dist/v20.18.0/node-v20.18.0-linux-x64.tar.xz | sudo tar -xJ -C /usr/local --strip-components=1
fi
node --version
apt-get update -qq
apt-get install -y --no-install-recommends \
ca-certificates curl git file fuse3 xvfb xauth openssl dpkg-dev \
libgl1-mesa-dev libglu1-mesa-dev libx11-dev libxkbcommon-dev \
libxcb-xkb-dev libfontconfig1-dev
- name: Checkout repository
uses: https://github.com/actions/checkout@v4
@@ -63,14 +49,11 @@ jobs:
fetch-depth: 0
fetch-tags: true
- name: Install pixi if needed
shell: bash
- name: Install pixi
run: |
if ! command -v pixi &> /dev/null; then
curl -fsSL https://pixi.sh/install.sh | bash
echo "$HOME/.pixi/bin" >> $GITHUB_PATH
export PATH="$HOME/.pixi/bin:$PATH"
fi
curl -fsSL https://pixi.sh/install.sh | bash
echo "$HOME/.pixi/bin" >> $GITHUB_PATH
export PATH="$HOME/.pixi/bin:$PATH"
pixi --version
- name: Restore ccache
@@ -78,24 +61,15 @@ jobs:
uses: https://github.com/actions/cache/restore@v4
with:
path: /tmp/ccache-kindred-create
key: ccache-release-${{ github.ref_name }}-${{ github.sha }}
key: ccache-release-linux-${{ github.sha }}
restore-keys: |
ccache-release-
ccache-release-linux-
ccache-build-main-
- name: Prepare ccache
shell: bash
run: |
mkdir -p $CCACHE_DIR
pixi run ccache -z
echo "=== ccache configuration ==="
pixi run ccache -p
echo ""
echo "=== ccache status before build ==="
pixi run ccache -s
- name: Write version info
run: python3 package/scripts/write_version_info.py ../freecad_version.txt
- name: Build release package (AppImage)
working-directory: package/rattler-build
@@ -104,54 +78,267 @@ jobs:
pixi run -e package create_bundle
- name: Show ccache statistics
shell: bash
run: |
echo "=== ccache statistics after build ==="
pixi run ccache -s
echo ""
echo "=== ccache cache directory size ==="
du -sh $CCACHE_DIR
run: pixi run ccache -s
- name: Save ccache
if: always()
uses: https://github.com/actions/cache/save@v4
with:
path: /tmp/ccache-kindred-create
key: ccache-release-${{ github.ref_name }}-${{ github.sha }}
key: ccache-release-linux-${{ github.sha }}
- name: Build .deb package
shell: bash
run: |
# Build .deb package from the AppDir used for AppImage
# The build-deb.sh script handles version conversion to Debian format
# (e.g., weekly-2025.01.28 -> 0~weekly.2025.01.28)
./package/debian/build-deb.sh \
package/rattler-build/linux/AppDir/usr \
package/rattler-build/linux \
"${BUILD_TAG}"
- name: List built artifacts
shell: bash
run: |
echo "Built artifacts:"
ls -lah package/rattler-build/linux/*.AppImage* 2>/dev/null || echo "No AppImage files found"
ls -lah package/rattler-build/linux/*.deb* 2>/dev/null || echo "No .deb files found"
ls -lah package/rattler-build/linux/*-SHA256.txt 2>/dev/null || echo "No SHA256 files found"
echo "=== Linux release artifacts ==="
ls -lah package/rattler-build/linux/*.AppImage* 2>/dev/null || true
ls -lah package/rattler-build/linux/*.deb* 2>/dev/null || true
ls -lah package/rattler-build/linux/*-SHA256.txt 2>/dev/null || true
- name: Upload AppImage artifact
- name: Upload Linux artifacts
uses: https://github.com/actions/upload-artifact@v3
with:
name: kindred-create-appimage
name: release-linux
path: |
package/rattler-build/linux/*.AppImage
package/rattler-build/linux/*.deb
package/rattler-build/linux/*-SHA256.txt
package/rattler-build/linux/*.sha256
if-no-files-found: error
- name: Upload .deb artifact
# ---------------------------------------------------------------------------
# macOS: DMG (Intel + Apple Silicon)
# ---------------------------------------------------------------------------
build-macos:
strategy:
matrix:
include:
- runner: macos-13
arch: x86_64
preset: conda-macos-release
- runner: macos-14
arch: arm64
preset: conda-macos-release
runs-on: ${{ matrix.runner }}
env:
CCACHE_DIR: /tmp/ccache-kindred-create
CCACHE_COMPRESS: "true"
CCACHE_COMPRESSLEVEL: "6"
CCACHE_MAXSIZE: "4G"
CCACHE_SLOPPINESS: "include_file_ctime,include_file_mtime,pch_defines,time_macros"
CCACHE_BASEDIR: ${{ github.workspace }}
BUILD_TAG: ${{ github.ref_name || inputs.tag }}
CFLAGS: "-O3"
CXXFLAGS: "-O3"
steps:
- name: Checkout repository
uses: https://github.com/actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
fetch-tags: true
- name: Install pixi
run: |
curl -fsSL https://pixi.sh/install.sh | bash
echo "$HOME/.pixi/bin" >> $GITHUB_PATH
export PATH="$HOME/.pixi/bin:$PATH"
pixi --version
- name: Restore ccache
id: ccache-restore
uses: https://github.com/actions/cache/restore@v4
with:
path: /tmp/ccache-kindred-create
key: ccache-release-macos-${{ matrix.arch }}-${{ github.sha }}
restore-keys: |
ccache-release-macos-${{ matrix.arch }}-
- name: Prepare ccache
run: |
mkdir -p $CCACHE_DIR
pixi run ccache -z
- name: Build release package (DMG)
working-directory: package/rattler-build
run: |
pixi install
pixi run -e package create_bundle
- name: Show ccache statistics
run: pixi run ccache -s
- name: Save ccache
if: always()
uses: https://github.com/actions/cache/save@v4
with:
path: /tmp/ccache-kindred-create
key: ccache-release-macos-${{ matrix.arch }}-${{ github.sha }}
- name: List built artifacts
run: |
echo "=== macOS ${{ matrix.arch }} release artifacts ==="
ls -lah package/rattler-build/osx/*.dmg* 2>/dev/null || true
- name: Upload macOS artifacts
uses: https://github.com/actions/upload-artifact@v3
with:
name: kindred-create-deb
name: release-macos-${{ matrix.arch }}
path: |
package/rattler-build/linux/*.deb
package/rattler-build/linux/*.deb.sha256
if-no-files-found: warn
package/rattler-build/osx/*.dmg
package/rattler-build/osx/*-SHA256.txt
if-no-files-found: error
# ---------------------------------------------------------------------------
# Windows: .exe installer + .7z archive
# ---------------------------------------------------------------------------
build-windows:
runs-on: windows-latest
env:
CCACHE_DIR: C:\ccache-kindred-create
CCACHE_COMPRESS: "true"
CCACHE_COMPRESSLEVEL: "6"
CCACHE_MAXSIZE: "4G"
CCACHE_SLOPPINESS: "include_file_ctime,include_file_mtime,pch_defines,time_macros"
CCACHE_BASEDIR: ${{ github.workspace }}
BUILD_TAG: ${{ github.ref_name || inputs.tag }}
CFLAGS: "/O2"
CXXFLAGS: "/O2"
MAKE_INSTALLER: "true"
steps:
- name: Checkout repository
uses: https://github.com/actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
fetch-tags: true
- name: Install pixi
shell: bash
run: |
curl -fsSL https://pixi.sh/install.sh | bash
echo "$HOME/.pixi/bin" >> $GITHUB_PATH
export PATH="$HOME/.pixi/bin:$PATH"
pixi --version
- name: Restore ccache
id: ccache-restore
uses: https://github.com/actions/cache/restore@v4
with:
path: C:\ccache-kindred-create
key: ccache-release-windows-${{ github.sha }}
restore-keys: |
ccache-release-windows-
- name: Build release package
shell: bash
working-directory: package/rattler-build
run: |
pixi install
pixi run -e package create_bundle
- name: Build NSIS installer
shell: bash
working-directory: package/rattler-build/windows
run: |
if [ "${MAKE_INSTALLER}" == "true" ]; then
# The create_bundle.sh script builds the 7z archive.
# NSIS installer is built as part of the bundle script when
# MAKE_INSTALLER is set and NSIS is available.
echo "Installer build completed as part of create_bundle"
fi
- name: Save ccache
if: always()
uses: https://github.com/actions/cache/save@v4
with:
path: C:\ccache-kindred-create
key: ccache-release-windows-${{ github.sha }}
- name: List built artifacts
shell: bash
run: |
echo "=== Windows release artifacts ==="
ls -lah package/rattler-build/windows/*.7z* 2>/dev/null || true
ls -lah package/rattler-build/windows/*.exe 2>/dev/null || true
ls -lah package/rattler-build/windows/*-SHA256.txt 2>/dev/null || true
- name: Upload Windows artifacts
uses: https://github.com/actions/upload-artifact@v3
with:
name: release-windows
path: |
package/rattler-build/windows/*.7z
package/rattler-build/windows/*.exe
package/rattler-build/windows/*-SHA256.txt
if-no-files-found: error
# ---------------------------------------------------------------------------
# Create Gitea release from all platform artifacts
# ---------------------------------------------------------------------------
publish-release:
needs: [build-linux, build-macos, build-windows]
runs-on: ubuntu-latest
env:
BUILD_TAG: ${{ github.ref_name || inputs.tag }}
steps:
- name: Download all artifacts
uses: https://github.com/actions/download-artifact@v3
with:
path: artifacts
- name: List all release artifacts
run: |
echo "=== All release artifacts ==="
find artifacts -type f | sort
- name: Collect release files
run: |
mkdir -p release
find artifacts -type f \( \
-name "*.AppImage" -o \
-name "*.deb" -o \
-name "*.dmg" -o \
-name "*.7z" -o \
-name "*.exe" -o \
-name "*SHA256*" -o \
-name "*.sha256" \
\) -exec cp {} release/ \;
echo "=== Release files ==="
ls -lah release/
- name: Create release
uses: https://gitea.com/actions/release-action@main
with:
files: release/*
title: "Kindred Create ${{ env.BUILD_TAG }}"
body: |
## Kindred Create ${{ env.BUILD_TAG }}
### Downloads
| Platform | File |
|----------|------|
| Linux (AppImage) | `KindredCreate-*-Linux-x86_64.AppImage` |
| Linux (Debian/Ubuntu) | `kindred-create_*.deb` |
| macOS (Intel) | `KindredCreate-*-macOS-x86_64.dmg` |
| macOS (Apple Silicon) | `KindredCreate-*-macOS-arm64.dmg` |
| Windows (Installer) | `KindredCreate-*-Windows-x86_64-installer.exe` |
| Windows (Portable) | `KindredCreate-*-Windows-x86_64.7z` |
SHA256 checksums are provided alongside each artifact.
prerelease: ${{ contains(github.ref_name, 'rc') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'alpha') }}
api_key: ${{ secrets.RELEASE_TOKEN }}

6
.gitmodules vendored
View File

@@ -1,6 +1,6 @@
[submodule "src/3rdParty/OndselSolver"]
path = src/3rdParty/OndselSolver
url = https://gitea.kindred.internal/kindred/ondsel.git
url = https://git.kindred-systems.com/kindred/solver.git
[submodule "tests/lib"]
path = tests/lib
url = https://github.com/google/googletest
@@ -12,7 +12,7 @@
url = https://github.com/FreeCAD/AddonManager.git
[submodule "mods/ztools"]
path = mods/ztools
url = https://gitea.kindred.internal/kindred/ztools-0065.git
url = https://git.kindred-systems.com/forbes/ztools.git
[submodule "mods/silo"]
path = mods/silo
url = https://gitea.kindred.internal/kindred/silo-0062.git
url = https://git.kindred-systems.com/kindred/silo.git

290
README.md
View File

@@ -2,179 +2,50 @@
**An engineering-focused parametric 3D CAD platform built on FreeCAD 1.0+**
[Website](https://www.kindred-systems.com/create)
[FreeCAD Documentation](https://wiki.freecad.org)
[FreeCAD Forum](https://forum.freecad.org/)
[Website](https://www.kindred-systems.com/create) |
[Downloads](https://git.kindred-systems.com/kindred/create/releases) |
[Issue Tracker](https://git.kindred-systems.com/kindred/create/issues)
> **MVP Release** - Kindred Create is currently in active development. Features and interfaces may change.
> Kindred Create is in active development. Features and interfaces may change.
---
## Overview
## What is Kindred Create?
Kindred Create is a fork of FreeCAD 1.0+ that integrates custom workbenches and tooling for professional engineering workflows. It combines the power of FreeCAD's parametric modeling with purpose-built extensions for part design, assembly management, and parts database integration.
Kindred Create is a fork of [FreeCAD](https://www.freecad.org) that adds integrated tooling for professional engineering workflows. It ships two custom workbenches and a purpose-built dark theme on top of FreeCAD's parametric modeling core.
**Key additions over base FreeCAD:**
**ztools** -- A unified workbench that consolidates part design, assembly, and sketcher tools into a single interface. Adds custom datum creation (planes, axes, points with 16 creation modes), pattern tools for assemblies, an enhanced pocket with flip-side cutting, and spreadsheet formatting commands.
- **ztools Workbench** - Unified interface consolidating part design, assembly, and drawing tools with custom engineering features
- **Silo Integration** - Parts database system for tracking, versioning, and managing CAD files across teams, with an interactive browsing panel for searching, opening, and inspecting parts directly from the dock
- **Catppuccin Mocha Theme** - Dark theme with spanning tree branch indicators, optimized for extended work sessions
**Silo** -- A parts database system for managing CAD files, part numbers, revisions, and bills of materials across teams. Includes a Go REST API server backed by PostgreSQL and MinIO, with FreeCAD commands for opening, saving, and syncing files directly from the application. A dock panel provides in-viewport browsing and search.
**Catppuccin Mocha theme** -- A dark theme applied across the entire application, including the 3D viewport, sketch editor, spreadsheet view, and tree view. Uses spanning-line branch indicators instead of disclosure arrows.
Kindred Create is maintained by [Kindred Systems LLC](https://www.kindred-systems.com).
---
## Features
### ztools Workbench
The ztools workbench provides a unified interface that consolidates functionality from multiple FreeCAD workbenches while adding custom engineering-oriented tools.
#### Consolidated Toolbars (17 total)
ztools exposes commands from PartDesign, Sketcher, Assembly, and Spreadsheet workbenches through a single unified interface:
- **Structure & Sketcher Tools** - Body/Sketch creation, editing, mapping, validation
- **PartDesign Datums** - Planes, lines, points, coordinate systems, binders, clones
- **Additive Features** - Pad, revolution, loft, pipe, helix, and primitives
- **Subtractive Features** - Pocket, hole, groove, loft, pipe, helix, and primitives
- **Transformations** - Mirror, linear pattern, polar pattern, multi-transform
- **Dress-Up Features** - Fillet, chamfer, draft, thickness
- **Boolean Operations** - Union, intersection, difference
- **Assembly Tools** - Create assembly, insert link, insert new part
- **Assembly Joints** - 13 joint types (fixed, revolute, cylindrical, slider, ball, distance, parallel, perpendicular, angle, rack-pinion, screw, gears, belt)
- **Assembly Management** - Toggle grounded, solve, create view, create BOM, export
- **Spreadsheet Tools** - Create, import, export, aliases, merge/split cells
#### Custom Datum Tools
ztools includes a custom datum creation system with 16 creation modes, using a purpose-built attachment system that stores geometry references for reliable parametric updates.
**Datum Planes (7 modes):**
- Offset from Face - Distance offset perpendicular to a face
- Offset from Plane - Distance offset from an existing datum plane
- Midplane - Plane equidistant between two parallel faces
- 3 Points - Plane defined by three vertices
- Normal to Edge - Plane perpendicular to an edge at a specified parameter
- Angled - Plane rotated about an edge by a specified angle
- Tangent to Cylinder - Plane tangent to a cylindrical surface
**Datum Axes (4 modes):**
- 2 Points - Axis through two vertices
- From Edge - Axis aligned with a linear edge
- Cylinder Center - Axis at the center of a cylindrical face
- Plane Intersection - Axis at the intersection of two planes
**Datum Points (5 modes):**
- At Vertex - Point at a selected vertex
- XYZ Coordinates - Point at absolute coordinates
- On Edge - Point at a parameter along an edge
- Face Center - Point at the centroid of a face
- Circle Center - Point at the center of a circular edge
#### Custom Pattern Tools
- **Rotated Linear Pattern** - Linear pattern with incremental rotation per instance
- **Assembly Linear Pattern** - Pattern assembly components linearly with spacing control
- **Assembly Polar Pattern** - Pattern assembly components around a rotation axis
#### Enhanced Pocket
SOLIDWORKS-style "Flip Side to Cut" feature that removes material outside the sketch profile instead of inside, using boolean operations internally.
#### Spreadsheet Formatting Tools
Dark-theme compatible formatting commands for spreadsheets:
- Bold, italic, underline toggles
- Left, center, right alignment
- Background and text color pickers
- Quick alias creation from cell labels
### Silo Parts Database
Silo is a parts database system designed for managing CAD files, part numbers, and revision history across engineering teams.
**Architecture:**
- **Server Daemon** - Go-based REST API server
- **PostgreSQL** - Relational data storage for part metadata, relationships, and revision history
- **MinIO** - S3-compatible object storage for CAD file versioning
- **FreeCAD Workbench** - Integrated commands for check-in, check-out, and synchronization
**Core Capabilities:**
- Configurable part numbering schemas defined in YAML
- Full revision history with status tracking (draft, review, released, obsolete)
- Bill of materials management with relationship types and quantities
- Project-based organization and tagging
- CSV import/export for bulk operations
- Web interface for browsing and searching parts
**FreeCAD Integration:**
- Open, save, and commit files directly to Silo
- Pull latest revisions and push changes
- View item metadata and revision history
- **Database Activity Panel** - Interactive dock panel for browsing, searching, and opening parts without leaving the 3D viewport. Includes search filtering, type filtering (part/assembly), a details pane showing metadata (part number, description, type, revision, projects), and direct open/info actions.
Silo documentation is available in the `mods/silo/` directory.
### Catppuccin Mocha Theme
A comprehensive dark theme using the [Catppuccin Mocha](https://github.com/catppuccin/catppuccin) color palette, applied across:
- Main window, toolbars, and menus
- 3D viewport backgrounds and selection colors
- Sketch editor colors for edges, vertices, and constraints
- Python console and code editor syntax highlighting
- Spreadsheet backgrounds and text
- Property editor and tree view
- All dialog and widget styling
- Tree view branch indicators using spanning lines (`├`, `└`, `│`) instead of disclosure arrows
The theme is automatically applied when using Kindred Create.
---
## Installing
### Prebuilt Binaries
### Prebuilt packages (Linux)
Prebuilt packages for Linux are available on the [releases page](https://git.kindred-systems.com/kindred/create/releases).
Download from the [releases page](https://git.kindred-systems.com/kindred/create/releases).
#### Debian/Ubuntu (.deb)
Download the `.deb` package from the releases page and install with:
```bash
sudo dpkg -i kindred-create_*.deb
sudo apt-get install -f # Install any missing dependencies
```
Or install directly with apt:
**Debian/Ubuntu:**
```bash
sudo apt install ./kindred-create_*.deb
```
#### AppImage
Download the `.AppImage` file from the releases page, make it executable, and run:
**AppImage:**
```bash
chmod +x KindredCreate-*.AppImage
./KindredCreate-*.AppImage
```
After installation, launch Kindred Create from your application menu or run `kindred-create` from the terminal.
### Building from source
### Building from Source
Kindred Create uses [pixi](https://pixi.sh) for dependency management and CMake for building. Pixi handles all build dependencies automatically via conda packages.
#### Prerequisites
- [pixi](https://pixi.sh) (conda-based package manager)
- Git with submodule support
#### Build Steps
Kindred Create uses [pixi](https://pixi.sh) for dependency management and CMake for building.
```bash
git clone --recursive ssh://git@git.kindred-systems.com:2222/kindred/create.git
@@ -182,91 +53,61 @@ cd create
pixi run configure
pixi run build
pixi run install
```
To launch the built application:
```bash
pixi run freecad
```
#### Platform Support
Debug and release variants are available (`pixi run build-debug`, `pixi run build-release`). See `CMakePresets.json` for platform-specific presets covering Linux, macOS, and Windows.
CMake presets are available for:
- **Linux** (x86-64, aarch64) - Clang compiler
- **macOS** (Intel, Apple Silicon) - Clang compiler
- **Windows** - MSVC compiler
See `CMakePresets.json` for platform-specific configuration. For general FreeCAD build guidance:
- [Linux](https://wiki.freecad.org/Compile_on_Linux)
- [Windows](https://wiki.freecad.org/Compile_on_Windows)
- [macOS](https://wiki.freecad.org/Compile_on_MacOS)
For general FreeCAD compilation guidance, see the FreeCAD wiki for [Linux](https://wiki.freecad.org/Compile_on_Linux), [Windows](https://wiki.freecad.org/Compile_on_Windows), or [macOS](https://wiki.freecad.org/Compile_on_MacOS).
---
## Usage
### Getting Started
Kindred Create is compatible with standard FreeCAD workflows. The [FreeCAD wiki](https://wiki.freecad.org/Getting_started) covers general usage.
Kindred Create is compatible with standard FreeCAD workflows. The FreeCAD wiki provides documentation on general usage:
### ztools
- [Getting started](https://wiki.freecad.org/Getting_started)
- [Features list](https://wiki.freecad.org/Feature_list)
- [Workbenches](https://wiki.freecad.org/Workbenches)
- [Scripting](https://wiki.freecad.org/Power_users_hub)
Select the ztools workbench from the workbench selector. It presents consolidated toolbars from PartDesign, Sketcher, Assembly, and Spreadsheet in a single interface. ZTools commands are also injected into the PartDesign workbench menus and toolbars automatically.
### ztools Workbench
See `mods/ztools/` for workbench-specific documentation.
The ztools workbench is available from the workbench selector. On activation, it presents consolidated toolbars and applies the Catppuccin Mocha theme to spreadsheets.
### Silo
### Silo Setup
Silo requires a running server instance. See `mods/silo/README.md` for server deployment instructions.
Silo requires a running server instance with PostgreSQL and MinIO. See `mods/silo/README.md` for server deployment instructions.
The FreeCAD workbench reads configuration from:
- `SILO_API_URL` -- Server API endpoint (default: `http://localhost:8080/api`)
- `SILO_PROJECTS_DIR` -- Local projects directory (default: `~/projects`)
Configure the FreeCAD workbench with environment variables:
- `SILO_API_URL` - Silo server API endpoint (default: `http://localhost:8080/api`)
- `SILO_PROJECTS_DIR` - Local projects directory (default: `~/projects`)
On first launch, Kindred Create prompts for Silo server configuration. Silo commands (Open, Save, Commit, Pull, Push, BOM) are available in the File menu across all workbenches.
---
## Project Structure
## Project structure
```
kindred-create/
├── src/ # FreeCAD core source
│ ├── App/ # Core application logic
│ ├── Base/ # Base classes and utilities
│ ├── Gui/ # GUI framework
│ └── Stylesheets/ # Theme stylesheets and assets
├── Mod/ # FreeCAD modules (PartDesign, Assembly, Sketcher, etc.)
│ │ └── Create/ # Kindred Create module (loads addon workbenches)
create/
├── src/
│ ├── App/ # Core application (C++)
│ ├── Base/ # Base classes (C++)
│ ├── Gui/ # GUI framework and stylesheets (C++)
├── Mod/ # FreeCAD modules (PartDesign, Assembly, Sketcher, ...)
│ └── Create/ # Kindred bootstrap module -- loads ztools and Silo
│ └── 3rdParty/ # Vendored dependencies (OndselSolver, GSL)
├── mods/ # Kindred Create addon modules (git submodules)
├── mods/ # Kindred addon workbenches (git submodules)
│ ├── ztools/ # ztools workbench
│ │ ├── ztools/ # Workbench package
│ │ │ ├── InitGui.py # Workbench registration
│ │ │ └── ztools/ # Commands and resources
│ │ └── CatppuccinMocha/ # Theme preference pack
│ └── silo/ # Silo parts database
│ ├── cmd/ # Go server entry points
│ ├── internal/ # Go API, database, storage packages
│ ├── pkg/freecad/ # FreeCAD workbench (Python)
│ ├── deployments/ # Docker configuration
│ └── migrations/ # PostgreSQL migrations
├── resources/ # Branding and preferences
│ ├── branding/ # Splash screens and logos
│ ├── preferences/ # Default preference packs
│ └── icons/ # Application icons (16x16 to 512x512)
├── package/ # Packaging (debian, AppImage)
├── .gitea/workflows/ # CI/CD workflows (build, release)
├── CMakeLists.txt # Root CMake configuration
├── CMakePresets.json # Platform-specific CMake presets
└── pixi.toml # Pixi environment and dependency definition
├── resources/preferences/ # Default preferences and theme
├── package/ # Packaging (Debian, AppImage, Windows installer, RPM)
├── .gitea/workflows/ # CI/CD (build and release pipelines)
├── docs/ # Architecture and integration docs
├── CMakeLists.txt # Root build configuration
├── CMakePresets.json # Platform build presets
└── pixi.toml # Pixi environment and tasks
```
The `mods/ztools` and `mods/silo` directories are git submodules. After cloning, initialize them with:
The `mods/` workbenches are git submodules. If you cloned without `--recursive`, initialize them with:
```bash
git submodule update --init --recursive
@@ -274,34 +115,37 @@ git submodule update --init --recursive
---
## Reporting Issues
## Contributing
Report issues at [git.kindred-systems.com/kindred/create/issues](https://git.kindred-systems.com/kindred/create/issues).
Contributions follow the [FreeCAD Contribution Process](CONTRIBUTING.md) (FCP). Submit changes as pull requests against the `main` branch.
When reporting:
Code quality tools are configured and enforced via pre-commit hooks:
- **C/C++**: clang-format, clang-tidy
- **Python**: black (100-char lines), pylint
1. Specify whether the issue is with Kindred Create additions (ztools, Silo, theme) or base FreeCAD functionality
2. Include version info from `Help > About FreeCAD > Copy to clipboard`
3. Provide steps to reproduce the issue
4. Attach example files if applicable (FCStd as ZIP)
Install pre-commit hooks locally:
For base FreeCAD issues, consider also checking the [FreeCAD issue tracker](https://github.com/FreeCAD/FreeCAD/issues).
```bash
pip install pre-commit
pre-commit install
```
## Reporting issues
Report issues at the [issue tracker](https://git.kindred-systems.com/kindred/create/issues). When reporting:
1. Note whether the issue involves Kindred Create additions (ztools, Silo, theme) or base FreeCAD
2. Include version info from **Help > About FreeCAD > Copy to clipboard**
3. Provide reproduction steps and attach example files (FCStd as ZIP) if applicable
For base FreeCAD issues, also check the [FreeCAD issue tracker](https://github.com/FreeCAD/FreeCAD/issues).
---
## License
Kindred Create is licensed under the [LGPL-2.1-or-later](LICENSE) license, consistent with FreeCAD.
---
[LGPL-2.1-or-later](LICENSE), consistent with FreeCAD.
## Acknowledgments
Kindred Create is built on [FreeCAD](https://www.freecad.org), an open-source parametric 3D modeler. We acknowledge the FreeCAD community and contributors for their foundational work.
**Underlying Technology:**
- [OpenCASCADE](https://www.opencascade.com/) - Geometry kernel
- [Coin3D](https://github.com/coin3d/coin) - 3D scene representation
- [Qt](https://www.qt.io/) - GUI framework
- [Python](https://www.python.org/) - Scripting and workbench development
- [Catppuccin](https://github.com/catppuccin/catppuccin) - Color palette
Built on [FreeCAD](https://www.freecad.org) and its ecosystem: [OpenCASCADE](https://www.opencascade.com/), [Coin3D](https://github.com/coin3d/coin), [Qt](https://www.qt.io/), [Python](https://www.python.org/). Theme colors from [Catppuccin](https://github.com/catppuccin/catppuccin).

273
docs/CI_CD.md Normal file
View File

@@ -0,0 +1,273 @@
# CI/CD
Kindred Create uses Gitea Actions for continuous integration and release builds. Workflows are defined in `.gitea/workflows/`.
## Overview
| Workflow | Trigger | Purpose | Artifacts |
|----------|---------|---------|-----------|
| `build.yml` | Push to `main`, pull requests | Build + test | Linux tarball |
| `release.yml` | Tags matching `v*` | Multi-platform release | AppImage, .deb, .dmg, .exe, .7z |
All builds run on public runners in dockerized mode. No host-mode or internal infrastructure is required.
---
## Build workflow (`build.yml`)
Runs on every push to `main` and on pull requests. Builds the project in an Ubuntu 24.04 container and runs the test suite.
### Steps
1. Install system prerequisites (GL, X11, fontconfig headers)
2. Checkout with recursive submodules
3. Install pixi
4. Restore ccache from prior builds
5. Configure via `pixi run cmake --preset conda-linux-release`
6. Build with `pixi run cmake --build build/release -j$(nproc)`
7. Run C++ unit tests under xvfb (Assembly_tests excluded due to discovery timeouts)
8. Install to `build/release/install`
9. Run Python CLI tests (`FreeCADCmd -t 0`)
10. Run GUI tests headless (`xvfb-run FreeCAD -t 0`)
11. Package as `.tar.xz` with SHA256 checksum
12. Upload artifact (14-day retention)
### Caching
ccache is persisted between builds using `actions/cache`. Cache keys are scoped by branch and commit SHA, with fallback to the branch key then `main`.
```
Key: ccache-build-{branch}-{sha}
Fallback: ccache-build-{branch}-
Fallback: ccache-build-main-
```
ccache configuration: 4 GB max, zlib compression level 6, sloppy mode for include timestamps and PCH.
---
## Release workflow (`release.yml`)
Triggered by pushing a tag matching `v*` (e.g., `v0.1.0`, `v1.0.0-rc1`). Builds release packages for all platforms in parallel, then creates a Gitea release with all artifacts.
### Triggering a release
```bash
git tag v0.1.0
git push origin v0.1.0
```
Or manually via `workflow_dispatch` with a tag input.
Tags containing `rc`, `beta`, or `alpha` are marked as pre-releases.
### Platform matrix
| Job | Runner | Container | Preset | Output |
|-----|--------|-----------|--------|--------|
| `build-linux` | `ubuntu-latest` | `ubuntu:24.04` | `conda-linux-release` | AppImage, .deb |
| `build-macos` (Intel) | `macos-13` | native | `conda-macos-release` | .dmg (x86_64) |
| `build-macos` (Apple Silicon) | `macos-14` | native | `conda-macos-release` | .dmg (arm64) |
| `build-windows` | `windows-latest` | native | `conda-windows-release` | .exe (NSIS), .7z |
All four jobs run concurrently. After all succeed, `publish-release` collects artifacts and creates the Gitea release.
### Linux build
Uses the rattler-build packaging pipeline:
1. `pixi install` in `package/rattler-build/`
2. `pixi run -e package create_bundle` -- invokes `linux/create_bundle.sh`
3. The bundle script:
- Copies the pixi conda environment to an AppDir
- Strips unnecessary files (includes, static libs, cmake files, `__pycache__`)
- Downloads `appimagetool` and creates a squashfs AppImage (zstd compressed)
- Generates SHA256 checksums
4. `package/debian/build-deb.sh` builds a .deb from the AppDir
- Installs to `/opt/kindred-create/` with wrapper scripts in `/usr/bin/`
- Sets up LD_LIBRARY_PATH, QT_PLUGIN_PATH, PYTHONPATH in wrappers
- Creates desktop entry, MIME types, AppStream metainfo
### macOS build
Builds natively on macOS runners (Intel via `macos-13`, Apple Silicon via `macos-14`):
1. `pixi run -e package create_bundle` invokes `osx/create_bundle.sh`
2. The bundle script:
- Creates `FreeCAD.app` bundle with conda environment in `Contents/Resources/`
- Runs `fix_macos_lib_paths.py` to convert absolute rpaths to `@loader_path`
- Builds native macOS launcher from `launcher/CMakeLists.txt`
- Patches `Info.plist` with version info
- Creates DMG via `dmgbuild`
- If `SIGN_RELEASE=true`: signs and notarizes via `macos_sign_and_notarize.zsh`
### Windows build
Builds natively on Windows runner:
1. `pixi run -e package create_bundle` invokes `windows/create_bundle.sh` (bash via Git for Windows)
2. The bundle script:
- Copies conda environment DLLs, Python, and FreeCAD binaries to `FreeCAD_Windows/`
- Applies SSL certificate patch (`ssl-patch.py`)
- Creates `qt6.conf` for Qt6 plugin paths
- Creates `.exe` wrapper shims via Chocolatey `shimgen`
- Compresses to `.7z` (compression level 9)
- If `MAKE_INSTALLER=true` and NSIS available: builds NSIS installer
3. NSIS installer (`package/WindowsInstaller/`):
- Multi-language support (28 languages)
- File associations for `.FCStd`
- Start menu and desktop shortcuts
- LZMA compression
### Publish step
`publish-release` runs after all platform builds succeed:
1. Downloads all artifacts from `build-linux`, `build-macos`, `build-windows`
2. Collects release files (AppImage, .deb, .dmg, .7z, .exe, checksums)
3. Creates a Gitea release via `gitea.com/actions/release-action`
4. Requires `RELEASE_TOKEN` secret with repository write permissions
---
## Runner configuration
### Public runner setup
Workflows target public Gitea-compatible runners. The Linux build job runs inside a Docker container (`ubuntu:24.04`) for isolation and reproducibility. macOS and Windows jobs run directly on hosted runners.
**Required runner labels:**
- `ubuntu-latest` -- Linux builds (dockerized)
- `macos-13` -- macOS Intel builds
- `macos-14` -- macOS Apple Silicon builds
- `windows-latest` -- Windows builds
### Registering a self-hosted runner
If using self-hosted runners instead of hosted:
```bash
# Download the Gitea runner binary
curl -fsSL -o act_runner https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64
chmod +x act_runner
# Register with your Gitea instance
./act_runner register \
--instance https://git.kindred-systems.com \
--token <RUNNER_REGISTRATION_TOKEN> \
--labels ubuntu-latest:docker://ubuntu:24.04
# Start the runner
./act_runner daemon
```
For dockerized mode, the runner executes jobs inside containers. Ensure Docker is installed on the runner host.
The runner config file (`config.yaml`) should set:
```yaml
runner:
labels:
- "ubuntu-latest:docker://ubuntu:24.04"
container:
privileged: false
network: bridge
```
---
## Secrets
| Secret | Used by | Purpose |
|--------|---------|---------|
| `RELEASE_TOKEN` | `release.yml` (publish) | Gitea API token for creating releases |
| `SIGNING_KEY_ID` | macOS build (optional) | Apple Developer signing identity |
---
## Build tools
### pixi
[pixi](https://pixi.sh) manages all build dependencies via conda-forge. It ensures consistent toolchains (clang, cmake, Qt6, OpenCASCADE, etc.) across all platforms without relying on system packages.
Key pixi tasks (defined in root `pixi.toml`):
| Task | Description |
|------|-------------|
| `pixi run configure` | CMake configure (defaults to debug) |
| `pixi run build` | CMake build (defaults to debug) |
| `pixi run install` | CMake install |
| `pixi run test` | Run ctest |
| `pixi run freecad` | Launch built FreeCAD |
| `pixi run build-release` | CMake build (release) |
### CMake presets
Defined in `CMakePresets.json`. Release builds use:
| Platform | Preset | Compiler | Linker |
|----------|--------|----------|--------|
| Linux | `conda-linux-release` | clang/clang++ | mold |
| macOS | `conda-macos-release` | clang/clang++ | default |
| Windows | `conda-windows-release` | MSVC | default |
### ccache
Compiler cache is used across all builds to speed up incremental compilation. Cache is persisted between CI runs via `actions/cache`. Configuration:
- Max size: 4 GB
- Compression: zlib level 6
- Sloppy mode: include timestamps, PCH defines, time macros
---
## Adding a new platform or package format
1. Create a bundle script at `package/rattler-build/<platform>/create_bundle.sh`
2. Add the platform to `package/rattler-build/pixi.toml` if needed
3. Add a new job to `release.yml` following the existing pattern
4. Add the new artifact pattern to the `publish-release` job's `find` command
5. Update the release body template with the new download entry
---
## Troubleshooting
### Build fails with missing system libraries
The Docker container installs only minimal dependencies. If a new dependency is needed, add it to the `apt-get install` step in the workflow. Check the pixi environment first -- most dependencies should come from conda-forge, not system packages.
### ccache misses are high
ccache misses spike when:
- The compiler version changes (pixi update)
- CMake presets change configuration flags
- The cache key doesn't match (new branch, force-pushed SHA)
Check `pixi run ccache -s` output for hit/miss ratios.
### Submodule checkout fails
Submodules pointing to internal Gitea instances require either:
- Public mirrors of the submodule repositories
- Runner network access to the Gitea instance
- Submodule URLs updated to public-facing addresses in `.gitmodules`
### macOS signing
Code signing and notarization require:
- `SIGNING_KEY_ID` secret set to the Apple Developer identity
- `SIGN_RELEASE=true` environment variable
- Valid Apple Developer account credentials in the runner keychain
Without signing, the DMG is created unsigned. Users will see Gatekeeper warnings.
### Windows NSIS installer not created
The NSIS installer requires:
- NSIS 3.x installed in the runner environment (available via conda or Chocolatey)
- `MAKE_INSTALLER=true` environment variable
- Chocolatey `shimgen.exe` for wrapper executables
If NSIS is unavailable, only the `.7z` portable archive is produced.

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.

View File

@@ -1,332 +1,215 @@
# Kindred Create Repository State Report
# Repository State
**Generated:** 2026-01-31
**Branch:** main
**Parent repo:** create-0070 @ `364a7057ef`
**Submodules:**
- ztools @ `8d1f195` (ztools-0065, main)
- silo @ `c778825` (silo-0062, main)
**Last updated:** 2026-02-03
**Branch:** main @ `0ef9ffcf51`
**Kindred Create:** v0.1.0
**FreeCAD base:** v1.0.0
## Submodules
| Submodule | Path | Source | Pinned commit |
|-----------|------|--------|---------------|
| ztools | `mods/ztools` | `gitea.kindred.internal/kindred/ztools-0065` | `d2f94c3` |
| silo | `mods/silo` | `gitea.kindred.internal/kindred/silo-0062` | `17a10ab` |
| OndselSolver | `src/3rdParty/OndselSolver` | `gitea.kindred.internal/kindred/ondsel` | `e32c9cd` |
| GSL | `src/3rdParty/GSL` | `github.com/microsoft/GSL` | `756c91a` |
| AddonManager | `src/Mod/AddonManager` | `github.com/FreeCAD/AddonManager` | `01e242e` |
| googletest | `tests/lib` | `github.com/google/googletest` | `56efe39` |
---
## Recent Changes (This Session)
## Architecture
### Assembly Solver Fix
- `610fd43` — PartDesign datum planes (including ZTools datums) now work
correctly as joint references in Assembly. `findPlacement()` in
`src/Mod/Assembly/UtilsAssembly.py` extracts geometry from
`PartDesign::Plane` and `PartDesign::Point` objects instead of returning
zero placements.
### ZTools Bug Fixes
- `665bdb2` — Three fixes in the ztools submodule:
- Removed `int()` wrapper from `getStandardButtons()` (Qt6/PySide6
compatibility — `StandardButton` enum is not int-castable).
- Guarded `_setup_ztools_viewprovider()` against C++ ViewProviders
that lack a `Proxy` attribute (`PartDesign::Plane` uses
`ViewProviderDatumPlane`, pure C++).
- Changed `setEditorMode(prop, 2)` to `setPropertyStatus(prop, "Hidden")`
for persistent attachment property hiding across save/reload.
### ZTools Parametric Datums via AttachExtension
- `2c716b4` — ZTools datum planes now leverage FreeCAD's built-in
`Part::AttachExtension` for automatic parametric updates. Instead of
`MapMode = "Deactivated"` with manual placement, each datum type maps to a
vanilla `MapMode` (`FlatFace`, `ThreePointsPlane`, `NormalToEdge`, etc.)
with appropriate `AttachmentSupport` and `AttachmentOffset`. Datums update
automatically when source geometry changes on recompute.
### Stylesheet Fixes (Uncommitted — Staged)
- **Tree branch indicators**: Created `branch_closed.svg` and
`branch_open.svg` in `images_dark-light/` with Catppuccin `#cdd6f4`
chevrons. Added `QTreeView::branch` image rules for expand/collapse states.
- **Spinbox/combobox arrows**: Synced all three QSS copies so the CSS
border-triangle arrow fix (previously only in `resources/preferences/`)
now applies at runtime via `src/Gui/Stylesheets/KindredCreate.qss`.
- **Header clipping**: Added `min-height: 20px` to `QHeaderView::section`.
- **QSS sync**: All four copies (`resources/preferences/`, `src/Gui/Stylesheets/`,
`src/Gui/PreferencePacks/`, `mods/ztools/CatppuccinMocha/`) are now
byte-identical. Merged dock widget padding and spreadsheet cell editor
styling improvements that existed only in the Stylesheets copy.
### ZTools-PartDesign Merge (Uncommitted — Staged)
- `_ZToolsPartDesignManipulator` in `mods/ztools/ztools/InitGui.py` uses
`Gui.addWorkbenchManipulator()` to inject ZTools commands into PartDesign's
C++ toolbars:
- `ZTools_DatumCreator` + `ZTools_DatumManager` → "Part Design Helper Features"
- `ZTools_EnhancedPocket` → "Part Design Modeling Features"
- `ZTools_RotatedLinearPattern` → "Part Design Transformation Features"
- Same commands also inserted into the Part Design menu after `PartDesign_Boolean`.
### Silo Enhancements (Uncommitted — Staged)
- **File menu integration**: `SiloMenuManipulator` in `src/Mod/Create/InitGui.py`
injects `Silo_New`, `Silo_Open`, `Silo_Save`, `Silo_Commit`, `Silo_Pull`,
`Silo_Push`, and `Silo_BOM` into the File menu across all workbenches.
- **Toolbar toggle**: `Silo_ToggleMode` checkable command swaps `Ctrl+O/S/N`
between standard FreeCAD file commands and Silo equivalents. Appended to the
global File toolbar via the manipulator.
- **SSL certificate browsing**: Settings dialog now includes a file browser for
custom CA certificates (`SslCertPath` preference). `_get_ssl_context()` loads
the custom cert before system CAs.
- **BOM integration**: Upstream BOM feature (`Silo_BOM` command with tabbed
dialog — BOM + Where Used) merged with local changes and added to the
Create File menu manipulator.
---
## Architecture Overview
```
create-0070/ Kindred Create (FreeCAD 1.0+ fork)
├── src/
│ ├── Mod/
│ │ ├── Assembly/ Assembly solver (OndselSolver)
│ │ ├── PartDesign/ Part modeling (C++ workbench)
│ │ ├── Sketcher/ 2D constraint sketcher
│ │ ├── Create/ Kindred bootstrap module (Python)
│ │ │ └── InitGui.py Loads ztools+silo, installs manipulators
│ │ └── ... Other FreeCAD modules
│ └── Gui/
│ ├── Stylesheets/ QSS themes + arrow/branch SVGs
│ ├── PreferencePacks/ Preference pack bundles
│ └── WorkbenchManipulatorPython.cpp Menu/toolbar injection API
├── mods/
│ ├── ztools/ [submodule] ztools-0065
│ │ └── ztools/
│ │ ├── InitGui.py ZToolsWorkbench + PartDesign manipulator
│ │ └── ztools/
│ │ ├── commands/ Datum, pattern, pocket, assembly, spreadsheet
│ │ ├── datums/core.py Datum creation with AttachExtension
│ │ └── resources/ Icons, theme utilities
│ └── silo/ [submodule] silo-0062
│ └── pkg/freecad/
│ ├── InitGui.py SiloWorkbench
│ └── silo_commands.py 12 commands + SiloClient API
└── resources/
└── preferences/KindredCreate/ Canonical QSS + preference pack
```
### Integration Flow
### Bootstrap flow
```
FreeCAD startup
└─ src/Mod/Create/InitGui.py exec()
exec(mods/ztools/ztools/InitGui.py)
├─ registers ZToolsWorkbench
└─ installs _ZToolsPartDesignManipulator (global)
├─ exec(mods/silo/pkg/freecad/InitGui.py)
│ └─ registers SiloWorkbench
QTimer.singleShot(2000):
├─ _setup_silo_menu()
installs SiloMenuManipulator (global)
├─ modifyMenuBar: Silo commands in File menu
│ └─ modifyToolBars: Silo_ToggleMode in File toolbar
├─ _check_silo_first_start()
└─ _setup_silo_activity_panel()
└─ src/Mod/Create/Init.py
setup_kindred_addons()
├─ exec(mods/ztools/ztools/Init.py)
└─ exec(mods/silo/pkg/freecad/Init.py)
└─ src/Mod/Create/InitGui.py
setup_kindred_workbenches()
├─ exec(mods/ztools/ztools/InitGui.py)
registers ZToolsWorkbench
└─ installs _ZToolsPartDesignManipulator (global)
│ └─ exec(mods/silo/pkg/freecad/InitGui.py)
└─ registers SiloWorkbench
└─ Deferred setup (QTimer):
├─ 1500ms: _setup_silo_auth_panel() → "Database Auth" dock
├─ 2000ms: _setup_silo_menu() → SiloMenuManipulator
├─ 3000ms: _check_silo_first_start() → settings prompt
└─ 4000ms: _setup_silo_activity_panel() → "Database Activity" dock
```
### Key source layout
```
src/Mod/Create/ Kindred bootstrap module (Python)
├── Init.py Adds mods/ addon paths, loads Init.py files
└── InitGui.py Loads workbenches, installs Silo manipulators
mods/ztools/ [submodule] ztools workbench
├── ztools/InitGui.py ZToolsWorkbench + PartDesign manipulator
├── ztools/ztools/
│ ├── commands/ Datum, pattern, pocket, assembly, spreadsheet
│ ├── datums/core.py Datum creation via Part::AttachExtension
│ └── resources/ Icons, theme utilities
└── CatppuccinMocha/ Theme preference pack (QSS)
mods/silo/ [submodule] Silo parts database
├── cmd/ Go server entry points
├── internal/ Go API, database, storage packages
├── pkg/freecad/ FreeCAD workbench (Python)
│ ├── InitGui.py SiloWorkbench
│ └── silo_commands.py Commands + SiloClient API
├── deployments/ Docker compose configuration
└── migrations/ PostgreSQL schema migrations
src/Gui/Stylesheets/ QSS themes and SVG assets
resources/preferences/ Canonical preference pack (KindredCreate)
```
---
## Submodule Status
## Component status
### ztools (ztools-0065)
### ztools workbench
| Commit | Description |
|--------|-------------|
| `8d1f195` | Add PartDesign WorkbenchManipulator and sync Catppuccin theme |
| `005348b` | Leverage FreeCAD AttachExtension for parametric datum updates |
| `0e95d1c` | Fix Qt6 StandardButton TypeError and C++ ViewProvider Proxy errors |
| `98bd444` | Fix workbench init and spreadsheet syntax errors |
**Registered commands (9):**
**Custom commands**: 9 registered
- `ZTools_DatumCreator`, `ZTools_DatumManager`
- `ZTools_RotatedLinearPattern`
- `ZTools_EnhancedPocket`
- `ZTools_AssemblyLinearPattern`, `ZTools_AssemblyPolarPattern`
- `ZTools_SpreadsheetStyle{Bold,Italic,Underline}`, `ZTools_SpreadsheetAlign{Left,Center,Right}`, `ZTools_Spreadsheet{BgColor,TextColor,QuickAlias}`
| Command | Function |
|---------|----------|
| `ZTools_DatumCreator` | Create datum planes, axes, points (16 modes) |
| `ZTools_DatumManager` | Manage existing datum objects |
| `ZTools_EnhancedPocket` | Flip-side pocket (cut outside sketch profile) |
| `ZTools_RotatedLinearPattern` | Linear pattern with incremental rotation |
| `ZTools_AssemblyLinearPattern` | Pattern assembly components linearly |
| `ZTools_AssemblyPolarPattern` | Pattern assembly components around axis |
| `ZTools_SpreadsheetStyle{Bold,Italic,Underline}` | Text style toggles |
| `ZTools_SpreadsheetAlign{Left,Center,Right}` | Cell alignment |
| `ZTools_Spreadsheet{BgColor,TextColor,QuickAlias}` | Colors and alias creation |
**Datum types supported**: offset_from_face, offset_from_plane, midplane,
3_points, normal_to_edge, angled, tangent_to_cylinder
**PartDesign integration** via `_ZToolsPartDesignManipulator`:
- `ZTools_DatumCreator`, `ZTools_DatumManager` → "Part Design Helper Features" toolbar
- `ZTools_EnhancedPocket` → "Part Design Modeling Features" toolbar
- `ZTools_RotatedLinearPattern` → "Part Design Transformation Features" toolbar
- Same commands inserted into Part Design menu after `PartDesign_Boolean`
### silo (silo-0062)
**Datum types (7):** offset_from_face, offset_from_plane, midplane, 3_points, normal_to_edge, angled, tangent_to_cylinder. All except tangent_to_cylinder use `Part::AttachExtension` for automatic parametric updates.
| Commit | Description |
|--------|-------------|
| `c778825` | Add Silo mode toggle, SSL cert browsing, and BOM menu integration |
| `8c06899` | (upstream) Various fixes |
| `bce7d5a` | Add BOM handling and routes to API and web UI |
| `3a79d89` | feat: add BOM system with API, database repository, and FreeCAD command |
| `8e44ed2` | Fix SIGSEGV: defer document open after dialog close |
### Silo workbench
**FreeCAD commands**: 12 registered
- `Silo_Open`, `Silo_New`, `Silo_Save`, `Silo_Commit`
- `Silo_Pull`, `Silo_Push`, `Silo_Info`, `Silo_BOM`
- `Silo_TagProjects`, `Silo_Rollback`, `Silo_SetStatus`
- `Silo_Settings`, `Silo_ToggleMode`
**Registered commands (13):**
**API surface**: 38 REST routes covering items, revisions, files, BOM,
projects, schemas, and Odoo integration stubs. See
`mods/silo/docs/REPOSITORY_STATUS.md` for full route table.
| Command | Function |
|---------|----------|
| `Silo_New` | Create new Silo-tracked document |
| `Silo_Open` | Open file from Silo database |
| `Silo_Save` | Save to Silo (create revision) |
| `Silo_Commit` | Commit current revision |
| `Silo_Pull` | Pull latest revision from server |
| `Silo_Push` | Push local changes to server |
| `Silo_Info` | View item metadata and history |
| `Silo_BOM` | Bill of materials dialog (BOM + Where Used) |
| `Silo_TagProjects` | Assign project tags |
| `Silo_Rollback` | Rollback to previous revision |
| `Silo_SetStatus` | Set revision status (draft/review/released/obsolete) |
| `Silo_Settings` | Configure API URL, projects dir, SSL certificates |
| `Silo_ToggleMode` | Swap Ctrl+O/S/N between FreeCAD and Silo commands |
**BOM API methods** (new):
- `get_bom()`, `get_bom_expanded()`, `get_bom_where_used()`
- `add_bom_entry()`, `update_bom_entry()`, `delete_bom_entry()`
**Global integration** via `SiloMenuManipulator` in `src/Mod/Create/InitGui.py`:
- File menu: Silo_New, Silo_Open, Silo_Save, Silo_Commit, Silo_Pull, Silo_Push, Silo_BOM
- File toolbar: Silo_ToggleMode button
**Server architecture:** Go REST API (38 routes) + PostgreSQL + MinIO. See `mods/silo/docs/REPOSITORY_STATUS.md` for route details.
### Theme
**Canonical source:** `resources/preferences/KindredCreate/KindredCreate.qss`
Four copies must stay in sync:
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`
---
## Potential Issues
## Known issues
### Critical
1. **Three QSS copies can drift again.** The canonical source is
`resources/preferences/KindredCreate/KindredCreate.qss`. The other copies
(`src/Gui/Stylesheets/`, `src/Gui/PreferencePacks/`, `mods/ztools/CatppuccinMocha/`)
must be kept in sync manually. Consider a build step or symlinks to
eliminate duplication.
1. **QSS duplication.** Four copies of the stylesheet must be kept in sync manually. A build step or symlinks should eliminate this.
2. **WorkbenchManipulator command registration timing.** The
`_ZToolsPartDesignManipulator` appends commands by name (e.g.
`ZTools_DatumCreator`). If the ZTools workbench has not been initialized
(first activation) when the user switches to PartDesign, the commands may
not be registered yet. The `Create/InitGui.py` `exec()` loads the
workbench class but `Initialize()` only runs on first activation. The
manipulator API tolerates missing commands silently (toolbar append
returns without error), but the buttons won't appear until ZTools
initializes. Mitigation: force-activate ZToolsWorkbench during Create
module load, or defer manipulator installation.
2. **WorkbenchManipulator timing.** The `_ZToolsPartDesignManipulator` appends commands by name. If ZToolsWorkbench hasn't been activated when the user switches to PartDesign, the commands may not be registered. The manipulator API tolerates missing commands silently, but buttons won't appear.
3. **Silo toggle mode shortcut persistence.** `_swap_shortcuts()` stores
original shortcuts in a module-level dict (`_original_shortcuts`). If
FreeCAD crashes with Silo mode ON, the original shortcuts are lost and
standard commands will have no keyboard shortcuts on next launch. Consider
persisting original shortcuts to preferences.
3. **Silo shortcut persistence.** `Silo_ToggleMode` stores original shortcuts in a module-level dict. If FreeCAD crashes with Silo mode on, original shortcuts are lost on next launch.
### High
4. **Silo has no authentication.** All API endpoints are publicly accessible.
Required before multi-user deployment. See
`mods/silo/docs/REPOSITORY_STATUS.md` for full list.
4. **No authentication on Silo server.** All API endpoints are publicly accessible. Required before multi-user deployment.
5. **No unit tests for silo FreeCAD commands or ztools.** Python code has zero
test coverage. The Go backend also lacks tests per the silo status report.
5. **No unit tests.** Zero test coverage for ztools and Silo FreeCAD commands. Silo Go backend also lacks tests.
6. **Assembly solver `findPlacement()` geometry extraction is minimal.** The
fix at `UtilsAssembly.py:1006` extracts placement from `obj.Shape.Faces[0]`
for `PartDesign::Plane`. This works for planar datums but does not handle
edge cases like empty shapes or non-planar datum objects.
6. **Assembly solver datum handling is minimal.** The `findPlacement()` fix extracts placement from `obj.Shape.Faces[0]` for `PartDesign::Plane`. Does not handle empty shapes or non-planar datum objects.
### Medium
7. **`Silo_BOM` depends on `get_tracked_object()`** which looks for a
`SiloPartNumber` property on document objects. Documents not registered
with Silo show a warning dialog. The UX could be improved with a
one-click registration flow from the BOM dialog.
7. **`Silo_BOM` requires Silo-tracked document.** Depends on `SiloPartNumber` property. Unregistered documents show a warning with no registration path.
8. **`_ZToolsPartDesignManipulator.modifyMenuBar()` inserts after
`PartDesign_Boolean`.** If upstream FreeCAD renames or removes this
command, the menu insertions silently fail. The toolbar appends (by
toolbar name) are more robust.
8. **PartDesign menu insertion fragility.** `_ZToolsPartDesignManipulator.modifyMenuBar()` inserts after `PartDesign_Boolean`. If upstream renames this command, insertions silently fail.
9. **ZTools `plane_tangent_to_cylinder` falls back to manual placement**
because the TangentPlane MapMode requires a vertex reference not currently
collected by the UI. This is the only datum type that does not benefit
from automatic parametric updates.
9. **tangent_to_cylinder falls back to manual placement.** TangentPlane MapMode requires a vertex reference not collected by the current UI.
10. **Silo `delete_bom_entry()` uses raw `urllib.request`** instead of the
`_request()` helper method on `SiloClient`. This bypasses error
normalization. Should be refactored to use `self._request("DELETE", ...)`.
10. **`delete_bom_entry()` bypasses error normalization.** Uses raw `urllib.request` instead of `SiloClient._request()`.
---
## Feature Stubs and TODOs
## Incomplete features
### From silo REPOSITORY_STATUS
### Silo
| Feature | Status | Location |
|---------|--------|----------|
| Odoo ERP integration | Stub (returns "not yet implemented") | `internal/odoo/` |
| Part number date segments | Broken (`formatDate()` returns error) | `internal/partnum/generator.go:102` |
| Location/Inventory APIs | Tables exist, no handlers | `migrations/001_initial.sql` |
| File locking | Not implemented | — |
| Authentication/Authorization | Not implemented | — |
| Feature | Status | Notes |
|---------|--------|-------|
| Authentication/authorization | Not implemented | Required for multi-user |
| File locking | Not implemented | Needed to prevent concurrent edits |
| Odoo ERP integration | Stub only | Returns "not yet implemented" |
| Part number date segments | Broken | `formatDate()` returns error |
| Location/inventory APIs | Tables exist, no handlers | |
| CSRF protection | Not implemented | Web UI only |
| CSV import transaction rollback | Not implemented | `bom_handlers.go` |
| CSV import rollback | Not implemented | `bom_handlers.go` |
### From ztools
### ztools
| Feature | Status | Location |
|---------|--------|----------|
| Tangent-to-cylinder attachment | Manual fallback (no vertex ref) | `datums/core.py` |
| Angled datum live editing | AttachmentOffset rotation not updated in panel | `datum_viewprovider.py` |
| Assembly pattern undo support | Not implemented | `assembly_pattern_commands.py` |
| Feature | Status | Notes |
|---------|--------|-------|
| Tangent-to-cylinder attachment | Manual fallback | No vertex ref in UI |
| Angled datum live editing | Incomplete | AttachmentOffset not updated in panel |
| Assembly pattern undo | Not implemented | |
### From Create integration plan
### Integration plan
| Phase | Feature | Status |
|-------|---------|--------|
| 1 | Addon auto-loading | Done (`src/Mod/Create/InitGui.py`) |
| 1 | Addon auto-loading | Done |
| 2 | Enhanced Pocket as C++ feature | Not started |
| 3 | Datum C++ helpers | Not started (Python AttachExtension approach used instead) |
| 4 | Theme system refinement | Partially done (QSS synced, not moved to Create module) |
| 5 | Silo deep integration | Done (manipulator-based menu/toolbar injection) |
| 6 | Build system integration | Not started |
| 3 | Datum C++ helpers | Not started (Python approach used) |
| 4 | Theme moved to Create module | Partial (QSS synced, not relocated) |
| 5 | Silo deep integration | Done |
| 6 | Build system install rules for mods/ | Partial (CI/CD done, CMake install rules pending) |
---
## File Change Summary (Uncommitted)
## Next steps
### Parent repo (create-0070)
1. **Authentication** -- LDAP/FreeIPA integration for Silo multi-user deployment. Server needs auth middleware; FreeCAD client needs credential storage.
| File | Change |
|------|--------|
| `resources/preferences/KindredCreate/KindredCreate.qss` | Branch indicators, header min-height, dock/actiongroup improvements, spreadsheet cell editor |
| `src/Gui/Stylesheets/KindredCreate.qss` | Synced with canonical |
| `src/Gui/PreferencePacks/KindredCreate/KindredCreate.qss` | Synced with canonical |
| `src/Gui/Stylesheets/images_dark-light/branch_closed.svg` | New: right-pointing chevron |
| `src/Gui/Stylesheets/images_dark-light/branch_open.svg` | New: down-pointing chevron |
| `src/Mod/Create/InitGui.py` | Expanded SiloMenuManipulator (BOM, full commands, toolbar toggle) |
| `mods/silo` | Submodule pointer updated |
| `mods/ztools` | Submodule pointer updated |
2. **BOM-Assembly bridge** -- Auto-populate Silo BOM from Assembly component links on save.
### ztools submodule (committed + pushed)
3. **File locking** -- Pessimistic locks on `Silo_Open` to prevent concurrent edits. Requires server-side lock table and client-side lock display.
| File | Change |
|------|--------|
| `ztools/InitGui.py` | Added `_ZToolsPartDesignManipulator` |
| `CatppuccinMocha/CatppuccinMocha.qss` | Synced with canonical KindredCreate.qss |
4. **Build system** -- CMake install rules for `mods/` submodules so packages include ztools and Silo without manual steps.
### silo submodule (committed + pushed)
| File | Change |
|------|--------|
| `pkg/freecad/silo_commands.py` | SSL cert browsing, `Silo_ToggleMode`, BOM merge |
| `pkg/freecad/InitGui.py` | Added `Silo_ToggleMode` to toolbar |
---
## Silo Integration Path
Based on `mods/silo/docs/REPOSITORY_STATUS.md` and the existing
`docs/INTEGRATION_PLAN.md`, the integration path forward is:
### Completed
- Addon auto-loading via `src/Mod/Create/InitGui.py`
- Global File menu injection via `WorkbenchManipulator`
- Toolbar toggle for Silo mode (shortcut swapping)
- SSL certificate configuration for internal CAs
- BOM command integrated into File menu
### Next Steps
1. **Authentication** — LDAP/FreeIPA integration for multi-user. Silo server
needs auth middleware; FreeCAD client needs credential storage in preferences.
2. **BOM-Assembly bridge** — Auto-populate Silo BOM from FreeCAD Assembly
component links. When a user saves an assembly, extract child part numbers
from `Assembly_InsertLink` objects and sync to Silo BOM.
3. **File locking** — Pessimistic locks on checkout (`Silo_Open`) to prevent
concurrent edits. Requires server-side lock table and client-side
lock-status display.
4. **Status bar integration** — Show current Silo item part number, revision,
and sync status in FreeCAD's status bar. Use
`FreeCADGui.getMainWindow().statusBar()`.
5. **Build system** — CMake install rules for `mods/` submodules so `.deb`
packages include ztools and silo without manual intervention.
5. **Test coverage** -- Unit tests for ztools datum creation, Silo FreeCAD commands, and Go API endpoints.