Commit Graph
30416 Commits
Author SHA1 Message Date
5ede9711f5 Fix GTK3 dialog min size: SetSizer → SetSizerAndFit for dialogs without explicit SetMinSize (#14948)
* For dialog without explicitly `SetMinSize`, we should use `SetSizerAndFit` instead, otherwise the dialog will not show correctly on GTK3. (OrcaSlicer/OrcaSlicer#14561)
- and if `SetSizer` is called before the full layout has been built, then an extra `SetSizeHints` should be called before layout/fit so the min size can be properly set automatically based on children's min sizes accordingly.

* Fix GTK3 dialog min size: SetSizer → SetSizerAndFit for dialogs without explicit SetMinSize

Replace SetSizer() with SetSizerAndFit() in 11 dialog constructors that
neither call SetMinSize() nor SetSizeHints(), ensuring proper minimum
size propagation from child widgets on GTK3.

SetSizerAndFit internally calls sizer->SetSizeHints(window), which
sets the window's minimum size based on children — the same fix
applied to ProjectDropDialog in 8a7662083e.

Also drop sizer->Fit(this) calls where present, since they only
resize but don't set the min size hint needed by GTK3.

Co-Authored-By: Claude <noreply@anthropic.com>

* Update code style

* Update TroubleshootDialog.hpp

* Fix unsaved preset dialog layout

* Fix MsgDialog layout

* Fix other 3 instances in MsgDialog.cpp

* Fix a few more instances

* Fix printer option dialog too big on Windows

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: yw4z <ywsyildiz@gmail.com>
2026-07-28 14:32:55 +08:00
Ian Bassi 6bcb809dd0 Calibrations improvements (#14759) 2026-07-27 20:11:44 -03:00
Ian Bassi 33dfb66aa5 Cyclic ordering improvement (#14784) 2026-07-27 19:58:54 -03:00
Maksym PyrozhokandIan Bassi ef7bfeda9c Cyclic ordering (#13578)
Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2026-07-27 19:52:29 -03:00
47ccca7f72 Full Orca translation via AI (tagged) (#14970)
Co-authored-by: Felix14_v2 <75726196+Felix14-v2@users.noreply.github.com>
Co-authored-by: π² <189209038+pi-squared-studio@users.noreply.github.com>
2026-07-27 19:31:33 -03:00
SoftFever 5b475e5e98 Route the in-place toolchange tower entry through the skip-point gap
On multi-tool printers without ramming the tool changes away from the
tower and the entry travel is the tcr's own positioning move, which went
straight across the printed wall. Append the avoid-perimeter path to the
change-filament gcode instead, so the head approaches around the tower
and enters through the wall opening (append_tcr parity).
2026-07-28 00:15:59 +08:00
SoftFever 56810c8c7f Port the skip-points gap wall to WipeTower2
prime_tower_skip_points was stubbed for Type2 towers: the wall call
hard-coded skip_points=false, the gap cutter received an empty vector,
and append_tcr2 never routed the entry travel. Now the toolchange entry
positions are precomputed from the finalized plan, the wall is cut open
at each entry, and the entry travel approaches around the tower bounding
box through the opening when it starts outside the tower. The geometry
helpers are re-synced with the BBL versions (add_extra_point guards,
per-point side selection). The cone wall keeps its separate path, where
the option stays inert.

Behavior change: non-BBL towers now honor the (default-on) checkbox with
gap walls and routed entries; with the option off the output is
unchanged, and the BBL tower path is untouched.
2026-07-27 21:18:53 +08:00
Tommaso Bianchi 5f1811c2c5 A subtraction that removes nothing is an error, not a silent success
A boolean cut whose tool misses the target is a perfectly legal
operation: OCCT reports IsDone(), the shape comes back unchanged, and the
feature lands in the recipe reporting ok:true. Driving the MCP socket,
that produced two consecutive {ok: true, bodies: 1, error: ''} responses
for a hole that was never drilled -- same viewport, same 46939.11 mm3 --
and the tree grew two Hole features that will never cut anything. A
caller, an agent especially, has no signal at all that the thing it asked
for did not happen.

Measure the volume across the op in route_feature's in-place branch and
refuse the no-op. Only for removals: Hole, Thread, and Extrude / Revolve
/ Sweep / Loft in Cut mode. Everything else may legitimately leave the
volume alone -- a Transform certainly does. The tolerance is relative,
because an absolute epsilon is wrong across the mm-to-metre range of real
parts, and a cut that shaves a numerically invisible sliver is a miss
too. The existing rollback in the MCP actions already preserves the
reason, so a missed hole now answers ok:false with the error and undoes
the feature.

The confusion underneath was not itself a bug: hole's x/y are in the
sketch plane's frame, whose origin is describe_scene's modeling_origin,
and describe_tools documented them only as "number, unit mm". Passing the
world centre put the hole 135 mm clear of the solid. All six x/y params
on hole / hole_styled / hole_standard now say which frame they are in,
since the wrong guess was silent.

Regression test drives the reported failure directly: a hole at x=135 on
a 20x20x20 box is rejected and leaves the body untouched, the same hole
at the origin still removes exactly pi*4^2*20, and a cut-mode extrude
whose profile sits at x=200 is rejected too. 152 cases / 2083 assertions
green on both forks.

snaporca-daf.
2026-07-27 07:21:06 +02:00
SoftFever c3c37e474a Print the WipeTower2 shell with a non-support, non-soluble filament
Like the BBL tower: the layer's sparse infill, wall, and brim go to the
first toolchange to a non-support/non-soluble filament, or are printed
with the incoming filament before any toolchange. The minimal-purge
clamp now also covers toolchanges that get no finish-layer saving.
Output is unchanged when no support/soluble filament is used.
2026-07-27 12:30:57 +08:00
Tommaso Bianchi 5d05ca30ca Sketch shortcuts: pick the key map by mode, not by whether a session exists
All 17 single-letter sketch shortcuts were dead. The dispatch gate was
circular: the sketch key map was consulted only when
m_viewport->is_sketching() was already true, but is_sketching() is a
whole-session flag whose only riser is begin_sketch(), called from
select_tool() -- which is precisely what every sketch key closure calls.
So the first letter after entering sketch mode fell through to the
feature map, where the keys are Shift+letter, matched nothing, and did
nothing. The mouse worked only because the toolbar flyout row reaches
select_tool() directly, bypassing the gate.

Which key MAP applies is a question about the mode. Split the flag:
'sketching' (live session) still drives undo/redo, Delete and the
section-view branch, where a session genuinely has to exist; a new
'sketch_mode' (m_ui_mode == UiMode::Sketch alone) drives the map choice.

Verified headless end to end, keyboard only: Shift+S, then R draws a
143.4 x 133.7 rectangle on XY reporting 4 degrees of freedom, then F and
L arm Fillet and Line (artifacts/shots/0udb-01..03). Note the toolbar
strip does NOT change when a tool is armed by keyboard -- the family
buttons are flyouts and only show their own pressed state -- so the
pixel diff on that strip, which is how this was originally measured,
reads 0 for a tool that is live. The status line is the surface that
actually reflects the armed tool.

Also fixed, from snaporca-d9i's list: the plane-pick status line said
"press Sketch to draw on it", naming a button that exists only in
Feature mode. It is now mode-aware.

Adds a SNAPORCA_KEYTRACE=1 trace in the CHAR_HOOK printing key, ui mode,
is_sketching, in_text and the focused window's class. It is what
separated "the fix does not work" from "the surface being measured never
moves", and it costs a full GUI build to re-add, so it stays.

snaporca-0ud, partial snaporca-d9i.
2026-07-27 00:40:25 +02:00
Kiss Lorand 04e13200aa Support bugfixes (#14678) 2026-07-26 19:16:19 -03:00
Tommaso Bianchi 347b83d887 Sketch fillet: never write a failed solve's geometry back, and commit the op
Two P0s in the same gesture. Filleting a corner of a parametric rectangle
produced either nothing at all or a sharp corner with a stray arc floating
above it.

Trigger (snaporca-cq2): the only routes that ever reached confirm_op were
finishing the whole sketch and an unsignposted click on empty space.
set_tool() dropped a ready op, so typing a radius or dragging the arrow and
then touching any other tool threw the value away. Commit a ready op on tool
change (before m_mode is reassigned — op_ready() and confirm_op() both switch
on it), commit on Enter in the radius editor, and drop the pending op before
Esc's tool downgrade so Esc still cancels rather than applies.

Substitution (snaporca-pl5): libslvs writes its last Newton iterate into the
params whether or not it converged, and SketchSolver read them back
unconditionally, so every REJECTED solve deformed the sketch. The fillet
ladder tries a deliberately over-constrained rung first (a tangent on each
leg, against the legs' own H/V); it is correctly rejected, but its wreckage
then failed rungs 2 and 3, which solve cleanly on their own. The arc ended up
with no constraints at all, the rigid loop won, and the corner snapped shut.
Measured: from pristine geometry rung 1 gives result=INCONSISTENT with 3 bad
constraints, rung 2 gives dof=6 with the arc's radius intact.

Read the geometry back only on success. try_add_constraints then needs no
"restore" re-solve — the entities still hold the prior solved state.

Kernel suite 151 cases / 2072 assertions green on both forks; the GUI check
ran on the Snapmaker fork (9d72c4377a).

Ported from the Snapmaker fork. snaporca-pl5 snaporca-cq2
2026-07-26 23:33:50 +02:00
Tommaso Bianchi 50577d66d0 Dress-up card: say whether Confirm will round the picked edge or the group
The card decides between a single picked edge and a whole face-group from a
viewport pick it never mentioned. With no edge picked the user saw only the
group combo and concluded per-edge rounding did not exist; with an edge picked
the combo still read "All" — the opposite of what Confirm would do.

Adds a Target row that names the actual target and greys the group combo out
while an edge is picked, wired at the same three points Shell already uses:
the selection-changed handler, the re-edit load, and open_tool.

Ported from the Snapmaker fork (33771a0e95). snaporca-40d
2026-07-26 23:33:50 +02:00
SoftFever 1696d5ca39 Use the squared rib tower size in arrange estimates
estimate_wipe_tower_polygon reserved the arrange footprint and clamped the
tower X position with the raw prime_tower_width, under-reserving space
whenever the rib wall squares the tower to a different width.
2026-07-27 03:08:47 +08:00
SoftFever 466c36eaa3 Complete the rib wipe tower port in WipeTower2
The rib tower is now always square (prime_tower_width is ignored, as the
GUI already implies), carries the rib origin offset like the BBL tower so
the rib tips sit inside the configured position, clamps the rib length to
the tower diagonal, and extends the ribs for short towers.
2026-07-27 03:07:07 +08:00
SoftFever 5792fef805 Merge branch 'main' into feature/update_wipetower 2026-07-27 00:51:29 +08:00
SoftFever bc016af1c9 Fix post-slice self-invalidation on custom multi-extruder printers 2026-07-27 00:49:08 +08:00
SoftFever 7a378d2fc4 Sync WipeTower from BambuStudio(through ca1881761) 2026-07-27 00:48:44 +08:00
Tommaso Bianchi d5dee45acf Port the four DesignPanel fixes that never crossed from the Snapmaker fork
The CAD sources are meant to be byte-identical across the two forks, with exactly
two permitted divergences: DesignPanel.cpp's flyout plumbing (30 lines — mainline's
DropDown is Item-based where the other fork takes three parallel vectors) and
DesignCanvas.cpp's Bed3D::set_shape signature (16 lines). DesignPanel.cpp had drifted
to 105.

Nothing failed to announce this. The kernel suite does not compile the GUI, and all
four defects are interaction-level, so both forks stayed green while only one of them
had the fixes. The parity diff is what found it.

  * combo_append_index and its five call sites. Orca's ComboBox keeps client data in
    its own vector, so Append()'s clientData argument never reaches wxItemContainer
    and m_clientDataItemsType stays wxClientData_None. GetClientData() opens with a
    wxCHECK_MSG, which is an early return — so every read came back NULL and every
    caller resolved it to index 0, silently, because 0 is a legal answer. Affects the
    rib sketch picker, the sheet-body picker, both mate coordinate-system pickers and
    the sweep path picker.

  * Wrap(240) over the card labels. wxStaticText never wraps itself, so a one-sentence
    hint sets its card's minimum width to the width of the whole sentence and every
    control in that card is clipped at the sidebar's right edge.

  * Shell and Draft face-label initialisation in open_tool. Both read the face from the
    live pick at Confirm time, but only the pick handler wrote their labels, so picking
    a face and then opening the card left the card describing one operation while
    Confirm performed another.

  * SurfaceOffset and ThickenSurface added to build_candidate's negative list. Both read
    a sheet body from their own combo and both had it overwritten by the picked solid's
    index. The list is a negative one, so a tool that picks its own body breaks by
    omission — noted in a comment now.

Also drops a stray <cstdio> left behind by a debug probe.

Verified by building the GUI here for the first time since these landed: 461/461,
liblibslic3r_gui.a links. DesignPanel.cpp is back to exactly 30 divergent lines and
every other CAD file is byte-identical again.

snaporca-7xx snaporca-aqu snaporca-gu9 snaporca-c03 snaporca-5pl
2026-07-26 18:05:13 +02:00
Alexandre Folle de Menezes ed86efcf56 Improve and complement pt-BR translation (#14963) 2026-07-26 12:59:24 -03:00
Kris Austin 306d4b73ef test: stop littering the working directory with debug files (#14785) 2026-07-26 12:53:56 -03:00
SoftFever f60e0e776e Add back 02.03.00 plugin to support debugging (#14966)
* Add back 02.03.00 plugin to support debugging
2026-07-26 23:49:53 +08:00
Tommaso BianchiandClaude Opus 5 faed169a01 Sketch dimensions: make Tab commit, instead of silently dropping what you typed
The inline editor special-cased Escape and Skip()ped every other key, so Tab fell
through to wx's default navigation. Its popup frame holds exactly one control, so
focus came straight back to that control with its text re-selected.

Type 60, Tab, 40, Enter — expecting to fill two dimensions — and the 60 is gone: Tab
neither committed it nor advanced, so the 40 just replaced the re-selected text. A
re-selected field is pixel-identical to a freshly opened one, so nothing on screen says
a number was dropped. Tab-to-next-dimension is what Onshape, SolidWorks and Fusion do,
which is exactly why it is the key a user reaches for.

Tab now calls do_commit(), the same path Enter takes; the caller's on_commit is already
what walks to the next dimension. Verified by driving the GUI: 37 Tab 24 Enter now
produces a 37.0 x 24.0 rectangle, where before it produced 24 and a mouse-derived value.

snaporca-xah

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
2026-07-26 17:48:11 +02:00
Tommaso Bianchi 695932ad87 Drop the degenerate triangle OCCT emits at every filleted corner
A filleted solid arrived on the plate as a broken model: the slicer reported
"8 non-manifold edges" on an 80x50x12 box with r=3 on all edges, and advised
repairing it in another CAD application -- the exact round trip the Design tab
exists to remove. The same box without the fillet committed cleanly.

Measured rather than guessed. Each of the 8 bad edges is degenerate, both
endpoints the same vertex:

    open tri=145  edge=1 face=5 v59(3.000000 3.000000 0.000000) v59(3.000000 3.000000 0.000000)
    open tri=538  edge=1 face=6 v87(3.000000 3.000000 12.000000) v87(...)
    ... one per corner, 8 corners

OCCT triangulates a degenerate surface parameterization with a triangle at the
pole; a corner sphere patch has exactly one. Its two pole nodes are distinct in
the per-face triangulation and collapse to a single vertex when the faces are
welded, leaving a zero-area triangle whose v->v edge can never pair with a
neighbour. its_face_neighbors counts it as open, and the field the object panel
prints as "non-manifold edges" is in fact stats.open_edges.

So the geometry was never wrong -- the B-rep volume matches the Steiner formula
for a box dilated by a ball to 0.016%. Only the bookkeeping was.

Dropping those triangles after the weld removes 8 of 3492 and takes open_edges
to 0. Zero area, so nothing about the shape changes. tri_face is compacted in
the same pass, since it must stay index-aligned with the triangle list that the
face picking and per-body colouring both index into.

Guarded by a new [CadDocument] case that asserts open_edges == 0, no degenerate
triangle survives, and both per-triangle maps still match the triangle count.
The existing suite only ever checked B-rep volumes and areas, which is why a
mesh defect this visible went unnoticed: 150 cases / 2049 assertions green on
both forks.

snaporca-agw
2026-07-26 17:27:40 +02:00
SoftFever d84a211b91 fix crashes when switching bbl printers 2026-07-26 22:15:53 +08:00
Tommaso BianchiandClaude Opus 5 2c5ddd4102 OCCT link order: put TKFillet/TKOffset before their dependencies, not after
OCCT_LIBS is an explicit single-pass static link order — dependents first, TKernel
deliberately last. The CAD block appended its two extra toolkits to the END of that list,
which puts them after everything they depend on:

    list(APPEND OCCT_LIBS TKFillet TKOffset)

TKOffset references BRepAlgo_Loop, and nm against the built deps prefix shows TKBool is the
only toolkit that defines it (TKTopAlgo, TKBO, TKPrim, TKFillet and TKOffset all define it
zero times). TKBool sits first in the list, so a single-pass linker has passed it long before
it reaches the appended TKOffset and will not go back:

    libTKOffset.a(BRepOffset_MakeLoops.cxx.o): undefined reference to
    BRepAlgo_Loop::BRepAlgo_Loop()

Only one configuration ever objected — the Snapmaker fork Flatpak (aarch64). Ordinary Linux,
macOS and Windows links resolve it regardless, and the mainline fork Flatpaks pass, so six
green platform legs said nothing about whether this list was correct.

Prepended via set() rather than list(PREPEND), which needs CMake 3.15 while this project
supports 3.13.

Worth knowing for later: TKFillet and TKOffset are mutually dependent, 20 symbols needed in
each direction, so a stricter single-pass link could still trip on that pair. It does not on
any current platform, so no --start-group or duplicate entry is added here; if something ever
complains about ChFi or BRepFill symbols, that cycle is the reason.

snaporca-2kj

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
2026-07-26 15:28:46 +02:00
NoisyfoxandClaude 63044b7661 feat: Add layout debugging/inspecting tool (#14919)
* Add wxInspector dep

* Initial intergration of wxInspector

* docs: add wxInspector plugins design spec

Design spec for two wxInspector plugins (DPIAware + CustomWidgets) that expose
OrcaSlicer's custom control properties in the inspector property grid.

Covers: DPIAware scale-factor properties, Button, CheckBox, TextInput,
SwitchButton, ProgressBar, Label, and LabeledStaticBox.

* docs: add wxInspector plugins implementation plan

6-task plan covering: source changes to existing widget headers,
DPIAwarePlugin, CustomWidgetsPlugin, registration helper,
MainFrame/CMake wiring, and build verification.

* feat: add getters/setters for wxInspector plugin access

Add minimal public accessors to DPIAware (set_scale_factor,
set_prev_scale_factor, set_em_unit, force_rescale), Button
(GetStyle, GetType, IsSelected), CheckBox (IsHalfChecked),
TextInput (GetCornerRadius), and LabeledStaticBox
(GetCornerRadius, GetBorderWidth, GetBorderColor, GetScale).

* feat: add wxInspector plugin registration helper

Add RegisterOrcaInspectorPlugins() inline function that creates
and registers the DPIAwarePlugin and CustomWidgetsPlugin as
static instances (matching wxInspector's built-in pattern).

* feat: add DPIAware wxInspector plugin

Exposes DPI scaling properties (scale_factor, prev_scale_factor,
em_unit, normal_font, force_rescale) on DPIFrame and DPIDialog
widgets. Uses dynamic_cast for detection and a template helper
to capture the correct static type for lambda accessors.

* feat: add OrcaCustomWidgets wxInspector plugin

Exposes Orca-specific properties on 7 widget types:
- Button: Style, Type, Selected
- CheckBox: Half Checked
- TextInput: Label, Text Value, Corner Radius
- SwitchButton: Value
- ProgressBar: Proportion, Show Number
- Label: Is Hyperlink, Font Point Size
- LabeledStaticBox: Corner Radius, Border Width, Border Color, Scale

Each widget type uses dynamic_cast for safe detection.

* feat: wire wxInspector plugins into MainFrame and build

Call RegisterOrcaInspectorPlugins() in MainFrame constructor after
SetupInspectorAccelerator(). Add all 5 plugin source files to
SLIC3R_GUI_SOURCES in CMakeLists.txt.

* fix: move plugin registration to GUI_App::on_init_inner

Register plugins once in app init rather than in MainFrame
constructor, which may be recreated during the application
lifetime.

* fix: include plugin headers in Registration.hpp for complete types

Static locals require complete type. Include DPIAwarePlugin.hpp and
CustomWidgetsPlugin.hpp instead of forward-declaring. Also remove
unused include from MainFrame.cpp (registration moved to GUI_App).

* fix: qualify DPIFrame/DPIDialog with Slic3r::GUI namespace

* Make DPIDialog inspectable. For other dialogs, we will add them if necessary later.

* docs: add spec for moving wxInspectable into DPIAware template

Move wxInspector::wxInspectable base class from DPIDialog and MainFrame
into the common DPIAware<P> template, making all DPIAware widgets
automatically visible in the inspector tree.

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: add implementation plan for moving wxInspectable into DPIAware

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: update spec/plan — move SetupInspectorAccelerator into DPIAware too

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: move wxInspectable and SetupInspectorAccelerator into DPIAware

DPIAware<P> now inherits wxInspector::wxInspectable and calls
SetupInspectorAccelerator in its constructor, making all DPIAware
widgets automatically appear in the inspector tree with the
Ctrl+Shift+I shortcut. DPIDialog now uses 'using' to inherit the
constructor. Remove redundant wxInspectable inheritance and
SetupInspectorAccelerator calls from DPIDialog and MainFrame.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: use LB_HYPERLINK constant instead of magic number 0x0020

Co-Authored-By: Claude <noreply@anthropic.com>

* Clean up

* Fix Linux build

* Don't build wxInspector sample

* Use shallow clone

* Try fix flatpak build

* Attempt to fix build again

* Fix build failure caused by https://github.com/wxWidgets/wxWidgets/commit/436c16135ec7ddf580f44624bf74c592aae43b66

* wxWidgets build only download required submodules

* This should fix build on Windows on ARM

* Enable PIC

* Disable layout inspector by default for public release

* Use wxInspector 1.0.0 release

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-26 20:25:25 +08:00
Tommaso BianchiandClaude Opus 5 295c030309 DesignPanel.hpp: declare the three wx types it uses but never named
The header forward-declares a long list of wx types and omitted wxBoxSizer, wxTextCtrl and
wxListCtrl. All three are used as pointer members only (m_expr_text, m_var_list,
m_parts_hdr, m_hdr_tree_row), so a forward declaration is all they need — but there was
none. Every ordinary build compiled anyway because the wx/panel.h + wx/scrolwin.h chain
happens to pull the real headers in transitively.

The Snapmaker fork Flatpak build (aarch64) has a wx that does not, and it failed outright:

    DesignPanel.hpp:511: error: 'wxTextCtrl' does not name a type; did you mean 'wxTreeCtrl'?
    DesignPanel.hpp:521: error: 'wxListCtrl' does not name a type; did you mean 'wxFileCtrl'?
    DesignPanel.hpp:663: error: 'wxBoxSizer' does not name a type; did you mean 'wxSizer'?

plus a cascade of "m_var_list / m_expr_text / m_parts_hdr was not declared in this scope".
Not an environment quirk: the header was simply not self-contained, which is exactly what
breaks a reviewer building in an unfamiliar configuration. The mainline fork Flatpaks passed
on both arches, so only that one manifest exposed it.

Audited the rest of the header afterwards: every other wx pointer type is either
forward-declared or genuinely included — only wxScrolledWindow and wxWindow are undeclared,
and both come from the real wx/scrolwin.h include.

snaporca-4dn

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
2026-07-26 12:27:02 +02:00
Tommaso BianchiandClaude Opus 5 c1b0484495 3mf test: give the BBS save a writable temp dir, instead of the filesystem root
store_bbs_3mf reaches Model::get_backup_path(), which builds
temporary_dir() + "/orcaslicer_model/" + timestamp. temporary_dir() returns a file-static
that ONLY OrcaSlicer.cpp's startup sets, so in a test binary it is the empty string and the
backup path becomes "/orcaslicer_model/..." — absolute, at the filesystem root. An
unprivileged process cannot create that, so the save returned false and the scenario died
on REQUIRE(store_bbs_3mf(sp)).

This was the SINGLE failure in this fork's Unit Tests — 1 of 566, on Linux x86_64, Linux
aarch64 and macOS arm64 — from CI run 30191490709:

    Failed to create backup path "/orcaslicer_model/Sun_Jul_26/08_49_41#5398#1":
    boost::filesystem::create_directories: Permission denied [system:13]

It hid because that job had never run to completion on this branch before: every earlier
run was cancelled by the concurrency group first. It also passed on Windows x64, where the
drive-root path is writable, and it passes in the local build container, which runs as root.
Verified against the same defect in the Snapmaker fork by running the built binary as
uid 1000: permission denied before, 4 assertions passing after.

snaporca-vg8

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
2026-07-26 12:22:38 +02:00
Tommaso BianchiandClaude Opus 5 5026dd11a6 Fix the solver abort on circle-line tangency; the CAD suite now runs complete
snaporca-tkz, the last quarantined test. Root cause read out of the vendored
source rather than guessed: slvs/constrainteq.cpp, Type::ARC_LINE_TANGENT does

    ExprVector ap = SK.GetEntity(arc->point[other ? 2 : 1])->PointGetExprs();

so it dereferences the ARC'S ENDPOINTS. A full circle entity carries only
point[0], its centre. point[1] and point[2] are zero handles, FindById throws
"Cannot find handle", and the process ABORTS rather than failing the solve —
taking every later test in the binary with it. That is also the wrong equation
for a circle regardless: it only makes the line perpendicular to the radius at
an endpoint that does not exist.

CT::Tangent no longer hands a full circle to that constraint. For a circle it
emits PT_LINE_DISTANCE(centre, line) = radius, which is precisely what tangency
to a circle means. Arcs keep the ARC_LINE_TANGENT path they are built for.

One limitation, stated rather than buried: the slvs C API takes a constant
distance and offers no way to reference the circle's radius parameter, so the
radius is captured when the constraint is emitted. That is exact whenever the
radius is fixed or is simply not driven by another constraint in the same
solve, and re-solving restores tangency if something else moves it. Tying them
would need an auxiliary point constrained onto both the circle and the line.

With this and eeca6794e7, both quarantined tests are gone and the exclusion in
kernel-test.sh goes with them. A green run now means the whole CAD suite
passed, not "everything except the two we gave up on":

    149 cases / 2043 assertions, no filters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 09:33:50 +02:00
Tommaso BianchiandClaude Opus 5 f599ff0ff7 kernel-test.sh: one quarantined case now, not two
Follow-through from eeca6794e7. The header claimed two pre-existing failures
are excluded and named both; the internal-thread case now runs like any other,
so only the solver SIGABRT is left. A comment that lists a test which is no
longer excluded sends the next reader looking for something that is not there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 09:33:21 +02:00
Tommaso BianchiandClaude Opus 5 df6ef85614 Un-quarantine the internal-thread test: the geometry was right, the test was not
snaporca-kzy was filed as "internal thread cuts too little material". It does
not. Measured on the test's own fixture, a 40x40x20 box:

  plain Ø12 bore   removes 2261 mm3
  internal thread  removes 2157 = 1571 (minor bore) + 586 (groove)

apply_thread bores at the MINOR radius (radius - depth = 5) and then carves the
groove out to radius + depth = 7. A tapped hole therefore keeps the crests
between turns and holds MORE material than a plain clearance hole at the
nominal radius — which is what every real tapped hole does. The test asserted
the opposite, so it was asking for something physically wrong and had been
quarantined for it since it was written.

One hypothesis discarded on the way: that the shortfall was a tessellation
artefact, since chords on a helical surface undercut a concave bore. Exact
BRepGProp::VolumeProperties agreed with the tessellated volume to within
2.5 mm3, so that was not it and is not offered as a hedge.

The reference is now the tap-drill bore the thread actually starts from (Ø10),
against which the groove's 586 mm3 is the meaningful quantity — that is what
"the thread cuts" means. Test re-tagged [CadDocument][thread], so CI covers the
thread path again instead of skipping it.

Also documented the (void)internal in make_thread_profile. It reads like a bug
and is not: the V is the same shape either way and the caller decides, fusing
it onto a shaft or cutting it out of a wall. Someone "fixing" it to point
inward for the internal case would make the groove sweep already-empty bore
space and cut nothing — the exact failure the old comment described.

Suite 148 cases / 2035 assertions, with this test now among them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 09:32:47 +02:00
Tommaso BianchiandClaude Opus 5 a95e8ee701 Re-edit: list the bodies as of the feature's timeline slot, not the final ones
Found by sweeping the index-space defect class deliberately rather than by
hitting it: that class produced 4 of the 8 defects found by hand yesterday, so
it was worth auditing every combo in the panel that maps a row selection onto
a document index.

Most of it came back clean — the loft sidecar vectors are consistent at all
four read sites, the sheet-body pickers go through the helper everywhere, mate
connectors carry client data. Six did not. A stored target_body indexes the
body list AS IT WAS just before that feature ran during replay, but Transform,
Mirror, Thicken, Rib, Project and DeleteFace all populated their combo from
the live m_doc.bodies. Boolean and Cut already replayed to the right slot.

The failure is concrete: model a body, Thicken it, then Cut something later in
the tree. A Cut replaces one body with two, so every index at or after it
shifts. Reopen the Thicken and the combo lists the post-cut bodies while
selecting the pre-cut index — showing, and on confirm re-targeting, a
different body than the feature actually used. A Boolean that consumes its
tool body shifts them the other way for the same result.

fill_body_choice() does the truncated replay populate_body_choices() already
did, for the single-combo tools. Six call sites, and 60 lines of duplicated
population loops go with them.

Visible change when testing: re-editing an early feature now lists FEWER
bodies, because it lists only those that existed then. That is correct — you
cannot target a body that did not exist yet — and it is what Boolean and Cut
have always done.

The new kernel test pins the invariant the GUI now leans on: a Cut turns one
body into two, and replaying to just before it yields the earlier, shorter
list. If body ordering after a split ever changes, that assumption fails
loudly here instead of silently in a dialog.

NOT click-tested — GUI wiring, compile-verified only. Filed as snaporca-oz7
and added to snaporca-cfi.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 09:32:47 +02:00
Tommaso BianchiandClaude Opus 5 8f06b9dfd8 Design tab: make Cut re-editable, and stop misdescribing Import
on_edit_feature had two types falling into default: with "This feature type
can't be edited yet". Boolean was already handled, so the follow-up note was
stale on that point; the real gap was Cut and Import.

Cut now re-edits like any other feature: plane, offset and target body are
restored and the generic replace_feature path commits the change. The body
list is rebuilt with populate_body_choices(m_edit_index) for the same reason
Boolean does it — a Cut splits one body into two, so the live body list no
longer matches the one this feature's target index was recorded against.
Replaying to just before the feature makes the stored index land on the right
entry.

Import deliberately gets no dialog. An imported solid has no parameters to
re-edit: its geometry is rigid data read from a file, not something rebuilt
from numbers, and moving it is what the Transform feature already does.
Building an "edit" for it would duplicate Transform behind a second name. So
it now says that instead — the previous message implied a dialog was coming
that should not.

Imported 2D Text/SVG art is a different thing and stays re-editable; it
arrives as a Sketch feature carrying imported_regions and is handled above.

The default: arm is kept as a guard so a feature type added later announces
itself rather than silently swallowing the click.

NOT click-tested — GUI wiring, compile-verified only. Added to snaporca-cfi.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 09:32:47 +02:00
Tommaso BianchiandClaude Opus 5 9c199e4a38 Fix the Shellcheck CI job, red since the CAD kernel-test loop landed
The Shellcheck workflow has failed on every push and every scheduled run since
2026-07-24, on exactly one finding: SC2029 in scripts/kernel-test.sh, the file
the CAD branch added. So the CAD work is what turned that job red, and a PR
arriving with a red job is a bad way to open a conversation with a maintainer.

Client-side expansion of $REMOTE is the intended behaviour — it is derived from
$VOL locally and the remote has no such variable, exactly as the rsync
destination two lines down relies on. So this is a disable with a reason, not a
silencing: the note says why the warning does not apply.

Verified by running the workflow's own command over all 24 matched scripts:
exit 0, no findings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 08:46:35 +02:00
Tommaso BianchiandClaude Opus 5 e2b921745d Add user documentation for the Design tab
docs/design_tab_upstream_portability.md explains the subsystem to a
maintainer; nothing explained it to a user. This is that: what the tab is,
how to get a first solid out of it, every tool grouped the way the toolbar
groups them, and the keyboard shortcuts read out of the source rather than
remembered.

The limitations section is deliberate. Rib needing a sketch with an explicit
open line, Surface Loft and Surface Fill having no hands-on verification, mates
composing transforms instead of solving simultaneously, move-face and
replace-face being absent, and the two quarantined kernel tests are all things
a user would otherwise discover by hitting them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 08:44:27 +02:00
Tommaso BianchiandClaude Opus 5 f347afd22e Document the CAD subsystem's real dependency weight
Maintainers will ask what the Design tab costs before they will look at the
diff, so measure it rather than assert it.

The headline correction: the OCCT delta is THREE toolkits, not two. The
comment in deps/OCCT/OCCT.cmake claimed "TKFillet + TKOffset (3.77 MiB,
Windows only)". Walking OCCT's own adm/MODULES and each toolkit's EXTERNLIB
shows ModelingAlgorithms holds twelve toolkits, that eight of them are built
either way because DataExchange (the STEP path upstream already ships) depends
on them, and that the true delta is TKFeat, TKFillet, TKOffset and TKXMesh —
of which TKXMesh is never produced. So three archives are built: 7.40, 5.38
and 4.42 MiB.

TKFeat is the interesting one. Nothing in the Design tab references it and it
is absent from the TKFillet/TKOffset dependency closure, so it is built for
nothing — OCCT's module flag is all-or-nothing per module. On static-link
platforms that is build time and zero shipped bytes.

Two numbers are deliberately absent, marked as absent, and not approximated:
the Windows DLL delta needs a Windows build (snaporca-gix), and a clean-build
time delta needs the deps prefix built twice on one machine. The old 3.77 MiB
figure is withdrawn rather than reused — it covered two of the three toolkits.

Also recorded: the vendored solver is 9,339 lines under GPLv3 with its LICENSE
preserved, which combines into this AGPLv3 fork without difficulty (AGPLv3
§13), and it is live code driving every sketch constraint — not a carried
corpse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 08:41:50 +02:00
Tommaso BianchiandClaude Opus 5 e2b58a17f7 Design i18n: extract the tab's strings at all, and translate them into Italian
DesignPanel.cpp was never listed in localization/i18n/list.txt, and xgettext
extracts only what that file names. All 934 of the Design tab's _L() calls
were therefore invisible to every translator in every language — not merely
untranslated, unextractable. Adding the one line is the actual fix; the rest
follows from it.

Regenerating the .pot brings the catalogue from 6007 to 6536 msgids. 549 of
the new ones are now translated into Italian: 5230 to 5779 translated
messages. Verified no existing work was destroyed — every msgid that survived
into the new .pot kept its translation, and the 36 that lost one are genuinely
gone from the sources. msgfmt --check-format passes, which matters here
because a large share of these carry %d / %s / %zu.

CAD terms follow Italian CAD convention rather than literal glosses: Fillet ->
Raccordo, Chamfer -> Smusso, Draft -> Sformo, Rib -> Nervatura, Mate ->
Accoppiamento, Shell -> Svuotamento, Pattern -> Serie, Sheet body -> Corpo
superficie. Strings identical in both languages are deliberately left
untranslated so gettext falls back to the msgid.

The long mixed-filament / Local-Z dithering tooltips are left untranslated on
purpose: slicer internals, outside a Design i18n task, and untranslated before
this commit too.

One string changed rather than translated. The Coord Sys hint read "Without an
edge the frame's rotation about its normal follows world X, not the body" —
that described the defect fixed in 1726e93760, so it was a lie as of that
commit. It now says X comes from the face's first edge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 08:33:53 +02:00
Tommaso BianchiandClaude Opus 5 1726e93760 Mate connectors: derive a face-only frame's X from the face, not from world
datum_frame took a FaceAndDirection frame's Z from the face normal, which
follows the body, but its X from coordsys_x_hint, a world constant, whenever
no explicit edge reference was set. Spinning a body about its own face normal
therefore left the frame bit-identical: the connector could not encode that
rotation at all, so Fastened and Slider mates claimed to fix an orientation
the frame could not see.

X now comes from the face's own first usable edge, which rotates with the
body. The hint survives only as a last resort, for faces that offer no
in-plane direction — a full circular edge has coincident endpoints, and a
seam projects to nothing in-plane.

The new test spins a box 90 degrees about its top-face normal and asserts the
frame's X turned with it. Reverting just the X_tent derivation and rerunning
makes it fail with "1.0 is within 0.000001 of 0.0" — cos(angle) between the
before and after X is exactly 1, i.e. the frame did not move — and that is the
only failure in 2019 assertions, so the test discriminates this defect and
nothing else.

Note for anyone replaying an older document: a face-only connector's frame
can now differ from what that recipe produced before, so a mate built on one
may place its body differently. Nothing in the suite or the golden v3 fixture
changed, but the semantics did.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 08:18:42 +02:00
Tommaso BianchiandClaude Opus 5 f13f2876f6 Tag the two known-broken tests [NotWorking] so CI stops being red on every commit
This fork's Unit Tests job failed on every single commit, because CI runs the
whole ctest suite including the two cases tagged [known-broken] that
scripts/kernel-test.sh has always excluded locally. A job that is red
unconditionally is worse than no job: it trains everyone to ignore it, so the
next genuine regression arrives invisible.

No workflow change was needed. scripts/run_unit_tests.sh already passes
-LE NotWorking, and tests/CMakeLists.txt registers Catch2 tags as ctest labels
via catch_discover_tests(ADD_TAGS_AS_LABELS) — so the exclusion upstream
already ships works as soon as the cases carry the tag. Verified against the
built test tree: 337 tests unfiltered, 335 with -LE NotWorking, i.e. exactly
these two dropped and nothing else.

The second cause recorded in the issue, the test-reporter step failing with
"Resource not accessible by integration: 403" on a fork, is already fixed
upstream: the Publish Test Results step now carries continue-on-error: true.

The comment these cases carried claimed CI kept the bugs visible by reporting
them forever. That is now false and was never a good mechanism anyway, so
visibility moves to the tracker: snaporca-tkz for the solver SIGABRT, and
snaporca-kzy, filed now, for the thread groove volume. Neither is fixed;
neither is forgotten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 08:13:08 +02:00
Tommaso BianchiandClaude Opus 5 b25335e1b3 Rib: accept a Project feature as its sketch ref
Rib guarded with `sk.type != CadFeatureType::Sketch`, while every other
sketch consumer — Extrude, SurfaceExtrude, SurfaceRevolve, the loft paths —
tests `!= Sketch && != Project`. A Project feature carries a plane and Line
entities, which is all a rib reads, so the guard blocked "project a body
edge, then rib along it" for no stated reason.

The picker in the Design tab offered Sketch features only, so it is widened
to match: a kernel that accepts Project refs and a GUI that never lists them
would have left the path unreachable anyway.

Worth recording for whoever hits this next: Rib also needs a sketch carrying
EXPLICIT entities. A parametric Rectangle sketch (add_sketch with
width/height) has an empty entities vector — build_sketch_wire synthesises
its profile on demand — so rib_entity 0 is out of range there and it fails
with "rib: bad entity". That is why Rib could not be driven headlessly at
all before this change; a Project feature is now the one programmatic way to
produce a ribbable line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 07:48:16 +02:00
Tommaso BianchiandClaude Opus 5 1cb80f7f9f Project: implement "(all edges)"; stop discarding the failure reason
Two defects found while driving the tools that Phase B wired but nobody had
exercised yet.

apply_project had no all-edges branch: with no face picked and no explicit
edge list it threw "no edges or face selected". That is precisely the state
the Project card opens in, and its label reads "(all edges)" — so the card's
default could never be confirmed. It now projects every edge of the source
body. Edges perpendicular to the target plane collapse to a point when
projected, so segments whose endpoints coincide are dropped instead of being
emitted as zero-length lines that would poison the sketch downstream.

The second defect is why the first one was invisible. 29 of the 31 rollback
sites in McpControl ran `if (!ok) doc.undo();`, and undo() recomputes the
restored feature list — which succeeds and clears doc.error. Every failing
command therefore reported `error: ""`. Yesterday's fix covered 2 sites and I
treated the file as done; it was not. All 31 now capture the reason before
the rollback and restore it after. Failures that read as `""` now read as
"rib: bad entity" / "surface-revolve: revolve failed".

Verified on the running GUI through the control socket: the Project call that
previously returned ok:false now returns ok:true, and failures carry a reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 07:04:15 +02:00
Tommaso BianchiandClaude Opus 5 08e37296b9 Design tab: give every drawer entry a distinct icon
Six of the surface entries, both Thicken variants, Rib, Axis, Coord Sys,
Mate and Delete Face all reused a sibling's glyph, so a drawer opened as a
column of identical faces and the card that opened rarely matched the entry
clicked. Fixed both halves: entry icons are now unique within their drawer,
and each card header uses the icon of the entry that opens it.

Two new glyphs, design_thicken and design_rib, are the only ones added —
everywhere else an existing icon already carried the right meaning
(design_revolve, design_offset, design_line, design_point,
design_c_coincident, design_delete).

The Surface drawer BUTTON deliberately keeps design_surface; only its
entries may reuse the solid glyphs, because a menu row carries its own
text label while two adjacent toolbar buttons do not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 06:44:16 +02:00
Tommaso BianchiandClaude Opus 5 77f2f4d4ad Design tab: datum and curve tools were rejected by the solid-preview check
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>
2026-07-26 06:20:40 +02:00
Tommaso BianchiandClaude Opus 5 4d331099de CAD: Axis PlaneIntersection read its plane refs in the wrong index space
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>
2026-07-26 05:17:53 +02:00
Tommaso BianchiandClaude Opus 5 0455b0bf96 Design tab: give the Surface drawer its own icon
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>
2026-07-26 04:58:10 +02:00
Tommaso BianchiandClaude Opus 5 c565d6ba86 CAD: undo() never rolled back variables, so a bad one bricked the recipe
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>
2026-07-25 23:56:34 +02:00
Kris Austin 11fdb472d6 fix: hide the extruder switch on single-variant printers (#14930) 2026-07-25 18:46:55 -03:00
Tommaso BianchiandClaude Opus 5 33275f2c74 Design tab: sheet pickers targeted the wrong body; guard Delete Face's face list
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>
2026-07-25 23:46:14 +02:00
Kiss Lorand 68ce4da19f Fix overhang fan speed bugs (#14788) 2026-07-25 18:38:21 -03:00