Compare commits

..

1 Commits

Author SHA1 Message Date
forbes
c527198044 refactor: extract theme from ztools into base distribution (#278)
All checks were successful
Build and Test / build (pull_request) Successful in 29m32s
- Add Spreadsheet color preferences to KindredCreate.cfg using FCText
  entries (TextColor, AliasedCellBackgroundColor, PositiveNumberColor,
  NegativeNumberColor) matching the C++ GetASCII() reader in SheetModel.cpp
- Remove CatppuccinMocha install directive from CMakeLists.txt
- Update ztools submodule: theme.py deleted, CatppuccinMocha preference
  pack removed, package.xml cleaned up

The previous apply_spreadsheet_colors() in ztools was a no-op: it called
SetUnsigned() but the Spreadsheet C++ reads GetASCII() — different param
types in FreeCAD's parameter system. Now properly fixed via preference pack.

Closes #278
2026-02-19 14:50:35 -06:00
294 changed files with 9039 additions and 24229 deletions

View File

@@ -47,6 +47,7 @@ body:
description: Which part of Kindred Create is affected?
options:
- General / Core
- ztools Workbench
- Silo (Parts Database)
- Theme / QSS
- Assembly

View File

@@ -29,7 +29,7 @@ body:
attributes:
label: Location
description: Where should this documentation live? Link to existing pages if applicable.
placeholder: e.g. docs/src/guide/assembly.md, or "new page under Reference"
placeholder: e.g. docs/src/guide/ztools.md, or "new page under Reference"
validations:
required: false

View File

@@ -27,6 +27,7 @@ body:
description: Which part of Kindred Create does this relate to?
options:
- General / Core
- ztools Workbench
- Silo (Parts Database)
- Theme / QSS
- Assembly

View File

@@ -322,7 +322,6 @@ jobs:
env:
BUILD_TAG: ${{ github.ref_name || inputs.tag }}
COMMIT_SHA: ${{ github.sha }}
NODE_EXTRA_CA_CERTS: /etc/ssl/certs/ca-certificates.crt
steps:
@@ -387,7 +386,6 @@ jobs:
'name': f'Kindred Create {tag}',
'body': body,
'prerelease': prerelease,
'target_commitish': '${COMMIT_SHA}',
}))
")

View File

@@ -0,0 +1,92 @@
name: Report a Problem
description: Have you found something that does not work well, is too hard to do or is missing altogether? Please create a Problem Report.
labels: ["Status: Needs triage","Status: Needs confirmation"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this problem report! Please [search](https://github.com/FreeCAD/FreeCAD/issues) if a similar issue already exists and check out [how to report issues](https://github.com/FreeCAD/FreeCAD?tab=readme-ov-file#reporting-issues). By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/FreeCAD/FreeCAD/blob/main/CODE_OF_CONDUCT.md).
- type: textarea
id: description
attributes:
label: Problem description
description: Describe the problem and how it impacts user experience, workflow, maintainability or performance. You can attach images or log files by clicking this area to highlight it and then dragging files in. To attach a FCStd file, ZIP it first.
placeholder: Describe your problem briefly.
validations:
required: true
- type: dropdown
id: wb
attributes:
label: Workbench affected?
options:
- Assembly
- BIM
- CAM
- Core (App, Gui,...)
- Draft
- FEM
- Material
- Measurement
- Mesh
- Part
- Part Design
- Sketcher
- Spreadsheet
- TechDraw
- Other (specify in description)
- type: textarea
id: steps_to_reproduce
attributes:
label: Steps to reproduce
description: If the problem appears to be a bug with the current functionality, provide a test case or recipe that reliably reproduces the issue. Ideally [record a macro](https://wiki.freecad.org/Std_DlgMacroRecord) and attach it. Please also add an example file as ZIP.
placeholder: |
Drag an example file as ZIP into this textbox to attach it.
Steps to reproduce the behavior:
1. Open my example file
2. Go to '...'
3. Click on '...'
validations:
required: true
- type: textarea
id: expected_behavior
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen when following the provided steps above.
placeholder: What is the expected behavior?
validations:
required: true
- type: textarea
id: actual_behavior
attributes:
label: Actual behavior
description: A clear and concise description of what actually happens. If applicable, add screenshots to help explain the problem.
placeholder: What is actually happening? You can add screenshorts or videos by dragging them into this textbox.
validations:
required: true
- type: textarea
id: dev_version
attributes:
label: Development version About Info (in Safe Mode)
description: Download the latest weekly [development release](https://github.com/FreeCAD/FreeCAD/releases) and try reproducing the issue in safe mode (Help → Restart in Safe Mode). Use the [About FreeCAD](https://wiki.freecad.org/About) dialog to copy your full version information and paste it here.
placeholder: |
1. Download the latest weekly development version (link above).
2. Make sure to run in Safe Mode (Help -> Restart in Safe Mode) to exclude interference from 3rd party addons.
3. If the issue is still reproducible, open the About FreeCAD dialog and copy the full version info here.
render: shell
validations:
required: true
- type: textarea
id: good_version
attributes:
label: Last known good version (optional)
description: If this is a regression, paste the [About FreeCAD](https://wiki.freecad.org/About) info from the last known working version.
placeholder: If the problem did not exist in a previous version, paste the About FreeCAD info from that version here.
render: shell

View File

@@ -0,0 +1,103 @@
name: Report a Code Quality Issue
description: Report problems related to code structure, maintainability, performance, correctness, or technical debt that do not directly affect end-user behavior.
labels: ["Status: Needs triage", "Status: Needs confirmation", "Type: Code Quality"]
type: "Code Quality"
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to help improve FreeCAD's code quality!
> [!NOTE]
> This form is intended **only for code quality issues**, such as:
> - Architectural or design problems
> - Maintainability or readability issues
> - Performance or scalability concerns
> - Incorrect abstractions, layering violations, or technical debt
>
> If the issue affects user-visible behavior, workflows, or UI, please use the regular **Report Problem** form instead.
Please [search existing issues](https://github.com/FreeCAD/FreeCAD/issues) before submitting.
By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/FreeCAD/FreeCAD/blob/main/CODE_OF_CONDUCT.md).
- type: dropdown
id: wb
attributes:
label: Area / Workbench affected
description: Select the primary area affected by the code quality issue.
options:
- Assembly
- BIM
- CAM
- Core (App, Gui, Base, ...)
- Draft
- FEM
- Material
- Measurement
- Mesh
- Part
- Part Design
- Sketcher
- Spreadsheet
- TechDraw
- Build system / CI
- Documentation
- Other (specify below)
- type: textarea
id: description
attributes:
label: Problem description
description: |
Describe the code quality issue clearly.
Focus on *why* the current implementation is problematic (e.g. maintainability, performance, correctness, extensibility).
Reference files, classes, functions, or modules where applicable.
placeholder: |
Example:
- Class X violates dependency rules by including Y
- Function Z mixes responsibilities and is hard to reason about
- Algorithm has unnecessary complexity or poor performance characteristics
validations:
required: true
- type: textarea
id: impact
attributes:
label: Impact
description: |
Explain the practical impact of this issue.
For example: increased maintenance cost, higher bug risk, performance degradation, difficulty extending functionality, or CI/build issues.
placeholder: Describe how this affects the codebase long-term.
validations:
required: true
- type: textarea
id: proposed_solution
attributes:
label: Suggested improvement (optional)
description: |
If you have ideas on how to address the issue, outline them here.
This can include refactoring suggestions, alternative designs, or references to best practices.
placeholder: Optional — leave empty if unsure.
- type: textarea
id: references
attributes:
label: References / evidence
description: |
Add relevant references such as:
- File paths or code snippets
- Related issues or pull requests
- Benchmarks, logs, or static analysis results
placeholder: Links, snippets, or related issues.
- type: textarea
id: dev_version
attributes:
label: Development version (if relevant)
description: |
If the issue is version-specific or recently introduced, paste the output from the
**About FreeCAD** dialog of a development build.
Otherwise, this can be left empty.
placeholder: Paste About FreeCAD information here.
render: shell

View File

@@ -0,0 +1,19 @@
name: Request a Backport
description: Suggest that a commit from the main development branch be backported to the next point release
title: "[Backport] "
labels: ["needs triage", "backport"]
body:
- type: input
id: pull_request
attributes:
label: Original PR
description: A link to the PR that contains the commits to be backported
placeholder: https://github.com/FreeCAD/FreeCAD/pulls/1234
validations:
required: true
- type: textarea
id: description
attributes:
label: Description and Justification
description: Why should this PR (or individual commit) be backported?
placeholder: Please explain why this PR or commit should be backported to the next point release.

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: 💬 START HERE -- FreeCAD Forum
url: https://forum.freecad.org/
about: You are encouraged to discuss the problem you are seeing on the FreeCAD Forums before opening an issue on GitHub

102
.github/codespellignore vendored Normal file
View File

@@ -0,0 +1,102 @@
aci
addmin
afile
ake
aline
alle
alledges
alocation
anid
anormal
anull
apoints
aply
appy
ascript
ba
beginn
bloaded
bottome
brushin
childrens
childs
connexion
currenty
curvelinear
detet
documentin
doubleclick
dum
eiter
ende
extaction
finde
findn
fle
fo
fpt
freez
froms
graphin
hist
incrementin
indexin
indicies
indx
inout
invertin
isnt
ist
itsel
leadin
linez
localy
lod
mantatory
mata
methode
mke
modell
nd
nin
normale
normaly
numer
oce
ontop
ot
pard
parm
parms
pointin
programm
propt
rady
recurrance
rin
rougly
sectionin
seh
ser
sergent
serie
shs
siz
som
strack
strin
substraction
sur
te
textin
thist
tread
ue
upto
uptodate
usind
vas
vertexes
vew
wallthickness
zuser

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
- package-ecosystem: pip
directory: /
schedule:
interval: daily

BIN
.github/images/assembly-dark.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

BIN
.github/images/assembly.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

BIN
.github/images/bim-dark.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

BIN
.github/images/bim.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 KiB

BIN
.github/images/fem.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

BIN
.github/images/partdesign-dark.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

BIN
.github/images/partdesign.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

113
.github/labels.yml vendored Normal file
View File

@@ -0,0 +1,113 @@
# Add 'source' label to any change to src files within the source dir EXCEPT for the docs sub-folder
# source:
# - any: ['src/**/*', '!src/docs/*']
# 'Mod' related labels
"Mod: Core":
- changed-files:
- any-glob-to-any-file: ['src/App/**/*', 'src/Base/**/*', 'src/Gui/**/*']
:octocat::
- changed-files:
- any-glob-to-any-file: ['.github/**/*']
"Mod: Addon Manager":
- changed-files:
- any-glob-to-any-file: ['src/Mod/AddonManager/**/*']
"Mod: Materials":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Material/**/*']
"Mod: Measurement":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Measure/**/*']
"Mod: BIM":
- changed-files:
- any-glob-to-any-file: ['src/Mod/BIM/**/*']
"Mod: Assembly":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Assembly/**/*']
"Mod: Draft":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Draft/**/*']
"Mod: FEM":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Fem/**/*']
"Mod: Mesh":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Mesh/**/*']
"Mod: MeshPart":
- changed-files:
- any-glob-to-any-file: ['src/Mod/MeshPart/**/*']
"Mod: OpenSCAD":
- changed-files:
- any-glob-to-any-file: ['src/Mod/OpenSCAD/**/*']
"Mod: Part":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Part/**/*']
"Mod: Part Design":
- changed-files:
- any-glob-to-any-file: ['src/Mod/PartDesign/**/*']
"Mod: CAM":
- changed-files:
- any-glob-to-any-file: ['src/Mod/CAM/**/*']
"Mod: Points":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Points/**/*']
"Mod: Reverse Engineering":
- changed-files:
- any-glob-to-any-file: ['src/Mod/ReverseEngineering/**/*']
"Mod: Sketcher":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Sketcher/**/*']
"Mod: Spreadsheet":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Spreadsheet/**/*']
"Mod: Start":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Start/**/*']
"Mod: Surface":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Surface/**/*']
"Mod: TechDraw":
- changed-files:
- any-glob-to-any-file: ['src/Mod/TechDraw/**/*']
"Mod: Test":
- changed-files:
- any-glob-to-any-file: ['src/Mod/Test/**/*']
# 'Packaging' related labels
"Packaging/building":
- changed-files:
- any-glob-to-any-file: ['cMake/**/*']
"3rd party component":
- changed-files:
- any-glob-to-any-file: ['src/3rdParty/**/*']
# 'Topic' related labels
"Topic: Stylesheets":
- changed-files:
- any-glob-to-any-file: ['src/Gui/Stylesheets/**/*']

View File

@@ -0,0 +1,15 @@
{
"problemMatcher": [
{
"owner": "black-warning",
"severity": "warning",
"pattern": [
{
"regexp": "^(would reformat (.*))$",
"file": 2,
"message": 1
}
]
}
]
}

17
.github/problemMatcher/clang.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"problemMatcher": [
{
"owner": "clang",
"pattern": [
{
"regexp": "^(.+):([0-9]+):([0-9]+): (error|warning|note): (.+)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
]
}
]
}

16
.github/problemMatcher/codespell.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"problemMatcher": [
{
"owner": "codespell-matcher",
"severity": "warning",
"pattern": [
{
"regexp": "^(.+):(\\d+):\\s+(.+)$",
"file": 1,
"line": 2,
"message": 3
}
]
}
]
}

16
.github/problemMatcher/cpplint.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"problemMatcher": [
{
"owner": "cpplint",
"severity": "warning",
"pattern": [
{
"regexp": "^(.+):([0-9]+): (.+)$",
"file": 1,
"line": 2,
"message": 3
}
]
}
]
}

18
.github/problemMatcher/gcc.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"__comment": "Taken from vscode-cpptools's Extension/package.json gcc rule",
"problemMatcher": [
{
"owner": "gcc-problem-matcher",
"pattern": [
{
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
]
}
]
}

View File

@@ -0,0 +1,16 @@
{
"problemMatcher": [
{
"owner": "grepMatcher-warning",
"severity": "warning",
"pattern": [
{
"regexp": "^(.+):([0-9]+):(.+)$",
"file": 1,
"line": 2,
"message": 3
}
]
}
]
}

20
.github/problemMatcher/msvc.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
"__comment":
"Taken from vscode's vs/workbench/contrib/tasks/common/problemMatcher.ts msCompile rule",
"problemMatcher": [
{
"owner": "msvc-problem-matcher",
"pattern": [
{
"regexp": "^(?:\\s+\\d+\\>)?([^\\s].*)\\((\\d+),?(\\d+)?(?:,\\d+,\\d+)?\\)\\s*:\\s+(error|warning|info)\\s+(\\w{1,2}\\d+)\\s*:\\s*(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"code": 5,
"message": 6
}
]
}
]
}

18
.github/problemMatcher/pylintError.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"problemMatcher": [
{
"owner": "pylint-error",
"severity": "error",
"pattern": [
{
"regexp": "^(.+?):([0-9]+):([0-9]+): ([([E|F][0-9]+): (.*)$",
"file": 1,
"line": 2,
"column": 3,
"code": 4,
"message": 5
}
]
}
]
}

View File

@@ -0,0 +1,18 @@
{
"problemMatcher": [
{
"owner": "pylint-warning",
"severity": "warning",
"pattern": [
{
"regexp": "^(.+?):([0-9]+):([0-9]+): ([^E|F][0-9]+): (.*)$",
"file": 1,
"line": 2,
"column": 3,
"code": 4,
"message": 5
}
]
}
]
}

46
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,46 @@
<!-- Include a brief summary of the changes. -->
<!--
The FreeCAD community thanks you for your contribution!
By creating a Pull Request you agree to the contributing policy. The complete policy can be found in the root of the source tree (CONTRIBUTING.md) or at https://github.com/FreeCAD/FreeCAD/blob/main/CONTRIBUTING.md
This template provides guidance on creating a PR that can be reviewed and approved as quickly as possible. Comments may be safely deleted.
Unless you know exactly what you're doing, please leave the checkbox 'Allow edits by maintainers' enabled. This will allow maintainers to help you.
-->
## Issues
<!-- link to individual issues this PR closes by referencing the issue number (e.g., fixes #1234, closes #4321). -->
## Before and After Images
<!-- If your proposed changes affect the FreeCAD GUI, add before and after screenshots -->
<!-- Notes on the PR Review Process
The following section describes what the maintainers consider when reviewing your Pull Request. These items may not require you to take any action. This information is provided for context. Understanding what we consider will help you prepare your request for speedy approval.
You can find additional documentation about these guidelines in the [Developers handbook](https://freecad.github.io/DevelopersHandbook).
Alignment (Does the PR align with the goals and interests of the project?)
- Does the PR have at least one issue linked, which this PR closes?
- Has the conversation on the PR and related issue(s) reached consensus?
- If the PR affects the GUI, is the Design Working Group (DWG) aware and have they had time to review and comment?
- If the PR affects the GUI, did the contributor include before/after images?
- If the PR affects standards and workflow, is the CAD Working Group (CWG) aware and have they had time to review/comment?
Impact (Does the change affect other parts of the project?)
- Has the impact on documentation been considered and appropriate action taken?
- Has the impact on translation been considered appropriate action taken?
- Will the PR affect existing user documents?
Code Quality (Is code well-written and maintainable?)
- Does the PR warrant a review by the Code Quality Working Group (CQWG)?
- Does the change include tests?
- Is the PR rebased on the current main branch with unnecessary commits squashed?
Release (Are there considerations related to release timing?)
- Has the PR been considered for backporting to the latest release branch?
- Have the release notes been considered/updated?
-->

168
.github/scripts/run_gui_tests.py vendored Normal file
View File

@@ -0,0 +1,168 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1-or-later
"""
run_gui_tests.py
List registered tests via `FreeCAD -t`, filter for GUI tests (names containing 'Gui'), and run each
GUI test module using the specified FreeCAD executable.
Usage:
run_gui_tests.py [FREECAD_EXEC]
If FREECAD_EXEC is omitted the script falls back to 'FreeCAD' on PATH.
If FREECAD_EXEC is a directory containing bin/FreeCAD, that binary is used.
If FREECAD_EXEC is an executable path, it is used directly.
This script returns 0 if all GUI modules run successfully. Otherwise it returns the last non-zero
exit code.
"""
from __future__ import annotations
import sys
import subprocess
import os
from pathlib import Path
def find_executable(arg: str | None) -> str:
"""Return the FreeCAD executable path to use.
If `arg` is None or empty, returns the plain name 'FreeCAD' which will be looked up on PATH. If
`arg` is a directory and contains `bin/FreeCAD` that binary will be returned. If `arg` is a file
path it is returned as-is. Otherwise the original argument is returned.
Common use cases: use the FreeCAD binary from a build directory or an installed FreeCAD prefix.
"""
if not arg:
return "FreeCAD"
p = Path(arg)
if p.is_dir():
candidate = p / "bin" / "FreeCAD"
if candidate.exists():
return str(candidate)
if p.is_file():
return str(p)
# fallback: return as-is (may be on PATH)
return arg
def validate_executable(path: str) -> tuple[bool, str]:
"""Return (ok, message). Checks if the executable exists or is likely on PATH.
This is best effort: if a bare name is given (e.g. 'FreeCAD') we can't stat it here, so we
accept it but warn. If the path points to a file, we check executability. Also warn if the name
looks like the CLI-only 'FreeCADCmd'.
"""
p = Path(path)
if p.is_file():
if not os.access(str(p), os.X_OK):
return False, f"File exists but is not executable: {path}"
if p.name.endswith("FreeCADCmd"):
return (
True,
(
"Warning: executable looks like 'FreeCADCmd' (CLI); GUI tests require the GUI "
"binary 'FreeCAD'."
),
)
return True, ""
# Bare name or non-existent path: accept but warn
if p.name.endswith("FreeCADCmd"):
return (
True,
(
"Warning: executable name looks like 'FreeCADCmd' (CLI); GUI tests require the GUI "
"binary 'FreeCAD'."
),
)
return True, ""
def run_and_capture(cmd: list[str]) -> tuple[int, str]:
"""Run `cmd` and return (returncode, combined stdout+stderr string).
If the executable is not found a return code of 127 is returned and a small error string is
provided as output to aid diagnosis.
"""
try:
proc = subprocess.run(
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, check=False
)
return proc.returncode, proc.stdout
except FileNotFoundError:
return 127, f"Executable not found: {cmd[0]}\n"
def parse_registered_tests(output: str) -> list[str]:
"""Parse output from `FreeCAD -t` and return a list of registered test unit names.
The function looks for the section starting with the literal 'Registered test units:' and
then collects non-empty, stripped lines from that point onwards as test names.
"""
lines = output.splitlines()
tests: list[str] = []
started = False
for ln in lines:
if not started:
if "Registered test units:" in ln:
started = True
continue
s = ln.strip()
if not s:
# allow blank lines but keep going
continue
tests.append(s)
return tests
def main(argv: list[str]) -> int:
"""Entry point: run GUI test modules registered in the FreeCAD executable.
Returns the last non-zero exit code from any GUI test module, or 0 on success.
"""
exec_arg = argv[1] if len(argv) > 1 else None
freecad_exec = find_executable(exec_arg)
print(f"Using FreeCAD executable: {freecad_exec}")
ok, msg = validate_executable(freecad_exec)
if msg:
print(msg, file=sys.stderr)
if not ok:
print(f"Aborting: invalid FreeCAD executable: {freecad_exec}", file=sys.stderr)
return 3
code, out = run_and_capture([freecad_exec, "-t"])
if code != 0:
print(
f"Warning: listing tests returned exit code {code}; attempting to parse output anyway",
file=sys.stderr,
)
tests = parse_registered_tests(out)
if not tests:
print("No registered tests found; exiting with success.")
return 0
gui_tests = [t for t in tests if "Gui" in t]
if not gui_tests:
print("No GUI tests found in registered tests; nothing to run.")
return 0
print("Found GUI test modules:")
for t in gui_tests:
print(" ", t)
last_rc = 0
for mod in gui_tests:
print(f"\nRunning GUI tests for module: {mod}")
rc, out = run_and_capture([freecad_exec, "-t", mod])
print(out)
if rc != 0:
print(f"Module {mod} exited with code {rc}", file=sys.stderr)
last_rc = rc
return last_rc
if __name__ == "__main__":
sys.exit(main(sys.argv))

115
.github/workflows/CI_cleanup.yml vendored Normal file
View File

@@ -0,0 +1,115 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
# This workflow is a complementary one to the master CI.
# It aims at doing cleanup operations after a CI workflow ran.
# Being triggered when the master workflow ends allows it to run with necessary privileges.
# Indeed it always run with push-like rights even for PR events.
# In order to work, this cleanup workflow imposes name formatting for caches
# Caches that have to be cleaned (typically compiler caches) shall be named as below :
# ${MARK}-${CONTEXT}-${REF}-${ID}
# with :
# ${MARK} => A mark identifying a cache to be cleaned, defined as being "FC" (without quotes)
# ${CONTEXT} => A string identifying cache saving context, typically OS name or compiler name
# ${REF} => The full reference of the branch owning the cache (starting with "/refs/pull/" or "/refs/heads/")
# ${ID} => A cache unique identifier, generally an ascending number, in no case containing a '-' (hyphen) sign
name: FreeCAD CI cleaner
on:
workflow_run:
workflows: [FreeCAD master CI]
types:
- completed
env:
dryrun: false
concurrency:
group: FC-CI-cleaner
cancel-in-progress: false
jobs:
CachesCleanup:
runs-on: ubuntu-latest
env:
logdir: /tmp/log/
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Make needed directories
run: |
mkdir -p ${{ env.logdir }}
- name: Get existing caches for the repo
run: |
curl -H "Accept: application/vnd.github+json" -H "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/caches > ${{ env.logdir }}caches.json
- name: Extract pull request caches
run: |
# Extract caches of which names starts with MARK and contains "/refs/pull/"
jq ".actions_caches | map(select(.key | startswith(\"FC-\"))) | map(select(.key | contains(\"refs/pull/\")))" ${{ env.logdir }}caches.json > ${{ env.logdir }}pulls.json
- name: Extract and delete pull request obsolete cache IDs
run: |
# Group the caches by MARK-CONTEXT-REF, sort by ascending last access datetime and keep all but the last as to be deleted
# As a consequence, for pull requests, only the most recent cache is kept (one for each context and for each PR)
PRID=$(jq "group_by(.key | .[:rindex(\"-\")]) | .[] | sort_by(.last_accessed_at) | .[:-1][].id" ${{ env.logdir }}pulls.json)
for id in $PRID
do
echo "Trying to delete pull request obsolete cache ID : $id"
if [ ${{ env.dryrun }} == "false" ]
then
curl -X DELETE -H "Accept: application/vnd.github+json" -H "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/caches/$id
else
echo "DRYRUN: executing : curl -X DELETE -H \"Accept: application/vnd.github+json\" $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/caches/$id"
fi
done
- name: Extract push caches
run: |
# Extract caches of which names starts with MARK and contains "/refs/heads/"
jq ".actions_caches | map(select(.key | startswith(\"FC-\"))) | map(select(.key | contains(\"refs/heads/\")))" ${{ env.logdir }}caches.json > ${{ env.logdir }}pushes.json
- name: Extract and delete push obsolete cache IDs
run: |
# Group the caches by MARK-CONTEXT-REF, sort by ascending last access datetime, keep all but the last 2 and keep all accessed for more than 1 hour as to be deleted
# As a consequence, for pushes (repo branches), at least 2 caches (for each context and for each branch) are kept, others are deleted if they have been useless for more than 1 hour
PSID=$(jq "group_by(.key | .[:rindex(\"-\")]) | .[] | sort_by(.last_accessed_at) | .[:-2][] | select((.last_accessed_at | if contains(\".\") then .[:rindex(\".\")]+\"Z\" else . end | fromdateiso8601) < (now | floor - 3600)) | .id" ${{ env.logdir }}pushes.json)
for id in $PSID
do
echo "Trying to delete push obsolete cache ID : $id"
if [ ${{ env.dryrun }} == "false" ]
then
curl -X DELETE -H "Accept: application/vnd.github+json" -H "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/caches/$id
else
echo "DRYRUN: executing : curl -X DELETE -H \"Accept: application/vnd.github+json\" $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/caches/$id"
fi
done
- name: Upload logs
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ${{ github.job }}-Logs
path: |
${{ env.logdir }}

93
.github/workflows/CI_master.yml vendored Normal file
View File

@@ -0,0 +1,93 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
# This is the master workflow for CI of FreeCAD.
# It (only) aims at properly organizing the sub-workflows.
name: FreeCAD master CI
on:
workflow_dispatch: ~
push:
branches:
- main
- releases/**
pull_request: ~
merge_group: ~
concurrency:
group: FC-CI-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
Prepare:
uses: ./.github/workflows/sub_prepare.yml
with:
artifactBasename: Prepare-${{ github.run_id }}
Pixi:
needs: [Prepare]
uses: ./.github/workflows/sub_buildPixi.yml
with:
artifactBasename: Pixi-${{ github.run_id }}
Ubuntu:
needs: [Prepare]
if: "!startsWith(github.head_ref, 'refs/heads/backport-')"
uses: ./.github/workflows/sub_buildUbuntu.yml
with:
artifactBasename: Ubuntu-${{ github.run_id }}
Windows:
needs: [Prepare]
if: "!startsWith(github.head_ref, 'refs/heads/backport-')"
uses: ./.github/workflows/sub_buildWindows.yml
with:
artifactBasename: Windows-${{ github.run_id }}
Lint:
needs: [Prepare]
if: "!startsWith(github.head_ref, 'refs/heads/backport-')"
uses: ./.github/workflows/sub_lint.yml
with:
artifactBasename: Lint-${{ github.run_id }}
changedFiles: ${{ needs.Prepare.outputs.changedFiles }}
changedLines: ${{ needs.Prepare.outputs.changedLines }}
changedCppFiles: ${{ needs.Prepare.outputs.changedCppFiles }}
changedCppLines: ${{ needs.Prepare.outputs.changedCppLines }}
changedPythonFiles: ${{ needs.Prepare.outputs.changedPythonFiles }}
changedPythonLines: ${{ needs.Prepare.outputs.changedPythonLines }}
WrapUp:
needs: [
Prepare,
Pixi,
Ubuntu,
Windows,
Lint
]
if: always()
uses: ./.github/workflows/sub_wrapup.yml
with:
previousSteps: ${{ toJSON(needs) }}

View File

@@ -0,0 +1,74 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
name: build
description: "Linux: build application"
inputs:
builddir:
description: "Directory where build will happen"
required: true
logFile:
description: "Path for log file"
required: true
errorFile:
description: "Path to error file"
required: true
reportFile:
description: "Path for report file"
required: true
extraParameters:
description: "Extra parameters to CMake build"
required: false
runs:
using: "composite"
steps:
- name: Build
id: build
shell: bash -l {0}
run: |
set -o pipefail
(stdbuf -oL -eL cmake --build ${{ inputs.builddir }} -j$(nproc) ${{ inputs.extraParameters }}) \
2> >(tee -a ${{ inputs.errorFile }}) | tee -a ${{ inputs.logFile }}
- name: Write report
shell: bash -l {0}
if: always()
run: |
if [ ${{ steps.build.outcome }} == 'success' ]
then
echo "<details><summary>:heavy_check_mark: CMake build succeeded</summary>" >> ${{ inputs.reportFile }}
else
echo "<details><summary>:fire: CMake build failed</summary>" >> ${{ inputs.reportFile }}
fi
echo "" >> ${{ inputs.reportFile }}
echo "Build Error Log (stderr output):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.errorFile }} >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
echo "Build Log (only built targets reported):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.logFile }} | sed -ne "/Built target/p" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
echo "</details>">> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}

View File

@@ -0,0 +1,79 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
name: configure
description: "Linux: configure CMake"
inputs:
sourcedir:
description: "Directory where sources are stored"
required: false
default: ./
builddir:
description: "Directory where build will happen"
required: true
logFile:
description: "Path for log file"
required: true
errorFile:
description: "Path to error file"
required: true
reportFile:
description: "Path for report file"
required: true
extraParameters:
description: "Extra parameters to CMake configure"
required: false
runs:
using: "composite"
steps:
- name: Configure CMake
id: configure
shell: bash -l {0}
run: |
set -o pipefail
(stdbuf -oL -eL cmake -S ${{ inputs.sourcedir }} -B ${{ inputs.builddir }} -D CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE ${{inputs.extraParameters }}) \
2> >(tee -a ${{ inputs.errorFile }}) | tee -a ${{ inputs.logFile }}
- name: Write report
shell: bash -l {0}
if: always()
run: |
if [ ${{ steps.configure.outcome }} == 'success' ]
then
echo "<details><summary>:heavy_check_mark: CMake configure succeeded</summary>" >> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}
echo "Configure Error Log (stderr output):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.errorFile }} >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
else
echo "<details><summary>:fire: CMake configure failed</summary>" >> ${{ inputs.reportFile }}
fi
echo "" >> ${{ inputs.reportFile }}
echo "Configure Log (only final configuration values reported):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.logFile }} | sed -ne "/^ *==/,/^====/p" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
echo "</details>">> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}

View File

@@ -0,0 +1,47 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
name: generateCacheKey
description: "Linux: generates a cache key taking into account distro and compiler"
inputs:
compiler:
description: "Binary name/path of compiler to be used"
required: true
qt_major_version:
description: "Major version number of qt to be used"
required: true
outputs:
cacheKey:
description: "Cache key with distro and compiler version"
value: ${{ steps.generateCacheKey.outputs.cacheKey }}
runs:
using: "composite"
steps:
- id: generateCacheKey
shell: bash -l {0}
run: |
cacheKey=$(lsb_release -ds | tr -d ' ')-$(basename ${{ inputs.compiler }})$(${{ inputs.compiler }} -dumpfullversion -dumpversion)-qt${{ inputs.qt_major_version }}
echo "Generated cache key : $cacheKey"
echo "cacheKey=$cacheKey" >> $GITHUB_OUTPUT

View File

@@ -0,0 +1,74 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
name: install
description: "Linux: install application"
inputs:
builddir:
description: "Directory where build is stored"
required: true
logFile:
description: "Path for log file"
required: true
errorFile:
description: "Path to error file"
required: true
reportFile:
description: "Path for report file"
required: true
extraParameters:
description: "Extra parameters to CMake install"
required: false
runs:
using: "composite"
steps:
- name: Install
id: install
shell: bash -l {0}
run: |
set -o pipefail
(stdbuf -oL -eL sudo cmake --install ${{ inputs.builddir }} ${{ inputs.extraParameters }}) \
2> >(tee -a ${{ inputs.errorFile }}) | tee -a ${{ inputs.logFile }}
- name: Write report
shell: bash -l {0}
if: always()
run: |
if [ ${{ steps.install.outcome }} == 'success' ]
then
echo "<details><summary>:heavy_check_mark: CMake install succeeded</summary>" >> ${{ inputs.reportFile }}
else
echo "<details><summary>:fire: CMake install failed</summary>" >> ${{ inputs.reportFile }}
fi
echo "" >> ${{ inputs.reportFile }}
echo "Install Error Log (stderr output):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.errorFile }} >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
echo "Install Error Log (stdout output trimmed to the last 100 Lines):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
tail -n 100 ${{ inputs.logFile }} >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
echo "</details>">> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}

View File

@@ -0,0 +1,73 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
name: build
description: "macOS: build application"
inputs:
builddir:
description: "Directory where build will happen"
required: true
logFile:
description: "Path for log file"
required: true
errorFile:
description: "Path to error file"
required: true
reportFile:
description: "Path for report file"
required: true
extraParameters:
description: "Extra parameters to CMake build"
required: false
runs:
using: "composite"
steps:
- name: Build
id: build
shell: bash -l {0}
run: |
(cmake --build ${{ inputs.builddir }} -j$(nproc) ${{ inputs.extraParameters }}) \
2> >(tee -a ${{ inputs.errorFile }}) | tee -a ${{ inputs.logFile }}
- name: Write report
shell: bash -l {0}
if: always()
run: |
if [ ${{ steps.build.outcome }} == 'success' ]
then
echo "<details><summary>:heavy_check_mark: CMake build succeeded</summary>" >> ${{ inputs.reportFile }}
else
echo "<details><summary>:fire: CMake build failed</summary>" >> ${{ inputs.reportFile }}
fi
echo "" >> ${{ inputs.reportFile }}
echo "Build Error Log (stderr output):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.errorFile }} >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
echo "Build Log (only built targets reported):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.logFile }} | sed -ne "/Built target/p" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
echo "</details>">> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}

View File

@@ -0,0 +1,78 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
name: configure
description: "macOS: configure CMake"
inputs:
sourcedir:
description: "Directory where sources are stored"
required: false
default: ./
builddir:
description: "Directory where build will happen"
required: true
logFile:
description: "Path for log file"
required: true
errorFile:
description: "Path to error file"
required: true
reportFile:
description: "Path for report file"
required: true
extraParameters:
description: "Extra parameters to CMake configure"
required: false
runs:
using: "composite"
steps:
- name: Configure CMake
id: configure
shell: bash -l {0}
run: |
(cmake -S ${{ inputs.sourcedir }} -B ${{ inputs.builddir }} -D CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE ${{inputs.extraParameters }}) \
2> >(tee -a ${{ inputs.errorFile }}) | tee -a ${{ inputs.logFile }}
- name: Write report
shell: bash -l {0}
if: always()
run: |
if [ ${{ steps.configure.outcome }} == 'success' ]
then
echo "<details><summary>:heavy_check_mark: CMake configure succeeded</summary>" >> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}
echo "Configure Error Log (stderr output):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.errorFile }} >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
else
echo "<details><summary>:fire: CMake configure failed</summary>" >> ${{ inputs.reportFile }}
fi
echo "" >> ${{ inputs.reportFile }}
echo "Configure Log (only final configuration values reported):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.logFile }} | sed -ne "/^ *==/,/^====/p" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
echo "</details>">> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}

View File

@@ -0,0 +1,44 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
name: generateCacheKey
description: "macOS: generates a cache key taking into account distro and compiler"
inputs:
compiler:
description: "Binary name/path of compiler to be used"
required: true
outputs:
cacheKey:
description: "Cache key with distro and compiler version"
value: ${{ steps.generateCacheKey.outputs.cacheKey }}
runs:
using: "composite"
steps:
- id: generateCacheKey
shell: bash -l {0}
run: |
cacheKey=$(sw_vers --productName)-$(sw_vers --productVersion)-$(basename ${{ inputs.compiler }})$(${{ inputs.compiler }} -dumpfullversion -dumpversion)
echo "Generated cache key : $cacheKey"
echo "cacheKey=$cacheKey" >> $GITHUB_OUTPUT

View File

@@ -0,0 +1,73 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
name: install
description: "macOS: install application"
inputs:
builddir:
description: "Directory where build is stored"
required: true
logFile:
description: "Path for log file"
required: true
errorFile:
description: "Path to error file"
required: true
reportFile:
description: "Path for report file"
required: true
extraParameters:
description: "Extra parameters to CMake install"
required: false
runs:
using: "composite"
steps:
- name: Install
id: install
shell: bash -l {0}
run: |
(sudo cmake --install ${{ inputs.builddir }} ${{ inputs.extraParameters }}) \
2> >(tee -a ${{ inputs.errorFile }}) | tee -a ${{ inputs.logFile }}
- name: Write report
shell: bash -l {0}
if: always()
run: |
if [ ${{ steps.install.outcome }} == 'success' ]
then
echo "<details><summary>:heavy_check_mark: CMake install succeeded</summary>" >> ${{ inputs.reportFile }}
else
echo "<details><summary>:fire: CMake install failed</summary>" >> ${{ inputs.reportFile }}
fi
echo "" >> ${{ inputs.reportFile }}
echo "Install Error Log (stderr output):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.errorFile }} >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
echo "Install Error Log (stdout output trimmed to the last 100 Lines):" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
tail -n 100 ${{ inputs.logFile }} >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
echo "</details>">> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}

View File

@@ -0,0 +1,169 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 The FreeCAD Project Association *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
# !!! Each step running a single test shall have an'id' defined so its report will be output
name: Run all C++ Tests
description: "Run all C++ tests, generating logs and reports for each"
inputs:
reportdir:
description: "Report directory where logs will be stored"
required: true
builddir:
description: "Build directory where tests are located"
required: true
reportFile:
description: "Path for report file"
required: true
runs:
using: "composite"
steps:
- name: C++ Assembly tests
id: assembly
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Assembly_tests_run --gtest_output=json:${{ inputs.reportdir }}assembly_gtest_results.json
testLogFile: ${{ inputs.reportdir }}assembly_gtest_test_log.txt
testName: Assembly
- name: C++ app tests
id: app
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/App_tests_run --gtest_output=json:${{ inputs.reportdir }}app_gtest_results.json
testLogFile: ${{ inputs.reportdir }}app_gtest_test_log.txt
testName: App
- name: C++ base tests
id: base
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Base_tests_run --gtest_output=json:${{ inputs.reportdir }}base_gtest_results.json
testLogFile: ${{ inputs.reportdir }}base_gtest_test_log.txt
testName: Base
- name: C++ Gui tests
id: gui
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Gui_tests_run --gtest_output=json:${{ inputs.reportdir }}gui_gtest_results.json
testLogFile: ${{ inputs.reportdir }}gui_gtest_test_log.txt
testName: Gui
- name: C++ Material tests
id: material
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Material_tests_run --gtest_output=json:${{ inputs.reportdir }}material_gtest_results.json
testLogFile: ${{ inputs.reportdir }}material_gtest_test_log.txt
testName: Material
- name: C++ Measure tests
id: measure
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Material_tests_run --gtest_output=json:${{ inputs.reportdir }}measure_gtest_results.json
testLogFile: ${{ inputs.reportdir }}measure_gtest_test_log.txt
testName: Measure
- name: C++ Mesh tests
id: mesh
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Mesh_tests_run --gtest_output=json:${{ inputs.reportdir }}mesh_gtest_results.json
testLogFile: ${{ inputs.reportdir }}mesh_gtest_test_log.txt
testName: Mesh
- name: C++ MeshPart tests
id: meshpart
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Mesh_tests_run --gtest_output=json:${{ inputs.reportdir }}meshpart_gtest_results.json
testLogFile: ${{ inputs.reportdir }}meshpart_gtest_test_log.txt
testName: MeshPart
- name: C++ Misc tests
id: misc
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Misc_tests_run --gtest_output=json:${{ inputs.reportdir }}misc_gtest_results.json
testLogFile: ${{ inputs.reportdir }}misc_gtest_test_log.txt
testName: Misc
- name: C++ Part tests
id: part
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Part_tests_run --gtest_output=json:${{ inputs.reportdir }}part_gtest_results.json
testLogFile: ${{ inputs.reportdir }}Part_gtest_test_log.txt
testName: Part
- name: C++ PartDesign tests
id: partdesign
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/PartDesign_tests_run --gtest_output=json:${{ inputs.reportdir }}partdesign_gtest_results.json
testLogFile: ${{ inputs.reportdir }}PartDesign_gtest_test_log.txt
testName: PartDesign
- name: C++ Points tests
id: points
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Points_tests_run --gtest_output=json:${{ inputs.reportdir }}points_gtest_results.json
testLogFile: ${{ inputs.reportdir }}points_gtest_test_log.txt
testName: Points
- name: C++ Sketcher tests
id: sketcher
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Sketcher_tests_run --gtest_output=json:${{ inputs.reportdir }}sketcher_gtest_results.json
testLogFile: ${{ inputs.reportdir }}sketcher_gtest_test_log.txt
testName: Sketcher
- name: C++ Spreadsheet tests
id: spreadsheet
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Sketcher_tests_run --gtest_output=json:${{ inputs.reportdir }}spreadsheet_gtest_results.json
testLogFile: ${{ inputs.reportdir }}spreadsheet_gtest_test_log.txt
testName: Spreadsheet
- name: C++ Start tests
id: start
uses: ./.github/workflows/actions/runCPPTests/runSingleTest
with:
testCommand: ${{ inputs.builddir }}/tests/Start_tests_run --gtest_output=json:${{ inputs.reportdir }}start_gtest_results.json
testLogFile: ${{ inputs.reportdir }}start_gtest_test_log.txt
testName: Start
- name: Compose summary report based on test results
if: always()
shell: bash -l {0}
run: |
# Print global result
if [ ${{ job.status }} != "success" ]
then
echo "<details><summary>:fire: C++ tests failed</summary>" >> ${{ inputs.reportFile }}
else
echo "<details><summary>:heavy_check_mark: C++ tests succeeded</summary>" >> ${{ inputs.reportFile }}
fi
echo "" >> ${{ inputs.reportFile }}
echo "<blockquote>" >> ${{ inputs.reportFile }}
#Extract individual results
cat > /tmp/data << "EOD"
${{ toJSON(steps) }}
EOD
echo "$(jq -r ".[].outputs.reportText" /tmp/data)" >> ${{ inputs.reportFile }}
# Close report
echo "</blockquote>" >> ${{ inputs.reportFile }}
echo "</details>" >> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}

View File

@@ -0,0 +1,74 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * Copyright (c) 2023 FreeCAD Project Association *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
name: runCPPTests
description: "Run C++ tests, generate log and report"
inputs:
testCommand:
description: "Test command to be run"
required: true
testLogFile:
description: "Path for the command-line output of the tests"
required: true
testName:
description: "A descriptive name for the test suite"
required: true
outputs:
reportText:
description: "Report text"
value: ${{ steps.report.outputs.reportText }}
runs:
using: "composite"
steps:
- name: Run C++ tests
shell: bash -l {0}
run: |
set -o pipefail
${{ inputs.testCommand }} | tee -a ${{ inputs.testLogFile }}
- name: Parse test results
if: always()
id: report
shell: bash -l {0}
run: |
result=$(sed -ne "/Global test environment tear-down/,/^$/{/^$/d;p}" ${{ inputs.testLogFile }})
if grep -qF "[ FAILED ]" <<< $result
then
reportText="<details><summary>:fire: GTest C++ test suite '${{ inputs.testName }}' failed</summary>\n"
else
reportText="<details><summary>:heavy_check_mark: GTest C++ test suite '${{ inputs.testName }}' succeeded</summary>\n"
fi
reportText+="\n"
reportText+="Results\n"
reportText+="\n"
reportText+='```\n'
reportText+="$result\n"
reportText+='```\n'
reportText+="</details>\n"
reportText+="\n"
echo "reportText<<EOD" >> $GITHUB_OUTPUT
echo -e "$reportText" >> $GITHUB_OUTPUT
echo "EOD" >> $GITHUB_OUTPUT
echo -e "$reportText"

View File

@@ -0,0 +1,82 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
name: runPythonTests
description: "Run Python tests, generate log and report"
inputs:
testDescription:
description: "Test description text, will be used on report"
required: true
testCommand:
description: "Test command to be run"
required: true
logFile:
description: "Path for log file"
required: true
reportFile:
description: "Path for report file"
required: true
runs:
using: "composite"
steps:
- name: Run tests
id: runTests
shell: bash -l {0}
run: |
set -o pipefail
${{ inputs.testCommand }} | sed -Ee "/[[:blank:]]*\([[:digit:]]{1,3} %\)[[:blank:]]*/d" | tee -a ${{ inputs.logFile }}
- name: Write report
shell: bash -l {0}
if: always()
run: |
sed -ne "/^\(FAILED\|ERROR\):/,/^[[:blank:]]*$/bF; /^Traceback/,/^[^[:blank:]]/{/^Traceback/bT; /^[^[:blank:]]/G; bT}; b; :T w ${{ inputs.logFile }}_tracebacks" -e "b; :F w ${{ inputs.logFile }}_failedtests" ${{ inputs.logFile }}
icon=""
if [ $( cat ${{ inputs.logFile }}_tracebacks | wc -l ) -gt 0 ]
then
icon=" :fire:"
fi
if [ ${{ steps.runTests.outcome }} == 'success' ]
then
echo "<details><summary>:heavy_check_mark: ${{ inputs.testDescription }} succeeded$icon</summary>" >> ${{ inputs.reportFile }}
else
echo "<details><summary>:fire: ${{ inputs.testDescription }} failed$icon</summary>" >> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}
echo "Failed tests" >> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.logFile }}_failedtests >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
fi
echo "" >> ${{ inputs.reportFile }}
if [ $( cat ${{ inputs.logFile }}_tracebacks | wc -l ) -gt 0 ]
then
echo "Uncaught tracebacks -- these tracebacks appeared during test but didn't fail a test --" >> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
cat ${{ inputs.logFile }}_tracebacks >> ${{ inputs.reportFile }}
echo '```' >> ${{ inputs.reportFile }}
fi
echo "</details>">> ${{ inputs.reportFile }}
echo "" >> ${{ inputs.reportFile }}

View File

@@ -0,0 +1,78 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
# This action aims at speeding up CI and reduce dependency to external resources
# by creating a cache of Ccache needed binaries then using it for CI runs rather
# than downloading every time.
#
# If it needs to be updated to another version, the process it to change
# 'downloadpath' and 'version' inputs below then delete the existing cache
# from Github interface so a new one is generated using new values.
name: getCcache
description: "Windows: tries to get a cached version of Ccache and create one if fails"
inputs:
ccachebindir:
description: "Directory where ccache binaries shall be stored"
required: true
# Below inputs shall generally not be provided as they won't be used if a cached version exists
# They are mainly used because Github do not support adding env variables in a composite action
ccachedownloadpath:
description: "Path where to download ccache"
required: false
default: https://github.com/ccache/ccache/releases/download/v4.9/
ccacheversion:
description: "Ccache version to be downloaded"
required: false
default: ccache-4.9-windows-x86_64
runs:
using: "composite"
steps:
- name: Create destination directory
shell: bash
run: |
mkdir -p ${{ inputs.ccachebindir }}
- name: Get cached version
uses: actions/cache/restore@v4
id: getCached
with:
path: ${{ inputs.ccachebindir }}
key: ccacheforwin-${{ inputs.ccacheversion }}
- name: Download ccache
shell: bash
if: steps.getCached.outputs.cache-hit != 'true'
run: |
curl -L -o ccache.zip ${{ inputs.ccachedownloadpath }}${{ inputs.ccacheversion }}.zip
7z x ccache.zip -o"ccachetemp" -r -y
cp -a ccachetemp/${{ inputs.ccacheversion }}/ccache.exe ${{ inputs.ccachebindir }}
cp -a ccachetemp/${{ inputs.ccacheversion }}/ccache.exe ${{ inputs.ccachebindir }}/cl.exe
rm ccache.zip
rm -rf ccachetemp
- name: Save version to cache
if: steps.getCached.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: ${{ inputs.ccachebindir }}
key: ${{ steps.getCached.outputs.cache-primary-key }}

View File

@@ -0,0 +1,76 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
# This action aims at speeding up CI and reduce dependency to external resources
# by creating a cache of Libpack needed files then using it for CI runs rather
# than downloading every time.
#
# If it needs to be updated to another version, the process it to change
# 'downloadpath' and 'version' inputs below then delete the existing cache
# from Github interface so a new one is generated using new values.
name: getLibpack
description: "Windows: tries to get a cached version of Libpack and create one if fails"
inputs:
libpackdir:
description: "Directory where libpack files shall be stored"
required: true
# Below inputs shall generally not be provided as they won't be used if a cached version exists
# They are mainly used because Github do not support adding env variables in a composite action
libpackdownloadurl:
description: "URL where to download libpack"
required: false
default: https://github.com/FreeCAD/FreeCAD-LibPack/releases/download/3.1.1.3/LibPack-1.1.0-v3.1.1.3-Release.7z
libpackname:
description: "Libpack name (once downloaded)"
required: false
default: LibPack-1.1.0-v3.1.1.3-Release
runs:
using: "composite"
steps:
- name: Create destination directory
shell: bash
run: |
mkdir -p ${{ inputs.libpackdir }}
- name: Get cached version
uses: actions/cache/restore@v4
id: getCached
with:
path: ${{ inputs.libpackdir }}
key: libpackforwin-${{ inputs.libpackname }}
- name: Download libpack
shell: bash
if: steps.getCached.outputs.cache-hit != 'true'
run: |
curl -L -o libpack.7z ${{ inputs.libpackdownloadurl }}
7z x libpack.7z -o"libpacktemp" -r -y
mv libpacktemp/${{ inputs.libpackname }}/* ${{ inputs.libpackdir }}
rm -rf libpacktemp
- name: Save version to cache
if: steps.getCached.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: ${{ inputs.libpackdir }}
key: ${{ steps.getCached.outputs.cache-primary-key }}

View File

@@ -0,0 +1,133 @@
# This workflow warns and then closes issues that have had no activity for a
# specified amount of time. You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/marketplace/actions/close-stale-issues
# https://github.com/actions/stale/blob/master/action.yml
# https://github.com/actions/stale
---
name: 'Stale Issues'
on: # yamllint disable-line rule:truthy
schedule:
- cron: '0 0 * * *' # Run at 00:00 UTC every day
workflow_dispatch:
permissions:
contents: read
issues: write # for actions/stale to close stale issues
pull-requests: write # for actions/stale to close stale pull requests
jobs:
stale:
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: '🧹 Tag & close stale unconfirmed bugs'
id: stale_issues
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-issue-stale: 90
days-before-issue-close: 14
days-before-pr-stale: -1
days-before-pr-close: -1
operations-per-run: 200 # max num of ops per run
stale-issue-label: 'Status: Stale'
close-issue-label: 'Status: Auto-closing'
exempt-issue-labels: 'Status: Confirmed,Priority: High,Priority: Critical,Blocker,Type: Feature,no-auto-close,3rd party: OCC'
remove-stale-when-updated: true
ascending: true
stale-issue-message: |
Hi! This issue hasnt seen activity in a while. If its still relevant, please update to the latest FreeCAD weekly build [download here](https://github.com/FreeCAD/FreeCAD/releases/) to see if the problem is resolved.
If the issue persists, let us know by adding a comment with any updates or details. Otherwise, well close this issue automatically in 14 days to keep our backlog tidy. Feel free to comment anytime to keep it open. Closed issues can always be reopened.
Thanks for helping improve FreeCAD!
Access additional [FreeCAD](https://freecad.org) resources:
- **Forum**: https://forum.freecad.org
- **Blog**: https://blog.freecad.org
- **Wiki**: https://wiki.freecad.org
- name: '🧹 Close stale requested feedback issues'
id: awaiting_issues
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-issue-stale: 20
days-before-issue-close: 14
days-before-pr-stale: -1
days-before-pr-close: -1
operations-per-run: 100 # max num of ops per run
stale-issue-label: 'Status: Stale'
close-issue-label: 'Status: Auto-closing'
only-labels: 'Status: Needs feedback,Status: Needs test on dev version,Status: Needs steps to reproduce'
remove-stale-when-updated: true
ascending: true
stale-issue-message: |
Hi! This issue hasnt seen activity in a while despite the need for further feedback.
If its still relevant, please update to the latest FreeCAD weekly build [download here](https://github.com/FreeCAD/FreeCAD/releases/) to see if the problem is resolved.
If the issue persists, let us know by adding a comment with any updates or details. Otherwise, well close this issue automatically in 14 days to keep our backlog tidy. Feel free to comment anytime to keep it open. Closed issues can always be reopened.
Thanks for helping improve FreeCAD!
Access additional [FreeCAD](https://freecad.org) resources:
- **Forum**: https://forum.freecad.org
- **Blog**: https://blog.freecad.org
- **Wiki**: https://wiki.freecad.org
- name: '🧹 Tag & close inactive issues'
id: inactive_issues
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-issue-stale: 190
days-before-issue-close: 60
days-before-pr-stale: -1
days-before-pr-close: -1
operations-per-run: 100 # max num of ops per run
stale-issue-label: 'Status: Stale'
close-issue-label: 'Status: Auto-closing'
exempt-issue-labels: 'Priority: High,Priority: Critical,Blocker,Type: Feature,no-auto-close,3rd party: OCC'
remove-stale-when-updated: true
ascending: true
stale-issue-message: |
Hi! This issue hasnt seen activity in a while. We automatically check each issue after 190 days without activity to keep the backlog tidy.
If its still relevant, please update to the latest FreeCAD weekly build [download here](https://github.com/FreeCAD/FreeCAD/releases/) to see if the issue is already resolved.
If the issue is still relevant, let us know by adding a comment.
Otherwise, well close this issue automatically in 60 days.
Feel free to comment anytime to keep it open. Closed issues can always be reopened.
Thanks for helping improve FreeCAD!
Access additional [FreeCAD](https://freecad.org) resources:
- **Forum**: https://forum.freecad.org
- **Blog**: https://blog.freecad.org
- **Wiki**: https://wiki.freecad.org
- name: '🧹 Tag & close inactive PRs'
id: inactive_pr
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-issue-stale: -1
days-before-issue-close: -1
days-before-pr-stale: 120
days-before-pr-close: 60
operations-per-run: 30 # max num of ops per run
stale-pr-label: 'Status: Stale'
close-pr-label: 'Status: Auto-closing'
exempt-pr-labels: 'Needs backport,Priority: High,Priority: Critical,no-auto-close'
remove-stale-when-updated: true
ascending: true
stale-pr-message: |
Thanks for helping improve FreeCAD!
This pull request hasnt seen activity in a while. We automatically check each PR after 120 days without activity to keep the repository tidy.
If the PR is still relevant, let us know by adding a comment.
Otherwise, well close this PR automatically in 60 days.
If you would like to keep working on this pull request, we advice to rebase it on current main branch, ask feedback from users or maintainers and engage with the community to get it forward.

52
.github/workflows/backport.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: Backport merged pull request
on:
pull_request_target:
types: [closed, labeled]
branches: [main, releases/*]
permissions:
contents: write
pull-requests: write
jobs:
backport:
name: Create backport pull request
runs-on: ubuntu-latest
# Run the action if a PR is merged with backport labels
# OR
# when already merged PR is labeled with backport labels
if: >
github.event.pull_request.merged
&& (
github.event.action == 'closed'
|| (
github.event.action == 'labeled'
&& startsWith(github.event.label.name, 'backport ')
)
)
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
token: ${{ secrets.GH_TOKEN_FOR_CI_RUNNERS }}
- name: Create backport pull requests
uses: korthout/backport-action@d07416681cab29bf2661702f925f020aaa962997 # v3.4.1
with:
# Inputs documented here: https://github.com/korthout/backport-action?tab=readme-ov-file#inputs
github_token: ${{ secrets.GH_TOKEN_FOR_CI_RUNNERS }}
github_workspace: ${{ github.workspace }}
# permit PRs with merge commits to be backported
merge_commits: 'skip'
# copy labels to backport to identify affected systems and priorities
copy_labels_pattern: '.*'
# Regex pattern to match github labels
# The capture group catches the target branch
# i.e. label "backport releases/FreeCAD-1-0" will create backport
# PR for branch releases/FreeCAD-1-0
label_pattern: ^backport ([^ ]+)$

213
.github/workflows/build_release.yml vendored Normal file
View File

@@ -0,0 +1,213 @@
name: Build Release
on:
release:
types: [created]
schedule:
- cron: "0 0 * * 3"
workflow_dispatch:
permissions:
contents: write
actions: write
jobs:
upload_src:
runs-on: ubuntu-latest
outputs:
build_tag: ${{ steps.get_tag.outputs.build_tag }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checkout Source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.sha }}
fetch-depth: 2
fetch-tags: true
submodules: 'recursive'
- name: get tag and create release if weekly
id: get_tag
shell: bash -l {0}
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ "${{ github.event_name }}" = "release" ]; then
export BUILD_TAG="${{ github.event.release.tag_name }}"
else
export BUILD_TAG=weekly-$(date "+%Y.%m.%d")
gh release create ${BUILD_TAG} --title "Development Build ${BUILD_TAG}" -F .github/workflows/weekly-build-notes.md --prerelease || true
fi
echo "BUILD_TAG=${BUILD_TAG}" >> "$GITHUB_ENV"
echo "build_tag=${BUILD_TAG}" >> "$GITHUB_OUTPUT"
- name: Trigger notes updater workflow (only for weekly)
if: startsWith(steps.get_tag.outputs.build_tag, 'weekly-')
uses: actions/github-script@v7
env:
WEEKLY_TAG: ${{ steps.get_tag.outputs.build_tag }}
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
// Reusable/dispatchable updater workflow file in .github/workflows/
const workflow_id = 'weekly-compare-link.yml';
// Use the default branch so the workflow file is available
const ref = (context.payload?.repository?.default_branch) || 'main';
const current_tag = process.env.WEEKLY_TAG || '';
await github.rest.actions.createWorkflowDispatch({
owner, repo, workflow_id, ref,
inputs: { current_tag }
});
core.info(`Dispatched ${workflow_id} on ${ref} with current_tag='${current_tag}'.`)
- name: Upload Source
id: upload_source
shell: bash -l {0}
env:
GH_TOKEN: ${{ github.token }}
run: |
python3 package/scripts/write_version_info.py ../freecad_version.txt
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git config user.name 'github-actions[bot]'
git apply package/disable_git_info.patch
git commit -a -m "Disable git info write to Version.h"
git archive HEAD -o freecad_source_${BUILD_TAG}.tar
git submodule foreach --recursive \
"git archive HEAD --prefix=\$path/ -o \$sha1.tar && \
tar -A -f \$toplevel/freecad_source_${BUILD_TAG}.tar \$sha1.tar && \
rm \$sha1.tar"
gzip freecad_source_${BUILD_TAG}.tar
sha256sum freecad_source_${BUILD_TAG}.tar.gz > freecad_source_${BUILD_TAG}.tar.gz-SHA256.txt
gh release upload --clobber ${BUILD_TAG} "freecad_source_${BUILD_TAG}.tar.gz" "freecad_source_${BUILD_TAG}.tar.gz-SHA256.txt"
build:
needs: upload_src
strategy:
matrix:
include:
- { target: linux-64, os: ubuntu-22.04 }
- { target: linux-arm64, os: ubuntu-22.04-arm }
- { target: osx-64, os: macos-15-intel, deploy_target: "10.13" }
- { target: osx-arm64, os: macos-latest, deploy_target: "11.0" }
- { target: osx-arm64, os: macos-latest, deploy_target: "15.0" }
- { target: win-64, os: windows-latest }
fail-fast: false
runs-on: ${{ matrix.os }}
environment: weekly-build
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
# prevent running out of disk space on Ubuntu runners.
- name: Maximize build space
if: runner.os == 'Linux'
uses: AdityaGarg8/remove-unwanted-software@90e01b21170618765a73370fcc3abbd1684a7793 # v5
with:
verbose: 'true'
remove-android: 'true' # (frees ~9 GB)
remove-cached-tools: 'true' # (frees ~8.3 GB)
- name: Set Platform Environment Variables
shell: bash -l {0}
env:
OPERATING_SYSTEM: ${{ runner.os }}
run: |
if [[ $OPERATING_SYSTEM == 'Windows' ]]; then
echo 'PIXI_CACHE_DIR=D:\rattler' >> "$GITHUB_ENV"
echo 'RATTLER_CACHE_DIR=D:\rattler' >> "$GITHUB_ENV"
fi
- name: Checkout Source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.sha }}
fetch-depth: 2
fetch-tags: true
submodules: 'recursive'
- uses: prefix-dev/setup-pixi@a0af7a228712d6121d37aba47adf55c1332c9c2e # v0.9.4
with:
pixi-version: v0.59.0
cache: false
- name: Install the Apple certificate and provisioning profile
id: get_cert
if: runner.os == 'macOS'
env:
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
DEVELOPER_TEAM_ID: ${{ secrets.DEVELOPER_TEAM_ID }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
run: |
if [ -z "$BUILD_CERTIFICATE_BASE64" ]; then
echo "has_cert=false" >> $GITHUB_OUTPUT
echo "No certificate avalable... skipping" && exit 0
else
echo "has_cert=true" >> $GITHUB_OUTPUT
fi
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/FreeCAD_bundle.provisionprofile
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
export KEYCHAIN_PASSWORD=$(openssl rand -base64 8)
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# apply provisioning profile
mkdir -p ~/Library/Provisioning\ Profiles
cp $PP_PATH ~/Library/Provisioning\ Profiles
xcrun notarytool store-credentials "FreeCAD" --keychain "$KEYCHAIN_PATH" --apple-id "${APPLE_ID}" --password "${APP_SPECIFIC_PASSWORD}" --team-id "${DEVELOPER_TEAM_ID}"
- name: Build and Release Packages
shell: bash
env:
GH_TOKEN: ${{ github.token }}
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
SIGN_RELEASE: ${{ steps.get_cert.outputs.has_cert }}
TARGET_PLATFORM: ${{ matrix.target }}
MAKE_INSTALLER: "true"
UPLOAD_RELEASE: "true"
BUILD_TAG: ${{ needs.upload_src.outputs.build_tag }}
run: |
if [[ "${{ runner.os }}" == "macOS" ]]; then
export MACOS_DEPLOYMENT_TARGET="${{ matrix.deploy_target }}"
fi
python3 package/scripts/write_version_info.py ../freecad_version.txt
cd package/rattler-build
pixi install
pixi run -e package create_bundle
## Needed if running on a self-hosted runner:
# - name: Clean up keychain and provisioning profile
# if: ${{ always() }}
# run: |
# security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
# rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision

127
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,127 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '28 12 * * 6'
permissions:
contents: read
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
# - language: c-cpp
# build-mode: autobuild
# - language: javascript-typescript
# build-mode: none
- language: python
build-mode: none
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 2
submodules: recursive
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended
queries: security-and-quality
# Change the CodeQL Bundle version
# tools: https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.20.7/codeql-bundle-linux64.tar.gz
# Add exclusions
config: |
paths-ignore:
- src/Mod/Import/App/SCL_output/**
query-filters:
- exclude:
id: py/file-not-closed
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
category: "/language:${{matrix.language}}"

163
.github/workflows/codeql_cpp.yml vendored Normal file
View File

@@ -0,0 +1,163 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced (c-cpp)"
on:
# push:
# branches: [ "main" ]
# pull_request:
# branches: [ "main" ]
schedule:
- cron: '28 12 * * 6'
workflow_dispatch: # Allow manual triggers
env:
CODEQL_EXTRACTOR_CPP_AUTOINSTALL_DEPENDENCIES: false
permissions:
contents: read
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
# - language: actions
# build-mode: none
- language: c-cpp
build-mode: autobuild
# - language: javascript-typescript
# build-mode: none
# - language: python
# build-mode: none
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
# prevent running out of disk space on Ubuntu runners.
- name: Maximize build space
uses: AdityaGarg8/remove-unwanted-software@90e01b21170618765a73370fcc3abbd1684a7793 # v5
with:
verbose: 'true'
remove-android: 'true' # (frees ~9 GB)
remove-cached-tools: 'true' # (frees ~8.3 GB)
remove-haskell: 'true' # (frees ~5.2 GB)
remove-swapfile: 'true' # (frees ~4 GB)
remove-docker-images: 'true' # (frees ~3.2 GB)
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 2
submodules: recursive
# Install FreeCAD dependencies (cpp)
- name: Setup build environment
run: ./package/ubuntu/install-apt-packages.sh
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended
queries: security-and-quality
# Change the CodeQL Bundle version
# tools: https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.20.7/codeql-bundle-linux64.tar.gz
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
category: "/language:${{matrix.language}}"
output: sarif-results
upload: failure-only
- name: filter-sarif
uses: advanced-security/filter-sarif@v1
with:
patterns: |
-tests/**/*
-src/3rdParty/**/*
-**/ui_*.h
-**/moc_*.cpp
input: sarif-results/cpp.sarif
output: sarif-results/cpp.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: sarif-results/cpp.sarif
- name: Upload loc as a Build Artifact
uses: actions/upload-artifact@v5
with:
name: sarif-results
path: sarif-results
retention-days: 1

27
.github/workflows/dependency-review.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Request,
# surfacing known-vulnerable versions of the packages declared or updated in the PR.
# Once installed, if the workflow run is marked as required,
# PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
name: 'Dependency Review'
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: 'Checkout Repository'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: 'Dependency Review'
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4

39
.github/workflows/fedora-daily.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Fedora Daily Build
permissions:
contents: read
on:
schedule:
- cron: "00 00 * * *"
workflow_dispatch:
jobs:
trigger-copr-build:
environment: fedora-daily
env:
copr_login: ${{ secrets.COPR_LOGIN }}
copr_username: ${{ secrets.COPR_USERNAME }}
copr_token: ${{ secrets.COPR_TOKEN }}
runs-on: ubuntu-latest
container: quay.io/packit/packit
steps:
- name: setup copr token
run: |
mkdir -p ~/.config
echo \
"[copr-cli]
login = $copr_login
username = $copr_username
token = $copr_token
copr_url = https://copr.fedorainfracloud.org
" > ~/.config/copr
- name: checkout sources
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 500
fetch-tags: true
- name: Setup safe Git directory
run: git config --global --add safe.directory $GITHUB_WORKSPACE
- name: trigger copr build
run: |
packit build in-copr --project freecad

View File

@@ -0,0 +1,68 @@
name: Fetch Crowdin Translations
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 1"
jobs:
update-crowdin:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
token: ${{ secrets.GH_TOKEN_FOR_CROWDIN_SYNC }}
- name: Install Qt ≥ 6.8
uses: jurplel/install-qt-action@d325aaf2a8baeeda41ad0b5d39f84a6af9bcf005
with:
aqtversion: "==3.1.*"
version: "6.8.3"
host: "linux"
target: "desktop"
arch: "linux_gcc_64"
- name: Setup Python & dependencies
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: "3.13"
- name: Install Python packages
run: |
python3 -m pip install --upgrade pip
pip install pyside6
- name: Gather translations from crowdin
run: |
./updatecrowdin.py build
while ./updatecrowdin.py build-status | grep -q "status: inProgress"; do sleep 10; done
./updatecrowdin.py download
./updatecrowdin.py install
working-directory: ./src/Tools
env:
CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
CROWDIN_PROJECT_ID: freecad
- name: Commit changes
run: |
git config --global user.name "freecad-gh-actions-translation-bot"
git config --global user.email "freecad-gh-actions-translation-bot@github.com"
git add src
git commit -m "Update translations from Crowdin" || echo "No changes to commit"
- name: Push changes to a new branch
run: |
git branch update-crowdin-translations
git push origin update-crowdin-translations --force
- name: Create Pull Request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0
with:
branch: update-crowdin-translations
title: "Update translations from Crowdin"
body: "Automatic Crowdin update."
token: ${{ secrets.GH_TOKEN_FOR_CROWDIN_SYNC }}
delete-branch: true
add-paths: |
src

49
.github/workflows/issue-metrics.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: Monthly issue metrics
on:
workflow_dispatch:
schedule:
- cron: '0 0 15 * *'
permissions:
issues: write
pull-requests: read
jobs:
build:
name: issue metrics
runs-on: ubuntu-latest
if: github.repository_owner == 'FreeCAD'
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Get dates for last month
shell: bash
run: |
# Calculate the first day of the previous month
first_day=$(date -d "last month" +%Y-%m-15)
# Calculate the last day of the previous month
last_day=$(date -d "$first_day +1 month -1 day" +%Y-%m-%d)
#Set an environment variable with the date range
echo "$first_day..$last_day"
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@67526e7bd8100b870f10b1c120780a8375777b43 # v3.25.5
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:FreeCAD/FreeCAD is:issue created:${{ env.last_month }}'
- name: Create issue
uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6.0.0
with:
title: Monthly issue metrics report
token: ${{ secrets.GITHUB_TOKEN }}
content-filepath: ./issue_metrics.md
assignees: maxwxyz

32
.github/workflows/labeler.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
# This workflow will triage pull requests and apply a label based on the
# paths that are modified in the pull request.
#
# For more information, see:
# https://github.com/actions/labeler
name: Labeler
on:
pull_request_target:
types: [opened, reopened]
permissions:
contents: read
jobs:
label:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
configuration-path: ".github/labels.yml"
sync-labels: false

View File

@@ -0,0 +1,43 @@
name: Push Crowdin Translations
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 1"
jobs:
update-crowdin:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
ref: releases/FreeCAD-1-1
- name: Install Qt ≥ 6.8
uses: jurplel/install-qt-action@d325aaf2a8baeeda41ad0b5d39f84a6af9bcf005
with:
aqtversion: "==3.1.*"
version: "6.8.3"
host: "linux"
target: "desktop"
arch: "linux_gcc_64"
- name: Setup Python & dependencies
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: "3.13"
- name: Install Python packages
run: |
python3 -m pip install --upgrade pip
pip install pyside6
- name: Push translations to Crowdin
run: |
./updatecrowdin.py gather
./updatecrowdin.py upload
working-directory: ./src/Tools
env:
CROWDIN_TOKEN: ${{ secrets.CROWDIN_TOKEN }}
CROWDIN_PROJECT_ID: freecad

81
.github/workflows/scorecards.yml vendored Normal file
View File

@@ -0,0 +1,81 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '20 7 * * 2'
push:
branches: ["main"]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
contents: read
actions: read
# To allow GraphQL ListCommits to work
issues: read
pull-requests: read
# To detect SAST tools
checks: read
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecards on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
sarif_file: results.sarif

232
.github/workflows/sub_buildPixi.yml vendored Normal file
View File

@@ -0,0 +1,232 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2024 0penBrain, Lorenz Lechner and Jacob Oursland. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
# This is a build and test workflow for CI of FreeCAD.
# This workflow aims at building and testing FreeCAD on a Conda environment on macOS.
name: Pixi Builds
on:
workflow_call:
inputs:
artifactBasename:
type: string
required: true
testOnBuildDir:
default: false
type: boolean
required: false
allowedToFail:
default: false
type: boolean
required: false
outputs:
reportFile:
value: ${{ jobs.Build.outputs.reportFile }}
jobs:
build_with_pixi:
runs-on: ${{ matrix.os }}
continue-on-error: ${{ inputs.allowedToFail }}
env:
CCACHE_COMPRESS: true
CCACHE_COMPRESSLEVEL: 5
CCACHE_CONFIGPATH: ${{ github.workspace }}/ccache/config
CCACHE_DIR: ${{ github.workspace }}/ccache
CCACHE_MAXSIZE: 1G
CCACHE_NODIRECT: true
CCACHE_NOHASHDIR: true
CCACHE_NOINODECACHE: true
CCACHE_SLOPPINESS: "include_file_ctime,include_file_mtime,pch_defines,time_macros"
builddir: ${{ github.workspace }}/build/release/
cacheKey: pixi-${{ matrix.os }}
config: release
logdir: ${{ github.workspace }}/logs/
reportdir: ${{ github.workspace }}/report/
reportfilename: ${{ inputs.artifactBasename }}-${{ matrix.os }}-report.md
outputs:
reportFile: ${{ steps.Init.outputs.reportFile }}
strategy:
max-parallel: 6
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Set Platform Environment Variables
shell: bash -l {0}
env:
OPERATING_SYSTEM: ${{ runner.os }}
run: |
if [[ $OPERATING_SYSTEM == 'Windows' ]]; then
echo 'CCACHE_COMPILERCHECK=%compiler%' >> "$GITHUB_ENV"
else
echo 'CCACHE_COMPILERCHECK=%compiler% -dumpfullversion -dumpversion' >> "$GITHUB_ENV"
fi
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Add GCC Problem Matcher
if: runner.os == 'Linux'
run: |
echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/gcc.json"
- name: Add Clang Problem Matcher
if: runner.os == 'macOS'
run: |
echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/clang.json"
- name: Add MSVC++ Problem Matcher
if: runner.os == 'Windows'
run: |
echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/msvc.json"
- name: Make needed directories, files and initializations
id: Init
run: |
mkdir -p ${{ env.builddir }}
mkdir -p ${{ env.logdir }}
mkdir -p ${{ env.reportdir }}
echo "reportFile=${{ env.reportfilename }}" >> $GITHUB_OUTPUT
- uses: prefix-dev/setup-pixi@a0af7a228712d6121d37aba47adf55c1332c9c2e # v0.9.4
with:
pixi-version: v0.59.0
cache: false
- name: Restore Compiler Cache
id: cache-restore
if: always()
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ env.CCACHE_DIR }}
key: FC-${{ env.cacheKey }}-${{ github.ref }}-${{ github.run_id }}
restore-keys: |
FC-${{ env.cacheKey }}-${{ github.ref }}-
FC-${{ env.cacheKey }}-
- name: Print CCache statistics before build, reset stats and print config
run: |
pixi run ccache -s
pixi run ccache -z
pixi run ccache -p
- name: CMake Configure
run: |
pixi run configure-${{ env.config }}
- name: CMake Build
run: |
pixi run build-${{ env.config }}
- name: Print ccache statistics after Build
if: always()
run: |
pixi run ccache -s
- name: FreeCAD CLI tests on build dir
if: inputs.testOnBuildDir
timeout-minutes: 10
uses: ./.github/workflows/actions/runPythonTests
with:
testDescription: "CLI tests on build dir"
testCommand: pixi run ${{ env.builddir }}/bin/FreeCADCmd -t 0
logFile: ${{ env.logdir }}TestCLIBuild.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: FreeCAD GUI tests on build dir
if: runner.os == 'Linux' && inputs.testOnBuildDir
timeout-minutes: 15
uses: ./.github/workflows/actions/runPythonTests
with:
testDescription: "GUI tests on build dir"
testCommand: pixi run xvfb-run ${{ env.builddir }}/bin/FreeCAD -t 0
logFile: ${{ env.logdir }}TestGUIBuild.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: C++ tests
timeout-minutes: 10
if: runner.os != 'Windows'
uses: ./.github/workflows/actions/runCPPTests/runAllTests
with:
reportdir: ${{ env.reportdir }}
builddir: ${{ env.builddir }}
reportFile: ${{ env.reportdir }}${{ env.reportfilename }}
- name: CMake Install
run: |
pixi run install-${{ env.config }}
- name: FreeCAD CLI tests on install
if: runner.os != 'Windows'
timeout-minutes: 10
uses: ./.github/workflows/actions/runPythonTests
with:
testDescription: "CLI tests on install"
testCommand: pixi run FreeCADCmd -t 0
logFile: ${{ env.logdir }}TestCLIInstall.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: FreeCAD GUI tests on install
# if: runner.os == 'Linux'
# currently broken on Qt6 builds
if: false
timeout-minutes: 15
uses: ./.github/workflows/actions/runPythonTests
with:
testDescription: "GUI tests on install"
testCommand: pixi run xvfb-run FreeCAD -t 0
logFile: ${{ env.logdir }}TestGUIInstall.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: Save Compiler Cache
id: cache-save
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ env.CCACHE_DIR }}
key: FC-${{ env.cacheKey }}-${{ github.ref }}-${{ github.run_id }}
- name: Upload logs
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ${{ inputs.artifactBasename }}-${{ matrix.os }}-Logs
path: |
${{ env.logdir }}
/var/crash/*FreeCAD*
- name: Upload report
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ${{ env.reportfilename }}
path: |
${{env.reportdir}}${{ env.reportfilename }}

239
.github/workflows/sub_buildUbuntu.yml vendored Normal file
View File

@@ -0,0 +1,239 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
# This is a build and test workflow for CI of FreeCAD.
# This workflow aims at building and testing FreeCAD on Ubuntu 24.04 using GCC.
name: Build Ubuntu 24.04
on:
workflow_call:
inputs:
artifactBasename:
type: string
required: true
testOnBuildDir:
default: false
type: boolean
required: false
allowedToFail:
default: false
type: boolean
required: false
outputs:
reportFile:
value: ${{ jobs.Build.outputs.reportFile }}
jobs:
Build:
runs-on: ubuntu-24.04
continue-on-error: ${{ inputs.allowedToFail }}
env:
CCACHE_COMPILERCHECK: "%compiler% -dumpfullversion -dumpversion" # default:mtime
CCACHE_COMPRESS: true
CCACHE_COMPRESSLEVEL: 5
CCACHE_CONFIGPATH: ${{ github.workspace }}/ccache/config
CCACHE_DIR: ${{ github.workspace }}/ccache
CCACHE_MAXSIZE: 1G
CCACHE_NODIRECT: true
CCACHE_NOHASHDIR: true
CCACHE_NOINODECACHE: true
CCACHE_SLOPPINESS: "include_file_ctime,include_file_mtime,pch_defines,time_macros"
CC: /usr/bin/gcc
CXX: /usr/bin/g++
#CC: /usr/bin/clang
#CXX: /usr/bin/clang++
builddir: ${{ github.workspace }}/build/release/
config: release
logdir: /tmp/logs/
reportdir: /tmp/report/
reportfilename: ${{ inputs.artifactBasename }}-report.md
defaults:
run:
shell: bash
outputs:
reportFile: ${{ steps.Init.outputs.reportFile }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checking out source code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: true
- name: Install FreeCAD dependencies
run: ./package/ubuntu/install-apt-packages.sh
- name: Install FreeCAD Python test dependencies
run: |
sudo apt-get update -y -qq
sudo apt-get install -y -qq python3-pip
python3 -m pip install --upgrade pip
python3 -m pip install ifcopenshell==0.8.2 --break-system-packages
- name: Make needed directories, files and initializations
id: Init
run: |
mkdir -p ${{ env.CCACHE_DIR }}
mkdir -p ${{ env.CCACHE_CONFIGPATH }}
mkdir -p ${{ env.builddir }}
mkdir -p ${{ env.logdir }}
mkdir -p ${{ env.reportdir }}
echo "reportFile=${{ env.reportfilename }}" >> $GITHUB_OUTPUT
- name: Generate cache key
id: genCacheKey
uses: ./.github/workflows/actions/linux/generateCacheKey
with:
compiler: ${{ env.CXX }}
qt_major_version: 5
- name: Restore Compiler Cache
id: cache-restore
if: always()
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ env.CCACHE_DIR }}
key: FC-${{ steps.genCacheKey.outputs.cacheKey }}-${{ github.ref }}-${{ github.run_id }}
restore-keys: |
FC-${{ steps.genCacheKey.outputs.cacheKey }}-${{ github.ref }}-
FC-${{ steps.genCacheKey.outputs.cacheKey }}-
- name: Print CCache statistics before build, reset stats and print config
run: |
ccache -s
ccache -z
ccache -p
- name: Install cmake
uses: jwlawson/actions-setup-cmake@3a6cbe35ba64df7ca70c51365c4aff65db9a9037 # v2.1.1
with:
cmake-version: '3.31.6'
- name: CMake Configure
uses: ./.github/workflows/actions/linux/configure
with:
extraParameters: -G Ninja --preset ${{ env.config }}
builddir: ${{ env.builddir }}
logFile: ${{ env.logdir }}Cmake.log
errorFile: ${{ env.logdir }}CmakeErrors.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: CMake Build
uses: ./.github/workflows/actions/linux/build
with:
builddir: ${{ env.builddir }}
logFile: ${{ env.logdir }}Build.log
errorFile: ${{ env.logdir }}BuildErrors.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: Print ccache statistics after Build
if: always()
run: |
ccache -s
- name: FreeCAD CLI tests on build dir
if: inputs.testOnBuildDir
timeout-minutes: 10
uses: ./.github/workflows/actions/runPythonTests
with:
testDescription: "CLI tests on build dir"
testCommand: ${{ env.builddir }}/bin/FreeCADCmd -t 0
logFile: ${{ env.logdir }}TestCLIBuild.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: FreeCAD GUI tests on build dir
if: inputs.testOnBuildDir
timeout-minutes: 15
uses: ./.github/workflows/actions/runPythonTests
with:
testDescription: "GUI tests on build dir"
# Use Python wrapper to run only GUI-registered tests with the built FreeCAD executable
testCommand: xvfb-run python3 ./.github/scripts/run_gui_tests.py "${{ env.builddir }}"
logFile: ${{ env.logdir }}TestGUIBuild.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: C++ tests
timeout-minutes: 1
uses: ./.github/workflows/actions/runCPPTests/runAllTests
with:
reportdir: ${{ env.reportdir }}
builddir: ${{ env.builddir }}
reportFile: ${{ env.reportdir }}${{ env.reportfilename }}
- name: CMake Install
uses: ./.github/workflows/actions/linux/install
with:
builddir: ${{ env.builddir }}
logFile: ${{ env.logdir }}Install.log
errorFile: ${{ env.logdir }}InstallErrors.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: FreeCAD CLI tests on install
timeout-minutes: 10
uses: ./.github/workflows/actions/runPythonTests
with:
testDescription: "CLI tests on install"
testCommand: FreeCADCmd -t 0
logFile: ${{ env.logdir }}TestCLIInstall.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: FreeCAD GUI tests on install
timeout-minutes: 15
uses: ./.github/workflows/actions/runPythonTests
with:
testDescription: "GUI tests on install"
# Use Python wrapper to run only GUI-registered tests using the installed FreeCAD
testCommand: xvfb-run python3 ./.github/scripts/run_gui_tests.py "FreeCAD"
logFile: ${{ env.logdir }}TestGUIInstall.log
reportFile: ${{env.reportdir}}${{ env.reportfilename }}
- name: Save Compiler Cache
id: cache-save
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ env.CCACHE_DIR }}
key: FC-${{ steps.genCacheKey.outputs.cacheKey }}-${{ github.ref }}-${{ github.run_id }}
- name: Upload logs
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ${{ inputs.artifactBasename }}-Logs
path: |
${{ env.logdir }}
/var/crash/*FreeCAD*
- name: Upload report
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ${{ env.reportfilename }}
path: |
${{env.reportdir}}${{ env.reportfilename }}

183
.github/workflows/sub_buildWindows.yml vendored Normal file
View File

@@ -0,0 +1,183 @@
# ***************************************************************************
# * Copyright (c) 2023 0penBrain *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
# This is a build and test workflow for CI of FreeCAD.
# This workflow aims at building and testing FreeCAD on Windows using MSVC.
name: Build Windows
on:
workflow_dispatch:
inputs:
artifactBasename:
description: "Base name for artifact"
required: false
type: string
default: "FreeCAD"
allowedToFail:
description: "whether a failed build is allowed"
required: false
type: boolean
default: false
workflow_call:
inputs:
artifactBasename:
required: true
type: string
allowedToFail:
required: false
default: false
type: boolean
outputs:
reportFile:
value: ${{ jobs.Build.outputs.reportFile }}
jobs:
Build:
runs-on: windows-latest
continue-on-error: ${{ inputs.allowedToFail }}
env:
CCACHE_COMPILERCHECK: "%compiler%" # default:mtime
CCACHE_COMPRESS: true
CCACHE_COMPRESSLEVEL: 5
CCACHE_DIR: C:/FC/cache/
CCACHE_LOGFILE: C:/logs/ccache.log
CCACHE_MAXSIZE: 1G
CCACHE_NODIRECT: true
CCACHE_NOHASHDIR: true
CCACHE_NOINODECACHE: true
CCACHE_SLOPPINESS: "include_file_ctime,include_file_mtime,pch_defines,time_macros" # Can't get PCH to work on Windows
## Have to use C:\ because not enough space on workspace drive
builddir: C:/FC/build/release/
cacheKey: Windows
ccachebindir: C:/FC/ccache/
config: release
libpackdir: C:/FC/libpack/
logdir: C:/logs/
reportdir: C:/report/
reportfilename: ${{ inputs.artifactBasename }}-report.md
outputs:
reportFile: ${{ steps.Init.outputs.reportFile }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checking out source code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: true
- name: Make needed directories, files and initializations
id: Init
run: |
mkdir ${{ env.CCACHE_DIR }}
mkdir ${{ env.ccachebindir }}
mkdir ${{ env.libpackdir }}
mkdir ${{ env.builddir }}
mkdir ${{ env.logdir }}
mkdir ${{ env.reportdir }}
echo "reportFile=${{ env.reportfilename }}" >> $GITHUB_OUTPUT
- name: Get Ccache
uses: ./.github/workflows/actions/windows/getCcache
with:
ccachebindir: ${{ env.ccachebindir }}
- name: Get Libpack
uses: ./.github/workflows/actions/windows/getLibpack
with:
libpackdir: ${{ env.libpackdir }}
- name: Restore compiler cache
id: cache-restore
if: always()
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ env.CCACHE_DIR }}
key: FC-${{ env.cacheKey }}-${{ github.ref }}-${{ github.run_id }}
restore-keys: |
FC-${{ env.cacheKey }}-${{ github.ref }}-
FC-${{ env.cacheKey }}-
- name: Print Ccache statistics before build, reset stats and print config
run: |
. $env:ccachebindir\ccache -s
. $env:ccachebindir\ccache -z
. $env:ccachebindir\ccache -p
- name: Install cmake
uses: jwlawson/actions-setup-cmake@3a6cbe35ba64df7ca70c51365c4aff65db9a9037 # v2.1.1
with:
cmake-version: '3.31.6'
- name: Configuring CMake
run: >
cmake -B"${{ env.builddir }}" .
--preset ${{ env.config }}
-DCMAKE_VS_NO_COMPILE_BATCHING=ON
-DCMAKE_BUILD_TYPE=Release
-DFREECAD_USE_PCH=OFF
-DFREECAD_RELEASE_PDB=OFF
-DFREECAD_LIBPACK_DIR="${{ env.libpackdir }}"
-DFREECAD_COPY_DEPEND_DIRS_TO_BUILD=ON
-DFREECAD_COPY_LIBPACK_BIN_TO_BUILD=ON
-DFREECAD_COPY_PLUGINS_BIN_TO_BUILD=ON
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0
- name: Compiling sources
run: |
cd $env:builddir
msbuild ALL_BUILD.vcxproj /m /p:Configuration=Release /p:TrackFileAccess=false /p:CLToolPath=${{ env.ccachebindir }}
- name: Print Ccache statistics after build
if: always()
run: |
. $env:ccachebindir\ccache -s
- name: C++ unit tests
if: false # Disabled because seems to not function on Windows build
timeout-minutes: 1
run: |
. ${{ env.builddir }}\tests\Release\Tests_run --gtest_output=json:${{ env.reportdir }}gtest_results.json # 2>&1 | tee -filepath ${{ env.logdir }}\unitTests.log
- name: FreeCAD CLI tests
run: |
. ${{ env.builddir }}\bin\FreeCADCmd -t 0 # 2>&1 | tee -filepath ${{ env.logdir }}\integrationTests.log
- name: Save Compiler Cache
id: cache-save
if: always()
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ env.CCACHE_DIR }}
key: FC-${{ env.cacheKey }}-${{ github.ref }}-${{ github.run_id }}
- name: Upload logs
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ${{ inputs.artifactBasename }}-Logs
path: |
${{ env.logdir }}

371
.github/workflows/sub_lint.yml vendored Normal file
View File

@@ -0,0 +1,371 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
# This is the lint workflow for CI of FreeCAD
name: Lint
on:
workflow_call:
inputs:
artifactBasename:
type: string
required: true
changedFiles:
type: string
required: true
changedLines:
type: string
required: false
changedCppFiles:
type: string
required: true
changedCppLines:
type: string
required: false
changedPythonFiles:
type: string
required: true
changedPythonLines:
type: string
required: false
checkLineendings:
default: false
type: boolean
required: false
lineendingsFailSilent:
default: true
type: boolean
required: false
checkWhitespace:
default: true
type: boolean
required: false
whitespaceFailSilent:
default: true
type: boolean
required: false
checkTabs:
default: true
type: boolean
required: false
tabsFailSilent:
default: true
type: boolean
required: false
checkQtConnections:
default: true
type: boolean
required: false
qtConnectionsFailSilent:
default: true
type: boolean
required: false
checkCpplint:
default: true
type: boolean
required: false
cpplintFailSilent:
default: true
type: boolean
required: false
checkPylint:
default: true
type: boolean
required: false
pylintDisable:
default: C0302,C0303 # Trailing whitespaces (C0303) are already checked
type: string
required: false
pylintFailSilent:
default: true
type: boolean
required: false
checkBlack:
default: true
type: boolean
required: false
blackFailSilent:
default: true
type: boolean
required: false
checkClangFormat:
default: false
type: boolean
required: false
clangStyle:
default: file # for .clang-format file
type: string
required: false
clangFormatFailSilent:
default: true
type: boolean
required: false
checkSpelling:
default: true
type: boolean
required: false
listIgnoredMisspelling:
default: .github/codespellignore
type: string
required: false
spellingIgnore:
default: ./.git*,*.po,*.ts,*.svg,./src/3rdParty,./src/Base/swig*,./src/Mod/Robot/App/kdl_cp,./src/Mod/Import/App/SCL*,./src/Doc/FreeCAD.uml,./build/
type: string
required: false
codespellFailSilent:
default: true
type: boolean
required: false
checkClangTidy:
default: true
type: boolean
required: false
clangTidyFailSilent:
default: true # warnings or notes will never fail the CI, only errors
type: boolean
required: false
checkClazy: # for the Message codes see: https://invent.kde.org/sdk/clazy#list-of-checks
default: false
type: boolean
required: false
clazyChecks:
default: level2,no-non-pod-global-static,no-copyable-polymorphic
type: string
required: false
clazyFailSilent:
default: true # warnings or notes will never fail the CI, only errors
type: boolean
required: false
checkClazyQT6:
default: false
type: boolean
required: false
clazyQT6Checks:
default: qt6-deprecated-api-fixes,qt6-header-fixes,qt6-qhash-signature,qt6-fwd-fixes,missing-qobject-macro # for QT6 Porting https://invent.kde.org/sdk/clazy#list-of-checks
type: string
required: false
QT6Branch: # branch to check for QT6 Porting
default: main
type: string
required: false
clazyQT6FailSilent:
default: true # warnings or notes will never fail the CI, only errors
type: boolean
required: false
outputs:
reportFile:
value: ${{ jobs.Lint.outputs.reportFile }}
permissions:
contents: read
jobs:
Lint:
if: inputs.changedFiles != ''
runs-on: ubuntu-latest
env:
logdir: /tmp/logs/
fixesdir: /tmp/fixes/
reportdir: /tmp/report/
reportfilename: ${{ inputs.artifactBasename }}-report.md
defaults:
run:
shell: bash
outputs:
reportFile: ${{ steps.Init.outputs.reportFile }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: true
- name: Make needed directories, files and initializations
id: Init
run: |
mkdir -p ${{ env.logdir }}
mkdir -p ${{ env.fixesdir }}
mkdir -p ${{ env.reportdir }}
echo "reportFile=${{ env.reportfilename }}" >> $GITHUB_OUTPUT
# Generic lints steps
- name: Check File Case Sensitivity
uses: credfeto/action-case-checker@cb652aeab29ed363bbdb7d9ee1bfcc010c46cac5 # v1.3.0
- name: Check for non Unix line ending
if: inputs.checkLineendings && always()
continue-on-error: ${{ inputs.lineendingsFailSilent }}
run: |
python3 tools/lint/generic_checks.py \
--lineendings-check \
--files "${{ inputs.changedFiles }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}" \
--log-dir "${{ env.logdir }}"
- name: Check for trailing whitespaces
if: inputs.checkWhitespace && always()
continue-on-error: ${{ inputs.whitespaceFailSilent }}
run: |
python3 tools/lint/generic_checks.py \
--whitespace-check \
--files "${{ inputs.changedFiles }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}" \
--log-dir "${{ env.logdir }}"
- name: Check for Tab usage
if: inputs.checkTabs && always()
continue-on-error: ${{ inputs.tabsFailSilent }}
run: |
python3 tools/lint/generic_checks.py \
--tabs-check \
--files "${{ inputs.changedFiles }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}" \
--log-dir "${{ env.logdir }}"
# Python linting steps
- name: Pylint
if: inputs.checkPylint && inputs.changedPythonFiles != '' && always()
continue-on-error: ${{ inputs.pylintFailSilent }}
run: |
python3 tools/lint/pylint.py \
--files "${{ inputs.changedPythonFiles }}" \
--disable "${{ inputs.pylintDisable }}" \
--log-dir "${{ env.logdir }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}"
- name: Black
if: inputs.checkBlack && inputs.changedPythonFiles != '' && always()
continue-on-error: ${{ inputs.blackFailSilent }}
run: |
python3 tools/lint/black.py \
--files "${{ inputs.changedPythonFiles }}" \
--log-dir "${{ env.logdir }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}"
# C++ linting steps
- name: Install FreeCAD dependencies
if: inputs.changedCppFiles != '' && always()
run: ./package/ubuntu/install-apt-packages.sh
- name: Run CMake # This is needed for Clang tools to work correctly
if: inputs.changedCppFiles != '' && always()
run: |
mkdir build && cmake -S ./ -B ./build/ -D CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE
- name: Check old Qt string-based connections (https://wiki.qt.io/New_Signal_Slot_Syntax)
if: inputs.checkQtConnections && inputs.changedCppFiles != '' && always()
continue-on-error: ${{ inputs.qtConnectionsFailSilent }}
run: |
python3 tools/lint/qt_connections.py \
--files "${{ inputs.changedFiles }}" \
--log-dir "${{ env.logdir }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}"
- name: Cpplint
if: inputs.checkCpplint && inputs.changedCppFiles != '' && always()
continue-on-error: ${{ inputs.cpplintFailSilent }}
run: |
python3 tools/lint/cpplint.py \
--files "${{ inputs.changedCppFiles }}" \
--log-dir "${{ env.logdir }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}"
- name: Clang-format
if: inputs.checkClangFormat && inputs.changedCppFiles != '' && always()
continue-on-error: ${{ inputs.clangFormatFailSilent }}
run: |
python3 tools/lint/clang_format.py \
--files "${{ inputs.changedCppFiles }}" \
--clang-style "${{ inputs.clangStyle }}" \
--log-dir "${{ env.logdir }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}"
- name: Codespell
if: inputs.checkSpelling && always()
continue-on-error: ${{ inputs.codespellFailSilent }}
run: |
python3 tools/lint/codespell.py \
--files ${{ inputs.changedFiles }} \
--ignore-words "${{ inputs.listIgnoredMisspelling }}" \
--skip "${{ inputs.spellingIgnore }}" \
--log-dir "${{ env.logdir }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}"
- name: Clang-tidy
if: inputs.checkClangTidy && inputs.changedCppFiles != '' && always()
continue-on-error: ${{ inputs.clangTidyFailSilent }}
run: |
python3 tools/lint/clang_tidy.py \
--files "${{ inputs.changedCppFiles }}" \
--line-filter '${{ inputs.changedCppLines }}' \
--clang-style "${{ inputs.clangStyle }}" \
--log-dir "${{ env.logdir }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}"
- name: Clazy
if: inputs.checkClazy && inputs.changedCppFiles != '' && always()
continue-on-error: ${{ inputs.clazyFailSilent }}
run: |
python3 tools/lint/clazy.py \
--files "${{ inputs.changedCppFiles }}" \
--clazy-checks "${{ inputs.clazyChecks }}" \
--log-dir "${{ env.logdir }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}"
- name: Clazy-QT6
if: inputs.checkClazyQT6 && inputs.changedCppFiles != '' && github.ref == inputs.QT6Branch && always()
continue-on-error: ${{ inputs.clazyQT6FailSilent }}
run: |
python3 tools/lint/clazy_qt6.py \
--files "${{ inputs.changedCppFiles }}" \
--clazy-qt6-checks "${{ inputs.clazyQT6Checks }}" \
--log-dir "${{ env.logdir }}" \
--report-file "${{ env.reportdir }}${{ env.reportfilename }}"
# Upload steps
- name: Upload logs and fixes
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ${{ inputs.artifactBasename }}-Logs
path: |
${{ env.logdir }}
${{ env.fixesdir }}
- name: Upload report
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ${{ env.reportfilename }}
path: |
${{env.reportdir}}${{ env.reportfilename }}

194
.github/workflows/sub_prepare.yml vendored Normal file
View File

@@ -0,0 +1,194 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
# This is the pre-check workflow for CI of FreeCAD.
# It aims at running some basic checks about the workflow run ...
# ... and gathering some data needed for the next steps.
name: Prepare
on:
workflow_call:
inputs:
artifactBasename:
type: string
required: true
dontFailOnOldRebase:
default: true
type: boolean
required: false
maxRebaseHours:
default: "48"
type: string
required: false
outputs:
reportFile:
value: ${{ jobs.Prepare.outputs.reportFile }}
changedFiles:
value: ${{ jobs.Prepare.outputs.changedFiles }}
changedLines:
value: ${{ jobs.Prepare.outputs.changedLines }}
changedPythonFiles:
value: ${{ jobs.Prepare.outputs.changedPythonFiles }}
changedPythonLines:
value: ${{ jobs.Prepare.outputs.changedPythonLines }}
changedCppFiles:
value: ${{ jobs.Prepare.outputs.changedCppFiles }}
changedCppLines:
value: ${{ jobs.Prepare.outputs.changedCppLines }}
jobs:
Prepare:
env:
isPR: ${{ github.event_name == 'pull_request' }}
isPush: ${{ github.event_name == 'push' }}
logdir: /tmp/logs/
reportdir: /tmp/report/
reportfilename: ${{ inputs.artifactBasename }}-report.md
runs-on: ubuntu-latest
defaults:
run:
shell: bash
outputs:
reportFile: ${{ steps.Init.outputs.reportFile }}
changedFiles: ${{ steps.Output.outputs.changedFiles }}
changedLines: ${{ steps.Output.outputs.changedLines }}
changedPythonFiles: ${{ steps.Output.outputs.changedPythonFiles }}
changedPythonLines: ${{ steps.Output.outputs.changedPythonLines }}
changedCppFiles: ${{ steps.Output.outputs.changedCppFiles }}
changedCppLines: ${{ steps.Output.outputs.changedCppLines }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Make needed directories, files and initializations
id: Init
run: |
mkdir -p ${{ env.logdir }}
mkdir -p ${{ env.reportdir }}
commitCnt=0
touch ${{ env.logdir }}changedFiles.lst ${{ env.logdir }}changedCppFiles.lst ${{ env.logdir }}changedPythonFiles.lst
echo "reportFile=${{ env.reportfilename }}" >> $GITHUB_OUTPUT
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: true
- name: Determine base and head SHA in case of PR
if: env.isPR == 'true'
run: |
baseSha=${{ github.event.pull_request.base.sha }}
headSha=${{ github.event.pull_request.head.sha }}
echo "baseSha=$baseSha" >> $GITHUB_ENV
echo "headSha=$headSha" >> $GITHUB_ENV
echo "This CI run is performed on a Pull Request" | tee -a ${{env.reportdir}}${{ env.reportfilename }}
echo "Base SHA is $baseSha, Head SHA is $headSha" | tee -a ${{env.reportdir}}${{ env.reportfilename }}
- name: Check if PR has been recently rebased
if: env.isPR == 'true'
continue-on-error: ${{ inputs.dontFailOnOldRebase }}
run: |
baseDate=$(curl -H "Accept: application/vnd.github+json" -H "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/commits/$baseSha | jq -r '.commit.committer.date')
dateDiff=$(( ( $(date +%s) - $(date -d $baseDate +%s) ) / 3600 ))
echo "Pull request is based on a $dateDiff hour-old commit" | tee -a ${{env.reportdir}}${{ env.reportfilename }}
# Exit the step with appropriate code
if [ $dateDiff -gt ${{ inputs.maxRebaseHours }} ]
then
echo -n ":warning: Pull request should be rebased" | tee -a ${{env.reportdir}}${{ env.reportfilename }}
exit 1
fi
- name: Determine base and head SHA in case of push
if: env.isPush == 'true'
run: |
baseSha=${{ github.event.before }}
headSha=${{ github.event.after }}
echo "headSha=$headSha" >> $GITHUB_ENV
if [ $baseSha -eq 0 ]
then
echo "This CI run is performed on a Push that created a new branch : files changed will be ignored" | tee -a ${{env.reportdir}}${{ env.reportfilename }}
echo "Head SHA is $headSha" | tee -a ${{env.reportdir}}${{ env.reportfilename }}
echo "isPush='false'" >> $GITHUB_ENV
else
echo "This CI run is performed on a Push" | tee -a ${{env.reportdir}}${{ env.reportfilename }}
echo "Base SHA is $baseSha, Head SHA is $headSha" | tee -a ${{env.reportdir}}${{ env.reportfilename }}
echo "baseSha=$baseSha" >> $GITHUB_ENV
fi
- name: Get compare between head and base
if: env.isPR == 'true' || env.isPush == 'true'
run: |
echo "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/compare/$baseSha...$headSha"
curl -H "Accept: application/vnd.github+json" -H "authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/compare/$baseSha...$headSha > ${{ env.logdir }}compare.json
- name: Get number of commits in the changeset
if: env.isPR == 'true' || env.isPush == 'true'
run: |
commitCnt=$(jq -re '.ahead_by' ${{ env.logdir }}compare.json)
echo "Changeset is composed of $commitCnt commit(s)" | tee -a ${{env.reportdir}}${{ env.reportfilename }}
- name: Get files modified in changeset #TODO check what happens with deleted file in the subsequent process
if: env.isPR == 'true'
env:
API_URL: ${{ github.api_url }}
TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
REF: ${{ github.ref_name }}
PR: ${{ github.event.number }}
run: |
# could reduce this to a single
python3 tools/lint/changed_lines.py --api-url ${API_URL} --token ${TOKEN} --repo ${REPO} --ref=${REF} --pr=${PR} > ${{ env.logdir }}changedLines.lst
cat ${{ env.logdir }}changedLines.lst | jq '.[].name' > ${{ env.logdir }}changedFiles.lst
python3 tools/lint/changed_lines.py --api-url ${API_URL} --token ${TOKEN} --repo ${REPO} --ref=${REF} --pr=${PR} --file-filter '.py, .pyi' > ${{ env.logdir }}changedPythonLines.lst
cat ${{ env.logdir }}changedPythonLines.lst | jq '.[].name' > ${{ env.logdir }}changedPythonFiles.lst
python3 tools/lint/changed_lines.py --api-url ${API_URL} --token ${TOKEN} --repo ${REPO} --ref=${REF} --pr=${PR} --file-filter '.c, .cc, .cu, .cuh, .c++, .cpp, .cxx, .h, .hh, .h++, .hpp, .hxx' > ${{ env.logdir }}changedCppLines.lst
cat ${{ env.logdir }}changedCppLines.lst | jq '.[].name' > ${{ env.logdir }}changedCppFiles.lst
# Write the report
echo "::group::Modified files in changeset (removed files are ignored) :" ; cat ${{ env.logdir }}changedFiles.lst ; echo "::endgroup::"
echo "<details><summary>Modified files (removed files are ignored):</summary>" >> ${{env.reportdir}}${{ env.reportfilename }}
cat ${{ env.logdir }}changedFiles.lst >> ${{env.reportdir}}${{ env.reportfilename }}
echo "</details>" >> ${{env.reportdir}}${{ env.reportfilename }}
echo "" >> ${{env.reportdir}}${{ env.reportfilename }}
- name: Transmitting outputs
id: Output
run: |
echo "changedFiles=$(cat ${{ env.logdir }}changedFiles.lst | tr '\n' ' ')" >> $GITHUB_OUTPUT
echo "changedLines=$(cat ${{ env.logdir }}changedLines.lst | tr '\n' ' ')" >> $GITHUB_OUTPUT
echo "changedPythonFiles=$(cat ${{ env.logdir }}changedPythonFiles.lst | tr '\n' ' ')" >> $GITHUB_OUTPUT
echo "changedPythonLines=$(cat ${{ env.logdir }}changedPythonLines.lst | tr '\n' ' ')" >> $GITHUB_OUTPUT
echo "changedCppFiles=$(cat ${{ env.logdir }}changedCppFiles.lst | tr '\n' ' ')" >> $GITHUB_OUTPUT
echo "changedCppLines=$(cat ${{ env.logdir }}changedCppLines.lst | tr '\n' ' ')" >> $GITHUB_OUTPUT
echo "" >> $GITHUB_OUTPUT
- name: Upload logs
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ${{ inputs.artifactBasename }}-Logs
path: |
${{ env.logdir }}
- name: Upload report
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: ${{ env.reportfilename }}
path: |
${{env.reportdir}}${{ env.reportfilename }}

125
.github/workflows/sub_wrapup.yml vendored Normal file
View File

@@ -0,0 +1,125 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2023 0penBrain. *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
# This is a generic wrapup workflow that only aims at gathering reports and ...
# ... presenting them as a unified summary
#
# It expects steps to be summarized to be presented a JSON input formatted ...
# ... as the "needs" context of Github :
# https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context
# In addition to standard "result", each step shall have a string entry in "outputs" ...
# ... named "reportFile" containing the name of the corresponding report. The ...
# ... report file shall be available in an artifact with the same name.
name: WrapUp
on:
workflow_call:
inputs:
previousSteps:
type: string
required: true
permissions:
contents: read
jobs:
WrapUp:
runs-on: ubuntu-latest
env:
artifactsDownloadDir: /tmp/artifacts/
defaults:
run:
shell: bash
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Make needed directories, files and initializations
run: |
mkdir -p ${{ env.artifactsDownloadDir }}
- name: Download artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
path: ${{ env.artifactsDownloadDir }}
- name: Save input data to file
run: |
cat > data << "EOD"
${{ inputs.previousSteps }}
EOD
- name: Compute the report
run: |
echo "usedArtifacts<<EOD" >> $GITHUB_ENV
for step in $(jq -r "keys_unsorted | .[]" data)
do
echo "Processing step $step"
result=$(jq -r ".\"$step\".result" data)
icon=":heavy_check_mark:"
if [ $result == 'failure' ]
then
icon=":x:"
elif [ $result == 'cancelled' ]
then
icon=":no_entry_sign:"
elif [ $result == 'skipped' ]
then
icon=":white_check_mark:"
fi
echo "### $icon $step step" >> report.md
if [ $result == 'skipped' ]
then
echo "Step was skipped, no report was generated" | tee -a report.md
continue
elif [ $result == 'cancelled' ]
then
echo "Step was cancelled when executing, report may be incomplete" | tee -a report.md
fi
report=$(jq -r ".\"$step\".outputs.reportFile" data)
if [ $report ]
then
echo "Report for step $step is $report"
echo "$report" >> $GITHUB_ENV
if [ $(find ${{ env.artifactsDownloadDir }} -type f -name $report | wc -l) -eq 1 ]
then
find ${{ env.artifactsDownloadDir }} -type f -name $report -exec cat {} \; >> report.md
else
echo "No or several files found for report $report, not printing" | tee -a report.md
echo "Below files found :"
find ${{ env.artifactsDownloadDir }} -type f -name $report
fi
else
echo "Report file was not set by step $step" | tee -a report.md
fi
echo "" >> report.md
done
echo "EOD" >> $GITHUB_ENV
cat report.md >> $GITHUB_STEP_SUMMARY
- name: Delete used artifacts
continue-on-error: true
uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # v5.1.0
with:
name: |
${{ env.usedArtifacts }}

16
.github/workflows/weekly-build-notes.md vendored Normal file
View File

@@ -0,0 +1,16 @@
> [!IMPORTANT]
> Bleeding edge FreeCAD development builds for testing bugfixes, regressions, and recently implemented features. Do not use in a production environment.
**Changes since last weekly:** <!--DIFF_LINK-->
### How-to use
1. Download the appropriate asset for your OS below
2. Unpack the bundle to any folder on your system
3. Launch the application
- **Windows**
Run `\FreeCAD.exe` in the extracted directory
- **macOS**
Launch `/FreeCAD.app` in the extracted directory
- **Linux**
Open the `*.AppImage`

View File

@@ -0,0 +1,140 @@
name: Weekly compare link to release notes
on:
release:
types: [published] # run automatically when a (pre-)release is published
workflow_dispatch: # allow manual runs too
inputs:
current_tag:
description: "Weekly tag (e.g., weekly-2026.01.07). Leave empty to auto-detect latest weekly pre-release."
required: false
dry_run:
description: "Only compute; do not update the release body."
required: false
type: boolean
default: false
permissions:
contents: write # required to PATCH the release body
jobs:
update-notes:
runs-on: ubuntu-latest
steps:
- name: Inject compare link into weekly release notes
uses: actions/github-script@v7
env:
# Pass manual inputs via env for convenience
CURRENT_TAG: ${{ github.event.inputs.current_tag }}
DRY_RUN: ${{ github.event.inputs.dry_run }}
PLACEHOLDER: "<!--DIFF_LINK-->"
with:
script: |
// Updates the current weekly release notes with a compare link to the previous weekly.
// Works for both release-published events and manual (workflow_dispatch) runs.
const owner = context.repo.owner;
const repo = context.repo.repo;
const rx = /^weekly-(\d{4})\.(\d{2})\.(\d{2})$/;
// Determine currentTag:
// 1) Manual input via workflow_dispatch (env.CURRENT_TAG)
// 2) Tag from release event payload
// 3) Fallback: newest weekly pre-release
let currentTag = process.env.CURRENT_TAG || (context.payload?.release?.tag_name) || null;
async function detectLatestWeeklyTag() {
const releases = await github.paginate(github.rest.repos.listReleases, { owner, repo, per_page: 100 });
const cand = releases.find(r => r.prerelease && typeof r.tag_name === 'string' && rx.test(r.tag_name));
return cand?.tag_name || null;
}
if (!currentTag) {
currentTag = await detectLatestWeeklyTag();
}
if (!currentTag || !rx.test(currentTag)) {
core.info(`No weekly tag detected or tag format mismatch ('${currentTag}'). Skipping.`);
return;
}
// Resolve the current release object
let curRel;
try {
const { data } = await github.rest.repos.getReleaseByTag({ owner, repo, tag: currentTag });
curRel = data;
} catch (e) {
core.setFailed(`No release for tag ${currentTag}: ${e.message}`);
return;
}
// If event is a normal release (not pre-release), skip automatically-run case;
// but allow manual override (manual runs can patch any weekly tag).
const isManual = context.eventName === 'workflow_dispatch';
if (!isManual && !curRel.prerelease) {
core.info('Current release is not a pre-release; skipping (auto run).');
return;
}
// Helpers
const toPrevWeeklyTag = (tag) => {
const [, y, m, d] = tag.match(rx);
const dt = new Date(Date.UTC(+y, +m - 1, +d));
const prev = new Date(dt.getTime() - 7 * 24 * 3600 * 1000); // minus 7 days
const iso = prev.toISOString().slice(0, 10); // YYYY-MM-DD
return `weekly-${iso.replace(/-/g, '.')}`; // weekly-YYYY.MM.DD
};
const ymdKey = (t) => t.replace(rx, '$1$2$3'); // YYYYMMDD
async function tagExists(tag) {
try {
await github.rest.git.getRef({ owner, repo, ref: `tags/${tag}` });
return true;
} catch {
return false;
}
}
// Compute previous weekly deterministically, then fall back if needed
let prevTag = toPrevWeeklyTag(currentTag);
if (!(await tagExists(prevTag))) {
core.info(`Computed previous tag ${prevTag} not found; scanning older weeklies...`);
const releases = await github.paginate(github.rest.repos.listReleases, { owner, repo, per_page: 100 });
const curKey = ymdKey(currentTag);
const older = releases
.filter(r => r.prerelease && typeof r.tag_name === 'string' && rx.test(r.tag_name))
.map(r => ({ tag: r.tag_name, key: ymdKey(r.tag_name) }))
.filter(x => x.key < curKey)
.sort((a, b) => b.key.localeCompare(a.key)); // newest older first
if (!older.length) {
core.info('No older weekly found; nothing to do.');
return;
}
prevTag = older[0].tag;
}
const compareUrl = `https://github.com/${owner}/${repo}/compare/${prevTag}...${currentTag}`;
const head = `**Changes since last weekly (${prevTag} → ${currentTag}):**\n${compareUrl}\n`;
if (process.env.DRY_RUN === 'true') {
core.info(`[DRY RUN] Would update release ${currentTag} with: ${compareUrl}`);
return;
}
// Idempotent body update
let body = curRel.body || '';
if (body.includes(compareUrl)) {
core.info('Compare URL already present; done.');
return;
}
const placeholder = process.env.PLACEHOLDER || '<!--DIFF_LINK-->';
if (body.includes(placeholder)) {
body = body.replace(placeholder, compareUrl);
} else if (/\*\*Changes since last weekly:/i.test(body)) {
body = body.replace(/\*\*Changes since last weekly:[^\n]*\n?/i, head + '\n');
} else {
body += (body.endsWith('\n') ? '\n' : '\n\n') + head;
}
await github.rest.repos.updateRelease({ owner, repo, release_id: curRel.id, body });
core.info(`Release notes updated with compare link: ${compareUrl}`);

3
.gitignore vendored
View File

@@ -9,6 +9,7 @@
!/.gitmodules
!/.pre-commit-config.yaml
!/.pylintrc
!/.github/
!/.gitea/
!/contrib/.vscode/
!*.gitattributes
@@ -76,5 +77,3 @@ docs/book/
# To regenerate themed icons: python3 icons/retheme.py
# icons/themed/ is tracked (committed) so CI builds include them
CLAUDE.md

16
.gitmodules vendored
View File

@@ -10,19 +10,9 @@
[submodule "src/Mod/AddonManager"]
path = src/Mod/AddonManager
url = https://github.com/FreeCAD/AddonManager.git
[submodule "mods/ztools"]
path = mods/ztools
url = https://git.kindred-systems.com/forbes/ztools.git
[submodule "mods/silo"]
path = mods/silo
url = https://git.kindred-systems.com/kindred/silo-mod.git
branch = main
[submodule "mods/solver"]
path = mods/solver
url = https://git.kindred-systems.com/kindred/solver.git
branch = main
[submodule "mods/gears"]
path = mods/gears
url = https://git.kindred-systems.com/kindred/gears.git
branch = main
[submodule "mods/datums"]
path = mods/datums
url = https://git.kindred-systems.com/kindred/datums.git
branch = main

View File

@@ -1,5 +0,0 @@
forbes <contact@kindred-systems.com> forbes <joseph.forbes@kindred-systems.com>
forbes <contact@kindred-systems.com> forbes <zoe.forbes@kindred-systems.com>
forbes <contact@kindred-systems.com> forbes-0023 <joseph.forbes@kindred-systems.com>
forbes <contact@kindred-systems.com> josephforbes23 <joseph.forbes@kindred-systems.com>
forbes <contact@kindred-systems.com> Zoe Forbes <forbes@copernicus-9.kindred.internal>

68
.packit.yaml Normal file
View File

@@ -0,0 +1,68 @@
specfile_path: package/fedora/freecad.spec
files_to_sync:
- .packit.yaml
actions:
post-upstream-clone:
- bash -c '/usr/bin/python3 package/scripts/write_version_info.py freecad_version.txt'
- rm -f freecad-sources.tar.gz
changelog-entry:
- bash -c 'git log --no-merges --pretty="format:- %s (%an)" $(git describe --tags --abbrev=0 )..HEAD -- |sed 's/%/%%/g''
create-archive:
- git submodule update --init
- bash -c 'git ls-files --recurse-submodules | tar -caf freecad-sources.tar.gz -T-'
- echo -n 'freecad-sources.tar.gz'
downstream_package_name: freecad
additional_packages:
- python3
jobs:
- job: copr_build
identifier: pull_request
trigger: pull_request
notifications:
pull_request:
successful_build: True
branch: main
additional_repos:
- copr://bpostle/IfcOpenShell
manual_trigger: true
targets:
fedora-stable:
without_opts:
- debug_info
fedora-development:
without_opts:
- debug_info
- job: tests
identifier: pull_request
trigger: pull_request
branch: main
manual_trigger: true
targets:
fedora-latest-stable:
without_opts:
- debug_info
fmf_path: package/fedora/tests
- job: copr_build
identifier: main
trigger: commit
manual_trigger: true
branch: main
additional_repos:
- copr://bpostle/IfcOpenShell
owner: freecad
project: nightly
- job: copr_build
identifier: release
trigger: release
owner: freecad
project: freecad
additional_repos:
- copr://bpostle/IfcOpenShell
targets:
fedora-all:
without_opts:
- tests

View File

@@ -4,6 +4,7 @@
# See https://pre-commit.com/hooks.html for more hooks
files: |
(?x)^(
.github|
src/Base|
src/Gui|
src/Main|
@@ -59,7 +60,7 @@ exclude: |
.*\.brep$
)
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
hooks:
- id: trailing-whitespace
@@ -67,12 +68,12 @@ repos:
- id: check-yaml
- id: check-added-large-files
- id: mixed-line-ending
- repo: https://github.com/psf/black-pre-commit-mirror
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 2892f1f81088477370d4fbc56545c05d33d2493f # frozen: 25.11.0
hooks:
- id: black
args: ["--line-length", "100"]
- repo: https://github.com/pre-commit/mirrors-clang-format
args: ['--line-length', '100']
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: 317810f3c6a0ad3572367dc86cb6e41863e16e08 # frozen: v21.1.5
hooks:
- id: clang-format

347
CLAUDE.md
View File

@@ -1,347 +0,0 @@
# CLAUDE.md — Developer Context for Kindred Create
## Project Overview
Kindred Create is a fork of FreeCAD 1.0+ that adds integrated tooling for professional engineering workflows. It ships a context-aware UI system, five addon modules (SDK, Gears, Datums, Silo, Solver), a KCSDK C++ extension layer, a Catppuccin Mocha dark theme, and a pluggable file origin layer on top of FreeCAD's parametric modeling core.
- **Kindred Create version:** 0.1.5
- **FreeCAD base version:** 1.2.0
- **License:** LGPL-2.1-or-later
- **Repository:** `git.kindred-systems.com/kindred/create` (Gitea)
- **Main branch:** `main`
## Quick Start
```bash
git clone --recursive ssh://git@git.kindred-systems.com:2222/kindred/create.git
cd create
pixi run configure # CMake configure (debug by default)
pixi run build # Build
pixi run install # Install to build dir
pixi run freecad # Launch
pixi run test # Run C++ tests (ctest)
pixi run test-kindred # Run Python/Kindred tests
```
Build variants: append `-debug` or `-release` (e.g., `pixi run build-release`). See `CMakePresets.json` for platform-specific presets (Linux x86_64/aarch64, macOS Intel/ARM, Windows x64).
## Repository Structure
```
create/
├── src/
│ ├── App/ Core application (C++)
│ ├── Base/ Base classes, type system, persistence (C++)
│ ├── Gui/ GUI framework (C++)
│ │ ├── SDK/ KCSDK C++ library (Kindred feature)
│ │ │ ├── bindings/ pybind11 module (kcsdk.so)
│ │ │ ├── IPanelProvider.h Dock panel interface
│ │ │ ├── IToolbarProvider.h Toolbar interface
│ │ │ ├── IMenuProvider.h Menu interface
│ │ │ ├── SDKRegistry.h Provider registry
│ │ │ ├── ThemeEngine.h Runtime theme token access
│ │ │ └── WidgetBridge.h Qt widget creation bridge
│ │ ├── EditingContext.h Editing context resolver (Kindred feature)
│ │ ├── BreadcrumbToolBar.h Breadcrumb navigation widget (Kindred feature)
│ │ ├── FileOrigin.h Abstract origin interface (Kindred feature)
│ │ ├── OriginManager.h Origin lifecycle management
│ │ ├── CommandOrigin.cpp Origin_Commit/Pull/Push/Info/BOM commands
│ │ ├── ApplicationPy.h All FreeCADGui.* Python bindings
│ │ ├── Application.h App signals (fastsignals)
│ │ ├── Stylesheets/ QSS theme files
│ │ └── PreferencePacks/ Preference configurations (build-time generated)
│ ├── Mod/ FreeCAD modules (PartDesign, Assembly, Sketcher, etc.)
│ │ └── Create/ Kindred Create module
│ │ ├── App/ C++ app-side module (AppCreate)
│ │ ├── Gui/ C++ GUI-side module (AppCreateGui)
│ │ ├── Init.py Console bootstrap — loads addons
│ │ ├── InitGui.py GUI bootstrap — loads addons, kc_format, update checker
│ │ ├── addon_loader.py Manifest-driven loader with dependency resolution
│ │ ├── kc_format.py .kc file format preservation
│ │ ├── silo_document.py .kc tree building observer
│ │ ├── silo_tree.py Silo metadata tree builder
│ │ ├── silo_objects.py Silo tree FeaturePython objects
│ │ ├── silo_viewers.py Silo tree MDI viewers
│ │ └── silo_viewproviders.py Silo tree ViewProvider icons
│ └── 3rdParty/ Vendored dependencies
│ ├── OndselSolver/ [submodule] Assembly constraint solver (forked)
│ ├── FastSignals/ Signal/slot library (NOT Boost)
│ └── GSL/ [submodule] Microsoft Guidelines Support Library
├── mods/ Kindred addon modules
│ ├── sdk/ Addon SDK — stable API contract (priority 0)
│ ├── gears/ [submodule] Parametric gear workbench (priority 40)
│ ├── datums/ [submodule] Unified datum creator (priority 45)
│ ├── silo/ [submodule] PLM workbench (priority 60)
│ └── solver/ [submodule] Assembly solver research (priority 10)
├── reference/ Archived addons (not built)
│ ├── ztools/ Archived — commands migrated to datums + core
│ └── quicknav/ Archived — navigation addon
├── docs/ mdBook documentation + architecture docs
├── tests/ C++ unit tests (GoogleTest)
├── package/ Packaging (debian/, rattler-build/)
├── resources/ Branding, icons, desktop integration
├── cMake/ CMake helper modules
├── .gitea/workflows/ CI/CD pipelines
├── CMakeLists.txt Root build configuration (CMake 3.22.0+)
├── CMakePresets.json Platform build presets
└── pixi.toml Pixi environment and build tasks
```
## Build System
- **Primary:** CMake 3.22.0+ with Ninja generator
- **Environment:** [Pixi](https://pixi.sh) (conda-forge) manages all dependencies
- **Key deps:** Qt 6.8.x, Python 3.11.x, OpenCASCADE 7.8.x, PySide6, Boost, VTK, SMESH
- **Presets:** `conda-linux-debug`, `conda-linux-release`, `conda-macos-debug`, `conda-macos-release`, `conda-windows-debug`, `conda-windows-release`
- **Tasks summary:**
| Task | Description |
|------|-------------|
| `pixi run configure` | CMake configure (debug) |
| `pixi run build` | Build (debug) |
| `pixi run install` | Install to build dir |
| `pixi run freecad` | Launch FreeCAD |
| `pixi run test` | C++ tests via ctest |
| `pixi run test-kindred` | Python/Kindred test suite |
## Architecture Patterns
### Signals — Use FastSignals, NOT Boost
```cpp
#include <fastsignals/signal.h>
// See src/Gui/Application.h:121-155 for signal declarations
```
All signals in `src/Gui/` use `fastsignals::signal`. Never use Boost.Signals2.
### Type Checking Across Modules
Avoid header dependencies between `src/Gui/` and `src/Mod/` by using runtime type checks:
```cpp
auto type = Base::Type::fromName("Sketcher::SketchObject");
if (obj->isDerivedFrom(type)) { ... }
```
### Python Bindings
All `FreeCADGui.*` functions go in `src/Gui/ApplicationPy.h` and `src/Gui/ApplicationPy.cpp`. Use `METH_VARARGS` only (no `METH_KEYWORDS` in this file). Do not create separate files for new Python bindings.
### KCSDK C++ Layer
The `src/Gui/SDK/` directory provides a C++ shared library (`libKCSDK`) with pybind11 bindings (`kcsdk` Python module). This is the stable C++ API for Kindred features:
- **`IPanelProvider`** — interface for registering dock panels
- **`IToolbarProvider`** — interface for toolbar registration
- **`IMenuProvider`** — interface for menu registration
- **`SDKRegistry`** — central registry for all providers
- **`ThemeEngine`** — runtime access to Catppuccin Mocha theme tokens
- **`WidgetBridge`** — Qt widget creation from Python factories
Python addons access these through `kindred_sdk.*` wrappers (e.g., `kindred_sdk.register_dock_panel()` routes through `kcsdk.register_panel()` + `kcsdk.create_panel()`). Do not use `kcsdk` directly from addons — always use `kindred_sdk`.
### Toolbar Visibility
Use `ToolBarItem::DefaultVisibility::Unavailable` to hide toolbars by default, then `ToolBarManager::setState(ForceAvailable)` to show them contextually. This pattern is proven by the Sketcher module.
The `appendToolbar` Python API accepts an optional 3rd argument: `"Visible"`, `"Hidden"`, or `"Unavailable"`.
### Editing Context System
The `EditingContextResolver` singleton (`src/Gui/EditingContext.h/.cpp`) drives the context-aware UI. It evaluates registered context definitions in priority order and activates the matching one, setting toolbar visibility and updating the `BreadcrumbToolBar`.
Built-in contexts: `sketcher.edit`, `assembly.edit`, `partdesign.feature`, `partdesign.body`, `assembly.idle`, `spreadsheet`, `empty_document`, `no_document`.
Python API (prefer `kindred_sdk` wrappers over direct `FreeCADGui` calls):
- `kindred_sdk.register_context()` — register a new context
- `kindred_sdk.register_overlay()` — conditional toolbar overlay
- `kindred_sdk.inject_commands()` — add commands to existing contexts
- `kindred_sdk.current_context()` — query active context
- `kindred_sdk.refresh_context()` — force re-evaluation
### Addon Loading
Addons in `mods/` are loaded by `src/Mod/Create/addon_loader.py`. Each addon provides a `package.xml` at the mod root with `<kindred>` extensions declaring version bounds, load priority, and dependencies. The loader discovers manifests, validates version bounds, and resolves load order via topological sort on dependencies, breaking ties by `(load_priority, name)`.
Current load order: **sdk** (0) → **solver** (10) → **gears** (40) → **datums** (45) → **silo** (60).
A `<workbench>` tag in `package.xml` is required for `InitGui.py` to be loaded, even if no actual workbench is registered. The `<subdirectory>` element within `<workbench>` specifies where `Init.py`/`InitGui.py` live relative to the mod root (e.g., `freecad` for silo, `datums` for datums, `./` for sdk).
### Deferred Initialization
GUI setup uses `QTimer.singleShot` with staggered delays. Create core (`src/Mod/Create/InitGui.py`) handles:
- 500ms: `.kc` file format registration
- 10000ms: Update checker
Each addon manages its own deferred setup in its `InitGui.py`. For example, Silo (`mods/silo/InitGui.py`) defers:
- 500ms: kindred:// URL handler
- 600ms: Document observer for .kc tree building
- 2000ms: Auth dock panel (via `kindred_sdk.register_dock_panel`)
- 2500ms: Silo overlay context
- 3000ms: First-start settings check
- 4000ms: Activity dock panel (via `kindred_sdk.register_dock_panel`)
### Unified Origin System
File operations (New, Open, Save, Commit, Pull, Push) are abstracted behind `FileOrigin` (`src/Gui/FileOrigin.h`). `LocalFileOrigin` handles local files; `SiloOrigin` (`mods/silo/freecad/silo_origin.py`) backs Silo-tracked documents. The active origin is selected automatically based on document properties (`SiloItemId`, `SiloPartNumber`).
Origins are registered via `kindred_sdk.register_origin()`. Query functions (`list_origins`, `active_origin`, `get_origin`, `set_active_origin`) route through the `kcsdk` C++ module.
## Submodules
| Path | Repository | Branch | Purpose |
|------|------------|--------|---------|
| `mods/silo` | `git.kindred-systems.com/kindred/silo-mod` | `main` | PLM workbench (includes silo-client submodule) |
| `mods/gears` | `git.kindred-systems.com/kindred/gears` | `main` | Parametric gear generation workbench |
| `mods/datums` | `git.kindred-systems.com/kindred/datums` | `main` | Unified datum creator (plane, axis, point) |
| `mods/solver` | `git.kindred-systems.com/kindred/solver` | `main` | Assembly solver research (GNN-based) |
| `src/3rdParty/OndselSolver` | `git.kindred-systems.com/kindred/solver` | — | Constraint solver (forked with NR fix) |
| `src/3rdParty/GSL` | `github.com/microsoft/GSL` | — | Guidelines Support Library |
| `src/Mod/AddonManager` | `github.com/FreeCAD/AddonManager` | — | FreeCAD addon manager |
| `tests/lib` | `github.com/google/googletest` | — | C++ test framework |
Update a submodule:
```bash
cd mods/silo
git checkout main && git pull
cd ../..
git add mods/silo
git commit -m "chore: update silo submodule"
```
Initialize all submodules: `git submodule update --init --recursive`
## Key Addon Modules
### SDK (`mods/sdk/`)
Stable API contract for addons. Python package `kindred_sdk` wraps the KCSDK C++ module, providing:
- **Editing contexts:** `register_context()`, `register_overlay()`, `inject_commands()`, `refresh_context()`
- **Origins:** `register_origin()`, `unregister_origin()`, `list_origins()`, `active_origin()`
- **Dock panels:** `register_dock_panel(object_name, title, factory, area, delay_ms)`
- **Commands:** `register_command(cmd_id, classname, pixmap, tooltip)`
- **Theme:** `get_theme_tokens()`, `load_palette()` (Catppuccin Mocha YAML palette)
- **Toolbars/Menus:** `register_toolbar()`, `register_menu()`
- **Version:** `create_version()`, `freecad_version()`
Addons should use `kindred_sdk.*` instead of `FreeCADGui.*` internals where possible.
### Gears (`mods/gears/`)
Parametric gear workbench. Provides `GearWorkbench` with gear generation and analysis tools. Code lives in `freecad/gears/` (workbench) and `pygears/` (computational library). Priority 40, depends on SDK.
### Datums (`mods/datums/`)
Unified datum creator that replaces the three stock PartDesign datum commands (Plane, Line, Point) with a single `Create_DatumCreator` command. Features:
- 16 smart creation modes (7 plane, 4 axis, 5 point)
- Auto-detection engine: selects best mode from geometry selection
- Mode override combo for manual selection
- Dynamic parameter UI (offset, angle, position, XYZ)
- Edit panel with real-time parameter updates via AttachExtension
- Injected into `partdesign.body` and `partdesign.feature` contexts via SDK
Source: `mods/datums/datums/` (command.py, core.py, panel.py, detection.py, edit_panel.py). Priority 45, depends on SDK.
### Silo (`mods/silo/`)
PLM workbench with 14 commands for parts lifecycle management. Go REST API server + PostgreSQL + MinIO backend. FreeCAD client communicates via shared `silo-client` submodule.
Structure: root-level `package.xml`/`Init.py`/`InitGui.py`, workbench code in `freecad/`, client library in `silo-client/`. All deferred setup (origin, overlay, dock panels, document observer) is self-contained in `mods/silo/InitGui.py`.
Silo origin detection: `silo_origin.py:ownsDocument()` checks for `SiloItemId`/`SiloPartNumber` properties on the active document. Origin registered via `kindred_sdk.register_origin()`.
### Solver (`mods/solver/`)
Assembly solver research addon (GNN-based). Pure Python, priority 10. Provides `KindredSolverBackend` for experimental assembly constraint solving.
## Theme
- **Canonical source:** `src/Gui/Stylesheets/KindredCreate.qss`
- The PreferencePacks copy at `src/Gui/PreferencePacks/KindredCreate/KindredCreate.qss` is **generated at build time** via `configure_file()`. Only edit the Stylesheets copy.
- Color palette: Catppuccin Mocha (26 colors + 14 semantic roles, defined in `mods/sdk/kindred_sdk/palettes/catppuccin-mocha.yaml`)
- Runtime access: `kindred_sdk.get_theme_tokens()` returns palette dict; C++ side via `ThemeEngine` in `src/Gui/SDK/`
- Default preferences: `src/Gui/PreferencePacks/KindredCreate/KindredCreate.cfg`
## Git Conventions
### Branch Names
`type/kebab-case-description`
Types: `feat/`, `fix/`, `chore/`, `docs/`, `refactor/`, `art/`
### Commit Messages
[Conventional Commits](https://www.conventionalcommits.org/):
```
type(scope): lowercase imperative description
```
| Prefix | Purpose |
|--------|---------|
| `feat:` | New feature |
| `fix:` | Bug fix |
| `chore:` | Maintenance, dependencies |
| `docs:` | Documentation only |
| `art:` | Icons, theme, visual assets |
| `refactor:` | Code restructuring |
Scopes: `solver`, `sketcher`, `editing-context`, `toolbar`, `datums`, `silo`, `gears`, `sdk`, `breadcrumb`, `gui`, `assembly`, `ci`, `theme`, or omitted.
### PR Workflow
1. Create a branch from `main`: `git checkout -b feat/my-feature main`
2. Commit with conventional commit messages
3. Push and open a PR against `main` via Gitea (or `tea pulls create`)
4. CI runs automatically on PRs
### Code Style
- **C++:** clang-format (`.clang-format`), clang-tidy (`.clang-tidy`)
- **Python:** black (100-char line length), pylint (`.pylintrc`)
- **Pre-commit hooks:** `pre-commit install` (runs clang-format, black, trailing-whitespace, etc.)
## CI/CD
- **Build:** `.gitea/workflows/build.yml` — runs on pushes to `main` and on PRs
- **Release:** `.gitea/workflows/release.yml` — triggered by `v*` tags, builds AppImage and .deb
- **Platform:** Currently Linux x86_64 only in CI; other platforms have presets but no runners yet
## Documentation
| Document | Content |
|----------|---------|
| `README.md` | Project overview, installation, usage |
| `CONTRIBUTING.md` | Branch workflow, commit conventions, code style |
| `docs/ARCHITECTURE.md` | Bootstrap flow, addon lifecycle, source layout |
| `docs/COMPONENTS.md` | Feature inventory (Silo, origin, theme, icons) |
| `docs/KNOWN_ISSUES.md` | Known issues, incomplete features, next steps |
| `docs/INTEGRATION_PLAN.md` | 5-layer architecture, phase status |
| `docs/CI_CD.md` | Build and release workflows |
| `docs/KC_SPECIFICATION.md` | .kc file format specification |
| `docs/UPSTREAM.md` | FreeCAD upstream merge strategy |
| `docs/INTER_SOLVER.md` | Assembly solver integration |
| `docs/BOM_MERGE.md` | BOM-Assembly bridge specification |
The `docs/src/` directory contains an mdBook site with detailed guides organized by topic (architecture, development, guide, reference, silo-server, solver).
## Issue Tracker
Issues are tracked on Gitea at `git.kindred-systems.com/kindred/create/issues`. Use the `tea` CLI for local interaction:
```bash
tea issues # List open issues
tea issues 123 # View issue #123 details
tea pulls create # Create a PR
```
## Known Issues and Pitfalls
1. **Silo auth not production-hardened** — LDAP/OIDC backends are coded but need infrastructure deployment
2. **No unit tests** for Silo FreeCAD commands or Go backend
3. **Assembly solver datum handling is minimal** — joints referencing datum planes/points may produce incorrect placement
4. **`Silo_BOM` requires Silo-tracked document** — unregistered documents show a warning with no registration path
5. **QSS edits** — only edit `src/Gui/Stylesheets/KindredCreate.qss`; the PreferencePacks copy is auto-generated
6. **Archived addons**`reference/ztools/` and `reference/quicknav/` are archived source for reference only; they are not built or loaded

View File

@@ -53,7 +53,7 @@ project(KindredCreate)
# Kindred Create version
set(KINDRED_CREATE_VERSION_MAJOR "0")
set(KINDRED_CREATE_VERSION_MINOR "1")
set(KINDRED_CREATE_VERSION_PATCH "5")
set(KINDRED_CREATE_VERSION_PATCH "0")
set(KINDRED_CREATE_VERSION "${KINDRED_CREATE_VERSION_MAJOR}.${KINDRED_CREATE_VERSION_MINOR}.${KINDRED_CREATE_VERSION_PATCH}")
# Underlying FreeCAD version

View File

@@ -1,8 +1,8 @@
# Kindred Create Code of Conduct
# FreeCAD Project Code of Conduct
## Statement
The Kindred Create community includes people from a wide variety of countries,
The FreeCAD Project community covers people from a wide variety of countries,
backgrounds and positions. This global diversity is a great strength of the
project, but can also lead to communication issues, which may in turn cause
unhappiness. To maximise happiness of the project community taken as a whole,
@@ -55,13 +55,12 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by opening an issue at
https://git.kindred-systems.com/kindred/create/issues or by contacting the
Kindred Systems project maintainers directly. All complaints will be reviewed
and investigated and will result in a response that is deemed necessary and
appropriate to the circumstances. The project team is obligated to maintain
confidentiality with regard to the reporter of an incident. Further details
of specific enforcement policies may be posted separately.
reported by contacting any of the community admins or moderators at
https://forum.freecad.org/memberlist.php?mode=team . All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other

View File

@@ -66,10 +66,8 @@ Kindred Create uses git submodules for addon workbenches:
| Submodule | Path | Repository |
|-----------|------|------------|
| ztools | `mods/ztools` | `git.kindred-systems.com/forbes/ztools` |
| silo-mod | `mods/silo` | `git.kindred-systems.com/kindred/silo-mod` |
| gears | `mods/gears` | `git.kindred-systems.com/kindred/gears` |
| datums | `mods/datums` | `git.kindred-systems.com/kindred/datums` |
| solver | `mods/solver` | `git.kindred-systems.com/kindred/solver` |
To update a submodule:
@@ -89,9 +87,15 @@ git submodule update --init --recursive
## Theme and QSS changes
The Catppuccin Mocha theme canonical source is `src/Gui/Stylesheets/KindredCreate.qss`. The copy at `src/Gui/PreferencePacks/KindredCreate/KindredCreate.qss` is **generated at build time** via `configure_file()` in CMake -- do not edit it directly.
The Catppuccin Mocha theme has **three QSS copies** that must be kept in sync:
Only edit `src/Gui/Stylesheets/KindredCreate.qss` when modifying the theme.
1. `resources/preferences/KindredCreate/KindredCreate.qss` (canonical)
2. `src/Gui/Stylesheets/KindredCreate.qss`
3. `src/Gui/PreferencePacks/KindredCreate/KindredCreate.qss`
When modifying the theme, apply changes to all three files. Note that the copies have intentional differences (e.g., tree branch rendering style), so do not blindly copy between them -- apply edits individually.
See [KNOWN_ISSUES.md](docs/KNOWN_ISSUES.md) for the planned QSS consolidation.
## Preference pack

View File

@@ -1,37 +1,28 @@
# Kindred Create Privacy Policy
# FreeCAD Privacy Policy
Kindred Create is built on FreeCAD and does not collect, transmit, share or use any Personal Data by default.
The FreeCAD application does not collect, transmit, share or use any Personal Data.
The software does not contain advertisements or trackers.
FreeCAD is community-developed Free Software. The community does not condone the unauthorized usage of private data, so our software does not gather or send personal data.
## Update Checker
The FreeCAD website is mostly static, it does not contain any trackers, neither ours nor third-party. The website uses cookies to remember logged in status, timezone and other data related to navigating the site. The website does not contain advertisements.
Kindred Create includes an optional update checker that periodically contacts a Kindred Systems server to determine whether a newer version is available. This request transmits only the current application version. No personally identifiable information is sent. The update checker can be disabled in Preferences.
## Silo Integration
Kindred Create includes optional integration with Silo, a product lifecycle management (PLM) system developed by Kindred Systems. When connected to a Silo server:
* Authentication credentials (username and password or token) are transmitted to the configured Silo server over HTTPS.
* Document metadata (part numbers, revision history, bill of materials data) is exchanged with the Silo server as part of normal PLM operations.
* File contents are uploaded to and downloaded from the Silo server's storage backend.
All Silo communication occurs only with servers you explicitly configure. No data is sent to Kindred Systems or any third party unless you choose to connect to such a server.
## File Origins
Kindred Create's origin system tracks where documents were opened from or saved to. This metadata (file paths, origin identifiers) is stored locally within the document and in application preferences. It is not transmitted externally unless you use a remote origin such as Silo.
## Local Storage
While running and for subsequent runs, Kindred Create uses local persistent storage for logs, configuration files, cache, thumbnails, recently accessed files and other information which may contain private data. This stays on local storage.
The software does not contain advertisements or trackers either.
## Caveats
Kindred Create is able to load or save files to/from remote servers. If you choose to load or save a remote file, your IP or other private data might be shared as part of the normal connection flow for the given protocol. This is out of our control and it is up to you to decide whether you trust a remote host.
FreeCAD is able to load or save files to/from remote servers (for some protocols and platforms). If you choose to load or save a remote file, your IP or other private data might be shared as part of the normal connection flow for the given protocol. This is out of our control and it is up to you to decide whether you trust a remote host.
Kindred Create can be used to create and run macros. These are Python scripts that can perform any action that the user can perform on a system. When running a macro from an outside source, it is your responsibility to ensure you trust the author.
The FreeCAD eco system includes user developed workbenches. These workbenches can be installed/updated using the Add-on Manager. The Add-on Manager retrieves workbenches from remote servers across the internet. Add-on workbenches are not checked for malicious content. It is your responsibility to decide whether you trust an add-on workbench.
CAD files may contain metadata including local directory paths which could reveal user names if the user name forms part of the path. It is your responsibility to verify the metadata contained in your files before you share them with others.
FreeCAD is meant to manipulate CAD files which may contain metadata. It is your responsibility to verify the metadata contained in your files before you share them with others. These files may contain local directory paths which could reveal user names if the user name forms part of the path - as in “C:\MrsCAD\Documents\myFreeCADFile.FCstd”.
Kindred Create is Free Software and therefore may be packaged by other people, who may include additional software or modify the source code. We do not vouch for these third-party packages and cannot tell you what they contain and what they do regarding your privacy.
FreeCAD can also be used to create and run macros. These are Python scripts that can perform any action that the user can perform on a system. When running a macro from an outside source, it is your responsibility to ensure you trust the author.
While running and for subsequent runs, FreeCAD uses local persistent storage for logs, configuration files, cache, thumbnails, recently accessed files and other information which may contain private data. This stays on local storage.
When reading the online version of the User Manual within FreeCAD, manual contents is requested through HTTPS connections.
FreeCAD is Free Software and therefore may be packaged by other people, who may include additional software or modify the source code. We do not vouch for these third-party packages and cannot tell you what they contain and what they do regarding your privacy. The official packages are explicitly listed in our download page.
*The above privacy policy is based on the [GIMP privacy policy](https://www.gimp.org/about/privacy.html).*

File diff suppressed because one or more lines are too long

32
SECURITY.md Normal file
View File

@@ -0,0 +1,32 @@
# Security Policy
The FreeCAD project is a FOSS (Free and Open-Source Software) project that has a community of thousands of users and
hundreds of developers worldwide. We encourage responsible reporting of security vulnerabilities that may affect users
of this software, and will endeavor to address these vulnerabilities when they are discovered.
## Bounties
FreeCAD does not have a program to pay bounties for security bugs. If you discover a vulnerability that affects a part
of the FreeCAD project (either directly in FreeCAD, in a library it depends on, or in any of the various other
subprojects such as our website, forums, etc.) we ask you to join the large community of volunteer contributors and
file a report about the issue.
Note that funds may be available from the [FreeCAD Project Association (FPA)](https://fpa.freecad.org) to pursue
security research and/or the development of fixes to any vulnerabilities discovered. However, vulnerabilities held as
hostage in demands for "bounties" will not be entertained. Contact the FPA at fpa@freecad.org for more information.
## Supported Versions
FreeCAD implements security fixes to the current release series, and to the current development on the main branch.
| Version | Supported |
|---------| ------------------ |
| 1.1dev | :white_check_mark: |
| 1.0 | :white_check_mark: |
| < 1.0 | :x: |
## Reporting a Vulnerability
To report a vulnerability use GitHub's security reporting tool:
https://github.com/FreeCAD/FreeCAD/security/advisories/new

View File

@@ -20,26 +20,25 @@ FreeCAD startup
│ └─ for each addon in order:
│ └─ exec(InitGui.py)
│ ├─ sdk (priority 0): logs "SDK loaded"
│ ├─ solver (priority 10): registers KindredSolverBackend
├─ gears (priority 40): registers GearsWorkbench
├─ datums (priority 45): registers Create_DatumCreator
│ ├─ ztools (priority 50): schedules deferred _register() (2000ms)
│ ├─ imports ZTools commands
│ ├─ installs _ZToolsManipulator (global)
│ │ └─ injects commands into editing contexts
│ └─ silo (priority 60): registers SiloWorkbench
│ └─ schedules deferred Silo setup (overlay, panels, observer)
│ └─ schedules deferred Silo overlay registration (2500ms)
├─ EditingContextResolver singleton created (MainWindow constructor)
│ ├─ registers built-in contexts (PartDesign, Sketcher, Assembly, Spreadsheet)
│ ├─ connects to signalInEdit/signalResetEdit/signalActiveDocument/signalActivateView
│ └─ BreadcrumbToolBar connected to contextChanged signal
└─ Deferred setup (QTimer):
Create core (src/Mod/Create/InitGui.py):
├─ 500ms: _register_kc_format() → .kc file format
─ 10000ms: _check_for_updates() → update checker (Gitea API)
Silo addon (mods/silo/InitGui.py):
├─ 500ms: kindred:// URL handler
├─ 600ms: document observer (.kc tree building)
├─ 2000ms: auth dock panel (via kindred_sdk.register_dock_panel)
─ 1500ms: _register_silo_origin() → registers Silo FileOrigin
├─ 2000ms: _setup_silo_auth_panel() → "Database Auth" dock
├─ 2000ms: ZTools _register() → commands + manipulator
├─ 2500ms: Silo overlay registration → "Silo Origin" toolbar overlay
├─ 3000ms: first-start settings check
─ 4000ms: activity dock panel (via kindred_sdk.register_dock_panel)
├─ 3000ms: _check_silo_first_start() → settings prompt
─ 4000ms: _setup_silo_activity_panel() → "Database Activity" dock
└─ 10000ms: _check_for_updates() → update checker (Gitea API)
```
### Addon lifecycle
@@ -66,7 +65,7 @@ The loader (`addon_loader.py`) processes addons in this order:
5. **Load** — execute `Init.py` (console) then `InitGui.py` (GUI) for each addon
6. **Register** — populate `FreeCAD.KindredAddons` registry with addon state
Current load order: **sdk** (0) → **solver** (10) → **gears** (40) → **datums** (45) → **silo** (60)
Current load order: **sdk** (0) → **ztools** (50) → **silo** (60)
## Key source layout
@@ -99,39 +98,24 @@ mods/sdk/ [dir] Kindred addon SDK — stable API contract
│ └── catppuccin-mocha.yaml 26 colors + 14 semantic roles
└── Init.py / InitGui.py Minimal log messages
mods/gears/ [submodule → gears.git] Gears workbench
├── package.xml Manifest (priority 40, depends on sdk)
── ... Parametric gear generation
mods/ztools/ [submodule] command provider (not a workbench)
├── package.xml Manifest (priority 50, depends on sdk)
── ztools/InitGui.py Deferred command registration + _ZToolsManipulator
├── ztools/ztools/
│ ├── commands/ Datum, pattern, pocket, assembly, spreadsheet
│ ├── datums/core.py Datum creation via Part::AttachExtension
│ └── resources/ Icons (SDK theme tokens), theme utilities
└── CatppuccinMocha/ Theme preference pack (QSS)
mods/solver/ [submodule → solver.git] Solver addon
── ... Solver plugin support
mods/datums/ [submodule → datums.git] Unified datum creator
├── package.xml Manifest (priority 45, depends on sdk)
├── Init.py / InitGui.py Bootstrap
└── datums/ Python package (command, core, panel, detection, edit_panel)
mods/silo/ [submodule → silo-mod.git] PLM workbench
├── package.xml Manifest (priority 60, depends on sdk)
├── Init.py sys.path setup for silo-client
├── InitGui.py Consolidated GUI bootstrap (workbench, panels, observer, overlay)
mods/silo/ [submodule → silo-mod.git] FreeCAD workbench
── freecad/package.xml Manifest (priority 60, depends on sdk)
├── silo-client/ [submodule → silo-client.git] shared API client
│ └── silo_client/ SiloClient, SiloSettings, CATEGORY_NAMES
└── freecad/ FreeCAD workbench (Python)
├── InitGui.py SiloWorkbench + overlay registration (via SDK)
├── silo_commands.py Commands + FreeCADSiloSettings adapter
└── silo_origin.py FileOrigin backend for Silo (via SDK)
src/Gui/SDK/ KCSDK C++ shared library (libKCSDK.so)
├── KCSDKGlobal.h DLL export macros
├── Types.h Plain C++ types (ContextDef, DockArea, PanelPersistence)
├── IPanelProvider.h Abstract dock panel interface
├── WidgetBridge.h/.cpp PySide QWidget <-> C++ QWidget* (via Gui::PythonWrapper)
├── SDKRegistry.h/.cpp Singleton registry — contexts, panels, providers
└── bindings/ pybind11 module (kcsdk.so)
├── kcsdk_py.cpp Module definition — enums, functions, classes
├── PyIPanelProvider.h Trampoline for Python subclassing
└── PyProviderHolder.h GIL-safe forwarding wrapper
src/Gui/EditingContext.h/.cpp EditingContextResolver singleton + context registry
src/Gui/BreadcrumbToolBar.h/.cpp Color-coded breadcrumb toolbar (Catppuccin Mocha)
src/Gui/FileOrigin.h/.cpp FileOrigin base class + LocalFileOrigin

File diff suppressed because one or more lines are too long

View File

@@ -1,29 +1,31 @@
# Components
## Gears workbench
## ztools (command provider)
Gears (`mods/gears/`) is a parametric gear generation addon. Provides `GearWorkbench` with gear generation and analysis tools. Code lives in `freecad/gears/` (workbench) and `pygears/` (computational library). Priority 40, depends on SDK.
ZTools is no longer a standalone workbench. It registers as a command provider that
injects tools into the appropriate editing contexts via `WorkbenchManipulator` and
the `EditingContextResolver` system.
---
**Registered commands (9):**
## Datums
| Command | Function | Injected Into |
|---------|----------|---------------|
| `ZTools_DatumCreator` | Create datum planes, axes, points (16 modes) | PartDesign Helper Features |
| `ZTools_DatumManager` | Manage existing datum objects | PartDesign Helper Features |
| `ZTools_EnhancedPocket` | Flip-side pocket (cut outside sketch profile) | PartDesign Modeling Features |
| `ZTools_RotatedLinearPattern` | Linear pattern with incremental rotation | PartDesign Transformation Features |
| `ZTools_AssemblyLinearPattern` | Pattern assembly components linearly | Assembly |
| `ZTools_AssemblyPolarPattern` | Pattern assembly components around axis | Assembly |
| `ZTools_SpreadsheetStyle{Bold,Italic,Underline}` | Text style toggles | Spreadsheet |
| `ZTools_SpreadsheetAlign{Left,Center,Right}` | Cell alignment | Spreadsheet |
| `ZTools_Spreadsheet{BgColor,TextColor,QuickAlias}` | Colors and alias creation | Spreadsheet |
Datums (`mods/datums/`) is a unified datum creator that replaces the three stock PartDesign datum commands (Plane, Line, Point) with a single `Create_DatumCreator` command. Features:
**Integration** via `_ZToolsManipulator` (WorkbenchManipulator) and `injectEditingCommands()`:
- Commands are injected into native workbench toolbars (PartDesign, Assembly, Spreadsheet)
- Context toolbar injections ensure commands appear when the relevant editing context is active
- PartDesign menu items inserted after `PartDesign_Boolean`
- 16 smart creation modes (7 plane, 4 axis, 5 point)
- Auto-detection engine that selects the best mode from geometry selection
- Mode override combo for manual selection
- Dynamic parameter UI (offset, angle, position, XYZ)
- Edit panel with real-time parameter updates via AttachExtension
- Injected into `partdesign.body` and `partdesign.feature` contexts via SDK
Source: `mods/datums/datums/` (command.py, core.py, panel.py, detection.py, edit_panel.py). Priority 45, depends on SDK.
---
## Solver
Solver (`mods/solver/`) is an assembly solver research addon (GNN-based). Pure Python, priority 10. Provides `KindredSolverBackend` for experimental assembly constraint solving.
**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.
---
@@ -68,7 +70,7 @@ These appear in the File menu and "Origin Tools" toolbar across all workbenches
**Global integration** via the origin system in `src/Gui/`:
- File toolbar: `Std_Origin` selector widget + `Std_New`/`Std_Open`/`Std_Save` (delegate to current origin)
- Origin Tools toolbar: `Origin_Commit`/`Origin_Pull`/`Origin_Push`/`Origin_Info`/`Origin_BOM` (auto-disable by capability)
- Silo origin registered at startup by `mods/silo/InitGui.py` via `kindred_sdk.register_origin()`
- Silo origin registered at startup by `src/Mod/Create/InitGui.py`
**Dock panels:**
- Database Auth (2000ms) -- Login/logout and API token management
@@ -108,6 +110,10 @@ Notable theme customizations beyond standard Catppuccin colors:
`silo-auth.svg`, `silo-bom.svg`, `silo-commit.svg`, `silo-info.svg`, `silo-new.svg`, `silo-open.svg`, `silo-pull.svg`, `silo-push.svg`, `silo-save.svg`, `silo.svg`
### Missing icons
3 command icon names have no corresponding SVG file: `silo-tag`, `silo-rollback`, `silo-status`. The `_icon()` function returns an empty string for these, so `Silo_TagProjects`, `Silo_Rollback`, and `Silo_SetStatus` render without toolbar icons.
### Palette
All silo-* icons use the Catppuccin Mocha color scheme. See `icons/kindred/README.md` for palette specification and icon design standards.

View File

@@ -1,10 +1,10 @@
# Integration Plan
Strategy for integrating Kindred addons as built-in components 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** -- Addons behave as first-class citizens, not bolted-on extras
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
@@ -18,11 +18,12 @@ Strategy for integrating Kindred addons as built-in components while maintaining
├─────────────────────────────────────────────────────────────┤
│ Layer 5: Kindred Addons (mods/) │
│ ┌──────────────────┐ ┌──────────────────┐ │
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ │ Silo │ │ Gears Datums │ │ Solver
│ │ PLM/VCS │ │ Gear gen │ │ Datum │ │ Assembly │
│ │ BOM mgmt │ │ │ │ creator │ │ research │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
│ ztools │ │ Silo │
│ │ Datum Creator Open/Save/Commit
│ │ Enhanced Pocket Part numbering │
│ │ Assembly Patterns│ │ Revision control│
│ Spreadsheet fmt │ │ BOM management │
│ └──────────────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Layer 4: Kindred Addon SDK (mods/sdk/) │
│ ┌─────────────────────────────────────────────────────────┐│
@@ -57,7 +58,7 @@ FreeCAD's fundamental data structures: `PartDesign::Body`, `PartDesign::Pocket`,
### Layer 2: FreeCAD Python API -- use as-is
The Python API provides everything needed for feature creation, command registration, and geometry access. Addons operate through this layer. Stable FreeCAD APIs (`Gui.addCommand()`, `Gui.addWorkbench()`, `Gui.addWorkbenchManipulator()`) are called directly -- they do not need SDK wrappers.
The Python API provides everything needed for feature creation, command registration, and geometry access. ZTools and Silo operate entirely through this layer. Stable FreeCAD APIs (`Gui.addCommand()`, `Gui.addWorkbench()`, `Gui.addWorkbenchManipulator()`) are called directly they do not need SDK wrappers.
### Layer 3: Kindred Create Module -- `src/Mod/Create/`
@@ -66,9 +67,9 @@ The Create module serves two roles:
**Python bootstrap** (`Init.py`, `InitGui.py`, `addon_loader.py`):
- Scans `mods/` for `package.xml` manifests with `<kindred>` extensions
- Validates version compatibility, resolves dependencies via topological sort
- Loads addons in priority order (sdk → solver → gears → datums → silo)
- Loads addons in priority order (sdk → ztools → silo)
- Populates `FreeCAD.KindredAddons` registry for runtime introspection
- Sets up deferred .kc format registration and update checker
- Sets up deferred Silo dock panels, origin registration, and update checker
**C++ module scaffold** (`App/`, `Gui/`, `CreateGlobal.h`):
- `CreateApp.so` and `CreateGui.so` shared libraries
@@ -90,7 +91,7 @@ The SDK is loaded first (priority 0) and has no dependencies.
### Layer 5: Kindred Addons -- `mods/`
Pure Python addons with `package.xml` manifests. Self-contained with `Init.py`, `InitGui.py`, and `<kindred>` metadata. Developed and versioned independently as git submodules. Must declare `<dependency>sdk</dependency>` to use SDK APIs. Current addons: **solver** (assembly research), **gears** (parametric gear generation), **datums** (unified datum creator), **silo** (PLM).
Pure Python addons with `package.xml` manifests. Self-contained with `Init.py`, `InitGui.py`, and `<kindred>` metadata. Developed and versioned independently as git submodules. Must declare `<dependency>sdk</dependency>` to use SDK APIs.
---
@@ -100,7 +101,7 @@ Pure Python addons with `package.xml` manifests. Self-contained with `Init.py`,
**Implementation:** `src/Mod/Create/addon_loader.py` implements manifest-driven loading with dependency resolution. Replaces the original `exec()`-based approach. Each addon provides a `package.xml` with `<kindred>` extensions specifying version bounds, load priority, and dependencies.
**Default workbench:** `PartDesignWorkbench` (set in `PreferencePacks/KindredCreate/KindredCreate.cfg`).
**Default workbench:** `PartDesignWorkbench` (set in `PreferencePacks/KindredCreate/KindredCreate.cfg`). ZTools is no longer a workbench — its commands are injected into native workbench toolbars via the `EditingContextResolver` and `WorkbenchManipulator` systems.
**Registry:** `FreeCAD.KindredAddons` provides runtime introspection — `.loaded()` returns addon names and states, `.get(name)` returns manifest data, `.contexts()` lists registered editing contexts.
@@ -110,7 +111,7 @@ Pure Python addons with `package.xml` manifests. Self-contained with `Init.py`,
**Implementation:** `mods/sdk/kindred_sdk/` package with wrappers for editing contexts, theme tokens, FileOrigin registration, and dock panel creation. YAML-driven palette system (`palettes/catppuccin-mocha.yaml`) replaces hardcoded color dicts that were duplicated across 5+ locations.
**Migration:** Silo has been migrated to use the SDK (#250) — it declares `<dependency>sdk</dependency>` and uses `kindred_sdk` for overlay registration and theme tokens. Gears also depends on the SDK. The former ztools addon was removed from the build (#344) and archived to `reference/ztools/` (#345) as part of the UI/UX rework (#346); its commands will be reimplemented through the KCSDK system.
**Migration:** Both ztools and Silo have been migrated to use the SDK (#250). No addon code calls `FreeCADGui.registerEditingOverlay()`, `FreeCADGui.addOrigin()`, or defines hardcoded MOCHA dicts directly.
### Phase 1.75: C++ module scaffold -- DONE
@@ -120,16 +121,17 @@ Pure Python addons with `package.xml` manifests. Self-contained with `Init.py`,
### Phase 2: Enhanced Pocket as C++ feature -- NOT STARTED
**Goal:** Implement a proper `Create::FlipPocket` C++ feature inheriting from `PartDesign::ProfileBased`.
**Goal:** Replace the Python boolean-operation workaround in `ZTools_EnhancedPocket` with a proper `Create::FlipPocket` C++ feature inheriting from `PartDesign::ProfileBased`.
**Rationale:** The former ztools Python implementation created a standard Pocket then applied a boolean Common. A native feature would integrate properly with the feature tree, support undo/redo correctly, and perform better on complex geometry.
**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.
**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
**Decision:** Deferred. The former Python approach was functional. Revisit as part of the KCSDK-driven command reimplementation (#346).
**Decision:** Deferred. The Python approach is functional for current needs. Revisit when performance or feature-tree integration becomes a problem.
### Phase 3: Datum C++ helpers -- NOT STARTED (SUPERSEDED)
@@ -157,60 +159,14 @@ Theme colors are now centralized in the SDK's YAML palette (`mods/sdk/kindred_sd
### Phase 6: Build system integration -- DONE
**Goal:** CMake install rules for `mods/` submodules so packages include all addons automatically.
**Goal:** CMake install rules for `mods/` submodules so packages include ztools, Silo, and SDK automatically.
**Implementation:** `src/Mod/Create/CMakeLists.txt` includes install rules for all addons (`mods/sdk`, `mods/solver`, `mods/gears`, `mods/datums`, `mods/silo`) and the C++ module scaffold (`App/`, `Gui/`). Build-time code generation: `version.py.in``version.py` via `configure_file()`.
**Implementation:** `src/Mod/Create/CMakeLists.txt` includes install rules for all addons (`mods/ztools`, `mods/silo`, `mods/sdk`) and the C++ module scaffold (`App/`, `Gui/`). Build-time code generation: `version.py.in``version.py` via `configure_file()`.
**CI/CD status:** Release workflows (`.gitea/workflows/release.yml`) 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.
---
### Phase 7: KCSDK — C++-backed SDK module -- IN PROGRESS
**Goal:** Replace the pure-Python SDK wrappers with a C++ shared library (`libKCSDK.so`) and pybind11 bindings (`kcsdk.so`). This gives addons a stable, typed API with proper GIL safety and enables future C++ addon development without Python.
**Architecture:**
```
Python Addons (silo, future addons, ...)
|
kindred_sdk (mods/sdk/) <- convenience layer (try kcsdk, fallback FreeCADGui)
|
kcsdk.so (pybind11 module) <- C++ API bindings
|
KCSDK (C++ shared library) <- SDKRegistry + provider interfaces
|
FreeCADGui (EditingContextResolver, DockWindowManager, OriginManager, ...)
```
**Sub-phases:**
| # | Issue | Status | Description |
|---|-------|--------|-------------|
| 1 | #350 | DONE | Scaffold KCSDK library + kcsdk pybind11 module |
| 2 | #351 | DONE | Migrate editing context API to kcsdk |
| 3 | #352 | DONE | Panel provider system (IPanelProvider) |
| 4 | #353 | DONE | C++ theme engine |
| 5 | #354 | DONE | Toolbar provider system (IToolbarProvider) |
| 6 | #355 | DONE | Menu and action system |
| 7 | #356 | DONE | Status bar provider + origin migration |
| 8 | #357 | — | Deprecation cleanup + SDK v1.0.0 |
**Key files:**
- `src/Gui/SDK/` — C++ library (KCSDKGlobal.h, Types.h, SDKRegistry, IPanelProvider, WidgetBridge)
- `src/Gui/SDK/bindings/` — pybind11 module (kcsdk_py.cpp, PyIPanelProvider, PyProviderHolder)
- `mods/sdk/kindred_sdk/` — Python wrappers with kcsdk/legacy fallback
**Design decisions:**
- **No Qt in public C++ API** — `Types.h` uses `std::string`, `std::vector`, `std::function`. Qt conversion happens internally in `SDKRegistry.cpp`.
- **GIL-safe Python callables** — Python callbacks stored via `std::make_shared<py::object>` with `py::gil_scoped_acquire` before every invocation.
- **PySide widget bridging** — `WidgetBridge::toQWidget()` converts PySide QWidget objects to C++ `QWidget*` via `Gui::PythonWrapper` (Shiboken).
- **Provider pattern** — Interfaces like `IPanelProvider` enable addons to register factories. The registry calls `create_widget()` once and manages the lifecycle through `DockWindowManager`.
---
## Design decisions
1. **`Create::` namespace prefix.** All Kindred Create C++ features use this prefix to distinguish them from FreeCAD core.
@@ -219,15 +175,15 @@ FreeCADGui (EditingContextResolver, DockWindowManager, OriginManager, ...)
3. **Silo server distributed separately.** Users deploy the Silo server independently. Setup instructions live in `mods/silo/README.md`.
4. **Version synchronization via submodule pins.** Addon versions are pinned git submodule commits. Updates are deliberate:
4. **Version synchronization via submodule pins.** ztools and Silo versions are pinned git submodule commits. Updates are deliberate:
```bash
cd mods/silo && git pull origin main && cd ../..
git add mods/silo && git commit -m "Update silo submodule"
cd mods/ztools && git pull origin main && cd ../..
git add mods/ztools && git commit -m "Update ztools submodule"
```
5. **Python-first approach.** C++ extensions are deferred until Python cannot achieve the requirement.
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 addon loader skips addons that fail validation or loading. Each addon load is wrapped in try/except with console logging. Any addon can be absent without breaking the bootstrap.
6. **Graceful degradation.** The addon loader skips addons that fail validation or loading. Each addon load is wrapped in try/except with console logging. The SDK, ztools, and Silo can all be absent without breaking the bootstrap.
7. **SDK as adaptation layer.** Addons call `kindred_sdk.*` instead of `FreeCADGui.*` platform internals. This creates a single point of adaptation during upstream rebases — update the SDK wrappers, not every addon.

View File

@@ -1,568 +0,0 @@
# Pluggable Assembly Solver Architecture
**Status:** Phase 2 complete
**Last Updated:** 2026-02-19
---
## 1. Problem
Kindred Create currently vendors OndselSolver as a monolithic assembly constraint solver. Different engineering domains benefit from different solver strategies — Lagrangian methods work well for rigid body assemblies but poorly for over-constrained or soft-constraint systems. A pluggable architecture lets us ship multiple solvers (including experimental ones) without touching core assembly logic, and lets the server farm out solve jobs to headless worker processes.
---
## 2. Design Goals
1. **Stable C++ API** — A solver-agnostic interface that the Assembly module calls. Solvers are shared libraries loaded at runtime.
2. **Python binding layer** — Every C++ solver is exposed to Python via pybind11, enabling rapid prototyping, debugging, and server-side execution without a full GUI build.
3. **Solver-defined joint types** — Each solver declares its own joint/mate vocabulary, mapped from a common base set (inspired by SOLIDWORKS mates: coincident, concentric, tangent, distance, angle, lock, etc.).
4. **Semi-deterministic solving** — Consistent results given consistent input ordering, with configurable tolerance and iteration limits.
5. **Server-compatible** — Solvers run as detached processes claimed by `silorunner` workers via the existing job queue.
---
## 3. Architecture Layers
```
┌──────────────────────────────────────────────────────┐
│ Layer 4: Server / Worker │
│ silorunner claims solve jobs, executes via Python │
│ Headless Create or standalone solver process │
├──────────────────────────────────────────────────────┤
│ Layer 3: Python Debug & Scripting │
│ pybind11 bindings for all solvers │
│ Introspection, step-through, constraint viz │
│ import kcsolve; s = kcsolve.load("ondsel") │
├──────────────────────────────────────────────────────┤
│ Layer 2: Solver Plugins (.so / .dll / .dylib) │
│ Each implements IKCSolver interface │
│ Registers joint types via manifest │
│ Loaded by SolverRegistry at runtime │
├──────────────────────────────────────────────────────┤
│ Layer 1: C++ Solver API (libkcsolve) │
│ IKCSolver, JointDef, SolveContext, SolveResult │
│ SolverRegistry (discovery, loading, selection) │
│ Ships as a shared library linked by Assembly module │
└──────────────────────────────────────────────────────┘
```
---
## 4. Layer 1: C++ Solver API
Located at `src/Mod/Assembly/Solver/` (or `src/Lib/KCSolve/` if we want it independent of Assembly).
### 4.1 Core Types
```cpp
namespace KCSolve {
// Unique identifier for a joint type within a solver
struct JointTypeId {
std::string solver_id; // e.g. "ondsel", "gnn", "relaxation"
std::string joint_name; // e.g. "coincident", "distance"
};
// Base joint categories (SOLIDWORKS-inspired vocabulary)
enum class BaseJointKind {
Coincident,
Concentric,
Tangent,
Distance,
Angle,
Lock,
Parallel,
Perpendicular,
PointOnLine,
SymmetricPlane,
Gear,
Rack,
Cam,
Slot,
Hinge,
Slider,
Cylindrical,
Planar,
Ball,
Screw,
Universal,
Custom // solver-specific extension
};
// A joint definition registered by a solver plugin
struct JointDef {
JointTypeId id;
BaseJointKind base_kind; // which vanilla category it maps to
std::string display_name;
std::string description;
uint32_t dof_removed; // degrees of freedom this joint removes
std::vector<std::string> params; // parameter names (e.g. "distance", "angle")
bool supports_limits = false;
bool supports_friction = false;
};
// A constraint instance in a solve problem
struct Constraint {
JointTypeId joint_type;
std::string part_a; // part label or id
std::string part_b;
// Geometry references (face, edge, vertex indices)
std::vector<std::string> refs_a;
std::vector<std::string> refs_b;
std::map<std::string, double> params; // param_name -> value
bool suppressed = false;
};
// Input to a solve operation
struct SolveContext {
std::vector<Constraint> constraints;
// Part placements as 4x4 transforms (initial guess)
std::map<std::string, std::array<double, 16>> placements;
// Which parts are grounded (fixed)
std::set<std::string> grounded;
// Solver config
double tolerance = 1e-10;
uint32_t max_iterations = 500;
bool deterministic = true; // force consistent ordering
// Optional: previous solution for warm-starting
std::map<std::string, std::array<double, 16>> warm_start;
};
enum class SolveStatus {
Converged,
MaxIterationsReached,
Overconstrained,
Underconstrained,
Redundant,
Failed
};
struct ConstraintDiagnostic {
std::string constraint_id;
double residual;
bool satisfied;
std::string message;
};
struct SolveResult {
SolveStatus status;
uint32_t iterations;
double final_residual;
double solve_time_ms;
std::map<std::string, std::array<double, 16>> placements;
std::vector<ConstraintDiagnostic> diagnostics;
// For semi-deterministic: hash of input ordering
uint64_t input_hash;
};
} // namespace KCSolve
```
### 4.2 Solver Interface
```cpp
namespace KCSolve {
class IKCSolver {
public:
virtual ~IKCSolver() = default;
// Identity
virtual std::string id() const = 0;
virtual std::string name() const = 0;
virtual std::string version() const = 0;
// Joint type registry — called once at load
virtual std::vector<JointDef> supported_joints() const = 0;
// Solve
virtual SolveResult solve(const SolveContext& ctx) = 0;
// Incremental: update a single constraint without full re-solve
// Default impl falls back to full solve
virtual SolveResult update(const SolveContext& ctx,
const std::string& changed_constraint) {
return solve(ctx);
}
// Diagnostic: check if a constraint set is well-posed before solving
virtual SolveStatus diagnose(const SolveContext& ctx) {
return SolveStatus::Converged; // optimistic default
}
// Determinism: given identical input, produce identical output
virtual bool is_deterministic() const { return false; }
};
// Plugin entry point — each .so exports this symbol
using CreateSolverFn = IKCSolver* (*)();
} // namespace KCSolve
```
### 4.3 Solver Registry
```cpp
namespace KCSolve {
class SolverRegistry {
public:
// Scan a directory for solver plugins (*.so / *.dll / *.dylib)
void scan(const std::filesystem::path& plugin_dir);
// Manual registration (for built-in solvers like Ondsel)
void register_solver(std::unique_ptr<IKCSolver> solver);
// Lookup
IKCSolver* get(const std::string& solver_id) const;
std::vector<std::string> available() const;
// Joint type resolution: find which solvers support a given base kind
std::vector<JointTypeId> joints_for(BaseJointKind kind) const;
// Global default solver
void set_default(const std::string& solver_id);
IKCSolver* get_default() const;
};
} // namespace KCSolve
```
### 4.4 Plugin Loading
Each solver plugin is a shared library exporting:
```cpp
extern "C" KCSolve::IKCSolver* kcsolve_create();
extern "C" const char* kcsolve_api_version(); // "1.0"
```
The registry `dlopen`s each library, checks `kcsolve_api_version()` compatibility, and calls `kcsolve_create()`. Plugins are discovered from:
1. `<install_prefix>/lib/kcsolve/` — system-installed solvers
2. `~/.config/KindredCreate/solvers/` — user-installed solvers
3. `KCSOLVE_PLUGIN_PATH` env var — development overrides
---
## 5. Layer 2: OndselSolver Adapter
The first plugin wraps the existing OndselSolver, mapping its internal constraint types to the `IKCSolver` interface.
```
src/Mod/Assembly/Solver/
├── IKCSolver.h # Interface + types from §4
├── SolverRegistry.cpp # Plugin discovery and loading
├── OndselAdapter.cpp # Wraps OndselSolver as IKCSolver plugin
└── CMakeLists.txt
```
`OndselAdapter` translates between `SolveContext` ↔ OndselSolver's Lagrangian formulation. This is the reference implementation and proves the API works before any new solvers are written.
Joint mapping for OndselAdapter:
| BaseJointKind | Ondsel Constraint | DOF Removed |
|---------------|-------------------|-------------|
| Coincident | PointOnPoint | 3 |
| Concentric | CylindricalOnCylindrical | 4 |
| Tangent | FaceOnFace (tangent mode) | 1 |
| Distance | PointOnPoint + offset | 2 |
| Angle | AxisAngle | 1 |
| Lock | FullLock | 6 |
| Hinge | RevoluteJoint | 5 |
| Slider | PrismaticJoint | 5 |
| Cylindrical | CylindricalJoint | 4 |
| Ball | SphericalJoint | 3 |
---
## 6. Layer 3: Python Bindings
### 6.1 pybind11 Module
```
src/Mod/Assembly/Solver/bindings/
├── kcsolve_py.cpp # pybind11 module definition
└── CMakeLists.txt
```
```python
import kcsolve
# List available solvers
print(kcsolve.available()) # ["ondsel", ...]
# Load a solver
solver = kcsolve.load("ondsel")
print(solver.name, solver.version)
print(solver.supported_joints())
# Build a problem
ctx = kcsolve.SolveContext()
ctx.add_part("base", placement=..., grounded=True)
ctx.add_part("arm", placement=...)
ctx.add_constraint("coincident", "base", "arm",
refs_a=["Face6"], refs_b=["Face1"])
# Solve
result = solver.solve(ctx)
print(result.status) # SolveStatus.Converged
print(result.iterations) # 12
print(result.solve_time_ms) # 3.4
print(result.placements["arm"])
# Diagnostics per constraint
for d in result.diagnostics:
print(f"{d.constraint_id}: residual={d.residual:.2e} ok={d.satisfied}")
```
### 6.2 Debug / Introspection API
The Python layer adds capabilities the C++ interface intentionally omits for performance:
```python
# Step-through solving (debug mode)
debugger = kcsolve.Debugger(solver, ctx)
for step in debugger.iterate():
print(f"iter {step.iteration}: residual={step.residual:.6e}")
print(f" moved: {step.parts_moved}")
print(f" worst constraint: {step.worst_constraint}")
if step.residual < 1e-8:
break
# Constraint dependency graph
graph = kcsolve.dependency_graph(ctx)
# Returns dict: constraint_id -> [dependent_constraint_ids]
# DOF analysis
analysis = kcsolve.dof_analysis(ctx)
print(f"Total DOF: {analysis.total_dof}")
print(f"Removed: {analysis.constrained_dof}")
print(f"Remaining: {analysis.free_dof}")
for part, dofs in analysis.per_part.items():
print(f" {part}: {dofs} free")
```
### 6.3 Pure-Python Solver Support
The Python layer also supports solvers written entirely in Python (no C++ required). This is the fast path for prototyping new approaches (GNN, relaxation, etc.):
```python
class RelaxationSolver(kcsolve.PySolver):
"""A pure-Python iterative relaxation solver for prototyping."""
id = "relaxation"
name = "Iterative Relaxation"
version = "0.1.0"
def supported_joints(self):
return [
kcsolve.JointDef("coincident", kcsolve.BaseJointKind.Coincident, dof_removed=3),
kcsolve.JointDef("distance", kcsolve.BaseJointKind.Distance, dof_removed=2),
# ...
]
def solve(self, ctx: kcsolve.SolveContext) -> kcsolve.SolveResult:
placements = dict(ctx.placements)
for i in range(ctx.max_iterations):
max_residual = 0.0
for c in ctx.constraints:
residual = self._eval_constraint(c, placements)
correction = self._compute_correction(c, residual)
self._apply_correction(placements, c, correction)
max_residual = max(max_residual, abs(residual))
if max_residual < ctx.tolerance:
return kcsolve.SolveResult(
status=kcsolve.SolveStatus.Converged,
iterations=i + 1,
final_residual=max_residual,
placements=placements
)
return kcsolve.SolveResult(
status=kcsolve.SolveStatus.MaxIterationsReached,
iterations=ctx.max_iterations,
final_residual=max_residual,
placements=placements
)
# Register at runtime
kcsolve.register(RelaxationSolver())
```
Python solvers are discovered from:
- `<user_macros>/solvers/*.py` — user-written solvers
- `mods/*/solvers/*.py` — addon-provided solvers
---
## 7. Layer 4: Server Integration
### 7.1 Solve Job Definition
Extends the existing worker system (WORKERS.md) with a new job type:
```yaml
job:
name: assembly-solve
version: 1
description: "Solve assembly constraints using specified solver"
trigger:
type: revision_created
filter:
item_type: assembly
scope:
type: assembly
compute:
type: solve
command: create-solve
args:
solver: ondsel # or "auto" for registry default
tolerance: 1e-10
max_iterations: 500
deterministic: true
output_placements: true # write solved placements back to revision
output_diagnostics: true # store constraint diagnostics in job result
runner:
tags: [create, solver]
timeout: 300
max_retries: 1
priority: 75
```
### 7.2 Headless Solve via Runner
The `create-solve` command in `silorunner`:
1. Claims job from Silo server
2. Downloads the assembly `.kc` file
3. Launches Headless Create (or standalone Python if pure-Python solver)
4. Loads the assembly, extracts constraint graph → `SolveContext`
5. Calls `solver.solve(ctx)`
6. Reports `SolveResult` back via `POST /api/runner/jobs/{id}/complete`
7. Optionally writes updated placements as a new revision
### 7.3 Standalone Solve Process (No GUI)
For server-side batch solving without Headless Create overhead:
```python
#!/usr/bin/env python3
"""Standalone solver worker — no FreeCAD dependency."""
import kcsolve
import json, sys
problem = json.load(sys.stdin)
ctx = kcsolve.SolveContext.from_dict(problem)
solver = kcsolve.load(problem.get("solver", "ondsel"))
result = solver.solve(ctx)
json.dump(result.to_dict(), sys.stdout)
```
This enables lightweight solver containers that don't need the full Create installation — useful for CI validation, quick constraint checks, and scaling solver capacity independently of geometry workers.
---
## 8. Semi-Deterministic Behavior
"Semi-deterministic" means: given the same constraint set and initial placements, the solver produces the same result. This is achieved by:
1. **Canonical input ordering**`SolveContext` sorts constraints and parts by a stable key (part label + constraint index) before passing to the solver. The ordering hash is stored in `SolveResult.input_hash`.
2. **Solver contract**`IKCSolver::is_deterministic()` reports whether the implementation guarantees this. OndselAdapter does (Lagrangian formulation with fixed pivot ordering). A GNN solver might not.
3. **Tolerance-aware comparison** — Two `SolveResult`s are "equivalent" if all placement deltas are within tolerance, even if iteration counts differ. Used for regression testing.
4. **Warm-start stability** — When `warm_start` placements are provided, the solver should converge to the same solution as a cold start (within tolerance), just faster. This is validated in the test suite.
---
## 9. Implementation Phases
### Phase 1: API + OndselAdapter (foundation) -- COMPLETE
- Defined `IKCSolver.h`, core types (`Types.h`), `SolverRegistry`
- Implemented `OndselAdapter` wrapping existing solver
- Assembly module calls through `SolverRegistry` instead of directly calling OndselSolver
- 18 C++ tests, 6 Python integration tests
- **PR:** #297 (merged)
### Phase 2: pybind11 Bindings -- COMPLETE
- Built `kcsolve` pybind11 module exposing all enums, structs, and classes
- `PyIKCSolver` trampoline for pure-Python solver subclasses
- `register_solver()` for runtime Python solver registration
- `PySolverHolder` for GIL-safe forwarding of virtual calls
- 16 Python tests covering types, registry, and Python solver round-trips
- Debug/introspection API (Debugger, `dependency_graph()`, `dof_analysis()`) deferred to Phase 4+
- Automatic Python solver discovery (`mods/*/solvers/`) deferred -- users call `register_solver()` explicitly
- **PR:** #298
- **Docs:** `docs/src/architecture/ondsel-solver.md`, `docs/src/reference/kcsolve-python.md`
### Phase 3: Server Integration
- `create-solve` command for `silorunner`
- YAML job definition for solve jobs
- Standalone solver process (no FreeCAD dependency)
- `SolveContext` JSON serialization for inter-process communication
- **Deliverable:** Solve jobs run async through the worker system
### Phase 4: Second Solver (validation)
- Implement a simple relaxation or gradient-descent solver as a Python plugin
- Validates that the API actually supports different solving strategies
- Benchmark against OndselAdapter for correctness and performance
- **Deliverable:** Two interchangeable solvers, selectable per-assembly
### Phase 5: GNN Solver (future)
- Graph Neural Network approach from existing roadmap
- Likely a Python solver wrapping a trained model
- Focus on fast approximate solutions for interactive editing
- Falls back to OndselAdapter for final precision solve
- **Deliverable:** Hybrid solve pipeline (GNN fast-guess → Lagrangian refinement)
---
## 10. File Locations
```
src/Lib/KCSolve/ # or src/Mod/Assembly/Solver/
├── include/
│ └── KCSolve/
│ ├── IKCSolver.h # Interface + all types
│ ├── SolverRegistry.h # Plugin loading and lookup
│ └── Types.h # Enums, structs
├── src/
│ ├── SolverRegistry.cpp
│ └── OndselAdapter.cpp
├── bindings/
│ └── kcsolve_py.cpp # pybind11
├── plugins/ # Additional compiled solver plugins
└── CMakeLists.txt
```
---
## 11. Open Questions
1. **Location**: `src/Lib/KCSolve/` (independent library, usable without Assembly module) vs `src/Mod/Assembly/Solver/` (tighter coupling, simpler build)? Leaning toward `src/Lib/` since server workers need it without the full Assembly module.
2. **Geometry abstraction**: The C++ API uses string references for faces/edges/vertices. Should we pass actual OCC geometry (TopoDS_Shape) through the interface, or keep it abstract and let each solver adapter resolve references? Abstract is more portable but adds a translation step.
3. **Constraint persistence**: Currently constraints live in the FCStd XML. Should the pluggable layer introduce its own serialization, or always read/write through FreeCAD's property system?
4. **API versioning**: `kcsolve_api_version()` returns a string. Semver with major-only breaking changes? How strict on backward compat for the plugin ABI?
5. **License implications**: OndselSolver is LGPL. New solver plugins could be any license since they're loaded at runtime via a stable C API boundary. Confirm this interpretation.
---
## 12. References
- [ondsel-solver.md](ondsel-solver.md) — Current solver documentation
- [WORKERS.md](WORKERS.md) — Worker/runner job system
- [MULTI_USER_EDITS.md](MULTI_USER_EDITS.md) — Async validation pipeline
- [DAG.md](DAG.md) — Dependency graph for incremental recompute
- [ROADMAP.md](ROADMAP.md) — Tier 3 compute modules, GNN solver plans

View File

@@ -1,26 +1,84 @@
# Known Issues
**Last updated:** 2026-03-03
## Issues
## Silo
### Critical
1. **Auth not production-hardened** -- LDAP/OIDC backends are coded but need infrastructure deployment. Currently only local (bcrypt) auth is tested in production.
2. **No unit tests** for Silo FreeCAD commands or Go backend.
3. **`Silo_BOM` requires Silo-tracked document** -- Unregistered documents show a warning with no inline registration path.
1. ~~**QSS duplication.**~~ Resolved. The canonical QSS lives in `src/Gui/Stylesheets/KindredCreate.qss`. The PreferencePacks copy is now generated at build time via `configure_file()` in `src/Gui/PreferencePacks/CMakeLists.txt`. The unused `resources/preferences/KindredCreate/` directory has been removed.
## Assembly
2. ~~**WorkbenchManipulator timing.**~~ Resolved. ZTools is no longer a workbench. Commands are registered via a deferred `QTimer.singleShot(2000ms)` in `InitGui.py`, which activates dependent workbenches first, then imports ZTools commands and installs the `_ZToolsManipulator`. The `EditingContextResolver` handles toolbar visibility based on editing context.
4. **Solver datum handling is minimal** -- Joints referencing datum planes/points may produce incorrect placement. The Kindred fork carries patches to `findPlacement()` for `PartDesign::Plane`, `PartDesign::Point`, `App::Plane`, and `App::Point`, but coverage is incomplete for complex assemblies.
3. ~~**Silo shortcut persistence.**~~ Resolved. `Silo_ToggleMode` removed; file operations now delegate to the selected origin via the unified origin system.
## Theme
### High
5. **QSS edits** -- Only edit `src/Gui/Stylesheets/KindredCreate.qss`. The PreferencePacks copy at `src/Gui/PreferencePacks/KindredCreate/KindredCreate.qss` is generated at build time via `configure_file()`.
4. **Silo authentication not production-hardened.** Local auth (bcrypt) works end-to-end. LDAP (FreeIPA) and OIDC (Keycloak) backends are coded but depend on infrastructure not yet deployed. FreeCAD client has `Silo_Auth` dock panel for login and API token management. Server has session middleware (`alexedwards/scs`), CSRF protection (`nosurf`), and role-based access control (admin/editor/viewer). Migration `009_auth.sql` adds users, api_tokens, and sessions tables.
## Platform
5. **No unit tests.** Zero test coverage for ztools and Silo FreeCAD commands. Silo Go backend also lacks tests.
6. **CI/CD is Linux x86_64 only** -- macOS and Windows build presets are defined but no CI runners are configured.
7. **Wayland fractional scaling** -- Some HiDPI scaling issues remain on Wayland with fractional scaling factors.
6. **Assembly solver datum handling is minimal.** The `findPlacement()` fix in `src/Mod/Assembly/UtilsAssembly.py` extracts placement from `obj.Shape.Faces[0]` for `PartDesign::Plane` and from shape vertex for `PartDesign::Point`. Does not handle empty shapes or non-planar datum objects.
## Archived Addons
### Medium
8. **ztools and QuickNav archived** -- These addons are in `reference/ztools/` and `reference/quicknav/` for reference only. They are not built or loaded. ZTools commands are being reimplemented through the KCSDK system and the datums addon.
7. **`Silo_BOM` requires Silo-tracked document.** Depends on `SiloPartNumber` property. Unregistered documents show a warning with no registration path.
8. **PartDesign menu insertion fragility.** `_ZToolsPartDesignManipulator.modifyMenuBar()` inserts after `PartDesign_Boolean`. If upstream renames this command, insertions silently fail.
9. **tangent_to_cylinder falls back to manual placement.** TangentPlane MapMode requires a vertex reference not collected by the current UI.
10. **`delete_bom_entry()` bypasses error normalization.** Uses raw `urllib.request` instead of `SiloClient._request()`.
11. **Missing Silo icons.** Three commands reference icons that don't exist: `silo-tag.svg` (`Silo_TagProjects`), `silo-rollback.svg` (`Silo_Rollback`), `silo-status.svg` (`Silo_SetStatus`). The `_icon()` function returns an empty string, so these commands render without toolbar icons.
### Fixed (retain for reference)
12. **OndselSolver Newton-Raphson convergence.** `NewtonRaphson::isConvergedToNumericalLimit()` compared `dxNorms->at(iterNo)` to itself instead of `dxNorms->at(iterNo - 1)`. This prevented convergence detection on complex assemblies, causing solver exhaustion and "grounded object moved" warnings. Fixed in Kindred fork (`src/3rdParty/OndselSolver`). Needs upstreaming to `FreeCAD/OndselSolver`.
13. **Assembly solver crash on document restore.** `AssemblyObject::onChanged()` called `updateSolveStatus()` when the Group property changed during document restore, triggering the solver while child objects were still deserializing (SIGSEGV). Fixed with `isRestoring()` and `isPerformingTransaction()` guards at `src/Mod/Assembly/App/AssemblyObject.cpp:143`.
14. **`DlgSettingsGeneral::applyMenuIconSize` visibility.** The method was `private` but called from `StartupProcess.cpp`. Fixed by moving to `public` (PR #49). Also required `Dialog::` namespace qualifier in `StartupProcess.cpp`.
---
## Incomplete features
### Silo
| Feature | Status | Notes |
|---------|--------|-------|
| Authentication | Local auth complete | LDAP/OIDC backends coded, pending infrastructure. Auth dock panel available. |
| CSRF protection | Implemented | `nosurf` library on web form routes |
| 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 | |
| CSV import rollback | Not implemented | `bom_handlers.go` |
| SSE event streaming | Implemented | Reconnect logic with exponential backoff |
| Database Activity panel | Implemented | Dock panel showing real-time server events |
| Start panel | Implemented | In-viewport start page with recent files and Silo integration |
### ztools
| 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 | |
---
## Next steps
1. **Authentication hardening** -- Deploy FreeIPA and Keycloak infrastructure. End-to-end test LDAP and OIDC flows. Harden token rotation and session expiry.
2. **BOM-Assembly bridge** -- Auto-populate Silo BOM from Assembly component links on save.
3. **File locking** -- Pessimistic locks on `Silo_Open` to prevent concurrent edits. Requires server-side lock table and client-side lock display.
4. **Build system** -- CMake install rules for `mods/` submodules so packages include ztools and Silo without manual steps.
5. **Test coverage** -- Unit tests for ztools datum creation, Silo FreeCAD commands, and Go API endpoints.
6. **QSS consolidation** -- Eliminate the 3-copy QSS duplication via build-time copy or symlinks. The canonical source is `resources/preferences/KindredCreate/KindredCreate.qss`.
7. **Update notification UI** -- Display in-app notification when a new release is available (issue #30). The update checker backend is already implemented.

395
docs/MULTI_USER_CLIENT.md Normal file
View File

@@ -0,0 +1,395 @@
# DAG Client Integration Contract
**Status:** Draft
**Last Updated:** 2026-02-13
This document describes what silo-mod and Headless Create runners need to implement to integrate with the Silo dependency DAG and worker system.
---
## 1. Overview
The DAG system has two client-side integration points:
1. **silo-mod workbench** (desktop) -- pushes DAG data to Silo on save or revision create.
2. **silorunner + silo-mod** (headless) -- extracts DAGs, validates features, and exports geometry as compute jobs.
Both share the same Python codebase in the silo-mod repository. Desktop users call the code interactively; runners call it headlessly via `create --console`.
---
## 2. DAG Sync Payload
Clients push feature trees to Silo via:
```
PUT /api/items/{partNumber}/dag
Authorization: Bearer <user_token or runner_token>
Content-Type: application/json
```
### 2.1 Request Body
```json
{
"revision_number": 3,
"nodes": [
{
"node_key": "Sketch001",
"node_type": "sketch",
"properties_hash": "a1b2c3d4e5f6...",
"metadata": {
"label": "Base Profile",
"constraint_count": 12
}
},
{
"node_key": "Pad001",
"node_type": "pad",
"properties_hash": "f6e5d4c3b2a1...",
"metadata": {
"label": "Main Extrusion",
"length": 25.0
}
}
],
"edges": [
{
"source_key": "Sketch001",
"target_key": "Pad001",
"edge_type": "depends_on"
}
]
}
```
### 2.2 Field Reference
**Nodes:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `node_key` | string | yes | Unique within item+revision. Use Create's internal object name (e.g. `Sketch001`, `Pad003`). |
| `node_type` | string | yes | One of: `sketch`, `pad`, `pocket`, `fillet`, `chamfer`, `constraint`, `body`, `part`, `datum`. |
| `properties_hash` | string | no | SHA-256 hex digest of the node's parametric inputs. Used for memoization. |
| `validation_state` | string | no | One of: `clean`, `dirty`, `validating`, `failed`. Defaults to `clean`. |
| `metadata` | object | no | Arbitrary key-value pairs for display or debugging. |
**Edges:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `source_key` | string | yes | The node that is depended upon. |
| `target_key` | string | yes | The node that depends on the source. |
| `edge_type` | string | no | One of: `depends_on` (default), `references`, `constrains`. |
**Direction convention:** Edges point from dependency to dependent. If Pad001 depends on Sketch001, the edge is `source_key: "Sketch001"`, `target_key: "Pad001"`.
### 2.3 Response
```json
{
"synced": true,
"node_count": 15,
"edge_count": 14
}
```
---
## 3. Computing properties_hash
The `properties_hash` enables memoization -- if a node's inputs haven't changed since the last validation, it can be skipped. Computing it:
```python
import hashlib
import json
def compute_properties_hash(feature_obj):
"""Hash the parametric inputs of a Create feature."""
inputs = {}
if feature_obj.TypeId == "Sketcher::SketchObject":
# Hash geometry + constraints
inputs["geometry_count"] = feature_obj.GeometryCount
inputs["constraint_count"] = feature_obj.ConstraintCount
inputs["geometry"] = str(feature_obj.Shape.exportBrep())
elif feature_obj.TypeId == "PartDesign::Pad":
inputs["length"] = feature_obj.Length.Value
inputs["type"] = str(feature_obj.Type)
inputs["reversed"] = feature_obj.Reversed
inputs["sketch"] = feature_obj.Profile[0].Name
# ... other feature types
canonical = json.dumps(inputs, sort_keys=True)
return hashlib.sha256(canonical.encode()).hexdigest()
```
The exact inputs per feature type are determined by what parametric values affect the feature's geometry. Include anything that, if changed, would require recomputation.
---
## 4. Feature Tree Walking
To extract the DAG from a Create document:
```python
import FreeCAD
def extract_dag(doc):
"""Walk a Create document and return nodes + edges."""
nodes = []
edges = []
for obj in doc.Objects:
# Skip non-feature objects
if not hasattr(obj, "TypeId"):
continue
node_type = classify_type(obj.TypeId)
if node_type is None:
continue
nodes.append({
"node_key": obj.Name,
"node_type": node_type,
"properties_hash": compute_properties_hash(obj),
"metadata": {
"label": obj.Label,
"type_id": obj.TypeId,
}
})
# Walk dependencies via InList (objects this one depends on)
for dep in obj.InList:
if hasattr(dep, "TypeId") and classify_type(dep.TypeId):
edges.append({
"source_key": dep.Name,
"target_key": obj.Name,
"edge_type": "depends_on",
})
return nodes, edges
def classify_type(type_id):
"""Map Create TypeIds to DAG node types."""
mapping = {
"Sketcher::SketchObject": "sketch",
"PartDesign::Pad": "pad",
"PartDesign::Pocket": "pocket",
"PartDesign::Fillet": "fillet",
"PartDesign::Chamfer": "chamfer",
"PartDesign::Body": "body",
"Part::Feature": "part",
"Sketcher::SketchConstraint": "constraint",
}
return mapping.get(type_id)
```
---
## 5. When to Push DAG Data
Push the DAG to Silo in these scenarios:
| Event | Trigger | Who |
|-------|---------|-----|
| User saves in silo-mod | On save callback | Desktop silo-mod workbench |
| User creates a revision | After `POST /api/items/{pn}/revisions` succeeds | Desktop silo-mod workbench |
| Runner extracts DAG | After `create-dag-extract` job completes | silorunner via `PUT /api/runner/jobs/{id}/dag` |
| Runner validates | After `create-validate` job, push updated validation states | silorunner via `PUT /api/runner/jobs/{id}/dag` |
---
## 6. Runner Entry Points
silo-mod must provide these Python entry points for headless invocation:
### 6.1 silo.runner.dag_extract
Extracts the feature DAG from a Create file and writes it as JSON.
```python
# silo/runner.py
def dag_extract(input_path, output_path):
"""
Extract feature DAG from a Create file.
Args:
input_path: Path to the .kc (Kindred Create) file.
output_path: Path to write the JSON output.
Output JSON format:
{
"nodes": [...], // Same format as DAG sync payload
"edges": [...]
}
"""
doc = FreeCAD.openDocument(input_path)
nodes, edges = extract_dag(doc)
with open(output_path, 'w') as f:
json.dump({"nodes": nodes, "edges": edges}, f)
FreeCAD.closeDocument(doc.Name)
```
### 6.2 silo.runner.validate
Rebuilds all features and reports pass/fail per node.
```python
def validate(input_path, output_path):
"""
Validate a Create file by rebuilding all features.
Output JSON format:
{
"valid": true/false,
"nodes": [
{
"node_key": "Pad001",
"state": "clean", // or "failed"
"message": null, // error message if failed
"properties_hash": "..."
}
]
}
"""
doc = FreeCAD.openDocument(input_path)
doc.recompute()
results = []
all_valid = True
for obj in doc.Objects:
if not hasattr(obj, "TypeId"):
continue
node_type = classify_type(obj.TypeId)
if node_type is None:
continue
state = "clean"
message = None
if hasattr(obj, "isValid") and not obj.isValid():
state = "failed"
message = f"Feature {obj.Label} failed to recompute"
all_valid = False
results.append({
"node_key": obj.Name,
"state": state,
"message": message,
"properties_hash": compute_properties_hash(obj),
})
with open(output_path, 'w') as f:
json.dump({"valid": all_valid, "nodes": results}, f)
FreeCAD.closeDocument(doc.Name)
```
### 6.3 silo.runner.export
Exports geometry to STEP, IGES, or other formats.
```python
def export(input_path, output_path, format="step"):
"""
Export a Create file to an external format.
Args:
input_path: Path to the .kc file.
output_path: Path to write the exported file.
format: Export format ("step", "iges", "stl", "obj").
"""
doc = FreeCAD.openDocument(input_path)
import Part
shapes = [obj.Shape for obj in doc.Objects if hasattr(obj, "Shape")]
compound = Part.makeCompound(shapes)
format_map = {
"step": "STEP",
"iges": "IGES",
"stl": "STL",
"obj": "OBJ",
}
Part.export([compound], output_path)
FreeCAD.closeDocument(doc.Name)
```
---
## 7. Headless Invocation
The `silorunner` binary shells out to Create (with silo-mod installed):
```bash
# DAG extraction
create --console -e "from silo.runner import dag_extract; dag_extract('/tmp/job/part.kc', '/tmp/job/dag.json')"
# Validation
create --console -e "from silo.runner import validate; validate('/tmp/job/part.kc', '/tmp/job/result.json')"
# Export
create --console -e "from silo.runner import export; export('/tmp/job/part.kc', '/tmp/job/output.step', 'step')"
```
**Prerequisites:** The runner host must have:
- Headless Create installed (Kindred's fork of FreeCAD)
- silo-mod installed as a Create addon (so `from silo.runner import ...` works)
- No display server required -- `--console` mode is headless
---
## 8. Validation Result Handling
After a runner completes a `create-validate` job, it should:
1. Read the result JSON.
2. Push updated validation states via `PUT /api/runner/jobs/{jobID}/dag`:
```json
{
"revision_number": 3,
"nodes": [
{"node_key": "Sketch001", "node_type": "sketch", "validation_state": "clean", "properties_hash": "abc..."},
{"node_key": "Pad001", "node_type": "pad", "validation_state": "failed", "properties_hash": "def..."}
],
"edges": [
{"source_key": "Sketch001", "target_key": "Pad001"}
]
}
```
3. Complete the job via `POST /api/runner/jobs/{jobID}/complete` with the summary result.
---
## 9. SSE Events
Clients should listen for these events on `GET /api/events`:
| Event | Payload | When |
|-------|---------|------|
| `dag.updated` | `{item_id, part_number, revision_number, node_count, edge_count}` | After any DAG sync |
| `dag.validated` | `{item_id, part_number, valid, failed_count}` | After validation completes |
| `job.created` | `{job_id, definition_name, trigger, item_id}` | Job auto-triggered or manually created |
| `job.claimed` | `{job_id, runner_id, runner}` | Runner claims a job |
| `job.progress` | `{job_id, progress, message}` | Runner reports progress |
| `job.completed` | `{job_id, runner_id}` | Job finishes successfully |
| `job.failed` | `{job_id, runner_id, error}` | Job fails |
| `job.cancelled` | `{job_id, cancelled_by}` | Job cancelled by user |
---
## 10. Cross-Item Edges
For assembly constraints that reference geometry in child parts (e.g. a mate constraint between two parts), use the `dag_cross_edges` table. These edges bridge the BOM DAG and the feature DAG.
Cross-item edges are **not** included in the standard `PUT /dag` sync. They will be managed through a dedicated endpoint in a future iteration once the assembly constraint model in Create/silo-mod is finalized.
For now, the DAG sync covers intra-item dependencies only. Assembly-level interference detection uses the BOM DAG (`relationships` table) combined with per-item feature DAGs.

View File

@@ -1,31 +1,26 @@
# Kindred Create
**Last updated:** 2026-03-02
**Branch:** main @ `b0621f9731c`
**Kindred Create:** v0.1.5
**FreeCAD base:** v1.2.0
**Last updated:** 2026-02-08
**Branch:** main @ `cf523f1d87a`
**Kindred Create:** v0.1.0
**FreeCAD base:** v1.0.0
## Documentation
| Document | Contents |
|----------|----------|
| [ARCHITECTURE.md](ARCHITECTURE.md) | Bootstrap flow, source layout, submodules |
| [COMPONENTS.md](COMPONENTS.md) | Silo, Gears, Origin commands, theme, icons |
| [COMPONENTS.md](COMPONENTS.md) | ztools, Silo, Origin commands, theme, icons |
| [KNOWN_ISSUES.md](KNOWN_ISSUES.md) | Bugs, incomplete features, next steps |
| [INTEGRATION_PLAN.md](INTEGRATION_PLAN.md) | Architecture layers, integration phases |
| [CI_CD.md](CI_CD.md) | Build and release workflows |
| [INTER_SOLVER.md](INTER_SOLVER.md) | Inter-solver communication |
| [DAG_CLIENT_INTEGRATION.md](DAG_CLIENT_INTEGRATION.md) | DAG client integration (draft) |
| [BOM_MERGE.md](BOM_MERGE.md) | Bill of materials merge specification |
| [KC_SPECIFICATION.md](KC_SPECIFICATION.md) | .kc file format specification |
## Submodules
| Submodule | Path | Source | Pinned commit |
|-----------|------|--------|---------------|
| silo-mod | `mods/silo` | `git.kindred-systems.com/kindred/silo-mod` | `cc6a79f` |
| gears | `mods/gears` | `git.kindred-systems.com/kindred/gears` | `1e26c39` |
| datums | `mods/datums` | `git.kindred-systems.com/kindred/datums` | — |
| solver | `mods/solver` | `git.kindred-systems.com/kindred/solver` | `cd7f66f` |
| ztools | `mods/ztools` | `git.kindred-systems.com/forbes/ztools` | `3298d1c` |
| silo-mod | `mods/silo` | `git.kindred-systems.com/kindred/silo-mod` | `f9924d3` |
| OndselSolver | `src/3rdParty/OndselSolver` | `git.kindred-systems.com/kindred/solver` | `fe41fa3` |
| GSL | `src/3rdParty/GSL` | `github.com/microsoft/GSL` | `756c91a` |
| AddonManager | `src/Mod/AddonManager` | `github.com/FreeCAD/AddonManager` | `01e242e` |
@@ -33,6 +28,4 @@
The silo submodule was split from a monorepo into three repos: `silo-client` (shared Python API client), `silo-mod` (FreeCAD workbench, used as Create's submodule), and `silo-calc` (LibreOffice Calc extension). The `silo-mod` repo includes `silo-client` as its own submodule.
OndselSolver is forked from `github.com/FreeCAD/OndselSolver` to carry a Newton-Raphson convergence fix.
The ztools addon was removed from the build (#344) and archived to `reference/ztools/` (#345) as part of the UI/UX rework (#346). Its commands will be reimplemented through the KCSDK system.
OndselSolver is forked from `github.com/FreeCAD/OndselSolver` to carry a Newton-Raphson convergence fix (see [KNOWN_ISSUES.md](KNOWN_ISSUES.md#12)).

332
docs/REPOSITORY_STATE.md Normal file
View File

@@ -0,0 +1,332 @@
# Kindred Create Repository State Report
**Generated:** 2026-01-31
**Branch:** main
**Parent repo:** create-0070 @ `364a7057ef`
**Submodules:**
- ztools @ `8d1f195` (ztools-0065, main)
- silo @ `c778825` (silo-0062, main)
---
## Recent Changes (This Session)
### 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
```
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()
```
---
## Submodule Status
### ztools (ztools-0065)
| 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 |
**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}`
**Datum types supported**: offset_from_face, offset_from_plane, midplane,
3_points, normal_to_edge, angled, tangent_to_cylinder
### silo (silo-0062)
| 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 |
**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`
**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.
**BOM API methods** (new):
- `get_bom()`, `get_bom_expanded()`, `get_bom_where_used()`
- `add_bom_entry()`, `update_bom_entry()`, `delete_bom_entry()`
---
## Potential 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.
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.
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.
### 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.
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.
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.
### 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.
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.
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.
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", ...)`.
---
## Feature Stubs and TODOs
### From silo REPOSITORY_STATUS
| 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 | — |
| CSRF protection | Not implemented | Web UI only |
| CSV import transaction rollback | Not implemented | `bom_handlers.go` |
### From 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` |
### From Create integration plan
| Phase | Feature | Status |
|-------|---------|--------|
| 1 | Addon auto-loading | Done (`src/Mod/Create/InitGui.py`) |
| 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 |
---
## File Change Summary (Uncommitted)
### Parent repo (create-0070)
| 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 |
### ztools submodule (committed + pushed)
| File | Change |
|------|--------|
| `ztools/InitGui.py` | Added `_ZToolsPartDesignManipulator` |
| `CatppuccinMocha/CatppuccinMocha.qss` | Synced with canonical KindredCreate.qss |
### 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.

View File

@@ -37,11 +37,9 @@ These files/directories exist only in Kindred Create and can be copied directly
| Directory | Description |
|-----------|-------------|
| `icons/kindred/` | 1444 Catppuccin Mocha SVG icon overrides |
| `mods/silo/` | Silo PLM workbench submodule (`silo-mod`) |
| `mods/gears/` | Gears workbench submodule |
| `mods/datums/` | Datums addon submodule |
| `mods/solver/` | Solver addon submodule |
| `src/Mod/Create/` | Kindred Create module (InitGui.py, kc_format.py, update_checker.py, version.py.in) |
| `mods/silo/` | Silo workbench submodule (`silo-mod`) |
| `mods/ztools/` | ZTools workbench submodule |
| `src/Mod/Create/` | Kindred Create workbench (InitGui.py, kc_format.py, update_checker.py, version.py.in) |
| `package/` | rattler-build packaging recipe |
| `docs/` | Kindred documentation (architecture, specifications, guides) |
| `.gitea/` | CI workflows, issue templates, runner configs |
@@ -196,7 +194,7 @@ Files touched: `src/Mod/Assembly/App/AssemblyObject.cpp`, `src/Mod/Assembly/Util
| `CMakeLists.txt` | Re-apply Kindred version variables on top of upstream 1.2.0-dev |
| `package/rattler-build/recipe.yaml` | Update for new version, verify dependency pins |
| `.gitea/workflows/` | Verify CI still works with updated source |
| Submodule pins | Re-add `mods/silo`, `mods/gears`, `mods/datums`, `mods/solver` submodules |
| Submodule pins | Re-add `mods/silo`, `mods/ztools` submodules |
| `src/3rdParty/OndselSolver` | Verify our fork is still needed or if upstream fixed the issues |
---

View File

@@ -10,14 +10,16 @@
- [Installation](./guide/installation.md)
- [Building from Source](./guide/building.md)
- [Workbenches](./guide/workbenches.md)
- [ztools](./guide/ztools.md)
- [Silo](./guide/silo.md)
- [Document Templates](./guide/templates.md)
# Architecture
- [Bootstrap and Addon Loading](./reference/create-module-bootstrap.md)
- [KCSDK C++ Layer](./reference/kcsdk-python.md)
- [KCSolve: Pluggable Solver](./reference/kcsolve-python.md)
- [Overview](./architecture/overview.md)
- [Python as Source of Truth](./architecture/python-source-of-truth.md)
- [Silo Server](./architecture/silo-server.md)
- [Signal Architecture](./architecture/signal-architecture.md)
- [OndselSolver](./architecture/ondsel-solver.md)
# Development
@@ -27,10 +29,10 @@
- [Build System](./development/build-system.md)
- [Gui Module Build](./development/gui-build-integration.md)
- [Package.xml Schema Extensions](./development/package-xml-schema.md)
- [Writing an Addon](./development/writing-an-addon.md)
# Silo Server
- [Overview](./silo-server/overview.md)
- [Specification](./silo-server/SPECIFICATION.md)
- [Configuration](./silo-server/CONFIGURATION.md)
- [Deployment](./silo-server/DEPLOYMENT.md)
@@ -42,23 +44,10 @@
- [Component Audit](./silo-server/COMPONENT_AUDIT.md)
- [Status System](./silo-server/STATUS.md)
- [Gap Analysis](./silo-server/GAP_ANALYSIS.md)
- [Modules](./silo-server/MODULES.md)
- [Projects and Approvals](./silo-server/PROJECTS_AND_APPROVALS.md)
- [Workers](./silo-server/WORKERS.md)
- [Frontend Spec](./silo-server/frontend-spec.md)
- [Installation](./silo-server/INSTALL.md)
- [Solver Service](./silo-server/SOLVER.md)
- [Roadmap](./silo-server/ROADMAP.md)
# Kindred Solver
- [Overview](./solver/overview.md)
- [Expression DAG](./solver/expression-dag.md)
- [Constraints](./solver/constraints.md)
- [Solving Algorithms](./solver/solving.md)
- [Diagnostics](./solver/diagnostics.md)
- [Assembly Integration](./solver/assembly-integration.md)
- [Writing a Custom Solver](./solver/writing-a-solver.md)
# Reference
- [Configuration](./reference/configuration.md)
@@ -75,5 +64,3 @@
- [OriginSelectorWidget](./reference/cpp-origin-selector-widget.md)
- [FileOriginPython Bridge](./reference/cpp-file-origin-python.md)
- [Creating a Custom Origin (C++)](./reference/cpp-custom-origin-guide.md)
- [KCSDK Python API](./reference/kcsdk-python.md)
- [KCSolve Python API](./reference/kcsolve-python.md)

View File

@@ -0,0 +1,27 @@
# OndselSolver
OndselSolver is the assembly constraint solver used by FreeCAD's Assembly workbench. Kindred Create vendors a fork of the solver as a git submodule.
- **Path:** `src/3rdParty/OndselSolver/`
- **Source:** `git.kindred-systems.com/kindred/solver` (Kindred fork)
## How it works
The solver uses a **Lagrangian constraint formulation** to resolve assembly constraints (mates, joints, fixed positions). Given a set of parts with geometric constraints between them, it computes positions and orientations that satisfy all constraints simultaneously.
The Assembly workbench (`src/Mod/Assembly/`) calls the solver whenever constraints are added or modified. Kindred Create has patches to `Assembly/` that extend `findPlacement()` for better datum and origin handling.
## Why a fork
The solver is forked from the upstream Ondsel project for:
- **Pinned stability** — the submodule is pinned to a known-good commit
- **Potential modifications** — the fork allows Kindred-specific patches if needed
- **Availability** — hosted on Kindred's Gitea instance for reliable access
## Future: GNN solver
There are plans to explore a Graph Neural Network (GNN) approach to constraint solving that could complement or supplement the Lagrangian solver for specific use cases. This is not yet implemented.
## Related: GSL
The `src/3rdParty/GSL/` submodule is Microsoft's Guidelines Support Library (`github.com/microsoft/GSL`), providing C++ core guidelines utilities like `gsl::span` and `gsl::not_null`. It is a build dependency, not related to the constraint solver.

View File

@@ -0,0 +1,78 @@
# Architecture Overview
Kindred Create is structured as a thin integration layer on top of FreeCAD. The design follows three principles:
1. **Minimal core modifications** — prefer submodule addons over patching FreeCAD internals
2. **Graceful degradation** — Create runs without ztools or Silo if submodules are missing
3. **Pure Python addons** — workbenches follow FreeCAD's standard addon pattern
## Three-layer model
```
┌─────────────────────────────────┐
│ FreeCAD Documents (.FCStd) │ Python source of truth
│ Workbench logic (Python) │
├─────────────────────────────────┤
│ PostgreSQL │ Silo metadata, revisions, BOM
├─────────────────────────────────┤
│ MinIO (S3-compatible) │ Binary file storage cache
└─────────────────────────────────┘
```
FreeCAD documents are the authoritative representation of CAD data. Silo's PostgreSQL database stores metadata (part numbers, revisions, BOM relationships) and MinIO stores the binary `.FCStd` files. The FreeCAD workbench synchronizes between local files and the server.
## Source layout
```
create/
├── src/App/ # Core application (C++)
├── src/Base/ # Foundation classes (C++)
├── src/Gui/ # GUI framework (C++ + Qt6 + QSS)
│ ├── Stylesheets/ # KindredCreate.qss theme
│ ├── PreferencePacks/ # Theme preference pack
│ ├── Icons/ # silo-*.svg origin icons
│ ├── FileOrigin.* # Abstract file origin interface
│ └── OriginManager.* # Origin lifecycle management
├── src/Mod/ # ~37 FreeCAD modules
│ ├── Create/ # Kindred bootstrap module
│ ├── Assembly/ # Assembly workbench (Kindred patches)
│ ├── PartDesign/ # Part Design (stock + ztools injection)
│ └── ... # Other stock FreeCAD modules
├── mods/
│ ├── ztools/ # Datum/pattern/pocket workbench (submodule)
│ └── silo/ # Parts database workbench (submodule)
└── src/3rdParty/
├── OndselSolver/ # Assembly solver (submodule)
└── GSL/ # Guidelines Support Library (submodule)
```
## Bootstrap sequence
1. FreeCAD core initializes, discovers `src/Mod/Create/`
2. `Init.py` runs `setup_kindred_addons()` — adds `mods/ztools/ztools` and `mods/silo/freecad` to `sys.path`, executes their `Init.py`
3. GUI phase: `InitGui.py` runs `setup_kindred_workbenches()` — executes addon `InitGui.py` files to register workbenches
4. Deferred QTimer cascade:
- **1500ms** — Register Silo as a file origin
- **2000ms** — Dock the Silo auth panel
- **3000ms** — Check for Silo first-start configuration
- **4000ms** — Dock the Silo activity panel
- **10000ms** — Check for application updates
The QTimer cascade exists because FreeCAD's startup is not fully synchronous — Silo registration must wait for the GUI framework to be ready.
## Origin system
The origin system is Kindred's primary addition to FreeCAD's GUI layer:
- **`FileOrigin`** — abstract C++ interface for file backends
- **`LocalFileOrigin`** — default implementation (local filesystem)
- **`SiloOrigin`** — Silo database backend (registered by the Python addon)
- **`OriginManager`** — manages origin lifecycle, switching, capability queries
- **`OriginSelectorWidget`** — dropdown in the File toolbar
- **`CommandOrigin.cpp`** — Commit / Pull / Push / Info / BOM commands that delegate to the active origin
## Module interaction
- **ztools** injects commands into PartDesign via `_ZToolsPartDesignManipulator`
- **Silo** registers as a `FileOrigin` backend via `silo_origin.register_silo_origin()`
- **Create module** is glue only — no feature code, just bootstrap and version management

View File

@@ -0,0 +1,26 @@
# Python as Source of Truth
In Kindred Create's architecture, FreeCAD documents (`.FCStd` files) are the authoritative representation of all CAD data. The Silo database and MinIO storage are caches and metadata indexes — they do not define the model.
## How it works
An `.FCStd` file is a ZIP archive containing:
- XML documents describing the parametric model tree
- BREP geometry files for each shape
- Thumbnail images
- Embedded spreadsheets and metadata
When a user runs **Commit**, the workbench uploads the entire `.FCStd` file to MinIO and records metadata (part number, revision, timestamp, commit message) in PostgreSQL. When a user runs **Pull**, the workbench downloads the `.FCStd` from MinIO and opens it locally.
## Why this design
- **No data loss** — the complete model is always in the `.FCStd` file, never split across systems
- **Offline capability** — engineers can work without a server connection
- **FreeCAD compatibility** — files are standard FreeCAD documents, openable in stock FreeCAD
- **Simple sync model** — the unit of transfer is always a whole file, avoiding merge conflicts in binary geometry
## Trade-offs
- **Large files** — `.FCStd` files can grow large with complex assemblies; every commit stores the full file
- **No partial sync** — you cannot pull a single feature or component; it is all or nothing
- **Conflict resolution** — two users editing the same file must resolve conflicts manually (last-commit-wins by default)

View File

@@ -0,0 +1,274 @@
# Signal Architecture
Kindred Create uses two signal systems side by side: **fastsignals** for domain and application events, and **Qt signals** for UI framework integration. This page explains why both exist, where each is used, and the patterns for working with them.
## Why two signal systems
Qt signals require the `Q_OBJECT` macro, MOC preprocessing, and a `QObject` base class. This makes them the right tool for widget-to-widget communication but a poor fit for domain classes like `FileOrigin`, `OriginManager`, and `App::Document` that are not `QObject` subclasses and should not depend on the Qt meta-object system.
fastsignals is a header-only C++ library that provides type-safe signals with lambda support, RAII connection management, and no build-tool preprocessing. It lives at `src/3rdParty/FastSignals/` and is linked into the Gui module via CMake.
| | Qt signals | fastsignals |
|---|-----------|-------------|
| **Requires** | `Q_OBJECT`, MOC, `QObject` base | Nothing (header-only) |
| **Dispatch** | Event loop (can be queued) | Synchronous, immediate |
| **Connection** | `QObject::connect()` | `signal.connect(lambda)` |
| **Lifetime** | Tied to `QObject` parent-child tree | `scoped_connection` RAII |
| **Thread model** | Can queue across threads | Fires on emitter's thread |
| **Slot types** | Slots, lambdas, `std::function` | Lambdas, `std::function`, function pointers |
## Where each is used
### fastsignals — domain events
These are events about application state that multiple independent listeners may care about. The emitting class is not a `QObject`.
**FileOrigin** (`src/Gui/FileOrigin.h`):
```cpp
fastsignals::signal<void(ConnectionState)> signalConnectionStateChanged;
```
**OriginManager** (`src/Gui/OriginManager.h`):
```cpp
fastsignals::signal<void(const std::string&)> signalOriginRegistered;
fastsignals::signal<void(const std::string&)> signalOriginUnregistered;
fastsignals::signal<void(const std::string&)> signalCurrentOriginChanged;
fastsignals::signal<void(App::Document*, const std::string&)> signalDocumentOriginChanged;
```
**Gui::Document** (`src/Gui/Document.h`):
```cpp
mutable fastsignals::signal<void(const ViewProviderDocumentObject&)> signalNewObject;
mutable fastsignals::signal<void(const ViewProviderDocumentObject&)> signalDeletedObject;
mutable fastsignals::signal<void(const ViewProviderDocumentObject&,
const App::Property&)> signalChangedObject;
// ~9 more document-level signals
```
**Gui::Application** (`src/Gui/Application.h`):
```cpp
fastsignals::signal<void(const Gui::Document&, bool)> signalNewDocument;
fastsignals::signal<void(const Gui::Document&)> signalDeleteDocument;
fastsignals::signal<void(const Gui::ViewProvider&)> signalNewObject;
// ~7 more application-level signals
```
### Qt signals — UI interaction
These are events between Qt widgets, actions, and framework components where `QObject` is already the base class and the Qt event loop handles dispatch.
**OriginSelectorWidget** (a `QToolButton` subclass):
```cpp
// QActionGroup::triggered → onOriginActionTriggered
connect(m_originActions, &QActionGroup::triggered,
this, &OriginSelectorWidget::onOriginActionTriggered);
// QAction::triggered → onManageOriginsClicked
connect(m_manageAction, &QAction::triggered,
this, &OriginSelectorWidget::onManageOriginsClicked);
```
### The boundary
The pattern is consistent: **fastsignals for observable state changes in domain classes, Qt signals for UI framework plumbing**. A typical flow crosses the boundary once:
```
OriginManager emits signalCurrentOriginChanged (fastsignals)
→ OriginSelectorWidget::onCurrentOriginChanged (lambda listener)
→ updates QToolButton text/icon (Qt API calls)
```
## fastsignals API
### Declaring a signal
Signals are public member variables with a template signature:
```cpp
fastsignals::signal<void(const std::string&)> signalSomethingHappened;
```
Use `mutable` if the signal needs to fire from `const` methods (as `Gui::Document` does):
```cpp
mutable fastsignals::signal<void(int)> signalReadOnlyEvent;
```
Name signals with the `signal` prefix by convention.
### Emitting
Call the signal like a function:
```cpp
signalSomethingHappened("hello");
```
All connected slots execute **synchronously**, in connection order, before the call returns. There is no event loop queuing.
### Connecting
`signal.connect()` accepts any callable and returns a connection object:
```cpp
auto conn = mySignal.connect([](const std::string& s) {
Base::Console().log("Got: %s\n", s.c_str());
});
```
### Connection types
| Type | RAII | Copyable | Use case |
|------|------|----------|----------|
| `fastsignals::connection` | No | Yes | Long-lived, manually managed |
| `fastsignals::scoped_connection` | Yes | No (move only) | Member variable, automatic cleanup |
| `fastsignals::advanced_connection` | No | No | Temporary blocking via `shared_connection_block` |
`scoped_connection` is the standard choice for class members. It disconnects automatically when destroyed.
### Disconnecting
```cpp
conn.disconnect(); // Explicit
// or let scoped_connection destructor handle it
```
## Connection patterns
### Pattern 1: Scoped member connections
The most common pattern. Store `scoped_connection` as a class member and connect in the constructor or an `attach()` method.
```cpp
class MyListener {
fastsignals::scoped_connection m_conn;
public:
MyListener(OriginManager* mgr)
{
m_conn = mgr->signalOriginRegistered.connect(
[this](const std::string& id) { onRegistered(id); }
);
}
// Destructor auto-disconnects via m_conn
~MyListener() = default;
private:
void onRegistered(const std::string& id) { /* ... */ }
};
```
### Pattern 2: Explicit disconnect in destructor
`OriginSelectorWidget` disconnects explicitly before destruction to prevent any signal delivery during teardown:
```cpp
OriginSelectorWidget::~OriginSelectorWidget()
{
disconnectSignals(); // m_conn*.disconnect()
}
```
This is defensive — `scoped_connection` would disconnect on its own, but explicit disconnection avoids edge cases where a signal fires between member destruction order.
### Pattern 3: DocumentObserver base class
`Gui::DocumentObserver` wraps ~10 document signals behind virtual methods:
```cpp
class DocumentObserver {
using Connection = fastsignals::scoped_connection;
Connection connectDocumentCreatedObject;
Connection connectDocumentDeletedObject;
// ...
void attachDocument(Document* doc); // connects all
void detachDocument(); // disconnects all
};
```
Subclasses override `slotCreatedObject()`, `slotDeletedObject()`, etc. This pattern avoids repeating connection boilerplate in every document listener.
### Pattern 4: Temporary blocking
`advanced_connection` supports blocking a slot without disconnecting:
```cpp
auto conn = signal.connect(handler, fastsignals::advanced_tag());
fastsignals::shared_connection_block block(conn, true); // blocked
signal(); // handler does NOT execute
block.unblock();
signal(); // handler executes
```
Use this to prevent recursive signal handling.
## Thread safety
### What is thread-safe
- **Emitting** a signal from any thread (internal spin mutex protects the slot list).
- **Connecting and disconnecting** from any thread, even while slots are executing on another thread.
### What is not thread-safe
- **Accessing the same `connection` object** from multiple threads. Protect with your own mutex or keep connection objects thread-local.
- **Slot execution context.** Slots run on the emitter's thread. If a fastsignal fires on a background thread and the slot touches Qt widgets, you must marshal to the main thread:
```cpp
mgr->signalOriginRegistered.connect([this](const std::string& id) {
QMetaObject::invokeMethod(this, [this, id]() {
// Now on the main thread — safe to update UI
updateUI(id);
}, Qt::QueuedConnection);
});
```
In practice, all origin and document signals in Kindred Create fire on the main thread, so this marshalling is not currently needed. It would become necessary if background workers emitted signals.
## Performance
- **Emission:** O(n) where n = connected slots. No allocation, no event loop overhead.
- **Connection:** O(1) with spin mutex.
- **Memory:** Each signal stores a shared pointer to a slot vector. Each `scoped_connection` is ~16 bytes.
- fastsignals is rarely a bottleneck. Profile before optimising signal infrastructure.
## Adding a new signal
1. Declare the signal as a public member (or `mutable` if emitting from const methods).
2. Name it with the `signal` prefix.
3. Emit it at the appropriate point in your code.
4. Listeners store `scoped_connection` members and connect via lambdas.
5. Document the signal's signature and when it fires.
Do not create a fastsignal for single-listener scenarios — a direct method call is simpler.
## Common mistakes
**Dangling `this` capture.** If a lambda captures `this` and the object is destroyed before the connection, the next emission crashes. Always store the connection as a `scoped_connection` member so it disconnects on destruction.
**Assuming queued dispatch.** fastsignals are synchronous. A slot that blocks will block the emitter. Keep slots fast or offload work to a background thread.
**Forgetting `mutable`.** If you need to emit from a `const` method, the signal member must be `mutable`. Otherwise the compiler rejects the call.
**Copying `scoped_connection`.** It is move-only. Use `std::move()` when putting connections into containers:
```cpp
std::vector<fastsignals::scoped_connection> conns;
conns.push_back(std::move(conn)); // OK
```
## See also
- [OriginManager](../reference/cpp-origin-manager.md) — signal catalog for origin lifecycle events
- [FileOrigin Interface](../reference/cpp-file-origin.md) — `signalConnectionStateChanged`
- [OriginSelectorWidget](../reference/cpp-origin-selector-widget.md) — listener patterns in practice
- `src/3rdParty/FastSignals/` — library source and headers

View File

@@ -0,0 +1,11 @@
# Silo Server
The Silo server architecture is documented in the dedicated [Silo Server](../silo-server/overview.md) section.
- [Overview](../silo-server/overview.md) — Architecture, stack, and key features
- [Specification](../silo-server/SPECIFICATION.md) — Full API specification with 38+ routes
- [Configuration](../silo-server/CONFIGURATION.md) — YAML config reference
- [Deployment](../silo-server/DEPLOYMENT.md) — Docker Compose, systemd, production setup
- [Authentication](../silo-server/AUTH.md) — LDAP, OIDC, and local auth backends
- [Status System](../silo-server/STATUS.md) — Revision lifecycle states
- [Gap Analysis](../silo-server/GAP_ANALYSIS.md) — Current gaps and planned improvements

View File

@@ -13,7 +13,7 @@ Kindred Create uses **CMake** for build configuration, **pixi** (conda-based) fo
## CMake configuration
The root `CMakeLists.txt` defines:
- **Kindred Create version:** `0.1.5` (via `KINDRED_CREATE_VERSION`)
- **Kindred Create version:** `0.1.0` (via `KINDRED_CREATE_VERSION`)
- **FreeCAD base version:** `1.0.0` (via `FREECAD_VERSION`)
- CMake policy settings for compatibility
- ccache auto-detection
@@ -25,7 +25,7 @@ The root `CMakeLists.txt` defines:
The version flows from CMake to Python via `configure_file()`:
```
CMakeLists.txt (KINDRED_CREATE_VERSION = "0.1.5")
CMakeLists.txt (KINDRED_CREATE_VERSION = "0.1.0")
→ src/Mod/Create/version.py.in (template)
→ build/*/Mod/Create/version.py (generated)
→ update_checker.py (imports VERSION)

View File

@@ -37,7 +37,7 @@ Use [Conventional Commits](https://www.conventionalcommits.org/):
| `art:` | Icons, theme, visual assets |
Scope is optional but encouraged:
- `feat(datums): add datum point creation mode`
- `feat(ztools): add datum point creation mode`
- `fix(gui): correct menu icon size on Wayland`
- `chore: update silo submodule`
@@ -45,7 +45,7 @@ Scope is optional but encouraged:
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 (Silo, Gears, Datums, theme) or base FreeCAD
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

View File

@@ -157,7 +157,7 @@ Edit only the canonical file in `Stylesheets/` — the preference pack copy is g
Defined in the top-level `CMakeLists.txt` and injected as compiler definitions:
```cmake
set(KINDRED_CREATE_VERSION "0.1.5")
set(KINDRED_CREATE_VERSION "0.1.0")
set(FREECAD_VERSION "1.0.0")
add_definitions(-DKINDRED_CREATE_VERSION="${KINDRED_CREATE_VERSION}")

View File

@@ -6,7 +6,6 @@ create/
│ ├── App/ # Core application (C++)
│ ├── Base/ # Base classes (C++)
│ ├── Gui/ # GUI framework and stylesheets (C++)
│ │ └── SDK/ # KCSDK C++ library (libKCSDK)
│ ├── Main/ # Application entry points
│ ├── Mod/ # FreeCAD modules (~37)
│ │ ├── Create/ # Kindred bootstrap module
@@ -18,15 +17,9 @@ create/
│ └── 3rdParty/
│ ├── OndselSolver/ # Assembly solver (submodule)
│ └── GSL/ # Guidelines Support Library (submodule)
├── mods/ # Kindred addon modules
│ ├── sdk/ # Addon SDK — stable API contract (priority 0)
── solver/ # Assembly solver research (submodule, priority 10)
│ ├── gears/ # Gears workbench (submodule, priority 40)
│ ├── datums/ # Unified datum creator (submodule, priority 45)
│ └── silo/ # Silo PLM workbench (submodule, priority 60)
├── reference/ # Archived addons (not built)
│ ├── ztools/ # Archived — commands migrated to datums + core
│ └── quicknav/ # Archived — navigation addon
├── mods/ # Kindred addon workbenches (submodules)
│ ├── ztools/ # ztools workbench
── silo/ # Silo parts database
├── icons/ # Icon theming (kindred overrides, palettes, retheme script)
│ ├── kindred/ # Hand-crafted Catppuccin Mocha SVG overrides (~1444 icons)
│ ├── mappings/ # Color palette CSVs (FCAD.csv, kindred.csv)
@@ -36,7 +29,10 @@ create/
│ └── icons/ # Platform icons (.ico, .icns, hicolor)
├── package/ # Packaging scripts
│ ├── debian/ # Debian package
── rattler-build/ # Cross-platform bundles (AppImage, DMG, NSIS)
── ubuntu/ # Ubuntu-specific
│ ├── fedora/ # RPM package
│ ├── rattler-build/ # Cross-platform bundles (AppImage, DMG, NSIS)
│ └── WindowsInstaller/ # NSIS installer definition
├── .gitea/workflows/ # CI/CD pipelines
│ ├── build.yml # Build + test on push/PR
│ └── release.yml # Release on tag push
@@ -45,6 +41,9 @@ create/
│ └── lib/ # Google Test framework (submodule)
├── cMake/ # CMake helper modules
├── docs/ # Documentation (this book)
├── tools/ # Dev utilities (build, lint, profile)
├── contrib/ # IDE configs (VSCode, CLion, debugger)
├── data/ # Example and test data
├── CMakeLists.txt # Root build configuration
├── CMakePresets.json # Platform build presets
├── pixi.toml # Pixi environment and tasks
@@ -58,10 +57,8 @@ create/
| Submodule | Path | Source | Purpose |
|-----------|------|--------|---------|
| silo-mod | `mods/silo` | `git.kindred-systems.com/kindred/silo-mod` | PLM workbench |
| gears | `mods/gears` | `git.kindred-systems.com/kindred/gears` | Parametric gear generation |
| datums | `mods/datums` | `git.kindred-systems.com/kindred/datums` | Unified datum creator |
| solver | `mods/solver` | `git.kindred-systems.com/kindred/solver` | Assembly solver research |
| ztools | `mods/ztools` | `git.kindred-systems.com/forbes/ztools` | Unified workbench |
| silo-mod | `mods/silo` | `git.kindred-systems.com/kindred/silo-mod` | Parts database |
| OndselSolver | `src/3rdParty/OndselSolver` | `git.kindred-systems.com/kindred/solver` | Assembly solver |
| GSL | `src/3rdParty/GSL` | `github.com/microsoft/GSL` | C++ guidelines library |
| AddonManager | `src/Mod/AddonManager` | `github.com/FreeCAD/AddonManager` | Extension manager |
@@ -72,11 +69,8 @@ create/
| File | Purpose |
|------|---------|
| `src/Mod/Create/Init.py` | Console-phase bootstrap — loads addons |
| `src/Mod/Create/InitGui.py` | GUI-phase bootstrap — kc_format, update checker |
| `src/Mod/Create/addon_loader.py` | Manifest-driven addon loader with dependency resolution |
| `src/Mod/Create/InitGui.py` | GUI-phase bootstrap — registers workbenches, deferred setup |
| `src/Gui/FileOrigin.h` | Abstract file origin interface (Kindred addition) |
| `src/Gui/EditingContext.h` | Editing context resolver (Kindred addition) |
| `src/Gui/SDK/` | KCSDK C++ library — stable addon API |
| `src/Gui/Stylesheets/KindredCreate.qss` | Catppuccin Mocha theme |
| `pixi.toml` | Build tasks and dependencies |
| `CMakeLists.txt` | Root CMake configuration |

View File

@@ -1,283 +0,0 @@
# Writing an Addon
This guide walks through creating a Kindred Create addon from scratch. Addons are Python packages in the `mods/` directory that extend Create with commands, panels, and UI modifications through the SDK.
## Addon structure
A minimal addon has this layout:
```
mods/my-addon/
├── package.xml # Manifest (required)
├── Init.py # Console-phase bootstrap
├── InitGui.py # GUI-phase bootstrap
└── my_addon/
├── __init__.py
└── commands.py # Your commands
```
## Step 1: Create the manifest
Every addon needs a `package.xml` with a `<kindred>` extension block. The `<workbench>` tag is required for `InitGui.py` to be loaded, even if your addon doesn't register a workbench.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<package format="1">
<name>my-addon</name>
<description>My custom addon for Kindred Create.</description>
<version>0.1.0</version>
<maintainer email="you@example.com">Your Name</maintainer>
<license>LGPL-2.1-or-later</license>
<!-- Required for InitGui.py loading -->
<workbench>
<classname>MyAddonWorkbench</classname>
</workbench>
<kindred>
<min_create_version>0.1.5</min_create_version>
<load_priority>70</load_priority>
<pure_python>true</pure_python>
<dependencies>
<dependency>sdk</dependency>
</dependencies>
</kindred>
</package>
```
### Priority ranges
| Range | Use |
|-------|-----|
| 0-9 | SDK and core infrastructure |
| 10-49 | Foundation addons (solver, gears, datums) |
| 50-99 | Standard addons (silo) |
| 100+ | Optional/user addons |
See [Package.xml Schema Extensions](./package-xml-schema.md) for the full schema.
## Step 2: Console bootstrap (Init.py)
`Init.py` runs during FreeCAD's console initialization, before the GUI exists. Use it for non-GUI setup.
```python
import FreeCAD
FreeCAD.Console.PrintLog("my-addon: loaded (console)\n")
```
## Step 3: GUI bootstrap (InitGui.py)
`InitGui.py` runs when the GUI is ready. This is where you register commands, contexts, panels, and overlays.
```python
import FreeCAD
import FreeCADGui
FreeCAD.Console.PrintLog("my-addon: loaded (GUI)\n")
def _deferred_setup():
"""Register commands and UI after the main window is ready."""
from my_addon import commands
commands.register()
from PySide.QtCore import QTimer
QTimer.singleShot(2000, _deferred_setup)
```
Deferred setup via `QTimer.singleShot()` avoids timing issues during startup. See [Create Module Bootstrap](../reference/create-module-bootstrap.md) for the full timer cascade.
## Step 4: Register commands
FreeCAD commands use `Gui.addCommand()`. This is a stable FreeCAD API and does not need SDK wrappers.
```python
# my_addon/commands.py
import FreeCAD
import FreeCADGui
class MyCommand:
def GetResources(self):
return {
"MenuText": "My Command",
"ToolTip": "Does something useful",
}
def Activated(self):
FreeCAD.Console.PrintMessage("My command activated\n")
def IsActive(self):
return FreeCAD.ActiveDocument is not None
def register():
FreeCADGui.addCommand("MyAddon_MyCommand", MyCommand())
```
## Step 5: Inject into editing contexts
Use the SDK to add your commands to existing toolbar contexts, rather than creating a standalone workbench.
```python
from kindred_sdk import inject_commands
# Add your command to the PartDesign body context toolbar
inject_commands("partdesign.body", "PartDesign", ["MyAddon_MyCommand"])
```
Built-in contexts you can inject into: `sketcher.edit`, `assembly.edit`, `partdesign.feature`, `partdesign.body`, `assembly.idle`, `spreadsheet`, `empty_document`, `no_document`.
## Step 6: Register a custom context
If your addon has its own editing mode, register a context to control which toolbars are visible.
```python
from kindred_sdk import register_context
def _is_my_object_in_edit():
import FreeCADGui
doc = FreeCADGui.activeDocument()
if doc and doc.getInEdit():
obj = doc.getInEdit().Object
return obj.isDerivedFrom("App::FeaturePython") and hasattr(obj, "MyAddonType")
return False
register_context(
"myaddon.edit",
"Editing {name}",
"#f9e2af", # Catppuccin yellow
["MyAddonToolbar", "StandardViews"],
_is_my_object_in_edit,
priority=55,
)
```
## Step 7: Register a dock panel
For panels that live in the dock area (like Silo's database panels), use the SDK panel registration.
### Simple approach (recommended for most addons)
```python
from kindred_sdk import register_dock_panel
def _create_my_panel():
from PySide import QtWidgets
widget = QtWidgets.QTreeWidget()
widget.setHeaderLabels(["Name", "Value"])
return widget
register_dock_panel(
"MyAddonPanel", # unique object name
"My Addon", # title bar text
_create_my_panel,
area="right",
delay_ms=3000, # create 3 seconds after startup
)
```
### Advanced approach (IPanelProvider)
For full control over panel behavior, implement the `IPanelProvider` interface directly:
```python
import kcsdk
class MyPanelProvider(kcsdk.IPanelProvider):
def id(self):
return "myaddon.inspector"
def title(self):
return "Inspector"
def create_widget(self):
from PySide import QtWidgets
tree = QtWidgets.QTreeWidget()
tree.setHeaderLabels(["Property", "Value"])
return tree
def preferred_area(self):
return kcsdk.DockArea.Left
def context_affinity(self):
return "myaddon.edit" # only visible in your custom context
# Register and create
kcsdk.register_panel(MyPanelProvider())
kcsdk.create_panel("myaddon.inspector")
```
## Step 8: Use theme colors
The SDK provides the Catppuccin Mocha palette for consistent theming.
```python
from kindred_sdk import get_theme_tokens, load_palette
# Quick lookup
tokens = get_theme_tokens()
blue = tokens["blue"] # "#89b4fa"
error = tokens["error"] # mapped from semantic role
# Full palette object
palette = load_palette()
palette.get("accent.primary") # semantic role lookup
palette.get("mauve") # direct color lookup
# Format QSS templates
qss = palette.format_qss("background: {base}; color: {text};")
```
## Complete example
Putting it all together, here's a minimal addon that adds a command and a dock panel:
```
mods/my-addon/
├── package.xml
├── Init.py
├── InitGui.py
└── my_addon/
├── __init__.py
└── commands.py
```
**InitGui.py:**
```python
import FreeCAD
def _setup():
from my_addon.commands import register
from kindred_sdk import inject_commands, register_dock_panel
register()
inject_commands("partdesign.body", "PartDesign", ["MyAddon_MyCommand"])
from PySide import QtWidgets
register_dock_panel(
"MyAddonPanel", "My Addon",
lambda: QtWidgets.QLabel("Hello from my addon"),
area="right", delay_ms=0,
)
from PySide.QtCore import QTimer
QTimer.singleShot(2500, _setup)
```
## Key patterns
- **Use `kindred_sdk` wrappers** instead of `FreeCADGui.*` internals. The SDK handles fallback and error logging.
- **Defer initialization** with `QTimer.singleShot()` to avoid startup timing issues.
- **Declare `<dependency>sdk</dependency>`** in your manifest to ensure the SDK loads before your addon.
- **Inject commands into existing contexts** rather than creating standalone workbenches. This gives users a unified toolbar experience.
- **Use theme tokens** from the palette for colors. Don't hardcode hex values.
## Related
- [KCSDK Python API Reference](../reference/kcsdk-python.md)
- [Package.xml Schema Extensions](./package-xml-schema.md)
- [Create Module Bootstrap](../reference/create-module-bootstrap.md)

View File

@@ -19,14 +19,12 @@ If cloned without `--recursive`:
git submodule update --init --recursive
```
The repository includes several submodules:
The repository includes six submodules:
| Submodule | Path | Source |
|-----------|------|--------|
| ztools | `mods/ztools` | `git.kindred-systems.com/forbes/ztools` |
| silo-mod | `mods/silo` | `git.kindred-systems.com/kindred/silo-mod` |
| gears | `mods/gears` | `git.kindred-systems.com/kindred/gears` |
| datums | `mods/datums` | `git.kindred-systems.com/kindred/datums` |
| solver | `mods/solver` | `git.kindred-systems.com/kindred/solver` |
| OndselSolver | `src/3rdParty/OndselSolver` | `git.kindred-systems.com/kindred/solver` |
| GSL | `src/3rdParty/GSL` | `github.com/microsoft/GSL` |
| AddonManager | `src/Mod/AddonManager` | `github.com/FreeCAD/AddonManager` |
@@ -100,7 +98,7 @@ ccache is auto-detected by CMake at configure time.
## Common problems
**Submodules not initialized:** If you see missing file errors for addon modules, run `pixi run initialize` or `git submodule update --init --recursive`.
**Submodules not initialized:** If you see missing file errors for ztools or Silo, run `pixi run initialize` or `git submodule update --init --recursive`.
**Pixi not found:** Install pixi from <https://pixi.sh>.

View File

@@ -33,7 +33,7 @@ See [Installation](./installation.md) for prebuilt package details and [Building
On first launch, Kindred Create:
1. Loads addon modules (SDK, Solver, Gears, Datums, Silo) via the Create bootstrap module
1. Loads **ztools** commands and the **Silo** workbench via the Create bootstrap module
2. Opens the **PartDesign** workbench as the default (with context-driven toolbars)
3. Prompts for Silo server configuration if not yet set up
4. Checks for application updates in the background (after ~10 seconds)

View File

@@ -32,14 +32,12 @@ The AppImage is a self-contained bundle using squashfs with zstd compression. No
Launch Kindred Create and check the console output (View > Report View) for:
```
addon_loader: loading sdk (priority 0)
addon_loader: loading solver (priority 10)
addon_loader: loading gears (priority 40)
addon_loader: loading datums (priority 45)
addon_loader: loading silo (priority 60)
Create: Loaded ztools Init.py
Create: Loaded silo Init.py
Create module initialized
```
This confirms the bootstrap module loaded all addons. If Silo is not configured, you will see a settings prompt on first launch.
This confirms the bootstrap module loaded both workbenches. If Silo is not configured, you will see a settings prompt on first launch.
## Uninstalling

Some files were not shown because too many files have changed in this diff Show More