mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
d7583f0a2d96bcd1b3c5eb4cd32c01dfcf740e03
30090
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d7583f0a2d |
docs: the grammar becomes object-driven, and commit stops being invisible
Two changes to section 4, both from Tommaso. FIRST: the selection does not merely feed the tool, it DETERMINES WHICH TOOLS EXIST. Point at a planar face and the product offers the small set of things a planar face can become; point at an edge and it offers fillet, chamfer and the sketch tools that can reference it. Nothing else, because nothing else is possible. This is the largest single thing available to us for a first-time user, and the reason is worth writing down: a beginner's difficulty is not operating a tool, it is not knowing which tools apply to what they are looking at. Sixty icons answer a question they cannot yet ask; a face that offers its own five verbs teaches the product by being used. It also deletes a whole class of failure — a tool that silently does nothing because the selection was wrong becomes unreachable. The offer is an accelerator, not a toll gate: toolbar and single-letter shortcuts keep working unchanged and consume the same selection, so an expert never looks at the offer and a beginner never needs the toolbar. Both routes land in the same place, which is how one interface serves all three audiences. SECOND: "click empty space to commit" is withdrawn. It was an invisible gesture with a destructive meaning — nothing on screen said it, and a stray click committed a feature still being adjusted. Exactly what L5 forbids. A pending feature now carries a confirm/cancel puck attached to its own geometry, beside its handles, with Enter/Escape mirroring it; empty space reverts to the safe meaning, clear the selection. The puck is an object in the scene, not a dialog: the camera orbits, the values stay editable, nothing is blocked (L4 intact). Two cases the rule has to get right or it damages the inner loop: continuous tools (line, rectangle, circle) still commit each entity on its own gesture — a tick per line would be miserable — and Enter/Escape end the tool rather than confirm an entity. And ambiguity resolves toward keeping work: starting another operation with a valid feature pending commits it rather than discarding it, because undo reaches everything and the recoverable direction is the right default. Section 10 gains the two honest consequences: today a selection offers nothing (the largest single item of new work this charter asks for) and committing is still the invisible empty-space click. snaporca-2is. |
||
|
|
610acfcd37 |
docs: reach is the first accessibility, and it gets a law
The charter had accessibility only in the assistive sense — keyboard, contrast, colour, targets — and said nothing about who can get through the door in the first place. That was the larger omission. The premise of an OSS CAD tool is that a kid on a school laptop, with no licence, no account, no fast machine and nobody to teach them, can open it and make a real thing; a tool that only the equipped can run reaches people who were already going to design something. So the fourteen-year-old is now the FIRST of three audiences, ahead of the maker and the mechanical designer, with an explicit rule that when audiences conflict the earlier one wins unless someone writes down why not. L11 states the floor: runs completely on a low-end laptop with integrated graphics at 1366x768, offline, no account, and no capability withheld behind a tier, a plugin or a cloud service. Section 6 splits into 6.1 reach and 6.2 the assistive floor: the reference machine, the small screen as the layout target rather than the stretch case, files that belong to the user, learnable with no documentation, plain language at the entry tier, and exploration that is never punished — undo reaches everything, nothing asks the user to be sure. Consequences elsewhere: the screen budget is set by the smallest screen we serve, not the reviewer's monitor; the PR gate gains a reach question; B6 joins the benchmark (the inner loop on the reference machine, offline, fresh install) and every task is measured there rather than on a workstation. The side-panel debt now fails L11 as well as L1 — on that screen the cards leave the model a strip. One role addition: the absent audience needs a seat. The kid cannot file an issue, so someone owns B5/B6 and the group watches real first-timers quarterly. Approachability is the one thing here that cannot be argued from principle. snaporca-2is. |
||
|
|
76690f66e9 |
docs: the UX charter names only this fork's product
The doctrine is shared but the document is not: each fork's copy now speaks about its own product only, so it reads as that project's own charter rather than as a note about a sibling repository. This is a deliberate divergence — the two copies must NOT be reconciled by a parity sweep. The CAD sources stay byte-identical; only this doc branches. snaporca-2is. |
||
|
|
8cc08845a8 |
docs: UX guidelines and charter for the Orca-CAD design group
The call with SoftFever settled that Orca-CAD is one of the branches to be implemented and that a design+dev group forms around it. A group without a written doctrine reviews by taste, and a CAD reviewed by taste becomes FreeCAD one locally-reasonable side panel at a time. So the doctrine is written first, as something a reviewer can FAIL a pull request against: ten laws each with its own test, the interaction grammar they compose into, the accessibility floor as a merge requirement, and a ten-question gate answered in every UI pull request. The position is Shapr3D's interaction economy, not its feature list — direct, gestural, almost no chrome, depth revealed by what you touch. Depth for mechanical designers arrives as progressive disclosure of tools that never move, in three tiers, non-modal, with assemblies and exploded views obeying the same point-then-act grammar as a beginner's extrude. The one thing neither Shapr3D nor FreeCAD has is that we live inside a slicer: plate, nozzle, material and build volume are known at design time, so print-domain failures are warnings on the geometry, not a report. Section 10 is an honest inventory: what already complies, and the six things that violate the laws today, none of them defended. The appendix keeps the anti-patterns we have already paid for, because each one is cheap to reintroduce. snaporca-2is. |
||
|
|
f14d31d956 |
scripts/gui-session.sh: bring the headless GUI up without clicking blind
The relaunch sequence was an ad-hoc pile of docker exec one-liners, and it had a real bug: it dismissed the first-run dialogs by computing the titlebar close box from `xdotool getwindowgeometry --shell` and clicking it. When the dialog had already closed, that eval left the geometry variables stale or empty, the click landed at a garbage coordinate, and it kept hitting the Sketch button in the toolbar underneath — so the app came up in sketch mode with a stray Sketch feature that then had to be cancelled by hand. Three times in one session. The fix is not a different mechanism. `xdotool windowclose` looks cleaner and KILLS THE APP: it destroys the GdkWindow out from under the dialog and the process dies with "GdkWindow unexpectedly destroyed", three GLib-GObject criticals and a segfault. Measured, not guessed — that is what the first version of this script did. Escape does not close the Setup Wizard either, which is why it needs handling at all. So the titlebar click stays, and what changes is that it refuses to click geometry it has not validated: the window id is re-resolved immediately before, all four geometry variables are unset first and must come back numeric, and the computed point must be inside the screen. Any of those failing logs why and clicks nothing. Two further honesty fixes in the status output, both caught by reading it rather than by it failing: app_pid skipped nothing, so with a container full of <defunct> instances it printed a dead pid as though the session were healthy — it now walks /proc/<pid>/stat and ignores zombies. And a container without x11vnc reported "vnc: DOWN" as if something had broken, when nothing was ever installed; it now says so, and does not try to start what is not there. Also replaces the fixed post-launch sleep with a wait for the main window, because cold starts under software GL vary by a lot, and adds --status for diagnosis: "no windows but the desktop is up" means the app died, "cannot connect at all" means the desktop did. That distinction cost real time to work out by hand. Verified on both containers: one run each, wizard closed on validated geometry, no stray sketch mode, live pid reported, and the app still up. snaporca-e1p adjacent (tooling, not the tab itself). |
||
|
|
3f8f46f93f |
Delete the sketch plane dropdown; the viewport decides
The plane combo is gone. A sketch takes its plane from what is picked in the viewport: a face on a solid, or one of the reference-plane ghosts, clicked in 3D. The card is now a single line of instruction instead of a control. The combo had become worse than redundant. Once a picked face could be the plane it displayed a row that CONTRADICTED the actual target — it still said XY while the sketch went onto the face — so the one place a user could look to confirm where they were drawing was the one place guaranteed to be wrong. What replaces it is state, not UI: m_ref_plane records which reference plane was last clicked in 3D (0/1/2 = XY/XZ/YZ, >=3 indexes the datums) and ref_plane_name() turns it into text for the on-geometry hint. Clicking a ghost plane while a session is live re-planes it immediately, which the combo's own handler used to do; that behaviour is kept, just driven from the geometry instead of the widget. A plane click also drops a stale face pick, so last pick wins in both directions. populate_plane_choices() stays — seven other pickers use it (Plane base, Axis A/B, Helix, Project, Mirror, Cut). Those are the next candidates, tracked on snaporca-e1p; this commit only removes the one that had become actively misleading. Also: tessellation now matches Orca's OWN STEP importer, linear deflection 0.003 instead of 0.01 (Format/STEP.hpp default; angular was already 0.5 rad and unchanged). The Design viewport was never using a different rendering technique — it hosts a real GLCanvas3D, builds a real Model/ModelVolume and goes through the same reload/GLVolume path and the same shaders as Prepare and Preview. What differed was the mesh handed to it: 3.3x coarser than anything else in the application, which is why a curved face read as faceted beside an imported part. Suite unaffected at 154 cases / 2125 assertions, so nothing depended on the old density. Verified on :10: the card shows no dropdown, one click on a face then a sketch tool still reports "on the picked face", and the circle is drawn in that face's plane (artifacts/shots/h3a2-02-sketch.png, h3a2-03-drawn.png). snaporca-e1p, snaporca-3a2. |
||
|
|
3c0843c68c |
Sketch on the face you clicked, not the one you clicked twice
The previous commit made a picked face the sketch plane and I verified it by clicking the face TWICE. That was the wrong test. handle_solid_click cycles whole -> face -> edge, and "First click on a (new) body/face selects the WHOLE solid; refine on repeat clicks" — so at level 1 m_sel_solid_face is -1, and one click on a face, which is what selecting a face means to anyone, still fell through to the plane combo. The fix was real and unreachable, which from the outside is indistinguishable from no fix at all. The face id was never missing. handle_solid_click resolves it by ray on the FIRST click and passes it to on_solid_selection_changed regardless of the cycle level; the panel simply discarded it whenever level < 2. Keep it in m_pick_face/m_pick_face_body and let a sketch use it, preferring an explicit face-level selection when there is one. Nothing about the cycle changes, so body operations that rely on whole-body selection are untouched. The new state is dropped wherever the existing picks are, so a stale face cannot come back: choosing a body from the Bodies list (an explicit choice with nothing pointed at), picking a committed sketch loop (last pick wins), undo/redo (recompute invalidates topology ids), and when a sketch consumes the face. Verified on :10 with ONE click, which is the flow that was broken: build a box, single-click its top face, S then C, and the hint reads "Circle — click center, then radius · on the picked face" with the circle drawn in that face's plane (artifacts/shots/g3a2-01-one-click.png, g3a2-02-sketch.png, g3a2-03-drawn.png). GUI-only, so the kernel suite is unaffected — plane_of_face and its 154 cases / 2125 assertions are unchanged from the previous commit. snaporca-3a2. |
||
|
|
6ce20d78c3 |
Sketch where the user pointed: a picked face is the sketch plane
Selecting a face and sketching on it is the most common gesture in solid
modelling, and it was impossible. The plane came from a combo holding
XY/XZ/YZ plus datums, and plane_from_choice had no face branch at all, so
the only route onto a face was to build a Coincident datum plane on it
first, confirm that, reopen the sketch and find the datum in the
dropdown. Three extra steps and a junk feature in the tree.
The fix is not another combo row. A new sketch now takes its plane from
what is SELECTED IN THE VIEWPORT: a picked planar face wins outright, and
only when nothing is picked does it fall back to the reference plane —
which is itself normally set by clicking one of the ghost planes in 3D,
not by opening the combo. The tool hint names the target ("Circle — click
center, then radius · on the picked face") so the choice is visible on the
geometry side rather than needing a control to read back.
CadDocument::plane_of_face is the shared derivation, so the sketch path
and the Coincident datum method cannot drift apart. It refuses
non-planar faces: face_normal_world evaluates at the mid parameter, which
on a cylinder or a fillet is a tangent plane at one arbitrary point —
fine for offsetting a datum, wrong as a sketch plane, and silently
sketching on a tangent is worse than declining.
Picking the face also CONSUMES it. Leaving the pick live meant the next
Extrude saw a selected face and push/pulled it instead of extruding the
sketch just drawn — the same trap the imported-art path already guards
against.
Verified on :10 end to end with no combo interaction: build a box, click
its top face twice to cycle whole -> face, press S then C, and the circle
is drawn in the plane of that face with its Radius tab on the geometry
(artifacts/shots/f3a2-03-face.png, f3a2-04-sketch-on-face.png,
f3a2-05-circle-drawn.png). Kernel side: 154 cases / 2125 assertions green
on both forks, including that a cylinder resolves exactly its two flat
caps and refuses the barrel.
Still side-panel-shaped and to be dealt with separately: the Plane combo
remains on the card and now merely displays a stale row when a face is
the real target. It should show the actual target or go away.
snaporca-3a2.
|
||
|
|
7f0a8c85ee |
An entity sketch that forms no wire fails, instead of extruding a default box
entities_to_wire handles exactly two shapes: one lone closed entity (Circle/Ellipse), or a chain of open ones (Line/Arc/EllipseArc/BSpline). Anything else -- a circle coexisting with a line, two circles -- returns a null wire. build_sketch_wire answered that by falling through to its legacy tail, which ends in a rectangle built from width/height. For an entity sketch those fields are whatever they were initialised to, so the extrude produced a box the user never drew, silently and with ok:true. That is how the ellipse+stray-arc case in the P2 Tier-B.1 verification turned into a default-rectangle solid. Throw there instead. The legacy profile/shape paths below are still reached by sketches that legitimately carry no entities at all, so the enum and profile constructors are untouched -- only the case where entities exist and cannot be turned into a wire now fails, which is exactly the case that was fabricating geometry. This does NOT implement the multi-loop support the issue asks for. Doing that properly means deciding containment -- a circle inside a rectangle is a hole, a circle beside it is a second region -- and make_extrude_regions cannot be reused because it takes flattened Vec2d contours for imported Text/SVG art and would discard the analytic circle. Guessing containment would trade a visible failure for a wrong solid, which is the opposite of the point. Left scoped on snaporca-88v. Also converts the three float comparisons in the two test cases added this session from Approx to WithinAbs/WithinRel, per tests/CLAUDE.md, which rules Approx out for being asymmetric and double-only. The rest of the file's pre-existing Approx uses are left alone. 153 cases / 2090 assertions green on both forks; no existing test depended on the default-rectangle fallback. snaporca-88v (partial: the silent-fallback half). |
||
|
|
5f1811c2c5 |
A subtraction that removes nothing is an error, not a silent success
A boolean cut whose tool misses the target is a perfectly legal
operation: OCCT reports IsDone(), the shape comes back unchanged, and the
feature lands in the recipe reporting ok:true. Driving the MCP socket,
that produced two consecutive {ok: true, bodies: 1, error: ''} responses
for a hole that was never drilled -- same viewport, same 46939.11 mm3 --
and the tree grew two Hole features that will never cut anything. A
caller, an agent especially, has no signal at all that the thing it asked
for did not happen.
Measure the volume across the op in route_feature's in-place branch and
refuse the no-op. Only for removals: Hole, Thread, and Extrude / Revolve
/ Sweep / Loft in Cut mode. Everything else may legitimately leave the
volume alone -- a Transform certainly does. The tolerance is relative,
because an absolute epsilon is wrong across the mm-to-metre range of real
parts, and a cut that shaves a numerically invisible sliver is a miss
too. The existing rollback in the MCP actions already preserves the
reason, so a missed hole now answers ok:false with the error and undoes
the feature.
The confusion underneath was not itself a bug: hole's x/y are in the
sketch plane's frame, whose origin is describe_scene's modeling_origin,
and describe_tools documented them only as "number, unit mm". Passing the
world centre put the hole 135 mm clear of the solid. All six x/y params
on hole / hole_styled / hole_standard now say which frame they are in,
since the wrong guess was silent.
Regression test drives the reported failure directly: a hole at x=135 on
a 20x20x20 box is rejected and leaves the body untouched, the same hole
at the origin still removes exactly pi*4^2*20, and a cut-mode extrude
whose profile sits at x=200 is rejected too. 152 cases / 2083 assertions
green on both forks.
snaporca-daf.
|
||
|
|
5d05ca30ca |
Sketch shortcuts: pick the key map by mode, not by whether a session exists
All 17 single-letter sketch shortcuts were dead. The dispatch gate was circular: the sketch key map was consulted only when m_viewport->is_sketching() was already true, but is_sketching() is a whole-session flag whose only riser is begin_sketch(), called from select_tool() -- which is precisely what every sketch key closure calls. So the first letter after entering sketch mode fell through to the feature map, where the keys are Shift+letter, matched nothing, and did nothing. The mouse worked only because the toolbar flyout row reaches select_tool() directly, bypassing the gate. Which key MAP applies is a question about the mode. Split the flag: 'sketching' (live session) still drives undo/redo, Delete and the section-view branch, where a session genuinely has to exist; a new 'sketch_mode' (m_ui_mode == UiMode::Sketch alone) drives the map choice. Verified headless end to end, keyboard only: Shift+S, then R draws a 143.4 x 133.7 rectangle on XY reporting 4 degrees of freedom, then F and L arm Fillet and Line (artifacts/shots/0udb-01..03). Note the toolbar strip does NOT change when a tool is armed by keyboard -- the family buttons are flyouts and only show their own pressed state -- so the pixel diff on that strip, which is how this was originally measured, reads 0 for a tool that is live. The status line is the surface that actually reflects the armed tool. Also fixed, from snaporca-d9i's list: the plane-pick status line said "press Sketch to draw on it", naming a button that exists only in Feature mode. It is now mode-aware. Adds a SNAPORCA_KEYTRACE=1 trace in the CHAR_HOOK printing key, ui mode, is_sketching, in_text and the focused window's class. It is what separated "the fix does not work" from "the surface being measured never moves", and it costs a full GUI build to re-add, so it stays. snaporca-0ud, partial snaporca-d9i. |
||
|
|
347b83d887 |
Sketch fillet: never write a failed solve's geometry back, and commit the op
Two P0s in the same gesture. Filleting a corner of a parametric rectangle produced either nothing at all or a sharp corner with a stray arc floating above it. Trigger (snaporca-cq2): the only routes that ever reached confirm_op were finishing the whole sketch and an unsignposted click on empty space. set_tool() dropped a ready op, so typing a radius or dragging the arrow and then touching any other tool threw the value away. Commit a ready op on tool change (before m_mode is reassigned — op_ready() and confirm_op() both switch on it), commit on Enter in the radius editor, and drop the pending op before Esc's tool downgrade so Esc still cancels rather than applies. Substitution (snaporca-pl5): libslvs writes its last Newton iterate into the params whether or not it converged, and SketchSolver read them back unconditionally, so every REJECTED solve deformed the sketch. The fillet ladder tries a deliberately over-constrained rung first (a tangent on each leg, against the legs' own H/V); it is correctly rejected, but its wreckage then failed rungs 2 and 3, which solve cleanly on their own. The arc ended up with no constraints at all, the rigid loop won, and the corner snapped shut. Measured: from pristine geometry rung 1 gives result=INCONSISTENT with 3 bad constraints, rung 2 gives dof=6 with the arc's radius intact. Read the geometry back only on success. try_add_constraints then needs no "restore" re-solve — the entities still hold the prior solved state. Kernel suite 151 cases / 2072 assertions green on both forks; the GUI check ran on the Snapmaker fork (9d72c4377a). Ported from the Snapmaker fork. snaporca-pl5 snaporca-cq2 |
||
|
|
50577d66d0 |
Dress-up card: say whether Confirm will round the picked edge or the group
The card decides between a single picked edge and a whole face-group from a viewport pick it never mentioned. With no edge picked the user saw only the group combo and concluded per-edge rounding did not exist; with an edge picked the combo still read "All" — the opposite of what Confirm would do. Adds a Target row that names the actual target and greys the group combo out while an edge is picked, wired at the same three points Shell already uses: the selection-changed handler, the re-edit load, and open_tool. Ported from the Snapmaker fork (33771a0e95). snaporca-40d |
||
|
|
d5dee45acf |
Port the four DesignPanel fixes that never crossed from the Snapmaker fork
The CAD sources are meant to be byte-identical across the two forks, with exactly
two permitted divergences: DesignPanel.cpp's flyout plumbing (30 lines — mainline's
DropDown is Item-based where the other fork takes three parallel vectors) and
DesignCanvas.cpp's Bed3D::set_shape signature (16 lines). DesignPanel.cpp had drifted
to 105.
Nothing failed to announce this. The kernel suite does not compile the GUI, and all
four defects are interaction-level, so both forks stayed green while only one of them
had the fixes. The parity diff is what found it.
* combo_append_index and its five call sites. Orca's ComboBox keeps client data in
its own vector, so Append()'s clientData argument never reaches wxItemContainer
and m_clientDataItemsType stays wxClientData_None. GetClientData() opens with a
wxCHECK_MSG, which is an early return — so every read came back NULL and every
caller resolved it to index 0, silently, because 0 is a legal answer. Affects the
rib sketch picker, the sheet-body picker, both mate coordinate-system pickers and
the sweep path picker.
* Wrap(240) over the card labels. wxStaticText never wraps itself, so a one-sentence
hint sets its card's minimum width to the width of the whole sentence and every
control in that card is clipped at the sidebar's right edge.
* Shell and Draft face-label initialisation in open_tool. Both read the face from the
live pick at Confirm time, but only the pick handler wrote their labels, so picking
a face and then opening the card left the card describing one operation while
Confirm performed another.
* SurfaceOffset and ThickenSurface added to build_candidate's negative list. Both read
a sheet body from their own combo and both had it overwritten by the picked solid's
index. The list is a negative one, so a tool that picks its own body breaks by
omission — noted in a comment now.
Also drops a stray <cstdio> left behind by a debug probe.
Verified by building the GUI here for the first time since these landed: 461/461,
liblibslic3r_gui.a links. DesignPanel.cpp is back to exactly 30 divergent lines and
every other CAD file is byte-identical again.
snaporca-7xx snaporca-aqu snaporca-gu9 snaporca-c03 snaporca-5pl
|
||
|
|
faed169a01 |
Sketch dimensions: make Tab commit, instead of silently dropping what you typed
The inline editor special-cased Escape and Skip()ped every other key, so Tab fell through to wx's default navigation. Its popup frame holds exactly one control, so focus came straight back to that control with its text re-selected. Type 60, Tab, 40, Enter — expecting to fill two dimensions — and the 60 is gone: Tab neither committed it nor advanced, so the 40 just replaced the re-selected text. A re-selected field is pixel-identical to a freshly opened one, so nothing on screen says a number was dropped. Tab-to-next-dimension is what Onshape, SolidWorks and Fusion do, which is exactly why it is the key a user reaches for. Tab now calls do_commit(), the same path Enter takes; the caller's on_commit is already what walks to the next dimension. Verified by driving the GUI: 37 Tab 24 Enter now produces a 37.0 x 24.0 rectangle, where before it produced 24 and a mouse-derived value. snaporca-xah Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM |
||
|
|
695932ad87 |
Drop the degenerate triangle OCCT emits at every filleted corner
A filleted solid arrived on the plate as a broken model: the slicer reported
"8 non-manifold edges" on an 80x50x12 box with r=3 on all edges, and advised
repairing it in another CAD application -- the exact round trip the Design tab
exists to remove. The same box without the fillet committed cleanly.
Measured rather than guessed. Each of the 8 bad edges is degenerate, both
endpoints the same vertex:
open tri=145 edge=1 face=5 v59(3.000000 3.000000 0.000000) v59(3.000000 3.000000 0.000000)
open tri=538 edge=1 face=6 v87(3.000000 3.000000 12.000000) v87(...)
... one per corner, 8 corners
OCCT triangulates a degenerate surface parameterization with a triangle at the
pole; a corner sphere patch has exactly one. Its two pole nodes are distinct in
the per-face triangulation and collapse to a single vertex when the faces are
welded, leaving a zero-area triangle whose v->v edge can never pair with a
neighbour. its_face_neighbors counts it as open, and the field the object panel
prints as "non-manifold edges" is in fact stats.open_edges.
So the geometry was never wrong -- the B-rep volume matches the Steiner formula
for a box dilated by a ball to 0.016%. Only the bookkeeping was.
Dropping those triangles after the weld removes 8 of 3492 and takes open_edges
to 0. Zero area, so nothing about the shape changes. tri_face is compacted in
the same pass, since it must stay index-aligned with the triangle list that the
face picking and per-body colouring both index into.
Guarded by a new [CadDocument] case that asserts open_edges == 0, no degenerate
triangle survives, and both per-triangle maps still match the triangle count.
The existing suite only ever checked B-rep volumes and areas, which is why a
mesh defect this visible went unnoticed: 150 cases / 2049 assertions green on
both forks.
snaporca-agw
|
||
|
|
2c5ddd4102 |
OCCT link order: put TKFillet/TKOffset before their dependencies, not after
OCCT_LIBS is an explicit single-pass static link order — dependents first, TKernel
deliberately last. The CAD block appended its two extra toolkits to the END of that list,
which puts them after everything they depend on:
list(APPEND OCCT_LIBS TKFillet TKOffset)
TKOffset references BRepAlgo_Loop, and nm against the built deps prefix shows TKBool is the
only toolkit that defines it (TKTopAlgo, TKBO, TKPrim, TKFillet and TKOffset all define it
zero times). TKBool sits first in the list, so a single-pass linker has passed it long before
it reaches the appended TKOffset and will not go back:
libTKOffset.a(BRepOffset_MakeLoops.cxx.o): undefined reference to
BRepAlgo_Loop::BRepAlgo_Loop()
Only one configuration ever objected — the Snapmaker fork Flatpak (aarch64). Ordinary Linux,
macOS and Windows links resolve it regardless, and the mainline fork Flatpaks pass, so six
green platform legs said nothing about whether this list was correct.
Prepended via set() rather than list(PREPEND), which needs CMake 3.15 while this project
supports 3.13.
Worth knowing for later: TKFillet and TKOffset are mutually dependent, 20 symbols needed in
each direction, so a stricter single-pass link could still trip on that pair. It does not on
any current platform, so no --start-group or duplicate entry is added here; if something ever
complains about ChFi or BRepFill symbols, that cycle is the reason.
snaporca-2kj
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
|
||
|
|
295c030309 |
DesignPanel.hpp: declare the three wx types it uses but never named
The header forward-declares a long list of wx types and omitted wxBoxSizer, wxTextCtrl and
wxListCtrl. All three are used as pointer members only (m_expr_text, m_var_list,
m_parts_hdr, m_hdr_tree_row), so a forward declaration is all they need — but there was
none. Every ordinary build compiled anyway because the wx/panel.h + wx/scrolwin.h chain
happens to pull the real headers in transitively.
The Snapmaker fork Flatpak build (aarch64) has a wx that does not, and it failed outright:
DesignPanel.hpp:511: error: 'wxTextCtrl' does not name a type; did you mean 'wxTreeCtrl'?
DesignPanel.hpp:521: error: 'wxListCtrl' does not name a type; did you mean 'wxFileCtrl'?
DesignPanel.hpp:663: error: 'wxBoxSizer' does not name a type; did you mean 'wxSizer'?
plus a cascade of "m_var_list / m_expr_text / m_parts_hdr was not declared in this scope".
Not an environment quirk: the header was simply not self-contained, which is exactly what
breaks a reviewer building in an unfamiliar configuration. The mainline fork Flatpaks passed
on both arches, so only that one manifest exposed it.
Audited the rest of the header afterwards: every other wx pointer type is either
forward-declared or genuinely included — only wxScrolledWindow and wxWindow are undeclared,
and both come from the real wx/scrolwin.h include.
snaporca-4dn
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
|
||
|
|
c1b0484495 |
3mf test: give the BBS save a writable temp dir, instead of the filesystem root
store_bbs_3mf reaches Model::get_backup_path(), which builds
temporary_dir() + "/orcaslicer_model/" + timestamp. temporary_dir() returns a file-static
that ONLY OrcaSlicer.cpp's startup sets, so in a test binary it is the empty string and the
backup path becomes "/orcaslicer_model/..." — absolute, at the filesystem root. An
unprivileged process cannot create that, so the save returned false and the scenario died
on REQUIRE(store_bbs_3mf(sp)).
This was the SINGLE failure in this fork's Unit Tests — 1 of 566, on Linux x86_64, Linux
aarch64 and macOS arm64 — from CI run 30191490709:
Failed to create backup path "/orcaslicer_model/Sun_Jul_26/08_49_41#5398#1":
boost::filesystem::create_directories: Permission denied [system:13]
It hid because that job had never run to completion on this branch before: every earlier
run was cancelled by the concurrency group first. It also passed on Windows x64, where the
drive-root path is writable, and it passes in the local build container, which runs as root.
Verified against the same defect in the Snapmaker fork by running the built binary as
uid 1000: permission denied before, 4 assertions passing after.
snaporca-vg8
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
|
||
|
|
5026dd11a6 |
Fix the solver abort on circle-line tangency; the CAD suite now runs complete
snaporca-tkz, the last quarantined test. Root cause read out of the vendored
source rather than guessed: slvs/constrainteq.cpp, Type::ARC_LINE_TANGENT does
ExprVector ap = SK.GetEntity(arc->point[other ? 2 : 1])->PointGetExprs();
so it dereferences the ARC'S ENDPOINTS. A full circle entity carries only
point[0], its centre. point[1] and point[2] are zero handles, FindById throws
"Cannot find handle", and the process ABORTS rather than failing the solve —
taking every later test in the binary with it. That is also the wrong equation
for a circle regardless: it only makes the line perpendicular to the radius at
an endpoint that does not exist.
CT::Tangent no longer hands a full circle to that constraint. For a circle it
emits PT_LINE_DISTANCE(centre, line) = radius, which is precisely what tangency
to a circle means. Arcs keep the ARC_LINE_TANGENT path they are built for.
One limitation, stated rather than buried: the slvs C API takes a constant
distance and offers no way to reference the circle's radius parameter, so the
radius is captured when the constraint is emitted. That is exact whenever the
radius is fixed or is simply not driven by another constraint in the same
solve, and re-solving restores tangency if something else moves it. Tying them
would need an auxiliary point constrained onto both the circle and the line.
With this and eeca6794e7, both quarantined tests are gone and the exclusion in
kernel-test.sh goes with them. A green run now means the whole CAD suite
passed, not "everything except the two we gave up on":
149 cases / 2043 assertions, no filters.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
f599ff0ff7 |
kernel-test.sh: one quarantined case now, not two
Follow-through from eeca6794e7. The header claimed two pre-existing failures are excluded and named both; the internal-thread case now runs like any other, so only the solver SIGABRT is left. A comment that lists a test which is no longer excluded sends the next reader looking for something that is not there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
df6ef85614 |
Un-quarantine the internal-thread test: the geometry was right, the test was not
snaporca-kzy was filed as "internal thread cuts too little material". It does not. Measured on the test's own fixture, a 40x40x20 box: plain Ø12 bore removes 2261 mm3 internal thread removes 2157 = 1571 (minor bore) + 586 (groove) apply_thread bores at the MINOR radius (radius - depth = 5) and then carves the groove out to radius + depth = 7. A tapped hole therefore keeps the crests between turns and holds MORE material than a plain clearance hole at the nominal radius — which is what every real tapped hole does. The test asserted the opposite, so it was asking for something physically wrong and had been quarantined for it since it was written. One hypothesis discarded on the way: that the shortfall was a tessellation artefact, since chords on a helical surface undercut a concave bore. Exact BRepGProp::VolumeProperties agreed with the tessellated volume to within 2.5 mm3, so that was not it and is not offered as a hedge. The reference is now the tap-drill bore the thread actually starts from (Ø10), against which the groove's 586 mm3 is the meaningful quantity — that is what "the thread cuts" means. Test re-tagged [CadDocument][thread], so CI covers the thread path again instead of skipping it. Also documented the (void)internal in make_thread_profile. It reads like a bug and is not: the V is the same shape either way and the caller decides, fusing it onto a shaft or cutting it out of a wall. Someone "fixing" it to point inward for the internal case would make the groove sweep already-empty bore space and cut nothing — the exact failure the old comment described. Suite 148 cases / 2035 assertions, with this test now among them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a95e8ee701 |
Re-edit: list the bodies as of the feature's timeline slot, not the final ones
Found by sweeping the index-space defect class deliberately rather than by hitting it: that class produced 4 of the 8 defects found by hand yesterday, so it was worth auditing every combo in the panel that maps a row selection onto a document index. Most of it came back clean — the loft sidecar vectors are consistent at all four read sites, the sheet-body pickers go through the helper everywhere, mate connectors carry client data. Six did not. A stored target_body indexes the body list AS IT WAS just before that feature ran during replay, but Transform, Mirror, Thicken, Rib, Project and DeleteFace all populated their combo from the live m_doc.bodies. Boolean and Cut already replayed to the right slot. The failure is concrete: model a body, Thicken it, then Cut something later in the tree. A Cut replaces one body with two, so every index at or after it shifts. Reopen the Thicken and the combo lists the post-cut bodies while selecting the pre-cut index — showing, and on confirm re-targeting, a different body than the feature actually used. A Boolean that consumes its tool body shifts them the other way for the same result. fill_body_choice() does the truncated replay populate_body_choices() already did, for the single-combo tools. Six call sites, and 60 lines of duplicated population loops go with them. Visible change when testing: re-editing an early feature now lists FEWER bodies, because it lists only those that existed then. That is correct — you cannot target a body that did not exist yet — and it is what Boolean and Cut have always done. The new kernel test pins the invariant the GUI now leans on: a Cut turns one body into two, and replaying to just before it yields the earlier, shorter list. If body ordering after a split ever changes, that assumption fails loudly here instead of silently in a dialog. NOT click-tested — GUI wiring, compile-verified only. Filed as snaporca-oz7 and added to snaporca-cfi. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8f06b9dfd8 |
Design tab: make Cut re-editable, and stop misdescribing Import
on_edit_feature had two types falling into default: with "This feature type can't be edited yet". Boolean was already handled, so the follow-up note was stale on that point; the real gap was Cut and Import. Cut now re-edits like any other feature: plane, offset and target body are restored and the generic replace_feature path commits the change. The body list is rebuilt with populate_body_choices(m_edit_index) for the same reason Boolean does it — a Cut splits one body into two, so the live body list no longer matches the one this feature's target index was recorded against. Replaying to just before the feature makes the stored index land on the right entry. Import deliberately gets no dialog. An imported solid has no parameters to re-edit: its geometry is rigid data read from a file, not something rebuilt from numbers, and moving it is what the Transform feature already does. Building an "edit" for it would duplicate Transform behind a second name. So it now says that instead — the previous message implied a dialog was coming that should not. Imported 2D Text/SVG art is a different thing and stays re-editable; it arrives as a Sketch feature carrying imported_regions and is handled above. The default: arm is kept as a guard so a feature type added later announces itself rather than silently swallowing the click. NOT click-tested — GUI wiring, compile-verified only. Added to snaporca-cfi. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9c199e4a38 |
Fix the Shellcheck CI job, red since the CAD kernel-test loop landed
The Shellcheck workflow has failed on every push and every scheduled run since 2026-07-24, on exactly one finding: SC2029 in scripts/kernel-test.sh, the file the CAD branch added. So the CAD work is what turned that job red, and a PR arriving with a red job is a bad way to open a conversation with a maintainer. Client-side expansion of $REMOTE is the intended behaviour — it is derived from $VOL locally and the remote has no such variable, exactly as the rsync destination two lines down relies on. So this is a disable with a reason, not a silencing: the note says why the warning does not apply. Verified by running the workflow's own command over all 24 matched scripts: exit 0, no findings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e2b921745d |
Add user documentation for the Design tab
docs/design_tab_upstream_portability.md explains the subsystem to a maintainer; nothing explained it to a user. This is that: what the tab is, how to get a first solid out of it, every tool grouped the way the toolbar groups them, and the keyboard shortcuts read out of the source rather than remembered. The limitations section is deliberate. Rib needing a sketch with an explicit open line, Surface Loft and Surface Fill having no hands-on verification, mates composing transforms instead of solving simultaneously, move-face and replace-face being absent, and the two quarantined kernel tests are all things a user would otherwise discover by hitting them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f347afd22e |
Document the CAD subsystem's real dependency weight
Maintainers will ask what the Design tab costs before they will look at the diff, so measure it rather than assert it. The headline correction: the OCCT delta is THREE toolkits, not two. The comment in deps/OCCT/OCCT.cmake claimed "TKFillet + TKOffset (3.77 MiB, Windows only)". Walking OCCT's own adm/MODULES and each toolkit's EXTERNLIB shows ModelingAlgorithms holds twelve toolkits, that eight of them are built either way because DataExchange (the STEP path upstream already ships) depends on them, and that the true delta is TKFeat, TKFillet, TKOffset and TKXMesh — of which TKXMesh is never produced. So three archives are built: 7.40, 5.38 and 4.42 MiB. TKFeat is the interesting one. Nothing in the Design tab references it and it is absent from the TKFillet/TKOffset dependency closure, so it is built for nothing — OCCT's module flag is all-or-nothing per module. On static-link platforms that is build time and zero shipped bytes. Two numbers are deliberately absent, marked as absent, and not approximated: the Windows DLL delta needs a Windows build (snaporca-gix), and a clean-build time delta needs the deps prefix built twice on one machine. The old 3.77 MiB figure is withdrawn rather than reused — it covered two of the three toolkits. Also recorded: the vendored solver is 9,339 lines under GPLv3 with its LICENSE preserved, which combines into this AGPLv3 fork without difficulty (AGPLv3 §13), and it is live code driving every sketch constraint — not a carried corpse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e2b58a17f7 |
Design i18n: extract the tab's strings at all, and translate them into Italian
DesignPanel.cpp was never listed in localization/i18n/list.txt, and xgettext
extracts only what that file names. All 934 of the Design tab's _L() calls
were therefore invisible to every translator in every language — not merely
untranslated, unextractable. Adding the one line is the actual fix; the rest
follows from it.
Regenerating the .pot brings the catalogue from 6007 to 6536 msgids. 549 of
the new ones are now translated into Italian: 5230 to 5779 translated
messages. Verified no existing work was destroyed — every msgid that survived
into the new .pot kept its translation, and the 36 that lost one are genuinely
gone from the sources. msgfmt --check-format passes, which matters here
because a large share of these carry %d / %s / %zu.
CAD terms follow Italian CAD convention rather than literal glosses: Fillet ->
Raccordo, Chamfer -> Smusso, Draft -> Sformo, Rib -> Nervatura, Mate ->
Accoppiamento, Shell -> Svuotamento, Pattern -> Serie, Sheet body -> Corpo
superficie. Strings identical in both languages are deliberately left
untranslated so gettext falls back to the msgid.
The long mixed-filament / Local-Z dithering tooltips are left untranslated on
purpose: slicer internals, outside a Design i18n task, and untranslated before
this commit too.
One string changed rather than translated. The Coord Sys hint read "Without an
edge the frame's rotation about its normal follows world X, not the body" —
that described the defect fixed in
|
||
|
|
1726e93760 |
Mate connectors: derive a face-only frame's X from the face, not from world
datum_frame took a FaceAndDirection frame's Z from the face normal, which follows the body, but its X from coordsys_x_hint, a world constant, whenever no explicit edge reference was set. Spinning a body about its own face normal therefore left the frame bit-identical: the connector could not encode that rotation at all, so Fastened and Slider mates claimed to fix an orientation the frame could not see. X now comes from the face's own first usable edge, which rotates with the body. The hint survives only as a last resort, for faces that offer no in-plane direction — a full circular edge has coincident endpoints, and a seam projects to nothing in-plane. The new test spins a box 90 degrees about its top-face normal and asserts the frame's X turned with it. Reverting just the X_tent derivation and rerunning makes it fail with "1.0 is within 0.000001 of 0.0" — cos(angle) between the before and after X is exactly 1, i.e. the frame did not move — and that is the only failure in 2019 assertions, so the test discriminates this defect and nothing else. Note for anyone replaying an older document: a face-only connector's frame can now differ from what that recipe produced before, so a mate built on one may place its body differently. Nothing in the suite or the golden v3 fixture changed, but the semantics did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f13f2876f6 |
Tag the two known-broken tests [NotWorking] so CI stops being red on every commit
This fork's Unit Tests job failed on every single commit, because CI runs the whole ctest suite including the two cases tagged [known-broken] that scripts/kernel-test.sh has always excluded locally. A job that is red unconditionally is worse than no job: it trains everyone to ignore it, so the next genuine regression arrives invisible. No workflow change was needed. scripts/run_unit_tests.sh already passes -LE NotWorking, and tests/CMakeLists.txt registers Catch2 tags as ctest labels via catch_discover_tests(ADD_TAGS_AS_LABELS) — so the exclusion upstream already ships works as soon as the cases carry the tag. Verified against the built test tree: 337 tests unfiltered, 335 with -LE NotWorking, i.e. exactly these two dropped and nothing else. The second cause recorded in the issue, the test-reporter step failing with "Resource not accessible by integration: 403" on a fork, is already fixed upstream: the Publish Test Results step now carries continue-on-error: true. The comment these cases carried claimed CI kept the bugs visible by reporting them forever. That is now false and was never a good mechanism anyway, so visibility moves to the tracker: snaporca-tkz for the solver SIGABRT, and snaporca-kzy, filed now, for the thread groove volume. Neither is fixed; neither is forgotten. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b25335e1b3 |
Rib: accept a Project feature as its sketch ref
Rib guarded with `sk.type != CadFeatureType::Sketch`, while every other sketch consumer — Extrude, SurfaceExtrude, SurfaceRevolve, the loft paths — tests `!= Sketch && != Project`. A Project feature carries a plane and Line entities, which is all a rib reads, so the guard blocked "project a body edge, then rib along it" for no stated reason. The picker in the Design tab offered Sketch features only, so it is widened to match: a kernel that accepts Project refs and a GUI that never lists them would have left the path unreachable anyway. Worth recording for whoever hits this next: Rib also needs a sketch carrying EXPLICIT entities. A parametric Rectangle sketch (add_sketch with width/height) has an empty entities vector — build_sketch_wire synthesises its profile on demand — so rib_entity 0 is out of range there and it fails with "rib: bad entity". That is why Rib could not be driven headlessly at all before this change; a Project feature is now the one programmatic way to produce a ribbable line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1cb80f7f9f |
Project: implement "(all edges)"; stop discarding the failure reason
Two defects found while driving the tools that Phase B wired but nobody had exercised yet. apply_project had no all-edges branch: with no face picked and no explicit edge list it threw "no edges or face selected". That is precisely the state the Project card opens in, and its label reads "(all edges)" — so the card's default could never be confirmed. It now projects every edge of the source body. Edges perpendicular to the target plane collapse to a point when projected, so segments whose endpoints coincide are dropped instead of being emitted as zero-length lines that would poison the sketch downstream. The second defect is why the first one was invisible. 29 of the 31 rollback sites in McpControl ran `if (!ok) doc.undo();`, and undo() recomputes the restored feature list — which succeeds and clears doc.error. Every failing command therefore reported `error: ""`. Yesterday's fix covered 2 sites and I treated the file as done; it was not. All 31 now capture the reason before the rollback and restore it after. Failures that read as `""` now read as "rib: bad entity" / "surface-revolve: revolve failed". Verified on the running GUI through the control socket: the Project call that previously returned ok:false now returns ok:true, and failures carry a reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
08e37296b9 |
Design tab: give every drawer entry a distinct icon
Six of the surface entries, both Thicken variants, Rib, Axis, Coord Sys, Mate and Delete Face all reused a sibling's glyph, so a drawer opened as a column of identical faces and the card that opened rarely matched the entry clicked. Fixed both halves: entry icons are now unique within their drawer, and each card header uses the icon of the entry that opens it. Two new glyphs, design_thicken and design_rib, are the only ones added — everywhere else an existing icon already carried the right meaning (design_revolve, design_offset, design_line, design_point, design_c_coincident, design_delete). The Surface drawer BUTTON deliberately keeps design_surface; only its entries may reuse the solid glyphs, because a menu row carries its own text label while two adjacent toolbar buttons do not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
77f2f4d4ad |
Design tab: datum and curve tools were rejected by the solid-preview check
refresh_preview() exempted only Sketch and Plane from the ghost-preview path. Axis, CoordSys, Helix and Project produce no solid either, so they fell through to it, found nothing, and reported "invalid: preview produced no geometry" — which also DISABLED Confirm, so all four tools were unusable rather than merely noisy. Guaranteed on an empty document; with a body present the ghost path finds something and masks it, which is why it survived until the tools were tried on a fresh project. All six non-solid tools are now exempt, each with its own ready message. The list is not a guess: recompute() skips Sketch, Helix, Plane, Axis and CoordSys outright and routes Project through apply_project(), which emits sketch entities and no solid — so the panel and the kernel now agree on exactly what is not a solid. Mate already had its own branch, since it needs Confirm gated on having two distinct CoordSys features. Introduced when Axis/CoordSys (batch 1) and Helix/Project (batch 3) were wired without extending this exemption. Confirmed fixed on hardware: Axis -> Plane Intersection with XY and XZ now resolves on an empty document, which also exercises 60b04feea1. Compiles clean; kernel untouched, suite unaffected at 143 cases / 1980 assertions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4d331099de |
CAD: Axis PlaneIntersection read its plane refs in the wrong index space
axis_plane_a/b are filled by the GUI from populate_plane_choices(), whose rows
are XY / XZ / YZ followed by the datum planes, and the row is stored verbatim.
The kernel's base_plane() indexed datum_planes[ref] directly, so the two spaces
were off by three: picking XY resolved to datum plane 0, and picking the first
datum ran past the end and failed with "plane ref not found". The
PlaneIntersection axis type could not work from the GUI at all.
base_plane() now uses the encoding CadFeature::plane_base already uses — 0/1/2
are the base planes through the modeling origin, >=3 indexes datum_planes[ref-3]
— so there is one convention for plane references instead of two. That also
makes two base planes usable, which the previous code rejected as out of scope
even though XY x XZ is an ordinary way to define the X axis.
Removed the dead find_plane lambda directly above it. It was never called and
half-anticipated this exact offset ("if (ref >= 3) // base plane offset"),
which is presumably where the confusion started.
Tests: the existing parallel-planes case encoded the OLD convention, passing
axis_plane_a = 0 to mean "datum 0" — values the GUI cannot produce — so it is
re-based onto rows 3 and 4. Two new cases cover what the GUI actually emits:
base x base (XY x XZ -> X) and base x datum, the latter pinning the +3 offset.
Both verified to FAIL against the previous indexing, at test_caddocument.cpp
:2325 and :2346.
Found by auditing the remaining tools for the index-space defect class that had
already produced three bugs in the GUI; this is the first instance of it
crossing the GUI/kernel boundary.
Suite 143 cases / 1980 assertions (was 141/1972). GUI compiles clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
0455b0bf96 |
Design tab: give the Surface drawer its own icon
The Surface drawer used design_extrude, the same face as the Add-material drawer, so the two buttons were indistinguishable in the feature bar. design_surface.svg is a draped patch — deliberately unlike design_plane (a flat parallelogram) and design_extrude (a box with an up-arrow) — with a faint interior rule so it reads as a skin rather than a solid face. Same visual language as the other 71: 24x24, no fill, #b6b6b6, stroke-width 0.85, round caps and joins. The five surface ENTRIES that also used design_extrude now use it too. That is not cosmetic tidying: a flyout button's face follows the last-picked entry (SetBitmap_(icon_names[i])), so changing only the drawer's default icon would have been undone the moment the user picked anything. Surface Loft keeps design_loft, which already suits it. The six rows still share one glyph between them, so they are told apart by label alone inside the flyout. Per-entry icons belong with snaporca-vrg (Draft/Shell reusing design_dressup), not here. Compiles clean; kernel untouched. Icon confirmed legible on hardware. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c565d6ba86 |
CAD: undo() never rolled back variables, so a bad one bricked the recipe
checkpoint() snapshotted `features` and undo() restored `features`, but
`variables` is a separate member of CadDocument. Every caller of the documented
checkpoint -> mutate -> recompute -> undo-on-failure pattern therefore failed to
roll a variable back: the bad value stayed in the document and every later
recompute failed, which is exactly the corruption the pattern exists to prevent.
Feature `expr` bindings were unaffected only because expr lives inside
CadFeature and rode along in the features snapshot — which is why the feature
side appeared to work.
This was a kernel gap, not a GUI one: McpControl::action_set_variable has the
same sequence and was equally broken.
The undo/redo stacks now hold a {features, variables} Snapshot. Nothing here is
serialized, so no recipe version change and no golden-fixture regeneration.
Two tests, both verified to FAIL against a faithful reproduction of the bug
(undo() leaving `variables` untouched) at test_caddocument.cpp:4420 and :4441:
one covers restoring a variable's previous value, the other covers removing a
variable that did not exist before the checkpoint. Worth recording that the
first mutation attempt was NOT faithful — it dropped the restore but kept
std::move(variables) into the redo stack, which empties the map as a side effect
and made the second test pass for the wrong reason. A mutation has to reproduce
the original defect, not merely break the code.
Second defect, same area: undo() calls recompute(), which succeeds and clears
doc.error, so the reason an edit was rejected was destroyed before anything
could display it. Six sites — four in DesignPanel, two in McpControl — now carry
the message across the rollback. on_remove_variable additionally asserted
"referenced by a feature expression" as fact; it now offers that as the likely
cause and appends the real error, since that diagnosis is wrong for any other
failure.
Suite 141 cases / 1972 assertions (was 139/1960). GUI compiles clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
33275f2c74 |
Design tab: sheet pickers targeted the wrong body; guard Delete Face's face list
The sheet-only pickers filtered correctly and then threw the filtering away. Their
rows are the SHEET bodies, but GetSelection() was passed straight through as an
index into m_doc.bodies. With a solid at 0 and a sheet at 1 — the normal order,
since you extrude a solid before making a surface — the single row is row 0 but
body 1, so Surface Offset and Thicken Surface targeted the SOLID. The kernel then
refused with "target is not a sheet", which reads as a kernel bug rather than a
picker bug, and the row's own label ("Body 2") disagreed with what was targeted.
Four sites per tool were wrong, including the re-edit path, which compared a body
index against the sheet-only row count and so restored the wrong row.
populate_sheet_body_choices() now carries the real body index in client data, and
two helpers make the row/body distinction hard to get wrong again:
sheet_choice_body() reads it back, select_sheet_choice() finds the row holding a
given body. No caller touches GetSelection()/SetSelection() on these pickers.
This is the third instance of the same index-space confusion in this file, after
the 0-based body labels in the interference report and the Rib sketch picker. The
kernel suite cannot catch any of them: the kernel receives whatever index the GUI
computed, and its own tests pass correct ones.
Delete Face was structurally right — its picker uses the all-bodies populate, so
its indices genuinely match, and accumulation appends with a running list. Two
gaps closed: clicking "Add picked face" with nothing picked was a silent no-op,
indistinguishable from a broken button, and the same face could be added twice,
putting a duplicate id into delete_faces that the defeaturing has no reason to
cope with. Re-adding is now a no-op with a message, not an error.
Both confirmed working on hardware. Kernel untouched: 139 cases / 1960 assertions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
d4904b8e2e |
Design tab: stop eight tool cards rendering at startup; regroup the drawers
The sidebar opened with eight tool cards stacked in it — Transform, Mirror,
Thicken, Rib, Project, Delete Face, Helix and Mate. A card added to the cards
sizer is visible until something hides it, and close_tool()'s hide-all only
runs on a tool SWITCH, so anything missing from the construction-time hide
block is on screen from the moment the tab opens. Those eight were wired into
close_tool() but never added here. All 37 cards are now hidden at startup.
Worth stating because it invalidates a check I ran while diagnosing this: every
card IS hidden somewhere in the file, so grepping for "hidden anywhere" says
nothing. The block that matters is the one in the constructor.
Second, the drawers mixed unrelated operations, and two group tooltips no
longer described their contents — Dress-up listed eight tools spanning three
different kinds of operation, and Add material still claimed to hold only
extrude/revolve/sweep/loft after Thicken and Rib were added to it.
One concept per drawer now:
Add material extrude, revolve, sweep, loft, thicken, rib
-> grows new solid material, whether from a profile, a face or
a line
Surface unchanged; already coherent
Datum / Curve plane, axis, coord sys, helix, PROJECT
-> reference geometry and derived curves. Project consumes a
body but PRODUCES sketch entities, so it is curve creation,
not a finishing operation
Placement TRANSFORM, MIRROR, MATE (new)
-> moves a body without changing its shape; a mate places one
body relative to another
Dress-up fillet/chamfer, draft, shell, delete face
-> finishing on the faces and edges of an existing solid
Hole / thread unchanged
The new drawer costs no toolbar width: the layout order already contained an
empty put("place") slot between "material" and "plane" with nothing registered
to it. Shift+Y and Shift+Z follow Transform and Mirror; every tool still
appears exactly once.
Compiles clean; kernel untouched, so the suite is unaffected at 139 cases /
1960 assertions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
6372b3b505 |
Design tab: M6 variables panel + per-feature expression bindings
M6 landed the kernel side as two plain public maps — CadDocument::variables and CadFeature::expr — reachable only through MCP's set_variable / set_feature_expr. Nothing in the GUI could create a variable, so the parametric layer was unreachable from the Design tab. Variables get a wxListCtrl (name, expression) with add/edit/remove below the feature tree, since they are document-scope and must not live in a card that only exists while a tool is open. Expression bindings get one generic row in the feature-edit path — a field-name combo plus an expression box — rather than an extra control on each of 32 cards. Every mutation copies McpControl's sequence exactly, and the rollback is the part that matters: checkpoint, mutate, recompute, undo() on failure. Without it one typo leaves the recipe permanently unrecomputable, since load() replays the whole list. Removing a variable a feature still references fails that recompute, so it reports the reference rather than a bare evaluation error. The field-name combo is deliberately editable: only 11 feature types get a curated field list, and free text is what makes the other 21 reachable. That is safe because assign_field() throws "unknown parameter: <name>" for anything it does not know, inside recompute()'s try block — so a wrong name gives a clear message and a rollback, never a silently dead binding. Two fixes on top of the generated wiring: - make_combo() passes wxCB_READONLY, under which Orca's ComboBox HIDES its text ctrl (ComboBox.cpp:51). There is no SetEditable() to undo that, so the field combo is constructed directly with style 0; that shows the ctrl with wxTE_PROCESS_ENTER and makes GetValue() return typed text. - the field-list helper had been made a file-static function taking DesignPanel::Tool, which required moving Tool out of private and into the public API. It is now a private static member instead: 32 values of internal card state should not be published to satisfy a signature. Compiles clean (0 errors); kernel suite unchanged at 139 cases / 1960 assertions. Phase B is complete on this fork — all 16 previously GUI-less tools plus the variables panel. Not yet exercised on a display. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
cf97b7aba8 |
Design tab: Mate tool + interference report — the 16 tools are now reachable
Mate completes the set: every CadDocument feature type now has a card. It is the
one that needed CoordSys wired first, since a mate connector IS a CoordSys
feature and cs_a/cs_b are feature indices into the recipe.
The card states what each kind constrains rather than just naming it, because
the five kinds are not distinguishable from their labels — the useful fact is
which DOF each PRESERVES: Fastened fixes all six, Planar leaves in-plane
sliding, Revolute leaves spin, Slider leaves axial travel, Cylindrical leaves
both. The offset/angle spins retitle per kind, since offset is a plane distance
for Planar and a position along the axis for the three joint kinds.
Confirm is blocked, with the reason in the status line, when fewer than two
CoordSys features exist or when A and B are the same one: a mate with cs_a ==
cs_b is meaningless and a dangling index recomputes to nothing useful.
check_interference() gets a button in the feature-tree header behind a rule, not
a tool card — it adds no feature, so it must not checkpoint(), recompute(), or
touch the undo stack, and a card would imply it does. Results go to the status
line as count + worst volume, with the per-pair list in a message box, named as
the parts tree names them.
Fixes on top of the generated wiring:
- the button's sizer adds sat after the closing brace of the block declaring
trow, so trow was out of scope ("'trow' was not declared in this scope");
- the CoordSys client data was typed const void*, which Append rejects;
- the report labelled bodies 0-based while all 28 other body labels in this
panel (and the parts tree) are 1-based, so it would have called the tree's
"Body 2" an interference on "Body 1" — and it was the only unlocalised label.
Compiles clean (0 errors); kernel suite unchanged at 139 cases / 1960
assertions. Still to come: the M6 variables panel. The GUI has not yet been
exercised on a display.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
9f01c78456 |
Design tab: GUI for 15 tools that only had an MCP method
Every CAD feature added across M1-M8 got a kernel API and an MCP method, and almost none got a card. The MCP path was the only way to reach them, so from the Design tab these features did not exist: add_axis and add_coordsys have been callable since M1 with no UI at all. Wired here, in three batches: datum Axis (5 construction types), CoordSys surface Surface Extrude / Revolve / Loft / Fill / Offset / Thicken Surface body Transform, Mirror, Thicken, Rib, Project, Delete Face, Helix Grouped into existing dropdowns rather than widening the 16-slot toolbar: surfaces get one new "Surface" dropdown, body ops join Dress-up, Thicken/Rib join Add-material, Helix joins Datum (renamed "Datum / Curve"). Shift+Y and Shift+Z went to Transform and Mirror; the remaining five are shortcut-less rather than getting invented chords. Three places where the UI has to encode a kernel distinction, not just expose a field: - Surface Offset and Thicken Surface consume a SHEET body and fail with "target is not a sheet" on a solid, so their pickers filter on CadDocument::is_sheet_shape() and say so when no sheet exists. Thicken (solid face -> plate) is a different tool and is kept visibly separate. - delete_faces is a vector, so Delete Face accumulates picks via "Add picked face" and shows the running list. Supporting one face would have been a silent downgrade of the kernel field. - CoordSys labels its edge pick with the consequence of omitting it: without an edge, datum_frame() takes x from coordsys_x_hint (world constant) and the frame cannot express rotation about its own normal — which is snaporca-en4, and is why a Fastened mate built on a face-only connector cannot fix spin. The Rib sketch picker needed the 3-arg Append(text, wxNullBitmap, clientdata): ComboBox's own Append(text, bitmap) hides wxItemContainer's (text, void*), so the 2-arg call resolves to the bitmap overload and fails with "conversion from void* to const wxBitmap is ambiguous". The Sweep picker already documents this; Rib now matches it. Compiles clean (0 errors) against snaporca-deps; kernel suite unchanged at 139 cases / 1960 assertions. Mate, the interference report and the M6 variables panel are still to come; the GUI itself has not been exercised on a display yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
afe6d11375 |
Make this fork's GUI link: OpenCV was reusing snaporca's JPEG-enabled build
First successful link of orca-slicer in this fork's history (158 MB, 738/738
objects, plus OrcaSlicer_profile_validator).
|
||
|
|
ed9d02093e |
Default both build scripts to orcacad-deps, not the other fork's image
Both scripts defaulted IMAGE to snaporca-deps. That image is Snapmaker-based
and lacks Eigen 5.0.1, CGAL 5.6.3, wx 3.3.2 and Python 3.12 Development.Embed,
so running either script here without an explicit IMAGE= dies at CMake
configure — which is a large part of why this fork reached M8 having never once
compiled (
|
||
|
|
1633005bba |
Make this fork actually compile: first green Catch2 run in its history
206/206 targets built, 139 [CadDocument] cases / 1960 assertions passing —
identical to snaporca's suite. Until now this fork had never compiled at all:
CMake died at configure, so the M1-M8 "suite green" figures were snaporca's
alone and the ports rested on patch-apply plus byte-identical sources.
Three fixes here; the deps work is in the orcacad-deps image (see below).
1. kernel-test.sh mounts deps_src. pybind11 is vendored in-tree and CMakeLists
requires its headers; without the mount the container fell back to the
image's baked tree, which predates it.
2. tests/libslic3r/test_3mf.cpp: repair the upstream-merge conflict resolution.
Resolving it as a union dropped the three closing braces of our SCENARIO, so
upstream's SCENARIO opened inside ours ("a function-definition is not allowed
here", plus 12 cascading catch2 registry errors). Restored from the pre-merge
file; whole-file brace balance is now 0 and the case count reconciles as
5 (ours) + 8 (upstream) - 3 (shared) = 10, with both CAD recipe tests intact.
3. tests/libslic3r/test_caddocument.cpp: REQUIRE_CONTAINS / CHECK_CONTAINS.
Catch2 v2 (snaporca) spells substring-match Matchers::Contains; v3 (here)
spells it ContainsSubstring and gives Contains an incompatible meaning,
range-contains-ELEMENT, which fails to COMPILE against std::string. Four
sites had been hand-adapted long ago, but M2-M8 kept porting in un-adapted
Contains calls — 16 of them — and nothing objected because nothing compiled.
Both forks now use the same find()-based macros, so the assertion lines are
byte-identical again and future format-patch ports carry across unchanged.
Five orphaned `using Catch::Matchers::Contains;` lines removed with them.
The deps gap that blocked configure needed five additions on top of
snaporca-deps, built into image orcacad-deps: Eigen 5.0.1, Python 3.12.13
(exact, with Development.Embed), wxWidgets 3.3.2 (was 3.1.5), CGAL 5.6.3
(was 5.4 — mainline's own MeshBoolean.cpp calls CGAL::parameters::default_values,
added in 5.5), plus the pybind11 mount above. OCCT V7_6_0, Boost 1.84.0 and
OpenCV 4.6.0 are pinned identically in both forks and were reused as-is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
113e75e7b3 |
kernel-test.sh: stop defaulting to the other fork's build volume
BUILD_VOL defaulted to snaporca_buildcache — snaporca's volume — so running
this fork's kernel test wrote into the other fork's build cache. Defaults to
orcacad_kerneltest now.
docker-iter-build.sh had the identical defect and was fixed to
orcacad_buildcache; this script was missed at the time.
Observed rather than theorised: an orca_cad run under a different deps image
overwrote snaporca_buildcache's CMakeCache.txt, after which snaporca's own
kernel test failed to configure ("Cannot find NLopt library 'nlopt_cxx' in
.../lib/cmake/nlopt/lib") because it inherited the foreign cached paths. No
foreign object files were written — the run died at configure — but the cache
was poisoned, and the volume had to be wiped and rebuilt clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
8c6df84acc |
Merge upstream/main into cad-mainline (530 commits)
Catches the fork up from |
||
|
|
30d54f0074 |
M8c: interference detection
check_interference(min_volume) reports every pair of solid bodies whose
intersection encloses more than min_volume, as {body_a, body_b, volume}. It
reports only — no geometry is mutated, so calling it cannot disturb mates or
placements. Read-only at the MCP surface too: no checkpoint, no recompute.
Sheet bodies are skipped up front: an intersection involving one encloses no
volume, so the boolean would be wasted work. Bodies that merely touch share a
face and enclose nothing, so face-to-face contact is not an interference.
A boolean that fails on one pair must not lose the report for every other pair,
so each pair is guarded — and OCCT raises Standard_Failure, which is not a
std::exception and would otherwise escape.
No new serialized fields, no recipe bump: this reads `bodies`, which is
recompute output and was never serialized.
No separate MCP listing for instances and mates: describe_scene already emits
the feature tree, and Mate has rendered there correctly since M8a fixed
feature_type_name.
Tests assert the exact overlap volume (20*20*4 = 1600 mm^3), both negative cases
(clearly apart, and exact face contact), that sheets are skipped, that the
min_volume gate silences a real overlap, and that a clash created by a Fastened
mate is detected — which ties the M8b placement work to this report.
Suite 139 cases / 1960 assertions green. McpControl.cpp is reviewed but not
compiled by kernel-test.sh, which builds only libslic3r_tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
6a0031c9e5 |
M8b: Revolute / Slider / Cylindrical mates
Each kind constrains the DOFs it owns and PRESERVES the rest from the body's current pose, following the pattern Planar established in M8a. Resolved instead as "Fastened with a parameter", all three would have been geometrically identical to Fastened — relabelling rather than behaviour. Revolute fixes position on the axis line; rotation about it survives Slider fixes orientation and perpendicular position; axial position survives Cylindrical fixes the axis line only; rotation and axial position both survive No new serialized fields, no recipe bump, no fixture regeneration: mate_kind is already an int and mate_offset / mate_angle already exist. The minimum-rotation z-alignment (including the antiparallel 180 deg case fixed in M8a) is now a shared make_z_align lambda rather than a second copy. Fixes a rotation-about-pivot bug found by the no-op tests: R_full was built as a rotation about the origin with a translation to oB appended, instead of a proper rotation about oB (translation = oB - R*oB). It moved bodies that were already correctly placed, and accounted for three of the seven initially failing cases. Testing notes, both of which cost real debugging time here: - Mates are defined on connector FRAMES, but the convenient thing to measure is CentreOfMass(), and the two coincide only when the body is symmetric about its connector. Five expectations in this milestone asserted the centroid while meaning the connector. These tests assert on the mated face's centroid. - A CoordSys built from a face ALONE takes its z from the face normal (which follows the body) but its x from coordsys_x_hint, a world constant. Such a frame cannot see rotation about its own normal, so no mate can correct or preserve a spin it does not encode. The Slider and Cylindrical rotation tests pin coordsys_edge to an edge of their own body; without that both passed vacuously, one of them for a wrong implementation. The Cylindrical rotation test was verified to fail when its mate kind is mutated to Slider, and the Slider test failed at axis_aligned == 2 before the connectors were edge-pinned. Neither is green by accident. Known wart: mate_angle is silently ignored for Slider, whose rotation is fully constrained. Defensible but undiagnosed at the API surface. Suite 134 cases / 1927 assertions green. McpControl.cpp is reviewed but not compiled by kernel-test.sh, which builds only libslic3r_tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |