From 3f52166e3226307364f31e3b51cd6ed635eefbaf Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Sat, 5 Sep 2026 11:45:29 +0200 Subject: [PATCH] A sketch should not look like plate preparation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three cues, because one is missed. A teal banner across the top of the viewport names the session ("Editing: Sketch N") and where its exits are; the printer bed is muted for the duration, since 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; and N looks straight down the plane normal at the current zoom, with the plane's own y axis as up, because no hand-orbit lands exactly square and a sketch read at an angle is one whose right angles do not look like right angles. The banner is an INDICATOR. Finish and Cancel stay on the single ribbon action bar — the tab had three competing confirm surfaces once and that is not being reopened for a strip of colour. It sits above the canvas rather than floating inside it: a child window over a wxGLCanvas is a native window on GTK with no reliable stacking over GL, and being unmissable beats being clever. The bed checkbox stays the stored preference and is restored on leaving the sketch; ticking it mid-sketch still shows the bed, because that is a deliberate act and this is only a default. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA --- docs/{ => CAD}/ux/interaction-model.md | 33 +++++++++++++++++++ src/slic3r/GUI/CAD/DesignCanvas.cpp | 15 +++++++++ src/slic3r/GUI/CAD/DesignCanvas.hpp | 3 ++ src/slic3r/GUI/CAD/DesignPanel.cpp | 45 ++++++++++++++++++++++++++ src/slic3r/GUI/CAD/DesignPanel.hpp | 7 ++++ 5 files changed, 103 insertions(+) rename docs/{ => CAD}/ux/interaction-model.md (73%) diff --git a/docs/ux/interaction-model.md b/docs/CAD/ux/interaction-model.md similarity index 73% rename from docs/ux/interaction-model.md rename to docs/CAD/ux/interaction-model.md index e6d5428253..2041978ea0 100644 --- a/docs/ux/interaction-model.md +++ b/docs/CAD/ux/interaction-model.md @@ -109,3 +109,36 @@ also mean "open a menu". Right-hold-and-drag is not in the table on purpose: past 3 px or 200 ms 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. diff --git a/src/slic3r/GUI/CAD/DesignCanvas.cpp b/src/slic3r/GUI/CAD/DesignCanvas.cpp index 1d0ea603c6..e538495caa 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.cpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.cpp @@ -5,6 +5,7 @@ #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/OpenGLManager.hpp" #include "slic3r/GUI/3DBed.hpp" +#include "slic3r/GUI/Camera.hpp" // N: look down the sketch plane normal #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "libslic3r/Model.hpp" @@ -1486,6 +1487,20 @@ int DesignCanvas::sketch_selection_count() const return int(m_sketch_tool.selection().size()); } +bool DesignCanvas::view_normal_to_sketch() +{ + if (m_canvas == nullptr) return false; + const SketchPlane& pl = m_sketch_tool.plane(); + Camera& cam = wxGetApp().plater()->get_camera(); + // Keep the distance: this is an orientation change, not a zoom. The plane's own y axis is + // the up vector, so "up" on screen is up in sketch coordinates — which is what makes a + // dimension typed after pressing N land where the eye expects it. + const double dist = cam.get_distance(); + cam.look_at(pl.origin + pl.normal * dist, pl.origin, pl.y_axis); + request_repaint(); + return true; +} + bool DesignCanvas::sketch_abort_gesture() { if (!m_sketch_tool.abort_gesture()) return false; diff --git a/src/slic3r/GUI/CAD/DesignCanvas.hpp b/src/slic3r/GUI/CAD/DesignCanvas.hpp index b6ceca2390..c6b7a3d2aa 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.hpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.hpp @@ -80,6 +80,9 @@ public: void unbind_canvas_event_handlers(); // app close / language switch, from the plater's teardown void reset_canvas_volumes(); void set_show_bed(bool b); // view option: draw the printer bed + plate grid, or not + // N: look straight down the sketch plane's normal, keeping the current zoom. A sketch drawn + // at an angle is a sketch drawn wrong, and no amount of orbiting by hand lands exactly square. + bool view_normal_to_sketch(); void cancel_sketch(); void set_on_sketch_commit(std::function cb); void set_on_sketch_entities_commit( diff --git a/src/slic3r/GUI/CAD/DesignPanel.cpp b/src/slic3r/GUI/CAD/DesignPanel.cpp index c1ff2df5c3..c35f773330 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.cpp +++ b/src/slic3r/GUI/CAD/DesignPanel.cpp @@ -441,6 +441,16 @@ DesignPanel::DesignPanel(wxWindow* parent) set_status(_L("Nothing here has a value to type — pick a line, an arc, a circle, or two entities")); }; m_keys_sketch['K'] = [this] { enter_constrain_inline(); }; + // N: square up to the plane. Not a view preference but part of drawing — a sketch read at an + // angle is a sketch whose right angles do not look like right angles, and no hand-orbit lands + // exactly normal. Sketch map only: in Feature mode the navigator orb owns orientation. + m_keys_sketch['N'] = [this] { + if (m_viewport && m_viewport->view_normal_to_sketch()) { + m_status->SetForegroundColour(wxNullColour); + set_status(_L("Normal to the sketch plane")); + m_status->Refresh(); + } + }; m_keys_sketch['Q'] = [this] { // With geometry selected, Q converts THAT geometry (snaporca-6zic) — the reading // everyone arrives with from other sketchers. With nothing selected it keeps its @@ -4316,6 +4326,23 @@ DesignPanel::DesignPanel(wxWindow* parent) }); auto* vcol = new wxBoxSizer(wxVERTICAL); + // The sketch banner sits ABOVE the viewport rather than floating inside it: a child window + // over a wxGLCanvas is a platform argument (it is a native window on GTK and does not + // reliably stack over GL), and the banner's job is to be unmissable, not to be clever. + m_sketch_banner = new wxPanel(this, wxID_ANY); + m_sketch_banner->SetBackgroundColour(wxColour(0, 122, 116)); // Orca teal: not a plate colour + m_sketch_banner_txt = new wxStaticText(m_sketch_banner, wxID_ANY, wxString()); + m_sketch_banner_txt->SetForegroundColour(*wxWHITE); + { + wxFont f = m_sketch_banner_txt->GetFont(); + f.SetWeight(wxFONTWEIGHT_BOLD); + m_sketch_banner_txt->SetFont(f); + auto* bs = new wxBoxSizer(wxHORIZONTAL); + bs->Add(m_sketch_banner_txt, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(6)); + m_sketch_banner->SetSizer(bs); + } + m_sketch_banner->Hide(); + vcol->Add(m_sketch_banner, 0, wxEXPAND); // The bottom 3D-navigator orb handles all view orientation, so no separate view buttons. // Fit view is a double-click on the viewport (the tool intercepts it -> zoom_to_volumes). vcol->Add(m_viewport, 1, wxEXPAND); @@ -4455,6 +4482,24 @@ void DesignPanel::set_ui_mode(UiMode m) // leaving it takes them back. Without this they would only refresh on the next tree // rebuild, which is not an event that happens when you merely press Sketch. update_reference_planes(); + + // Say where you are, in words, across the top of the viewport — and mute the printer bed + // while you are there. The 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 bed + // checkbox stays 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. + if (m_sketch_banner != nullptr) { + const bool sketching = (m == UiMode::Sketch); + if (sketching && m_sketch_banner_txt != nullptr) + m_sketch_banner_txt->SetLabel( + wxString::Format(_L("Editing: Sketch %d · N = look normal to the plane · " + "Finish or Cancel in the toolbar"), + m_feature_counter + 1)); + m_sketch_banner->Show(sketching); + m_sketch_banner->GetParent()->Layout(); + if (m_viewport != nullptr) + m_viewport->set_show_bed(!sketching && (m_show_bed == nullptr || m_show_bed->GetValue())); + } } void DesignPanel::on_shape_changed() diff --git a/src/slic3r/GUI/CAD/DesignPanel.hpp b/src/slic3r/GUI/CAD/DesignPanel.hpp index fe6d7d30a9..60003ff50c 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.hpp +++ b/src/slic3r/GUI/CAD/DesignPanel.hpp @@ -451,6 +451,13 @@ private: // Top contextual toolbar (parented to the panel, above the form/viewport row). UiMode m_ui_mode{UiMode::Feature}; + // Sketch environment banner: a strip across the top of the viewport saying, in words, that + // this is a sketch and which one. The mode used to be legible only from the toolbar and the + // left card — both of which look like the rest of the app — so a sketch session and plate + // preparation were one glance apart. Indicator only: Finish/Cancel stay on the ONE ribbon + // action bar (the Design UX contract), and the banner never grows a second pair. + wxPanel* m_sketch_banner{nullptr}; + wxStaticText* m_sketch_banner_txt{nullptr}; wxScrolledWindow* m_toolbar{nullptr}; // horizontally scrollable so the action bar stays reachable on narrow windows wxSizer* m_tb_feature{nullptr}; wxSizer* m_tb_sketch{nullptr};