mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-09 08:41:14 +00:00
4cb6ca7ecbb50d267dce95bb8436e18fbc7daf53
28
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4cb6ca7ecb | Let Esc and a click on empty space deselect Design feature and body rows | ||
|
|
073e2d9c44 |
Highlight the faces a selected feature made in the Design tab
Selections are drawn as opaque faces in the selection colour with a cased outline instead of a translucent tint over the body, so they read on a body of any colour. Selecting a Feature tree row lights the faces that feature made rather than its whole body, which also makes fillet and chamfer rows highlight again. |
||
|
|
1d06b8576b |
Fix Design tab getting stuck unable to start a new sketch
After undo, redo, New Design, a project load, a delete or a reorder, a sketch profile picked beforehand stayed selected even though it was gone. The right-click menu then offered "Sketch profile" over an empty design with Create greyed out. Those operations now clear the selection. The Confirm button no longer stays greyed in a sketch session after a card whose preview was invalid has been closed. Delete and reorder now wait while a sketch or constrain session, the Text dialog, an Insert placement or the move gizmo is open, instead of editing whichever feature took its place in the list. |
||
|
|
9351bbaa83 | Merge branch 'main' into claude/inspiring-knuth-7cp6pk-upstream | ||
|
|
1a5f91d727 |
Add Missing Includes Across src/slic3r/GUI (#16048)
* Add Missing Includes Across src/slic3r/GUI Every GUI source and header now directly includes the headers declaring what it uses, rather than relying on the precompiled header or transitive includes. Generated with clang-tidy misc-include-cleaner, plus one hand edit making CalibrationPanel.hpp self-contained. * Drop the OS-Specific Includes Added Outside Their Platform Guards GLib, GTK, D-Bus and POSIX headers are only used inside platform #if blocks, which already include them. Added unconditionally at the top of the file they broke the Windows build. * Add the clang-tidy Configuration That Generated These Includes Only misc-include-cleaner's missing-include check, with the headers it must never suggest: per-platform, internal and OS-specific ones that would break other platforms or are not meant to be included directly. * Match Windows Paths in the clang-tidy Ignore List Header paths use backslashes on Windows, so every / in a pattern is now [/\\]. The Windows SDK headers are ignored alongside the other OS-specific ones, and the list is one pattern per line. Suggested by @raistlin7447 from a Windows clang-cl run. |
||
|
|
410399fdb5 |
Design tab: follow Orca's theme, scale, mouse settings, dialogs and undo
Felix14-v2's review of OrcaSlicer#16019 found the tab behaving as a world of its own. - Icons: the design_* glyphs were drawn in a fixed light grey, made for the dark ribbon, and the toolbar re-tinted some of them by rebuilding the bitmap from a wxImage, which drops the HiDPI scale factor Orca sets on Windows: at 150 % the icons came out half again too large for buttons that were sized in raw pixels, overlapping and clipped. The glyphs now use Orca's sidebar icon grey (#949494), which the icon cache maps per theme, nothing is re-tinted, toolbar glyphs drawn for Prepare's light toolbar use their "_dark" twin, and every size is in DIP. - Theme: the chrome colours were read once, at construction, and nothing in the tab answered a theme switch, so switching left light surfaces and unreadable text in a dark tab and the other way round. The colours are now {light, dark} token pairs; MainFrame::on_sys_color_changed reaches DesignPanel::on_sys_color_changed, which moves every token colour onto the other theme's, runs the app's dark pass and re-rasterises the icons. Card borders are StateColors, resolved at paint time. - Scale: MainFrame::on_dpi_changed reaches DesignPanel::msw_rescale, which re-rasterises every icon (buttons, flyout rows, card headers, the tree's image list, now sized from its bitmaps) and re-measures the Orca widgets. - Mouse: the canvas no longer forces middle-drag to orbit and right-drag to pan; it reads the drag actions in Preferences > Control like Prepare. Left-drag is shared with picking, so the whole-body rubber band takes Shift+left-drag while left-drag is given to the camera. - FPS counter: ImGui's display size is shared and only refreshed when a canvas sees its own size change; the Design canvas now re-announces its size when the tab is entered (and the editor canvas when it is left), as Plater does between Prepare and Preview. - Viewport text: the status line and the tool readout were top-level popups over GL. A popup does not follow its frame, so the empty-canvas hint floated over other applications, and the readout was never taken down with the tab. Both are drawn by the canvas in the tool's ImGui pass now, with the theme's overlay style. - Dialogs: messages use MessageDialog/RichMessageDialog; Add/Edit Variable is one Orca dialog with Name and Expression fields instead of two native text prompts; the Text dialog uses TextInput, ComboBox and CheckBox (its height is a TextInput: SpinInput is integer-only), and enumerates the installed fonts once per session. The ribbon's Confirm/Cancel, the reference pick buttons and the expression buttons are Orca Buttons; the variable actions are icon buttons like the other cards'. - Undo: the tab's own Undo/Redo buttons are gone. The top bar's Undo/Redo drive the Design history while the tab is shown, greyed to what an undo would do, as Ctrl+Z and Edit already did. - The first build of the tab logs how long each phase took ("Design tab build: ..."): it is under a second here but was reported at about fifteen on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9 |
||
|
|
a4387e93f2 |
Design tab: revolve about a line of the sketch
Reported: the Revolve axis could only be the sketch plane's X or Y axis through its origin, so a half-profile drawn beside a centerline, the usual way, could not be revolved about that centerline. - CadFeature::revolve_axis_entity names a Line of the profile sketch to revolve about (a construction centerline, or an edge of the profile itself); -1 keeps revolve_axis. Appended at the end of the framed recipe, so existing projects load and rebuild unchanged. Revolve and Surface Revolve resolve their axis in one place (revolve_axis_of); an index that no longer names a line fails with a reason. - SketchEngine::make_revolve takes the world axis. A profile with points on both sides of it is refused with "the profile crosses the revolve axis"; MakeRevol failed there with no reason. - The Axis list of both cards reads Plane X, Plane Y, then every line of the sketch, named as the constraint list names them (Centerline E4, Line E3). A fresh revolve preselects the sketch's centerline when it has exactly one. The gizmo turns about the chosen axis and draws it dashed; construction lines no longer pull its centre. Tests: a rectangle beside a construction centerline revolves into the tube of the expected volume along that line; about its own edge, into a cylinder; an axis through it is refused with the reason; a stale axis index fails with a reason; the axis survives save and load. The truncated-recipe test accounts for the new tail field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9 |
||
|
|
b8d45c6bb1 |
Design tab: studio lighting and body edges in the 3D view
Reported: once extruded, a part is hard to read; the lighting says little about its shape. Both lights of the object shaders sit near the camera, so the sides of a part come out in almost the same tone, and nothing marks where one face ends and the next begins. - The phong shader gains a studio lighting model, chosen by a new lighting_model uniform: a sky/ground hemisphere in world space (up faces cool and bright, down faces warm and dark), a key light from the upper left and a weak fill from the right, a plastic-like highlight, and a darker base with a faint sheen toward the silhouette so curved faces read as round. GLCanvas3D::set_studio_lighting() makes a canvas draw its objects with it whatever the realistic-view preferences; only the Design canvas turns it on. Every canvas sets the uniform on each use, 0 for the slicer's, so they render as before. - Every B-rep edge of a body is drawn as a thin dark line over it, depth tested and pulled a few pixels toward the eye so it wins against the faces meeting at it and hides behind the faces in front. The seam of a closed surface and degenerate edges are left out (GeometryEngine::display_edges). Edges are sampled once per shape and kept across recomputes that leave a body unchanged; bodies faded by body focus get fainter edges, and a dress-up previewing its result alone hides them with the bodies. Tests: display_edges gives a box its 12 edges at their lengths, a cylinder its two round rims without the seam, a cone its base rim without the seam or the apex. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9 |
||
|
|
4983796970 |
Design tab: sketch Offset takes the whole outline, not one segment
Offset worked on the single entity under the pointer. On an outline made of many short entities (a text glyph, an imported shape) that is a segment a few tenths of a millimetre long, and the starting distance was a tenth of that: the ghost was too small to see, and a typed distance moved one invisible segment. It read as "Offset does nothing, no preview". - The pick takes the chain the entity belongs to (connected_loop, same construction state), highlights all of it, and offsets it as one outline through offset_entities, which already joins and trims chains at their seams. - The starting distance is a twentieth of the outline's size, so the preview is visible at once. - The arrow reads its side off the ghost, since the engine may walk the picked entity backwards in the chain; it keeps pointing at the offset copy, flipped for a negative distance. - A single entity still gets its Parallel/Concentric constraint. A chain's offset is placed as geometry, since its joined entities no longer map one to one onto the originals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9 |
||
|
|
c664a24f4b |
Design tab: a closed loop that crosses or folds back is not a region
A sketch drawn on the rig extruded to walls with no caps. Every joint of its loop met, so the loop analysis called it closed and MakeFace accepted it, but the loop crossed itself: an arc left the top line's end heading back over it and crossed it again 2.5 mm on. A second arc left a 0.28 mm line tangent to it but the other way, a cusp. The prism of that face is an invalid solid, and it was shipped as a body. - SketchEngine::wires_to_face checks the face it builds and, when OCCT calls it invalid, fails with "the profile crosses or folds back on itself, so it does not bound one region". The extrude reports that instead of producing the broken body. - sketch_loop_defect() judges a closed loop of lines and arcs exactly: any contact between two of its entities away from the joints they share, or a joint where the curve turns straight back (a cusp; OCCT still builds that one, but it is never what was meant). It returns the point. - The sketch uses it on every region: the loop is tinted red, the point gets a marker, and the status line says what the red means the first time one appears. The MCP loop report lists the defects and no longer calls such a profile buildable. Tests: the rig's profile, with each defect and with both, from a recording of the real entities. The analysis names the cusp at its joint and the crossing on the top line, in either traversal order, and passes ordinary tangent and collinear joints. The extrude refuses every crossing variant with the reason, and the same arcs swept the other way round extrude to a valid solid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9 |
||
|
|
3c218591bc |
Design tab: pick several solid edges and dress them in one feature
A solid edge could only be picked one at a time, and Fillet/Chamfer took either that one edge or a whole face group. Rounding three chosen edges meant three features, whose edge ids each resolve against a body the previous one had already changed. - Shift+click (or Ctrl+click) on an edge of the body already picked adds it to the selection, or removes it; the same modifiers that extend a sketch selection. The whole set is highlighted. A plain click replaces it, as before. - Fillet/Chamfer dresses every picked edge in ONE feature at one size, all ids resolved against the same body. The card says "3 edges", the status line and the offer header name the count. - CadFeature gains dressup_edges, appended at the end of the framed recipe, so existing projects load and rebuild unchanged. dressup_edge keeps the first edge, so an older build opening a newer project still dresses that edge instead of falling back to the face group. - The MCP fillet/chamfer verbs take `edge` as one id or an array. Tests: a fillet on the four picked top edges equals the Top face group exactly; the list survives save/load; two opposite chamfers remove exactly twice one; a missing id fails with a reason. The truncated- recipe test accounts for the new tail field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9 |
||
|
|
829124982d |
Design tab: one rule for mouse, Enter and Esc; honest status messages
Keyboard and mouse - Esc drops what is pending (picks, a dimension's first point, an edit-op or transform) and never applies it; Enter applies a ready edit-op/transform, ends a polyline/spline chain, ends an armed tool, and confirms a feature card exactly when its ✓ is enabled. - Right-click only abandons the gesture in progress; with nothing pending it opens the offer in every tool (Trim, edit-ops, transforms, Dimension, Constrain, TransformArt, move gizmo). Clicking empty space no longer commits. The offer needs no timing, only a still press. - Delete removes only an explicit selection. Undo/redo inside a sketch go through the same route as the buttons (whole shapes, with redo); Edit > Undo follows the shown tab. - The canvas no longer handles Delete/Esc/Ctrl+Z itself (Backspace in a value field deleted the geometry it measured); F is in the panel's key map. - Value fields: a refused value keeps the field open with the reason; click outside and Tab commit; an untouched field commits the exact value; any decimal separator is accepted; lengths are always mm; validation is the same for every editor. - Snapping: the marker shows only where the click will actually snap; pick tolerances are one set of pixel budgets (Constrain picks within reach; no mm floor on labels). Messages and consistency - set_status(kind, text) gives every status line its own colour and glyph; kernel errors are translated into sentences and formatted, not concatenated; sketch refusals go to the status line instead of the per-frame HUD that erased them. - Hints describe the gestures that now work; Dimension shows its second step; Constrain uses the sketch palette (red means conflict only); the straight slot's value is its width. - Hole/Thread/Project keep the user's pick or refuse up front; circular pattern opens with its own preview; thread fields use the nominal diameter and the ISO internal depth. Integration - MCP loads the project's recipe before touching the document, refuses to mutate it while the tab is busy, never runs a request that already timed out, only replaces a socket at its path, caps line length and removes the socket at exit; not started in the G-code viewer. - Hiding a feature keeps later body references on their bodies; a design keeps its modeling origin across printer changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9 |
||
|
|
73bfcccd50 |
The sketch tools stop lying about what they did
Twelve defects found by the 2D design pass. - ONE erase path repairs the dimensions' cached constraint indices. Removing a constraint renumbered m_constraints and left every later DimAnnot.con pointing one slot short, and set_dimension_value checked only the range, not the slot's identity — so editing a dimension's value could overwrite an unrelated constraint. All five mutators (badge delete, fillet/chamfer, Move/Rotate/Scale, the two drop paths) now route through erase_constraints(). - apply_dimension validates BEFORE moving or recording. A value outside a case's threshold moved nothing and then recorded the constraint anyway, handing the solver a number it could never satisfy; the socket guarded against this, the tool did not. - A Distance and its zero case are one dimension slot: typing 0 and then 5 used to leave a Coincident AND a Distance on the same operands. - A dimension whose constraint the solver rejected is named: the label renders in the refusal colour and the commit says the sketch is over-constrained, instead of showing a number the geometry does not have. - Six silent refusals now speak: fillet/chamfer on a non-corner and on an overrunning radius, offset on an ellipse or spline (the kernel's own reason), a rejected array binding ladder, a dimension pick on an unsupported entity. - set_tool commits a ready transform instead of dropping it, the rule the ready edit-op already followed. - Constraint releases are reported, and roles_of is one function again (the two copies had already diverged on EllipseArc). - Labels no longer collide: every label is its own centred ImGui window at an anchor whose offsets are multiples of the text height, so on a feature smaller than one text height a line's Length and Angle labels landed on the same spot. draw_text, the one function all of them pass through, now pushes a colliding label clear of the ones already drawn this frame. Verified: build and LTO link on behemoth (exit 0, new binary); on the rig, badge-delete took constraints 3->2 with dof 9->10 and the geometry untouched; the draw-then-edit chain committed a typed 70 to exactly 70.0 mm; and a 5.4 mm selected line renders "5.4 mm" and "21.8°" as separate readable labels. NOT yet exercised: the stale-index corruption itself (needs a middle delete with a labelled dimension after it), the poison-value guard through the field, the transform commit, and the red refusal label. |
||
|
|
812063d085 | The label under an open value field is not drawn twice | ||
|
|
31a15cc5e5 | Rename snaporca/SnapOrca to orca_cad so the OrcaSlicer PR carries no Snapmaker naming | ||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
65e2b6f626 |
The sketch says what to do next, and construction geometry looks like it
User report, 2026-08-23, after using the freshly deployed build: "selection and
removal of existing elements of the 2d sketch is not intuitive, and the bottom ui
text does not illustrate what the user has to do to properly use the selected
tools. Mirror, for example, does not indicate: first select mirror line then
entities to be selected, and there is no UI indication of what is being selected.
normally, costruction lines are dotted." Four defects, all of them in the 2D
vocabulary this fork's charter puts at the centre, and all four fixed here.
snaporca-1c0c (P1) — the prompt was written ONCE, when the tool was armed.
DesignPanel's select_tool lambda set a sentence and nothing ever revised it, so
every step after the first was unguided: Mirror said "pick axis, then entities"
and then never said which of the two you were on; Escape silently downgraded an
armed tool to Select (request_exit is layered: anchors, then tool, then session)
while the line still named the tool you had left; and nothing ever mentioned that
Del removes a selection. The fix moves the line off the arm event and onto the
tool's LIVE state. DesignSketchTool::emit_step_hint() reports (mode, step, picks)
whenever that triple moves, from render() — the one place every state change in
this tool passes through. Putting it there instead of in the thirty-odd branches
of on_mouse is the whole point: a per-call-site notification is a thing the next
tool forgets to add, and it costs three int comparisons a frame. DesignPanel owns
the words, in ONE table (sketch_step_prompt), whose step numbers are the same ones
render() previews and on_mouse consumes, so the description cannot drift from the
code that reads the clicks. Every tool now names the gesture that ENDS it, because
none of them was discoverable: an empty click applies an edit-op or a transform,
right-click cancels it, Esc goes back to Select.
snaporca-vd6v (P2) — Mirror mirrors its axis pick and its target picks into
m_selection, so both painted white and the picture could not answer "what did I
select as what". The edit-op's first pick — Mirror's axis, Fillet/Chamfer's first
line — now paints violet. Violet and not cyan: cyan means SELECTED in this canvas
and nothing else may wear it, a rule this file already carries in writing.
snaporca-imlq (P2) — construction geometry drew as a solid grey line. Every CAD
dashes it, and grey alone does not read as "reference" against the
under-constrained orange. dash_polyline() chops the polyline before it reaches
draw_quad_strip, with the dash and gap in world units scaled by units-per-pixel,
so a dash keeps its size on screen instead of becoming a solid line when you zoom
out and three dashes when you zoom in.
snaporca-oql1 (P2) — Backspace now deletes as Del does. On every laptop this runs
on, Del is a chord and Backspace is what a hand reaches for. The Select-mode
prompt states the rest (Shift-click adds, double-click takes the loop, Del
removes), and the first step of every armed tool names the Esc route back to
Select, which was the invisible half of "selection is not intuitive".
Also, on the same report: the sketch stroke half-width goes 0.6 -> 0.3 mm. At 1.2
mm wide the orange line swallowed a short segment and hid which of two near
parallel lines the cursor was on. One constant, because all twenty call sites of
draw_quad_strip are sketch strokes.
Retired on the way: the on_sketch_selection_changed status writer. It said "N
selected — Delete removes them" while an edit-op mirrored its picks into the
selection, i.e. in the middle of a Mirror gesture, where Delete does nothing of
the sort. on_sketch_step says the true thing for Select and says nothing false
anywhere else. And the live length/angle readout is now APPENDED to the step
guidance rather than replacing it: it fires on every mouse move, so it used to
erase the instruction for the step in progress one move after the click that
started it.
Two false trails, recorded so the next session does not walk them again:
- DesignPanel.hpp deliberately does not include DesignSketchTool.hpp, so the
panel's handler takes the mode as an int and the .cpp casts it back. The
first attempt put Mode in the header signature and the build said only
"expected ',' or '...' before 'mode'".
- The offer ladder failed six properties against a perfectly good binary
because I had relaunched the rig myself without SNAPORCA_KEYTRACE=1, and its
[OFFER] trace lines ARE its instrument. A ladder with no instrument reports
"None", which reads exactly like a regression in the offer. ladder-all.sh
launches it correctly; a hand relaunch must too.
VERIFIED, not merely compiled. Driven on the headless rig with synthetic mouse
and keyboard, and photographed at each step: "Mirror — first click the LINE to
mirror about (a construction line works) · Esc goes back to Select" ->
"Mirror — axis set · now click the entities to mirror · right-click cancels"
-> "Mirror — axis set · 1 to mirror · click another to add or remove it ·
click empty space to apply", with the axis violet, its target white, and the
construction lines dashed while real geometry stays solid.
Full gate green afterwards (scripts/ladder-all.sh, ALL LADDERS HELD):
offer table vs the atlas OK
kernel suite 188 test cases, 2532 assertions
engine ladder rungs 1-8, ALL RUNGS HELD
corpus rung 977-sheet drawing corpus, every 20th -> 49
sampled, 39 gradeable, 39 clean
corpus scale rung the 6 heaviest sheets, all clean
gesture ladder 93/93 properties, real mouse and keyboard
offer ladder 108/108 properties, through the right-click
menu and the verbs behind it
That harness is the reason a UX change of this size can be made in one pass and
believed: 93 + 108 properties are driven the way a person drives the app, and the
977-sheet corpus keeps the engine underneath them honest against real drawings
rather than against my own arithmetic.
|
||
|
|
1bde448f51 |
Every 2D verb without a shortcut, driven from the offer — and three more defects
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 |
||
|
|
8c4b05ae9d |
The offer ladder: drive right-click, and fix the two things it found
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
|
||
|
|
82db99f337 |
Port from snaporca: sketch-only projects save, scripted geometry arrives exact,
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 |
||
|
|
05ce2607a8 |
Ladder rung 9: grade the engine against 50 real drawings, not against my taste
Ported from snaporca 5b82c846f3. |
||
|
|
1274d97983 |
Sketch: closing a polyline is now a previewed snap, not an invisible bubble
Ported from snaporca 982968b1af. |
||
|
|
5d7fc8c545 |
Port the sketch layer work from snaporca: offset chains, right-click, MCP verbs
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> |
||
|
|
2b02a8e3dd |
Design tab: move the CAD sources into their own folder
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. |