mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-06 07:11:10 +00:00
- 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
147 lines
8.2 KiB
Markdown
147 lines
8.2 KiB
Markdown
# Design tab — interaction model
|
|
|
|
The contract for Esc, the right mouse button, and the states between them. Code that changes any
|
|
of the three changes this file in the same commit.
|
|
|
|
## 1. The state machine
|
|
|
|
`src/slic3r/GUI/CAD/DesignInteraction.hpp` — a four-level LIFO stack. The enum value *is* the
|
|
depth, so "which level does this press belong to" is a comparison rather than a chain of
|
|
special cases spread over three files.
|
|
|
|
```cpp
|
|
enum class CadLevel : int {
|
|
Idle = 0, // nothing transient is up: Esc clears the selection
|
|
Tool = 1, // a feature card / armed sketch tool / constrain session: Esc exits it
|
|
Gesture = 2, // an uncommitted delta (entity being drawn, body being dragged): Esc reverts it
|
|
Transient = 3, // a value field or a popup menu: Esc closes just that
|
|
};
|
|
|
|
struct CadInteractionState { // the four bits routing actually needs
|
|
bool value_field_open{false};
|
|
bool gesture_active{false};
|
|
bool tool_armed{false};
|
|
bool has_selection{false};
|
|
};
|
|
|
|
constexpr CadLevel cad_escape_level(const CadInteractionState& s)
|
|
{
|
|
if (s.value_field_open) return CadLevel::Transient;
|
|
if (s.gesture_active) return CadLevel::Gesture;
|
|
if (s.tool_armed) return CadLevel::Tool;
|
|
return CadLevel::Idle;
|
|
}
|
|
```
|
|
|
|
The rule is a `constexpr` free function over a POD, not 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.
|
|
|
|
**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:
|
|
|
|
| To destroy | Gesture |
|
|
|---|---|
|
|
| a feature | Delete / Backspace on an explicit selection |
|
|
| a drawn sketch | the ribbon's ✗ Cancel, which asks first |
|
|
| the last committed change | Ctrl+Z |
|
|
|
|
## 2. Event routing
|
|
|
|
**`OnKeyDown(WXK_ESCAPE)`** — `DesignPanel`'s `wxEVT_CHAR_HOOK`, one line:
|
|
|
|
```cpp
|
|
if (key == WXK_ESCAPE) { escape(); return; }
|
|
```
|
|
|
|
Every Esc in the tab goes through it, whatever holds focus. `DesignPanel::escape_level()` answers
|
|
the four questions of `CadInteractionState` about this panel; `DesignPanel::escape()` acts on the
|
|
one level that answer names, and on no other:
|
|
|
|
| Level | What one press does | What it must not touch |
|
|
|---|---|---|
|
|
| `Transient` | close the value field (`cancel_value` / `inline_cancel`) | the tool, which stays armed |
|
|
| `Gesture` | drop the clicks of the entity being drawn, or put a moved body back at the pose it had when the gizmo appeared | everything already committed |
|
|
| `Tool` | discard a feature card's *candidate*; drop an armed sketch tool to Select; end Constrain | committed features; entities already drawn |
|
|
| `Idle` | clear the selection (model and sketch); leave a sketch session **only if it is empty** | a sketch holding geometry — it is left through Finish or Cancel |
|
|
|
|
A sketch *session* is deliberately not a `Tool`. It is the environment the Idle level lives in,
|
|
which is what makes the destructive path unrepresentable rather than merely unlikely.
|
|
|
|
**`OnRightDown` / `OnRightUp`** — `DesignCanvas::set_on_context_menu`, bound after `GLCanvas3D`'s
|
|
own handlers so it can consume the event before them:
|
|
|
|
```cpp
|
|
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 = !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));
|
|
return; // consumed
|
|
}
|
|
Skip(); // orbit / pan / the handlers underneath
|
|
```
|
|
|
|
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".
|
|
|
|
## 3. Transition table
|
|
|
|
`sel` = something is picked. Blank = the input does nothing at that state.
|
|
|
|
| State | Left-click | Right-click | Esc | Enter |
|
|
|---|---|---|---|---|
|
|
| **Idle — model view** | pick / escalate the pick | offer menu for what is under the cursor | clear the selection | — |
|
|
| **Idle — sketch, empty** | pick | sketch offer menu | leave the session (nothing to lose) | Finish sketch |
|
|
| **Idle — sketch, drawn** | pick | sketch offer menu | clear the selection; status says the sketch is kept | Finish sketch |
|
|
| **Tool — feature card** | pick the card's next reference | offer menu | discard the candidate, close the card | commit the feature |
|
|
| **Tool — sketch tool armed** | place the first point | drop the tool to Select | drop the tool to Select | — |
|
|
| **Tool — constrain** | pick an entity | offer menu | end the session | apply |
|
|
| **Gesture — drawing** | place the next point | terminate the chain (keep what is drawn) | drop the in-progress entity, tool stays armed | commit the entity as drawn |
|
|
| **Gesture — moving a body** | drop the body here | end the move | revert to the pose at move-start | keep the placement |
|
|
| **Transient — value field** | — | — | close the field, tool stays armed | commit the value, advance the chain |
|
|
| **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: once it has travelled past 3 px it is navigation, and
|
|
navigation does not transition the state machine.
|
|
|
|
## 4. Visual scaffolding
|
|
|
|
Entering a sketch changes three things at once, so the state is legible from across the room:
|
|
|
|
- **Banner.** A teal strip across the top of the viewport: `Editing: Sketch N · N = look normal to
|
|
the plane · Finish or Cancel in the toolbar`. Indicator only — Confirm and Cancel stay on the one
|
|
ribbon action bar, per the Design UX contract. It is a sibling above the canvas, not a floating
|
|
child over it: a child window over a `wxGLCanvas` is a native window on GTK and does not reliably
|
|
stack over GL, and this banner's job is to be unmissable rather than clever.
|
|
- **The printer bed is muted.** A plate grid and a sketch grid are the same visual language, and
|
|
reading one as the other is how a sketch gets drawn against the wrong reference. The view
|
|
checkbox remains the stored preference and is restored on the way out; ticking it mid-sketch
|
|
still shows the bed, because that is a deliberate act and this is only a default.
|
|
- **`N` looks normal to the plane**, keeping the current zoom, with the plane's own y axis as up.
|
|
Sketch key map only — in Feature mode the navigator orb owns orientation.
|
|
|
|
## 5. Context menu content
|
|
|
|
The offer is generated from `docs/CAD/ux/tool_atlas.json`; its 8-row shape and permanent row
|
|
indices are ratified and are not changed here. Checked against the per-context vocabularies asked
|
|
for in the 2026-09-05 interaction brief, the atlas already carries all of them except two, both on
|
|
a planar face:
|
|
|
|
| Asked for | Status |
|
|
|---|---|
|
|
| Revolve on a planar face | **not offered, and should not be**: `revolve` accepts `sk_loop` only, because the kernel takes a sketch profile — a face is not one |
|
|
| Offset Face | offered as **Thicken** (`thicken`, accepts `face_planar`); `surf_offset` is the sheet-body verb and accepts `body_sheet` |
|
|
|
|
View and document actions — Zoom to Fit, View Isometric, Clear Selection, Finish Sketch, Normal to
|
|
Sketch — stay in chrome by the atlas's own rule: the offer describes verbs that consume a
|
|
*selection*, and these act on the document or the camera. Esc covers Clear Selection, `N` covers
|
|
Normal to Sketch, and the ribbon covers Finish.
|