mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 22:42:37 +00:00
76d7dd6946bf2e837366efb8969c0397e72ffc58
27
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
76d7dd6946 |
CAD: an armed face/edge pick must not lose its click to the body escalation
A card that asks for a face ("Click a solid FACE in the viewport") could not be
satisfied. Clicking the same sub-element twice deliberately escalates to the
whole body — right for free picking, wrong here: the user clicks the very face
the card is pointing at, the escalation turns it into a whole-body pick, and the
armed capture rejects it and leaves "(none)". Both orders failed, so a
face-based Coord Sys was reachable only by accident of ordering. That mattered
beyond the card: a mate needs a connector with an owning body, and a face or
edge pick is the only thing that sets one.
Adds DesignSketchTool::set_escalate_on_repick, off while the Plane, Axis or
CoordSys card has a pick armed and back on as soon as it is captured or
abandoned. While armed, clicking a face means "this face", which is what the
prompt already says.
Verified on the rig: arm Pick Face, click the face, and it reads "#5" on the
first click. With nothing armed the escalation still alternates whole <-> face
as before.
|
||
|
|
5889f6640f |
CAD: extrude a sketch region with its holes, and stop crashing at startup
A rectangle with a circle inside it, drawn in ONE sketch, could not be extruded to a plate with a bore from the GUI. Five defects were in the way. Each was found by driving the app on a headless rig and measuring the result — the code reads correctly at every one of these points, which is why they survived. 1. Wire orientation (kernel). SketchEngine::wires_to_face added every hole as wires[i].Reversed(), which is only right when the sketch happens to wind both loops the same way. A circle drawn clockwise inside a counter-clockwise rectangle came out matching the outer boundary, OCCT swept it as a SECOND contour, and the prism was the plate with its bore filled and the disc's volume counted twice. Measured: bbox 67.17 x 219.67 x 10 with volume 152088 mm3 against a solid box of 147542 — a body larger than its own bounding box, which is the signature. Holes are now added as-is and ShapeFix_Face::FixOrientation() classifies them; that is winding-independent and is the idiom make_extrude_regions already used for imported glyphs, which is why holed TEXT always extruded correctly while a holed SKETCH never did. After the fix: 142996 mm3, implied bore radius 12.03 mm against the circle drawn. 2. The live-sketch click threw the picked region away. region_at() served only as a yes/no gate and on_face_selected() carried no argument, so Extrude fell back to whichever loop the resolver found first — clicking the material of a plate-with-a-hole extruded the disc. The region is now carried through, and DesignPanel also hands it to the tool with set_loop_pick(), AFTER open_tool() because that re-derives selection state, since extrude_uses_loop() reads selected_loop_entities() and that lives on the tool. 3. region_at() had no hole awareness and no innermost preference: it returned the first polygon containing the point. It now skips a region when the point lies inside one of that region's holes, and picks the smallest containing loop, so a click in the bore selects the disc and a click on the material selects the plate. 4. Startup segfault. DesignCanvas::request_repaint probed the GL backend via OpenGLManager::get_gl_info().get_renderer() before the canvas had initialised GL — glGetString with no context current and, before init_opengl(), no loaded function pointers. Anything that asked for a repaint while the panel was still being built landed there, with no window and nothing in the log. It now bails at the top on !is_initialized() and asks for a Refresh instead. Note the crash was in the PROBE, not in render(), which already guards itself. 5. A holed sketch on a plane whose normal points -Z came out as the full box PLUS a disc — 220274 mm3 where 163726 was due (192000 + 28274). wires_to_face took a SketchPlane parameter it never used and let OCCT infer a surface from the outer wire; when the inferred normal disagreed with the sketch's, the hole classification produced no hole. Every face is now built on the sketch's own gp_Pln. Also in this change, from the same rig session: - A right-click that only clears the sketch selection no longer reports itself as consumed, so it stops suppressing the offer menu. With any geometry in a live sketch there was no menu route left to add a second entity. - Escape no longer discards a live sketch that holds drawn geometry; it says so and keeps the work (live_sketch_has_work()). - The holed-region fill is an even-odd scanline instead of a keyhole bridge, so no corridor triangle leaks from the bore to the nearest corner. - Cyan is reserved for the selection: an unselected region no longer wears a shade one step off the selected one. - The origin planes follow the mode, so pressing Sketch on a document that already has a body offers them again instead of naming a plane you cannot see. Tests: three [holes] cases over add_extrude_entities asserting the plate-with-bore volume, solid and face counts on both a +Z and a -Z sketch plane, and the by-name refusal of two disjoint regions. Full [CadDocument] suite green. |
||
|
|
13922a52b6 |
Design status: clear the DoF line on leaving sketch mode, and wrap the HUD chip
Two independent leftovers, both in the same status area. snaporca-752: the "N degrees of freedom" line described a sketch's constraint state and stayed on screen after Confirm, Cancel and the Escape downgrade, in Feature mode where it means nothing — visible in every Feature-mode screenshot of the 2026-07-27 sweep. Cleared in set_ui_mode rather than at those three exits, because that is the one place all of them pass through and a fourth exit added later would otherwise reintroduce it. Constrain mode keeps the readout: that is where the number is the whole point. snaporca-8cc: moving the status out of the panel and into the viewport HUD removed the clipping, but not the underlying problem. The chip is a top-level popup that Fit()s to its text, so a long sentence grew past the right edge of the canvas and hung over the window instead of being cut off inside it — the same silent length limit wearing a different hat. The label now wraps to the room actually available (canvas width minus the view-cube inset), which is what makes the earlier promise that "a sentence can be a sentence" true at 1366 as well as at 1920. SetLabel + Wrap + Fit are now one function called from both the text change and the placement. Wrap() rewrites the label it is handed, so it has to follow a fresh SetLabel every time, and the placement path runs on resize — a chip wrapped for the old width either overhangs a narrowed canvas or wastes a widened one. The left inset is one constant now because the wrap width and the anchor have to agree, or the chip wraps to a width it is not then given. snaporca-752, snaporca-8cc. Reviewed and compiled (RC=0), not exercised. |
||
|
|
e6a14b39c9 |
Rib: the thickness gets its handle, so the whole tool is draggable
Rib's depth already reused the Extrude arrow. Its thickness could not: the arrow points along the plane normal, and thickness is an offset either side of the rib line, IN the plane. Different direction, different handle. Two square handles at mid ± perp·half, plus the slab's actual footprint drawn as a thin closed rectangle — the footprint matters more than the dots, because what a rib thickness means is how wide that slab lands on the body, and until now there was no way to see it before committing. A drag on either handle sets the FULL thickness, twice the perpendicular distance from the line, because the slab is centred on the line and the handle sits at half. Both handles behave identically for the same reason, so they share one colour rather than pretending to be two different actions. A zero-length line has no direction to grow a slab perpendicular to, so the shared rib_frame() helper returns false and render and drag both draw nothing rather than dividing by zero. Non-Line entities clear the gizmo instead of guessing: the kernel is line-only and a gizmo that guesses would be lying about what Confirm will build. Unlike the helix callback this one goes through refresh_preview(), because Rib builds a real solid ghost that has to rebuild. The helix has none and skips it deliberately. Both gizmos coexist and resolve the sketch and entity the same way, so the depth arrow and the thickness handles can never disagree about which line they are on. Reviewed and compiled (libslic3r_gui, RC=0); not exercised. snaporca-plew. |
||
|
|
e8306a6e9a |
Helix: draw the thing, then let the numbers be dragged
grep -i helix over the viewport code returned nothing at all. The tool was four coupled numbers and a Confirm button — you typed radius, pitch, height and taper blind and pressed OK to find out what you had made. So this is not only the charter's L2 failure; the tool had no visible state whatsoever while it was open. Adds a plane-anchored helix gizmo built on the datum-plane gizmo as its template, being the closest existing thing: also plane-anchored, also driven by a card while the sketch tool is inactive, also a render / hit-test / drag triad. It draws the live curve and the axis, and puts a handle on each of the three lengths: radius on the base circle, height at the top of the axis, pitch at the end of the first turn — which is exactly where one pitch of rise lands, so the handle means what it is standing on. Below one full turn the pitch handle moves to the end of the curve rather than floating off a curve that does not exist yet. Taper and handedness stay on the card. One is a shape modifier and the other a flag; L2 governs numbers you can point at. A drag reports the whole (radius, pitch, height) triple rather than one value, because pitch and height are coupled through the turn count and writing one alone would redraw a stale curve. The callback re-feeds the gizmo directly instead of going through refresh_preview(), since Helix takes the produces-no-solid early return and refresh_preview would rewrite the status line on every mouse move. REVIEW CATCH, fixed here: the first cut read taper as a fraction of the radius consumed over the turn count. It is an ANGLE IN DEGREES — helix_spine() builds a Geom_ConicalSurface of half-angle taper and takes the top radius as R+H*tan(taper), growing with the height risen. The wrong reading drew a preview that collapsed to a point for any non-zero taper while the committed feature was perfectly fine. A preview that lies is worse than no preview, which is what this commit replaced. Reviewed and compiled (libslic3r_gui, RC=0); not exercised. snaporca-i3jc. |
||
|
|
3f62d4d58d |
Bodies: colour that survives selection, hide that toggles twice, Delete that acts
Three defects behind one report ("bodies cannot be moved or hidden/shown or
deleted, colour does not work"). They are unrelated to each other; only the
symptom was shared.
1. The Color tool wrote a per-body override that was correct end to end —
stored on CadBody, carried across recompute (CadDocument.cpp:3381), read
back by DesignCanvas::body_color() — and then overpainted every frame.
m_body_selected is a DOCUMENT-WIDE flag raised whenever a non-Sketch
feature row is selected, which is the resting state after any modelling
operation, and while it was true every body rendered gold. An explicit
colour now outranks the selection tint; unpainted bodies still tint, which
is all the tint was ever for.
2. The eye toggle re-selected the body row through m_tree, using item ids that
belong to m_parts. The row came back unselected, so the second press found
tree_body_selection() == -1 and fell through to the feature-level branch
instead of un-hiding. Hide worked exactly once. The sibling call in
refresh_parts() had it right.
3. The tree card's Delete button answered a selected body row with "select the
FEATURE that created this body" — an instruction the user cannot act on,
because the tree does not say which feature that is. on_delete_body()
already resolves CadBody::source_feature and confirms by name; it was
reachable only from the right-click offer. The button now routes to it.
Reviewed and compiled (libslic3r_gui, RC=0); not exercised — needs a session at
the machine to confirm all three in the viewport. snaporca-zjvg.
|
||
|
|
3eec65f2bd |
CoordSys: pick a body first, x-ray the rest
In an assembly the face you want for a mate connector is nearly always behind another body, and a click only ever returns the frontmost hit. Hiding the occluder from the Parts list works but means leaving the tool mid-pick. The CoordSys card now carries a Body chooser. Pick a body and every other one drops to 0.25 alpha AND stops catching clicks, so the wanted face is both visible and reachable in one gesture. "(all)" restores normal picking. Deliberately NOT hit cycling: repeated-click cycling was removed from solid picking as a charter L5/§10 violation (DesignSketchTool.cpp, "NO CYCLE"), and re-introducing it here would make "click a face" a multi-click gesture again. Mechanics: DesignSketchTool::set_pick_only_body() gates body_pickable(), which every pick path already consults; DesignCanvas::set_xray_focus() drives both it and the per-body alpha in reload(). The chooser stays a pick FILTER only -- coordsys_body still comes from the actual pick, so nothing in the kernel moves. Body focus follows the CoordSys card: open_tool() reads it back from the combo rather than clearing outright, because editing a CoordSys feature loads the card (and its body) before open_tool runs. snaporca-bgvk. NOT COMPILED: deps/build lacks OpenVDB so the GUI tree will not configure here; reviewed by diff only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0a2faedc32 |
Design: draw the mate connector, so its verse and polarity are visible
A mate connector was visible only to a program. resolve_datum_coordsys had exactly ONE consumer in the whole tree -- McpControl.cpp, the agent socket -- so the frame every mate is built on could not be seen at all, and the two questions a connector has to answer on sight had no answer in the viewport: which way does Z point (the VERSE), and which of the pair is anchored versus about to move (the POLARITY). The glyph is Onshape's proven core plus the part nobody ships. Disc for the XY plane, one gold quadrant for the roll -- the only in-glyph answer to "where is X", which matters because Fastened and Slider lock the clocking -- and a Z arrow drawn on +Z ONLY, never double-headed. Polarity is carried by the head: a filled cone travels, an open collar receives. Onshape, Fusion, Inventor and FreeCAD all draw both ends of a mate identically, which is why "which part moves?" is a standing complaint; nothing here invents new semantics, it just stops hiding them. Polarity is read from the committed Mate features, not only from the open card. A connector some mate drives must read as driven whenever it is on screen, or the glyph tells the truth only while a dialog happens to be open. The card, when open, still wins -- that is the live intent. Judged on the rig rather than in a mock, which changed three decisions: - Three RGB axis arms lose to one Z arrow. Rendered side by side (SNAPORCA_GLYPH=A selects the Onshape-style trio), the three heads are as large as the 22 px disc, they bury the quadrant, and at an oblique angle they pile into a smudge -- and the trio is indistinguishable from the move gizmo and the bed triad, which are already RGB arrow trios in this viewport. - Depth off floats, depth on tears. With GL_DEPTH_TEST off, connectors on faces pointing AWAY from the camera drew their discs over the solid, so the part looked covered in frames that were on its back. Turning depth on fixed that and immediately z-fought: the disc is exactly coplanar with its face and came out a broken dotted arc. Depth ON plus a 0.7*upp lift along Z buys both, and scaling the lift by upp keeps it sub-pixel instead of opening a visible gap on zoom-in. - Foreshortening degenerates an arrow into a dot when the axis points at the camera. It now draws a ring instead of silently vanishing, which is what a naive projection does. Everything is sized in screen pixels via upp = 1/zoom, like every other gizmo here: a connector is a symbol, not a part, so it must not shrink with the model. Research and the empirical findings are written up in DESIGN_MATE_CONNECTORS.md section 8b; rig images in artifacts/shots/g-0*.png, vendor reference glyphs in artifacts/glyphs/. Not fixed here, and recorded rather than papered over: the quadrant collapses to a blob at a grazing angle, which is exactly when the roll is hardest to read (F4); roll-undefined in red makes the least important connector the loudest thing on screen (F5); and a true grazing view, a curved face, and overlap with the move gizmo are still untested. Also surfaced while testing and unrelated to drawing: add_mate accepted a mate between two connectors on the SAME body and duly transformed the body relative to itself -- a concrete instance of the missing validation already filed as G6. Fork parity unchanged: DesignCanvas.cpp 16, DesignPanel.cpp 30, the other four files 0. |
||
|
|
93395b7888 |
Design: escalate on the entity that was picked, and let the chip follow the window
Both found by Kimi reviewing the previous two commits, both then reproduced here before being touched. snaporca-97z. The re-pick escalation required m_solid_sel, m_sel_body, m_sel_face AND m_sel_edge to all match the previous pick. That looked stricter and was wrong: the edge branch sets only m_sel_edge and m_solid_sel, leaving m_sel_face as whichever face the ray happened to enter through — and a shared edge is entered through a different face depending on which side you view it from. So picking an edge and picking that same edge again from the other side compared equal edges, unequal faces, and refused the escalation the status line had just promised. Now the comparison is made at the level that was picked and nothing else. The edge id is already the stable global one from edge_index_of, so it identifies the edge without help from the face. Reproduced on the rig without needing to orbit, since two clicks 8px apart across an edge enter through different faces: pick -> sel=3 body=0 face=5 edge=3 ray -> body=0 face=0 re-pick -> escalated to whole body 0 pick -> sel=1 body=0 face=-1 edge=-1 Same edge, face 5 then face 0, escalation fires. The old condition could not. The frame-move case. The chip is anchored at an absolute screen position, and until now nothing told it the window had moved — only a resize, a status change or a tab switch re-placed it. Dragging the window by its title bar left it stranded where it was, verified on the rig by moving the frame and watching it stay put. wxEVT_MOVE on the top-level frame, alongside the ICONIZE and ACTIVATE binds from the previous commit. Two related cases are filed rather than bound, because the list of window-geometry events to chase is exactly what snaporca-lcq argues should stop: a layout change that translates the canvas without resizing it, and wxEVT_DPI_CHANGED. Not fixed, deliberately, and recorded on snaporca-97z: clicking the same FACE but landing within the vertex or edge tolerance resolves to a different kind and so does not escalate — that is the "smallest thing under the cursor" rule working as documented; and a vertex re-pick after moving the body compares stale world coordinates. Verified on both rigs. The cross-face edge case was exercised on orca_cad; DesignSketchTool.cpp is byte-identical across the forks, so snaporca inherits it, and its face-level escalation and empty-click clear were re-checked there directly. Fork parity unchanged: DesignSketchTool.cpp 0, DesignCanvas.cpp 16. |
||
|
|
6d1a4078ca |
Design: the status chip goes away with the window, not just with the page
Found by minimising the app on the rig with a face selected: the whole screen goes black and the chip is still drawn on the bare desktop. A wxPopupWindow is override-redirect — the window manager does not own it — so it neither iconises with its frame nor stacks behind other applications. IsShownOnScreen does not catch this either: an iconised frame still counts as shown, which is why the guard added for the tab case sails straight past it. So the frame has to say so itself: ICONIZE and ACTIVATE, both routed through the same show_status_hud the page change already uses. Restoring is safe — a popup cannot take focus, so our own Show() cannot re-trigger either event — and restoring while some other page is up still leaves the chip down, because show_status_hud(true) goes through place_status_hud's IsShownOnScreen guard. Verified on both rigs: chip up, minimise -> screen black and empty, restore -> chip back with its text and the face still selected. Restore while on Prepare -> chip stays down. This is the fourth defect from the same root, so snaporca-lcq now asks the question these binds keep deferring: whether the line should be canvas content, like the view cube and the round view buttons, rather than a window that has to be told about every way a window can stop being visible. Fork parity unchanged: DesignCanvas.cpp 16. |
||
|
|
9c3ce9b45e |
Design: clicking empty space lets go of the selection, and the status line follows its tab
Two things a click on nothing should already have done. snaporca-od0. A click that hit no geometry left the solid selection standing. A rubber band swept over empty space has always cleared it (pick_bodies_in_rectangle), and the two gestures cannot disagree about the same outcome. The visible cost was in the escalation that landed last commit: "click the face, click away, click the face again" arrived as the SECOND click on the same face and took the whole body, when the click away was the user letting go of it. Now the miss clears and says so. This gives up something real, deliberately: Thicken / Shell / Draft hold their input face in the panel's selection, so a stray click on empty canvas with one of those cards open hands that face back. Their handlers already write the "(pick a solid face)" placeholder and rebuild the ghost when the selection empties, so the card SAYS it lost the pick rather than confirming against a face the viewport has stopped highlighting. An orbit drag never reaches this branch — it exits at the 8px budget — so panning the view still does not deselect. snaporca-dlj. The status line is a wxPopupWindow, which is a TOP-LEVEL window: hiding the Design page does not hide it. Select a face, switch to Prepare, and the chip was still there reading "selected (whole body) — right-click for what applies to it" on a tab with no such selection and no such menu. Same cause, second symptom: a status update arriving while the page is hidden anchored against a client size that is not the size the page will have, and parked the chip on the tab bar. So: an IsShownOnScreen guard in place_status_hud, show_status_hud(bool) to take it down and bring it back with its text intact, driven from the page-changed handler. Verified on both rigs, not by reasoning about it: face 5 selected -> click bed -> "Nothing selected", tint gone -> click the same face -> face 5 again, NOT the body -> click it again with no click away -> whole body, so snaporca-gem is intact. Prepare -> chip gone; back to Design -> chip returns. KEYTRACE across the round trip shows shift+S then R still reaching the canvas (ui_mode 0 -> 1, Rectangle armed), which is the focus theft this popup replaced a wxFrame to avoid. Fork parity unchanged: DesignPanel.cpp 30, DesignCanvas.cpp 16, headers and DesignSketchTool.cpp 0. MainFrame.cpp is outside that set and was edited per fork. |
||
|
|
c32aa3f8ba |
Design: clicking the same face twice takes the body, and the status line moves onto the viewport
A click could point at a face, an edge or a vertex, but never at the body those
belong to: offer_selection_kind() can only return BodySolid when all three are
clear, which a viewport click never produces. The rubber band was the only door,
and the status line said "face 5 selected" while the user believed they had taken
the body. A second click on the SAME sub-element now escalates to it (snaporca-gem).
Not the pick cycle that was removed in bc2b741ce9 -- that one was silent and three
deep, so no click had a predictable meaning. Here the status line names the next
click before you make it, and a further click just takes the face under the cursor
again, which needs no teaching. Double-click is untouched: wx sends Down/Up/DClick/Up
and only the first Up carries a pending press, so a fast double-click still zooms to
fit and picks once.
The status line itself moved to the base of the viewport. In the side panel it was
clipped at ~73 characters with no warning and no wrap -- set_status()'s Wrap() never
took effect (snaporca-8cc) -- which silently length-limited every hint in the tab; the
first version of this change lost a clause to it. m_status is kept, hidden, as the
owner of the text and its colour, and the line is drawn in a bottom-left twin of the
readout HUD where there is a whole window's width.
Three defects found driving it on the rig, none of which the build could see:
* the HUD as a wxFrame took the WM's keyboard focus every time it was raised, and
the canvas then received NO key events -- every sketch shortcut silently dead.
Caught with SNAPORCA_KEYTRACE: shift+S logged a line, the following R logged
nothing. It is a wxPopupWindow now, which cannot be focused. SetFocus() on the
canvas does not fix it: focus was on another toplevel.
* zero vertical padding fits the popup tighter than the font's line box and clips
the glyphs; 6 (what the readout uses) reads as a two-line box. 3 is right.
* "has a caller chosen a colour?" compared the label's foreground against its
PARENT's, which differ by default, so every line counted as chosen and the
neutral text came out the panel's dark grey -- invisible on a dark chip. Compare
against the colour the label was created with, captured before any caller writes.
Verified on both rigs against fresh binaries: sketch -> extrude -> click face ->
click again -> whole body tinted, offer opens with the body rows live and Create /
Add material correctly greyed. Keyboard drives the whole sequence.
Filed and NOT fixed here: snaporca-od0 -- a bare-plate click does not deselect the
solid, so "click away, click back" escalates. Pre-existing; clearing there would also
drop the face the Thicken/Shell/Draft cards hold, which needs its own pass.
Refs: snaporca-gem, snaporca-8cc, snaporca-od0
|
||
|
|
bc4fb3b680 |
Design: Text and SVG draw INTO the open sketch instead of beside it
Mirror of snaporca 1fb7786d9a (DesignPanel.cpp and DesignCanvas.cpp applied as patches; parity 30 / 16, shared files byte-identical). With a sketch open, Text/SVG outlines become ordinary Line entities via push_closed_lines() instead of a separate Sketch feature carrying rigid imported_regions — so the letters can be constrained, trimmed and extruded like anything drawn by hand. The buttons and offer actions arm Select first when in Sketch mode, since begin_sketch() does not run until a tool is armed. add_imported_regions() calls reset_autoedit(): without it the glyph contours entered the draw-then-edit queue and opened a Length field on the first segment, which freezes the canvas. Caught on the rig, not by reading. No sketch open: unchanged — a new Sketch feature, still dropped on a picked face. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM |
||
|
|
a1fdb9f217 |
Design: double-click a sketch stroke to edit it — the gesture belongs on the geometry
Selecting a committed sketch line lit the right tree row and then told the user to go and press Edit in the panel. That is the side-panel dependency this tab exists to remove, and it made "selectable" true while "editable from the geometry" stayed false. on_edit_feature already does the whole job — re-open the entities in the sketch UI with handles and live quotes — and was only ever reachable from a tree row. A double-click on a committed stroke now calls it for that feature. Double-click on empty space still fits the view, so nothing is taken away. The stroke hit test is now one hit_display_sketch() shared by the click and the double-click. Two copies of "what is under the pointer" drift, and a double-click acting on a different entity than the click before it is a miserable thing to chase. It also reports the entity index, which the tracer prints, so a pick that lands on the wrong stroke can be seen rather than inferred. Verified on :11 end to end: draw an open line, commit, double-click it. The tracer prints "double-click -> edit sketch feature 0 (entity 0)", the panel reads "Editing sketch — drag a handle or click a quote to edit", and a click inside the session selects the line with endpoint handles, live quotes and "1 selected — Delete removes them". NOT delivered by this commit, found while verifying it: typing a new value into a length quote is accepted and displayed but the geometry does not move and the solver drops to "Conflicting constraints". Filed separately — it lives in the constraint layer, not in selection, and nothing here touches it. Refs snaporca-e1p. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM |
||
|
|
9f2b2bc511 |
Design: sketch means a tool — the offer works inside a sketch, and the app stops
contradicting itself about the plane Reported from the rig: pick a plane, press Sketch, and you are told to pick a plane. The app prescribed a sequence and then refused to acknowledge that you had followed it. Right-click did nothing, so there was no way to reach a drawing tool except the toolbar this tab exists to retire. act_sketch was two lines: set the mode, then print "Click a face or a reference plane in the viewport, then a sketch tool" — unconditionally, without ever asking whether a plane was already chosen. The plane was never lost; m_ref_plane held it and begin_sketch captures it when the first tool is armed. The sentence was simply false. It now asks. sketch_plane_target() is a companion to sketch_plane_from_selection that distinguishes "the user chose XZ" from "nothing chosen, falling back to XY" — a distinction m_ref_plane cannot express on its own, being always a valid index, so m_plane_picked carries it. With a target the readout names it and the offer opens on the Create row; without one the old prompt stands, because then it is true. The card above the status line was a local wxStaticText that nothing could update, so it went on asking for a plane two inches from a line saying the plane was chosen. It is a member now and the two are written together. Right-click inside a sketch was excluded wholesale so that it could end a polyline chain, abandon an anchor, exit a tool. That made every sketch row in the atlas unreachable. The honest test is not which mode we are in but whether the tool actually USED this right-click, and only the tool knows: on_mouse now wraps on_mouse_impl and records that once, for every terminator, instead of threading a flag through the twenty-odd sites that consume a RightDown. The canvas read-and-clears it on the matching release. Underneath all of it was one confusion — MODE versus SESSION — at four sites. begin_sketch does not run until the first tool is armed, so is_sketching() is false for exactly the interval between "press Sketch" and "pick a tool", which is precisely when the drawing tools must be on offer. The keyboard learned this once already (snaporca-0ud, whose comment states the rule) and I reintroduced it in offer_selection_kind and again in show_offer_menu, where the offer built from the FEATURE map and rendered nine rows that all refused the sketch selection. Both now call sketch_map_applies(), so they cannot drift apart again. The keyboard keeps its own split: its "sketching" gates undo and delete-last-entity, which genuinely need a live session. Verified on :11 against a fresh build. Pick XZ, press Sketch: card reads "Drawing on XZ", status reads "Sketching on XZ — pick a tool", offer opens with Create and Reference live and the six rows needing geometry greyed. Right-click while idle opens the offer. Right-click as a terminator does NOT — the chain ends, the line lands on XZ, its length field arms at 49.36 mm. That last one is the regression the blanket exclusion was buying and the reason this shape of fix was chosen over a mode test. Refs snaporca-6vs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM |
||
|
|
bb403b82cf |
Design: left-drag sweeps a rubber band, and it takes the whole body
The pick cycle died two commits ago and left no viewport route to a whole body at all: one click resolves vertex, edge or face, double-click is already zoom-to-fit, and the only way to take a body was the Bodies list — a geometry-first violation for as long as it stood. The rubber band is that route. Left-drag is the gesture, as asked. That button was orbit, so this canvas now maps the mouse the way every CAD the user already knows does: left selects, middle orbits, right pans. The change is a single flag on GLCanvas3D set only by DesignCanvas, so Prepare and Preview keep the mouse their users learned. Sketch mode inherits the same mapping, which is the consistent reading — Design is one modality, not two. Past an 8 px budget a press becomes a sweep, anchored at the ORIGINAL press point rather than at the frame where the threshold was crossed, so the first few pixels are not lost. Below the budget it is still a click and the existing vertex/edge/face pick runs untouched. Sampling is the display mesh's triangle vertices plus centroids — the same points the ray pick tests, already in world coordinates — and the body with the most samples inside wins, because the selection callback downstream carries one body. Crossing semantics: touching selects. Enclosed-only for left-to-right and crossing for right-to-left is the fuller CAD convention and is deferred, not forgotten; with one selectable body it would have bought nothing. Two defects fixed on the way, both found by exercising this: Right-drag pans, and every pan ended by popping the offer over wherever the camera stopped — the context menu arriving as the reward for moving the view. The offer is now the release of a STATIONARY right-click, at the same 8 px budget the pick uses. The selection handler wrote m_status twice. Only the later write ever reached the screen, so the earlier block had been dead since it was written, and its labels drifted out of step with the live ones unnoticed — including a vertex fix I made this morning in the branch that never renders. Deleted, with a note saying why, rather than left as two writers for the next person to pick the wrong one. Verified on :11 against a fresh build: click takes face 5; left-drag across the body reports "selected (whole body)" with the whole solid tinted and the camera unmoved; left-drag over empty space clears; stationary right-click opens the offer; right-drag pans with no menu; middle-drag orbits. Precedence re-checked after the deletion — face at 25 px from the corner, vertex from 10 px in. Refs snaporca-9xw. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM |
||
|
|
7114e316ea |
Design: the offer ships — right-click the geometry, get what applies to it
Row order is RATIFIED (charter 4.1, 2026-07-31) and this is the first working implementation of it: right-click in the Design viewport and a vertical list opens at the pointer with the eight families in their fixed order, the verbs that apply live, and the ones that do not disabled IN PLACE carrying their reason. THE MAP EXISTS ONCE. DesignOffer.hpp is GENERATED from docs/ux/tool_atlas.json by docs/ux/mockups/gen_offer_table.py — the same file the 113 mockups are drawn from. A drawing and the product therefore cannot drift apart, which is the only way row constancy survives contact with a codebase. Never hand-edit the header. NOTHING IS RE-IMPLEMENTED. Each row routes to the code that already runs that verb: "key:S+E" through m_keys_feature, "key:L" through m_keys_sketch, "fly:material#4" through the feature flyout's own action, "btn:colour" through the standalone button. The offer is a second door onto the same room, so the toolbar, the shortcuts and the menu cannot drift into three behaviours. The 8 verbs with kernel support but no GUI path show disabled, which is honest and matches section 10 of the charter. Right-click only fires the offer when the canvas is IDLE. Right-click already ends a polyline chain and finishes the move gizmo; taking those over would break two working interactions to add a third. Two things the running build corrected, both found by looking at screenshots: - THE REASON MUST BE TRUE FOR WHAT IS IN FRONT OF THE USER. Taking the first refusal in a family printed "Transform needs a body — add or import one first" on a document that HAS a body, because the real obstacle was that nothing was selected. Now the reason comes from a verb that accepts the current selection and fails only on document state; if no verb in the family accepts this selection at all, it says "select something first" or says nothing. A menu whose whole value is telling the truth cannot ship a lie. - Classification follows the level the pick cycle has REACHED, not the face the ray happened to hit, so the header cannot name a face while the whole body is lit. Sketching on the face you merely clicked is untouched — that path is sketch_plane_from_selection (snaporca-3a2). Verified on :11 end to end: nothing selected shows Sketch live with Shift+S and seven greyed rows each explaining itself; a selected solid shows Move directly with Shift+Y (one applicable verb, so no submenu and no extra click) and five families as submenus. Both forks compile and link. Fork parity re-checked after the port: DesignPanel.cpp 30 divergent lines, DesignCanvas.cpp 16, every other CAD file byte-identical — the invariant exactly. snaporca-96r. |
||
|
|
8621f1168e |
Design tab: show/hide the printer bed; give Placement its own toolbar slot
Port of snaporca ca25352d74. Hand-applied rather than cherry-picked: unlike the
Phase B commits, which only touched DesignPanel.{cpp,hpp} and transfer verbatim,
this one reaches into GLCanvas3D and DesignCanvas, where the forks genuinely
differ — mainline passes m_show_world_axes to _render_bed where Snapmaker passes
a local show_axes, and the surrounding code sits ~90 lines further down. git am
refused, correctly; the six edits were applied against mainline's own context and
the DesignPanel half came across as a patch.
Bed toggle: a "Bed" checkbox in the document/view row, on by default, in that row
rather than in a card because a view option must stay reachable with no tool open.
It drives GLCanvas3D::m_show_bed (default true, so Prepare and Preview are
untouched) and gates _render_platelist as well as _render_bed — hiding the bed
while leaving its grid and outline floating would read as a rendering fault.
Bound to wxEVT_TOGGLEBUTTON, not wxEVT_CHECKBOX: Orca's CheckBox derives from
wxBitmapToggleButton, so a wxEVT_CHECKBOX handler never fires.
Also gives the Placement drawer its own "placement" toolbar slot. put("place")
already holds the Place-on-Face button, and put() formats slot item 0 as the
control and later items as its chevron, so sharing the slot bottom-aligned the
drawer's button like a chevron.
196/196 targets, 0 compile errors, orca-slicer links (165 MB). The build script
still exits non-zero at the AppImage bundling step on libpython3.12.so.1.0 —
that is snaporca-96t, packaging only, and does not affect the binary.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
8d529ad413 |
Size the Design move/rotate gizmo from the body, like Orca's Prepare gizmos
The Design gizmo already had both move arrows and rotation rings, but drew them at a fixed 70 px arm regardless of the body: on a 40 mm cube everything collapsed into a ~100 px tangle buried inside the solid, so the rings were effectively invisible and the tool read as "move only, no rotate". Orca's Prepare gizmos size themselves from the selection's bounding sphere (GLGizmoRotate3D: m_radius = Offset + sphere radius) so the handles always clear the object. Same rule here: DesignPanel passes the body's bounding-sphere radius (scale-aware) into the gizmo, and move_gizmo_arm() returns max(70 px, 1.25 * radius) — the screen-space floor keeps it grabbable on a tiny body or when zoomed far out. Ring radius and BOTH hit-tests derive from that one helper, so picking cannot drift from what is drawn. Verified on a 40 mm cube: rings now encircle the body, arrow drag moves with a live mm readout, ring drag rotates live. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q |
||
|
|
6d9368276c |
Fix Design tab not painting on hardware GL after a page switch
request_repaint() only invalidates the canvas (Refresh()) on the hardware-GL path and relies on a wxEVT_PAINT to follow. When the notebook re-shows the Design page, that invalidation is issued mid-show and dropped: no paint event arrives, the canvas never renders, and the pane stays blank until another tab switch forces an expose. Software GL renders directly, so it never showed there. force_repaint() defers past the show, then Refresh() + Update() for a synchronous paint. Called from on_tab_shown(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q |
||
|
|
1673c2c760 |
Design tab: keyboard shortcuts, plane/axis toggles, section view
- Keyboard shortcuts (Onshape-style, three scoped layers): feature tools on Shift+letter, 2D sketch tools on single letters (in-sketch), view/nav on single letters (out-of-sketch). Dispatched via the DesignPanel CHAR_HOOK. - View toggles: P = origin planes, A = world axis triad (render_view_helpers). - Section view (non-destructive): a single horizontal clip that hides half the model to inspect inside, showing only the solid remaining half (no ghost). Left-panel "Section View" button or X toggles it; PageUp/PageDown move the plane; "Flip Section" button or F shows the opposite half. Never a body/Cut. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q |
||
|
|
de16d566b2 |
Design tab: value-label titles + 5 sketch/UX fixes
Six fixes to the Design tab, all live-verified on :10: - Datum-plane re-pick: wxEVT_CHOICE on m_draw_plane re-planes the live sketch - Delete-feature dismisses its lingering settings card (on_delete_feature) - Revert rotation-orbit regression (no feed_bodies/reload on close_tool) - Sketch undo/delete via focus-independent CHAR_HOOK (Ctrl+Z/Y, Delete) - Fix undo hang: reset_autoedit() clears dangling auto-edit sequence - Titled value fields: each inline dim field shows its role (Width, Height, Radius, Angle, Length, Side, Distance, Major, Minor) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q |
||
|
|
5a005d4728 |
Design: in-canvas gizmos for Draft/Cut + operand highlight for Boolean/Sweep/Loft
Closes the gizmo-parity gap (bd snaporca-4h5): the five solid features that were
card-only now give in-canvas feedback like their siblings.
Draft — angle-arc drag gizmo (clone of the Revolve gizmo): once a side face is
picked, a cyan arc anchored at the face centroid shows the taper angle; drag the
tip or type the angle, the live ghost tapers with it. Axis = world +Z (the neutral
pull direction), clamped [-89, 89].
Cut — plane offset-arrow + cutting-plane rectangle (clone of the Shell arrow, adds
a wire rectangle in the cut plane sized to the target body bbox). The arrow drags
the signed offset along the plane normal; the rectangle rides at the cut position;
the ghost splits live. (Also covers bd snaporca-1gh / snaporca-mmr.)
Boolean / Sweep / Loft — operand highlighting (new by-index highlight infra):
- Boolean tints the target body teal-green and the tool body orange (per-index
body tint added to the DesignCanvas GLVolume colour loop + set_operand_bodies).
- Sweep tints the profile sketch cyan and the path sketch magenta.
- Loft tints every selected profile sketch green.
Sketch tints reuse the DisplaySketch overlay via a feature-index -> colour map
(sketch_hl_color); DisplaySketch struct unchanged. All self-gate by active tool
and clear on close_tool.
Wiring mirrors the existing gizmo pattern 1:1 (m_*_active / render_* / set_* /
clear_* / update_* in refresh_preview / DesignCanvas passthroughs / render dispatch
+ on_mouse drag branch). Both forks; DesignSketchTool.{cpp,hpp} + DesignCanvas.hpp
+ DesignPanel.hpp byte-identical across forks. Built clean on both. Draft, Cut and
Boolean highlight live-verified on :10; Sweep/Loft sketch tint is code-complete and
build-clean (visual check pending — needs hand-drawn profile/path sketches).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
|
||
|
|
71b7a73e86 |
Design tab: reference planes at bed centre, slot dims, hole cube handle, real threads
Port of the snaporca CAD work to the mainline fork. - Onshape default planes (XY/XZ/YZ) at the bed centre (transparent, labelled); modeling origin unified to the bed centre (CadDocument::modeling_origin); world-axis triad moved to the bed centre on the Design canvas only. - Datum plane: clickable ghost-plane base pick + draggable offset arrow. - Slot: dims reassessed to inter-centre distance / radius / angle; fixed the duplicate cap-arc radius quote. - Hole: 3D cube move-handle on the face; binds to the face on the first click; decluttered side-distance construction lines. - Thread: derive the M spec (diameter/pitch/depth) from a picked cylindrical surface or circular edge (GeometryEngine::circle_of_edge); fuse the helical ridge onto the existing body; MakePipeShell fixed-binormal sweep (uniform, no twist) + self-intersection/param guards. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q |
||
|
|
a121e30a13 |
Design canvas: gate direct-render hacks behind sw-GL detection
Mirror of snaporca-cad f3b665b. DesignCanvas had ~38 call-sites doing `set_as_dirty(); render()` directly, tagged "llvmpipe: force repaint" — software-GL workarounds from the headless :10 test box. On hardware GL this bypasses the dirty/refresh cycle and burns frames outside the paint event. Funnel all 38 through a new request_repaint() helper that branches on the cached GL renderer string: hardware GL gets set_as_dirty()+wxGLCanvas::Refresh() (render() runs inside the paint cycle), software GL keeps the direct render(), undetected backend takes the safe direct path. Behaviour on the llvmpipe :10 box is byte-identical; hardware GL invalidates the canonical editor way. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q |
||
|
|
c22351f63a |
Fix GUI build on mainline OrcaSlicer: DropDown Item API + Bed3D::set_shape
Two Snapmaker->mainline API divergences surfaced once the ported CAD subsystem compiled (the whole libslic3r CAD kernel + vendored slvs solver built clean): - DesignPanel: Snapmaker's DropDown took 3 parallel vectors (texts/tips/icons); mainline's is Item-based (std::vector<DropDown::Item>). Collapsed both flyout structs (FeatFlyout, ToolFlyout) to one Item vector. Event/selection path unchanged (both emit wxEVT_COMBOBOX + SetInt). - DesignCanvas: mainline Bed3D::set_shape added extruder_areas/heights params before custom_model; pass empty vectors. Build verified: links clean (orca-slicer, 280/280). Runtime reaches the GTK event loop equivalently to the proven-good snaporca binary; full visual Design-tab verification pending an interactive :10/x11vnc session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q |
||
|
|
0f4060c0a9 |
Orca-Cad: port SnapOrca Design (parametric CAD tab) onto mainline OrcaSlicer
Grafts the sketch-first CAD environment from snaporca-cad onto the mainline OrcaSlicer/OrcaSlicer base (vs snaporca's Snapmaker/OrcaSlicer base): - 133 new files: CadDocument/SketchEngine/GeometryEngine/SketchConstraints/ SketchSolver/SketchInference/ThreadStandards + vendored libslvs solver; DesignPanel/DesignCanvas/DesignSketchTool/SketchInlineEditor GUI; GLGizmo Primitive/Sketch; 75 design icons; Catch2 tests. - Integration hooks ported to mainline's diverged versions: Design tab in MainFrame, embedded design viewport + sketch overlay + per-canvas chrome suppression in GLCanvas3D/PartPlate, gizmo registration, Plater accessors, CMake wiring (libslvs subdir, CAD sources, OCCT ModelingAlgorithms=ON). Structural integration complete; build verification pending. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q |