refresh_preview() exempted only Sketch and Plane from the ghost-preview path.
Axis, CoordSys, Helix and Project produce no solid either, so they fell through
to it, found nothing, and reported "invalid: preview produced no geometry" —
which also DISABLED Confirm, so all four tools were unusable rather than merely
noisy. Guaranteed on an empty document; with a body present the ghost path
finds something and masks it, which is why it survived until the tools were
tried on a fresh project.
All six non-solid tools are now exempt, each with its own ready message. The
list is not a guess: recompute() skips Sketch, Helix, Plane, Axis and CoordSys
outright and routes Project through apply_project(), which emits sketch entities
and no solid — so the panel and the kernel now agree on exactly what is not a
solid. Mate already had its own branch, since it needs Confirm gated on having
two distinct CoordSys features.
Introduced when Axis/CoordSys (batch 1) and Helix/Project (batch 3) were wired
without extending this exemption. Confirmed fixed on hardware: Axis ->
Plane Intersection with XY and XZ now resolves on an empty document, which also
exercises 60b04feea1.
Compiles clean; kernel untouched, suite unaffected at 143 cases / 1980
assertions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
axis_plane_a/b are filled by the GUI from populate_plane_choices(), whose rows
are XY / XZ / YZ followed by the datum planes, and the row is stored verbatim.
The kernel's base_plane() indexed datum_planes[ref] directly, so the two spaces
were off by three: picking XY resolved to datum plane 0, and picking the first
datum ran past the end and failed with "plane ref not found". The
PlaneIntersection axis type could not work from the GUI at all.
base_plane() now uses the encoding CadFeature::plane_base already uses — 0/1/2
are the base planes through the modeling origin, >=3 indexes datum_planes[ref-3]
— so there is one convention for plane references instead of two. That also
makes two base planes usable, which the previous code rejected as out of scope
even though XY x XZ is an ordinary way to define the X axis.
Removed the dead find_plane lambda directly above it. It was never called and
half-anticipated this exact offset ("if (ref >= 3) // base plane offset"),
which is presumably where the confusion started.
Tests: the existing parallel-planes case encoded the OLD convention, passing
axis_plane_a = 0 to mean "datum 0" — values the GUI cannot produce — so it is
re-based onto rows 3 and 4. Two new cases cover what the GUI actually emits:
base x base (XY x XZ -> X) and base x datum, the latter pinning the +3 offset.
Both verified to FAIL against the previous indexing, at test_caddocument.cpp
:2325 and :2346.
Found by auditing the remaining tools for the index-space defect class that had
already produced three bugs in the GUI; this is the first instance of it
crossing the GUI/kernel boundary.
Suite 143 cases / 1980 assertions (was 141/1972). GUI compiles clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Surface drawer used design_extrude, the same face as the Add-material
drawer, so the two buttons were indistinguishable in the feature bar.
design_surface.svg is a draped patch — deliberately unlike design_plane (a flat
parallelogram) and design_extrude (a box with an up-arrow) — with a faint
interior rule so it reads as a skin rather than a solid face. Same visual
language as the other 71: 24x24, no fill, #b6b6b6, stroke-width 0.85, round
caps and joins.
The five surface ENTRIES that also used design_extrude now use it too. That is
not cosmetic tidying: a flyout button's face follows the last-picked entry
(SetBitmap_(icon_names[i])), so changing only the drawer's default icon would
have been undone the moment the user picked anything. Surface Loft keeps
design_loft, which already suits it.
The six rows still share one glyph between them, so they are told apart by
label alone inside the flyout. Per-entry icons belong with snaporca-vrg
(Draft/Shell reusing design_dressup), not here.
Compiles clean; kernel untouched. Icon confirmed legible on hardware.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
checkpoint() snapshotted `features` and undo() restored `features`, but
`variables` is a separate member of CadDocument. Every caller of the documented
checkpoint -> mutate -> recompute -> undo-on-failure pattern therefore failed to
roll a variable back: the bad value stayed in the document and every later
recompute failed, which is exactly the corruption the pattern exists to prevent.
Feature `expr` bindings were unaffected only because expr lives inside
CadFeature and rode along in the features snapshot — which is why the feature
side appeared to work.
This was a kernel gap, not a GUI one: McpControl::action_set_variable has the
same sequence and was equally broken.
The undo/redo stacks now hold a {features, variables} Snapshot. Nothing here is
serialized, so no recipe version change and no golden-fixture regeneration.
Two tests, both verified to FAIL against a faithful reproduction of the bug
(undo() leaving `variables` untouched) at test_caddocument.cpp:4420 and :4441:
one covers restoring a variable's previous value, the other covers removing a
variable that did not exist before the checkpoint. Worth recording that the
first mutation attempt was NOT faithful — it dropped the restore but kept
std::move(variables) into the redo stack, which empties the map as a side effect
and made the second test pass for the wrong reason. A mutation has to reproduce
the original defect, not merely break the code.
Second defect, same area: undo() calls recompute(), which succeeds and clears
doc.error, so the reason an edit was rejected was destroyed before anything
could display it. Six sites — four in DesignPanel, two in McpControl — now carry
the message across the rollback. on_remove_variable additionally asserted
"referenced by a feature expression" as fact; it now offers that as the likely
cause and appends the real error, since that diagnosis is wrong for any other
failure.
Suite 141 cases / 1972 assertions (was 139/1960). GUI compiles clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The sheet-only pickers filtered correctly and then threw the filtering away. Their
rows are the SHEET bodies, but GetSelection() was passed straight through as an
index into m_doc.bodies. With a solid at 0 and a sheet at 1 — the normal order,
since you extrude a solid before making a surface — the single row is row 0 but
body 1, so Surface Offset and Thicken Surface targeted the SOLID. The kernel then
refused with "target is not a sheet", which reads as a kernel bug rather than a
picker bug, and the row's own label ("Body 2") disagreed with what was targeted.
Four sites per tool were wrong, including the re-edit path, which compared a body
index against the sheet-only row count and so restored the wrong row.
populate_sheet_body_choices() now carries the real body index in client data, and
two helpers make the row/body distinction hard to get wrong again:
sheet_choice_body() reads it back, select_sheet_choice() finds the row holding a
given body. No caller touches GetSelection()/SetSelection() on these pickers.
This is the third instance of the same index-space confusion in this file, after
the 0-based body labels in the interference report and the Rib sketch picker. The
kernel suite cannot catch any of them: the kernel receives whatever index the GUI
computed, and its own tests pass correct ones.
Delete Face was structurally right — its picker uses the all-bodies populate, so
its indices genuinely match, and accumulation appends with a running list. Two
gaps closed: clicking "Add picked face" with nothing picked was a silent no-op,
indistinguishable from a broken button, and the same face could be added twice,
putting a duplicate id into delete_faces that the defeaturing has no reason to
cope with. Re-adding is now a no-op with a message, not an error.
Both confirmed working on hardware. Kernel untouched: 139 cases / 1960 assertions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Port of snaporca ca25352d74. Hand-applied rather than cherry-picked: unlike the
Phase B commits, which only touched DesignPanel.{cpp,hpp} and transfer verbatim,
this one reaches into GLCanvas3D and DesignCanvas, where the forks genuinely
differ — mainline passes m_show_world_axes to _render_bed where Snapmaker passes
a local show_axes, and the surrounding code sits ~90 lines further down. git am
refused, correctly; the six edits were applied against mainline's own context and
the DesignPanel half came across as a patch.
Bed toggle: a "Bed" checkbox in the document/view row, on by default, in that row
rather than in a card because a view option must stay reachable with no tool open.
It drives GLCanvas3D::m_show_bed (default true, so Prepare and Preview are
untouched) and gates _render_platelist as well as _render_bed — hiding the bed
while leaving its grid and outline floating would read as a rendering fault.
Bound to wxEVT_TOGGLEBUTTON, not wxEVT_CHECKBOX: Orca's CheckBox derives from
wxBitmapToggleButton, so a wxEVT_CHECKBOX handler never fires.
Also gives the Placement drawer its own "placement" toolbar slot. put("place")
already holds the Place-on-Face button, and put() formats slot item 0 as the
control and later items as its chevron, so sharing the slot bottom-aligned the
drawer's button like a chevron.
196/196 targets, 0 compile errors, orca-slicer links (165 MB). The build script
still exits non-zero at the AppImage bundling step on libpython3.12.so.1.0 —
that is snaporca-96t, packaging only, and does not affect the binary.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The sidebar opened with eight tool cards stacked in it — Transform, Mirror,
Thicken, Rib, Project, Delete Face, Helix and Mate. A card added to the cards
sizer is visible until something hides it, and close_tool()'s hide-all only
runs on a tool SWITCH, so anything missing from the construction-time hide
block is on screen from the moment the tab opens. Those eight were wired into
close_tool() but never added here. All 37 cards are now hidden at startup.
Worth stating because it invalidates a check I ran while diagnosing this: every
card IS hidden somewhere in the file, so grepping for "hidden anywhere" says
nothing. The block that matters is the one in the constructor.
Second, the drawers mixed unrelated operations, and two group tooltips no
longer described their contents — Dress-up listed eight tools spanning three
different kinds of operation, and Add material still claimed to hold only
extrude/revolve/sweep/loft after Thicken and Rib were added to it.
One concept per drawer now:
Add material extrude, revolve, sweep, loft, thicken, rib
-> grows new solid material, whether from a profile, a face or
a line
Surface unchanged; already coherent
Datum / Curve plane, axis, coord sys, helix, PROJECT
-> reference geometry and derived curves. Project consumes a
body but PRODUCES sketch entities, so it is curve creation,
not a finishing operation
Placement TRANSFORM, MIRROR, MATE (new)
-> moves a body without changing its shape; a mate places one
body relative to another
Dress-up fillet/chamfer, draft, shell, delete face
-> finishing on the faces and edges of an existing solid
Hole / thread unchanged
The new drawer costs no toolbar width: the layout order already contained an
empty put("place") slot between "material" and "plane" with nothing registered
to it. Shift+Y and Shift+Z follow Transform and Mirror; every tool still
appears exactly once.
Compiles clean; kernel untouched, so the suite is unaffected at 139 cases /
1960 assertions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
M6 landed the kernel side as two plain public maps — CadDocument::variables and
CadFeature::expr — reachable only through MCP's set_variable / set_feature_expr.
Nothing in the GUI could create a variable, so the parametric layer was
unreachable from the Design tab.
Variables get a wxListCtrl (name, expression) with add/edit/remove below the
feature tree, since they are document-scope and must not live in a card that
only exists while a tool is open. Expression bindings get one generic row in the
feature-edit path — a field-name combo plus an expression box — rather than an
extra control on each of 32 cards.
Every mutation copies McpControl's sequence exactly, and the rollback is the
part that matters: checkpoint, mutate, recompute, undo() on failure. Without it
one typo leaves the recipe permanently unrecomputable, since load() replays the
whole list. Removing a variable a feature still references fails that recompute,
so it reports the reference rather than a bare evaluation error.
The field-name combo is deliberately editable: only 11 feature types get a
curated field list, and free text is what makes the other 21 reachable. That is
safe because assign_field() throws "unknown parameter: <name>" for anything it
does not know, inside recompute()'s try block — so a wrong name gives a clear
message and a rollback, never a silently dead binding.
Two fixes on top of the generated wiring:
- make_combo() passes wxCB_READONLY, under which Orca's ComboBox HIDES its text
ctrl (ComboBox.cpp:51). There is no SetEditable() to undo that, so the field
combo is constructed directly with style 0; that shows the ctrl with
wxTE_PROCESS_ENTER and makes GetValue() return typed text.
- the field-list helper had been made a file-static function taking
DesignPanel::Tool, which required moving Tool out of private and into the
public API. It is now a private static member instead: 32 values of internal
card state should not be published to satisfy a signature.
Compiles clean (0 errors); kernel suite unchanged at 139 cases / 1960
assertions. Phase B is complete on this fork — all 16 previously GUI-less tools
plus the variables panel. Not yet exercised on a display.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mate completes the set: every CadDocument feature type now has a card. It is the
one that needed CoordSys wired first, since a mate connector IS a CoordSys
feature and cs_a/cs_b are feature indices into the recipe.
The card states what each kind constrains rather than just naming it, because
the five kinds are not distinguishable from their labels — the useful fact is
which DOF each PRESERVES: Fastened fixes all six, Planar leaves in-plane
sliding, Revolute leaves spin, Slider leaves axial travel, Cylindrical leaves
both. The offset/angle spins retitle per kind, since offset is a plane distance
for Planar and a position along the axis for the three joint kinds.
Confirm is blocked, with the reason in the status line, when fewer than two
CoordSys features exist or when A and B are the same one: a mate with cs_a ==
cs_b is meaningless and a dangling index recomputes to nothing useful.
check_interference() gets a button in the feature-tree header behind a rule, not
a tool card — it adds no feature, so it must not checkpoint(), recompute(), or
touch the undo stack, and a card would imply it does. Results go to the status
line as count + worst volume, with the per-pair list in a message box, named as
the parts tree names them.
Fixes on top of the generated wiring:
- the button's sizer adds sat after the closing brace of the block declaring
trow, so trow was out of scope ("'trow' was not declared in this scope");
- the CoordSys client data was typed const void*, which Append rejects;
- the report labelled bodies 0-based while all 28 other body labels in this
panel (and the parts tree) are 1-based, so it would have called the tree's
"Body 2" an interference on "Body 1" — and it was the only unlocalised label.
Compiles clean (0 errors); kernel suite unchanged at 139 cases / 1960
assertions. Still to come: the M6 variables panel. The GUI has not yet been
exercised on a display.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every CAD feature added across M1-M8 got a kernel API and an MCP method, and
almost none got a card. The MCP path was the only way to reach them, so from
the Design tab these features did not exist: add_axis and add_coordsys have
been callable since M1 with no UI at all.
Wired here, in three batches:
datum Axis (5 construction types), CoordSys
surface Surface Extrude / Revolve / Loft / Fill / Offset / Thicken Surface
body Transform, Mirror, Thicken, Rib, Project, Delete Face, Helix
Grouped into existing dropdowns rather than widening the 16-slot toolbar:
surfaces get one new "Surface" dropdown, body ops join Dress-up, Thicken/Rib
join Add-material, Helix joins Datum (renamed "Datum / Curve"). Shift+Y and
Shift+Z went to Transform and Mirror; the remaining five are shortcut-less
rather than getting invented chords.
Three places where the UI has to encode a kernel distinction, not just expose
a field:
- Surface Offset and Thicken Surface consume a SHEET body and fail with "target
is not a sheet" on a solid, so their pickers filter on
CadDocument::is_sheet_shape() and say so when no sheet exists. Thicken (solid
face -> plate) is a different tool and is kept visibly separate.
- delete_faces is a vector, so Delete Face accumulates picks via "Add picked
face" and shows the running list. Supporting one face would have been a
silent downgrade of the kernel field.
- CoordSys labels its edge pick with the consequence of omitting it: without an
edge, datum_frame() takes x from coordsys_x_hint (world constant) and the
frame cannot express rotation about its own normal — which is snaporca-en4,
and is why a Fastened mate built on a face-only connector cannot fix spin.
The Rib sketch picker needed the 3-arg Append(text, wxNullBitmap, clientdata):
ComboBox's own Append(text, bitmap) hides wxItemContainer's (text, void*), so
the 2-arg call resolves to the bitmap overload and fails with "conversion from
void* to const wxBitmap is ambiguous". The Sweep picker already documents this;
Rib now matches it.
Compiles clean (0 errors) against snaporca-deps; kernel suite unchanged at
139 cases / 1960 assertions. Mate, the interference report and the M6 variables
panel are still to come; the GUI itself has not been exercised on a display yet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
First successful link of orca-slicer in this fork's history (158 MB, 738/738
objects, plus OrcaSlicer_profile_validator). 1633005bba got the test binary
green; the GUI had still never linked.
The blocker was in the deps image, not the code. That commit's message says
OCCT V7_6_0, Boost 1.84.0 and OpenCV 4.6.0 "are pinned identically in both
forks and were reused as-is". That is true of the VERSIONS and false of the
FLAGS: this fork's deps/OpenCV/OpenCV.cmake passes -DWITH_JPEG=OFF,
-DWITH_TIFF=OFF and -DBUILD_TIFF=OFF, and snaporca's does not. So the reused
build shipped lib/opencv4/3rdparty/liblibjpeg-turbo.a, which collides with the
deps' own lib/libjpeg.a — wx pulls that in via wxUSE_LIBJPEG=sys — on
jpeg_stdio_dest. Under -flto that duplicate is fatal, not a warning. snaporca
never sees it because its GUI does not link libopencv_world.a at all.
Fixed by rebuilding OpenCV 4.6.0 in orcacad-deps with this fork's own flags
(read out of the recipe rather than retyped), from the source already cached in
the image, into a clean build dir so no stale cache entry survived, and deleting
the orphaned bundled jpeg archive. The rebuilt libopencv_world.a has no jpeg or
tiff symbol references and its CMake config no longer names either library.
--allow-multiple-definition would have hidden this while leaving OpenCV carrying
codecs mainline deliberately turns off.
Lesson for the next dep: comparing deps recipes by version is not enough, diff
the CMAKE_ARGS.
Two more mounts, same root cause as the CMakeLists.txt mount this script
already documents — the baked tree is snaporca's:
- build_linux.sh, which builds `--target Snapmaker_Orca`; here the target is
OrcaSlicer and its output name is orca-slicer, so configure passed and ninja
then died on "unknown target". The binary check was looking for the wrong
name too.
- scripts/, because the packaging step needs scripts/appimage_lib_policy.sh;
without it a fully successful link still exited non-zero with "missing
AppImage helper" and the binary check never ran.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both scripts defaulted IMAGE to snaporca-deps. That image is Snapmaker-based
and lacks Eigen 5.0.1, CGAL 5.6.3, wx 3.3.2 and Python 3.12 Development.Embed,
so running either script here without an explicit IMAGE= dies at CMake
configure — which is a large part of why this fork reached M8 having never once
compiled (1633005bba). The volume defaults were fixed in that commit; the image
default was missed in both files.
docker-iter-build.sh also never mounted deps_src, so it could not have
configured even with the right image: root CMakeLists.txt:947 FATAL_ERRORs
when deps_src/pybind11/include/pybind11/pybind11.h is absent, and
src/CMakeLists.txt pulls semver/hints/imgui/imguizmo/hidapi from the same tree.
kernel-test.sh got that mount in 1633005bba; this is the same fix for the GUI
build path, needed before the Phase B GUI work can be ported here.
CadDocument.hpp: mate_kind comment, mirrored verbatim from snaporca dbaa104f62
so the header stays byte-identical across forks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
206/206 targets built, 139 [CadDocument] cases / 1960 assertions passing —
identical to snaporca's suite. Until now this fork had never compiled at all:
CMake died at configure, so the M1-M8 "suite green" figures were snaporca's
alone and the ports rested on patch-apply plus byte-identical sources.
Three fixes here; the deps work is in the orcacad-deps image (see below).
1. kernel-test.sh mounts deps_src. pybind11 is vendored in-tree and CMakeLists
requires its headers; without the mount the container fell back to the
image's baked tree, which predates it.
2. tests/libslic3r/test_3mf.cpp: repair the upstream-merge conflict resolution.
Resolving it as a union dropped the three closing braces of our SCENARIO, so
upstream's SCENARIO opened inside ours ("a function-definition is not allowed
here", plus 12 cascading catch2 registry errors). Restored from the pre-merge
file; whole-file brace balance is now 0 and the case count reconciles as
5 (ours) + 8 (upstream) - 3 (shared) = 10, with both CAD recipe tests intact.
3. tests/libslic3r/test_caddocument.cpp: REQUIRE_CONTAINS / CHECK_CONTAINS.
Catch2 v2 (snaporca) spells substring-match Matchers::Contains; v3 (here)
spells it ContainsSubstring and gives Contains an incompatible meaning,
range-contains-ELEMENT, which fails to COMPILE against std::string. Four
sites had been hand-adapted long ago, but M2-M8 kept porting in un-adapted
Contains calls — 16 of them — and nothing objected because nothing compiled.
Both forks now use the same find()-based macros, so the assertion lines are
byte-identical again and future format-patch ports carry across unchanged.
Five orphaned `using Catch::Matchers::Contains;` lines removed with them.
The deps gap that blocked configure needed five additions on top of
snaporca-deps, built into image orcacad-deps: Eigen 5.0.1, Python 3.12.13
(exact, with Development.Embed), wxWidgets 3.3.2 (was 3.1.5), CGAL 5.6.3
(was 5.4 — mainline's own MeshBoolean.cpp calls CGAL::parameters::default_values,
added in 5.5), plus the pybind11 mount above. OCCT V7_6_0, Boost 1.84.0 and
OpenCV 4.6.0 are pinned identically in both forks and were reused as-is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BUILD_VOL defaulted to snaporca_buildcache — snaporca's volume — so running
this fork's kernel test wrote into the other fork's build cache. Defaults to
orcacad_kerneltest now.
docker-iter-build.sh had the identical defect and was fixed to
orcacad_buildcache; this script was missed at the time.
Observed rather than theorised: an orca_cad run under a different deps image
overwrote snaporca_buildcache's CMakeCache.txt, after which snaporca's own
kernel test failed to configure ("Cannot find NLopt library 'nlopt_cxx' in
.../lib/cmake/nlopt/lib") because it inherited the foreign cached paths. No
foreign object files were written — the run died at configure — but the cache
was poisoned, and the volume had to be wiped and rebuilt clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Catches the fork up from 449a4cf9fc (2026-06-28) to d6cb667b89 (2026-07-24).
Upstream touched 2326 files; 17 of them overlap the 164 this branch touches.
16 of the 17 auto-merged, including all three CMakeLists.txt, the build_all.yml
CI workflow, and every GUI file. The CAD core never conflicts: CadDocument,
SketchEngine, SketchSolver, McpControl and test_caddocument are files this fork
adds, so upstream does not touch them.
The one conflict, tests/libslic3r/test_3mf.cpp, was purely additive in all three
hunks and is resolved as a union: our test pinning that store_bbs_3mf embeds the
CAD recipe as Metadata/SnapOrca_cad.bin, upstream's multi-nozzle plate-metadata
round-trip tests, and both sets of includes. All three were verified present
after resolution rather than assumed.
NOT BUILD-VERIFIED, for a reason that predates this merge and is not caused by
it: this fork cannot be configured on nativedev at all. Its CMakeLists has
required Eigen3 5.0.1 since before the merge (line 592 pre-merge), while the
only deps image on the machine is snaporca-deps, built for snaporca's
find_package(Eigen3 3.3). CMake fails at configure, so nothing compiles.
That means this fork's Catch2 suite has never run. Every "suite green" figure
recorded for M1-M8 was snaporca's suite; the ports were verified by patch-apply
plus the CAD sources being byte-identical to snaporca's. Building an orca_cad
deps image with Eigen 5.0.1 is what would finally close that gap.
Pre-merge state is preserved at branch cad-mainline-pre-upstream-2026-07-25
(30d54f0074).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
check_interference(min_volume) reports every pair of solid bodies whose
intersection encloses more than min_volume, as {body_a, body_b, volume}. It
reports only — no geometry is mutated, so calling it cannot disturb mates or
placements. Read-only at the MCP surface too: no checkpoint, no recompute.
Sheet bodies are skipped up front: an intersection involving one encloses no
volume, so the boolean would be wasted work. Bodies that merely touch share a
face and enclose nothing, so face-to-face contact is not an interference.
A boolean that fails on one pair must not lose the report for every other pair,
so each pair is guarded — and OCCT raises Standard_Failure, which is not a
std::exception and would otherwise escape.
No new serialized fields, no recipe bump: this reads `bodies`, which is
recompute output and was never serialized.
No separate MCP listing for instances and mates: describe_scene already emits
the feature tree, and Mate has rendered there correctly since M8a fixed
feature_type_name.
Tests assert the exact overlap volume (20*20*4 = 1600 mm^3), both negative cases
(clearly apart, and exact face contact), that sheets are skipped, that the
min_volume gate silences a real overlap, and that a clash created by a Fastened
mate is detected — which ties the M8b placement work to this report.
Suite 139 cases / 1960 assertions green. McpControl.cpp is reviewed but not
compiled by kernel-test.sh, which builds only libslic3r_tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each kind constrains the DOFs it owns and PRESERVES the rest from the body's
current pose, following the pattern Planar established in M8a. Resolved instead
as "Fastened with a parameter", all three would have been geometrically
identical to Fastened — relabelling rather than behaviour.
Revolute fixes position on the axis line; rotation about it survives
Slider fixes orientation and perpendicular position; axial position survives
Cylindrical fixes the axis line only; rotation and axial position both survive
No new serialized fields, no recipe bump, no fixture regeneration: mate_kind is
already an int and mate_offset / mate_angle already exist.
The minimum-rotation z-alignment (including the antiparallel 180 deg case fixed
in M8a) is now a shared make_z_align lambda rather than a second copy.
Fixes a rotation-about-pivot bug found by the no-op tests: R_full was built as a
rotation about the origin with a translation to oB appended, instead of a proper
rotation about oB (translation = oB - R*oB). It moved bodies that were already
correctly placed, and accounted for three of the seven initially failing cases.
Testing notes, both of which cost real debugging time here:
- Mates are defined on connector FRAMES, but the convenient thing to measure is
CentreOfMass(), and the two coincide only when the body is symmetric about its
connector. Five expectations in this milestone asserted the centroid while
meaning the connector. These tests assert on the mated face's centroid.
- A CoordSys built from a face ALONE takes its z from the face normal (which
follows the body) but its x from coordsys_x_hint, a world constant. Such a
frame cannot see rotation about its own normal, so no mate can correct or
preserve a spin it does not encode. The Slider and Cylindrical rotation tests
pin coordsys_edge to an edge of their own body; without that both passed
vacuously, one of them for a wrong implementation.
The Cylindrical rotation test was verified to fail when its mate kind is mutated
to Slider, and the Slider test failed at axis_aligned == 2 before the connectors
were edge-pinned. Neither is green by accident.
Known wart: mate_angle is silently ignored for Slider, whose rotation is fully
constrained. Defensible but undiagnosed at the API surface.
Suite 134 cases / 1927 assertions green. McpControl.cpp is reviewed but not
compiled by kernel-test.sh, which builds only libslic3r_tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An assembly is a multi-body document. An instance is already expressible as
Transform with xf_copy=true, and a mate connector is already a CoordSys
feature, so this adds exactly one feature type: Mate.
No constraint solver. A mate rigidly transforms the body carrying connector B
so that B's frame lands on connector A's, applied in feature order like every
other feature. Chains resolve by composition; closed kinematic loops do not
converge (last mate wins) and are out of scope.
The vendored SolveSpace in src/libslic3r/slvs/ was evaluated for 3D extension
and rejected: it is built and linked but has zero callers, and SketchEngine's
solver is hand-rolled. Extending it would mean adopting a dependency to write
more code than the alternative.
- CadFeatureType::Mate appended; six fields (mate_kind, mate_cs_a, mate_cs_b,
mate_offset, mate_angle, mate_flip) appended at the END of both cereal lists
- SNAPORCA_CAD_RECIPE_VERSION 2 -> 3; v2 blobs are rejected, as by design there
is no migration path. Golden fixture renamed to cad_recipe_v3.bin and
regenerated once, extended with two CoordSys + one Mate so the new fields are
tripwired by the field-order assertions
- datum_frame() extracted from resolve_datum_coordsys() so a mate can resolve
its connectors against the in-progress bodies vector during replay
- apply_mate dispatched early-return, so Mate is deliberately absent from
starts_new (unreachable for that dispatch style)
- Planar: the degenerate branch splits on the sign of zB.z_target — antiparallel
needs a 180 deg rotation about a perpendicular axis, which an earlier revision
silently skipped, leaving the body's normal inverted
- MCP: mate command, named bare to match the other 38 methods
Drive-by: feature_type_name() was missing Mirror, ThickenSurface, SurfaceOffset,
SurfaceLoft and SurfaceFill, which reported as "Unknown" to MCP clients.
Suite 122 cases / 1741 assertions green. Note that kernel-test.sh builds only
libslic3r_tests, so McpControl.cpp is reviewed but not compiled here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two remaining ways to create a sheet body. SurfaceLoft skins 2+ profile
sketches without end caps via a new SketchEngine::make_loft_surface — a
sibling of make_loft with the ThruSections solid flag false, so no existing
call site changes. SurfaceFill patches a single closed boundary wire into a
smooth face with BRepOffsetAPI_MakeFilling, adding each boundary edge as a
C0 constraint.
Purely additive: two enum values appended to CadFeatureType, reusing the
existing loft_profile_refs/loft_ruled and sketch_ref fields. No new cereal
fields, recipe stays v2, golden fixture unchanged (30773). MCP
surface_loft/surface_fill added as pure additions. Suite 107 cases / 1553
assertions green, including a test contrasting the open skin against the
solid loft of the same profiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Two features bridging sheet bodies back to solids and to other sheets:
ThickenSurface feeds a whole sheet shell to MakeThickSolidBySimple (the same
OCCT recipe the face-level Thicken already uses) and appends the result as a
solid; SurfaceOffset offsets a sheet's shell along its normals via
MakeOffsetShape::PerformBySimple, keeping it open. Both refuse a non-sheet
target with a clear error.
Purely additive: two enum values appended to CadFeatureType, reusing the
existing target_body / thicken_thickness / thicken_flip / plane_offset
fields. No new cereal fields, recipe stays v2, golden fixture unchanged
(30773). MCP thicken_surface/surface_offset added as pure additions.
Suite 103 cases / 1520 assertions green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Two body-producing features that emit an open shell instead of a capped
solid: SurfaceExtrude (prism of a sketch wire, no end caps) and
SurfaceRevolve (revolve of a wire about an in-plane axis, no caps). Each
appends a new sheet body whose TopoDS_Shape has no TopAbs_SOLID.
Purely additive: two enum values appended at the end of CadFeatureType,
reusing existing serialized fields (sketch_ref/distance,
revolve_angle/revolve_axis). No new cereal fields, recipe stays v2, golden
fixture unchanged. is_sheet_shape() derives sheet-ness from the OCCT shape
type (bodies are not serialized). MCP surface_extrude/surface_revolve added
as pure additions. Suite 99 cases / 1474 assertions green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Add named document variables (CadDocument::variables) and per-feature
expression bindings (CadFeature::expr, field-name -> expression). On
recompute(), variables are evaluated topologically (cycle detection), then
each feature's expr entries are evaluated and written into its numeric fields
before geometry runs. Self-contained shunting-yard evaluator (+ - * /, parens,
unary minus, sqrt/abs/sin/cos/tan(deg)/min/max, pi). assign_field allow-lists
the 33 dimension fields + pattern_count; unknown names error loudly.
Additive: recipe stays v2 (fields appended to both cereal lists, golden
fixture regenerated). MCP set_variable / set_feature_expr are pure additions.
Suite 95 cases / 1440 assertions green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Extend CadFeatureType::Pattern (no new enum) with a curve mode: when
pattern_curve_sketch >= 0 it takes precedence over linear/circular. The guide
entity is sampled at equal-parameter points via a file-local sample_entity_2d()
(Line lerp, Arc angle-lerp, cubic-BSpline Bernstein, p0->p1 fallback), and each
seed copy is translated by (P_i - P_0) and fused. Two serialized fields
(pattern_curve_sketch/pattern_curve_entity) appended to both symmetric cereal
lists (version stays 2, golden fixture regenerated 30269->30517). MCP:
pattern_on_curve. 3 new [CadDocument][pattern] tests; suite 89/1395.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
New CadFeatureType::Rib (appended). A straight open Line entity in a sketch
is offset ±thickness/2 along its in-plane perpendicular into a thin rectangle,
extruded rib_depth along the sketch-plane normal, and fused to the target body.
Line-only for now (ponytail; polyline/arc ribs are a later extension) — a
non-line entity fails cleanly at recompute. Four serialized fields
(rib_sketch_ref/rib_entity/rib_thickness/rib_depth) appended to both symmetric
cereal lists (version stays 2, golden fixture regenerated 29525->30269). MCP:
rib. 3 new [CadDocument][rib] tests; suite 86/1376.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Extend CadFeatureType::Hole (no new enum value) with a style flag
(simple/counterbore/countersink) and the matching geometry: a coaxial
shallow cylinder cut for counterbores, a cone-frustum cut for countersinks.
A file-local hole_std_lookup() resolves screw designations (ISO 273/4762/
10642 metric M3–M10 + common ANSI unified) into clearance/cbore/csink dims;
add_hole_standard() fills the feature from it, add_hole_styled() takes them
explicitly. Six serialized fields appended to both symmetric cereal lists
(recipe version stays 2, golden fixture regenerated 28161->29525). MCP:
hole_styled, hole_standard. 4 new [CadDocument][hole] tests; suite 83/1351.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
New CadFeatureType::DeleteFace: removes a set of global face ids from target_body
and heals the gap via BRepAlgoAPI_Defeaturing (TKBO, already linked), mirroring the
Shell/Draft body-modifying pattern. delete_faces appended to both symmetric cereal
lists (recipe version stays 2, golden fixture regenerated 27913->28161). MCP
delete_face method (pure additions). 3 new [CadDocument][deleteface] tests: remove a
fillet face restores the sharp-box volume, bad index fails safely, round-trip.
Full kernel suite green (79 cases, 1309 asserts).
Move-face / replace-face deferred to snaporca-3c4 / snaporca-tc6 (no clean shipping
OCCT direct-modeling primitive; need research, and replace-face depends on M7 surfaces).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Adds SketchEngine::make_bridge (4-pole cubic Bezier, G1-tangent to Line/Arc
endpoints, straight-line fallback for other types) emitted as the existing
BSpline SketchEntity — no new geometry type, no serialized-field change, golden
recipe fixture untouched. CadDocument::add_bridge appends it (non-parametric,
index-validated, throws on bad refs). MCP `bridge` method mirrors action_project.
4 new [CadDocument][bridge] tests; full kernel suite green (76 cases, 1280 asserts).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
The construction flag was already honored (excluded from the extrude wire in
SketchEngine.cpp, participates in the solver, and serialized) but the existing
"construction line excluded" test was a false tripwire: its construction line
ran corner-to-corner inside the square, so the bbox was unchanged whether or not
the line was excluded.
- Strengthen that test: the construction line now runs (-30,0)->(30,0) outside
the profile, so an exclusion regression breaks the closed wire / bbox.
- Add a serialize/deserialize round-trip test asserting construction survives.
- Lock the flag on-disk: add Sketch_Ctor to the golden fixture with a real edge
+ a construction edge, and assert both flags survive the binary recipe.
Test-only; no kernel change. Recipe version stays 2 (construction was already a
serialized field). Suite: 72 cases / 1246 assertions green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Onshape-style "Use / Convert entities": pick edges (or a whole face) of an
existing solid and get sketch geometry projected onto a target plane, then
extrude/revolve/edit it like any sketch. Parametric: apply_project re-derives
the feature's entities from the source body on every recompute, so editing the
source updates the projection.
Line edges -> Line entities (exact); circles/arcs whose plane is parallel to
the sketch plane -> Circle/Arc (exact); everything else (incl. non-parallel
circles that project to ellipses) -> sampled Line chain.
Append-only: new enum value Project + project_source_body/project_edges/
project_face fields at the end of save/load; recipe version stays 2. Recompute
loop made non-const solely so apply_project can write back f.entities.
Suite 67->71 cases, 1178->1229 assertions, RC=0. Fixture 25493->26835 B.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Split-by-face reuses the existing Cut feature rather than adding a new type:
two appended fields (cut_face_body, cut_face) let apply_cut derive the cut
plane from a picked face via SketchPlane::from_face when cut_face >= 0,
otherwise it keeps using the base `plane`. cut_offset / cut_flip still apply
along the derived normal, so the same square-wire split machinery handles
both cases. add_split_by_face() is the convenience entry point; MCP gains a
`split` method (body / face_body / face / keep_upper / keep_lower).
Serialization stays append-only — cut_face_body, cut_face appended to
save/load, recipe version unchanged at 2.
Tests: the previously-missing both-halves plane cut (keep_upper && keep_lower
=> two bodies whose volumes sum to the original), split-by-face via a
top-face plane offset into the interior, keep-upper-only, and a round-trip.
Golden fixture regenerated with a GoldenSplit cut-by-face feature and exact
field-value assertions. Suite 63 -> 67 cases, 1119 -> 1178 assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Pick a face of an existing body, offset it by a wall thickness along its
normal, and append the resulting thin solid as a new body. Onshape-parity
Tier-2 item; the kernel had Shell (hollow a whole solid) but no way to turn
a single face into a plate.
Kernel: CadFeatureType::Thicken, add_thicken()/apply_thicken() as a
body-level op next to Transform/Mirror. The picked face is wrapped in a
TopoDS_Shell and offset via BRepOffsetAPI_MakeThickSolid::MakeThickSolidBySimple;
the result is orientation-normalised to positive volume (same convention as
apply_mirror). Serialization stays append-only — thicken_face,
thicken_thickness, thicken_flip appended to save/load, recipe version
unchanged at 2.
MCP: `thicken` method (body/face/thickness/flip) plus the missing
feature_type_name() case.
Tests: 6 new [CadDocument] cases (plate volume within 1%, flip direction,
bad face id, zero thickness, fuse-with-source, round-trip). Golden fixture
regenerated with a GoldenThicken feature and exact field-value assertions.
Suite 57 -> 63 cases, 1054 -> 1119 assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Until now move and rotate lived only in the GUI as m_body_xform, a display
transform. That made them a correctness hole, not a missing tool: a moved body
recomputed and booleaned at its ORIGINAL position, and the move was not in the
recipe at all, so it vanished on save/reload. Only export_step consulted the
transform, which is why the discrepancy stayed hidden.
CadFeatureType::Transform makes it a real feature: rotate angle_deg about
xf_axis through xf_pivot, then translate by xf_translate, applied to the target
body with BRepBuilderAPI_Transform. xf_copy=true keeps the source and appends
the transformed body instead of mutating in place, which covers Onshape's
Transform/copy in the same feature.
Rotation is composed before translation (trsf = tr * rot) so the pivot means
what a user expects — the point the body turns about, not a point that then
drifts with the translation. A rotation with a degenerate axis is refused
rather than silently skipped; a zero angle skips the rotation entirely so a
pure move needs no axis at all.
The decisive test is not the bbox arithmetic but "moved body participates in a
later boolean at its new position": two coincident boxes, one moved to partial
overlap, fused. The fused volume must be strictly greater than one box (the
move took effect in the kernel) and strictly less than both (they still
intersect). With a display-only transform the first assertion fails.
Serialization stays append-only; recipe version unchanged at 2. Golden fixture
regenerated with a GoldenTransform feature carrying distinctive literals so a
field reorder shows up as obviously wrong values. Also fills in the Helix arm
of feature_type_name(), missing since the helix commit.
Kernel suite 51 -> 57 cases, 985 -> 1054 assertions, green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Adds CadFeatureType::Helix — the missing input for Sweep. Sweep already
existed but could only follow a sketch, so springs, coils, augers and
non-standard-pitch threads were unreachable. Helix + the existing Sweep now
gives all of them with no further work.
Built the OCCT way: a 2D line on a Geom_CylindricalSurface (Geom_ConicalSurface
when helix_taper_deg != 0) turned into an edge and lifted to 3D with
BRepLib::BuildCurves3d — a true analytic helix, not a sampled polyline, so a
swept spring is smooth rather than faceted. The axis is the plane normal
through the plane origin, matching how Revolve and Plane already work.
Sweep's path resolution is widened to accept either a Sketch (unchanged
behaviour) or a Helix, and rejects anything else with a clear error. Helix
itself is skipped in route_feature and recompute — like the datum features, it
produces no body and exists to be consumed.
Invalid input is refused rather than approximated: non-positive radius or
pitch, negative height, a turn count above 10000 (which would hang OCCT), and
a taper that would drive the radius negative before reaching the top all fail
with a specific error.
Serialization: helix_radius/pitch/height/left_handed/taper_deg appended at the
very end of both save and load, identical order, after the coordsys block.
SNAPORCA_CAD_RECIPE_VERSION stays 2; Helix is appended to the end of
CadFeatureType. Golden fixture regenerated with distinctive literals and
field-value assertions; all pre-existing assertions pass unchanged.
Tests assert analytic values. The one that actually proves it is a helix and
not a circle or a spiral: arc length of r=5 pitch=2 height=10 measured with
BRepGProp::LinearProperties against 5*sqrt((2*pi*5)^2 + 2^2), WithinRel 1e-3.
Plus bounding box (2r in X and Y, height in Z), the conical top radius, the
left-handed winding compared at equal parameter, and the integration test:
a circle r=1.5 swept along a 5-turn helix gives one valid solid of ~1115 mm^3
(WithinRel 0.1 — pipe sweeping is not exact).
MCP: `helix` method registered in describe_tools().
Ported from snaporca 6cdc6b5459. Two fork-specific adjustments: the two new
error-message assertions use Catch2 v3's ContainsSubstring (v2's Contains does
not exist here), and the golden fixture is copied rather than regenerated
because this fork cannot be compiled locally — make_golden_doc_v1() is
byte-identical across both forks, so the blob is provably the same.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Adds CadFeatureType::Axis and ::CoordSys — reference geometry that produces
no solid, modelled on the existing Plane datum feature.
Axis construction methods (AxisType): TwoPoints, FaceNormal,
CylinderCenterline, PlaneIntersection, AlongEdge. The centreline case is the
useful one: it gives a real axis through an existing hole or boss.
Coordinate systems (CoordSysType): PointWorld and FaceAndDirection. The
latter Gram-Schmidts the picked references, so the stored frame is
orthonormal even when the user's X hint is not perpendicular to the face
normal; the third axis is derived by cross product rather than stored, so it
cannot drift out of sync.
Revolve and pattern are deliberately NOT rewired to consume these — this
commit adds the reference geometry only and changes no existing behaviour.
Serialization: all axis_*/coordsys_* fields appended at the very end of both
CadFeature::save and load, identical order, after mirror_keep_original.
SNAPORCA_CAD_RECIPE_VERSION stays 2; Axis and CoordSys are appended to the
end of CadFeatureType so existing type ordinals are unchanged. Golden fixture
regenerated with distinctive non-default literals and field-value assertions
for every new field; all pre-existing assertions pass unchanged.
Tests assert analytic values: two-point axis direction exactly +Z with unit
length, cylinder centreline collinear with Z and on the true axis, parallel
planes fail cleanly, and the Gram-Schmidt frame is orthonormal to 1e-9 with
X x Y == Z. Degenerate input (identical points) fails with a non-empty error
rather than producing NaNs.
MCP: `axis` and `coordsys` methods registered in describe_tools().
Ported from snaporca 242d4efecb. The golden fixture is copied rather than
regenerated because this fork cannot be compiled locally (Eigen 5.0.1 vs the
build image's 3.3); make_golden_doc_v1() is byte-identical across both forks,
so the two fixtures are provably the same blob.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Adds CadFeatureType::Mirror: reflect a target body about a plane using
gp_Trsf::SetMirror + BRepBuilderAPI_Transform. BooleanMode::New keeps the
mirrored copy as its own body (mirror_keep_original decides whether the
source survives); BooleanMode::Add fuses it back into the source, so an
overlapping mirror does not double-count volume.
Serialization: mirror_keep_original is appended at the very end of both
CadFeature::save and load (append-only contract). The mirror plane reuses
the existing `plane` member and the body selector reuses `target_body`,
as Cut already does. Golden fixture regenerated at the current
SNAPORCA_CAD_RECIPE_VERSION = 2; the existing field-value assertions all
still pass unchanged, and the reorder tripwire was re-verified after
regeneration (swapping draft_face/draft_angle in `load` alone still
fails the golden test).
Tests assert analytic values: mirrored volumes equal (8000 each) with the
reflected centroid, Add on a non-overlapping asymmetric body gives exactly
2x volume, Add across an intersecting plane gives strictly less than 2x,
and an invalid body index fails cleanly with a non-empty error.
MCP: `mirror` method registered in describe_tools().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
# Description
Adding optional flag for enabling console logging introduced in #14439.
Console logging not working with LLDB-DAP on VSCode as per #14897
Code changes won't fix this because it is a LLDB-DAP issue documented in
#14909
Use flag `-DUSE_SLIC3R_CONSOLE_LOG=ON` or `-DUSE_SLIC3R_CONSOLE_LOG=OFF`
to enable/disable it.
Alternatively in you VSCode's settings.json,
```
"cmake.configureSettings": {
"USE_SLIC3R_CONSOLE_LOG": "OFF"
}
```
<!--
> A guide for users on how to download the artifacts from this PR.
-->
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
# Description
Remove DEPENDS/empty-COMMAND args that are invalid in the
add_custom_command(TARGET) form (CMP0175), fix the FindDraco.cmake case
mismatch, and opt Boost lookup into upstream BoostConfig via CMP0167 for
the OpenVDB module and the CGAL find.
# Screenshots/Recordings/Graphs
<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->
## Tests
<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->
<!--
> A guide for users on how to download the artifacts from this PR.
-->
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
Port of snaporca c2821af783. New GeometryEngine::mass_properties over BRepGProp
(separate VolumeProperties/SurfaceProperties), bounds-checked
CadDocument::body_mass_properties, and a mass_properties MCP method. Query-only:
no CadFeature, no serialization, no version change. Analytic tests
(WithinRel/WithinAbs): cube 8000/2400/COM(0,0,10)/inertia 533333, cylinder
500pi/300pi, hollow = solid-500pi, invalid index -> valid=false.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Port of snaporca 04c3d579a7. Bump SNAPORCA_CAD_RECIPE_VERSION 1 -> 2;
deserialize_recipe sets a user-facing error distinguishing too-new / too-old
/ corrupt instead of a silent bare false. No per-version migration by design.
Golden fixture regenerated at v2 (v1 retired), field-value reorder tripwire
unchanged. Fork adjustment: Catch2 v3 string matcher ContainsSubstring
(not v2's Contains) in the two new version-mismatch tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Remove DEPENDS/empty-COMMAND args that are invalid in the
add_custom_command(TARGET) form (CMP0175), fix the FindDraco.cmake case
mismatch, and opt Boost lookup into upstream BoostConfig via CMP0167 for
the OpenVDB module and the CGAL find.
# Description
attempt to fix#14851
# Screenshots/Recordings/Graphs
<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->
## Tests
<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->
<!--
> A guide for users on how to download the artifacts from this PR.
-->
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
CadFeature::save/load is append-only by contract, and ~20 planned features
each append fields. The existing roundtrip test cannot police that: it writes
and reads with the same code, so any self-consistent ordering passes. Only a
blob written by older code and stored on disk can detect that the format moved.
The first attempt at this test passed while the defect was present. I proved
it by swapping draft_face (int) with draft_angle (double) in both save() and
load() -- a genuine byte-layout change -- and it still reported 613 assertions,
exit 0. It asserted only derived geometry: body count, per-body volume, feature
types. The golden document had no Draft feature, so those fields sat at their
defaults, the reorder scrambled values nothing read, and the recomputed solids
came out byte-identical.
So the fixture now asserts the DATA, not what the data produces:
- make_golden_doc_v1() builds 22 features across 14 types (Draft, Shell,
Revolve, Pattern, Cut, Hole, Chamfer, Fillet, Extrude taper/symmetric,
Thread, Sweep, Loft, Boolean, Plane) with distinctive non-default literals
(draft_angle 7.25, shell_thickness 1.375, revolve_angle 217, pattern_count 5)
so a reorder produces visibly wrong values rather than swapped defaults.
- Layer 1 reads the committed blob with raw cereal and asserts field by field,
independent of recompute, so a geometry regression cannot mask a format break.
- Layer 2 keeps the geometry checks as a separate concern.
Verified to trip, twice, by deliberate breakage rather than by assertion:
draft_face <-> draft_angle -> draft_face reads 1075642368 (0x401d0000),
the high half of double 7.25
revolve_angle <-> revolve_axis -> revolve_angle reads 0.0, not 217.0
Both revert clean to 758 assertions / 30 cases.
Also scoped the "regenerate the fixture" hint to the feature-count check only.
It was in scope for every assertion in the block, so a detected reorder told
you to run [.regen] -- which would bake the corrupted layout in as the new
golden and permanently disarm the test. A guard must not advise disabling
itself.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Ported from snaporca f9e0f99bcb. The patch needed fuzz: this fork's copy of
test_caddocument.cpp carries a Catch2 v3 include, a `using Catch::Approx`, and
an extra [Deviation] case appended after line 1611 -- exactly where these hunks
land. Verified after applying: new symbols present, the [Deviation] case
intact, braces balanced, and only WithinAbs/WithinRel used (both exist in v3).
Compile and test verification here is CI, not local: this fork needs Eigen
5.0.1 while the local deps image ships 3.3.
Adds scripts/kernel-test.sh: build the libslic3r_tests target and run the CAD
kernel tags, exit code as the whole contract. Clean build 4m36s, incremental
18s, no display needed -- every [CadDocument] case builds a CadDocument,
recompute()s it and asserts on geometry.
Four things this had to get right, each found by it going wrong first:
- SLIC3R_GTK=3 and BUILD_TESTS=ON are mandatory. src/CMakeLists.txt turns
SLIC3R_GTK into 'wx-config --toolkit=gtk<N>', so omitting it asks for
toolkit "gtk", nothing matches, and configure dies with the thoroughly
misleading "Could NOT find wxWidgets" -- while wx-config sits right there
in the deps prefix, working. BUILD_TESTS=ON is what creates the target.
- Configure runs unconditionally. Guarding on "CMakeCache.txt exists" is
wrong because a FAILED configure writes that file too, after which the
guard skips reconfiguring forever and every later run silently reuses the
poisoned cache, ignoring corrected flags.
- A new build volume is always built clean. Cloning a warm cache from
another tree is a correctness trap: rsync preserves source mtimes, ninja
compares them against foreign object timestamps, concludes everything is
current and relinks stale objects. That produced a binary containing NO
[CadDocument] tests at all -- while exiting 0. A green run that tests
nothing is worse than a red one.
- --host builds where the deps image already lives, staged per volume so
parallel workers never share a tree.
The two [known-broken] tags: "entity constraints: tangent/midpoint/symmetric/
angle" aborts inside the vendored solver (slvs/dsc.h FindById, "Cannot find
handle"), and SIGABRT is fatal to the Catch2 process -- that single case took
the suite down at 12 of 31, so a green baseline was unreachable. The thread
groove case is a plain pre-existing assertion failure. Both are excluded from
the dev loop's default filter ONLY; ctest in CI still runs and reports them,
so neither bug is hidden. Baseline is now 603 assertions / 29 cases green,
which is what makes "I broke nothing" a meaningful statement.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
Enumerated from the source rather than from recollection: CadFeatureType and
add_* in CadDocument.hpp, Tool in DesignPanel.hpp, Mode in
DesignSketchTool.hpp, SketchConstraintType + SketchEntity::Type in
SketchEngine.hpp, and the JSON-RPC dispatch in McpControl.cpp.
Findings worth stating up front:
- The 2D sketcher is at or near Onshape parity -- 19 constraints, every entity
type including B-splines and elliptical arcs, trim/extend/offset/mirror and
both array kinds. Very little is missing there.
- The gaps are all breadth beyond sketching: assemblies/mates, surface
modelling, sheet metal, drawings, and variables/configurations.
- The most defensible criticism is the absence of variables and expressions.
Every dimension is a literal double, so the feature tree is parametric in
structure but not in value -- "change one number and the model updates" is
only half delivered. It is also the cheapest Tier 1 item to close.
The doc separates platform capabilities (version control, FeatureScript, FEA,
rendering, cloud PDM) into their own tier rather than counting them as missing
tools: that is Onshape-the-platform, not Onshape-the-modeller, and holding a
slicer tab to it would not be a fair comparison.
One entry is a correctness gap rather than a missing feature: move/rotate body
(m_body_xform) is display-only and never enters the B-rep, so a moved body
exports and booleans at its original position while the viewport shows it
moved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
# Description
Plugin discovery now distinguishes an unavailable or invalid
.install_state.json from a valid sidecar. During transient filesystem
replacement, the existing enabled state is preserved instead of being
reset, preventing plugins from unexpectedly losing their auto-load
behavior during rescans.
Thanks @WeLizard for pointing this out.
<!--
> A guide for users on how to download the artifacts from this PR.
-->
[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)