It passed alone and failed inside scripts/ladder-all.sh, twice, on the same property: "right-click
with two picked -> None". The diagnostic it now prints said what a screenshot could not — editing=True
with an empty selection after two clicks that should have picked two entities. Three rig facts, all
about the DRIVER, none of them a product defect:
A LEFT-CLICK ON A LINE'S MIDDLE OPENS ITS LENGTH FIELD. The Select branch tests m_live_quotes before
it picks, with a ~24 px label tolerance, and a line's Length quote sits at its midpoint — so the
click that was meant to select it promoted a dimension and froze the canvas instead. Everything
after it landed on nothing. It bit only when the previous step had left that line selected, because
live quotes are drawn for the SELECTION: hence passing alone and failing in the gate. Lines are now
picked at 0.3 along, clear of the label.
A FIELD THAT HAS NOT OPENED YET READS LIKE ONE THAT NEVER WILL. The queue opens each field from a
CallAfter, so a driver that looks once, sees nothing and moves on gets frozen by the field that
arrives a moment later. keep_as_drawn() now waits for QUIET — two consecutive clear readings — and
draw_line_at drains stragglers before handing back.
A KEY CANNOT CANCEL A SESSION WHOSE CANVAS IS FROZEN. gui-ladder's enter_sketch dismisses the old
sketch with Escapes, which an open field swallows, so the session survived and the four calibration
probes landed in it on top of what was already there: "calibration expected 4 points, got 7". Every
rung now enters through fresh_sketch(), which cancels through the socket first — that cannot be
swallowed.
Measured after the fix, in the sequence that failed: gesture ladder 93/93 then offer ladder 108/108,
back to back on the same app.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrMzTpAf78U4NG2M8jfvHY
snaporca-ziam said gen_offer_table.py would silently delete the model-mode "Constrain sketch"
row, because that row lived in the generated header and not in tool_atlas.json. Running it found
more than that: FOUR rows existed only in the header — constrain, rename, and the three typed-
value rows sk_length / sk_radius / sk_angdist — and sk_delete's action had drifted, pointing the
sketch row at btn:delete, the FEATURE delete.
All five are now in the atlas, so the header regenerates byte-identically from it. Verbs may carry
a `note`, emitted as a C++ comment above the row: a rationale written into a generated file is
deleted by the next regeneration, which is how this started.
snaporca-z8rs (P1), found by making that true: after the atlas held all 92 verbs, the regenerated
header differed from the checked-in one by EXACTLY ONE LINE — kOfferVerbCount, 91 against 92.
Every consumer loops i < kOfferVerbCount, so the last row of the table was invisible: never listed
by show_offer_menu, never findable by mcp_run_verb. The verb that fell off the end is sk_angdist,
"Angle / distance…" — the typed-value row for a two-entity selection. On the one selection where
you would ask for the angle between two lines, the row that types it was not in the menu.
It survived because nothing compared the Sk2Ent menu against the table: sk_angdist accepts Sk2Ent
and nothing else, so an off-by-one that dropped the LAST verb was invisible from every other
selection. The vocabulary rung now covers Sk2Ent too, and picking the pair taught it one more
rig fact — shift-clicking a circle at its +X point grabs the RADIUS GRIP, which replaces the
selection with that one entity, so the pair silently collapsed to one and the offer answered
SkLine. Correctly, for the selection that actually existed.
gen_offer_table.py --check proves header == atlas and changes nothing; it is now the first step of
scripts/ladder-all.sh, and the only one that needs no rig.
Offer ladder 107/107, gesture ladder 93/93, both on the rig.
snaporca-ziam snaporca-z8rs
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrMzTpAf78U4NG2M8jfvHY
The 2D vocabulary is 46 verbs: 22 have a shortcut and the gesture ladder drives them, 24 have
none and nothing had ever exercised those. They are reachable only from the right-click offer, so
a key-driven ladder could not have touched them whatever it did. Four new rungs drive all 24, and
the coverage claim itself is now arithmetic against DesignOffer.hpp (rung O8) rather than a
sentence in a comment that rots when a verb is added.
The assertions are CONSTRUCTION invariants wherever a click cannot be exact — a regular polygon's
sides are equal to 1e-9 and its vertices lie on one circle; a tangent arc's radius at the shared
endpoint is perpendicular to the line to 1e-9 (measured cos 5.97e-17); the three clicks of a
3-point circle all lie on it; a circumscribed pentagon's circumradius is the inscribed one's over
cos(pi/5), 1.236067977 against 1.236067977. Where a value field opens, the typed value is graded
exactly: a moved line travels +25.000000000 in X and 0 in Y, a rotation turns 30.000000000 deg
and leaves the length alone, a scale multiplies it by exactly 3, a linear array's pitch is
[20.0, 20.0, 20.0] and a polar one's spokes are 60 deg apart all the way round.
Three defects found doing it, all fixed here:
snaporca-ua9g (P1) — delete_selected left three things behind. The AUTO-EDIT QUEUE, so a queued
field opened on a deleted entity and its commit went nowhere: draw a rounded rectangle, delete
everything, draw a 2-point circle, type 30 — the field opens, the digits are accepted, and the
radius stays 32.992020763. reset_autoedit() exists for exactly this and its own comment says so;
it was simply never called from here. The FEATURE GROUPS, whose [begin,end) ranges all shift on a
delete, so feature_of() answered with a group the user never drew — survivors are now remapped
and any group that lost a member is dropped, the rule the placed quotes already followed. And the
SOLVER STATE: no re-solve, so sketch_describe reported dof=16 for a document holding one circle.
snaporca-ekt9 (P2) — the read-back could not see three of its seven entity types. Ellipse,
EllipseArc and BSpline serialised as a bare type name: no centre, no semi-axes, no rotation, no
sweep, no poles. gui-ladder's ellipse rung had to grade the faceted area of the loop at 2e-2 —
that tolerance IS the faceting error — and its spline rung could only count entities. Now they
carry their parameters, and the ellipse arc's ends are asserted to satisfy (x/a)^2+(y/b)^2 = 1 to
1e-9.
Also read-only, and the reason the other two were found at all: sketch_describe now reports the
armed TOOL, the count of PENDING anchors, and whether a value field is EDITING. A menu walk that
lands one row off arms a neighbouring tool and then draws something plausible — the first run of
the authoring rung drew a circle of area 45238.93 and graded it as a rectangle. Every menu pick
now asserts which tool it armed, and the polyline rung (a per-segment Length field freezes the
canvas after every click) could only be written once the driver could ask whether a field was open.
Offer ladder 102/102 -> 105/105 with coverage. Gesture ladder 93/93 and the kernel suite
188 cases / 2532 assertions, both unchanged.
snaporca-ua9g snaporca-ekt9
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrMzTpAf78U4NG2M8jfvHY
The gesture ladder proved the TARGET — a complex closed profile, exact in vertices, lengths, arcs
and symmetry, voids correctly attributed. It proved it by arming every tool with a letter key,
which leaves the goal's own MECHANISM untested: the design logic pivots on right-click, and the
verbs offered are supposed to adapt to the element under the cursor. 47 of 86 Design-tab verbs
have a GUI action and no shortcut, so a key-driven ladder cannot reach more than half of them.
scripts/offer-ladder.py drives the menu. It asserts nothing from pixels: show_offer_menu emits an
[OFFER] trace from the same loop that builds the rows (behind the existing SNAPORCA_KEYTRACE), so
what the ladder reads cannot drift from what the user is shown, and the expected row set is
predicted by parsing DesignOffer.hpp rather than transcribed by hand. 25 properties, four rungs:
what each element type offers, that the menu equals the table for four selections AND that the
four differ, a 120 x 80 profile authored entirely through the menu, and a tool with no keyboard
route at all driven from the only door it has.
Two real defects, both found by it, both fixed here:
snaporca-ghcz (P1) — right-click was a black hole while any draw tool was armed. Every draw case
ended with `if (evt.RightDown()) { m_points.clear(); return true; }` and returned true even with
nothing to abandon; on_mouse records that in m_right_consumed and DesignCanvas suppresses the
offer whenever it is set. Measured: with Line armed, two right-clicks in a row produced no menu
and no tool change; only Escape freed it. Same rule snaporca-xmh6 wrote for the selection —
clearing nothing is not a gesture terminator. One shared right_abandon() now consumes the click
only when an anchor was really down; 16 sites, plus Polyline/BSpline (which end a chain, correct
only when there IS one) and Point (which has no anchor at all).
snaporca-lnri (P2) — right-clicking a sketch point offered the empty vocabulary. select_at_screen
tests hit_test_point first and records the hit in m_point_sel, but the offer counts m_selection
only, so a Point entity could never reach the entity branch and SkPoint was unreachable by
construction. A Point IS its own handle, so it is selected as an entity; other entities keep the
handle pick, since a line's endpoint is a drag target, not a vocabulary.
Offer ladder 25/25, gesture ladder 93/93 (no regression), both on the rig. The offer ladder joins
scripts/ladder-all.sh as the fifth rung.
snaporca-ghcz snaporca-lnri
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrMzTpAf78U4NG2M8jfvHY
Draw-then-edit is armed from a jump in the entity count: render() sees n > m_autoedit_seen,
selects the last entity and schedules open_primary_autoedit. A scripted add made while a
creation tool was armed looked exactly like a drawn gesture, so it opened that tool's value
field — and an open field freezes the canvas (on_mouse_impl returns early on m_awaiting_length)
and swallows every letter (in_text includes inline_busy()). Measured on the rig: after
sketch_add, 'p' + click added nothing (4 entities before, 4 after); one Escape and the identical
sequence gave 5. It also explains the selection = [last index] that sketch_describe reported
although action_sketch_add never selects anything — the render pass wrote it.
Escape worked because it sequences two set_tool calls: the pending CallAfter fires between them,
so the second one commits the field it finds open. Arming a tool directly is one call, and the
CallAfter fires after it.
Fix: resync m_autoedit_seen at the end of add_entities_scripted, so a scripted add is not read as
something the user just drew. An already-open field is left alone. Covers sketch_add,
sketch_mirror and sketch_offset — the three callers.
The scale rung's Escape workaround is deleted, which is the issue's acceptance criterion; it is
now the regression test. Gesture ladder 93/93 on the rig.
snaporca-j7gc
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrMzTpAf78U4NG2M8jfvHY
Over the whole 977-sheet corpus: 767 graded, 767 fully clean, 0 failures. The 210 skips are sheets whose part outline is not a closed stroked path at all (largest loop 5 to 132 mm2, measured), and one of them — MPD133 — is password-protected, which was being reported as an engine ERROR. Both now say what they are.
snaporca-j6sr
Two commits carried across (snaporca 579a9a9162, f68613cfc5).
Past about 480 entities a sketch had NO constraints at all and said nothing: libslvs
declares MAX_UNKNOWNS = 1024 and is handed every entity in the sketch at two params
per point, so the whole system came back TOO_MANY_UNKNOWNS and try_add_constraints
rolled the entire inferred batch back. From there no dimension could ever be applied.
Constraints only couple entities that share a point, so the solver now falls back —
only on TOO_MANY_UNKNOWNS — to solving connected components separately and committing
all-or-nothing. The auto-constraint pass batches its Horizontal/Vertical constraints
instead of one solve each, which is what kept the bulk path fast once solves started
succeeding: a 1204-entity load went 1585 ms -> 562 ms.
Plus the scale rungs (a thousand-entity plate drawn on by hand; the heaviest real
drawings graded and timed), the --step 1 fix that used to select nothing while
reporting a clean run, and scripts/ladder-all.sh as the one-command gate.
Parity 17 identical / 8 diverging as expected. Kernel suite here: 188 cases /
2532 assertions, including "a sketch past the solver's unknown limit still solves".
snaporca-yww4, snaporca-x6v7, snaporca-j6sr
and a ladder that draws with the mouse
Three commits carried across (snaporca 4ffd60eacb, 421055c2ec, b71216ce0b):
1. A design made only of sketches must survive being saved. CadDocument::recompute
returned false with "no solid-producing features" for a document that has no
solid, and two callers read that as "unusable": the GUI syncs the 3MF recipe
only after a successful recompute, so a sketch-only design was saved with no
recipe at all, and deserialize_recipe ends with `return recompute()`, so even a
project that carried one was refused on load. Having nothing to build is now a
success; a feature that MEANT to build a solid and produced none still fails.
DesignPanel::refresh_tree syncs the recipe too, for the paths that call
m_doc.recompute() directly.
2. Scripted geometry arrives exact. The Horizontal/Vertical inference window and
the endpoint weld window both close to zero for add_entities_scripted; void
attribution probes from a point strictly inside each loop instead of from its
first vertex. Corpus rung 39 graded / 39 fully clean, was 35 with 6 failures.
3. scripts/gui-ladder.py — 17 rungs, 84 properties, all driven by synthetic clicks
and typed values rather than through the socket.
Parity 17 identical / 8 diverging as expected. Kernel suite here: 188 cases /
2532 assertions.
snaporca-mtav, snaporca-8xg1, snaporca-5hvl, snaporca-730j
Carries snaporca 9f0a6656bc.
Mouse3DController::apply DRAINS the input queue and every BOUND canvas idles and calls it, but a
hidden canvas's render() early-returns on _is_shown_on_screen() — so it swallows motion, applies
it to the shared plater camera, and draws nothing. The next visible frame jumps by more than one
state change. The plater keeps exactly one of its three views bound; the Design canvas binds once
at construction and never unbinds, so two canvases drain the same queue.
Guarded at the apply site so only the canvas actually on screen takes motion off the queue,
whatever happens to be bound, and without touching the plater's view-switching state machine.
Reported by exussum12 on PR #15238 as lag and jerkiness in the Design tab against "really smooth
on the other tab"; he guessed the mechanism correctly. NOT verified with a device — there is no
SpaceMouse here and the rig has no HID, so this is a mechanism traced in source and matched to a
user's description, not a measurement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Carries snaporca 0231bd5b68. Parity holds: 17 files identical, 8 diverging as expected.
A reflection reverses orientation, so mirror_entities now hands the reflected half back reversed
in ORDER and flipped per ENTITY — an arc swapping its angles as well as its ends, a spline
reversing its control points. Appending it to the source then yields one walkable chain instead
of two halves meeting head-to-head, and a mirrored CCW loop stays CCW.
This is the producer half of the confusion that cost three defects; the consumers (offset, and
the exact loop area) keep their defensive handling, because that is what makes them correct for
hand-built and imported sketches rather than only for geometry this function produced.
Contract change, carried with the reason: a mirrored line's p0 is the reflection of the SOURCE's
p1, and a mirrored CCW arc keeps a POSITIVE sweep — the reflection negates it, walking it the
other way negates it again. Both [SketchEdit] cases updated, and a new [SketchProfile] case
"a mirrored half continues the original chain" pins the property directly.
Kernel here: all tests passed, 2687 assertions in 232 test cases. GUI target builds and links.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Carries snaporca 572f794c84, d0f9a0052a, 9f7e4e3627 and 3974f8a170. Parity holds: 17 files
identical, 8 diverging by their expected counts.
EXACT AREA. A loop's area is now integrated entity by entity in traversal order — Green's
theorem — instead of being shoelaced over the render polyline, which faceted every arc into 24
chords and lost 2.02 mm2 on a 3706.86 mm2 stadium. 0.054%, invisible on screen, and wrong in a
number reported as "the area".
OFFSET FOLLOWS THE TRAVERSAL. Offsetting a mirrored profile put one half on the wrong side and
split the loop in two, because the chainer only followed p1->p0 links and each entity's offset
side was taken from its stored direction. Chains are now orientation-aware, seeded at a free end,
offset by `reversed ? -d : d`, and normalised head-to-tail on the way out — so offset is correct
for any input ordering and its own output cannot reintroduce the problem.
Both are the same underlying lesson, which has now cost three separate defects: an entity's
STORED direction is not its direction of TRAVEL around the loop.
THE LADDER. scripts/sketch-ladder.py is a graded suite of 2D sketches judged the way a person
judges them — VERTEX, LENGTH, ARC, TANGENT, SYMMETRY, CLOSED — with area only as a cross-check,
because area is derived and nobody can confirm it by eye. Eight rungs from a rectangle up to
MPD5 from the StudyCadCam corpus, a dia 27 x 95 pin reproduced as its revolve half-profile with
the R5 fillet tangency solved exactly. Entirely 2D: no extrude or any solid feature.
Kernel here: all tests passed, 2681 assertions in 231 test cases, including the new
"profile: a mirrored half offsets as one loop, not two". GUI target builds and links.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Carries snaporca 95e59289f9, faec177d42 and 20df726ecb. Parity re-verified: 17 files identical,
8 diverging by their expected counts — DesignCanvas.cpp back to 16 and DesignPanel.cpp back to
32, which is the proof each hunk landed on the right side of the FeatFlyout and TAB_ID_PREPARE
divergences rather than on top of them.
All three answer exussum12's review on OrcaSlicer PR #15238.
ENTER/ESC IN THE VALUE FIELD. The field is a borderless always-on-top frame, and whether it may
hold keyboard focus is the platform's decision — a borderless NSWindow can never be key, and
mutter refuses a re-mapped window. When focus is denied the keys reach the panel instead and the
queued-dimension chain (a line queues Length then Angle) cannot be walked. The CHAR_HOOK now
forwards Enter/Numpad-Enter/Tab/Esc to the field when it is open and unfocused, and stays out of
the way when it is focused.
ESC FROM ANYWHERE. Separately and more simply: `dismissable` is false throughout sketch mode
because m_active is the FEATURE tool, so Esc fell through to whatever widget had focus. Click
any toolbar button or the Construction checkbox first and Esc did nothing at all — the likelier
reading of "Esc hardly ever works", and platform-independent. DesignCanvas exposes
request_sketch_exit() and the hook calls it whenever a sketch is live, after the inline-field
forwarding so an open field still takes Esc first.
RENAME. wxTR_EDIT_LABELS plus the two label-edit events write through to CadFeature::name and the
recipe, with a Rename verb in the offer and F2. The rebuild is deferred with CallAfter because
refresh_tree() destroys the very wxTreeItemId wx is holding during END_LABEL_EDIT — inline, it
killed the process.
STALE PICKS. set_tool now drops the Dimension tool's first pick, the Constrain picks and
m_point_sel, and delete_selected clears the pending dimension reference that could otherwise
dereference a renumbered entity.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Carries snaporca 39fac9b725. Parity re-verified: 17 files identical, 8 diverging by their
expected counts, DesignPanel.cpp still at 32 — the mirrored files were copied and the two
divergent ones patched hunk by hunk, so the counts returning to their expected values is the
proof each landed on the right side.
All 90 offer verbs are now firable by name over the socket, which matters because a deck key
can only send a keystroke and 49 of them have no shortcut at all. sketch_set_value calls the
same apply_dimension the in-canvas value field calls, so a typed dimension can be asserted with
no window manager in the way.
Three guards came with it, each confirmed against the source: on_mass_properties bounds-checks
m_sel_solid_body (it defaults to -1, and run_verb bypasses the menu grey-out that used to hide
that); sketch_set_value validates its value at the boundary because apply_dimension records a
driving constraint even for values it refused to apply; and run_verb refuses btn:/fly: verbs
that do not apply to the selection while leaving key: verbs alone, so the socket offers exactly
what the GUI offers. Dispatch is deferred through CallAfter so no modal verb can wedge the
socket thread.
GUI target builds and links against the rebuilt deps image.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Carries snaporca 971320e129, 6b049f0dc6, 4aae782029, 444d59f212, 74cf3d7e54 and the build
guards from 597557a6e4. Parity re-verified after every hunk: 17 files identical, 8 diverging by
their expected counts — DesignPanel.cpp still 32, DesignCanvas.cpp still 16, which is the proof
each hunk landed on the right side rather than being copied over a real divergence.
OFFSET OFFSETS THE CHAIN. Per-entity offsetting returned a closed rectangle as four parallel
segments that no longer touch, so entities_to_wires gave four OPEN wires and nothing could be
extruded. offset_entities now chains by shared endpoints and repairs each seam by mitering the
neighbours to their intersection. Second bug, invisible to any single-entity test: +d meant
"left of travel" for a line but "radius + d" for an arc regardless of sweep, so a slot outline
offset with its straights going one way and its caps the other. The convention is now written on
the declaration and pinned by a test.
tests/libslic3r/test_sketchprofile.cpp is new and asserts the LOOP rather than coordinates —
the property that decides whether a profile can be built, and the one the existing single-entity
[SketchEdit] cases cannot see. Its include is catch2/catch_all.hpp here: this fork ships Catch2
v3 while snaporca is on v2, which is why the test files are a tolerated divergence.
RIGHT-CLICK PICKS WHAT YOU POINTED AT, so a line's own verbs are offered instead of the
empty-selection vocabulary; sk_delete stops sharing btn:delete with the feature tree; and an
element's defining number (length / radius / diameter / angle / distance) can be typed, from the
menu or from V.
TWELVE MCP SKETCH VERBS. The socket had ~40 verbs and none touched a sketch, so the 2D layer
could only be exercised by driving a GUI with synthetic clicks. sketch_describe reports each
closed loop, the loops it encloses as voids, exact areas, and where a chain is still open;
sketch_validate/sketch_heal are FreeCAD's ValidateSketch — find vertices that overlap within a
tolerance but carry no coincidence, then weld them AND record the constraint, so a loop closed
by floating-point luck becomes one closed by construction. scripts/mcp-sketch-smoke.py is the
loop that asserts all of it.
Kernel suite on this fork: all tests passed, 2677 assertions in 230 test cases. The GUI target
links against the rebuilt deps image (the wxInspector blockage is gone) and the binary carries
the new verbs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Building orcacad-deps from this Dockerfile removes the snaporca-deps base, which is the point
(Trap 1: the old image's baked tree was project(Snapmaker_Orca)) — but that base was also where
Xvfb, openbox, xdotool and scrot came from. Without them gui-session.sh reports display DOWN and
orca-slicer dies with a trace trap on no display. Verified: the session now comes up with
display up, wm up, and the Untitled - OrcaSlicer window present.
Raises the bound from -j8 to -j12: the incident dump measured 1.17 GB average per cc1plus,
so 12 in flight is ~14 GB typical, well inside the 40 GB cgroup ceiling that is the real
guarantee. Keeps the file byte-identical to snaporca's copy, which the header requires.
Dockerfile.deps builds the deps with -j 12 for the same reason, and symlinks
deps/build/destdir -> deps/build/OrcaSlicer_dep: this tree installs under the latter name
while the orcacad_buildcache volume has the former baked as absolute paths in its CMakeCache.
Same tree, two names — without the link, one directory rename costs a full cold rebuild.
Both forks ran this script at once on 2026-08-21, each with ninja -j$(nproc)=16.
36 cc1plus held 42 GB of a 62 GB box, the kernel OOM-killed for 2h28m, ssh went
unreachable, lightdm was destroyed (2946 session kill events), and neither build
produced a single object file.
Three bounds, weakest to strongest: a flock on a path SHARED by both forks so
they serialise instead of summing; -j8 so the box stays usable while it
compiles; and --memory on the container, which is the actual guarantee — a
runaway build now dies inside its own cgroup instead of taking the host with it.
--memory-swap is pinned equal to --memory because swap thrash is what made ssh
hang rather than fail.
Kept byte-identical to the copy in the snaporca fork, as the header requires.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both were mouse-only: Commit to Plate is a toolbar button bound to wxEVT_BUTTON,
the bed is a CheckBox, and neither had an accelerator. That put them out of
reach of anything driving the keyboard, and out of reach of a hand that had not
already left the model to find them.
Ctrl+Shift, because the Shift+letter space is full to the last letter and
because the char hook deliberately ignores every Ctrl-combo -- which is exactly
what leaves this layer free to claim. P is Plate and B is Bed; neither collides
with OrcaSlicer own Ctrl+Shift+S (Save as) or Ctrl+Shift+G (Print plate), and
nothing else in the tree binds either.
The lookup goes ahead of the guard that drops Ctrl-combos, and nothing already
bound changes meaning: a plain Shift+letter still resolves as before, because
the new layer only answers when Ctrl is held as well.
The bed toggle drives the checkbox rather than the viewport alone, so the
control and the view cannot disagree about what is shown, and it says which it
did in the status line.
Both verified on the running build: Ctrl+Shift+B toggles the grid and the
checkbox together, Ctrl+Shift+P commits to Prepare.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reported on PR #15238: on macOS, drawing a corner rectangle and pressing Enter
on the first dimension opens the second field and then wedges the entire
application — no typing, no Escape, dead menu bar, no tab switching, and the
field stays composited over the desktop after minimising. That last detail is
what identifies it: an already-drawn window keeps being composited by the
WindowServer once the process stops answering. The main thread is stuck.
4c8c93b512 is the only commit that ever changed the second-queued-field path.
It stopped unmapping the value field between two queued dimensions, and its
whole justification is mutter: focus-stealing prevention refuses keyboard focus
to a window that was just re-mapped, which left the second field visible but
dead on GNOME (snaporca-p8uw). It was applied with no platform guard, so macOS
re-activates an already-visible borderless NSWindow at NSModalPanelWindowLevel
from inside wxOSX's pending-event drain — which on macOS runs from a
CFRunLoopObserver at kCFRunLoopBeforeTimers (evtloop_cf.cpp) over an unbounded
`while (!m_handlersWithPendingEvents.IsEmpty())` (appbase.cpp).
One constexpr now carries that choice, and both halves of the contract read it,
because the failure mode of this fix is the two halves drifting apart: open()'s
reuse-if-shown and do_commit()'s deferred hide are one decision, not two.
Not a macOS guess I could not check: the non-GTK branch was exercised on the
Linux rig by forcing the constant to false and rebuilding. A corner rectangle
drew, its first field committed at 60 mm, the SECOND field opened, committed at
40 mm, and the sketch ended at 60.0 x 40.0 mm with the field closed and no
freeze — so the map-afresh path is functionally complete, not merely different.
On GTK the constant is true and every generated instruction is unchanged.
What is still unproven is the exact line where macOS wedges; the reporter has
been asked for a `sample` of the hung process. This fixes the cause the evidence
points at without waiting for that, and cannot regress the GTK behaviour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review request on PR #15238: "Place CAD-related files (e.g. CadDocument/
GeometryEngine) into a separate folder."
src/libslic3r/CAD/ the kernel — CadDocument, GeometryEngine, the four
Sketch* units, SketchSolver, ThreadStandards
src/slic3r/GUI/CAD/ the tab — DesignPanel, DesignCanvas, DesignSketchTool,
SketchInlineEditor, McpControl, generated DesignOffer
Pure relocation: no line of logic changes. Two include rewrites follow from it —
files that moved re-spell their own neighbours against src/ (already on the
include path), and files that did not move pick up the new folder. docs and
docs/ux/mockups/gen_offer_table.py follow the same paths.
Verified: libslic3r, libslic3r_gui and libslic3r_tests all build, CAD suite green
at 2518 assertions in 194 test cases, and the sibling fork builds identically —
17 shared sources still byte-identical, 8 diverging by their expected counts.
Review request on PR #15238: "Move the SLVS to deps if the source code remains
unmodified. If any changes were made to the source code, move it to deps_src."
It is unmodified — all 20 files under src/libslic3r/slvs were byte-identical to
JacobStoren/SolveSpaceLib@4d87045, the extraction of solvespace.com's libslvs.
So deps/ it is, fetched by hash like every other dependency.
Only the CMakeLists is ours: upstream's builds a demo executable and installs
nothing, so deps/SLVS/CMakeLists.txt.in replaces it via PATCH_COMMAND — the same
shape deps/OpenCSG already uses. The public header keeps its spelling, so
SketchSolver.cpp still says `#include <slvs.h>` and needs no edit.
The CI deps cache is keyed on hashFiles('deps/**'), so it rebuilds itself.
Verified: dep_SLVS builds and installs, libslic3r links against SLVS::slvs, and
the CAD suite is unchanged at 2518 assertions in 194 test cases.
# Description
Break the recursive include dependency by moving GUI_App.hpp from
HMS.hpp into HMS.cpp. Add the required standard headers and use explicit
std/nlohmann types to remove reliance on transitive includes.
This prevents recursive header inclusion while preserving HMS
functionality.
# 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)
# Description
This PR enables building OrcaSlicer on Windows with the **clang-cl
(LLVM)**
toolchain and the **Ninja** generator, in addition to the existing MSVC
path.
Clang is already supported on Linux with this codebase, so this extends
that
support to Windows.
The changes fall into three categories. All are either no-ops on the
existing
MSVC/Visual Studio path or Windows/clang-cl-specific, so the standard
build is
not affected.
### 1. C++ conformance fixes
clang-cl is stricter than MSVC and rejects several constructs that
cl.exe
silently accepted. Each of these is non-conforming code that MSVC
tolerated:
* **Explicit template instantiation in `BoundingBox.cpp`** — clang-cl
does not
instantiate `BoundingBoxBase<Point, Points>::construct` through the same
transitive path MSVC uses; added the explicit instantiation.
* **Eigen cast materialization in `AABBTreeLines.hpp`** — `.cast<T>()`
returns a
lazy `CwiseUnaryOp`, not a concrete `Matrix`; materialized it before
passing to
`distance_to_squared`, which expects a concrete type.
* **`LabelItemType` underlying type in `PresetComboBoxes.hpp`** — gave
the enum
the same `std::size_t` underlying type as `Marker` to resolve a
narrowing
conversion in a switch.
* **`T2A_` cast in `BaseException.cpp`** — explicit `static_cast<const
char*>` on
the ATL conversion helper result.
* **Wide string literals in `GUI_App.cpp`** — used `L""` literals where
concatenated with a `std::wstring` (`url_prefix`).
### 2. Build system / dependencies
* **Exclude clang-cl from MSVC-only CMake guards** — `if(MSVC)` is true
for
clang-cl, so blocks applying cl.exe-only flags now exclude Clang.
* **Disable TBB LTCG** — oneTBB enables MSVC IPO/LTCG by default,
emitting
proprietary `/GL` bitcode objects that `lld-link` cannot consume.
Disabling IPO
produces native COFF, linkable by both `link.exe` and `lld-link`. TBB is
threading infrastructure, so the runtime impact of disabling LTCG is
negligible.
* **wxWidgets target path** — clang-cl uses the MSVC frontend variant on
Windows
but reports compiler id `Clang`, so wxWidgets looked under
`clang_x64_lib`
instead of the `vc_x64_lib` layout the deps are built with.
### 3. Ninja generator support
* **Runtime DLL copy** — the DLL copy step was nested under
`CMAKE_CONFIGURATION_TYPES` (multi-config only), so single-config Ninja
skipped
copying OCCT/GMP/MPFR/WebView2/freetype DLLs next to the executable. Now
runs
for both generator styles, guarded by `if(WIN32)`.
* **`build_release_vs.bat` Ninja target** — `ALL_BUILD` is a Visual
Studio
target; Ninja uses `all`. The script failed with
`ninja: error: unknown target 'ALL_BUILD'` when invoked with `-x`.
## Tests
Built from a clean checkout on Windows with:
* clang-cl 22 (LLVM toolchain bundled with Visual Studio 18)
* Ninja Multi-Config generator
* lld-link as the linker
The full build (deps + slicer) compiles, `OrcaSlicer.dll` links with
`lld-link`,
and `orca-slicer.exe` runs. Verified end-to-end by loading a model,
slicing it,
and generating valid G-code (screenshot below).
The existing MSVC / Visual Studio build path is unaffected — all changes
are
guarded by compiler/generator/platform checks or are conformance fixes
that
compile identically under MSVC.
# Screenshots
<img width="1919" height="1079" alt="image"
src="https://github.com/user-attachments/assets/02082437-db36-4696-a91a-d9acf57d4a52"
/>
Selecting the web Device tab loaded the printer's web UI from the selected discovered machine
when the preset carried no host. That arm was lost merging main into this branch — two of the
three Plater.cpp hunks from #15134 survived, this one did not — leaving the tab blank, since
PrinterWebView starts on an empty URL and nothing else navigates it.
In printer-agents mode the legacy web page was appended under Notebook::PAGE_MONITOR, which
resolves to the same "monitor" id as the native Device tab. FindPageByName returns the first
match, so PluginPages::relayout() — which saves the selection by name and restores it after
rebuilding the tab strip — moved the user off the web tab onto the native one. The tab also
disagreed with its own label, being created as "Device (legacy)" and renamed to "Device (Web)"
on the next show_device() call.
* Add /bot merge for delegated vendor profile maintainers
Vendor profile PRs no longer need a maintainer with repository write access: an
account listed in the FOLDER_MERGERS variable can squash-merge a PR confined to
the folders it owns by commenting /bot merge on it. Anything reaching outside
that grant, targeting a branch other than main or release/*, or missing a green
Check profiles run is declined with a comment naming the offending files.
Grants live in the merge-delegation environment, so only an admin can change who
may merge, and MERGE_BOT_DRY_RUN stops all merging without a code change.
Check profiles now also runs on release/* pull requests; nothing else changes
for existing contributors.
* Add profile version bump to the code review checklist
Without the bump in resources/profiles/<Vendor>.json, a preset change never
reaches existing installs over the air.
The last unbuilt element of snaporca-wgsc. Two connectors a mate binds are one
object with a gap still in it; drawn as two separate frames they read as
unrelated, and "which two of these five frames are the mate?" had no answer on
screen at all.
Dashed, grey, drawn IN WORLD along the segment joining the origins -- so it
foreshortens with the model and its length is the gap the mate has left to
close. A Fastened mate therefore draws nothing, which is correct: the gap is
zero. Screen-constant dash pitch (6 px dash, 4 px gap) like every other gizmo
here, with the pitch opening up beyond 400 dashes so a mate across a large
assembly cannot emit thousands of segments. Depth test off: the line's job is
to say "these two belong together", and it has to say it even when a part sits
between the camera and one end.
Fed from BOTH sources of polarity truth, the same two the role colours already
use: every committed Mate feature (connectors named by feature index), and the
live pick of an open Mate card (named by combo ordinal). Only resolved frames
are eligible, so an unresolved end draws no line rather than a line to the
origin of the world.
RIG-VERIFIED on :10 with the fresh binary (/OrcaSlicer/build/src/Release,
2026-08-17 12:43): two imported bodies, a face-and-direction connector on each,
Planar mate offset 40. Sampling the segment between the two origins gives a
regular dash/gap alternation of 13:10 sample units -- the 6:4 px pitch -- in the
stroke grey (107,117,133), over both solids. The grey end keeps its open collar
head and the blue end its filled one, so polarity and pair now read together.
Refs snaporca-wgsc