Design: a tool's options come from the tool, not from a card on the left

Mirror of snaporca 6d5510734b (DesignPanel.cpp applied as a patch; parity re-checked at
30 lines, shared files byte-identical).

Polygon's Sides/Circumscribed card is deleted — the choice is made in Create > Polygon,
which names the counts and the two fits, because the side count cannot be recovered after
drawing. Dress-up, Combine and Pattern were single verbs hiding several behind a combo
and now name each one in the offer. Fixes fillet and chamfer both carrying key:S+F, which
made the offer's Chamfer open a Fillet.

Built green and verified on the rig: the Dress-up card opened from Chamfer reads Chamfer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
This commit is contained in:
Tommaso Bianchi
2026-08-01 08:48:05 +02:00
co-authored by Claude Opus 5
parent d1d61ce997
commit 3c998b8a62
4 changed files with 290 additions and 60 deletions
+213 -8
View File
@@ -329,7 +329,7 @@
},
{
"id": "boolean",
"name": "Combine",
"name": "Union",
"slot": "add",
"key": "Shift+B",
"feature": "Boolean",
@@ -342,7 +342,43 @@
},
"refusal": "Boolean needs two bodies — create or import a second solid",
"gui": true,
"action": "key:S+B"
"action": "btn:bool#0"
},
{
"id": "bool_subtract",
"name": "Subtract",
"slot": "add",
"key": null,
"feature": "Boolean",
"mcp": null,
"accepts": [
"bodies_2"
],
"needs": {
"bodies": 2
},
"refusal": "Boolean needs two bodies — create or import a second solid",
"gui": true,
"action": "btn:bool#1",
"mode": null
},
{
"id": "bool_intersect",
"name": "Intersect",
"slot": "add",
"key": null,
"feature": "Boolean",
"mcp": null,
"accepts": [
"bodies_2"
],
"needs": {
"bodies": 2
},
"refusal": "Boolean needs two bodies — create or import a second solid",
"gui": true,
"action": "btn:bool#2",
"mode": null
},
{
"id": "surf_extrude",
@@ -530,7 +566,7 @@
},
"refusal": "Pick an edge to round",
"gui": true,
"action": "key:S+F"
"action": "btn:dress#0"
},
{
"id": "chamfer",
@@ -550,7 +586,7 @@
},
"refusal": "Pick an edge to bevel",
"gui": true,
"action": "key:S+F"
"action": "btn:dress#1"
},
{
"id": "draft",
@@ -589,7 +625,7 @@
},
{
"id": "pattern",
"name": "Pattern",
"name": "Linear pattern",
"slot": "repeat",
"key": "Shift+N",
"feature": "Pattern",
@@ -605,7 +641,28 @@
},
"refusal": "Create a solid body to pattern first",
"gui": true,
"action": "key:S+N"
"action": "btn:pat#0"
},
{
"id": "pattern_circular",
"name": "Circular pattern",
"slot": "repeat",
"key": null,
"feature": "Pattern",
"mcp": null,
"accepts": [
"body_solid",
"face_planar",
"sk_loop",
"art"
],
"needs": {
"bodies": 1
},
"refusal": "Create a solid body to pattern first",
"gui": true,
"action": "btn:pat#1",
"mode": null
},
{
"id": "mirror",
@@ -1292,9 +1349,72 @@
"gui": true,
"action": "key:B"
},
{
"id": "sk_poly_3",
"name": "Triangle",
"slot": "create",
"key": null,
"feature": "Sketch",
"mcp": null,
"accepts": [
"sk_none",
"sk_point",
"sk_line",
"sk_arc",
"sk_2ent"
],
"needs": {},
"refusal": null,
"gui": true,
"action": "btn:poly#3",
"mode": "sketch",
"family": "Polygon"
},
{
"id": "sk_poly_4",
"name": "Square",
"slot": "create",
"key": null,
"feature": "Sketch",
"mcp": null,
"accepts": [
"sk_none",
"sk_point",
"sk_line",
"sk_arc",
"sk_2ent"
],
"needs": {},
"refusal": null,
"gui": true,
"action": "btn:poly#4",
"mode": "sketch",
"family": "Polygon"
},
{
"id": "sk_poly_5",
"name": "Pentagon",
"slot": "create",
"key": null,
"feature": "Sketch",
"mcp": null,
"accepts": [
"sk_none",
"sk_point",
"sk_line",
"sk_arc",
"sk_2ent"
],
"needs": {},
"refusal": null,
"gui": true,
"action": "btn:poly#5",
"mode": "sketch",
"family": "Polygon"
},
{
"id": "sk_polygon",
"name": "Polygon",
"name": "Hexagon",
"slot": "create",
"key": "G",
"feature": "Sketch",
@@ -1310,7 +1430,92 @@
"needs": {},
"refusal": null,
"gui": true,
"action": "key:G"
"action": "btn:poly#6",
"family": "Polygon"
},
{
"id": "sk_poly_8",
"name": "Octagon",
"slot": "create",
"key": null,
"feature": "Sketch",
"mcp": null,
"accepts": [
"sk_none",
"sk_point",
"sk_line",
"sk_arc",
"sk_2ent"
],
"needs": {},
"refusal": null,
"gui": true,
"action": "btn:poly#8",
"mode": "sketch",
"family": "Polygon"
},
{
"id": "sk_poly_12",
"name": "Dodecagon",
"slot": "create",
"key": null,
"feature": "Sketch",
"mcp": null,
"accepts": [
"sk_none",
"sk_point",
"sk_line",
"sk_arc",
"sk_2ent"
],
"needs": {},
"refusal": null,
"gui": true,
"action": "btn:poly#12",
"mode": "sketch",
"family": "Polygon"
},
{
"id": "sk_poly_inscribed",
"name": "Inscribed",
"slot": "create",
"key": null,
"feature": "Sketch",
"mcp": null,
"accepts": [
"sk_none",
"sk_point",
"sk_line",
"sk_arc",
"sk_2ent"
],
"needs": {},
"refusal": null,
"gui": true,
"action": "btn:polyfit#0",
"mode": "sketch",
"family": "Polygon"
},
{
"id": "sk_poly_circumscribed",
"name": "Circumscribed",
"slot": "create",
"key": null,
"feature": "Sketch",
"mcp": null,
"accepts": [
"sk_none",
"sk_point",
"sk_line",
"sk_arc",
"sk_2ent"
],
"needs": {},
"refusal": null,
"gui": true,
"action": "btn:polyfit#1",
"mode": "sketch",
"family": "Polygon"
},
{
"id": "sk_point_t",
+16 -6
View File
@@ -86,7 +86,9 @@ static const OfferVerb kOfferVerbs[] = {
{"loft", "Loft", 1, "Shift+L", "key:S+L", "Create at least two profile sketches to loft", 0x00004000u, 0, 2, false, false, nullptr},
{"thicken", "Thicken", 1, nullptr, "fly:material#4", "Thicken needs a solid body — add or import one first", 0x0000000au, 1, 0, false, false, nullptr},
{"rib", "Rib", 1, nullptr, "fly:material#5", "Rib needs a solid body — add or import one first", 0x00010000u, 1, 0, false, false, nullptr},
{"boolean", "Combine", 1, "Shift+B", "key:S+B", "Boolean needs two bodies — create or import a second solid", 0x00000200u, 2, 0, false, false, nullptr},
{"boolean", "Union", 1, "Shift+B", "btn:bool#0", "Boolean needs two bodies — create or import a second solid", 0x00000200u, 2, 0, false, false, nullptr},
{"bool_subtract", "Subtract", 1, nullptr, "btn:bool#1", "Boolean needs two bodies — create or import a second solid", 0x00000200u, 2, 0, false, false, nullptr},
{"bool_intersect", "Intersect", 1, nullptr, "btn:bool#2", "Boolean needs two bodies — create or import a second solid", 0x00000200u, 2, 0, false, false, nullptr},
{"surf_extrude", "Surface Extrude", 1, "Shift+G", "key:S+G", "Create a sketch first", 0x00004000u, 0, 0, false, false, nullptr},
{"surf_revolve", "Surface Revolve", 1, nullptr, "fly:surface#1", "Create a sketch profile to revolve first", 0x00004000u, 0, 0, false, false, nullptr},
{"surf_loft", "Surface Loft", 1, nullptr, "fly:surface#2", "Create at least two profile sketches to loft", 0x00004000u, 0, 2, false, false, nullptr},
@@ -97,11 +99,12 @@ static const OfferVerb kOfferVerbs[] = {
{"shell", "Shell", 2, "Shift+K", "key:S+K", "Shell needs a solid body", 0x00000082u, 1, 0, false, false, nullptr},
{"cut", "Cut", 2, "Shift+X", "key:S+X", "Create a solid body to cut first", 0x00000480u, 1, 0, false, false, nullptr},
{"split", "Split", 2, nullptr, nullptr, "Split needs a solid body", 0x00000080u, 1, 0, false, false, nullptr},
{"fillet", "Fillet", 3, "Shift+F", "key:S+F", "Pick an edge to round", 0x000000b2u, 1, 0, false, false, nullptr},
{"chamfer", "Chamfer", 3, nullptr, "key:S+F", "Pick an edge to bevel", 0x000000b2u, 1, 0, false, false, nullptr},
{"fillet", "Fillet", 3, "Shift+F", "btn:dress#0", "Pick an edge to round", 0x000000b2u, 1, 0, false, false, nullptr},
{"chamfer", "Chamfer", 3, nullptr, "btn:dress#1", "Pick an edge to bevel", 0x000000b2u, 1, 0, false, false, nullptr},
{"draft", "Draft", 3, "Shift+D", "key:S+D", "Pick a face to taper", 0x0000000au, 1, 0, false, false, nullptr},
{"surf_offset", "Surface Offset", 3, nullptr, "fly:surface#4", "target is not a sheet body", 0x00000100u, 0, 0, true, false, nullptr},
{"pattern", "Pattern", 4, "Shift+N", "key:S+N", "Create a solid body to pattern first", 0x00006082u, 1, 0, false, false, nullptr},
{"pattern", "Linear pattern", 4, "Shift+N", "btn:pat#0", "Create a solid body to pattern first", 0x00006082u, 1, 0, false, false, nullptr},
{"pattern_circular", "Circular pattern", 4, nullptr, "btn:pat#1", "Create a solid body to pattern first", 0x00006082u, 1, 0, false, false, nullptr},
{"mirror", "Mirror", 4, "Shift+Z", "key:S+Z", "Mirror needs a body — add or import one first", 0x00000480u, 1, 0, false, false, nullptr},
{"pat_curve", "Pattern on Curve", 4, nullptr, nullptr, "Pattern on curve needs a body and a curve", 0x00000090u, 1, 0, false, false, nullptr},
{"transform", "Move", 5, "Shift+Y", "key:S+Y", "Transform needs a body — add or import one first", 0x00002180u, 1, 0, false, false, nullptr},
@@ -136,7 +139,14 @@ static const OfferVerb kOfferVerbs[] = {
{"sk_ellipse", "Ellipse", 0, "E", "key:E", nullptr, 0x000f8000u, 0, 0, false, true, "Ellipse"},
{"sk_ellipse_arc", "Elliptical arc", 0, nullptr, "fly:design_ellipse#1", nullptr, 0x000f8000u, 0, 0, false, true, "Ellipse"},
{"sk_spline", "Spline", 0, "B", "key:B", nullptr, 0x000f8000u, 0, 0, false, true, nullptr},
{"sk_polygon", "Polygon", 0, "G", "key:G", nullptr, 0x000f8000u, 0, 0, false, true, nullptr},
{"sk_poly_3", "Triangle", 0, nullptr, "btn:poly#3", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon"},
{"sk_poly_4", "Square", 0, nullptr, "btn:poly#4", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon"},
{"sk_poly_5", "Pentagon", 0, nullptr, "btn:poly#5", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon"},
{"sk_polygon", "Hexagon", 0, "G", "btn:poly#6", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon"},
{"sk_poly_8", "Octagon", 0, nullptr, "btn:poly#8", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon"},
{"sk_poly_12", "Dodecagon", 0, nullptr, "btn:poly#12", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon"},
{"sk_poly_inscribed", "Inscribed", 0, nullptr, "btn:polyfit#0", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon"},
{"sk_poly_circumscribed", "Circumscribed", 0, nullptr, "btn:polyfit#1", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon"},
{"sk_point_t", "Point", 0, "P", "key:P", nullptr, 0x000f8000u, 0, 0, false, true, nullptr},
{"sk_offset", "Offset", 1, "O", "key:O", nullptr, 0x000b0000u, 0, 0, false, true, nullptr},
{"sk_trim", "Trim", 2, "T", "key:T", nullptr, 0x000b0000u, 0, 0, false, true, nullptr},
@@ -154,7 +164,7 @@ static const OfferVerb kOfferVerbs[] = {
{"sk_extend", "Extend", 7, "X", "key:X", nullptr, 0x000b0000u, 0, 0, false, true, nullptr},
{"sk_delete", "Delete", 7, "Del", "btn:delete", nullptr, 0x000f0000u, 0, 0, false, true, nullptr},
};
static const int kOfferVerbCount = 74;
static const int kOfferVerbCount = 84;
}} // namespace Slic3r::GUI
+55 -41
View File
@@ -358,7 +358,6 @@ DesignPanel::DesignPanel(wxWindow* parent)
m_viewport->set_sketch_tool(mode);
}
m_viewport->set_sketch_construction(m_construction->GetValue());
show_polygon_card(mode == DesignSketchTool::Mode::Polygon);
m_status->SetForegroundColour(wxNullColour);
m_status->SetLabel(m_sketch_on.IsEmpty() ? hint
: wxString::Format(_L("%s · on %s"), hint, m_sketch_on));
@@ -1196,12 +1195,53 @@ DesignPanel::DesignPanel(wxWindow* parent)
{"design_array", DesignSketchTool::Mode::Array, _L("Linear array"), _L("Pick entities, drag the spacing handle, click the count; click empty to apply")},
{"design_polararray", DesignSketchTool::Mode::PolarArray, _L("Polar array (about centroid)"), _L("Pick entities, drag the sweep handle, click the count; click empty to apply")} });
// Polygon's side count / circumscribed flag used to sit inline in this row; they are
// tool parameters, so they live in the sidebar card that opens with the tool.
auto* b_poly = icon_btn("design_polygon", _L("Polygon"));
b_poly->Bind(wxEVT_BUTTON, [this, select_tool](wxCommandEvent&) {
// Polygon's side count and fit are TOOL PARAMETERS, so they are chosen from the tool:
// the offer's Create > Polygon submenu. They used to sit inline in this row, then in a
// sidebar card; both put the choice somewhere you had to leave the geometry to reach,
// and the count cannot be recovered afterwards (a drawn polygon's inline editor offers
// Side and Angle, never the count). snaporca-e1p.
auto arm_polygon = [this, select_tool] {
push_polygon_params();
select_tool(DesignSketchTool::Mode::Polygon, _L("Click center then a vertex")); });
select_tool(DesignSketchTool::Mode::Polygon,
wxString::Format(_L("Click center then a vertex — %d sides, %s"),
m_poly_sides,
m_poly_circumscribed ? _L("circumscribed") : _L("inscribed")));
};
for (int n : {3, 4, 5, 6, 8, 12})
m_verb_actions["btn:poly#" + std::to_string(n)] =
[this, arm_polygon, n] { m_poly_sides = n; arm_polygon(); };
for (int c : {0, 1})
m_verb_actions["btn:polyfit#" + std::to_string(c)] =
[this, arm_polygon, c] { m_poly_circumscribed = (c == 1); arm_polygon(); };
// Same defect, one level up: a combo whose entries are different VERBS wearing a single
// name. "Dress-up" is Fillet or Chamfer; "Combine" is union, subtract or intersect;
// "Pattern" is linear or circular. The choice decides WHAT YOU ARE DOING, so it belongs
// where you chose the tool — not behind a card you must open to discover it existed.
// Each address opens the tool exactly as its shortcut does, then says which one.
// The members are read at INVOCATION, not capture: the cards are built after this row.
// snaporca-e1p.
auto open_feature = [this](int key) {
auto it = m_keys_feature.find(key);
if (it != m_keys_feature.end() && it->second) it->second();
};
// SHIFT() is a constructor-local helper, so resolve the codes here rather than inside
// the stored lambdas, which outlive it.
const int k_dress = SHIFT('F'), k_bool = SHIFT('B'), k_pat = SHIFT('N');
m_verb_actions["btn:dress#0"] = [this, open_feature, k_dress] {
open_feature(k_dress); if (m_dressup_type) m_dressup_type->SetSelection(0); };
m_verb_actions["btn:dress#1"] = [this, open_feature, k_dress] {
open_feature(k_dress); if (m_dressup_type) m_dressup_type->SetSelection(1); };
for (int op = 0; op < 3; ++op)
m_verb_actions["btn:bool#" + std::to_string(op)] = [this, open_feature, k_bool, op] {
open_feature(k_bool);
if (m_bool_op) { m_bool_op->SetSelection(op); refresh_preview(); } };
for (int t = 0; t < 2; ++t)
m_verb_actions["btn:pat#" + std::to_string(t)] = [this, open_feature, k_pat, t] {
open_feature(k_pat); if (m_pattern_type) m_pattern_type->SetSelection(t); };
auto* b_poly = icon_btn("design_polygon", _L("Polygon"));
b_poly->Bind(wxEVT_BUTTON, [arm_polygon](wxCommandEvent&) { arm_polygon(); });
sadd(b_poly);
add_sep(m_tb_sketch);
m_construction->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
@@ -1494,28 +1534,11 @@ DesignPanel::DesignPanel(wxWindow* parent)
// --- Polygon (sketch tool options) ---
{
auto* pform = two_col_form();
m_sides = new wxSpinCtrl(m_cards, wxID_ANY, "6", wxDefaultPosition, wxSize(90, -1));
m_sides->SetRange(3, 64);
m_sides->SetValue(6);
m_sides->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent&) {
if (m_viewport) m_viewport->set_sketch_polygon_sides(m_sides->GetValue()); });
pform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Sides")), 0, wxALIGN_CENTER_VERTICAL);
pform->Add(m_sides, 0, wxEXPAND);
m_poly_circ = new CheckBox(m_cards);
m_poly_circ->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) {
if (m_viewport) m_viewport->set_sketch_polygon_circumscribed(m_poly_circ->GetValue());
e.Skip(); });
pform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Circumscribed")), 0, wxALIGN_CENTER_VERTICAL);
pform->Add(m_poly_circ, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
m_box_polygon = new wxBoxSizer(wxVERTICAL);
m_box_polygon->Add(card_header(m_cards, "design_polygon", _L("Polygon"), m_hdr_polygon), 0,
wxLEFT | wxRIGHT | wxTOP, 12);
m_box_polygon->Add(new wxStaticLine(m_cards), 0, wxEXPAND | wxALL, 8);
m_box_polygon->Add(pform, 0, wxEXPAND | wxALL, 12);
cards->Add(m_box_polygon, 0, wxEXPAND);
// NO polygon card. Sides and Circumscribed are tool parameters and are chosen from the
// tool — the offer's Create > Polygon submenu names the common side counts and the two
// fits, and arming from there sets both. A spin field on the left could not be reached
// without leaving the geometry, and the count is unrecoverable afterwards: the inline
// editor a drawn polygon opens offers Side and Angle, never the count. snaporca-e1p.
}
// --- Extrude dialog (consumes the selected sketch) ---
@@ -2959,7 +2982,6 @@ DesignPanel::DesignPanel(wxWindow* parent)
root->Show(m_parts_box, false, false); // no bodies on a fresh document
root->Show(m_cards, false, false); // no tool open yet: don't draw an empty frame
cards->Show(m_box_sketch, false, true);
cards->Show(m_box_polygon, false, true);
cards->Show(m_box_move, false, true);
cards->Show(m_box_extrude, false, true);
cards->Show(m_box_revolve, false, true);
@@ -8704,21 +8726,13 @@ void DesignPanel::show_move_card(bool show)
if (m_form) { m_form->Layout(); m_form->FitInside(); }
}
// Polygon's options live in a sidebar card that is shown only while the tool is armed.
void DesignPanel::show_polygon_card(bool show)
{
if (m_box_polygon == nullptr || m_cards == nullptr || m_cards->GetSizer() == nullptr) return;
m_cards->GetSizer()->Show(m_box_polygon, show, true);
update_cards_frame();
if (m_form) { m_form->Layout(); m_form->FitInside(); }
}
// Push the card's current values into the live sketch tool before Polygon starts.
// Push the tool's current parameters into the live sketch tool before Polygon starts. They
// come from the offer's Polygon submenu (or from the last choice made there), never from a card.
void DesignPanel::push_polygon_params()
{
if (m_viewport == nullptr) return;
m_viewport->set_sketch_polygon_sides(m_sides ? m_sides->GetValue() : 6);
m_viewport->set_sketch_polygon_circumscribed(m_poly_circ && m_poly_circ->GetValue());
m_viewport->set_sketch_polygon_sides(m_poly_sides);
m_viewport->set_sketch_polygon_circumscribed(m_poly_circumscribed);
}
void DesignPanel::open_tool(Tool t)
+6 -5
View File
@@ -276,7 +276,6 @@ private:
StaticBox* m_parts_box{nullptr}; // framed bodies section (hidden while empty)
StaticBox* m_cards{nullptr}; // one framed panel holding every tool dialog (one visible at a time)
void update_cards_frame(); // show that frame iff some card inside it is visible
void show_polygon_card(bool show);
void show_move_card(bool show);
void apply_move_card(); // numeric move/rotate -> same xform the gizmo builds
void push_polygon_params();
@@ -284,7 +283,6 @@ private:
wxSizer* m_tb_doc{nullptr}; // toolbar document/view actions (new, commit, export, section, place)
CheckBox* m_show_bed{nullptr}; // view option: draw the printer bed + plate grid, or not
wxSizer* m_box_move{nullptr}; // Move/Rotate numeric options (distance, axis, angle)
wxSizer* m_box_polygon{nullptr}; // Polygon tool options (sides / circumscribed)
wxSizer* m_box_sketch{nullptr};
wxSizer* m_box_extrude{nullptr};
wxSizer* m_box_dressup{nullptr};
@@ -325,7 +323,6 @@ private:
// Onshape-style dialog-card title rows (icon + bold feature name), retitled
// per tool in open_tool() (edit-mode shows the feature's actual name).
wxStaticText* m_hdr_polygon{nullptr};
wxStaticText* m_hdr_move{nullptr};
wxStaticText* m_hdr_sketch{nullptr};
// Onshape sketch-entry card (plane/orientation) that opens on "New sketch" and
@@ -395,8 +392,12 @@ private:
wxSpinCtrlDouble* m_move_dz{nullptr};
ComboBox* m_move_axis{nullptr}; // rotation axis: X/Y/Z
wxSpinCtrlDouble* m_move_angle{nullptr}; // rotation angle (deg)
wxSpinCtrl* m_sides{nullptr}; // polygon sides
CheckBox* m_poly_circ{nullptr}; // polygon circumscribed toggle (Orca teal check)
// Polygon's two parameters are chosen FROM THE TOOL, in the offer's Polygon submenu, not
// from a card on the left: the side count cannot be edited after drawing (the inline editor
// offers Side and Angle only), so it has to be settled at the moment the tool is armed —
// which is exactly where the offer already is. snaporca-e1p.
int m_poly_sides{6}; // 3..64; the submenu names the common ones
bool m_poly_circumscribed{false};
// Which reference plane a sketch falls back to when no face is picked: 0/1/2 = XY/XZ/YZ,
// >=3 indexes resolve_datum_planes(). Set by CLICKING a ghost plane in the viewport — there is