CAD: constraints are reachable from the offer menu, not only from a toolbar icon

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) <noreply@anthropic.com>
This commit is contained in:
Tommaso Bianchi
2026-08-14 14:00:30 +02:00
co-authored by Claude Opus 5
parent 9013f530fa
commit 3eb6e5d608
2 changed files with 23 additions and 0 deletions
+4
View File
@@ -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_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_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"}, {"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_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_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"}, {"sk_delete", "Delete", 7, "Del", "btn:delete", nullptr, 0x000f0000u, 0, 0, false, true, nullptr, "design_delete", "Delete the selected sketch entities"},
+19
View File
@@ -994,6 +994,16 @@ DesignPanel::DesignPanel(wxWindow* parent)
m_verb_actions["btn:delete_body"] = [this] { on_delete_body(); }; m_verb_actions["btn:delete_body"] = [this] { on_delete_body(); };
m_verb_actions["btn:edit"] = [this] { on_edit_feature(); }; m_verb_actions["btn:edit"] = [this] { on_edit_feature(); };
m_verb_actions["btn:mass"] = [this] { on_mass_properties(); }; 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 // 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. // 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) void DesignPanel::on_begin_constrain(int sel_override)
{ {
int sel = (sel_override >= 0) ? sel_override : tree_selection(); 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())) { if (sel == wxNOT_FOUND || sel >= int(m_doc.features.size())) {
m_status->SetForegroundColour(wxColour(235, 110, 110)); m_status->SetForegroundColour(wxColour(235, 110, 110));
set_status(_L("Select a sketch in the tree first")); set_status(_L("Select a sketch in the tree first"));