mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-07 15:51:08 +00:00
382 lines
26 KiB
Markdown
382 lines
26 KiB
Markdown
# Design tab — High Level Design
|
|
|
|
## Purpose and scope
|
|
|
|
The Design tab is a parametric CAD environment inside the slicer: sketch, constrain, build
|
|
solid features, commit the result to the plate. It exists because the alternative is a round
|
|
trip through an external CAD application, and that round trip discards design intent at both
|
|
ends — a part edited after slicing comes back as a mesh rather than as the feature history
|
|
that produced it. Keeping the model in the project means a dimension can be changed after the
|
|
part has been sliced, with the nozzle diameter, the build volume and the material already
|
|
known.
|
|
|
|
Its coupling to the rest of the application is deliberately narrow. It adds no stage to the
|
|
slicing pipeline and touches neither the preset system nor `Tab`. It reaches the rest of Orca
|
|
in two places: **Commit to Plate**, which hands finished solids to Prepare as ordinary model
|
|
objects (by default one assembly object with a part per body, which keeps the bodies' relative
|
|
placement, or one object per body), and one optional 3MF archive entry that carries the recipe.
|
|
Everything else is contained in `src/libslic3r/CAD/` and `src/slic3r/GUI/CAD/`.
|
|
|
|
The user-facing manual lives in the wiki
|
|
([Design Tab](https://www.orcaslicer.com/wiki/design_tab)), not here. This document covers the
|
|
parts of the design that the code does not make evident.
|
|
|
|
## The model is a recipe
|
|
|
|
`CadDocument` holds an ordered list of `CadFeature` and nothing else that matters. Bodies,
|
|
meshes and display geometry are **derived**: `recompute()` replays the feature list from the
|
|
start and rebuilds them. Editing a dimension set twenty features ago is therefore an ordinary
|
|
edit — everything downstream is rebuilt by the same replay that built it the first time.
|
|
|
|
Two consequences follow from deriving rather than storing:
|
|
|
|
- **Undo is a snapshot of `features` alone.** The caller calls `checkpoint()` before the
|
|
mutations that make up one user action; undo restores that snapshot and recomputes. Because
|
|
everything else is derived, one checkpoint is exactly one `Ctrl+Z` step and the restored
|
|
state is exact rather than approximately reconstructed. The tab keeps this stack itself; it
|
|
is not Orca's project snapshot system, which operates on `Model` objects the Design tab does
|
|
not own until Commit.
|
|
- **Face and edge ids are session-scoped.** They are indices into `TopExp::MapShapes`, so a
|
|
rebuild invalidates every one of them. `CadDocument::topo_generation` is bumped on every
|
|
rebuild so a holder of an id can discover that it is stale instead of silently addressing a
|
|
different edge. The counter is deliberately not serialized: an id means nothing outside the
|
|
run that produced it.
|
|
|
|
## Geometry kernel and dependency surface
|
|
|
|
The kernel is OCCT, which OrcaSlicer **already** links — `Format/STEP.cpp`, `Format/svg.cpp`
|
|
and `Shape/TextShape.cpp` use it upstream. The Design tab adds no third-party dependency; it
|
|
widens the existing OCCT build by one module flag in `deps/OCCT/OCCT.cmake`:
|
|
|
|
```cmake
|
|
-DBUILD_MODULE_ModelingAlgorithms=${SLIC3R_CAD}
|
|
```
|
|
|
|
Most of that module's toolkits are built either way, because `DataExchange`, which the STEP
|
|
importer uses, depends on them. With the flag on, OCCT also builds `TKFillet` (used through
|
|
`BRepFilletAPI`), `TKOffset` (`BRepOffsetAPI`), and `TKFeat`, `TKHelix`, `TKXMesh` and
|
|
`TKExpress`, which nothing here references but which the module flag builds anyway, because
|
|
OCCT's module flags are all-or-nothing. On macOS and Linux OCCT links statically, so an
|
|
unreferenced toolkit costs build time and no shipped bytes. On Windows OCCT builds shared and
|
|
only the linked toolkits ship, so the tab adds the `TKFillet`, `TKOffset` and `TKBool` DLLs.
|
|
|
|
On Windows the packaging step asserts that every linked OCCT toolkit has a shipped DLL and
|
|
fails the configure with the name of any that is missing, because the alternative failure — a
|
|
deps prefix built with a different `SLIC3R_CAD` setting than the app — otherwise surfaces as a
|
|
missing DLL at first launch.
|
|
|
|
## The sketch constraint solver
|
|
|
|
`src/libslic3r/slvs/` is a vendored subset of SolveSpace's `libslvs`: self-contained, no
|
|
external dependencies, **GPL-3.0**, with its `LICENSE` preserved verbatim in the directory.
|
|
`SketchSolver.cpp` is its only consumer and drives every sketch constraint in the tab.
|
|
|
|
OrcaSlicer is AGPL-3.0. GPLv3 §13 permits combining a GPLv3 work with an AGPLv3 work and
|
|
AGPLv3 §13 grants the converse, so the combined work is distributable under AGPL-3.0 with the
|
|
solver's GPLv3 terms preserved. The solver is vendored rather than fetched as a dependency
|
|
because it is a pinned subset with no build system of its own; the cost of that choice is
|
|
upstream-sync burden, paid deliberately to keep `deps/` unchanged.
|
|
|
|
## The SLIC3R_CAD gate
|
|
|
|
`SLIC3R_CAD` (default ON) compiles the tab and selects the OCCT module flag above. With it OFF
|
|
the tab is not built and the deps prefix matches upstream exactly. The gate is cheap because
|
|
the hooks the Design tab adds to shared GUI code — chiefly the `m_design_sketch_tool` member
|
|
and the render, mouse and key hooks in `GLCanvas3D` — are null-guarded on the path they extend,
|
|
so removing the tab removes behaviour rather than requiring the host code to be rewritten.
|
|
|
|
The flag has to agree between the dependencies and the application; that is what the DLL
|
|
assertion above is checking.
|
|
|
|
## Project persistence
|
|
|
|
A project stores the recipe as one optional archive entry, `Metadata/orca_cad.bin`, backed by
|
|
a single `std::string cad_recipe` on `Model`. The entry is written only when the string is
|
|
non-empty, and readers that do not know it ignore it, so projects that contain no CAD model are
|
|
byte-identical to what upstream would have written and older readers are unaffected.
|
|
|
|
The blob is a cereal binary archive whose layout is the field order of `CadFeature`'s
|
|
save/load. That makes the format the one irreversible decision in the subsystem, and the rules
|
|
that keep it survivable are:
|
|
|
|
- **Append only, never reorder.** Enums serialize positionally as their underlying integer, so
|
|
inserting a value in the middle of `SketchConstraintType` or `CadFeatureType` reinterprets
|
|
every constraint in every saved project. New fields go at the end.
|
|
- **Features are length-framed.** Since v5 each feature is a length-prefixed, self-contained
|
|
cereal stream, so a reader can skip a feature written by a newer build and stop cleanly on an
|
|
older one. This is what makes appending a field a non-breaking change from here on. v4 and
|
|
earlier still open through the pre-framing flat path; v1 is deliberately not loadable and has
|
|
no migration path.
|
|
- **A newer stamp is refused, not guessed at.** `deserialize_recipe` rejects a blob whose
|
|
version exceeds `ORCA_CAD_RECIPE_VERSION` with a message naming both versions.
|
|
- **The rules are held by fixtures, not by discipline.** `tests/data/cad_recipe_v{3,4,5}.bin`
|
|
are checked-in blobs from the builds that wrote them, and the tests that load them fail if a
|
|
field is reordered — which the in-memory round-trip test cannot detect. A regeneration test
|
|
(`[.regen]`, not run by default) produces a fresh fixture when a new version is stamped.
|
|
|
|
`Import` features embed the imported solid as an OCCT BRep string inside the recipe rather than
|
|
referencing the source file, so a project opens without the STEP or mesh it was built from.
|
|
The cost is that saved projects are coupled to an OCCT BRep revision.
|
|
`tests/data/cad_brep_occt76.brep` holds a solid written by OCCT 7.6, and its test fails if the
|
|
bundled OCCT can no longer read it.
|
|
A Text feature follows the same rule: it stores the outlines it was vectorised into alongside
|
|
its string, font and height, so the project opens identically on a machine that lacks the font;
|
|
the three parameters are only what an edit reopens the dialog with.
|
|
|
|
## The interaction contract
|
|
|
|
Three inputs carry the whole modelling loop — left click, right click and `Esc` — and the
|
|
contract between them is stated in code rather than spread across handlers.
|
|
|
|
`DesignInteraction.hpp` defines a four-level LIFO stack whose enum value *is* the depth, so
|
|
"which level does this press belong to" is a comparison:
|
|
|
|
| Level | Holds | One `Esc` press |
|
|
| --- | --- | --- |
|
|
| `Transient` | a value field or a popup menu | closes it; the tool stays armed |
|
|
| `Gesture` | an uncommitted delta — an entity being drawn, a body being dragged | reverts it; committed work is untouched |
|
|
| `Tool` | a feature card, Sketch waiting for its plane, an armed sketch tool, a constrain session | exits it; drawn entities survive |
|
|
| `Idle` | nothing transient | clears the selection, a Feature tree or Bodies row included; leaves a sketch session only if it is empty |
|
|
|
|
`cad_escape_level()` is a `constexpr` free function over a POD of four booleans rather than a
|
|
method on the panel, so the ordering that is the entire contract is checkable without a window,
|
|
a GL context or an event loop — five `static_assert`s in the header do exactly that at compile
|
|
time.
|
|
|
|
**The strict invariant: no level of `Esc` deletes a feature, discards a sketch that holds
|
|
geometry, or rolls history back.** Destroying work needs a gesture that says so — `Del` on an
|
|
explicit selection, the sketch ribbon's Cancel, which asks first, or `Ctrl+Z`. A sketch
|
|
*session* is deliberately not a `Tool` level; it is the environment the `Idle` level lives in,
|
|
which makes the destructive path unrepresentable rather than merely unlikely.
|
|
|
|
A body Move is the one `Gesture` that outlives the press: its gizmo stays up between drags until
|
|
Confirm keeps the placement or `Esc` or Cancel puts the body back. Until then the selection is
|
|
held — a click off the gizmo only steers the camera — and undo is refused, since the placement
|
|
is not in the history. Anything that starts another edit (a feature card, a sketch, placing
|
|
imported art or text, another body's Move, a rebuild) keeps the placement, as switching gizmos
|
|
keeps a move in Prepare. The panel ends the Move in one place (`DesignPanel::end_body_move`), so
|
|
the gizmo, the Move / Rotate card and the ✓/✗ cannot outlive one another.
|
|
|
|
Right-click is read at button-up against one budget, 3 px of drift, applied to the whole press
|
|
rather than to its end points: a press that wandered past the budget at any moment is
|
|
navigation, even if it comes back to where it started, which is what stops a slow, careful
|
|
orbit from ending in a menu. There is no time budget — a gesture that means something different
|
|
when it is slow is exactly what the interaction charter rules out. The raycast uses the press
|
|
position, not the release. The sketch tool sees a right press only once the release has shown it
|
|
was a click: the press itself goes to the camera, which may pan or orbit with that button, and the
|
|
canvas replays it to the tool on a stationary release. A tool that uses the click (to terminate a
|
|
chain, say) keeps the menu closed. Past either budget the event is navigation, and navigation
|
|
does not transition the state machine.
|
|
|
|
Navigation itself is Prepare's: the camera reads the drag actions set in Preferences > Control
|
|
for each button, and in the Touchpad camera style a move with Alt held orbits and one with Shift
|
|
held pans, whatever tool is armed. The left button is shared with picking and drawing, so a tool
|
|
handle or a press that draws takes it first, as a gizmo does in Prepare; a whole body is swept
|
|
with a rectangle on plain left-drag only while no camera action is assigned to the left button,
|
|
and with Shift+left-drag otherwise — Prepare's own rectangle selection.
|
|
|
|
Entering a sketch changes two things at once so the mode is legible: a banner above the
|
|
canvas (a sibling of the canvas, not a child over it — on GTK a child window over a
|
|
`wxGLCanvas` is a native window and does not reliably stack over GL), and `N` to look normal to
|
|
the plane. The printer bed stays: there is no sketch grid, so the plate grid is the only ground
|
|
reference a sketch has. Code that changes either belongs with a change to this section.
|
|
|
|
Sketch mode is never entered without a plane under it, so the banner, the sketch keys and the
|
|
sketch offer always have a session to act on. Sketch on a picked flat face or reference plane opens
|
|
the session on it at once. With nothing picked it stays in Feature mode and waits for one — an
|
|
armed `Tool`, left with `Esc` or ✗, and ended by anything that starts another edit — and the
|
|
reference plane or flat face clicked next opens the session. A picked plane is a selection like a
|
|
face: the sketch on it uses it up, and `Esc` or a click on nothing lets go of it, so a plane that
|
|
can no longer be seen never decides where the next sketch goes.
|
|
|
|
The reference planes — XY, XZ and YZ through the modeling origin, with their half-axes — are
|
|
drawn on demand, because three translucent squares over every model are noise once they are not
|
|
the thing being picked. Sketch brings them up while it waits for a plane, which is exactly when
|
|
they are picked, and the session the pick opens takes them away; a live session draws none. The
|
|
Feature tree's Origin row keeps them up outside a sketch. Its state is a view preference in
|
|
AppConfig rather than part of the recipe, so it costs the project format nothing. The Plane tool
|
|
keeps its own rule: the planes and the datums as Offset bases, and nothing for the other methods,
|
|
where a click on a plane would rewrite the datum's references. The `P` and `A` keys are a
|
|
separate, unpickable view helper and do not follow the Origin row.
|
|
|
|
The Bed row, under the Origin row, is the printer bed's switch in the same way: it draws or hides
|
|
the bed and its plate grid in every mode. It is a view preference in AppConfig too, and the bed is
|
|
shown until it is turned off.
|
|
|
|
The two rows are view switches, not history, and the tree says so: they sit unframed on the Feature
|
|
tree's card, above the features' own framed list, and stay put while the features scroll. A click
|
|
never selects either row, since a selected Origin or Bed would have nothing to edit, move or
|
|
delete; the eye and the right-click menu are the only targets, and a row's label dims while its
|
|
thing is hidden, as a hidden body's does. Because the block never takes the focus, `Ctrl+Shift+O`
|
|
and `Ctrl+Shift+B` flip the Origin and the Bed from the keyboard.
|
|
|
|
## Rendering the bodies
|
|
|
|
The tab draws its bodies through the same `GLCanvas3D` object path as Prepare, so how they look
|
|
is decided in the shared object shader, not in the tab. The slicer's two lights both sit near
|
|
the camera, which leaves the sides of a part in nearly one tone; the Design canvas asks for a
|
|
studio model instead — a world-space sky/ground hemisphere, a key and a fill light, a
|
|
plastic-like highlight and a darker silhouette — through `GLCanvas3D::set_studio_lighting()`
|
|
and the phong shader's `lighting_model` uniform. The program is shared by every canvas, so each
|
|
use sets the uniform (0 for the slicer's canvases) rather than relying on a default: a canvas
|
|
that left it alone would inherit whatever the last canvas chose.
|
|
|
|
The B-rep edges of every body are drawn over it by the sketch overlay as thin view-facing
|
|
ribbons, depth tested and pulled a few pixels toward the eye so they win against the faces that
|
|
meet at them and still hide behind faces in front; lines are not used because they do not
|
|
rasterise under the software GL context the tab also supports. Seams of closed surfaces and
|
|
degenerate edges are left out (`GeometryEngine::display_edges`), and the polylines are sampled
|
|
once per shape, keyed by its `TShape`, because a recompute that leaves a body unchanged is the
|
|
common case.
|
|
|
|
While a feature card is open, its preview ghost is the whole model the candidate would produce,
|
|
drawn translucent over the bodies, so every face the feature leaves alone is in both at the same
|
|
depth. The ghost is drawn with a depth bias that pushes it back (`GLVolume::depth_bias`), so on a shared face
|
|
the body always wins instead of the two copies z-fighting, and the ghost shows only where the
|
|
result reaches past the bodies. Material a feature removes lies inside the old solid and would not
|
|
show at all, so the tools whose result mostly coincides with the body — Fillet/Chamfer, Draft,
|
|
Hole and the Mate hover — hide the bodies once the preview is valid and draw the result alone,
|
|
opaque.
|
|
|
|
## Showing what is selected
|
|
|
|
A selection is drawn on the faces it names, never as a tint over the body: a translucent
|
|
selection colour blended into the body's own colour turns a different hue on every body and
|
|
vanishes on one close to it. Selected faces are split out of their body into a volume of their
|
|
own, which the canvas draws opaque in the selection colour through the same shader and lighting
|
|
as the body (`DesignCanvas::rebuild_bodies`); the sketch overlay outlines them with a cased line
|
|
— a dark band under a selection-coloured one — so the outline still reads on a body that wears
|
|
the selection colour itself. A body picked whole, a face picked in the viewport and the faces of
|
|
the Feature tree's selected feature all draw this way. The hover pre-highlight is the outline
|
|
alone, uncased: it promises a click, it is not one. The automatic body colours keep clear of the
|
|
selection colour's blues and teals, so no body looks selected before anything is picked; a colour
|
|
the user sets on a body is theirs, and the cased outline keeps its selection readable.
|
|
|
|
Selecting a feature row lights the faces that feature made, not the whole body it sits on, so a
|
|
fillet row shows its round and the extrude under it keeps the faces the fillet trimmed.
|
|
`CadDocument::faces_made_by` answers it without per-feature history: it replays the recipe to
|
|
just before the feature and then the feature alone, and a face of the finished model belongs to
|
|
the feature when an interior point of it lies on the boundary afterwards and not before, facing
|
|
the same way — the facing keeps a block stacked on a base the owner of its bottom face. A feature
|
|
that makes no face of its own, such as a Boolean union, answers with the bodies it changed. The
|
|
replay costs up to a recompute, so the panel finds the faces once per row and topology
|
|
generation, off the UI thread, and only while no feature card is open. One selection is live at
|
|
a time: a viewport pick clears the feature row and a feature row clears the viewport pick, as the
|
|
Feature tree and Bodies list do between themselves. `Esc`, a click on empty space and an
|
|
empty rubber band all let go of it, whichever list or pick made it — except while a body Move is
|
|
open, which holds the selection until it ends (see the interaction contract).
|
|
|
|
Zoom to selection, on a Feature tree or Bodies row and in its right-click menu, frames one thing
|
|
along the current view direction, as the canvas's Fit button frames the selection. On a body it
|
|
frames the body whole, hidden or not, from its display mesh: the viewport never selects a hidden
|
|
body, so the selection cannot stand in for it. On a sketch it frames the sketch's own geometry,
|
|
drawn, consumed or suppressed; on any other feature, the faces the feature made, found as the row
|
|
highlight finds them and from the same cache. From the offer it frames whatever the selection is,
|
|
a body again included. A feature that is not a sketch and makes no faces, such as a datum plane
|
|
or a suppressed Extrude, has no Zoom to selection.
|
|
|
|
## Following the app
|
|
|
|
The tab is a page of Orca's main window and answers to the same settings as Prepare.
|
|
|
|
- **Theme.** Its chrome is coloured from a table of light/dark token pairs. A theme switch
|
|
reaches `DesignPanel::on_sys_color_changed` from `MainFrame`, which moves every colour that is
|
|
one theme's token onto the other theme's and then runs the app's own dark pass; the icons are
|
|
Orca's sidebar grey, which the icon cache maps per theme, so they are re-rasterised rather
|
|
than re-tinted.
|
|
- **Scale.** Sizes are in DIP, and a DPI change reaches `DesignPanel::msw_rescale`, which
|
|
re-rasterises every icon (button faces, flyout rows, card headers, the feature and body lists'
|
|
row icons).
|
|
- **Sidebar icons.** Every clickable icon in the sidebar shows a hover chip. The card-header and
|
|
constraint-row buttons are Orca's self-painted `Button`, because a native button cannot take a
|
|
hover background on macOS. The Feature tree and Bodies lists are a custom-drawn
|
|
`DesignRowList` rather than a `wxTreeCtrl`, so each row carries its own actions — Zoom to
|
|
selection, Edit, Show/hide and Delete on a feature, Zoom to selection, Move, Show/hide and
|
|
Delete on a body, and only Show/hide on the Origin and Bed rows, a separate non-selectable list
|
|
above the features — and the eye shows whether that row is hidden. A feature with nothing to
|
|
frame keeps a blank cell where Zoom to selection would be, so every icon stays in its column.
|
|
- **Plates.** This is the one thing the tab does not follow. The canvas has a bed of its own at
|
|
the printer bed's home position, whichever plate Prepare has current, and a new document's
|
|
modeling origin is that bed's centre. A bed that followed the current plate would slide out
|
|
from under a design: the origin is fixed once per document, baked into every sketch plane and
|
|
saved in the recipe, while the current plate can change between visits. Commit to Plate does
|
|
not need it either, since the committed object is placed on an empty spot of the current
|
|
plate. What the canvas does read from the plate is moved onto its bed: the exclude areas, the
|
|
plate box the camera orbits about when nothing is picked (`GLCanvas3D::_current_plate_box`),
|
|
and the first view, which starts from Prepare's camera turned to the tab's iso view: the CAD
|
|
isometric from the front-right corner that Home returns to, not Prepare's front-left one.
|
|
- **Viewport text.** The status line and the active tool's values are drawn by the canvas in
|
|
its ImGui pass, so they go with the canvas: a top-level window over GL does not follow its
|
|
frame and was left floating over other applications.
|
|
- **Undo.** The tab keeps its own history (the recipe is not part of Prepare's snapshots), but
|
|
it has no Undo/Redo of its own: the top bar, `Ctrl+Z` and Edit drive it while the tab is
|
|
shown, greyed to what an undo would actually do.
|
|
- **Docking.** The sidebar docks like Prepare's — either side, floating, resized, or collapsed
|
|
with the canvas's collapse button or `Shift+Tab` — through its own AUI manager under the
|
|
toolbar, because Prepare's manages the Plater and the Plater is not on this page. The button is
|
|
the canvas's own toolbar rather than Prepare's, which collapses Prepare's sidebar. The layout,
|
|
collapse included, is kept apart from Prepare's (`design_window_layout`) and starts where
|
|
Prepare's sidebar is, at its width, so the canvas edge holds still across the tab switch until
|
|
the user moves one of them. A floating sidebar is a top-level window, so it is hidden with the
|
|
tab rather than left over the other pages, and View > Reset Window Layout resets both tabs.
|
|
|
|
## The offer is generated, not hand-written
|
|
|
|
Right-clicking geometry opens the *offer*: eight families in a fixed order, each verb at a
|
|
permanent row index, verbs that do not apply shown disabled **in place with their reason**
|
|
rather than removed. The invariant is that a verb's row index is identical in every selection
|
|
where it appears and that adding a verb never moves an existing one — the hand learns the
|
|
position, so the menu is never re-sorted, compacted or adaptively ordered. Above the families
|
|
sits one *flat* row, holding Rename, Color and Zoom to selection — what a selection is opened for
|
|
most: its verbs are items of their own at the top of the menu rather than a family's submenu. It
|
|
is appended after the eight, so it moved no existing index, and it reads the same from the
|
|
viewport and from a row of the Bodies list.
|
|
|
|
An invariant across 93 verbs and 20 selection kinds does not survive by review, so the map
|
|
exists once, as data: `scripts/CAD/tool_atlas.json` carries every verb with its row, key, icon,
|
|
accepted selections, preconditions and refusal string, and `scripts/CAD/gen_offer_table.py`
|
|
emits `src/slic3r/GUI/CAD/DesignOffer.hpp` from it. The header is checked in and never
|
|
hand-edited; `scripts/CAD/run-all-checks.sh` runs the generator with `--check` as its first
|
|
rung, which is what makes "GENERATED — DO NOT EDIT" a fact rather than a request. The generator
|
|
also refuses an atlas with a duplicate verb id, since `mcp_run_verb` resolves a verb by id and
|
|
would make the second one unreachable.
|
|
|
|
The atlas and its generator sit in `scripts/CAD/` rather than in `docs/`: they are build inputs
|
|
for a checked-in header, not documentation.
|
|
|
|
## Automation surface
|
|
|
|
`McpControl` exposes the document over JSON-RPC when `ORCA_CAD_MCP` is set in the environment,
|
|
with `tools/orca_cad_mcp_bridge.py` as the client side. It describes the scene, queries
|
|
topology, measures, and runs the same verbs the offer does — it re-implements nothing, so a
|
|
scripted action and a clicked one cannot diverge. It is off unless the variable is set.
|
|
|
|
## Where the code lives
|
|
|
|
| Path | Role |
|
|
| --- | --- |
|
|
| `src/libslic3r/CAD/CadDocument.*` | the feature recipe, its replay, undo and serialization |
|
|
| `src/libslic3r/CAD/GeometryEngine.*` | OCCT wrapper — faces, edges, booleans, healing |
|
|
| `src/libslic3r/CAD/SketchEngine.*` | profile → wire → solid |
|
|
| `src/libslic3r/CAD/SketchSolver.*` | constraint solving, over the vendored solver |
|
|
| `src/libslic3r/slvs/` | vendored 2D constraint solver (GPLv3) |
|
|
| `src/slic3r/GUI/CAD/DesignPanel.*` | the tab: toolbar, feature cards, tree, key maps |
|
|
| `src/slic3r/GUI/CAD/DesignRowList.*` | the Feature tree and Bodies lists, with per-row actions |
|
|
| `src/slic3r/GUI/CAD/DesignCanvas.*` | viewport integration |
|
|
| `src/slic3r/GUI/CAD/DesignSketchTool.*` | in-canvas sketching |
|
|
| `src/slic3r/GUI/CAD/DesignInteraction.hpp` | the Esc level contract |
|
|
| `src/slic3r/GUI/CAD/DesignOffer.hpp` | generated offer table |
|
|
| `scripts/CAD/tool_atlas.json` | source of truth for the offer |
|
|
|
|
## Verification
|
|
|
|
The kernel is covered by Catch2 suites in `tests/libslic3r/` (`test_caddocument`,
|
|
`test_sketchconstraints`, `test_sketchedit`, `test_sketchimport`, `test_sketchinference`,
|
|
`test_sketchprofile`, `test_slvs_constraints`), which need no display;
|
|
`scripts/CAD/run-kernel-tests.sh` builds only `libslic3r_tests` and runs them headless.
|
|
|
|
The GUI half is not covered by CI, which has no OpenGL canvas or synthetic input: the ladders
|
|
in `scripts/CAD/` drive a running application in a local rig instead, and
|
|
`scripts/CAD/run-all-checks.sh` is the gate that runs all of them. A green kernel run says
|
|
nothing about the viewport, so the two are reported separately rather than as one number.
|