From 3eb6e5d608862c5d3226bcd9f38fde44613f3784 Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Fri, 14 Aug 2026 14:00:30 +0200 Subject: [PATCH] CAD: constraints are reachable from the offer menu, not only from a toolbar icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A user evaluating the Design tab against Onshape reported that "adding constraints seems to be missing" — with nineteen constraint types and a solver shipped behind it (OrcaSlicer PR #15238, exussum12). They were not wrong about what they could see. The only ways in were an icon-only toolbar button whose tooltip you have to hover to read, and an offer row gated on sketch_mode with a sketch ENTITY selected, filed under "Reference". Right after finishing a sketch — the moment you want to constrain it — neither was in front of the user, so a shipped headline feature read as absent. Add a model-mode row: "Constrain sketch", offered under Modify when a sketch region is selected, routed through the new btn:constrain verb action. on_begin_constrain() also gains a fallback to m_sel_sketch_feat. The offer reaches it from a SkLoop selection, which carries no TREE selection, and the function read only tree_selection() — so the new row would have answered "Select a sketch in the tree first" about a sketch the user had visibly selected. It now adopts the region's owning sketch and syncs the tree to match. Verified on the rig: draw a rectangle, finish the sketch, click the region, right-click -> Modify -> "Constrain sketch" enters Constrain mode with "Pick 1-2 lines, then a constraint" and the Constraints (8) card listing the sketch's inferred constraints. That path did not exist before. Does NOT address the other half of the report: there is still no Pierce constraint, so a sweep profile cannot be tied to its path. Tracked separately. Co-Authored-By: Claude Opus 5 (1M context) --- src/slic3r/GUI/DesignOffer.hpp | 4 ++++ src/slic3r/GUI/DesignPanel.cpp | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/slic3r/GUI/DesignOffer.hpp b/src/slic3r/GUI/DesignOffer.hpp index 9213dfef0b..6fb95d48e3 100644 --- a/src/slic3r/GUI/DesignOffer.hpp +++ b/src/slic3r/GUI/DesignOffer.hpp @@ -165,6 +165,10 @@ static const OfferVerb kOfferVerbs[] = { {"sk_scale", "Scale", 5, nullptr, "fly:design_move#2", nullptr, 0x000f0000u, 0, 0, false, true, "Move", "design_scale", "Pick entities, then drag the handle or click the factor; click empty to apply"}, {"sk_dimension", "Dimension", 6, "D", "key:D", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_dimension", "Dimension — click 2 points or an entity"}, {"sk_constrain", "Constrain", 6, "K", "key:K", nullptr, 0x000f0000u, 0, 0, false, true, nullptr, "design_constrain", "Constrain the selected sketch entities to each other"}, + // 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. + {"constrain", "Constrain sketch", 7, nullptr, "btn:constrain", "Select a sketch to constrain it", 0x00004000u, 0, 1, false, false, nullptr, "design_constrain", "Add dimensions and relations (coincident, tangent, parallel...) to the selected sketch"}, {"sk_construct", "Construction", 6, "Q", "key:Q", nullptr, 0x000b8000u, 0, 0, false, true, nullptr, nullptr, "Toggle construction: geometry that guides but is never built"}, {"sk_extend", "Extend", 7, "X", "key:X", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_extend", "Extend — click a line/arc to extend it"}, {"sk_delete", "Delete", 7, "Del", "btn:delete", nullptr, 0x000f0000u, 0, 0, false, true, nullptr, "design_delete", "Delete the selected sketch entities"}, diff --git a/src/slic3r/GUI/DesignPanel.cpp b/src/slic3r/GUI/DesignPanel.cpp index 3ebc63d2a0..434dc3b945 100644 --- a/src/slic3r/GUI/DesignPanel.cpp +++ b/src/slic3r/GUI/DesignPanel.cpp @@ -994,6 +994,16 @@ DesignPanel::DesignPanel(wxWindow* parent) m_verb_actions["btn:delete_body"] = [this] { on_delete_body(); }; m_verb_actions["btn:edit"] = [this] { on_edit_feature(); }; m_verb_actions["btn:mass"] = [this] { on_mass_properties(); }; + // Reachable from the offer menu on a SELECTED SKETCH, not only from the toolbar icon. + // A user evaluating against Onshape reported that "adding constraints seems to be + // missing" — with nineteen constraint types and a solver shipped. The only paths in + // were an icon-only button and a sketch-mode-only offer row filed under "Reference", + // so after finishing a sketch there was no affordance where users actually look. + m_verb_actions["btn:constrain"] = [this] { + on_begin_constrain(); + if (m_viewport && (m_viewport->is_constraining() || m_viewport->is_constraining_entities())) + set_ui_mode(UiMode::Constrain); + }; // Dress-up: finishing operations on the faces and edges of an existing solid — nothing // that moves a body (see the Placement drawer) and nothing that creates geometry. @@ -6872,6 +6882,15 @@ bool DesignPanel::enter_constrain_inline() void DesignPanel::on_begin_constrain(int sel_override) { int sel = (sel_override >= 0) ? sel_override : tree_selection(); + // Fall back to the sketch owning the region picked in the viewport. The offer menu reaches + // this verb from a SkLoop selection (a region clicked on screen), which carries no tree + // selection — without this, choosing "Constrain sketch" from the offer would answer + // "Select a sketch in the tree first" about a sketch the user has visibly selected. + if ((sel == wxNOT_FOUND || sel >= int(m_doc.features.size())) && m_sel_sketch_feat >= 0 + && m_sel_sketch_feat < int(m_doc.features.size())) { + sel = m_sel_sketch_feat; + set_tree_selection(sel); // keep the tree in step with what the viewport says + } if (sel == wxNOT_FOUND || sel >= int(m_doc.features.size())) { m_status->SetForegroundColour(wxColour(235, 110, 110)); set_status(_L("Select a sketch in the tree first"));