Compare commits

..

1 Commits

Author SHA1 Message Date
forbes
d5c2887f5a fix(assembly): update solver submodule — enforce drag quaternion continuity (#338)
Points solver to fix/drag-quat-continuity (solver#40) which fixes
the planar constraint drift during interactive drag by enforcing
quaternion continuity on dragged parts and detecting branch jumps
beyond simple hemisphere negation.
2026-02-27 09:37:42 -06:00
239 changed files with 8622 additions and 13944 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

@@ -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}`);

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@
!/.gitmodules
!/.pre-commit-config.yaml
!/.pylintrc
!/.github/
!/.gitea/
!/contrib/.vscode/
!*.gitattributes

15
.gitmodules vendored
View File

@@ -10,6 +10,10 @@
[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
branch = main
[submodule "mods/silo"]
path = mods/silo
url = https://git.kindred-systems.com/kindred/silo-mod.git
@@ -18,11 +22,6 @@
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
[submodule "mods/quicknav"]
path = mods/quicknav
url = https://git.kindred-systems.com/kindred/quicknav.git

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

@@ -3,76 +3,77 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
files: |
(?x)^(
src/Base|
src/Gui|
src/Main|
src/Tools|
src/Mod/Assembly|
src/Mod/BIM|
src/Mod/CAM|
src/Mod/Cloud|
src/Mod/Draft|
src/Mod/Fem|
src/Mod/Help|
src/Mod/Import|
src/Mod/Inspection|
src/Mod/JtReader|
src/Mod/Measure|
src/Mod/MeshPart|
src/Mod/Mesh|
src/Mod/PartDesign|
src/Mod/Part|
src/Mod/Plot|
src/Mod/Points|
src/Mod/ReverseEngineering|
src/Mod/Robot|
src/Mod/Show|
src/Mod/Sketcher|
src/Mod/Spreadsheet|
src/Mod/Start|
src/Mod/Surface|
src/Mod/Test|
src/Mod/Tux|
src/Mod/Web|
tests/src
)
(?x)^(
.github|
src/Base|
src/Gui|
src/Main|
src/Tools|
src/Mod/Assembly|
src/Mod/BIM|
src/Mod/CAM|
src/Mod/Cloud|
src/Mod/Draft|
src/Mod/Fem|
src/Mod/Help|
src/Mod/Import|
src/Mod/Inspection|
src/Mod/JtReader|
src/Mod/Measure|
src/Mod/MeshPart|
src/Mod/Mesh|
src/Mod/PartDesign|
src/Mod/Part|
src/Mod/Plot|
src/Mod/Points|
src/Mod/ReverseEngineering|
src/Mod/Robot|
src/Mod/Show|
src/Mod/Sketcher|
src/Mod/Spreadsheet|
src/Mod/Start|
src/Mod/Surface|
src/Mod/Test|
src/Mod/Tux|
src/Mod/Web|
tests/src
)
exclude: |
(?x)^(
.*vcproj.*|
.*vcxproj.*|
src/App/ExpressionParser.tab.c|
src/App/ExpressionParser.tab.h|
src/App/ExpressionParser.y|
src/App/lex.ExpressionParser.c|
src/Gui/3Dconnexion/navlib|
src/Gui/QSint|
src/Gui/Quarter|
src/Mod/Fem/femexamples|
src/Mod/Import/App/SCL|
src/Mod/Import/App/SCL_output|
src/Mod/Mesh/App/TestData|
src/Mod/Mesh/App/WildMagic4|
src/Mod/Robot/App/kdl_cp|
src/Mod/Robot/Lib|
.*\.ts$|
.*\.brep$
)
(?x)^(
.*vcproj.*|
.*vcxproj.*|
src/App/ExpressionParser.tab.c|
src/App/ExpressionParser.tab.h|
src/App/ExpressionParser.y|
src/App/lex.ExpressionParser.c|
src/Gui/3Dconnexion/navlib|
src/Gui/QSint|
src/Gui/Quarter|
src/Mod/Fem/femexamples|
src/Mod/Import/App/SCL|
src/Mod/Import/App/SCL_output|
src/Mod/Mesh/App/TestData|
src/Mod/Mesh/App/WildMagic4|
src/Mod/Robot/App/kdl_cp|
src/Mod/Robot/Lib|
.*\.ts$|
.*\.brep$
)
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: mixed-line-ending
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 2892f1f81088477370d4fbc56545c05d33d2493f # frozen: 25.11.0
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: mixed-line-ending
- 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
rev: 317810f3c6a0ad3572367dc86cb6e41863e16e08 # frozen: v21.1.5
- id: black
args: ['--line-length', '100']
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: 317810f3c6a0ad3572367dc86cb6e41863e16e08 # frozen: v21.1.5
hooks:
- id: clang-format
- id: clang-format

132
CLAUDE.md
View File

@@ -2,7 +2,7 @@
## 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 is a fork of FreeCAD 1.0+ that adds integrated tooling for professional engineering workflows. It ships a context-aware UI system, two addon command sets (ztools and Silo), 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
@@ -33,14 +33,6 @@ create/
│ ├── 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)
@@ -52,30 +44,20 @@ create/
│ │ └── 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
│ │ ├── InitGui.py GUI bootstrap — loads addons, Silo setup, 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
│ │ ── kc_format.py .kc file format preservation
│ └── 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)
│ ├── ztools/ [submodule] Command provider (priority 50)
│ ├── 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
── solver/ [submodule] Assembly solver research (GNN-based)
│ └── quicknav/ [submodule] Navigation addon
├── docs/ mdBook documentation + architecture docs
├── tests/ C++ unit tests (GoogleTest)
├── package/ Packaging (debian/, rattler-build/)
@@ -128,19 +110,6 @@ if (obj->isDerivedFrom(type)) { ... }
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.
@@ -153,49 +122,42 @@ The `EditingContextResolver` singleton (`src/Gui/EditingContext.h/.cpp`) drives
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
Python API:
- `FreeCADGui.registerEditingContext()` — register a new context
- `FreeCADGui.registerEditingOverlay()` — conditional toolbar overlay
- `FreeCADGui.injectEditingCommands()` — add commands to existing contexts
- `FreeCADGui.currentEditingContext()` — query active context
- `FreeCADGui.refreshEditingContext()` — 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)`.
Addons in `mods/` are loaded by `src/Mod/Create/addon_loader.py`. Each addon provides a `package.xml` with `<kindred>` extensions declaring version bounds, load priority, and dependencies. The loader resolves via topological sort: **sdk** (0) -> **ztools** (50) -> **silo** (60).
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).
A `<workbench>` tag in `package.xml` is required for `InitGui.py` to be loaded, even if no actual workbench is registered.
### Deferred Initialization
GUI setup uses `QTimer.singleShot` with staggered delays. Create core (`src/Mod/Create/InitGui.py`) handles:
GUI setup uses `QTimer.singleShot` with staggered delays:
- 500ms: `.kc` file format registration
- 1500ms: Silo origin registration
- 2000ms: Auth dock + ztools commands
- 2500ms: Silo overlay
- 3000ms: Silo first-start check
- 4000ms: Activity panel
- 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/ztools` | `git.kindred-systems.com/forbes/ztools` | `main` | Extended PartDesign/Assembly/Spreadsheet tools |
| `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) |
| `mods/quicknav` | `git.kindred-systems.com/kindred/quicknav` | — | Navigation addon |
| `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 |
@@ -214,53 +176,29 @@ Initialize all submodules: `git submodule update --init --recursive`
## Key Addon Modules
### SDK (`mods/sdk/`)
### ztools (`mods/ztools/`)
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()`
Command provider (NOT a workbench). Injects tools into PartDesign, Assembly, and Spreadsheet contexts via `_ZToolsManipulator` (WorkbenchManipulator) and `injectEditingCommands()`.
Addons should use `kindred_sdk.*` instead of `FreeCADGui.*` internals where possible.
Commands: `ZTools_DatumCreator`, `ZTools_EnhancedPocket`, `ZTools_RotatedLinearPattern`, `ZTools_AssemblyLinearPattern`, `ZTools_AssemblyPolarPattern`, spreadsheet formatting (Bold, Italic, Underline, alignment, colors, QuickAlias).
### 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.
Source: `mods/ztools/ztools/ztools/commands/` (note the double `ztools` nesting).
### 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.
Silo origin detection: `silo_origin.py:ownsDocument()` checks for `SiloItemId`/`SiloPartNumber` properties on the active document. Origin registered via `kindred_sdk.register_origin()`.
### SDK (`mods/sdk/`)
### Solver (`mods/solver/`)
Assembly solver research addon (GNN-based). Pure Python, priority 10. Provides `KindredSolverBackend` for experimental assembly constraint solving.
Stable API contract for addons. Provides wrappers for editing contexts, theme tokens (Catppuccin Mocha YAML palette), FileOrigin registration, and deferred dock panels. Addons should use `kindred_sdk.*` instead of `FreeCADGui.*` internals where possible.
## 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
@@ -288,7 +226,7 @@ type(scope): lowercase imperative description
| `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.
Scopes: `solver`, `sketcher`, `editing-context`, `toolbar`, `ztools`, `silo`, `breadcrumb`, `gui`, `assembly`, `ci`, `theme`, `quicknav`, or omitted.
### PR Workflow
@@ -316,7 +254,7 @@ Scopes: `solver`, `sketcher`, `editing-context`, `toolbar`, `datums`, `silo`, `g
| `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/COMPONENTS.md` | Feature inventory (ztools, 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 |
@@ -340,8 +278,8 @@ 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
2. **No unit tests** for ztools/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
4. **PartDesign menu insertion fragility**`_ZToolsPartDesignManipulator.modifyMenuBar()` inserts after `PartDesign_Boolean`; upstream renames break silently
5. **`Silo_BOM` requires Silo-tracked document** — unregistered documents show a warning with no registration path
6. **QSS edits** — only edit `src/Gui/Stylesheets/KindredCreate.qss`; the PreferencePacks copy is auto-generated

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

@@ -19,27 +19,26 @@ FreeCAD startup
├─ addon_loader.load_addons(gui=True)
│ └─ 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
└─ silo (priority 60): registers SiloWorkbench
└─ schedules deferred Silo setup (overlay, panels, observer)
│ ├─ sdk (priority 0): logs "SDK loaded"
│ ├─ 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 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:** Silo has been migrated to use the SDK (#250) — it declares `<dependency>sdk</dependency>` and uses `kindred_sdk` for overlay registration and theme tokens. ztools has **not yet been migrated**: its `package.xml` lacks `<kindred>` metadata and its `InitGui.py` does not import from `kindred_sdk`. See KNOWN_ISSUES.md next step #9.
### 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,26 +1,90 @@
# 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.** `UtilsAssembly.findPlacement()` handles standard shapes (faces, edges, vertices) and `App::Line` origin objects. It does not extract placement from `PartDesign::Plane` or `PartDesign::Point` datum objects — when no element is selected, it returns a default `App.Placement()`. This means assembly joints referencing datum planes/points may produce incorrect placement.
## 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.**~~ Resolved. `delete_bom_entry()` uses `self._request("DELETE", ...)` which routes through `SiloClient._request()` with proper error handling.
11. ~~**Missing Silo icons.**~~ Resolved. All three icons now exist: `silo-tag.svg`, `silo-rollback.svg`, `silo-status.svg` in `mods/silo/freecad/resources/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 | Unknown | `formatDate()` reference is stale — function not found in codebase |
| 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. See `docs/BOM_MERGE.md` for specification.
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**~~ Done. CMake install rules in `src/Mod/Create/CMakeLists.txt` handle all `mods/` submodules.
5. **Test coverage** -- Unit tests for ztools datum creation, Silo FreeCAD commands, and Go API endpoints.
6. ~~**QSS consolidation**~~ Done. Canonical QSS is `src/Gui/Stylesheets/KindredCreate.qss`; PreferencePacks copy generated at build time via `configure_file()`.
7. **Update notification UI** -- Display in-app notification when a new release is available (issue #30). The update checker backend (`update_checker.py`) runs at startup; notification UI still needed.
8. **KC file format completion** -- Populate `silo_instance` and `revision_hash` in manifest.json. Implement write-back for history.json, approvals.json, dependencies.json. See `docs/KC_SPECIFICATION.md`.
9. **ztools SDK migration** -- Add `<kindred>` metadata to `mods/ztools/package.xml` (load priority, version bounds, SDK dependency). Migrate `InitGui.py` to use `kindred_sdk` APIs for context/overlay registration.
10. **DAG cross-item edges** -- Assembly constraints referencing geometry in child parts should populate `dag_cross_edges`. Deferred until assembly constraint model is finalized.

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,17 @@
- [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)
- [KCSolve: Pluggable Solver](./architecture/ondsel-solver.md)
# Development
@@ -27,10 +30,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,9 +45,7 @@
- [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)
@@ -75,5 +76,4 @@
- [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,132 @@
# KCSolve: Pluggable Solver Architecture
KCSolve is the pluggable assembly constraint solver framework for Kindred Create. It defines an abstract solver interface (`IKCSolver`) and a runtime registry (`SolverRegistry`) that lets the Assembly module work with any conforming solver backend. The default backend wraps OndselSolver via `OndselAdapter`.
- **Library:** `src/Mod/Assembly/Solver/` (builds `libKCSolve.so`)
- **Python module:** `src/Mod/Assembly/Solver/bindings/` (builds `kcsolve.so`)
- **Tests:** `tests/src/Mod/Assembly/Solver/` (C++), `src/Mod/Assembly/AssemblyTests/TestKCSolvePy.py` (Python)
## Architecture
```
┌──────────────────────────────────────────────────┐
│ Assembly Module (AssemblyObject.cpp) │
│ Builds SolveContext from FreeCAD document, │
│ calls solver via SolverRegistry │
├──────────────────────────────────────────────────┤
│ SolverRegistry (singleton) │
│ register_solver(), get(), available() │
│ Plugin discovery via scan() / scan_default_paths │
├──────────────┬───────────────────────────────────┤
│ OndselAdapter │ Python solvers │ Future plugins │
│ (C++ built-in)│ (via kcsolve) │ (.so plugins) │
└──────────────┴───────────────────────────────────┘
```
The Assembly module never references OndselSolver directly. All solver access goes through `SolverRegistry::instance().get()`, which returns a `std::unique_ptr<IKCSolver>`.
## IKCSolver interface
A solver backend implements `IKCSolver` (defined in `IKCSolver.h`). Only three methods are pure virtual; all others have sensible defaults:
| Method | Required | Purpose |
|--------|----------|---------|
| `name()` | yes | Human-readable solver name |
| `supported_joints()` | yes | List of `BaseJointKind` values the solver handles |
| `solve(ctx)` | yes | Solve for static equilibrium |
| `update(ctx)` | no | Incremental re-solve after parameter changes |
| `pre_drag(ctx, parts)` | no | Begin interactive drag session |
| `drag_step(placements)` | no | One mouse-move during drag |
| `post_drag()` | no | End drag session |
| `run_kinematic(ctx)` | no | Run kinematic simulation |
| `num_frames()` | no | Frame count after simulation |
| `update_for_frame(i)` | no | Retrieve frame placements |
| `diagnose(ctx)` | no | Detect redundant/conflicting constraints |
| `is_deterministic()` | no | Whether output is reproducible (default: true) |
| `export_native(path)` | no | Write solver-native debug file (e.g. ASMT) |
| `supports_bundle_fixed()` | no | Whether solver handles Fixed-joint bundling internally |
## Core types
All types live in `Types.h` with no FreeCAD dependencies, making the header standalone for future server/worker use.
**Transform** -- position `[x, y, z]` + unit quaternion `[w, x, y, z]`. Equivalent to `Base::Placement` but independent. Note the quaternion convention differs from `Base::Rotation` which uses `(x, y, z, w)` ordering; the adapter layer handles the swap.
**BaseJointKind** -- 24 primitive constraint types decomposed from FreeCAD's `JointType` and `DistanceType` enums. Covers point constraints (Coincident, PointOnLine, PointInPlane), axis/surface constraints (Concentric, Tangent, Planar), kinematic joints (Fixed, Revolute, Cylindrical, Slider, Ball, Screw, Universal), mechanical elements (Gear, RackPinion), distance variants, and a `Custom` extension point.
**SolveContext** -- complete solver input: parts (with placements, mass, grounded flag), constraints (with markers, parameters, limits), optional motion definitions and simulation parameters.
**SolveResult** -- solver output: status code, updated part placements, DOF count, constraint diagnostics, and simulation frame count.
## SolverRegistry
Thread-safe singleton managing solver backends:
```cpp
auto& reg = SolverRegistry::instance();
// Registration (at module init)
reg.register_solver("ondsel", []() {
return std::make_unique<OndselAdapter>();
});
// Retrieval
auto solver = reg.get(); // default solver
auto solver = reg.get("ondsel"); // by name
// Queries
reg.available(); // ["ondsel", ...]
reg.joints_for("ondsel"); // [Fixed, Revolute, ...]
reg.set_default("ondsel");
```
Plugin discovery scans directories for shared libraries exporting `kcsolve_api_version()` and `kcsolve_create()`. Default paths: `KCSOLVE_PLUGIN_PATH` env var and `<prefix>/lib/kcsolve/`.
## OndselAdapter
The built-in solver backend wrapping OndselSolver's Lagrangian constraint formulation. Registered as `"ondsel"` at Assembly module initialization.
Supports all 24 joint types. The adapter translates between `SolveContext`/`SolveResult` and OndselSolver's internal `ASMTAssembly` representation, including:
- Part placement conversion (Transform <-> Base::Placement quaternion ordering)
- Constraint parameter mapping (BaseJointKind -> OndselSolver joint classes)
- Interactive drag protocol (pre_drag/drag_step/post_drag)
- Kinematic simulation (run_kinematic/num_frames/update_for_frame)
- Constraint diagnostics (redundancy detection via MbD system)
## Python bindings (kcsolve module)
The `kcsolve` pybind11 module exposes the full C++ API to Python. See [KCSolve Python API](../reference/kcsolve-python.md) for details.
Key capabilities:
- All enums, structs, and classes accessible from Python
- Subclass `IKCSolver` in pure Python to create new solver backends
- Register Python solvers at runtime via `kcsolve.register_solver()`
- Query the registry from the FreeCAD console
## File layout
```
src/Mod/Assembly/Solver/
├── Types.h # Enums and structs (no FreeCAD deps)
├── IKCSolver.h # Abstract solver interface
├── SolverRegistry.h/cpp # Singleton registry + plugin loading
├── OndselAdapter.h/cpp # OndselSolver wrapper
├── KCSolveGlobal.h # DLL export macros
├── CMakeLists.txt # Builds libKCSolve.so
└── bindings/
├── PyIKCSolver.h # pybind11 trampoline for Python subclasses
├── kcsolve_py.cpp # Module definition (enums, structs, classes)
└── CMakeLists.txt # Builds kcsolve.so (pybind11 module)
```
## Testing
- **18 C++ tests** (`KCSolve_tests_run`) covering SolverRegistry (8 tests) and OndselAdapter (10 tests including drag protocol and redundancy diagnosis)
- **16 Python tests** (`TestKCSolvePy`) covering module import, type bindings, registry functions, Python solver subclassing, and full register/load/solve round-trips
- **6 Python integration tests** (`TestSolverIntegration`) testing solver behavior through FreeCAD document objects
## Related
- [KCSolve Python API Reference](../reference/kcsolve-python.md)
- [INTER_SOLVER.md](../../INTER_SOLVER.md) -- full architecture specification

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

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

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

View File

@@ -1,27 +1,23 @@
# Workbenches
Kindred Create ships several addon modules on top of FreeCAD's standard set.
Kindred Create ships two custom workbenches on top of FreeCAD's standard set.
## Gears
## ztools
A parametric gear generation workbench for creating involute spur gears, helical gears, and other gear profiles directly within FreeCAD assemblies.
A unified workbench that consolidates part design, assembly, and sketcher tools into a single interface. It is the **default workbench** when Kindred Create launches.
## Datums
ztools commands are also injected into the PartDesign workbench menus and toolbars via a manipulator mechanism, so they are accessible even when working in stock PartDesign.
A unified datum creator that replaces the three stock PartDesign datum commands (Plane, Line, Point) with a single `Create_DatumCreator` command. Provides 16 smart creation modes with auto-detection from geometry selection. Injected into PartDesign body and feature contexts via the SDK.
See the [ztools guide](./ztools.md) for details.
## Silo
A parts lifecycle management workbench for managing CAD files, part numbers, revisions, and bills of materials across teams. Silo commands (New, Open, Save, Commit, Pull, Push, Info, BOM) are integrated into the File menu and toolbar across **all** workbenches via the origin system.
A parts database workbench for managing CAD files, part numbers, revisions, and bills of materials across teams. Silo commands (New, Open, Save, Commit, Pull, Push, Info, BOM) are integrated into the File menu and toolbar across **all** workbenches via the origin system.
Silo requires a running server instance. On first launch, Kindred Create prompts for server configuration.
See the [Silo guide](./silo.md) for details.
## Solver
An experimental assembly solver research addon using GNN-based constraint solving.
## Stock FreeCAD workbenches
All standard FreeCAD workbenches are available: PartDesign, Sketcher, Assembly, TechDraw, Draft, BIM, CAM, FEM, Mesh, Spreadsheet, and others. Kindred Create does not remove or disable any stock functionality.

133
docs/src/guide/ztools.md Normal file
View File

@@ -0,0 +1,133 @@
# ztools
ztools is a pure-Python FreeCAD workbench that consolidates part design, assembly, and sketcher tools into a single unified interface. It is the **default workbench** when Kindred Create launches.
- **Submodule path:** `mods/ztools/`
- **Source:** `git.kindred-systems.com/forbes/ztools`
- **Stats:** 6,400+ lines of code, 24+ command classes, 33 custom icons, 17 toolbars
## Commands
### Datum Creator (`ZTools_DatumCreator`)
Creates datum geometry (planes, axes, points) with 16 creation modes. The task panel auto-detects the geometry type from your selection and offers appropriate modes. Supports custom naming, spreadsheet linking, and body- or document-level creation.
### Datum Manager (`ZTools_DatumManager`)
Manages existing datums. (Stub — planned for Phase 1, Q1 2026.)
### Enhanced Pocket (`ZTools_EnhancedPocket`)
Extends FreeCAD's Pocket feature with **Flip Side to Cut** — a SOLIDWORKS-style feature that removes material *outside* the sketch profile rather than inside. Uses a Boolean Common operation internally. Supports all standard pocket types: Dimension, Through All, To First, Up To Face, Two Dimensions. Taper angle is supported for standard pockets (disabled for flipped).
### Rotated Linear Pattern (`ZTools_RotatedLinearPattern`)
Creates a linear pattern with incremental rotation per instance. Configure direction, spacing, number of occurrences, and cumulative or per-instance rotation. Source components are automatically hidden.
### Assembly Linear Pattern (`ZTools_AssemblyLinearPattern`)
Creates linear patterns of assembly components. Supports multi-component selection, direction vectors, total length or spacing modes, and creation as Links (recommended) or copies. Auto-detects the parent assembly.
### Assembly Polar Pattern (`ZTools_AssemblyPolarPattern`)
Creates polar (circular) patterns of assembly components. Supports custom or preset axes (X/Y/Z), full circle or custom angle, center point definition, and creation as Links or copies.
### Spreadsheet Formatting (9 commands)
| Command | Action |
|---------|--------|
| `ZTools_SpreadsheetStyleBold` | Toggle bold |
| `ZTools_SpreadsheetStyleItalic` | Toggle italic |
| `ZTools_SpreadsheetStyleUnderline` | Toggle underline |
| `ZTools_SpreadsheetAlignLeft` | Left align |
| `ZTools_SpreadsheetAlignCenter` | Center align |
| `ZTools_SpreadsheetAlignRight` | Right align |
| `ZTools_SpreadsheetBgColor` | Background color picker |
| `ZTools_SpreadsheetTextColor` | Text color picker |
| `ZTools_SpreadsheetQuickAlias` | Auto-create aliases from row/column labels |
## Datum creation modes
### Planes (7 modes)
| Mode | Description | Input |
|------|-------------|-------|
| Offset from Face | Offsets a planar face along its normal | Face + distance (mm) |
| Offset from Plane | Offsets an existing datum plane | Datum plane + distance (mm) |
| Midplane | Plane halfway between two parallel faces | Two parallel faces |
| 3 Points | Plane through three non-collinear points | Three vertices |
| Normal to Edge | Plane perpendicular to an edge at a parameter location | Edge + parameter (0.01.0) |
| Angled | Rotates a plane about an edge by a specified angle | Face + edge + angle (degrees) |
| Tangent to Cylinder | Plane tangent to a cylindrical face at an angular position | Cylindrical face + angle (degrees) |
### Axes (4 modes)
| Mode | Description | Input |
|------|-------------|-------|
| 2 Points | Axis through two points | Two vertices |
| From Edge | Axis along a linear edge | Linear edge |
| Cylinder Center | Axis along the centerline of a cylinder | Cylindrical face |
| Plane Intersection | Axis at the intersection of two planes | Two non-parallel planes |
### Points (5 modes)
| Mode | Description | Input |
|------|-------------|-------|
| At Vertex | Point at a vertex location | Vertex |
| XYZ Coordinates | Point at explicit coordinates | x, y, z (mm) |
| On Edge | Point at a location along an edge | Edge + parameter (0.01.0) |
| Face Center | Point at the center of mass of a face | Face |
| Circle Center | Point at the center of a circular or arc edge | Circular edge |
## PartDesign injection
ztools registers a `_ZToolsPartDesignManipulator` that hooks into the PartDesign workbench at startup. This injects the following commands into PartDesign's toolbars and menus:
| PartDesign toolbar | Injected command |
|--------------------|-----------------|
| Part Design Helper Features | `ZTools_DatumCreator`, `ZTools_DatumManager` |
| Part Design Modeling Features | `ZTools_EnhancedPocket` |
| Part Design Transformation Features | `ZTools_RotatedLinearPattern` |
The manipulator is registered in `InitGui.py` when the Create bootstrap module loads addon workbenches.
## Directory structure
```
mods/ztools/
├── ztools/ztools/
│ ├── InitGui.py # Workbench registration + manipulator
│ ├── Init.py # Console initialization
│ ├── commands/
│ │ ├── datum_commands.py # DatumCreator + DatumManager
│ │ ├── datum_viewprovider.py # ViewProvider + edit panel
│ │ ├── pocket_commands.py # EnhancedPocket + FlippedPocket
│ │ ├── pattern_commands.py # RotatedLinearPattern
│ │ ├── assembly_pattern_commands.py # Linear + Polar assembly patterns
│ │ └── spreadsheet_commands.py # 9 formatting commands
│ ├── datums/
│ │ └── core.py # 16 datum creation functions
│ └── resources/ # Icons and theme
└── CatppuccinMocha/ # Theme preference pack
```
## Internal properties
ztools stores metadata on feature objects using these properties (preserved for backward compatibility):
| Property | Purpose |
|----------|---------|
| `ZTools_Type` | Feature type identifier |
| `ZTools_Params` | JSON creation parameters |
| `ZTools_SourceRefs` | JSON source geometry references |
## Known gaps
- Datum Manager is a stub — full implementation planned for Q1 2026
- Datum parameter changes don't recalculate from source geometry yet
- Enhanced Pocket taper angle is disabled for flipped pockets
## Further reading
- `mods/ztools/KINDRED_INTEGRATION.md` — integration architecture and migration options
- `mods/ztools/ROADMAP.md` — phased development plan (Q1Q4 2026)

View File

@@ -1,36 +1,30 @@
# Kindred Create
Kindred Create is a fork of [FreeCAD](https://www.freecad.org) 1.0+ that adds integrated tooling for professional engineering workflows. It ships custom workbenches, a C++ SDK extension layer, and a dark theme on top of FreeCAD's parametric modeling core.
Kindred Create is a fork of [FreeCAD](https://www.freecad.org) 1.0+ that adds integrated tooling for professional engineering workflows. It ships custom workbenches and a dark theme on top of FreeCAD's parametric modeling core.
- **License:** LGPL 2.1+
- **Organization:** [Kindred Systems LLC](https://www.kindred-systems.com)
- **Build system:** CMake + [pixi](https://pixi.sh)
- **Current version:** Kindred Create v0.1.5 (FreeCAD base v1.2.0)
- **Current version:** Kindred Create v0.1.0 (FreeCAD base v1.0.0)
## Key features
**[Silo](./guide/silo.md)** -- A parts lifecycle management system for managing CAD files, part numbers, revisions, and bills of materials across teams. Includes a Go REST API server backed by PostgreSQL and MinIO, with FreeCAD commands for opening, saving, and syncing files directly from the application.
**[ztools](./guide/ztools.md)** A unified workbench that consolidates part design, assembly, and sketcher tools into a single interface. Adds custom datum creation (planes, axes, points with 16 creation modes), pattern tools for assemblies, an enhanced pocket with flip-side cutting, and spreadsheet formatting commands.
**Gears** -- A parametric gear generation workbench for creating involute spur gears, helical gears, and other gear profiles directly within FreeCAD assemblies.
**[Silo](./guide/silo.md)** — A parts database system for managing CAD files, part numbers, revisions, and bills of materials across teams. Includes a Go REST API server backed by PostgreSQL and MinIO, with FreeCAD commands for opening, saving, and syncing files directly from the application.
**Datums** -- A unified datum creator that replaces the three stock PartDesign datum commands (Plane, Line, Point) with a single command providing 16 smart creation modes with auto-detection from geometry selection.
**Catppuccin Mocha theme** A dark theme applied across the entire application, including the 3D viewport, sketch editor, spreadsheet view, and tree view. Uses spanning-line branch indicators instead of disclosure arrows, with tuned preference defaults for document handling, selection behavior, and notifications.
**KCSDK** -- A C++ shared library (`libKCSDK`) with pybind11 bindings providing stable interfaces for dock panels, toolbars, menus, themes, and editing contexts. Python addons access it through the `kindred_sdk` wrapper package.
**Origin system** A pluggable file backend abstraction. The origin selector in the File toolbar lets you switch between local filesystem operations and Silo database operations. Silo commands (Commit, Pull, Push, Info, BOM) are available across all workbenches when Silo is the active origin.
**Catppuccin Mocha theme** -- A dark theme applied across the entire application, including the 3D viewport, sketch editor, spreadsheet view, and tree view. Uses spanning-line branch indicators instead of disclosure arrows, with tuned preference defaults for document handling, selection behavior, and notifications.
**Origin system** -- A pluggable file backend abstraction. The origin selector in the File toolbar lets you switch between local filesystem operations and Silo database operations. Silo commands (Commit, Pull, Push, Info, BOM) are available across all workbenches when Silo is the active origin.
**Update checker** -- On startup, Kindred Create checks the Gitea releases API for newer versions and logs the result. Configurable check interval and skip-version preferences.
**Update checker** — On startup, Kindred Create checks the Gitea releases API for newer versions and logs the result. Configurable check interval and skip-version preferences.
## How it relates to FreeCAD
Kindred Create is a fork/distribution of FreeCAD 1.0+. The design minimizes core modifications -- custom functionality is delivered through submodule addons (Silo, Gears, Datums, Solver) that follow FreeCAD's standard workbench pattern. If the addon submodules are missing, Kindred Create still functions as a themed FreeCAD.
Kindred Create is a fork/distribution of FreeCAD 1.0+. The design minimizes core modifications custom functionality is delivered through submodule addons (ztools, Silo) that follow FreeCAD's standard workbench pattern. If the addon submodules are missing, Kindred Create still functions as a themed FreeCAD.
The primary additions to FreeCAD's core are:
- The **origin system** (`FileOrigin` interface in `src/Gui/`) for pluggable file backends
- The **editing context system** (`EditingContextResolver` in `src/Gui/`) for context-driven UI
- The **KCSDK** C++ library (`src/Gui/SDK/`) for addon integration
- The **Create bootstrap module** (`src/Mod/Create/`) that loads addons at startup
- The **Catppuccin Mocha theme** (`KindredCreate.qss`) and preference pack
- Patches to **Assembly** (`findPlacement()` datum/origin handling)

441
docs/src/quicknav/SPEC.md Normal file
View File

@@ -0,0 +1,441 @@
# QuickNav — Keyboard Navigation Addon Specification
**Addon name:** QuickNav
**Type:** Pure Python FreeCAD addon (no C++ required)
**Compatibility:** FreeCAD 1.0+, Kindred Create 0.1+
**Location:** `mods/quicknav/`
---
## 1. Overview
QuickNav provides keyboard-driven command access for FreeCAD and Kindred Create. It replaces mouse-heavy toolbar navigation with a numbered key system organized by workbench and command grouping. The addon is activated by loading its workbench and toggled on/off with the `0` key.
### Design Goals
- Numbers `1-9` execute commands within the active command grouping
- `Shift+1-9` switches command grouping within the active workbench
- `Ctrl+1-9` switches workbench context
- All groupings and workbenches are ordered by most-recently-used (MRU) history
- History is unlimited internally, top 9 shown, remainder scrollable/clickable
- Mouse interaction remains fully functional — QuickNav is purely additive
- Configuration persisted via `FreeCAD.ParamGet()`
---
## 2. Terminology
| Term | Definition |
|------|-----------|
| **Workbench** | A FreeCAD workbench (Sketcher, PartDesign, Assembly, etc.). Fixed assignment to Ctrl+N slots. |
| **Command Grouping** | A logical group of commands within a workbench, mapped from existing FreeCAD toolbar groupings. Max 9 per tier. |
| **Active Grouping** | The left-most visible grouping in the navigation bar. Its commands are accessible via `1-9`. |
| **Navigation Bar** | Bottom toolbar displaying the current state: active workbench, groupings, and numbered commands. |
| **MRU Stack** | Most-recently-used ordering. Position 0 = currently active, 1 = previously active, etc. |
| **Tier** | When a workbench has >9 command groupings, they are split: Tier 1 (most common 9), Tier 2 (next 9). |
---
## 3. Key Bindings
### 3.1 Mode Toggle
| Key | Action |
|-----|--------|
| `0` | Toggle QuickNav on/off. When off, all QuickNav key interception is disabled and the navigation bar hides. |
### 3.2 Command Execution
| Key | Action |
|-----|--------|
| `1-9` | Execute the Nth command in the active grouping. If the command is auto-executable (e.g., Pad after closed sketch), execute immediately. Otherwise, enter tool mode (same as clicking the toolbar button). |
### 3.3 Grouping Navigation
| Key | Action |
|-----|--------|
| `Shift+1-9` | Switch to the Nth command grouping (MRU ordered) within the current workbench. The newly activated grouping moves to position 0 in the MRU stack. |
| `Shift+Left/Right` | Scroll through groupings beyond the visible 9. |
### 3.4 Workbench Navigation
| Key | Action |
|-----|--------|
| `Ctrl+1` | Sketcher |
| `Ctrl+2` | Part Design |
| `Ctrl+3` | Assembly |
| `Ctrl+4` | Spreadsheet |
| `Ctrl+5` | TechDraw |
| `Ctrl+6-9` | User-configurable / additional workbenches |
Switching workbench via `Ctrl+N` also restores that workbench's last-active command grouping.
---
## 4. Navigation Bar
The navigation bar is a `QToolBar` positioned at the bottom of the main window (replacing or sitting alongside FreeCAD's default bottom toolbar area).
### 4.1 Layout
```
┌─────────────────────────────────────────────────────────────────────┐
│ [WB: Sketcher] │ ❶ Primitives │ ② Constraints │ ③ Dimensions │ ◀▶ │
│ │ 1:Line 2:Rect 3:Circle 4:Arc 5:Point 6:Slot ... │
└─────────────────────────────────────────────────────────────────────┘
```
- **Left section:** Current workbench name with Ctrl+N hint
- **Middle section (top row):** Command groupings, MRU ordered. Active grouping is ❶ (filled circle), others are ②③ etc. Scrollable horizontally if >9.
- **Middle section (bottom row):** Commands within the active grouping, numbered 1-9
- **Right section:** Scroll arrows for overflow groupings
### 4.2 Visual States
- **Active grouping:** Bold text, filled number badge, Catppuccin Mocha `blue` (#89b4fa) accent
- **Inactive groupings:** Normal text, outlined number badge, `surface1` (#45475a) text
- **Hovered command:** `surface2` (#585b70) background highlight
- **Active command (tool in use):** `green` (#a6e3a1) underline indicator
### 4.3 Mouse Interaction
- Click any grouping to activate it (equivalent to Shift+N)
- Click any command to execute it (equivalent to pressing N)
- Scroll wheel on grouping area to cycle through overflow groupings
- Click scroll arrows to page through overflow
---
## 5. Workbench Command Groupings
Each workbench's existing FreeCAD toolbars map to command groupings. Where a workbench has >9 toolbars, split into Tier 1 (default, most common) and Tier 2 (accessible via scrolling or `Shift+Left/Right`).
### 5.1 Sketcher (Ctrl+1)
| Grouping | Commands (1-9) |
|----------|---------------|
| Primitives | Line, Rectangle, Circle, Arc, Point, Slot, B-Spline, Polyline, Ellipse |
| Constraints | Coincident, Horizontal, Vertical, Parallel, Perpendicular, Tangent, Equal, Symmetric, Block |
| Dimensions | Distance, Horizontal Distance, Vertical Distance, Radius, Diameter, Angle, Lock, Constrain Refraction |
| Construction | Toggle Construction, External Geometry, Carbon Copy, Offset, Trim, Extend, Split |
| Tools | Mirror, Array (Linear), Array (Polar), Move, Rotate, Scale, Close Shape, Connect Edges |
### 5.2 Part Design (Ctrl+2)
| Grouping | Commands (1-9) |
|----------|---------------|
| Additive | Pad, Revolution, Additive Loft, Additive Pipe, Additive Helix, Additive Box, Additive Cylinder, Additive Sphere, Additive Cone |
| Subtractive | Pocket, Hole, Groove, Subtractive Loft, Subtractive Pipe, Subtractive Helix, Subtractive Box, Subtractive Cylinder, Subtractive Sphere |
| Datums | New Sketch, Datum Plane, Datum Line, Datum Point, Shape Binder, Sub-Shape Binder, ZTools Datum Creator, ZTools Datum Manager |
| Transformations | Mirrored, Linear Pattern, Polar Pattern, MultiTransform, ZTools Rotated Linear Pattern |
| Modeling | Fillet, Chamfer, Draft, Thickness, Boolean, ZTools Enhanced Pocket |
### 5.3 Assembly (Ctrl+3)
| Grouping | Commands (1-9) |
|----------|---------------|
| Components | Insert Component, Create Part, Create Assembly, Ground, BOM |
| Joints | Fixed, Revolute, Cylindrical, Slider, Ball, Planar, Distance, Angle, Parallel |
| Patterns | ZTools Linear Pattern, ZTools Polar Pattern |
### 5.4 Spreadsheet (Ctrl+4)
| Grouping | Commands (1-9) |
|----------|---------------|
| Editing | Merge Cells, Split Cell, Alias, Import CSV, Export CSV |
| Formatting | Bold, Italic, Underline, Align Left, Align Center, Align Right, BG Color, Text Color, Quick Alias |
### 5.5 TechDraw (Ctrl+5)
Groupings derived from TechDraw's existing toolbars at runtime.
> **Note:** The exact command lists above are initial defaults. The addon discovers available commands from each workbench's toolbar structure at activation time and falls back to these defaults only if discovery fails.
---
## 6. MRU History Behavior
### 6.1 Grouping History (per workbench)
Each workbench maintains its own grouping MRU stack.
- When a grouping is activated (via `Shift+N` or mouse click), it moves to position 0
- The previously active grouping moves to position 1, everything else shifts down
- Position 0 is always the active grouping (already selected, shown leftmost)
- `Shift+1` is a no-op (already active), `Shift+2` activates the previous grouping, etc.
### 6.2 Workbench History
- Workbenches have fixed Ctrl+N assignments (not MRU ordered)
- However, each workbench remembers its last-active grouping
- Switching to a workbench restores its last-active grouping as position 0
### 6.3 Persistence
Stored in `FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/QuickNav")`:
| Parameter | Type | Description |
|-----------|------|-------------|
| `Enabled` | Bool | Whether QuickNav is currently active |
| `GroupHistory/<Workbench>` | String | Semicolon-delimited list of grouping names in MRU order |
| `LastGrouping/<Workbench>` | String | Name of the last-active grouping per workbench |
| `CustomSlots/Ctrl6` through `Ctrl9` | String | Workbench names for user-configurable slots |
---
## 7. Auto-Execution Logic
When a command is invoked via number key, QuickNav checks if the command can be auto-executed:
### 7.1 Auto-Execute Conditions
A command auto-executes (runs and completes without entering a persistent mode) when:
1. **Pad/Pocket after closed sketch:** If the active body has a sketch that was just closed (sketch edit mode exited with a closed profile), pressing the Pad or Pocket command key creates the feature with default parameters. The task panel still opens for parameter adjustment.
2. **Boolean operations:** If exactly two bodies/shapes are selected, boolean commands execute with defaults.
3. **Constraint application:** If appropriate geometry is pre-selected in Sketcher, constraint commands apply immediately.
### 7.2 Mode-Entry (Default)
All other commands enter their standard FreeCAD tool mode — identical to clicking the toolbar button. The user interacts with the 3D view and/or task panel as normal.
---
## 8. Key Event Handling
### 8.1 Event Filter Architecture
```python
class QuickNavEventFilter(QObject):
"""Installed on FreeCAD's main window via installEventFilter().
Intercepts KeyPress events when QuickNav is active.
Passes through all events when QuickNav is inactive.
"""
def eventFilter(self, obj, event):
if event.type() != QEvent.KeyPress:
return False
if not self._active:
return False
# Don't intercept when a text input widget has focus
focused = QApplication.focusWidget()
if isinstance(focused, (QLineEdit, QTextEdit, QPlainTextEdit, QSpinBox, QDoubleSpinBox)):
return False
# Don't intercept when task panel input fields are focused
if self._is_task_panel_input(focused):
return False
key = event.key()
modifiers = event.modifiers()
if key == Qt.Key_0 and modifiers == Qt.NoModifier:
self.toggle_active()
return True
if key >= Qt.Key_1 and key <= Qt.Key_9:
n = key - Qt.Key_0
if modifiers == Qt.ControlModifier:
self.switch_workbench(n)
return True
elif modifiers == Qt.ShiftModifier:
self.switch_grouping(n)
return True
elif modifiers == Qt.NoModifier:
self.execute_command(n)
return True
return False # Pass through all other keys
```
### 8.2 Conflict Resolution
QuickNav's event filter takes priority when active. FreeCAD's existing keybindings for `Ctrl+1` through `Ctrl+9` (if any) are overridden while QuickNav is enabled. The original bindings are restored when QuickNav is toggled off or unloaded.
Existing `Shift+` and bare number key bindings in FreeCAD are similarly overridden only while QuickNav is active. This is safe because:
- FreeCAD does not use bare number keys as shortcuts by default
- Shift+number is not commonly bound in default FreeCAD
### 8.3 Input Widget Safety
The event filter must NOT intercept keys when the user is:
- Typing in the Python console
- Entering values in the task panel (dimensions, parameters)
- Editing spreadsheet cells
- Typing in any `QLineEdit`, `QTextEdit`, `QSpinBox`, or `QDoubleSpinBox`
- Using the Sketcher's inline dimension input
---
## 9. Addon Structure
```
mods/quicknav/
├── package.xml # FreeCAD addon manifest with <kindred> extension
├── Init.py # Non-GUI initialization (no-op)
├── InitGui.py # Registers QuickNavWorkbench
├── quicknav/
│ ├── __init__.py
│ ├── core.py # QuickNavManager singleton — orchestrates state
│ ├── event_filter.py # QuickNavEventFilter (QObject)
│ ├── nav_bar.py # NavigationBar (QToolBar subclass)
│ ├── workbench_map.py # Fixed workbench → Ctrl+N mapping + grouping discovery
│ ├── history.py # MRU stack with ParamGet persistence
│ ├── auto_exec.py # Auto-execution condition checks
│ ├── commands.py # FreeCAD command wrappers (QuickNav_Toggle, etc.)
│ └── resources/
│ ├── icons/ # Number badge SVGs, QuickNav icon
│ └── theme.py # Catppuccin Mocha color tokens
└── tests/
└── test_history.py # MRU stack unit tests
```
### 9.1 Manifest
```xml
<?xml version="1.0" encoding="UTF-8"?>
<package format="1">
<name>QuickNav</name>
<description>Keyboard-driven toolbar navigation</description>
<version>0.1.0</version>
<maintainer email="dev@kindred-systems.com">Kindred Systems</maintainer>
<license>LGPL-2.1</license>
<content>
<workbench>
<classname>QuickNavWorkbench</classname>
</workbench>
</content>
<kindred>
<min_create_version>0.1.0</min_create_version>
<load_priority>10</load_priority>
<pure_python>true</pure_python>
<dependencies>
<dependency>sdk</dependency>
</dependencies>
</kindred>
</package>
```
### 9.2 Activation
QuickNav activates when its workbench is loaded (via the addon loader or manual activation). It installs the event filter on the main window and creates the navigation bar. The workbench itself is invisible — it does not add its own toolbars or menus beyond the navigation bar. It acts as a transparent overlay on whatever workbench the user is actually working in.
```python
class QuickNavWorkbench(Gui.Workbench):
"""Invisible workbench that installs QuickNav on load.
QuickNav doesn't replace the active workbench — it layers on top.
Loading QuickNav installs the event filter and nav bar, then
immediately re-activates the previously active workbench.
"""
def Initialize(self):
QuickNavManager.instance().install()
def Activated(self):
# Re-activate the previous workbench so QuickNav is transparent
prev = QuickNavManager.instance().previous_workbench
if prev:
Gui.activateWorkbench(prev)
def Deactivated(self):
pass
def GetClassName(self):
return "Gui::PythonWorkbench"
```
**Alternative (preferred for Create):** Instead of a workbench, QuickNav can be activated directly from `Create/InitGui.py` at boot, gated by the `Enabled` preference. This avoids the workbench-switching dance entirely. The `QuickNavWorkbench` registration is kept for standalone FreeCAD compatibility.
---
## 10. Command Discovery
At activation time, QuickNav introspects each workbench's toolbars to build the command grouping map.
```python
def discover_groupings(workbench_name: str) -> list[CommandGrouping]:
"""Discover command groupings from a workbench's toolbar structure.
1. Temporarily activate the workbench (if not already active)
2. Enumerate QToolBars from the main window
3. Map toolbar name → list of QAction names
4. Filter out non-command actions (separators, widgets)
5. Split into tiers if >9 groupings
6. Restore the previously active workbench
"""
```
### 10.1 Fallback Defaults
If toolbar discovery fails (workbench not initialized, empty toolbars), QuickNav falls back to the hardcoded groupings in Section 5. These are stored as a Python dict in `workbench_map.py`.
### 10.2 ZTools Integration
ZTools commands injected via `WorkbenchManipulator` appear in the discovered toolbars and are automatically included in the relevant groupings. No special handling is needed — QuickNav discovers commands after all manipulators have run.
---
## 11. FreeCAD Compatibility
QuickNav is designed as a standalone FreeCAD addon that works without Kindred Create or the SDK.
| Feature | FreeCAD | Kindred Create |
|---------|---------|----------------|
| Core navigation (keys, nav bar) | ✅ | ✅ |
| Catppuccin Mocha theming | ❌ (uses Qt defaults) | ✅ (via SDK theme tokens) |
| Auto-boot on startup | ❌ (manual workbench activation) | ✅ (via addon loader) |
| ZTools commands in groupings | ❌ (not present) | ✅ (discovered from manipulated toolbars) |
The SDK dependency is optional — QuickNav checks for `kindred_sdk` availability and degrades gracefully:
```python
try:
from kindred_sdk.theme import get_theme_tokens
THEME = get_theme_tokens()
except ImportError:
THEME = None # Use Qt default palette
```
---
## 12. Implementation Phases
### Phase 1: Core Infrastructure
- Event filter with key interception and input widget safety
- QuickNavManager singleton with toggle on/off
- Navigation bar widget (QToolBar) with basic layout
- Hardcoded workbench/grouping maps from Section 5
- ParamGet persistence for enabled state
### Phase 2: Dynamic Discovery
- Toolbar introspection for command grouping discovery
- MRU history with persistence
- Grouping overflow scrolling
- Workbench restore (last-active grouping per workbench)
### Phase 3: Auto-Execution
- Context-aware auto-execute logic
- Sketcher closed-profile detection for Pad/Pocket
- Pre-selection constraint application
### Phase 4: Polish
- Number badge SVG icons
- Catppuccin Mocha theming (conditional on SDK)
- Scroll animations
- Settings dialog (custom Ctrl+6-9 assignments)
- FreeCAD standalone packaging
---
## 13. Open Questions
1. **Tier switching UX:** When a workbench has >9 groupings split into tiers, should `Shift+0` toggle between tiers, or should tiers be purely a scroll/mouse concept?
2. **Visual number badges:** Should the commands in the nav bar show keycap-style badges (like `⌨ 1`) or just prepend the number (`1: Line`)?
3. **Sketcher inline dimension input:** FreeCAD's Sketcher has an inline dimension entry that isn't a standard QLineEdit. Need to verify the event filter correctly identifies and skips this widget.
4. **Ctrl+N conflicts with Create shortcuts:** Verify that Create/Silo don't already bind Ctrl+1 through Ctrl+9. The Silo toggle uses Ctrl+O/S/N, so these should be clear.

View File

@@ -5,42 +5,41 @@ The Create module (`src/Mod/Create/`) is the integration layer that bootstraps K
**Source files:**
- `src/Mod/Create/Init.py` -- console-phase addon loading
- `src/Mod/Create/InitGui.py` -- GUI-phase addon loading and deferred timers
- `src/Mod/Create/addon_loader.py` -- manifest-driven loader with dependency resolution
- `src/Mod/Create/InitGui.py` -- GUI-phase workbench loading and deferred timers
- `src/Mod/Create/update_checker.py` -- Gitea releases API polling
- `src/Mod/Create/kc_format.py` -- .kc file format round-trip preservation
## Loading Mechanism
FreeCAD loads module `Init.py` and `InitGui.py` files using **direct `exec()`**, not Python's import system. The C++ startup code scans `Mod/` directories, reads each file as text, compiles it, and executes it in an isolated namespace.
FreeCAD loads module `Init.py` and `InitGui.py` files using **direct `exec()`**, not Python's import system. The C++ startup code scans `Mod/` directories, reads each file as text, compiles it, and executes it in an isolated namespace:
The addon loader (`addon_loader.py`) then discovers addons in `mods/` via their `package.xml` manifests, resolves dependencies via topological sort, and loads them in priority order.
```python
source = init_py.read_text(encoding="utf-8")
code = compile(source, init_py, "exec")
exec(code)
```
Modules loaded this way are **not** added to `sys.modules`. Each execution gets a fresh globals dict with no caching or dependency resolution.
## Phase 1: Console (`Init.py`)
Runs immediately at application startup, before any GUI is available.
`addon_loader.load_addons(gui=False)` discovers and loads addons from `mods/`:
`setup_kindred_addons()` discovers and loads two built-in addons from `mods/`:
| Addon | Priority | Purpose |
|-------|----------|---------|
| sdk | 0 | Stable API contract |
| solver | 10 | Assembly solver research |
| gears | 40 | Parametric gear generation |
| datums | 45 | Unified datum creator |
| silo | 60 | PLM workbench |
| Addon | Path | Purpose |
|-------|------|---------|
| ztools | `mods/ztools/ztools/` | Part design, assembly, and sketcher tools |
| silo | `mods/silo/freecad/` | Database integration and version control |
For each addon:
1. Discover `package.xml` manifest (depth 1 or 2)
2. Parse `<kindred>` extensions (version bounds, priority, dependencies)
3. Validate version compatibility
4. Resolve load order via topological sort on dependencies
5. Add addon dir to `sys.path`
6. Execute `Init.py`
7. Register in `FreeCAD.KindredAddons`
1. Build the path from `FreeCAD.getHomePath() + "mods/" + addon_path`
2. Add to `sys.path` if not already present
3. Find `Init.py` in the addon directory
4. Execute it with `exec(compile(source, init_file, "exec"), exec_globals)`
Failures are logged to `FreeCAD.Console` and do not prevent other addons from loading.
Failures are logged to `FreeCAD.Console.PrintWarning()` and do not prevent other addons from loading.
## Phase 2: GUI (`InitGui.py`)
@@ -48,36 +47,35 @@ Runs after the console phase, when `FreeCADGui` is available.
### Synchronous
`addon_loader.load_addons(gui=True)` loads `InitGui.py` from each addon in the same resolved order, registering workbenches and GUI commands.
`setup_kindred_workbenches()` loads `InitGui.py` from the same two addons (ztools, silo), registering their workbenches and GUI commands.
### Deferred Timers (Create core)
### Deferred Timers
Two `QTimer.singleShot` calls in `src/Mod/Create/InitGui.py`:
Six `QTimer.singleShot` calls stagger initialization to let the GUI event loop settle:
| Delay | Function | Purpose |
|-------|----------|---------|
| 500 ms | `_register_kc_format()` | Register .kc file format observer |
| 1500 ms | `_register_silo_origin()` | Register Silo as a file origin |
| 2000 ms | `_setup_silo_auth_panel()` | Dock the Database Auth panel |
| 3000 ms | `_check_silo_first_start()` | Show settings dialog on first run |
| 4000 ms | `_setup_silo_activity_panel()` | Dock the Database Activity panel |
| 10000 ms | `_check_for_updates()` | Poll Gitea for new releases |
### Deferred Timers (Silo addon)
The Silo addon manages its own deferred setup in `mods/silo/InitGui.py`:
| Delay | Function | Purpose |
|-------|----------|---------|
| 500 ms | `_handle_startup_urls()` | Process kindred:// URLs |
| 600 ms | `_register_silo_document_observer()` | .kc tree building observer |
| 2000 ms | `_setup_silo_auth_panel()` | "Database Auth" dock panel |
| 2500 ms | Silo overlay registration | "Silo Origin" toolbar overlay |
| 3000 ms | `_check_silo_first_start()` | Settings prompt on first run |
| 4000 ms | `_setup_silo_activity_panel()` | "Database Activity" dock panel |
Every timer callback is wrapped in `try/except`. Failures log to `FreeCAD.Console` and do not crash the application.
Every timer callback is wrapped in `try/except`. Failures log to `FreeCAD.Console.PrintLog()` and do not crash the application.
### Timer Details
**_register_kc_format (500 ms)** -- Imports `kc_format` and calls `kc_format.register()`, which installs a `DocumentObserver` with `slotStartSaveDocument` / `slotFinishSaveDocument` hooks. These cache `silo/` ZIP entries before FreeCAD's C++ save rewrites the archive, then re-inject them afterwards. Only processes `.kc` files.
**_register_silo_origin (1500 ms)** -- Imports `silo_origin` and calls `silo_origin.register_silo_origin()`, making Silo available in the origin selector dropdown alongside LocalFileOrigin.
**_setup_silo_auth_panel (2000 ms)** -- Creates a `QDockWidget` titled "Database Auth" containing a `SiloAuthDockWidget` from `silo_commands`. Docked on the right side of the main window. Guards against duplicates by checking if the widget already exists.
**_check_silo_first_start (3000 ms)** -- Reads `User parameter:BaseApp/Preferences/Mod/KindredSilo`. On the very first launch (when `FirstStartChecked` is not set), runs the `Silo_Settings` command if no API URL is configured.
**_setup_silo_activity_panel (4000 ms)** -- Creates a "Database Activity" dock widget showing the 20 most recent Silo items (part number, description, date). Falls back to "(Unable to connect to Silo database)" on connection failure.
**_check_for_updates (10000 ms)** -- Calls `update_checker._run_update_check()` in the background.
## Update Checker
@@ -114,32 +112,21 @@ The checker never shows a dialog -- it only logs to `FreeCAD.Console`.
FreeCAD startup
|
Init.py (exec'd, immediate)
+-- addon_loader.load_addons(gui=False)
| +-- sdk/Init.py
| +-- solver/Init.py
| +-- gears/Init.py
| +-- datums/Init.py
| +-- silo/Init.py
+-- setup_kindred_addons()
| +-- ztools/Init.py (exec'd)
| +-- silo/freecad/Init.py (exec'd)
|
[GUI startup]
|
InitGui.py (exec'd, immediate)
+-- addon_loader.load_addons(gui=True)
| +-- sdk/InitGui.py
| +-- solver/InitGui.py
| +-- gears/InitGui.py
| +-- datums/InitGui.py
| +-- silo/InitGui.py (schedules its own deferred timers)
+-- setup_kindred_workbenches()
| +-- ztools/InitGui.py (exec'd)
| +-- silo/freecad/InitGui.py (exec'd)
|
Create core timers:
+-- QTimer 500ms --> kc_format.register()
+-- QTimer 500ms --> kc_format.register()
+-- QTimer 1500ms --> silo_origin.register_silo_origin()
+-- QTimer 2000ms --> SiloAuthDockWidget
+-- QTimer 3000ms --> Silo first-start check
+-- QTimer 4000ms --> Database Activity panel
+-- QTimer 10000ms --> update_checker._run_update_check()
Silo addon timers (scheduled from silo/InitGui.py):
+-- QTimer 500ms --> kindred:// URL handler
+-- QTimer 600ms --> document observer
+-- QTimer 2000ms --> auth dock panel
+-- QTimer 2500ms --> Silo overlay
+-- QTimer 3000ms --> first-start check
+-- QTimer 4000ms --> activity dock panel
```

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