mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 17:21:10 +00:00
Design tab: translatable offer, one vocabulary, reports in the status line
- Offer table: user-facing strings carry the L() marker so xgettext
extracts them; DesignOffer.hpp, DesignSketchTool.cpp and
SketchInlineEditor.cpp are listed in localization/i18n/list.txt.
- Offer: model-mode Constrain sits in the same row as the sketch one;
Interference is wired; Rib shows its R key; a verb that accepts the
selection but is blocked by the document stays greyed with its reason
instead of vanishing from the submenu; one refusal wording per verb.
- Extrude infers Join when the profile touches a solid (new
CadDocument::body_touching_sketch) and on face push/pull; New body in
free space. Revolve/Sweep/Loft/Boolean use the same result words.
- Interference and volume/area reports go to the status line in mm3/mm2
instead of modal dialogs; Delete Body no longer asks (it is undoable).
- New feature names match the card header ("Extrude 3"), translated;
"Coordinate system", "Angle (°)", center/color spelling, translated
face and length readouts, slot hints say width.
- CAD gizmos in Prepare are selectable only with the CAD feature on; the
sketch auto-close setting is stored per design.
- Docs: confirm/cancel rules, enabling the feature and MCP in
design_tab.md; drift-only right-click in interaction-model.md; the
portability note rewritten to describe the integration as it is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
This commit is contained in:
+38
-5
@@ -14,10 +14,15 @@ already ships for STEP import.
|
||||
|
||||
## Getting started
|
||||
|
||||
The Design tab is experimental. It exists only in builds with `SLIC3R_CAD` on, and appears
|
||||
after **Preferences → CAD feature (experimental)** is ticked and the app restarted. Switched off,
|
||||
Prepare and every project behave exactly as without it; a project that carries a design keeps
|
||||
its recipe untouched.
|
||||
|
||||
1. Open the **Design** tab.
|
||||
2. Click a face or a reference plane in the viewport, then press `Shift+S` (Sketch). The offer
|
||||
opens with the sketch tools on it.
|
||||
3. Draw a closed profile, then press **✓ Confirm** in the floating action bar.
|
||||
3. Draw a closed profile, then press **✓ Confirm** in the floating action bar (or `Enter`).
|
||||
4. With the sketch selected, press `Shift+E` (Extrude).
|
||||
5. Press **Commit to Plate** to hand the solid to Prepare.
|
||||
|
||||
@@ -59,7 +64,8 @@ sketch tools — the app hands you the tools directly.
|
||||
Repeat, Transform, Reference, Modify.
|
||||
|
||||
- A family with at least one applicable verb shows it. Several applicable verbs collapse
|
||||
into a submenu under the family name.
|
||||
into a submenu under the family name. A verb that is about the selection but blocked by the
|
||||
document (no body yet, no second sketch) stays in that submenu, greyed, with its reason.
|
||||
- A family with nothing applicable is **shown greyed in place, with the reason** — e.g.
|
||||
*"Create — Click a face or a reference plane in the viewport, then a sketch tool"*. It is
|
||||
not hidden. A control that cannot be used still says what it is and what you would have to
|
||||
@@ -72,6 +78,22 @@ Redo, Variables, Section view, Origin planes, World axes. They live in the toolb
|
||||
|
||||
---
|
||||
|
||||
## Confirm and cancel
|
||||
|
||||
One rule everywhere, in the feature cards and in the sketch alike:
|
||||
|
||||
- `Enter` does what **✓** does; `Esc` does what **✗** does. Neither is ever the only way.
|
||||
- `Esc` steps back exactly one level — a value field, then the gesture in progress, then the
|
||||
armed tool, then the selection. It never deletes, discards a sketch or rolls back a feature.
|
||||
- A click on empty space clears the selection. It never applies anything: a pending fillet,
|
||||
offset, move or array waits for `Enter` or **✓**.
|
||||
- Right-click on a tool with something pending drops that gesture; with nothing pending it
|
||||
opens the offer, like anywhere else.
|
||||
- Switching to another tool applies a pending operation that is complete and valid, then arms
|
||||
the new tool.
|
||||
- A value typed into a field that the geometry cannot take (a zero-length side, an arc of more
|
||||
than 360°) is refused in place: the field stays open and says why.
|
||||
|
||||
## Keyboard
|
||||
|
||||
Single letters drive sketch tools **while a sketch is open**; Shift+letter drives feature
|
||||
@@ -100,8 +122,10 @@ selected by the mode, not by whether a sketch session is running.
|
||||
| `H` | Chamfer — pick two lines, set the distance |
|
||||
| `K` | Constrain — finish the live sketch and enter constrain |
|
||||
| `Q` | Construction toggle — draw the next entity as construction geometry |
|
||||
| `Del` | Delete the selected sketch entity |
|
||||
| `Esc` | Cancel the live tool |
|
||||
| `Del` | Delete the selected sketch entities (nothing selected: nothing happens) |
|
||||
| `Enter` | Apply what is pending; otherwise end the chain; otherwise drop the tool |
|
||||
| `Esc` | Undo one level: close the field, drop the gesture, drop the tool, clear the selection |
|
||||
| `Ctrl+Z` / `Ctrl+Y` | Undo / redo the last sketch edit |
|
||||
|
||||
### Feature (when no sketch is open)
|
||||
|
||||
@@ -151,7 +175,9 @@ kept. With no section on, `F` is Place on Face — lay the picked face flat on t
|
||||
## Sketching
|
||||
|
||||
A sketch is a closed (or open) 2D profile on a plane or on a flat face of an existing body.
|
||||
Press `Shift+S`, click the face or plane you want to sketch on, and draw. The toolbar and
|
||||
Pick the face or plane and press `Shift+S` — the offer opens with the sketch tools. Pressing
|
||||
`Shift+S` with nothing picked also works: the status line asks for the face or plane, and the
|
||||
first tool you arm sketches on it. The toolbar and
|
||||
the offer both carry the sketch tools.
|
||||
|
||||
**Entities:** line, polyline, rectangle (corner / centre / oblique / rounded), circle
|
||||
@@ -290,6 +316,13 @@ edit; the importer warns before you commit to it.
|
||||
inside the 3MF, so reopening the project restores the editable model rather than a frozen
|
||||
mesh.
|
||||
|
||||
**MCP control** lets an external agent drive the tab through the same kernel the GUI uses.
|
||||
Start the app with `ORCA_CAD_MCP=1` (socket `/tmp/orca-cad-mcp.sock`) or
|
||||
`ORCA_CAD_MCP=/path/to.sock`; the CAD feature must be enabled too. Linux and macOS only. A
|
||||
command that would change the document is refused while the Design tab is busy with it — a
|
||||
rebuild, an open feature card or a sketch session — so the agent and the user never edit the
|
||||
same thing at once.
|
||||
|
||||
---
|
||||
|
||||
## View controls
|
||||
|
||||
@@ -20,13 +20,14 @@ design intent that both steps discard. A part modified after slicing should retu
|
||||
feature history, not to a mesh. Keeping the CAD model inside the slicer preserves that
|
||||
loop — the nozzle diameter, the build volume and the material are known at design time.
|
||||
|
||||
For the integration case in full: [design_tab_upstream_portability.md](docs/design_tab_upstream_portability.md).
|
||||
For the integration case in full: [design_tab_upstream_portability.md](design_tab_upstream_portability.md).
|
||||
|
||||
## How it is built
|
||||
|
||||
The `SLIC3R_CAD` CMake flag (default ON) gates the entire tab. With it OFF the tab is not
|
||||
compiled and the deps prefix matches upstream exactly — the dependency diff is one line in
|
||||
OCCT's CMake: `BUILD_MODULE_ModelingAlgorithms=OFF → ON`.
|
||||
OCCT's CMake: `BUILD_MODULE_ModelingAlgorithms=OFF → ON`. At run time the tab is off until
|
||||
Preferences → CAD feature (experimental) is enabled.
|
||||
|
||||
Measured cost table: [cad_dependency_weight.md](docs/cad_dependency_weight.md).
|
||||
|
||||
@@ -55,7 +56,7 @@ circle-line tangency solver abort and the internal-thread reference) are fixed.
|
||||
|
||||
The vendored solver in `src/libslic3r/slvs/` is **GPL-3.0** (see `src/libslic3r/slvs/LICENSE`),
|
||||
not LGPL. The combined work is distributable under AGPL-3.0. See the Licensing section of
|
||||
[design_tab_upstream_portability.md](docs/design_tab_upstream_portability.md) for the
|
||||
[design_tab_upstream_portability.md](design_tab_upstream_portability.md) for the
|
||||
AGPLv3/GPLv3 compatibility argument; this point should be confirmed with upstream explicitly.
|
||||
|
||||
## Not verified
|
||||
|
||||
@@ -1,162 +1,77 @@
|
||||
# Design (CAD) tab — upstream integration brief
|
||||
# Design (CAD) tab — integration surface
|
||||
|
||||
**Question:** can the Design tab (sketch-first parametric CAD: sketch → constrain →
|
||||
extrude/revolve/fillet/hole/thread/shell, multi-body, undo, 3MF persistence) land in
|
||||
mainline OrcaSlicer?
|
||||
What the Design tab adds to OrcaSlicer, what it touches that upstream already has, and the
|
||||
constraints that come with it. The dependency cost is measured separately in
|
||||
[cad_dependency_weight.md](cad_dependency_weight.md); this page does not repeat its numbers.
|
||||
|
||||
**Answer: yes, and the ask is far smaller than previously believed.** OCCT is *already*
|
||||
an OrcaSlicer dependency. We are not asking upstream to adopt a new library; we are
|
||||
asking it to widen one it already builds, at a measured cost of **3.77 MiB on Windows**.
|
||||
## Two gates
|
||||
|
||||
> ### Corrections to the 2026-06-21 assessment
|
||||
> That revision was written before the persistence work landed and got two load-bearing
|
||||
> facts wrong. Both are corrected here from direct measurement of the branch:
|
||||
>
|
||||
> 1. **"The real blocker: OCCT … a dependency mainline OrcaSlicer has never carried."**
|
||||
> **False.** `deps/OCCT/` exists at the merge-base and upstream links it from
|
||||
> `Format/STEP.cpp`, `Format/svg.cpp`, and `Shape/TextShape.cpp`. Our entire
|
||||
> dependency diff is **one line**: `BUILD_MODULE_ModelingAlgorithms=OFF → ON`.
|
||||
> 2. **"vendored SolveSpace solver … LGPL."** **False.** `src/libslic3r/slvs/LICENSE` is
|
||||
> **GPL-3.0**, not LGPL. This is fine (see Licensing) but must not be misstated.
|
||||
>
|
||||
> It also claimed "no changes to Model" — no longer true; 3MF recipe persistence adds one
|
||||
> `std::string` to `Model`.
|
||||
- **Compile time: `SLIC3R_CAD`** (`CMakeLists.txt`, default ON in this tree). OFF builds no
|
||||
CAD code at all and leaves the OCCT deps prefix identical to upstream. ON also turns on
|
||||
OCCT's `ModelingAlgorithms` module (`deps/OCCT/OCCT.cmake`).
|
||||
- **Run time: Preferences → CAD feature (experimental)** (`enable_cad_feature`, default OFF).
|
||||
Off, the Design page and the MCP control socket are not created, the Prepare-toolbar Sketch
|
||||
and Primitive gizmos cannot be selected, and the Design-only preferences are hidden: Prepare
|
||||
behaves as it does without the feature. A project that carries a CAD recipe still loads,
|
||||
keeps the recipe and writes it back unchanged.
|
||||
|
||||
## Measured shape of the change
|
||||
## Dependencies
|
||||
|
||||
Against merge-base `449a4cf9fc` (34 commits ahead):
|
||||
OCCT is already an upstream dependency (STEP import, SVG, text shapes). The Design tab adds no
|
||||
library; it widens the OCCT build by one module, which builds three extra toolkits — TKFillet,
|
||||
TKOffset and TKFeat — of which the first two are linked. The module flag is all-or-nothing, so
|
||||
TKFeat is built although nothing references it.
|
||||
|
||||
| | files | lines |
|
||||
|---|---:|---:|
|
||||
| **New files** | 138 | +61,720 |
|
||||
| **Modified upstream files** | 23 | +457 / −75 |
|
||||
| **Deleted upstream files** | 0 | — |
|
||||
The constraint solver is vendored in `src/libslic3r/slvs/`: a self-contained subset of
|
||||
SolveSpace (`libslvs`), GPL-3.0, no external dependencies. GPLv3 §13 and AGPLv3 §13 permit the
|
||||
combination with OrcaSlicer's AGPL-3.0.
|
||||
|
||||
The 62 kLOC headline is inflated by localization. The feature itself:
|
||||
## Footprint in upstream files
|
||||
|
||||
| area | LOC | files |
|
||||
|---|---:|---:|
|
||||
| kernel (`src/libslic3r/`) | 15,828 | 37 |
|
||||
| GUI (`src/slic3r/`) | 19,544 | 14 |
|
||||
| tests (Catch2) | 2,567 | 6 |
|
||||
| i18n (unrelated; strip from the CAD PR) | 23,438 | 77 |
|
||||
Almost all of the change is new files under `src/libslic3r/CAD/`, `src/slic3r/GUI/CAD/`,
|
||||
`src/libslic3r/slvs/` and `tests/`. The hooks into existing code are small and null-guarded:
|
||||
|
||||
**99.3 % of the diff is new files.** The negotiable surface is 457 added lines across 23
|
||||
files, and nothing upstream is deleted. The largest single hook is `GLCanvas3D.cpp`
|
||||
(+110/−2): an `m_design_sketch_tool` member plus render/mouse/key hooks, **every one
|
||||
already null-guarded** — which is why the compile-time gate below is cheap.
|
||||
- `GLCanvas3D` — a `DesignSketchTool` pointer plus render, mouse and key hooks, each a no-op
|
||||
when no Design canvas owns it.
|
||||
- `MainFrame` — the Design page and the Edit menu routing Undo/Redo to it while it is shown.
|
||||
- `Plater` — the "no geometry" warning distinguishes a recipe-only project.
|
||||
- `Model` — one `std::string cad_recipe`, empty for non-CAD projects.
|
||||
- `bbs_3mf` — reading and writing that string (below).
|
||||
- `Preferences`, `GUI_App` — the two preferences.
|
||||
|
||||
No changes to the slicing pipeline (Print/PrintObject/Layer/GCode), Tab, or the
|
||||
printer-profile/config system.
|
||||
Nothing in the slicing pipeline (Print, PrintObject, Layer, GCode), Tab, or the profile and
|
||||
config system changes.
|
||||
|
||||
## The dependency ask, precisely
|
||||
|
||||
Not "adopt OCCT" — **widen the existing OCCT build**:
|
||||
|
||||
```diff
|
||||
- -DBUILD_MODULE_ModelingAlgorithms=OFF
|
||||
+ -DBUILD_MODULE_ModelingAlgorithms=ON
|
||||
```
|
||||
|
||||
Cost, measured from the shipped Windows artifact (42 OCCT DLLs, 45.43 MiB total):
|
||||
|
||||
| toolkit | size | note |
|
||||
|---|---:|---|
|
||||
| `TKFillet.dll` | 2.02 MiB | only exists with the flag ON |
|
||||
| `TKOffset.dll` | 1.75 MiB | only exists with the flag ON |
|
||||
| **delta** | **3.77 MiB** | Windows only (OCCT is Shared on Win, Static elsewhere) |
|
||||
|
||||
`TKBool` is *not* part of the delta — upstream's `DataExchange` already pulls it in
|
||||
transitively. On macOS/Linux OCCT links statically, so the cost is only the code actually
|
||||
referenced, not a 3.77 MiB floor.
|
||||
|
||||
**Unmeasured, and we should measure before the call:** clean-deps build-time delta with
|
||||
the flag ON vs OFF, and the resulting CI runner-minute cost. Do not guess these at him.
|
||||
|
||||
## Licensing
|
||||
|
||||
- Vendored solver `src/libslic3r/slvs/` — **GPL-3.0**, 9,339 LOC, © Jonathan Westhues,
|
||||
a self-contained subset of SolveSpace (`libslvs`). No external dependencies.
|
||||
- OrcaSlicer — **AGPL-3.0** (`LICENSE.txt`).
|
||||
|
||||
GPLv3 §13 expressly permits combining a GPLv3 work with an AGPLv3 work; AGPLv3 §13 grants
|
||||
the converse. The combined work is distributable under AGPL-3.0 with the solver's GPLv3
|
||||
terms preserved. This is a favourable direction (GPLv3 → into an AGPLv3 project), but it
|
||||
is a point to **confirm explicitly with upstream**, not to assert unilaterally.
|
||||
|
||||
Open question for SoftFever: keep the solver **vendored** (current: pinned, no submodule,
|
||||
no external build) or move it to `deps/` as a fetched external? Vendoring costs us
|
||||
upstream-sync burden; `deps/` costs build complexity.
|
||||
|
||||
## The one irreversible decision: the 3MF format
|
||||
|
||||
Persistence adds an **optional** archive entry and one field:
|
||||
|
||||
```cpp
|
||||
// Model.hpp
|
||||
std::string cad_recipe; // empty for non-CAD projects
|
||||
```
|
||||
## The 3MF entry
|
||||
|
||||
```
|
||||
Metadata/orca_cad.bin // written only when cad_recipe is non-empty
|
||||
Metadata/orca_cad.bin written only when cad_recipe is non-empty
|
||||
Metadata/SnapOrca_cad.bin legacy name: read, never written; loses to orca_cad.bin
|
||||
```
|
||||
|
||||
Readers that do not know the entry ignore it; writers skip it entirely when empty. So
|
||||
existing projects are bit-identical and old readers are unaffected. Good.
|
||||
Readers that do not know the entry ignore it and writers skip it when the recipe is empty, so
|
||||
projects without a design are byte-identical and older readers are unaffected. The reader caps
|
||||
the entry at 1 GiB.
|
||||
|
||||
**But the moment upstream ships this, it owns forward-compatibility forever.** Three
|
||||
things should be settled *before* the first release, because none can be changed after:
|
||||
The entry is a cereal `PortableBinaryArchive`, portable across endianness and word size. Its
|
||||
layout is versioned:
|
||||
|
||||
1. **Name.** Renamed to `Metadata/orca_cad.bin`.
|
||||
2. **Encoding.** The recipe is an opaque **cereal `PortableBinaryArchive`** blob whose
|
||||
layout is the field order of `CadFeature::serialize`. Portable across endianness and
|
||||
word size — *not* across a field reorder. Append-only is currently a convention held by
|
||||
discipline, not by any check.
|
||||
3. **Embedded BRep.** `Import` features embed OCCT's ASCII BRep for the imported solid,
|
||||
which couples saved project files to an OCCT BRep revision. Alternative: re-import from
|
||||
the source STEP and store only a reference. Worth deciding deliberately.
|
||||
- **v5 and later** frame every feature separately, so fields are appended to
|
||||
`CadFeature::serialize` without a version bump and an older reader skips what it does not
|
||||
know. Optional document-level data (variables, body names, origin, colours, the
|
||||
auto-close setting) follows in trailing blocks that a reader stops at cleanly when absent.
|
||||
- **v4** is the flat layout, read by the frozen `CadFeature::load_flat_v4`, which must never
|
||||
change.
|
||||
|
||||
**Concrete gap we should close before the call.** `test_caddocument.cpp` covers the
|
||||
in-memory round-trip and correctly refuses a version-999 blob — but there is **no
|
||||
checked-in v1 fixture on disk**. A reordered field in `CadFeature::serialize` would pass
|
||||
the entire suite while silently breaking every previously-saved project. Ship a golden
|
||||
`.bin` fixture generated today plus a test that loads it; that is the only thing that will
|
||||
hold the format still once real users have files.
|
||||
Checked-in fixtures `tests/data/cad_recipe_v3.bin`, `cad_recipe_v4.bin` and
|
||||
`cad_recipe_v5.bin` hold the format still: `test_caddocument.cpp` loads them and checks what
|
||||
they contain, so a reordered field fails the suite instead of breaking saved projects.
|
||||
|
||||
## Proposed PR decomposition
|
||||
`Import` features embed OCCT's ASCII BRep of the imported solid, which ties a saved project to
|
||||
a BRep revision OCCT can read.
|
||||
|
||||
35 kLOC in one PR is not reviewable. Behind the flag, slices 1–4 are behaviour-neutral for
|
||||
existing users:
|
||||
## Undo
|
||||
|
||||
1. **Build gate + OCCT flag + Windows packaging guard.** `-DSLIC3R_CAD=ON/OFF`, default
|
||||
**OFF**. Flips `ModelingAlgorithms=ON`. Includes the guard that asserts every linked
|
||||
OCCT toolkit has a shipped DLL (already on both forks: `546cef5f42`). ← *this is what
|
||||
makes SoftFever's "parallel build" a one-line CI matrix entry.*
|
||||
2. **Vendored `slvs` solver** + its Catch2 tests. No GUI, no OCCT.
|
||||
3. **CAD kernel** (`CadDocument`, `SketchEngine`, `GeometryEngine`, `Sketch*`) + kernel
|
||||
tests. Headless, no GUI.
|
||||
4. **3MF recipe persistence** + golden-fixture regression test.
|
||||
5. **GUI Design tab** (`DesignPanel`, `DesignCanvas`, `DesignSketchTool`, `GLGizmoSketch`)
|
||||
+ the 23 upstream hooks.
|
||||
|
||||
## Agenda for the call
|
||||
|
||||
Questions only SoftFever can answer:
|
||||
|
||||
- Does OrcaSlicer *want* to be a CAD-integrated slicer? (Strategic; everything else is mechanical.)
|
||||
- Default of `SLIC3R_CAD` at merge time, and when it flips ON.
|
||||
- Vendored solver vs `deps/` external; and confirmation of the GPLv3/AGPLv3 combination.
|
||||
- Project-file format: neutral name, encoding, embedded-BRep policy, and who owns v1 forward-compat.
|
||||
- Undo/redo: the Design tab has its own stack; integrate with Orca's snapshot system or keep separate?
|
||||
- Does he want the i18n work (Romanian, +23 kLOC) as a wholly separate PR? (Yes, almost certainly.)
|
||||
|
||||
## Verdict
|
||||
|
||||
Portability **high**. The prior "does upstream want OCCT" framing was wrong — OCCT is
|
||||
already there. What remains is a 3.77 MiB dependency widening, a compile-time gate that
|
||||
the existing null-guards make cheap, and one file-format decision that must be made before
|
||||
the first release rather than after.
|
||||
|
||||
---
|
||||
*Revised 2026-07-10 from direct measurement of `cad-mainline` @ `546cef5f42` vs upstream
|
||||
merge-base `449a4cf9fc`. Supersedes the 2026-06-21 read-only assessment.*
|
||||
The Design tab keeps its own history (`CadDocument` checkpoints), separate from the Plater's
|
||||
snapshot stack. The Edit menu's Undo/Redo drive whichever of the two belongs to the page on
|
||||
screen.
|
||||
|
||||
@@ -72,10 +72,11 @@ which is what makes the destructive path unrepresentable rather than merely unli
|
||||
own handlers so it can consume the event before them:
|
||||
|
||||
```cpp
|
||||
RIGHT_DOWN: remember the press position and the clock, then Skip() // the canvas still seeds the orbit
|
||||
RIGHT_DOWN: remember the press position, clear `travelled`, then Skip() // the canvas still seeds the orbit
|
||||
MOTION: travelled |= drift from the press > 3 px // an orbit that came back still orbited
|
||||
|
||||
RIGHT_UP: terminated = sketch_tool.take_right_consumed(); // read-and-clear, always
|
||||
is_click = drift <= 3 px && dt <= 200 ms; // both budgets, or it was navigation
|
||||
is_click = !travelled && drift <= 3 px; // otherwise it was navigation
|
||||
if (callback && !terminated && !inline_busy && is_click) {
|
||||
select_at_screen(press.x, press.y); // raycast at the PRESS, not the release
|
||||
on_context_menu(ClientToScreen(press));
|
||||
@@ -84,8 +85,9 @@ RIGHT_UP: terminated = sketch_tool.take_right_consumed(); // read-and
|
||||
Skip(); // orbit / pan / the handlers underneath
|
||||
```
|
||||
|
||||
Two independent budgets because the two failure modes are independent: drift alone still popped a
|
||||
menu at the end of a slow, careful orbit. `take_right_consumed()` is how a right-click that
|
||||
Only distance decides, never time (charter §6.2: no timing-dependent gestures): a slow, deliberate
|
||||
right-click is still a click. The drift is tracked over the whole press, not just at release, so a
|
||||
careful orbit that ends where it started does not pop a menu. `take_right_consumed()` is how a right-click that
|
||||
already meant something to the armed sketch tool (terminate a chain, drop an edit-op) declines to
|
||||
also mean "open a menu".
|
||||
|
||||
@@ -107,7 +109,7 @@ also mean "open a menu".
|
||||
| **Transient — popup menu** | run the entry | — | close the menu | run the highlighted entry |
|
||||
| **any** | — | — | *never* deletes, discards or rolls back | — |
|
||||
|
||||
Right-hold-and-drag is not in the table on purpose: past 3 px or 200 ms it is navigation, and
|
||||
Right-hold-and-drag is not in the table on purpose: once it has travelled past 3 px it is navigation, and
|
||||
navigation does not transition the state machine.
|
||||
|
||||
## 4. Visual scaffolding
|
||||
|
||||
@@ -42,6 +42,12 @@ def cstr(s):
|
||||
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
|
||||
|
||||
def tstr(s):
|
||||
# User-facing text: wrapped in the no-op L() marker so xgettext puts it in the catalogue.
|
||||
# The DesignPanel translates it at use; without the marker it compiles and never translates.
|
||||
return "nullptr" if s is None else "L(" + cstr(s) + ")"
|
||||
|
||||
|
||||
def main():
|
||||
A = json.load(open(ATLAS, encoding="utf-8"))
|
||||
sels = [s["id"] for s in A["selections"]]
|
||||
@@ -61,6 +67,10 @@ def main():
|
||||
"",
|
||||
"#include <cstdint>",
|
||||
"",
|
||||
"#ifndef L",
|
||||
"#define L(s) s // gettext marker, as in slic3r/GUI/I18N.hpp",
|
||||
"#endif",
|
||||
"",
|
||||
"namespace Slic3r { namespace GUI {",
|
||||
"",
|
||||
"// What the viewport has selected. Ordered as in tool_atlas.json; the bitmask in",
|
||||
@@ -83,7 +93,7 @@ def main():
|
||||
"// nullptr -> kernel support exists, no GUI path yet (row shows disabled)",
|
||||
"struct OfferVerb {",
|
||||
" const char* id;",
|
||||
" const char* name; // drawing-office word (L10); translated at use with wxGetTranslation",
|
||||
" const char* name; // drawing-office word (L10); marked L(); translated at use",
|
||||
" int row; // 0..7, the ratified index — NEVER reorder",
|
||||
" const char* key; // shortcut shown in the row, or nullptr",
|
||||
" const char* action;",
|
||||
@@ -106,7 +116,7 @@ def main():
|
||||
"static const char* const kOfferRowNames[] = {",
|
||||
]
|
||||
for s in A["slots"]:
|
||||
lines.append(f' "{s["label"]}",')
|
||||
lines.append(f' {tstr(s["label"])},')
|
||||
lines += [
|
||||
"};",
|
||||
f"static const int kOfferRowCount = {len(slots)};",
|
||||
@@ -131,12 +141,12 @@ def main():
|
||||
n = v.get("needs") or {}
|
||||
lines.append(
|
||||
" {%s, %s, %d, %s, %s, %s, 0x%08xu, %d, %d, %s, %s, %s, %s, %s}," % (
|
||||
cstr(v["id"]), cstr(v["name"]), slots.index(v["slot"]),
|
||||
cstr(v.get("key")), cstr(v.get("action")), cstr(v.get("refusal")),
|
||||
cstr(v["id"]), tstr(v["name"]), slots.index(v["slot"]),
|
||||
cstr(v.get("key")), cstr(v.get("action")), tstr(v.get("refusal")),
|
||||
mask, n.get("bodies", 0), n.get("sketches", 0),
|
||||
"true" if n.get("sheet") else "false",
|
||||
"true" if v.get("mode") == "sketch" else "false",
|
||||
cstr(v.get("family")), cstr(v.get("icon")), cstr(v.get("hint"))))
|
||||
tstr(v.get("family")), cstr(v.get("icon")), tstr(v.get("hint"))))
|
||||
lines += [
|
||||
"};",
|
||||
f"static const int kOfferVerbCount = {len(A['verbs'])};",
|
||||
|
||||
+38
-36
@@ -326,7 +326,7 @@
|
||||
"id": "rib",
|
||||
"name": "Rib",
|
||||
"slot": "add",
|
||||
"key": null,
|
||||
"key": "R",
|
||||
"feature": "Rib",
|
||||
"mcp": "rib",
|
||||
"accepts": [
|
||||
@@ -343,7 +343,7 @@
|
||||
},
|
||||
{
|
||||
"id": "boolean",
|
||||
"name": "Union",
|
||||
"name": "Join",
|
||||
"slot": "add",
|
||||
"key": "Shift+B",
|
||||
"feature": "Boolean",
|
||||
@@ -354,7 +354,7 @@
|
||||
"needs": {
|
||||
"bodies": 2
|
||||
},
|
||||
"refusal": "Boolean needs two bodies — create or import a second solid",
|
||||
"refusal": "Boolean needs two bodies — add or import a second one",
|
||||
"gui": true,
|
||||
"action": "btn:bool#0",
|
||||
"icon": "design_boolean",
|
||||
@@ -373,7 +373,7 @@
|
||||
"needs": {
|
||||
"bodies": 2
|
||||
},
|
||||
"refusal": "Boolean needs two bodies — create or import a second solid",
|
||||
"refusal": "Boolean needs two bodies — add or import a second one",
|
||||
"gui": true,
|
||||
"action": "btn:bool#1",
|
||||
"mode": null,
|
||||
@@ -393,7 +393,7 @@
|
||||
"needs": {
|
||||
"bodies": 2
|
||||
},
|
||||
"refusal": "Boolean needs two bodies — create or import a second solid",
|
||||
"refusal": "Boolean needs two bodies — add or import a second one",
|
||||
"gui": true,
|
||||
"action": "btn:bool#2",
|
||||
"mode": null,
|
||||
@@ -483,7 +483,7 @@
|
||||
"needs": {
|
||||
"sheet": true
|
||||
},
|
||||
"refusal": "target is not a sheet body",
|
||||
"refusal": "Thicken Surface needs a surface body — make one with a Surface tool first",
|
||||
"gui": true,
|
||||
"action": "fly:surface#5",
|
||||
"icon": "design_thicken",
|
||||
@@ -507,7 +507,7 @@
|
||||
"gui": true,
|
||||
"action": "key:S+H",
|
||||
"icon": "design_hole",
|
||||
"hint": "Drill a hole, centred on a picked face or placed on a plane"
|
||||
"hint": "Drill a hole, centerd on a picked face or placed on a plane"
|
||||
},
|
||||
{
|
||||
"id": "thread",
|
||||
@@ -543,7 +543,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Shell needs a solid body",
|
||||
"refusal": "Shell needs a solid body — add or import one first",
|
||||
"gui": true,
|
||||
"action": "key:S+K",
|
||||
"icon": "design_shell",
|
||||
@@ -569,7 +569,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Create a solid body to cut first",
|
||||
"refusal": "Cut needs a solid body — add or import one first",
|
||||
"gui": true,
|
||||
"action": "key:S+X",
|
||||
"icon": "design_cut",
|
||||
@@ -594,7 +594,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Split needs a solid body",
|
||||
"refusal": "Split needs a solid body — add or import one first",
|
||||
"gui": false,
|
||||
"action": null,
|
||||
"icon": null,
|
||||
@@ -677,7 +677,7 @@
|
||||
"needs": {
|
||||
"sheet": true
|
||||
},
|
||||
"refusal": "target is not a sheet body",
|
||||
"refusal": "Surface Offset needs a surface body — make one with a Surface tool first",
|
||||
"gui": true,
|
||||
"action": "fly:surface#4",
|
||||
"icon": "design_offset",
|
||||
@@ -699,7 +699,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Create a solid body to pattern first",
|
||||
"refusal": "Pattern needs a solid body — add or import one first",
|
||||
"gui": true,
|
||||
"action": "btn:pat#0",
|
||||
"icon": "design_array",
|
||||
@@ -721,7 +721,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Create a solid body to pattern first",
|
||||
"refusal": "Pattern needs a solid body — add or import one first",
|
||||
"gui": true,
|
||||
"action": "btn:pat#1",
|
||||
"mode": null,
|
||||
@@ -768,7 +768,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Pattern on curve needs a body and a curve",
|
||||
"refusal": "Pattern on Curve needs a body and a curve",
|
||||
"gui": false,
|
||||
"action": null,
|
||||
"icon": null,
|
||||
@@ -816,11 +816,11 @@
|
||||
"needs": {
|
||||
"bodies": 2
|
||||
},
|
||||
"refusal": "A mate needs two coordinate systems",
|
||||
"refusal": "Mate needs two coordinate systems — create them first",
|
||||
"gui": true,
|
||||
"action": "fly:placement#2",
|
||||
"icon": "design_c_coincident",
|
||||
"hint": "Assembly: align two CoordSys features (fastened, planar, revolute, slider, cylindrical)"
|
||||
"hint": "Assembly: align two coordinate systems (fastened, planar, revolute, slider, cylindrical)"
|
||||
},
|
||||
{
|
||||
"id": "align",
|
||||
@@ -835,7 +835,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Align needs a body",
|
||||
"refusal": "Align needs a body — add or import one first",
|
||||
"gui": false,
|
||||
"action": null,
|
||||
"icon": null,
|
||||
@@ -885,7 +885,7 @@
|
||||
},
|
||||
{
|
||||
"id": "coordsys_v",
|
||||
"name": "Coord Sys",
|
||||
"name": "Coordinate system",
|
||||
"slot": "reference",
|
||||
"key": "Shift+C",
|
||||
"feature": "CoordSys",
|
||||
@@ -972,7 +972,7 @@
|
||||
},
|
||||
{
|
||||
"id": "mass_props",
|
||||
"name": "Mass",
|
||||
"name": "Volume and area",
|
||||
"slot": "reference",
|
||||
"key": null,
|
||||
"feature": null,
|
||||
@@ -1003,14 +1003,15 @@
|
||||
"feature": null,
|
||||
"mcp": "check_interference",
|
||||
"accepts": [
|
||||
"body_solid",
|
||||
"bodies_2"
|
||||
],
|
||||
"needs": {
|
||||
"bodies": 2
|
||||
},
|
||||
"refusal": null,
|
||||
"gui": false,
|
||||
"action": null,
|
||||
"refusal": "Interference needs at least two bodies",
|
||||
"gui": true,
|
||||
"action": "btn:interference",
|
||||
"icon": null,
|
||||
"hint": "Check whether two bodies overlap — reports, changes nothing"
|
||||
},
|
||||
@@ -1082,7 +1083,7 @@
|
||||
},
|
||||
{
|
||||
"id": "colour",
|
||||
"name": "Colour",
|
||||
"name": "Color",
|
||||
"slot": "modify",
|
||||
"key": null,
|
||||
"feature": null,
|
||||
@@ -1104,7 +1105,7 @@
|
||||
"gui": true,
|
||||
"action": "btn:colour",
|
||||
"icon": "color_palette",
|
||||
"hint": "Set the selected body's display colour"
|
||||
"hint": "Set the selected body's display color"
|
||||
},
|
||||
{
|
||||
"id": "delete",
|
||||
@@ -1227,7 +1228,7 @@
|
||||
},
|
||||
{
|
||||
"id": "sk_rect_center",
|
||||
"name": "Centre rectangle",
|
||||
"name": "Center rectangle",
|
||||
"slot": "create",
|
||||
"key": null,
|
||||
"feature": "Sketch",
|
||||
@@ -1296,7 +1297,7 @@
|
||||
},
|
||||
{
|
||||
"id": "sk_circle",
|
||||
"name": "Centre circle",
|
||||
"name": "Center circle",
|
||||
"slot": "create",
|
||||
"key": "C",
|
||||
"feature": "Sketch",
|
||||
@@ -1411,7 +1412,7 @@
|
||||
},
|
||||
{
|
||||
"id": "sk_arc_center",
|
||||
"name": "Centre-point arc",
|
||||
"name": "Center-point arc",
|
||||
"slot": "create",
|
||||
"key": null,
|
||||
"feature": "Sketch",
|
||||
@@ -1453,7 +1454,7 @@
|
||||
"action": "key:S",
|
||||
"family": "Slot",
|
||||
"icon": "design_slot",
|
||||
"hint": "Slot — two centerline ends, then end radius"
|
||||
"hint": "Slot — two centerline ends, then the width"
|
||||
},
|
||||
{
|
||||
"id": "sk_slot_arc",
|
||||
@@ -1567,7 +1568,7 @@
|
||||
"mode": "sketch",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Triangle — click centre, then a vertex"
|
||||
"hint": "Triangle — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_poly_4",
|
||||
@@ -1590,7 +1591,7 @@
|
||||
"mode": "sketch",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Square — click centre, then a vertex"
|
||||
"hint": "Square — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_poly_5",
|
||||
@@ -1613,7 +1614,7 @@
|
||||
"mode": "sketch",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Pentagon — click centre, then a vertex"
|
||||
"hint": "Pentagon — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_polygon",
|
||||
@@ -1636,7 +1637,7 @@
|
||||
"action": "btn:poly#6",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Hexagon — click centre, then a vertex"
|
||||
"hint": "Hexagon — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_poly_8",
|
||||
@@ -1659,7 +1660,7 @@
|
||||
"mode": "sketch",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Octagon — click centre, then a vertex"
|
||||
"hint": "Octagon — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_poly_12",
|
||||
@@ -1682,7 +1683,7 @@
|
||||
"mode": "sketch",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Dodecagon — click centre, then a vertex"
|
||||
"hint": "Dodecagon — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_poly_inscribed",
|
||||
@@ -2048,7 +2049,7 @@
|
||||
{
|
||||
"id": "constrain",
|
||||
"name": "Constrain sketch",
|
||||
"slot": "modify",
|
||||
"slot": "reference",
|
||||
"key": null,
|
||||
"feature": "Sketch",
|
||||
"mcp": null,
|
||||
@@ -2067,7 +2068,8 @@
|
||||
"note": [
|
||||
"Same verb, model-mode vocabulary: offered when a SKETCH is selected (bit 14, SkLoop), the",
|
||||
"state a user is in right after finishing one. Without this row the only way in was the",
|
||||
"toolbar icon, and constraints read as absent — see the Onshape-comparison report."
|
||||
"toolbar icon, and constraints read as absent — see the Onshape-comparison report.",
|
||||
"It sits in the Reference row, the address the sketch-mode Constrain has: one verb, one row."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user