Move the Design-tab scripts into scripts/CAD/ and name them by role

Requested by SoftFever on PR #15238: ten of these had accumulated loose in
scripts/ next to ~20 unrelated upstream ones, with names that only meant
something to whoever wrote them. They now sit in scripts/CAD/, mirroring the
src/libslic3r/CAD/ and src/slic3r/GUI/CAD/ split, and the verb in the name is
the role: build- produces a binary, start- brings something up, run- runs a
suite, check- asserts one thing against a live app.

  kernel-test.sh        -> CAD/run-kernel-tests.sh
  ladder-all.sh         -> CAD/run-all-checks.sh
  sketch-ladder.py      -> CAD/check-sketch-engine.py
  ladder-corpus.py      -> CAD/check-sketch-engine-corpus.py
  gui-ladder.py         -> CAD/check-gui-sketching.py
  offer-ladder.py       -> CAD/check-gui-context-menu.py
  mcp-sketch-smoke.py   -> CAD/check-mcp-sketch.py
  rig-build.sh          -> CAD/build-gui.sh
  docker-iter-build.sh  -> CAD/build-gui-incremental.sh
  gui-session.sh        -> CAD/start-headless-gui.sh

"Ladder" was the worst of them: it named the shape of the test (rungs of
increasing difficulty) rather than what the test proves, so nothing in the
directory listing told you which one needed a GPU and which was pure kernel.

Every reference rewritten -- the docs, the cross-calls between the scripts,
Dockerfile.deps, and the container-side /OrcaSlicer/scripts paths. The three
shell scripts resolve REPO relative to themselves and now sit one level
deeper, so that walk went from /.. to /../.. . The copies these push into a
container's /tmp were renamed to match, or the container would have kept the
old names alive.

Two runtime paths deliberately NOT renamed. /tmp/orca-rig-build.lock is a
cross-fork contract -- both forks take the same lock so two concurrent builds
serialise instead of OOMing the box, and renaming it on one side silently
removes that guard. /tmp/gui-session.log is a runtime artefact, not a script.

Added scripts/CAD/README.md: what each script proves, what it needs, and the
two constraints that have each cost a session (never build inside the GUI
container; a window manager is required or synthetic keys are ignored).

On CI, which was the other half of the request: the kernel suite is already
there and always has been. The cases are registered in
tests/libslic3r/CMakeLists.txt under if (SLIC3R_CAD), which defaults ON and no
workflow turns off, so they build into libslic3r_tests and run under ctest on
every platform via unit_tests.yml -- like any other unit test, needing no new
job. They have simply never been seen to run, because the workflows on this PR
are still awaiting maintainer approval. run-kernel-tests.sh is the local loop
over the same cases, and it is the only script here CI could run: the other
six need an OpenGL canvas and synthetic input.

Verified: scripts/CAD/run-kernel-tests.sh from its new location, all tests
passed, 2562 assertions in 190 test cases.
This commit is contained in:
Tommaso Bianchi
2026-08-28 19:34:03 +02:00
parent cdd41e230d
commit 13d5eac891
16 changed files with 118 additions and 65 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ is new code. The negotiable surface is the 37 modified files.
205 `TEST_CASE` blocks across 6 new test source files. This counts assertions written, not 205 `TEST_CASE` blocks across 6 new test source files. This counts assertions written, not
assertions passed — a run needs a build. assertions passed — a run needs a build.
`scripts/kernel-test.sh` is the headless verification contract: it builds only `scripts/CAD/run-kernel-tests.sh` is the headless verification contract: it builds only
`libslic3r_tests` (not the GUI app), needs no display, and exit 0 means the CAD suite `libslic3r_tests` (not the GUI app), needs no display, and exit 0 means the CAD suite
passed. It now runs with **no exclusions** — both cases that used to be quarantined (the passed. It now runs with **no exclusions** — both cases that used to be quarantined (the
circle-line tangency solver abort and the internal-thread reference) are fixed. circle-line tangency solver abort and the internal-thread reference) are fixed.
+14 -14
View File
@@ -3,16 +3,16 @@
The build rig is two long-lived containers, `snaporca-gui` and `orcacad-gui`, one per fork. Each The build rig is two long-lived containers, `snaporca-gui` and `orcacad-gui`, one per fork. Each
mounts only its fork's build volume (`snaporca_buildcache` / `orcacad_buildcache`) at mounts only its fork's build volume (`snaporca_buildcache` / `orcacad_buildcache`) at
`/OrcaSlicer/build`, its fork's `resources/`, and a shots directory — nothing else. They run the `/OrcaSlicer/build`, its fork's `resources/`, and a shots directory — nothing else. They run the
binary; they do not build it. Rebuild with `scripts/rig-build.sh`. binary; they do not build it. Rebuild with `scripts/CAD/build-gui.sh`.
| fork repo | project() | deps image | build volume | GUI container | binary | | fork repo | project() | deps image | build volume | GUI container | binary |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `snaporca` | `Snapmaker_Orca` | `snaporca-deps` | `snaporca_buildcache` | `snaporca-gui` | `snapmaker-orca` | | `snaporca` | `Snapmaker_Orca` | `snaporca-deps` | `snaporca_buildcache` | `snaporca-gui` | `snapmaker-orca` |
| `orca_cad` | `OrcaSlicer` | `orcacad-deps` | `orcacad_buildcache` | `orcacad-gui` | `orca-slicer` | | `orca_cad` | `OrcaSlicer` | `orcacad-deps` | `orcacad_buildcache` | `orcacad-gui` | `orca-slicer` |
`scripts/rig-build.sh` exists alongside `scripts/docker-iter-build.sh` for one reason: it does a `scripts/CAD/build-gui.sh` exists alongside `scripts/CAD/build-gui-incremental.sh` for one reason: it does a
target-only `ninja` into the volume the GUI rig launches from, so a session can test a single target-only `ninja` into the volume the GUI rig launches from, so a session can test a single
change without a full repackage, whereas `docker-iter-build.sh` runs the full packaged build. change without a full repackage, whereas `build-gui-incremental.sh` runs the full packaged build.
Both start a throwaway container from the deps image with the live repo mounted over the baked Both start a throwaway container from the deps image with the live repo mounted over the baked
tree — never build inside the GUI container (Trap 1). tree — never build inside the GUI container (Trap 1).
@@ -32,7 +32,7 @@ reports an unknown target, and `orca-slicer` / `OrcaSlicer` have been replaced b
`snaporca-deps`, so even on the mainline fork the baked tree is the other fork's. A `cmake .` `snaporca-deps`, so even on the mainline fork the baked tree is the other fork's. A `cmake .`
there reconfigures the shared build dir under the wrong project name. there reconfigures the shared build dir under the wrong project name.
**Fix.** Build only via `scripts/rig-build.sh`, which starts a throwaway container from the deps **Fix.** Build only via `scripts/CAD/build-gui.sh`, which starts a throwaway container from the deps
image with the live repo mounted over the baked tree — `src`, `resources`, `cmake`, `deps_src`, image with the live repo mounted over the baked tree — `src`, `resources`, `cmake`, `deps_src`,
`localization`, `CMakeLists.txt`, `version.inc` — and writes into the same volume the rig `localization`, `CMakeLists.txt`, `version.inc` — and writes into the same volume the rig
launches from. launches from.
@@ -66,7 +66,7 @@ fork's root `CMakeLists.txt`, near line 948).
**Cause.** The deps image predates that requirement. Only the mainline (`orca_cad`) fork has **Cause.** The deps image predates that requirement. Only the mainline (`orca_cad`) fork has
`deps_src/pybind11` and the requirement; snaporca has neither. `deps_src/pybind11` and the requirement; snaporca has neither.
**Fix.** Mount `deps_src` over the baked tree — `scripts/rig-build.sh` does. Corollary: mounting a **Fix.** Mount `deps_src` over the baked tree — `scripts/CAD/build-gui.sh` does. Corollary: mounting a
snaporca tree into an `orcacad-deps` build reproduces this error exactly. snaporca tree into an `orcacad-deps` build reproduces this error exactly.
--- ---
@@ -83,7 +83,7 @@ its own configure, while the consumer in the root `CMakeLists.txt` (`if (NOT OCC
after the `TKFillet TKOffset` prepend (`src/libslic3r/CMakeLists.txt:599`) therefore links the after the `TKFillet TKOffset` prepend (`src/libslic3r/CMakeLists.txt:599`) therefore links the
previous list and drops `TKBool`/`TKOffset`. previous list and drops `TKBool`/`TKOffset`.
**Fix.** Configure twice. `scripts/rig-build.sh` runs `cmake .` twice for exactly this reason; if **Fix.** Configure twice. `scripts/CAD/build-gui.sh` runs `cmake .` twice for exactly this reason; if
you ever configure by hand, run it twice. you ever configure by hand, run it twice.
--- ---
@@ -100,7 +100,7 @@ snaporca, 319/320 in orca_cad). Every `#ifdef SLIC3R_CAD` block therefore compil
option still reads ON. option still reads ON.
**Fix.** Always mount the live `CMakeLists.txt` and `cmake/` — never inherit them from the image. **Fix.** Always mount the live `CMakeLists.txt` and `cmake/` — never inherit them from the image.
This is why `scripts/docker-iter-build.sh`, `scripts/kernel-test.sh` and `scripts/rig-build.sh` This is why `scripts/CAD/build-gui-incremental.sh`, `scripts/CAD/run-kernel-tests.sh` and `scripts/CAD/build-gui.sh`
all mount both. all mount both.
--- ---
@@ -109,18 +109,18 @@ all mount both.
`ninja <target>` writes `/OrcaSlicer/build/src/Release/<binary>`; only `build_linux.sh` `ninja <target>` writes `/OrcaSlicer/build/src/Release/<binary>`; only `build_linux.sh`
additionally packages to `/OrcaSlicer/build/package/bin/<binary>`. `orca_cad`'s additionally packages to `/OrcaSlicer/build/package/bin/<binary>`. `orca_cad`'s
`scripts/gui-session.sh` defaults `BIN` to `src/Release/orca-slicer`, but snaporca's defaults to `scripts/CAD/start-headless-gui.sh` defaults `BIN` to `src/Release/orca-slicer`, but snaporca's defaults to
`package/bin/snapmaker-orca`. So after a target-only rebuild on snaporca, launching `package/bin/snapmaker-orca`. So after a target-only rebuild on snaporca, launching
`gui-session.sh` with its default runs the **stale packaged** binary — the change under test is `start-headless-gui.sh` with its default runs the **stale packaged** binary — the change under test is
invisible and the session hunts a phantom. Pass `BIN` explicitly: invisible and the session hunts a phantom. Pass `BIN` explicitly:
docker exec -e BIN=/OrcaSlicer/build/src/Release/snapmaker-orca snaporca-gui /OrcaSlicer/scripts/gui-session.sh docker exec -e BIN=/OrcaSlicer/build/src/Release/snapmaker-orca snaporca-gui /OrcaSlicer/scripts/CAD/start-headless-gui.sh
`scripts/rig-build.sh` prints the correct line for the current fork when it finishes. `scripts/CAD/build-gui.sh` prints the correct line for the current fork when it finishes.
Note also that the GUI containers do **not** mount `scripts/`: `/OrcaSlicer/scripts` inside them Note also that the GUI containers do **not** mount `scripts/`: `/OrcaSlicer/scripts` inside them
is the baked copy, so a local edit to `gui-session.sh` has no effect until you is the baked copy, so a local edit to `start-headless-gui.sh` has no effect until you
`docker cp scripts/gui-session.sh <container>:/OrcaSlicer/scripts/`. `docker cp scripts/CAD/start-headless-gui.sh <container>:/OrcaSlicer/scripts/`.
--- ---
@@ -144,7 +144,7 @@ a real fault in your code gets past the version banner. Compare
## Trap 7 — a single-instance app plus a path-matched `pkill` ## Trap 7 — a single-instance app plus a path-matched `pkill`
`gui-session.sh` used to kill by `"$BIN"`, while its own `app_pid()` matched by BASENAME. Launch `start-headless-gui.sh` used to kill by `"$BIN"`, while its own `app_pid()` matched by BASENAME. Launch
with a `BIN` that differs from the running instance's path and the old process survives, keeps with a `BIN` that differs from the running instance's path and the old process survives, keeps
the single-instance lock, and the new one exits seconds after loading fonts — then `status` the single-instance lock, and the new one exits seconds after loading fonts — then `status`
reports the *stale* pid as a healthy session. Fixed by killing on the basename; `status` now also reports the *stale* pid as a healthy session. Fixed by killing on the basename; `status` now also
+1 -1
View File
@@ -144,7 +144,7 @@ def main():
text = "\n".join(lines) text = "\n".join(lines)
# --check: prove the checked-in header IS what this generator produces, and change nothing. # --check: prove the checked-in header IS what this generator produces, and change nothing.
# The header calls itself GENERATED and was hand-edited anyway; a claim like that is only # The header calls itself GENERATED and was hand-edited anyway; a claim like that is only
# worth having if something enforces it, so ladder-all.sh runs this on every gate. # worth having if something enforces it, so run-all-checks.sh runs this on every gate.
if "--check" in sys.argv: if "--check" in sys.argv:
have = open(OUT, encoding="utf-8").read() if os.path.exists(OUT) else "" have = open(OUT, encoding="utf-8").read() if os.path.exists(OUT) else ""
if have == text: if have == text:
+53
View File
@@ -0,0 +1,53 @@
# Design-tab scripts
Everything here supports the parametric Design tab (`src/libslic3r/CAD/`,
`src/slic3r/GUI/CAD/`). Nothing here is needed to build or run OrcaSlicer — these
are the development and verification tools for that one feature.
The verb in the name is the role:
| | |
|---|---|
| `build-…` | produce a binary |
| `start-…` | bring something up and leave it running |
| `run-…` | run a suite and report pass/fail |
| `check-…` | one specific assertion, usually driving a live app |
## Verification
| Script | What it proves | Needs |
|---|---|---|
| `run-kernel-tests.sh` | The CAD kernel builds and the Catch2 `[CadDocument]` tags pass — every case builds a document, recomputes it and asserts on real geometry. **Exit 0 is the verification contract.** | Docker only. No display. |
| `run-all-checks.sh` | Every check below, in one command. The gate before pushing a Design-tab change. | Docker + the GUI container |
| `check-sketch-engine.py` | A ladder of 2D sketches of increasing complexity, judged on loop count, closure and void attribution rather than on area. | Kernel only |
| `check-sketch-engine-corpus.py` | The same ladder graded against a systematic sample of real drawings instead of shapes we chose. | Kernel + corpus |
| `check-gui-sketching.py` | The same profiles drawn the way a person draws them — synthetic mouse gestures and typed values. | Headless GUI |
| `check-gui-context-menu.py` | That right-click is the pivot of the design gesture, and adapts to what was clicked. | Headless GUI |
| `check-mcp-sketch.py` | The sketch layer driven over the MCP socket, asserting what decides whether a profile is buildable. | Headless GUI + `SNAPORCA_MCP` |
**`run-kernel-tests.sh` is the only one CI can run.** The rest need a live
application with an OpenGL canvas and synthetic input, which hosted runners do not
have. The kernel suite itself is already in CI by an ordinary route: the cases are
registered in `tests/libslic3r/CMakeLists.txt` under `if (SLIC3R_CAD)`, so they are
part of `libslic3r_tests` and run under `ctest` on every platform like any other
unit test. This script exists for the local loop, where it is a two-minute round
trip instead of a full application build.
## Build and run
| Script | Purpose |
|---|---|
| `build-gui.sh` | Build the GUI binary in a throwaway container, writing into the build-cache volume the long-lived GUI container reads. |
| `build-gui-incremental.sh` | Incremental build against the deps-baked image, for a fast edit/compile loop. |
| `start-headless-gui.sh` | Bring the app up on a headless X display (Xvfb + a window manager), ready to drive or attach to over VNC. |
Two constraints that are not obvious and have each cost a session:
- **Never build inside the GUI container.** Its baked source tree silently
reconfigures the shared build directory and this fork's targets vanish.
- **A window manager is required.** Without one, windows are never focused, and an
unfocused GTK app ignores synthetic keys — which looks exactly like a code bug.
`docs/rig_build_traps.md` documents these and three more, with symptoms and exact
recovery commands. Read it before debugging a configure or link failure one of
these scripts reports.
@@ -9,8 +9,8 @@
# Result: edit -> rebuild in seconds-to-minutes instead of a full Docker rebuild. # Result: edit -> rebuild in seconds-to-minutes instead of a full Docker rebuild.
# #
# Usage (run on the build host, e.g. behemoth, from anywhere): # Usage (run on the build host, e.g. behemoth, from anywhere):
# scripts/docker-iter-build.sh # scripts/CAD/build-gui-incremental.sh
# IMAGE=orcacad-deps scripts/docker-iter-build.sh # IMAGE=orcacad-deps scripts/CAD/build-gui-incremental.sh
# #
# On success the binary is inside the persistent volume at # On success the binary is inside the persistent volume at
# /OrcaSlicer/build/package/bin/orca-slicer (copy it out with a follow-up # /OrcaSlicer/build/package/bin/orca-slicer (copy it out with a follow-up
@@ -18,8 +18,8 @@
# Rig build traps already paid for once each (stale project, NLopt cache, pybind11, OCCT_LIBS, SLIC3R_CAD gate): docs/rig_build_traps.md # Rig build traps already paid for once each (stale project, NLopt cache, pybind11, OCCT_LIBS, SLIC3R_CAD gate): docs/rig_build_traps.md
set -euo pipefail set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# orcacad-deps, NOT snaporca-deps: see the note in kernel-test.sh — the wrong image # orcacad-deps, NOT snaporca-deps: see the note in run-kernel-tests.sh — the wrong image
# fails at CMake configure, not at link time. # fails at CMake configure, not at link time.
IMAGE="${IMAGE:-orcacad-deps}" IMAGE="${IMAGE:-orcacad-deps}"
BUILD_VOL="${BUILD_VOL:-orcacad_buildcache}" BUILD_VOL="${BUILD_VOL:-orcacad_buildcache}"
@@ -42,10 +42,10 @@ echo "REPO=$REPO IMAGE=$IMAGE BUILD_VOL=$BUILD_VOL"
# ---- OOM guard (2026-08-21) ------------------------------------------------------------- # ---- OOM guard (2026-08-21) -------------------------------------------------------------
# Two of these builds ran at once on 2026-08-21, each with ninja -j$(nproc)=16: ~36 cc1plus # Two of these builds ran at once on 2026-08-21, each with ninja -j$(nproc)=16: ~36 cc1plus
# holding 42 GB of a 62 GB box -> global OOM at 21:05, a 2h28m kill storm, ssh unreachable, # holding 42 GB of a 62 GB box -> global OOM at 21:05, a 2h28m kill storm, ssh unreachable,
# lightdm destroyed. Neither build produced a single object. scripts/rig-build.sh grew the # lightdm destroyed. Neither build produced a single object. scripts/CAD/build-gui.sh grew the
# bounds first; every script that starts a compile needs the same three, or the guard is only # bounds first; every script that starts a compile needs the same three, or the guard is only
# as strong as the script you happened not to use. # as strong as the script you happened not to use.
# flock — the lock path is SHARED with rig-build.sh and the other fork on purpose, so # flock — the lock path is SHARED with build-gui.sh and the other fork on purpose, so
# concurrent builds serialise instead of summing. # concurrent builds serialise instead of summing.
# -j — bounded parallelism; ~1.17 GB per cc1plus was the measured average. # -j — bounded parallelism; ~1.17 GB per cc1plus was the measured average.
# --memory — the actual guarantee: a runaway build dies in its own cgroup instead of taking # --memory — the actual guarantee: a runaway build dies in its own cgroup instead of taking
@@ -9,11 +9,11 @@
# Read that file before debugging a configure or link failure this script reports. # Read that file before debugging a configure or link failure this script reports.
# #
# Usage: # Usage:
# scripts/rig-build.sh # configure + build the fork's GUI target # scripts/CAD/build-gui.sh # configure + build the fork's GUI target
# DRY_RUN=1 scripts/rig-build.sh # print the resolved fork identity and exit, no container # DRY_RUN=1 scripts/CAD/build-gui.sh # print the resolved fork identity and exit, no container
set -euo pipefail set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# Fork identity is DERIVED from the repo, never hardcoded, so this file is byte-identical in # Fork identity is DERIVED from the repo, never hardcoded, so this file is byte-identical in
# both forks and cannot be mirrored into the wrong one. Pointing a fork at the other fork's # both forks and cannot be mirrored into the wrong one. Pointing a fork at the other fork's
@@ -53,7 +53,7 @@ LOCK=/tmp/orca-rig-build.lock
exec 9>"$LOCK" exec 9>"$LOCK"
if ! flock -n 9; then if ! flock -n 9; then
echo "another fork's rig-build holds $LOCK — waiting (this is the OOM guard, not a hang)" echo "another fork's build-gui holds $LOCK — waiting (this is the OOM guard, not a hang)"
flock 9 flock 9
fi fi
@@ -90,9 +90,9 @@ docker run --rm \
exit \$rc exit \$rc
" || rc=$? " || rc=$?
# A target-only build writes src/Release/, but this fork's gui-session.sh may default BIN to the # A target-only build writes src/Release/, but this fork's start-headless-gui.sh may default BIN to the
# PACKAGED path that only build_linux.sh refreshes — launching with the default would then run a # PACKAGED path that only build_linux.sh refreshes — launching with the default would then run a
# stale binary. Pass BIN explicitly. See docs/rig_build_traps.md. # stale binary. Pass BIN explicitly. See docs/rig_build_traps.md.
echo "=== launch the rig on the binary just built ===" echo "=== launch the rig on the binary just built ==="
echo " docker exec -e BIN=/OrcaSlicer/build/src/Release/$BIN ${PREFIX}-gui /OrcaSlicer/scripts/gui-session.sh" echo " docker exec -e BIN=/OrcaSlicer/build/src/Release/$BIN ${PREFIX}-gui /OrcaSlicer/scripts/CAD/start-headless-gui.sh"
exit "$rc" exit "$rc"
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""The OFFER ladder: prove that right-click is the pivot, and that it adapts to what was clicked. """The OFFER ladder: prove that right-click is the pivot, and that it adapts to what was clicked.
The gesture ladder (scripts/gui-ladder.py) proved the TARGET — a complex closed profile, precise The gesture ladder (scripts/CAD/check-gui-sketching.py) proved the TARGET — a complex closed profile, precise
in vertices, lengths, arcs and symmetry, with its voids correctly attributed. It proved it by in vertices, lengths, arcs and symmetry, with its voids correctly attributed. It proved it by
arming every tool with a letter key. That leaves the goal's own MECHANISM untested: the design arming every tool with a letter key. That leaves the goal's own MECHANISM untested: the design
logic pivots on right-click, and the verbs offered are supposed to adapt to the element under the logic pivots on right-click, and the verbs offered are supposed to adapt to the element under the
@@ -20,7 +20,7 @@ This ladder drives the menu. Nothing here is asserted from pixels:
Run inside the rig container, with the app launched under SNAPORCA_KEYTRACE=1: Run inside the rig container, with the app launched under SNAPORCA_KEYTRACE=1:
docker exec snaporca-gui python3 /OrcaSlicer/scripts/offer-ladder.py [rung ...] docker exec snaporca-gui python3 /OrcaSlicer/scripts/CAD/check-gui-context-menu.py [rung ...]
""" """
import importlib.util import importlib.util
import math import math
@@ -34,7 +34,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
# The gesture ladder owns the hand and the eye: the homography per sketch, the window lookup by # The gesture ladder owns the hand and the eye: the homography per sketch, the window lookup by
# class, the synthetic click, the typed value, the socket. Importing it is the only way those # class, the synthetic click, the typed value, the socket. Importing it is the only way those
# stay one implementation — a second copy would drift the first time a rig detail moved. # stay one implementation — a second copy would drift the first time a rig detail moved.
_spec = importlib.util.spec_from_file_location("gui_ladder", os.path.join(HERE, "gui-ladder.py")) _spec = importlib.util.spec_from_file_location("gui_ladder", os.path.join(HERE, "check-gui-sketching.py"))
G = importlib.util.module_from_spec(_spec) G = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(G) _spec.loader.exec_module(G)
@@ -617,7 +617,7 @@ def clicked(X, Y):
def fresh_sketch(tool): def fresh_sketch(tool):
"""Enter a sketch from a KNOWN empty state, whatever the previous rung or run left behind. """Enter a sketch from a KNOWN empty state, whatever the previous rung or run left behind.
gui-ladder's enter_sketch dismisses the old session with keys, and a key is exactly what an check-gui-sketching's enter_sketch dismisses the old session with keys, and a key is exactly what an
open value field swallows — so a session that should have been cancelled survives, the four open value field swallows — so a session that should have been cancelled survives, the four
calibration probes land in it on top of whatever was already there, and the run dies with calibration probes land in it on top of whatever was already there, and the run dies with
"calibration expected 4 points, got 7". Cancelling through the socket cannot be swallowed: "calibration expected 4 points, got 7". Cancelling through the socket cannot be swallowed:
@@ -1196,7 +1196,7 @@ RUNGS = {"kinds": rung_kinds, "vocabulary": rung_vocabulary,
def main(): def main():
if not os.path.exists(LOG): if not os.path.exists(LOG):
G.die(f"no {LOG} — launch the app through scripts/gui-session.sh") G.die(f"no {LOG} — launch the app through scripts/CAD/start-headless-gui.sh")
if "[OFFER]" not in open(LOG, errors="replace").read()[-400000:]: if "[OFFER]" not in open(LOG, errors="replace").read()[-400000:]:
print(f"note: no [OFFER] lines in {LOG} yet — the app must run with SNAPORCA_KEYTRACE=1") print(f"note: no [OFFER] lines in {LOG} yet — the app must run with SNAPORCA_KEYTRACE=1")
want = sys.argv[1:] or list(RUNGS) want = sys.argv[1:] or list(RUNGS)
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""A ladder of sketches drawn the way a person draws them: mouse gestures and typed values. """A ladder of sketches drawn the way a person draws them: mouse gestures and typed values.
WHY THIS EXISTS, next to scripts/sketch-ladder.py. That ladder proves the ENGINE — it feeds WHY THIS EXISTS, next to scripts/CAD/check-sketch-engine.py. That ladder proves the ENGINE — it feeds
geometry through the MCP socket's add_entities_scripted and grades what comes back. The socket geometry through the MCP socket's add_entities_scripted and grades what comes back. The socket
path skips everything the goal actually rests on: gesture state, the auto-edit queue, snapping, path skips everything the goal actually rests on: gesture state, the auto-edit queue, snapping,
inference at gesture tolerance, and the right-click offer. A ladder that only drives the socket inference at gesture tolerance, and the right-click offer. A ladder that only drives the socket
@@ -11,8 +11,8 @@ here ONLY as an instrument, never as an author.
Runs INSIDE the headless rig container (Xvfb :10 + openbox + the app with SNAPORCA_MCP set): Runs INSIDE the headless rig container (Xvfb :10 + openbox + the app with SNAPORCA_MCP set):
docker cp scripts/gui-ladder.py snaporca-gui:/tmp/ && \ docker cp scripts/CAD/check-gui-sketching.py snaporca-gui:/tmp/ && \
docker exec snaporca-gui python3 /tmp/gui-ladder.py [rung ...] docker exec snaporca-gui python3 /tmp/check-gui-sketching.py [rung ...]
With no arguments every rung runs. Exit 0 = every property held. With no arguments every rung runs. Exit 0 = every property held.
""" """
@@ -9,7 +9,7 @@ loop: build a known profile, ask the app what it thinks it has, compare against
RUN IT AGAINST A RUNNING APP: RUN IT AGAINST A RUNNING APP:
SNAPORCA_MCP=/tmp/mcp.sock <binary> # launch with the socket enabled SNAPORCA_MCP=/tmp/mcp.sock <binary> # launch with the socket enabled
python3 scripts/mcp-sketch-smoke.py [socket] # default /tmp/mcp.sock python3 scripts/CAD/check-mcp-sketch.py [socket] # default /tmp/mcp.sock
Exit 0 = every assertion held. Anything else prints the first mismatch and stops. Exit 0 = every assertion held. Anything else prints the first mismatch and stops.
""" """
@@ -20,7 +20,7 @@ agree, and that the engine's own operations preserve what they promise.
MIRROR a real closed profile, mirrored, is still exactly one closed loop MIRROR a real closed profile, mirrored, is still exactly one closed loop
OFFSET a real closed profile, offset, is still closed OFFSET a real closed profile, offset, is still closed
Usage: ladder-corpus.py [--sample N] [--corpus DIR] Usage: check-sketch-engine-corpus.py [--sample N] [--corpus DIR]
""" """
import argparse import argparse
@@ -16,7 +16,7 @@ Every rung asserts those. Area appears only as a cross-check, never as the verdi
Entirely 2D: sketch entities only, no extrude, revolve or any solid feature. Entirely 2D: sketch entities only, no extrude, revolve or any solid feature.
SNAPORCA_MCP=/tmp/mcp.sock <binary> SNAPORCA_MCP=/tmp/mcp.sock <binary>
python3 scripts/sketch-ladder.py [socket] python3 scripts/CAD/check-sketch-engine.py [socket]
Exit 0 = every rung held. Otherwise the first broken property is named and the run stops. Exit 0 = every rung held. Otherwise the first broken property is named and the run stops.
""" """
@@ -6,12 +6,12 @@
# run this, read the last line, and do not push a red one. The kernel suite is the only part CI # run this, read the last line, and do not push a red one. The kernel suite is the only part CI
# can carry, and it already does. # can carry, and it already does.
# #
# scripts/ladder-all.sh # kernel + engine + corpus (every 20th) + gestures + offer # scripts/CAD/run-all-checks.sh # kernel + engine + corpus (every 20th) + gestures + offer
# FULL=1 scripts/ladder-all.sh # corpus over ALL 997 sheets (~25 min) # FULL=1 scripts/CAD/run-all-checks.sh # corpus over ALL 997 sheets (~25 min)
# SKIP_GUI=1 scripts/ladder-all.sh # kernel only, for a machine with no rig # SKIP_GUI=1 scripts/CAD/run-all-checks.sh # kernel only, for a machine with no rig
# #
# The rig container is expected to be up with the app running and SNAPORCA_MCP set; bring it up # The rig container is expected to be up with the app running and SNAPORCA_MCP set; bring it up
# with scripts/gui-session.sh inside it. The corpus lives at /corpus in that container. # with scripts/CAD/start-headless-gui.sh inside it. The corpus lives at /corpus in that container.
set -uo pipefail set -uo pipefail
cd "$(dirname "$0")/.." || exit 1 cd "$(dirname "$0")/.." || exit 1
@@ -43,17 +43,17 @@ run_in_rig() { # copy the script in fresh, then run it ther
# 92-row array, so the last verb was unreachable (snaporca-z8rs, snaporca-ziam). # 92-row array, so the last verb was unreachable (snaporca-z8rs, snaporca-ziam).
step "offer table matches the atlas" python3 docs/ux/mockups/gen_offer_table.py --check step "offer table matches the atlas" python3 docs/ux/mockups/gen_offer_table.py --check
step "kernel suite" scripts/kernel-test.sh --vol "${KVOL:-snaporca_kerneltest}" step "kernel suite" scripts/CAD/run-kernel-tests.sh --vol "${KVOL:-snaporca_kerneltest}"
if [ -z "${SKIP_GUI:-}" ]; then if [ -z "${SKIP_GUI:-}" ]; then
step "engine ladder (rungs 1-8, scripted geometry)" \ step "engine ladder (rungs 1-8, scripted geometry)" \
run_in_rig scripts/sketch-ladder.py /tmp/sketch-ladder.py run_in_rig scripts/CAD/check-sketch-engine.py /tmp/check-sketch-engine.py
step "corpus rung (real drawings, every ${STEP}th)" \ step "corpus rung (real drawings, every ${STEP}th)" \
run_in_rig scripts/ladder-corpus.py /tmp/ladder-corpus.py --corpus "$CORPUS" --step "$STEP" run_in_rig scripts/CAD/check-sketch-engine-corpus.py /tmp/check-sketch-engine-corpus.py --corpus "$CORPUS" --step "$STEP"
step "corpus scale rung (the heaviest sheets)" \ step "corpus scale rung (the heaviest sheets)" \
run_in_rig scripts/ladder-corpus.py /tmp/ladder-corpus.py --corpus "$CORPUS" --scale run_in_rig scripts/CAD/check-sketch-engine-corpus.py /tmp/check-sketch-engine-corpus.py --corpus "$CORPUS" --scale
step "gesture ladder (mouse and keyboard)" \ step "gesture ladder (mouse and keyboard)" \
run_in_rig scripts/gui-ladder.py /tmp/gui-ladder.py run_in_rig scripts/CAD/check-gui-sketching.py /tmp/check-gui-sketching.py
# The offer ladder needs TWO extra things the others do not: the app must have been launched # The offer ladder needs TWO extra things the others do not: the app must have been launched
# with SNAPORCA_KEYTRACE=1 (its [OFFER] lines are the whole instrument), and it reads the # with SNAPORCA_KEYTRACE=1 (its [OFFER] lines are the whole instrument), and it reads the
# generated offer table to predict what each selection should show — which is not in the # generated offer table to predict what each selection should show — which is not in the
@@ -61,7 +61,7 @@ if [ -z "${SKIP_GUI:-}" ]; then
# run_in_rig puts the script, is one of the paths the ladder looks in. # run_in_rig puts the script, is one of the paths the ladder looks in.
docker cp src/slic3r/GUI/CAD/DesignOffer.hpp "$C:/tmp/DesignOffer.hpp" >/dev/null docker cp src/slic3r/GUI/CAD/DesignOffer.hpp "$C:/tmp/DesignOffer.hpp" >/dev/null
step "offer ladder (right-click, the menu, the verbs behind it)" \ step "offer ladder (right-click, the menu, the verbs behind it)" \
run_in_rig scripts/offer-ladder.py /tmp/offer-ladder.py run_in_rig scripts/CAD/check-gui-context-menu.py /tmp/check-gui-context-menu.py
fi fi
echo echo
@@ -7,10 +7,10 @@
# CadDocument, recompute()s it and asserts on geometry. # CadDocument, recompute()s it and asserts on geometry.
# #
# Usage: # Usage:
# scripts/kernel-test.sh # [CadDocument] tags, default volume # scripts/CAD/run-kernel-tests.sh # [CadDocument] tags, default volume
# scripts/kernel-test.sh --tags '[CadDocument],[Sketch]' # scripts/CAD/run-kernel-tests.sh --tags '[CadDocument],[Sketch]'
# scripts/kernel-test.sh --vol wt_mirror # private build cache (parallel workers) # scripts/CAD/run-kernel-tests.sh --vol wt_mirror # private build cache (parallel workers)
# scripts/kernel-test.sh --host tommaso@100.103.234.2 # build on a remote host # scripts/CAD/run-kernel-tests.sh --host tommaso@100.103.234.2 # build on a remote host
# #
# Parallel workers MUST pass a distinct --vol: two builds sharing one cache corrupt # Parallel workers MUST pass a distinct --vol: two builds sharing one cache corrupt
# each other. A new volume pays one full build; runs after that are incremental. # each other. A new volume pays one full build; runs after that are incremental.
@@ -22,14 +22,14 @@
# Rig build traps already paid for once each (stale project, NLopt cache, pybind11, OCCT_LIBS, SLIC3R_CAD gate): docs/rig_build_traps.md # Rig build traps already paid for once each (stale project, NLopt cache, pybind11, OCCT_LIBS, SLIC3R_CAD gate): docs/rig_build_traps.md
set -euo pipefail set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# orcacad-deps, NOT snaporca-deps: this fork is mainline-based and needs Eigen 5.0.1, # orcacad-deps, NOT snaporca-deps: this fork is mainline-based and needs Eigen 5.0.1,
# CGAL 5.6.3, wx 3.3.2 and Python 3.12 Development.Embed, none of which snaporca-deps has. # CGAL 5.6.3, wx 3.3.2 and Python 3.12 Development.Embed, none of which snaporca-deps has.
# With the wrong image CMake dies at configure, which is exactly why this fork went # With the wrong image CMake dies at configure, which is exactly why this fork went
# M1-M8 without ever compiling (see commit 1633005bba). # M1-M8 without ever compiling (see commit 1633005bba).
IMAGE="${IMAGE:-orcacad-deps}" IMAGE="${IMAGE:-orcacad-deps}"
# Must NOT default to snaporca_buildcache: that is the other fork's volume, and pointing # Must NOT default to snaporca_buildcache: that is the other fork's volume, and pointing
# this fork at it makes the two silently trade build artefacts. docker-iter-build.sh had the # this fork at it makes the two silently trade build artefacts. build-gui-incremental.sh had the
# identical defect and was fixed to orcacad_buildcache; this script was missed. # identical defect and was fixed to orcacad_buildcache; this script was missed.
VOL="${BUILD_VOL:-orcacad_kerneltest}" VOL="${BUILD_VOL:-orcacad_kerneltest}"
# No exclusions. Both cases that used to be quarantined now run: the solver SIGABRT on # No exclusions. Both cases that used to be quarantined now run: the solver SIGABRT on
@@ -64,7 +64,7 @@ if [[ -n "$HOST" ]]; then
"$REPO/src" "$REPO/tests" "$REPO/resources" "$REPO/cmake" "$REPO/scripts" \ "$REPO/src" "$REPO/tests" "$REPO/resources" "$REPO/cmake" "$REPO/scripts" \
"$REPO/CMakeLists.txt" \ "$REPO/CMakeLists.txt" \
"$HOST:$REMOTE/" "$HOST:$REMOTE/"
exec ssh "$HOST" "cd $REMOTE && scripts/kernel-test.sh --vol '$VOL' --tags '$TAGS' --image '$IMAGE'" exec ssh "$HOST" "cd $REMOTE && scripts/CAD/run-kernel-tests.sh --vol '$VOL' --tags '$TAGS' --image '$IMAGE'"
fi fi
if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
@@ -84,17 +84,17 @@ if ! docker volume inspect "$VOL" >/dev/null 2>&1; then
docker volume create "$VOL" >/dev/null docker volume create "$VOL" >/dev/null
fi fi
# tests/ is mounted too -- unlike docker-iter-build.sh, this script exists precisely to # tests/ is mounted too -- unlike build-gui-incremental.sh, this script exists precisely to
# compile tests being edited. CMakeLists.txt and cmake/ carry the SLIC3R_CAD gate; taking # compile tests being edited. CMakeLists.txt and cmake/ carry the SLIC3R_CAD gate; taking
# them from the baked image instead leaves the gate off and the CAD symbols vanish. # them from the baked image instead leaves the gate off and the CAD symbols vanish.
# ---- OOM guard (2026-08-21) ------------------------------------------------------------- # ---- OOM guard (2026-08-21) -------------------------------------------------------------
# Two of these builds ran at once on 2026-08-21, each with ninja -j$(nproc)=16: ~36 cc1plus # Two of these builds ran at once on 2026-08-21, each with ninja -j$(nproc)=16: ~36 cc1plus
# holding 42 GB of a 62 GB box -> global OOM at 21:05, a 2h28m kill storm, ssh unreachable, # holding 42 GB of a 62 GB box -> global OOM at 21:05, a 2h28m kill storm, ssh unreachable,
# lightdm destroyed. Neither build produced a single object. scripts/rig-build.sh grew the # lightdm destroyed. Neither build produced a single object. scripts/CAD/build-gui.sh grew the
# bounds first; every script that starts a compile needs the same three, or the guard is only # bounds first; every script that starts a compile needs the same three, or the guard is only
# as strong as the script you happened not to use. # as strong as the script you happened not to use.
# flock — the lock path is SHARED with rig-build.sh and the other fork on purpose, so # flock — the lock path is SHARED with build-gui.sh and the other fork on purpose, so
# concurrent builds serialise instead of summing. # concurrent builds serialise instead of summing.
# -j — bounded parallelism; ~1.17 GB per cc1plus was the measured average. # -j — bounded parallelism; ~1.17 GB per cc1plus was the measured average.
# --memory — the actual guarantee: a runaway build dies in its own cgroup instead of taking # --memory — the actual guarantee: a runaway build dies in its own cgroup instead of taking
@@ -1,11 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Bring the headless GUI up on a VNC-served X display, ready to drive or to attach Remmina to. # Bring the headless GUI up on a VNC-served X display, ready to drive or to attach Remmina to.
# #
# Runs INSIDE the long-lived GUI container (see the header of scripts/docker-iter-build.sh for how # Runs INSIDE the long-lived GUI container (see the header of scripts/CAD/build-gui-incremental.sh for how
# that container is created). Idempotent: safe to re-run to recover a session whose app died. # that container is created). Idempotent: safe to re-run to recover a session whose app died.
# #
# docker exec <container> /OrcaSlicer/scripts/gui-session.sh # launch + settle # docker exec <container> /OrcaSlicer/scripts/CAD/start-headless-gui.sh # launch + settle
# docker exec <container> /OrcaSlicer/scripts/gui-session.sh --status # report, change nothing # docker exec <container> /OrcaSlicer/scripts/CAD/start-headless-gui.sh --status # report, change nothing
# #
# WHY THIS EXISTS. Dismissing the first-run dialogs by computing the titlebar close box from # WHY THIS EXISTS. Dismissing the first-run dialogs by computing the titlebar close box from
# `xdotool getwindowgeometry --shell` and clicking it went wrong whenever the dialog had already # `xdotool getwindowgeometry --shell` and clicking it went wrong whenever the dialog had already
+2 -2
View File
@@ -2,7 +2,7 @@
# Identical system+pinned-dependency setup to scripts/Dockerfile, but STOPS after # Identical system+pinned-dependency setup to scripts/Dockerfile, but STOPS after
# `build_linux.sh -dr` (no slicer/AppImage build). Produces an image with the pinned # `build_linux.sh -dr` (no slicer/AppImage build). Produces an image with the pinned
# deps baked at /OrcaSlicer/deps/build/destdir, so the slicer can be rebuilt # deps baked at /OrcaSlicer/deps/build/destdir, so the slicer can be rebuilt
# incrementally via scripts/docker-iter-build.sh without re-running the long deps build. # incrementally via scripts/CAD/build-gui-incremental.sh without re-running the long deps build.
# #
# Build once (rebuild only when deps/ changes, e.g. OCCT module flags): # Build once (rebuild only when deps/ changes, e.g. OCCT module flags):
# docker build -t snaporca-deps -f scripts/Dockerfile.deps . # docker build -t snaporca-deps -f scripts/Dockerfile.deps .
@@ -82,7 +82,7 @@ RUN ln -sfn /OrcaSlicer/deps/build/OrcaSlicer_dep /OrcaSlicer/deps/build/destdir
# The rig's GUI runtime. This used to arrive for free because orcacad-deps was layered on # The rig's GUI runtime. This used to arrive for free because orcacad-deps was layered on
# snaporca-deps; that lineage is Trap 1 in docs/rig_build_traps.md (a baked project(Snapmaker_Orca) # snaporca-deps; that lineage is Trap 1 in docs/rig_build_traps.md (a baked project(Snapmaker_Orca)
# tree) and building from this Dockerfile is what removes it — along with the X stack the rig # tree) and building from this Dockerfile is what removes it — along with the X stack the rig
# needs. scripts/gui-session.sh requires Xvfb and openbox (without a window manager `xdotool # needs. scripts/CAD/start-headless-gui.sh requires Xvfb and openbox (without a window manager `xdotool
# windowactivate` aborts with "windowmanager claims not to support..."), drives the UI with # windowactivate` aborts with "windowmanager claims not to support..."), drives the UI with
# xdotool, and captures to /shots with scrot/ImageMagick. Same set snaporca-deps carries. # xdotool, and captures to /shots with scrot/ImageMagick. Same set snaporca-deps carries.
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
+1 -1
View File
@@ -4005,7 +4005,7 @@ TEST_CASE("golden recipe v1 still deserialises", "[CadDocument]")
// A failure BELOW this point means the on-disk serialization format changed: some field // A failure BELOW this point means the on-disk serialization format changed: some field
// in CadFeature::save/load was reordered, retyped, or removed. Fields may only ever be // in CadFeature::save/load was reordered, retyped, or removed. Fields may only ever be
// APPENDED at the end of both lists. Do NOT regenerate the fixture to make this pass -- // APPENDED at the end of both lists. Do NOT regenerate the fixture to make this pass --
// fix the field order instead. See scripts/kernel-test.sh and the [.regen] case. // fix the field order instead. See scripts/CAD/run-kernel-tests.sh and the [.regen] case.
// Field-by-field assertions against expected values. // Field-by-field assertions against expected values.
// Every field set to a distinctive non-default literal must be checked here. // Every field set to a distinctive non-default literal must be checked here.