mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
e5659e0f0f9815f4e042702bb826ea3cb58f0728
30591
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e5659e0f0f |
The value field stops being a window, and now takes what is typed
Rebases the in-canvas work onto cad-mainline and finishes it. The field is drawn by ImGui inside the GL canvas instead of being a borderless top-level wxFrame. WHY THE FLOATING FRAME COULD NOT BE FIXED. Whether a borderless top-level may hold the keyboard is the window manager's decision, and it differs per desktop: openbox grants it, mutter refuses it, macOS denies key status outright. Seven workarounds fought that and one cost a macOS regression. Drawn inside the canvas there is no second top-level for anyone to refuse, so the question is never asked. The field is fed exactly like every other ImGui widget in the app — GLCanvas3D::on_char -> ImGuiWrapper::update_key_data -> io.AddInputCharacter. MEASURED, on behemoth: the click-edit ladder holds 28 checks — Line, Rectangle, Circle, Slot, Polygon, Ellipse, Arc, and click-to-edit on a placed dimension label — typing with NO click into the field first, committed == typed != prefill every time, and 27 [UX] imgui_char lines showing the characters arriving. WHAT WAS ACTUALLY WRONG. Not the field. The belief that "characters never reach the ImGui InputText" came from the harness: the ladder was delivering keys with `xdotool type --window` (XSendEvent), which GTK discards, so no build of any kind could have received them. The new probe in ImGuiWrapper::update_key_data — the one place ImGui is ever handed a character — is what separated that from a real defect, and it stays, because a canvas-side probe provably cannot answer the question: GLCanvas3D::on_char is bound later than any constructor-time probe, wx runs handlers in reverse bind order, and on_char returns without Skip(), so such a probe is silent whether or not the key arrived. A day was lost reading that silence as evidence. Also drops DesignPanel's content-based forwarder and DesignCanvas::inline_type_char. They were the right rule for a field that could not be focused; with the field inside the canvas there is nothing to forward, and keeping them would have masked whether the normal path works. STILL UNVERIFIED: behaviour under mutter itself. Neither focus-stealing-prevention WM available here survives long enough to judge — metacity SEGVs ~20s in and xfwm4 dies with BadWindow on SetInputFocus, both before the sketch opens and both unrelated to this field. The design's claim is structural rather than measured: no second top-level means no focus to refuse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA |
||
|
|
f6c551540e |
The key tracer had been printing one letter of the answer all along
focus= in [KEYTRACE] was never a class name. GetClassName() returns const wxChar* — wchar_t* in
this build — and the line cast that to const char* and printed it with %s, so it emitted the first
byte and stopped at the padding NUL. "wxGLCanvas" came out as "w". So did "wxWindow". Every focus
reading taken from this instrument for a whole day of diagnosis was a single character, and the
one question it existed to answer — WHICH widget has the keyboard — was the one it could not
answer. Printed through wxString now, and it says focus=wxGLCanvas: the canvas does hold wx focus
while the field is open, which removes the focus hypothesis for good.
Also here, and HONESTLY LABELLED AS INCONCLUSIVE: a wxEVT_CHAR probe on the canvas. It logged
nothing (cc=0), and the tempting reading is "the characters never reach the canvas". That reading
is not available, because the probe is bound in the DesignCanvas constructor BEFORE
GLCanvas3D::bind_event_handlers(), and wx runs the most recently bound handler first —
GLCanvas3D::on_char returns without Skip() exactly when ImGui consumes a character, which is
precisely the case under test. A silent probe is therefore consistent with ImGui consuming the
keys correctly AND with them never arriving. It measures nothing. Rebind it after
bind_event_handlers(), or instrument update_key_data itself, before believing anything about it.
Writing this down rather than acting on it: I came within one commit of "fixing" a mechanism I had
inferred from an instrument that could not see it, which is the same mistake as the focus= field
above and the same mistake that cost a whole session in September.
Deployed binary restored to
|
||
|
|
b45d675488 |
The frames now keep coming; the characters still do not
Third measured step, and the last one I will take without a second pair of eyes.
set_as_dirty() BEFORE Refresh(): GLCanvas3D's paint handler returns without rendering when the
canvas is not marked dirty, so the previous commit's bare Refresh() posted paint events that drew
nothing and the frames stopped anyway. With both halves the pump sustains, and the trace shows the
field holding the keyboard frame after frame:
[UX] frame want_text=1 want_kb=1 active=1 buf=158.74
[UX] frame want_text=1 want_kb=1 active=1 buf=158.74 (repeating)
STILL OPEN, and now narrowed to one question: buf never changes. ImGui owns the keyboard and our
InputText is the active item, so what is missing is upstream of ImGui — the characters are not
reaching io.AddInputCharacter at all. The next thing to MEASURE (not to change) is whether
wxEVT_CHAR arrives at the GL canvas in the Design tab: DesignPanel's wxEVT_CHAR_HOOK Skips digits
while inline_busy(), but Skip only helps if the focused widget is the canvas, and nothing has yet
proved that it is at the moment the keys are sent.
Three attempts have now gone into this one point. Per the standing rule that is where solo
iteration stops.
Deployed binary restored to
|
||
|
|
d82c8b59c2 |
The field now owns the keyboard; what it does not yet own is the characters
Measured, not reasoned. A per-frame trace of the ImGui state is what finally named the mechanism,
and it is a deadlock, not a focus problem:
[UX] frame want_text=0 want_kb=0 active=0 buf=158.74 <- frame 1: the widget is not active yet
[UX] frame want_text=0 want_kb=0 active=1 buf=158.74 <- frame 2: now it is
(nothing further) <- and the canvas stops
This canvas repaints ON DEMAND. ImGui decides whether it wants the keyboard at the END of a frame,
from the active item; GLCanvas3D::on_char only calls render() when update_key_data() says ImGui
wants it. So: no frames -> WantTextInput never turns on -> no render on a keystroke -> still no
frames. The characters sit in ImGui's input queue and the field is exactly as deaf as the window
it replaced, for a completely different reason.
request_frame breaks the circle, and the same trace says so:
[UX] frame want_text=1 want_kb=1 active=1
That is the first time in this file's history that the value field has owned the keyboard without
asking a window manager for it.
STILL OPEN: the typed characters do not reach the buffer (buf stays at the prefill) and the frames
stop after nine. The pump is the suspect — on software GL request_repaint() calls m_canvas->render()
SYNCHRONOUSLY, so this asks for a render from inside a render; it needs to schedule one instead.
That is the next thing to measure, not to guess.
The deployed binary on behemoth is restored to
|
||
|
|
5ab3072b9f |
WIP: the value field stops being a window — renders in-canvas, does not yet take keys
The decision (Tommaso's, put to him with the trade-offs): the field stops being a separate top-level window, because whether such a window may receive typing is the window manager's call and not ours. openbox grants it, mutter on his desktop refuses, and seven previous workarounds fought that — one of them causing a macOS regression, and the test harness ending up clicking the field before typing, which is a workaround no user can be asked to perform and is exactly the "label value not editable" report. DONE and proved on the rig: SketchInlineEditor is no longer a wxFrame + wxTextCtrl. It is state plus an ImGui overlay drawn by DesignSketchTool::render(), at the same screen anchor, in the same vocabulary as the dimension labels next to it (draw_dim_label is already an ImGui window). The field opens where it should — [UX] open title=Length prefill=158.74 from the running app. NOT DONE: typing does not reach it. ImGui is fed from GLCanvas3D's own key handler, so the keys have to arrive at the canvas; giving the canvas wx focus when the field opens was not enough. The remaining question is where a keystroke goes between DesignPanel's wxEVT_CHAR_HOOK and GLCanvas3D::on_char in the Design tab, and whether the canvas repaints often enough for ImGui to advance its input state. That is attempt three on this specific point, so it goes to a second opinion rather than a third guess. Also here: scripts/CAD/check-gui-click-edit.py, the ladder Tommaso asked for. It types into the field WITHOUT clicking it first — the click is what check-gui-sketching.py's focus_field() does and why that suite can never see this defect — and fails when the prefill is what gets committed. It currently fails, correctly, on the above. Not on cad-mainline: the deployed binary must stay the last good build until typing works. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA |
||
|
|
0fee4494e2 |
Never ask for activation with timestamp 0
present_toplevel() already asked for focus with a server timestamp, but only when
the widget happened to be realized. An unrealized widget has no GdkWindow, so
there was nothing to read a timestamp from and control fell through to
wxFrame::Raise() — which asks for activation with GDK_CURRENT_TIME, i.e. 0.
Zero is exactly what focus-stealing prevention discards. metacity says it out loud
when a sketch value field opens:
Buggy client sent a _NET_ACTIVE_WINDOW message with a timestamp of 0
and mutter, same lineage, refuses it silently on the user's desktop. That refusal
is the reported defect: the field is visible, never receives the keyboard, and
Enter commits the as-drawn prefill.
So realize the widget and retry, and do NOT fall back to Raise() on X11 — a
timestamp-0 activation is refused anyway, and on some window managers it only
marks the window as demanding attention.
Not yet confirmed end to end: both window managers with focus-stealing prevention
available here abort on this frame — metacity at frames.c:1239, xfwm4 with
BadWindow on SetInputFocus as the frame is destroyed under it — so the ladder
cannot yet return a trustworthy verdict under one. Two WMs crashing on the same
borderless, repeatedly re-mapped STAY_ON_TOP frame is its own signal about this
design. Tracked in projects-1p5 and projects-40m.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA
|
||
|
|
9134299233 |
Sketch value fields: content-based key arbiter + the gate that can judge it
The reported defect: sketch dimension labels are "not editable" — you draw a
rectangle, its Width field opens, you type, and the as-drawn number is committed
instead. It affects every sketch tool, not just the rounded rectangle.
WHAT THIS ADDS
1. The arbiter (DesignPanel CHAR_HOOK -> DesignCanvas::inline_type_char ->
SketchInlineEditor::type_char). Routes a key by what it IS, not by who the
window manager focused: digits, sign, decimal separator and Backspace/Delete
go to the open value field, Enter/Tab commit, letters stay tool shortcuts.
This is FreeCAD Sketcher's rule (DrawSketchKeyboardManager::
detectKeyboardEventHandlingMode), and the reason its sketcher behaves the same
on every desktop: it never asks who has focus.
2. The [UX] trace (SNAPORCA_UXTRACE) in SketchInlineEditor: open/commit/refused/
cancel, with the prefill and what the control actually held at Enter. It did
not exist — the ladder below was written against a surface no build emitted,
so it could only ever report "nothing opened". typed == prefill on a commit is
the defect's signature and nothing else makes it visible.
3. A draw-then-edit trace in DesignSketchTool: four early returns can swallow the
value-field chain and from outside they are indistinguishable.
4. scripts/CAD/check-gui-click-edit.py — types WITHOUT clicking the field, as a
person does, across Line/Rectangle/Circle/Slot/Polygon/Ellipse/Arc plus label
click-to-edit, and asserts committed == typed != prefill.
5. scripts/CAD/focus-loop.sh — sync/build/assert on behemoth. NOT the orcacad-gui
rig: its image pins deps 216 non-CAD files behind cad-mainline, so today's CAD
sources cannot build there without a deps rebuild.
WHAT IS PROVEN, AND WHAT IS NOT
Green under openbox: 28 checks, every tool, committed == typed != prefill.
But openbox CANNOT adjudicate this bug and the ladder says so in place. There the
field always wins the keyboard, so the same ladder also passes against a binary
with the arbiter compiled out — measured twice. Two ways of removing the keyboard
were tried and both are recorded as dead ends: XSetInputFocus loses to the field's
own re-focus CallAfter, and XSendEvent (xdotool --window) is dropped by GTK, which
made every run red regardless of the code.
Under metacity — same focus-stealing-prevention lineage as the user's mutter — the
mechanism appears in the WM's own log:
Buggy client sent a _NET_ACTIVE_WINDOW message with a timestamp of 0
That is the activation being refused, which is exactly the reported symptom.
present_toplevel() already asks for a server timestamp, so a path is still falling
through to frame->Raise(), which sends time 0. That is the next thing to fix, and
it is tracked; the arbiter alone does not close it. metacity also aborts on this
window (frames.c:1239), so the gate needs a WM that survives before it can return
a verdict.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA
|
||
|
|
af4bbe0217 |
A shape you selected whole had nothing left to click
"Still cannot edit labels in rounded rectangles." Reproduced on the rig in a few minutes, and it
is NOT the window-manager defect the rest of this week has been about — it happens on openbox,
where typing into a value field works perfectly. The value was never the problem. The LABEL was
not there.
render_live_quotes picks the entity to speak for like this:
else if (m_selection.size() == 1) ei = m_selection[0];
if (ei < 0 || ...) return;
A rounded rectangle is EIGHT entities — four lines and four arcs — so selecting the shape makes
m_selection.size() == 8 and the pass returns before drawing anything. Its Width, Height and fillet
Radius are live labels and nothing else, so with them gone there is no affordance at all: no
number to click, no field to open, no value to refuse. The rule hid the characteristic quotes for
precisely the shapes that have nothing but characteristic quotes.
A plain rectangle looked fine only by accident. Typing into its auto-edit chain creates a DRIVEN
dimension, which render_dimensions draws from the annotation list, so its labels survive. The
rounded rect's W/H/R go through set_rounded_rect, which rebuilds the geometry and leaves no
annotation behind. Same for slot, arc-slot and polygon: every grouped feature was in this hole.
A selection that is entirely ONE feature now speaks through any member. The switch below already
keys off feature_of(ei) rather than the entity, so nothing else had to change.
Measured on behemoth :10, before and after, same binary path:
before 8 selected -> no labels at all
after 8 selected -> R26.6 / 117.4 / 150.7 drawn; clicking R26.6 opens Radius prefilled 26.60;
typing 8 gives R8.0 mm and visibly sharper corners.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA
|
||
|
|
00d6c191dc |
The chip in the corner was holding the keyboard, and the planes were holding the bed
Two reports, three defects, all three measured on the running app rather than reasoned about.
"Keyboard focus in drawing tool is broken so that now they are slow and cumbersome." After a
dimensioned entity the bottom-right readout chip — 119x31, borderless, a wxFrame — held the X
input focus. Pressing r produced NO [KEYTRACE] line at all: the key never reached the panel's
CHAR_HOOK. One bare canvas click moved focus back to the main window and the identical key armed
the tool. So every shortcut was dead after every dimension, and the way to get the keyboard back
was to click somewhere harmless. That is the whole of "slow and cumbersome".
Its sibling, the status chip, is a wxPopupWindow for exactly this reason and carries a comment
warning against turning it back into a frame. The readout was left a frame on the premise that
"it appears mid-gesture and the next input is the mouse" — which the measurement falsifies: the
chip keeps the last value on screen after the gesture ends, and a frame that has the focus does
not give it back. It is now a popup too, with the placement and the iconise/deactivate lifecycle
its sibling already needed, because an override-redirect window would otherwise sit on the bare
desktop when the app is minimised.
"Planes hide the bed." Literally true, twice over. The reference planes were half-extent 0.6 *
the bed's larger side — a square 1.2x the plate — and all three are drawn with depth testing
off, so they painted over the plate grid from edge to edge. 0.3 puts them inside the bed, which
is also the Onshape look the size was reaching for: a modest square at the origin, not a
tablecloth.
And the other half was mine.
|
||
|
|
cf444a6ab6 |
A field that is logically closed can still be eating every key
Measured on the running app, not deduced. After a queued dimension chain the value field's frame is left MAPPED on purpose (mutter refuses keyboard focus to a re-mapped window), so there is a window in which m_open is already false and the frame is still on screen holding the X input focus. GTK meanwhile reports that window inactive and routes nothing into the text control. Every key then lands somewhere that cannot use it and will not give it back: [KEYTRACE] key=27 ui_mode=1 inline_busy=0 <- the last key the panel ever sees === MARK press Delete === <- no trace line at all xdotool getwindowfocus -> 0xe00404 86x60 <- the value field, still mapped Delete, Esc and typing all read as dead, which is exactly the report. And nothing could recover it: close(), cancel() and do_cancel() all return early on !m_open, so the one window still receiving keystrokes was also the one window no code could dismiss. is_mapped() asks the question the flag cannot answer, and dismiss() tears the frame down with no m_open guard, since m_open is precisely what lies in this state. Esc inside the field falls back to it — while the frame holds focus that handler is the only code the keyboard can still reach, so if it refuses, nothing else gets a turn. Every close now hands focus back to the canvas explicitly, because hiding a window does not move the X input focus off it. And inline_busy() reports the union of "a value is pending" and "a frame is mapped", so Esc routes to the field whenever one is on screen at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA |
||
|
|
3f52166e32 |
A sketch should not look like plate preparation
Three cues, because one is missed. A teal banner across the top of the viewport names the session
("Editing: Sketch N") and where its exits are; the printer bed is muted for the duration, since a
plate grid and a sketch grid are the same visual language and reading one as the other is how a
sketch gets drawn against the wrong reference; and N looks straight down the plane normal at the
current zoom, with the plane's own y axis as up, because no hand-orbit lands exactly square and a
sketch read at an angle is one whose right angles do not look like right angles.
The banner is an INDICATOR. Finish and Cancel stay on the single ribbon action bar — the tab had
three competing confirm surfaces once and that is not being reopened for a strip of colour. It
sits above the canvas rather than floating inside it: a child window over a wxGLCanvas is a native
window on GTK with no reliable stacking over GL, and being unmissable beats being clever.
The bed checkbox stays the stored preference and is restored on leaving the sketch; ticking it
mid-sketch still shows the bed, because that is a deliberate act and this is only a default.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA
|
||
|
|
324b558747 |
Esc is the safe key again: one press, one level, nothing destroyed
Two presses used to discard a live sketch. The key was answered in four places that could not see each other — the inline value field, a sketch branch, a feature-card branch, and the canvas — so a press aimed at one fell through to the next, and request_exit() carried a fourth layer that deliberately let the SECOND consecutive press through to cancel_sketch(). The warning it showed first did not help: the two presses are never one decision, the first is aimed at a field or a tool and the second at whatever was underneath it. The stack is now explicit. CadLevel (DesignInteraction.hpp) is four levels deep, the enum value IS the LIFO depth, and cad_escape_level() is a constexpr function over a POD of four booleans — so the ordering that is the entire contract is checked by static_assert at compile time, with no window, GL context or event loop. DesignPanel::escape() acts on the one level escape_level() names and on no other, and every Esc in the tab routes through it. The destructive layer is gone from request_exit() itself rather than guarded at its callers, so the guarantee cannot be re-opened by adding a route: a session holding geometry is left only through Finish (keep) or Cancel (discard). Cancel now asks before discarding — it used to refuse and tell the user to press the button they had just pressed, which meant a drawn sketch could be kept but never thrown away. Right-click also stops rewarding navigation with a menu: the offer needs BOTH budgets, released within 200 ms and moved no more than 3 px, and the raycast uses the press position, so the menu describes what was pointed at rather than where the camera stopped. Two budgets because drift alone still popped a menu at the end of a slow, careful orbit. docs/ux/interaction-model.md carries the state machine, the routing and the transition table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA |
||
|
|
bb6a1810f6 |
A stray click must not break a model that looks perfect on screen
Revolve failed on a sketch whose profile was closed. Decoding the reported 3mf: four
entities forming a proper closed loop (joints open by 4.44e-06 mm, well inside
tolerance) plus one stray 1.82 mm Line at (-24.2, 80.3), inside the shaded region,
touching nothing.
The viewport's region_loops discards open chains ON PURPOSE — it exists to find
EXTRUDABLE regions — so the user saw one clean closed region. entities_to_wires kept
the stray as its own one-edge loop, so it returned two wires, and Revolve goes through
entities_to_wire which demands exactly one. Extrude would have failed one step later in
wires_to_face, because a one-edge open wire bounds no face. Same class as the tolerance
split fixed in
|
||
|
|
8b568b7b9d |
The viewport and the kernel now answer "is this joint closed?" with one number
Tommaso asked the question that names the real defect: if the sketch was open, why was the same sketch shaded closed and offered for extrude? Because the two halves used different tolerances. region_loops shades a region closed at 1e-3 mm; connected_loop chained at 1e-3; the kernel welded at 1e-4 and OCCT matched vertices at 1e-7. The 2.28e-5 mm gap in the reported sketch did not cause that disagreement, it only made it visible — and fixing the gap alone would have left the contradiction in place, ready to reappear anywhere in (1e-4, 1e-3]. kSketchJoinTol now lives in SketchEngine.hpp and is the only place the number exists. region_loops, loop_report, connected_loop and entities_to_wires all read it through sketch_join_tol(). The viewport cannot promise a region the kernel refuses to build. The welding is optional, because a kernel that silently closes loops should let you say no: "Auto-close sketch loops" in Preferences, default ON, no restart. OFF means only exactly coincident endpoints join — and since both halves read the same value, the viewport simply stops shading the region closed, so an open loop is visible rather than welded behind your back. No separate UI needed for that; it falls out of sharing one number. Details that matter. The kernel defaults to auto-close ON independently of the GUI, so headless and MCP callers behave like the viewport instead of inheriting an unset preference. With the tolerance at 0 the comparisons become <=, because OFF must mean exact, not broken. OCCT never receives a zero vertex tolerance — it is clamped to Precision::Confusion. The preference is pushed from EVERY entry that starts a sketch session, not just begin(): a Constrain session enters through begin_constrain / begin_constrain_entities and uses region_loops and connected_loop, so a single push site would have left those sessions running on whatever the previous one set. begin_imported_transform is excluded deliberately — it works on imported regions, not chained entities. Tests: a loop with one joint open by 9e-4 mm, given out of traversal order, builds a closed four-edge wire; with auto-close off the same loop yields no wire; and an exactly closed loop still builds with auto-close off, proving OFF means exact. Kernel 66104 assertions / 606 cases green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA |
||
|
|
faf4406f89 |
A wire that lost two edges still called itself done
An extrude built a solid the user never drew: three sides of the handle plus the arc
that bulges outside the outline, with the bowl's second arc and the left edge missing.
Two faults met. entities_to_wires added edges in ENTITY-CREATION order, so a partial
wire rejects the next edge even when the sketch closes perfectly; and one joint of the
reported sketch is open by 2.28e-5 mm, wider than OCCT's 1e-7 vertex tolerance and
wider than this function's own EPS of 1e-6, so that edge was refused on geometry too.
Neither showed up, because BRepLib_MakeWire::Add DROPS a disconnected edge
(BRepLib_DisconnectedWire + NotDone) while every successful Add ends with
BRepLib_WireDone + Done() — overwriting the failure. `if (!wm.IsDone()) return {}`
was therefore asking only whether the LAST edge connected. Six edges in, four out,
IsDone() true.
Endpoints now weld into shared nodes at one tolerance (kSketchWeldTol) used by BOTH
the union-find grouping and the wire build — they disagreed before, which is how a
joint gets united into a loop and then refused by the builder. Each node becomes ONE
TopoDS_Vertex, so the builder matches on identity instead of proximity, with the
vertex tolerance widened because BRepLib_MakeEdge::Init projects a vertex onto the
curve within that tolerance and a welded node sits up to the weld gap off its
neighbour's curve. Members are then walked in traversal order. Finally the result is
counted: IsDone() alone is not evidence, edge_count == members.size() is.
Arc geometry is untouched — the midpoint from (start_angle+end_angle)/2 and the
solver's angle reflow both measured correct and were never part of this.
The regression case carries the reported sketch verbatim, open joint included. It
fails 4 == 6 without the fix, which was measured, not assumed. Kernel 66092
assertions / 604 cases green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA
|
||
|
|
9858080aa0 |
The constraint list follows you into a live sketch
The rows, their ✗ buttons and the click-to-highlight were all built against m_doc.features[m_constrain_feat].entity_constraints — a COMMITTED feature. A live sketch has no committed feature, so the card was hidden for the whole session and the list it would have shown was empty by construction. Every constraint applied while drawing was nameless: the badge said one existed, nothing said which. rebuild_constraint_list now picks its source by scope. live_constraint_scope() is the same discriminator apply_constraint already used to route to apply_live_constraint — both Constrain modes set m_active, so is_sketching() alone would claim the live scope while the committed manager is open. delete_constraint and highlight_constraint_entities branch on it too, and the card shows in Sketch mode as well as Constrain. Keeping the rows in step needed a signal that did not exist: on_solve_state fires on every frame of a drag, so rebuilding from it would rebuild the list continuously. The tool now fires on_constraints_changed only when the constraint SET changes — one added by try_add_constraints, one removed by remove_constraint (the indexed form the badge click and the ✗ row now share). The rebuild is deferred through CallAfter. One of its callers is the ✗ button's own click handler, and rebuild_constraint_list destroys those buttons: deleting the window whose handler is still on the stack is a use-after-free. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA |
||
|
|
b250a2b858 |
Tell the user the badge is a button, and refresh the DoF when one is deleted
A glyph reads as decoration until something says otherwise, so the badges shipped
last commit were discoverable only by accident. Two places now say it, chosen because
they are where the eye already is:
- the moment of applying, which is the one the user is watching ("Applied constraint ·
its badge is on the sketch — click the badge to remove it"). The hint line could not
carry this alone: it only refreshes when the (mode, step, picks) tuple changes, and
applying a constraint changes none of them.
- the Select-mode hint line, appended only while the live sketch actually holds a
constraint, so it never advertises a badge that is not on screen.
Also fixes what the previous commit got wrong: remove_constraint_near solved through
solve_sketch_entities directly, which relaxes the geometry but leaves m_dof and the
per-entity conflict flags untouched and never fires on_solve_state. Deleting a
constraint therefore left the DoF readout describing the system as it was BEFORE the
deletion, and any red over-constrained tint stranded on screen. It goes through
resolve_live() now, the same path every other live edit uses.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA
|
||
|
|
3da0af38c3 |
Constraints you apply while sketching are finally visible, and clicking one removes it
The constraint badges existed and had never once been drawn where they were needed. build_constraint_glyphs read m_constrain_cons, a vector only the COMMITTED-feature Constrain mode fills, and the draw call sat inside `if (m_mode == Mode::Constrain)`. Every constraint applied during a live sketch — which is the path the Constrain buttons take while drawing, the one added in "Constrain while you sketch" — went into m_constraints and was rendered by nothing. You could not see that Parallel had applied, so "nothing happens" was indistinguishable from "applied and invisible". The glyph builder now takes its constraint list as a parameter: Constrain mode passes m_constrain_cons as before, the live session passes its own m_constraints. Same glyphs, same teal. Seeing them is half of it. A constraint's entire state is exists / does not exist, so the toggle is a delete, and there was no way to reach one during a session — the ✗ rows in the panel list are bound to the committed feature. Each badge now records where it landed (m_glyph_hits) and a plain left click in Select mode within its cell drops that constraint and re-solves. Shift/Ctrl clicks are left alone so multi-select still works. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA |
||
|
|
ea32f8dc2f |
A selected sketch line stops being white, and a refused constraint says why
Two reports, one root: the sketch tab could not show what was selected. The bed grid landed last commit, and selection was painted pure white — a freshly drawn line is auto-selected by the creation tool, so the first thing a new line did was disappear into the grid. Selection now wears design_selection_color(), the same cyan a picked solid already wears. White is kept for the hover handle alone. That invisibility is also why "I apply Parallel and NOTHING HAPPENS": drawing two lines leaves exactly ONE selected (the last), Parallel needs two, so the planner correctly refused — but the status text named only the requirement, never the current pick, which reads as a dead button. It now reports how many are selected and how to pick the second. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA |
||
|
|
741682f874 |
The Design tab gets a grid a modeller can read, centred on the origin
In CAD the centre IS the sketch origin -- GLCanvas3D already moves the axis
triad there for exactly that reason. The grid under it did not agree: it comes
from PartPlate::calc_gridlines, generated from m_origin, the plate's front-left
corner, with an adaptive step meant for a print bed.
Measured on a screenshot from the user's machine: the nearest grid line was 10 px
from the origin in a 23 px pitch. The origin floated mid-cell, in both axes.
Corner-origin is CORRECT for Prepare -- a print bed starts at a corner -- and the
plate list is SHARED with the plater, so re-centring it there would change the
bed for every user of the app to fix one tab. The seam used instead already
existed: _render_platelist takes show_grid, and m_axes_at_bed_center is already
the "this is the Design canvas" flag. The Design canvas suppresses the plate's
grid and draws its own.
Minor every 10 mm, major every 50 mm, both generated from bed_center() so a line
passes exactly THROUGH the origin in each axis. Two GLModels, rebuilt only when
the bed shape changes, not per frame.
White majors, grey minors, the SAME in both themes. There is no white bed to
vanish against: the plate is dark grey either way (DEFAULT_MODEL_COLOR
{0.326,0.337,0.337} light, DEFAULT_MODEL_COLOR_DARK {0.255,0.255,0.283} dark),
a difference of 0.07. An earlier draft inverted the palette on the light theme;
that was a branch buying nothing. For contrast with what this replaces: the
plate's grid draws BOTH its thin and bold families in one 0.43 grey, which is
most of why the stock grid reads as a flat mesh with no scale to it.
z = -0.26, the same value as PartPlate::GROUND_Z_GRIDLINE -- below the bed fill
at -0.03, above the bed model at -0.41, so no z-fighting. Matched by
construction, since that constant is file-static in another TU.
Known limit, commented: the grid is clipped to the bed's BOUNDING BOX, not its
polygon. Identical on a rectangular bed; on a circular one it would spill past
the round edge. The target printers are rectangular.
Verified on the rig, not just compiled: white majors over a fine grey mesh, and
a white line through the origin in both axes.
snaporca-kha0
|
||
|
|
8b9ff36685 |
The eleven constraint buttons no rung had ever pressed
Twenty buttons on the CONSTRAIN bar, nine of them exercised. The other eleven were "implemented" in the sense that the kernel builds the right def for them -- which is exactly what was true of Parallel yesterday morning, right up until a user pressed it and got nothing. What a kernel test cannot see is whether the BUTTON is wired to the index its name claims. CON_BTN is 449 + 42*i over a hand-written name list, and it has drifted once already: six buttons were inserted, everything from index 6 on pointed at the wrong control, and nothing caught it for months because no rung pressed past index 5. D13 presses index 6; D14 through D22 press 8 to 19. The map turned out to be intact, which is worth knowing rather than assuming. D12 vertical, D13 equal_radius (its own button, not Equal's promotion), D14 concentric, D15 tangent, D16 midpoint, D17 symmetric (the three-pick form), D18 sym_h, D19 radius, D20 diameter, D21 fix, D22 dist_y. Three are shaped around a specific way the code could be wrong rather than around "does something happen": D20 exists for a factor of two. Diameter wired to the Radius handler gives r = 30 for a typed 30, and nothing on screen looks wrong. D21 -- Fix alone is unfalsifiable: nothing moved, so nothing proves the constraint exists. It only becomes observable when a SECOND constraint would otherwise move the fixed point, so the rung drives the pair to a 70 mm gap and checks which end travels. D14 asserts the radii did NOT change. Concentric is about centres; a solve that also equalised the radii would pass a naive check. D17 failed on its first run and the rung was wrong, not the app: all three entities are free, so the solver is entitled to satisfy the mirror by moving the AXIS instead of the points -- and it did, landing the pair symmetric about x = -8.18. It now measures signed perpendicular distance to the axis where the axis actually is, which is the stronger property anyway. Coverage: 20/20 buttons pressed, up from 9. Ladder 135 -> 177 properties across 43 rungs, all holding. snaporca-l2vm |
||
|
|
8f3f835636 |
Constrain while you sketch, and stop losing work to Esc and to invisible points
Five defects from ten minutes of real use, and the mode split behind the worst of them. One commit because the changes overlap in the same functions; the pieces are separable in the diff, not in the file. CONSTRAINING NO LONGER NEEDS A COMMITTED SKETCH. A constraint could only be applied by committing the sketch, selecting it in the feature tree, pressing the padlock, and only then picking. While drawing, the CONSTRAIN toolbar was not even on screen (set_ui_mode showed it in UiMode::Constrain alone) and apply_constraint answered "Press Constrain on a sketch first" -- in a status line nobody looks at. Draw two lines, press Parallel, get nothing: that is what a user reported as "the UX is a mess", and they were right. apply_constraint now takes the live session first, reading the picks from the selection model the sketch tool already had (click, ctrl-click to extend, double-click for the loop) and applying through try_add_constraints, which already did append -> solve -> keep-or-rollback. The committed Constrain path stays for editing an old sketch; it is no longer the only way in. The twenty constraint buttons now show in Sketch mode as well. The discriminator is is_sketching() && !is_constraining() && !is_constraining_entities(). Both begin_constrain and begin_constrain_entities set m_active, so is_sketching() alone is true DURING a constrain session and the new path would hijack the old one -- compiling perfectly and failing in behaviour. ONE PLANNER, NOT TWO. A second caller meant duplicating the logic that decides whether a constraint is legal, which roles it binds and whether it needs a typed value. That duplication is how today's Coincident bug survived: fixed in one branch, alive in the next one down. plan_entity_constraint() now lives in the kernel -- pure, no wx, no translation -- and both UI paths call it. DesignPanel loses 304 lines and gains 155. Being in the kernel makes it TESTABLE. The Parallel defect existed because a constraint type met an entity type nobody had tried, and the only instrument was a 13-minute GUI ladder. 19 new kernel cases cover the matrix: 264 -> 283 cases, 7648 -> 7867 assertions. Parallel, Perpendicular and EqualLength gain the two-line guard they never had. On non-lines they used to emit a def the solver silently dropped -- the sketch reported itself constrained when it was not, the same class as Horizontal on a Point. EqualLength on two rounds still promotes to EqualRadius first. Symmetric is planned completely, including its axis pick: the plan carries a VECTOR of defs because Symmetric on two lines is two constraints (P0/P0 and P1/P1). A single def would have half-applied it -- one end pinned, one free, looking correct until something moves. A PLACED POINT SURVIVES THE COMMIT. Type::Point was created correctly and never drawn once committed: both renderers skip it, correctly, since entity_polyline gives a point nothing. What was missing is the vertex-marker path the live session already used. rung_point passed throughout because it asserts the document, and the point was always in the document -- the pixels lied. ESC STOPS EATING AN UNSAVED SKETCH. The third press reached cancel_sketch(), clearing m_entities with no warning and nothing to undo. live_sketch_has_work() existed and was never consulted. The exit layer refuses once when there is work and lets a second consecutive Esc through; the refusal re-arms on a button press, never on mouse motion, or Esc could never exit while the hand moves. TWO NEW RUNGS. D10 drives Parallel through the committed path -- it passes on the PRE-fix binary, which is how we know the user's failure was the mode and not the constraint. D11 is the acceptance for the collapse: draw, pick both, press Parallel, no commit and no padlock. Ladder 126 -> 135 properties, all holding. CON_BTN_SKETCH is measured, not derived: in Sketch mode the group renders after the sketch toolbar, so the first button is at 677, not 449. Pitch 42, twenty buttons, read off a screenshot. Deriving it by offset is how that table drifted the last time. Known limit, commented at the call site: a constraint added to a LIVE sketch is not on the document undo stack, so Ctrl+Z will not take it back until the sketch is committed. snaporca-itp4, snaporca-oyhx, snaporca-l2vm |
||
|
|
cd30fb891e |
the same phantom-endpoint bug in Coincident, and the two unguarded branches next to it
Port of snaporca da8d011b87; parity holds (DesignPanel.cpp still exactly 32 divergent
lines). Verified independently on this fork's own rig: full ladder 126/126 against
BuildID 96c697a3, built from this tree.
Reviewing the DistanceX/Y fix for OTHER members of its class found three more live defects
on the constrain toolbar. All four share one root: a branch assumes every picked entity has
two endpoints, and the solver's refusal to resolve a role it cannot find is silent.
COINCIDENT had the identical closest-pair walk over {P0,p0},{P1,p1}. For two Points the
phantom (0,0) pair sits at distance 0, which is the smallest distance there is, so it
ALWAYS won: ptOf(Point,P1) -> 0, ref_ok fails (SketchSolver.cpp:185), constraint dropped.
Not sometimes -- every press.
HORIZONTAL/VERTICAL hardcoded ra=P0, rb=P1 with no type check. With a Point picked the
constraint is dropped by the same mechanism but still STORED: constraints goes 0 -> 1 after
the commit and nothing moves, so the Constraints list shows a dimension that can never do
anything. Worse than refusing -- the panel claims the sketch is constrained when it is not.
ANGLE computed p1-p0 on whatever was picked. On a circle that is (0,0)-centre, so two
circles pre-filled the field with the angle between their centre POSITION VECTORS (178.83
deg for two on the x axis), and accepting it emits SLVS_C_ANGLE on two circle prims.
Both branches now refuse with a message. entity_ends()/closest_ends() are file-scope and
shared by Coincident and DistanceX/Y, so there is one implementation instead of two that
drift.
Two smaller findings from the same review: infer_auto_constraints' roles_of omitted
EllipseArc while heal_coincidences' identical copy has it; and set_point(Circle, Center)
wrote e.center and not e.p0, breaking the "p0 mirrors centre" invariant for the duration of
a live drag.
New rungs D8 and D9, both RED against the shipped binary and green here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrMzTpAf78U4NG2M8jfvHY
|
||
|
|
507b45431c |
Keep the CAD recipe in the one 3mf backend that actually runs
Format/3mf.cpp also saved and loaded it, but nothing calls its store_3mf and its load_3mf only sees files fingerprinted as PrusaSlicer's, which never carry a recipe. Its round-trip test only exercised that dead loop. The BBS backend, which every save and load goes through, is untouched. |
||
|
|
493befecc5 |
Tear down the Design canvas at shutdown
The Design tab's viewport is the fourth GLCanvas3D on the shared GL context and the only one the plater does not own, so unbind_canvas_event_handlers() and reset_canvas_volumes() never reached it — the macOS Command+Q and Debian cases those calls exist for. Its frame-level handlers become members so they can be unbound. |
||
|
|
5a4f7f4c3c |
Re-anchor the Design status chip on canvas resize
The bind sat above GLCanvas3D::bind_event_handlers(), and on_size never Skips, so wx's reverse-order dispatch stopped before it and the handler never ran. |
||
|
|
afb17e8889 |
D3 was testing luck: the pair started 3 degrees from square, inside inference's snap
Port of snaporca b3221f8a12; this is the fork the fault surfaced on. The perpendicular rung drew its two lines 93.5 degrees apart and then asserted they did NOT start perpendicular. On this rig, whose camera maps the same click a pixel differently, they arrived at exactly 90.000000 -- inference had already done the job the rung exists to test, so the precondition failed while every later check passed. Held on the other fork and failed here from identical source: the rung depended on where a click happened to land, not on the app. The second point now starts the pair 56 degrees off, well outside any snap tolerance, so the button has real work to do. That immediately exposed a second, milder fault in the same rung. From a 51 degree start the LIVE solve converges to its own tolerance and lands at 89.999999991; the old 1e-9 assertion held only because the correction used to be tiny -- it was measuring how little work the solver had to do, not whether the lines came out perpendicular. It is 1e-6 degrees now, which is 1.7e-8 radians. The round-trip check still demands exactly 90 and gets it, because the committed feature re-solves from scratch. Full ladder 118/118 on BOTH rigs after this, each driving its own fork's binary. This fork had never had a green gesture ladder before today (snaporca-eoj1). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MrMzTpAf78U4NG2M8jfvHY |
||
|
|
d35d33971a |
the feature-tree row needs CHROME_DY too — the last chrome constant that did not carry it
Four of this fork's five absolute chrome coordinates were shifted by CHROME_DY when the
ladder was first brought up here (DESIGN_TAB, CONSTRUCTION_CHECKBOX, CON_BTN_Y,
CONFIRM_BTN). TREE_ROW0 was not, because it is declared above the CHROME_DY block and was
simply never in view.
The unshifted click lands 26 px below the first tree row, just past its 23 px height, so
the row is never selected and Delete does nothing. reset_document then spends 40 rounds on
it and dies with "could not empty the feature tree" — a message that names the feature
tree, which is not the fault. The same 26 px is why confirm_and_reopen's double-click did
not reopen the sketch, which surfaced as "sketch_describe: no sketch is open" three frames
away from the cause.
Measured, not inferred: the Sketch1 row centre reads y=241 on the rig at 1920x1080 with
the window at (0,0), against the constant's 215. CONFIRM_BTN was checked in the same pass
from a screenshot taken in CONSTRAIN mode and is correct at (1751, 101).
With this, the four new constraint rungs hold 20/20 on this fork's rig, driving the binary
built from
|
||
|
|
648b930e75 |
gesture rungs for the six new constraint buttons, and the DistanceX/Y bug they found
Port of snaporca 2951e3c60b; parity holds (17 identical, DesignPanel.cpp still exactly 32
divergent lines, so the hunk landed on the right side of the DropDown divergence).
The sketch-constraint epic added six toolbar buttons and covered all six with kernel
tests. Not one of them was ever clicked. The gesture ladder only pressed 'perpendicular'
and 'equal' -- and CON_BTN, which locates buttons by index, was silently wrong for every
entry past index 5 for the whole epic. The untested half of the toolbar was exactly the
broken half (snaporca-rqsy).
Four rungs now drive them: D4 Equal on two circles (must mean equal RADIUS, not the
equal-length no-op the epic fixed), D5 Collinear on two oblique lines, D6 a horizontal
distance, D7 symmetric about the implicit vertical axis. Full ladder 118/118 on snaporca;
this fork's rig has not been rebuilt against the change yet, so here it is reviewed,
parity-checked and NOT exercised.
D6 found a shipped defect. apply_entity_constraint enumerated {P0,p0},{P1,p1} for BOTH
entities regardless of type, but a Point's p1 is unused and reads (0,0), as does a
Circle's. The closest-pair search then picked those two phantom origins, distance 0: the
field opened pre-filled 0.00 and the solver dropped the constraint, because ptOf(Point,P1)
resolves to no handle. Nothing errored -- the dimension simply did nothing. ends_of() now
enumerates only the roles an entity actually exposes, and the pair with no point at all is
refused with a message instead of a silent no-op.
Five kernel tests, a 7/7 ladder, a review and a fork port all passed over this, because
every one of them exercises the kernel, where the geometry was always right.
Two rig faults fixed in the same pass, both of which produce a green-looking session that
tests nothing: start-headless-gui.sh never exported SNAPORCA_MCP or SNAPORCA_KEYTRACE, so
a freshly launched rig comes up healthy and every ladder dies on "Connection refused"; and
it never dismissed the "Restore" dialog a killed session leaves behind, which grabs every
synthetic click afterwards.
The value field also takes no keyboard focus from the WM -- typed digits go to the canvas
and Return commits the pre-filled number (typed 40, got 54.94). focus_field() finds it as
its own top-level window and clicks it first. The no-op tolerance is now 5e-3, the field's
own two-decimal display resolution, not 1e-6.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MrMzTpAf78U4NG2M8jfvHY
|
||
|
|
d0791c3b8a |
Make the ladder runnable on this fork's rig — 6 of 7 rungs now hold
This fork had never been gated end to end. Five separate things stopped it, none
of them a defect in the CAD code itself, and each failure named the wrong
subsystem — which is why they survived.
1. run-all-checks.sh invoked docs/ux/mockups/gen_offer_table.py. The design docs
moved to docs/CAD/ (
|
||
|
|
ca8f813994 |
Add assimp to the deps image, so this fork can run its own tests
This fork's kernel suite has never run. scripts/CAD/run-kernel-tests.sh died at CMake CONFIGURE time on find_package(assimp REQUIRED), before a single source file compiled, so every kernel change ported here was parity-checked against snaporca and never independently tested (snaporca-w80c). WHY IT WAS MISSING. OrcaSlicer mainline gained assimp (glTF/GLB/FBX import for texture-to-colour) after the orcacad-deps image was baked: the image's deps/ tree has no Assimp directory at all and nothing named assimp anywhere in it. On the host, deps/build/dep_Assimp-prefix carries only `patch` and `update` stamps -- no build, no install -- so the dependency was fetched and then never built, inside the image or out of it. The Snapmaker fork never hit this because its base requires neither assimp nor OpenCV. WHY A LAYER. A full deps rebuild is hours and would rewrite artifacts that currently work; this adds the one missing package on top. It is a Dockerfile rather than a `docker commit` so that what was done stays reviewable and repeatable instead of being an undocumented image mutation. The flags are the project's own recipe (deps/Assimp/Assimp.cmake) plus the standard superbuild arguments from orcaslicer_add_cmake_project (deps/CMakeLists.txt:158) and DEP_CMAKE_OPTS (deps/deps-linux.cmake). The file says to keep them in step with that recipe: it stands in for the superbuild, it is not a separate opinion about how to build assimp. The tarball's SHA256 was checked against the recipe's URL_HASH before this was written and is re-checked inside the build, and the build asserts the installed cmake config exists rather than trusting an exit code. OpenCV was confirmed already present, so it is not a second wall behind this one. RESULT, and it is the point: orca_cad kernel now runs and is GREEN at 7701 assertions / 270 cases. snaporca is 7700 / 270 -- same cases, one more assertion here, which is the tolerated test_caddocument.cpp divergence. The "this fork's kernel suite cannot run" caveat carried by the five commits of the sketch constraint epic no longer applies. |
||
|
|
b5ead4b29f |
Pull a body's edges into a sketch as construction references
Port of snaporca e635627b81. Parity OK: 17 files identical, 8 diverging at their expected counts. The last reference an industrial sketcher offers that this one did not: Onshape's Use, SolidWorks' Convert Entities. Project already turned a body's 3D edges into 2D Lines, Circles and Arcs on a plane, but the result landed in its OWN feature, so while drawing in one sketch you could not borrow an existing body's edge and constrain to it. No new geometry code: Project's per-edge conversion loop is factored into project_edges_to_entities() and called from a second entry point that appends into an EXISTING sketch with construction = true. The loop appears once now, not twice. Construction is what makes it cheap and safe: SketchEngine already skips construction entities when building wires, so the references guide without being built, and being otherwise ordinary entities every constraint from this epic -- Collinear, EqualRadius, the axis-projected distances, PointOnLine, Symmetric -- works against them for free. Part of this refactors working code, so Project's behaviour identity is the invariant; the existing [CadDocument][project] cases guard it and a new case asserts a Project feature still emits construction == false. project_edges_into_sketch returns the number of entities appended, or -1 on a bad reference rather than throwing. VERIFICATION LIMIT, as with the previous four commits: this fork's kernel suite still cannot run (find_package(assimp) at configure time, snaporca-w80c). Shared sources are byte-identical to snaporca's, where kernel is 7700 assertions / 270 cases and ALL LADDERS HELD 7/7. |
||
|
|
fac3cf44df |
Infer parallel, perpendicular, equal radius and tangent while drawing
Port of snaporca 2d36d28770. Parity OK: 17 files identical, 8 diverging at their expected counts. infer_axis_constraint returned only Horizontal or Vertical. On the CAD-1000-hours corpus the top two transitions are sketch_dim -> sketch_draw (5896) and back (5756): the signature of geometry that does not self-constrain as it is drawn. Every rule requires the relation to be ALREADY TRUE within tolerance, so nothing the user drew is moved; parallel/perpendicular and tangent additionally require a shared endpoint. TWO LIMITS THE CORPUS RUNG FORCED, neither visible to the unit tests: 1. At most ONE constraint per rule per new entity, not one per PAIR, and no one-at-a-time fallback for the relations batch. EqualRadius has no locality restriction, so 200 equal holes produced ~20000 candidates; the rejected batch then cost a solve per constraint and pinned the app at 95% of a core with the MCP socket unresponsive. 2. Relations only for gesture-sized batches. "A scripted add is not a drawn gesture" is already this file's rule at its bulk call site (snaporca-8xg1), and EqualRadius also couples geometrically distant entities, merging independent connected components and defeating the partitioning that makes large sketches solvable (snaporca-yww4). With the cap alone geometry stayed correct (32/32 sheets clean) but seven of the largest timed out, including MPD681 -- the sheet that call site's own comment names. Also fixes the tolerance leak behind 2: the bulk path asks for exact inference with ang_tol_rad = 0 but len_tol_frac kept its 0.01 default. ALSO independent of this feature: run-kernel-tests.sh defaulted to TAGS=[CadDocument] while four CAD test files carry their own tags and nothing selected them (2624 assertions / 206 cases reported, 7648 / 264 actual). All 58 dark cases were passing; the coverage was never exercised. VERIFICATION LIMIT, as with the previous three commits: this fork's kernel suite still cannot run (find_package(assimp) at configure time, snaporca-w80c). Shared sources are byte-identical to snaporca's, where kernel is 7651 assertions / 265 cases and ALL LADDERS HELD 7/7. |
||
|
|
45494c6035 |
The origin and the two axes become things you can constrain to
Port of snaporca 5b1294de59. Parity OK: 17 files identical, 8 diverging at their expected counts. Every industrial sketcher gives you the origin and the axes as references. Here the origin was only a SNAP target and the axes did not exist, so Symmetric needed a third picked ENTITY as its mirror axis: symmetry about the sketch's vertical axis first required drawing a construction line. Every constraint reference resolves through four lambdas in the solver (valid/ptOf/primOf/coordOf), so teaching those about three negative sentinel indices makes the origin and both axes available to EVERY constraint type at once. No new SketchEntity type, no serialization change; -1 still means "unset". The references live in G_FIXED and add no degrees of freedom, which a test asserts via the reported DoF. SymmetricAboutY / SymmetricAboutX are two buttons that need no third pick and no construction line. Making the axes clickable in the viewport is deliberately left out: that is canvas hit-testing work with its own risks. Recorded in the tests because it will catch the next person: sys.dragged[] is populated only during a drag, so a plain sketch_solve of an UNDER-constrained system may move any free parameter -- solvespace runs Newton, it does not minimise movement. PointOnLine onto an axis is one equation in two unknowns and the point legitimately slides along it. Those tests pin the free direction instead of asserting the other coordinate is untouched. VERIFICATION LIMIT, as with the previous two commits: this fork's kernel suite still cannot run (find_package(assimp) fails at configure, snaporca-w80c). The shared sources are byte-identical to snaporca's, where kernel is 2624 assertions / 206 cases and ALL LADDERS HELD across all seven rungs. |
||
|
|
a63bba2d55 |
Horizontal and vertical distance dimensions
Port of snaporca 9fa304c77a. Parity OK: 17 files identical, 8 diverging at their expected counts. The everyday dimension in SolidWorks and Onshape, and this kernel had no form of it. Distance constrains the straight-line gap; LockX/LockY pin one point's ABSOLUTE coordinate. Neither relates two points along an axis. DistanceX/DistanceY emit SLVS_C_PROJ_PT_DISTANCE against two unit direction lines built in the solver's G_FIXED group, so they add no degrees of freedom. THE DIRECTION IS SIGNED, and getting it backwards is silent. libslvs defines a LINE_SEGMENT's direction as point[0] - point[1] (entity.cpp) and PROJ_PT_DISTANCE constrains (pB - pA).dot(dir) (constrainteq.cpp:234), so the reference lines are built head-first to mean +X and +Y. The same signedness was a real defect in the GUI: the inline editor was pre-filled with |delta|, so when the closest endpoint pair ran right-to-left, opening the dimension and accepting the number shown would flip the point to the other side of its anchor. Opening a dimension and accepting its own value must be a no-op. The refs are now ordered so the shown value is positive. On the CAD-1000-hours corpus, dimensioning and constraining is 31.9% of all observed CAD time -- the largest single class, 7.6x feature operations. This is the item in the constraint epic that lands most directly on it. VERIFICATION LIMIT, as with the previous commit: this fork's kernel suite still cannot run (find_package(assimp) fails at configure time, snaporca-w80c). The shared sources are byte-identical to snaporca's, where kernel is 2603 assertions / 200 cases and ALL LADDERS HELD across all seven rungs. |
||
|
|
a1f2a2687a |
Equal radius and Collinear, and one Equal button that knows what it picked
Port of snaporca 9ec6405e2d. Parity OK: 17 files identical, 8 diverging at their
expected counts (DesignPanel.cpp 32, test_slvs_constraints.cpp 3).
Measured on the CAD-1000-hours corpus: 51.5% of observed CAD time is 2D sketch
work, and dimensioning/constraining alone is 31.9% -- the largest single class.
Two constraints every industrial sketcher has were missing here.
EqualRadius fixes a dead end rather than adding a feature. Picking two circles
and pressing Equal emitted EqualLength, which maps to SLVS_C_EQUAL_LENGTH_LINES
and constrains nothing on a curve: a silent no-op with no error. Equal is now
one button with two meanings, as in Onshape and SolidWorks.
Collinear emits PARALLEL plus PT_LINE_DISTANCE=0 rather than PT_ON_LINE, whose
internal valP param this libslvs port leaves at 0, drifting an already-collinear
pair.
Both types are appended at the END of SketchConstraintType: cereal serializes it
positionally, so inserting elsewhere reinterprets every saved recipe.
VERIFICATION LIMIT, stated rather than implied: this fork's kernel suite could
NOT be run. scripts/CAD/run-kernel-tests.sh fails at CMake configure time on
find_package(assimp), before any source compiles -- a pre-existing deps gap
(snaporca-w80c), not this change. The shared sources are byte-identical to
snaporca's, where the full gate passed: kernel 2588/195 and ALL LADDERS HELD
across all seven rungs.
Also fixes two defects in this fork's scripts/CAD/run-all-checks.sh:
- `cd $(dirname $0)/..` landed in scripts/ instead of the repo root, so every
rung looked for itself under scripts/scripts/. Broken since the script moved
into scripts/CAD/; the three sibling scripts were fixed then and this was
missed, so the gate has not run since.
- C defaulted to snaporca-gui, the OTHER fork's rig container, so this fork's
gate would drive snaporca's app and report green about the wrong binary.
run-kernel-tests.sh:31 documents the identical defect being fixed once
already for the build volume; this is the third instance.
|
||
|
|
937437907d |
Use Plater's existing background_process() accessor
It was already public and used elsewhere, so the new get_background_process() and its duplicate forward declaration were redundant; Plater.hpp is now untouched. |
||
|
|
bbd1989e1e | move design doc to CAD subfolder | ||
|
|
884a382a48 |
Esc leaves the sketch from the state you are actually left in
exussum12 on PR #15238: "Esc hardly ever works". He was right, and the word that matters is "hardly" — it works while you are mid-entity and stops working the moment you finish one. The branch asked whether a DRAW TOOL was armed, not whether a SKETCH SESSION was open: if (key == WXK_ESCAPE && m_viewport && m_viewport->is_sketching()) is_sketching() is DesignSketchTool::is_active(), true only between arming a tool and finishing with it. Commit an entity and you are left at ui_mode=Sketch with no tool armed, and from there Esc did nothing at all, no matter how many times you pressed it — the Cancel button was the only way out. Reproduced on the headless rig with SNAPORCA_KEYTRACE, which is what settled it rather than reading: the key ARRIVES and focus is fine, [KEYTRACE] key=27 ui_mode=1 is_sketching=0 in_text=0 inline_busy=0 focus=w so this was never the focus problem it looks like from the outside. Gate on the session instead. request_exit() is already layered — abort the in-progress entity, else drop the tool to Select, else exit to Feature — so widening the gate adds no new behaviour, it just lets the ladder be reached from its own last rung. is_sketching() stays in the condition as an OR, so nothing about the armed-tool path changes. This is the same mistake snaporca-0ud fixed thirty lines above in the same handler, where gating the sketch key MAP on is_sketching() made all 17 keys read as dead. The comment there now has a sibling. VERIFIED ON THE RIG, before and after, same sequence (Design > XY > Shift+S > click canvas): before, two Escapes left the toolbar on SKETCH with zero pixels changed; after, the toolbar reads FEATURES — one Esc drops the armed tool to Select, the second exits the session. Kernel suite green, 2568 assertions in 191 test cases. libslic3r_gui builds clean on both forks. Parity 17 identical / 8 diverging as expected. |
||
|
|
8f014de84c |
Load the CAD recipe from projects saved before it was renamed
The recipe's 3MF entry moved from Metadata/SnapOrca_cad.bin to Metadata/orca_cad.bin, so projects saved by earlier builds opened with an empty Design tab. Both 3MF backends now read either name and write only the new one; the recipe version advances to 6 to mark the move. |
||
|
|
0fc62d03b9 |
Hide the Design tab behind an experimental CAD preference
The tab is now gated on a new enable_cad_feature app-config key, off by default, exposed in Preferences under General > Features. When off, the page is never created, so the Design-only camera option is hidden too. Takes effect on restart, like the other feature toggles. |
||
|
|
13d5eac891 |
Move the Design-tab scripts into scripts/CAD/ and name them by role
Requested by SoftFever on PR #15238: ten of these had accumulated loose in scripts/ next to ~20 unrelated upstream ones, with names that only meant something to whoever wrote them. They now sit in scripts/CAD/, mirroring the src/libslic3r/CAD/ and src/slic3r/GUI/CAD/ split, and the verb in the name is the role: build- produces a binary, start- brings something up, run- runs a suite, check- asserts one thing against a live app. kernel-test.sh -> CAD/run-kernel-tests.sh ladder-all.sh -> CAD/run-all-checks.sh sketch-ladder.py -> CAD/check-sketch-engine.py ladder-corpus.py -> CAD/check-sketch-engine-corpus.py gui-ladder.py -> CAD/check-gui-sketching.py offer-ladder.py -> CAD/check-gui-context-menu.py mcp-sketch-smoke.py -> CAD/check-mcp-sketch.py rig-build.sh -> CAD/build-gui.sh docker-iter-build.sh -> CAD/build-gui-incremental.sh gui-session.sh -> CAD/start-headless-gui.sh "Ladder" was the worst of them: it named the shape of the test (rungs of increasing difficulty) rather than what the test proves, so nothing in the directory listing told you which one needed a GPU and which was pure kernel. Every reference rewritten -- the docs, the cross-calls between the scripts, Dockerfile.deps, and the container-side /OrcaSlicer/scripts paths. The three shell scripts resolve REPO relative to themselves and now sit one level deeper, so that walk went from /.. to /../.. . The copies these push into a container's /tmp were renamed to match, or the container would have kept the old names alive. Two runtime paths deliberately NOT renamed. /tmp/orca-rig-build.lock is a cross-fork contract -- both forks take the same lock so two concurrent builds serialise instead of OOMing the box, and renaming it on one side silently removes that guard. /tmp/gui-session.log is a runtime artefact, not a script. Added scripts/CAD/README.md: what each script proves, what it needs, and the two constraints that have each cost a session (never build inside the GUI container; a window manager is required or synthetic keys are ignored). On CI, which was the other half of the request: the kernel suite is already there and always has been. The cases are registered in tests/libslic3r/CMakeLists.txt under if (SLIC3R_CAD), which defaults ON and no workflow turns off, so they build into libslic3r_tests and run under ctest on every platform via unit_tests.yml -- like any other unit test, needing no new job. They have simply never been seen to run, because the workflows on this PR are still awaiting maintainer approval. run-kernel-tests.sh is the local loop over the same cases, and it is the only script here CI could run: the other six need an OpenGL canvas and synthetic input. Verified: scripts/CAD/run-kernel-tests.sh from its new location, all tests passed, 2562 assertions in 190 test cases. |
||
|
|
cdd41e230d |
The Design tab's MCP socket must not wait for someone to click the tab
Building the Design tab on first use (
|
||
|
|
149ae6c7fe | Merge branch 'main' into cad-mainline | ||
|
|
41365736ff | Fix the Windows build | ||
|
|
db29f570bd |
build: clear 50 warnings - pessimizing moves and null checks that cannot fail (#15408)
* build: remove std::move that blocks copy elision std::move wrapped around a temporary, or around a local being returned, stops the compiler constructing it in place. Each edit is the fix clang suggests, which is to delete the std::move call and keep its argument. Three of the 39 sites save a move, the two return std::move(local) in Print.cpp and TreeSupport.cpp:2749. The rest are equivalent either way and match how the codebase already writes this elsewhere. Clears 39 -Wpessimizing-move warnings. * build: drop null checks on references and this A reference cannot be bound to null and this cannot be null, so the compiler folds these conditions to true and drops the guard. Seven are if (&bitmap && bitmap.IsOk()), where IsOk() already does the work; two test this directly. The guarded code runs either way, so removing the dead operand changes nothing. Clears 11 -Wundefined-bool-conversion warnings. |
||
|
|
cc390f11ee |
feat(build): build the missing dependencies from the main CMake configure (#15373)
* fix(deps): build the dependencies from scratch with clang-cl Six dependencies fail once the superbuild compiles them with clang-cl instead of cl: - OpenSSL never goes through CMake. Its VC-WIN64A makefile only works with cl, and an unquoted clang-cl path with spaces produces no .obj files at all, so the lib step dies with LNK1181. Pin the upstream toolchain. - Boost.Container's bundled dlmalloc passes int* to the Interlocked API. cl warns, clang rejects it. - curl 7.75's configure probes rely on C laxness clang rejects. The results flip and nonblock.c ends up in the AmigaOS IoctlSocket branch. - OCCT installs RelWithDebInfo into bini/libi while find_package looks in lib. It also prepends -Wl,-s to the shared linker flags for every Clang build, which the MSVC-style linker gets as an argument it does not know. Both patched hunks sit inside if (MSVC) in the OCCT sources. - wxWidgets lands in lib/clang_x64_lib, so wxWidgetsConfig.cmake falls back to the layout that exists instead of assuming vc_x64_lib. It tries the derived path first, so a cl-built tree consumed by clang-cl keeps resolving the way it does today. The patch step also resets the one file it touches, so it can run again after an interrupted build or after the patch itself changed. - wxInspector goes through FindwxWidgets, which only searches lib/vc*_lib because _WX_TOOL is hardcoded to vc. It now gets the root and lib dir derived the same way wxWidgetsConfig.cmake derives them. Eigen is the seventh, and it breaks on the generator rather than the compiler. Its test, lapack and blas/testing subdirectories all call enable_language(Fortran), and they default to ON because the dependency configures as its own top-level project. Whether that hurts depends on what CMake finds: the Visual Studio generator supports no Fortran and finds nothing, clang-cl sits next to the LLVM toolset's flang and works, while MSVC with Ninja finds Strawberry Perl's MinGW gfortran, which this build already requires for OpenSSL, and hands it the MSVC-style /machine:x64 that MinGW's ld reads as a missing input file. The configure dies there and takes every dependency still in flight with it. Only the headers are consumed here, so the three subprojects are off. * fix(deps): honor the superbuild's generator and compiler in sub-builds orcaslicer_add_cmake_project pinned every dependency sub-build to the Visual Studio generator whenever MSVC was true, which is also true for clang-cl. That generator selects its compiler by toolset and ignores the CMAKE_C_COMPILER and CMAKE_CXX_COMPILER this file already forwards, so the dependencies were built with cl.exe no matter which generator or compiler the superbuild was given. Key the three affected decisions on the generator instead: which generator the sub-builds use, whether CMAKE_BUILD_TYPE is forwarded, and /m versus -j. A Visual Studio superbuild is unchanged, so the default path and CI behave exactly as they do today. build_release_vs.bat now accepts -l to select clang-cl, alongside the existing -x for Ninja, so the generator and the compiler can be chosen independently. On the Visual Studio generator -l reaches the slicer only, through the ClangCL toolset, because the dependency sub-builds have no toolset to inherit; a deps build in that combination says so rather than quietly using MSVC. * fix(deps): use upstream wxWidgets compiler layout fix The compiler-prefix layout fix now comes from SoftFever/Orca-deps-wxWidgets#7, so remove the duplicated local patch and apply step. * fix(deps): stop Assimp enabling ccache on the RC rule ASSIMP_BUILD_USE_CCACHE defaults on and applies the launcher through the global RULE_LAUNCH_COMPILE property, so it wraps the resource-compiler rule as well. Under Ninja that rule goes through cmcldeps, which does not survive being launched by ccache, and the build fails with clang-cl reporting /fo as a missing file. The superbuild already forwards CMAKE_<LANG>_COMPILER_LAUNCHER, which CMake applies per language and so keeps clear of the RC rule. --------- Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com> Co-authored-by: raistlin7447 <kris.austin@gmail.com> |
||
|
|
f130b713c8 | fix shellcheck errors | ||
|
|
7fc97a81bd |
Build only the OCCT and solver pieces the Design tab needs
SLIC3R_CAD=OFF now builds without SolveSpace or OCCT's ModelingAlgorithms module, leaving the dependency set identical to upstream's, and the Design tab's mate connector preference no longer appears in builds without the tab. When the deps prefix and the project disagree about the option, the configure fails naming the cause, rather than failing at link time or at first launch on Windows. The Windows packaging step stages exactly the toolkits libslic3r links. |
||
|
|
f693b8d9fe | List the Design tab headers and drop the unrelated build changes |