mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-22 00:12:34 +00:00
Design: slot Radius caption, keyboard offer, plane combo removal, mass props, docs
Mirror of snaporca ac85277bac..0e7cb3ec78 (six changes, applied as a patch to DesignPanel.cpp rather than copied, so this fork's 30 permitted divergent lines survive — parity re-checked afterwards: the five shared files are byte-identical, DesignCanvas.cpp and DesignPanel.cpp differ by exactly 16 and 30 lines). - The straight slot's inline field says Radius, which is what it sets. It stores the half-width and passed the typed number through unchanged, so 30 produced a 60 mm slot. - The offer opens from the keyboard (Menu, Shift+F10), anchored on the viewport rather than wherever the pointer happens to be. The card hint names the new route. - The sketch card's Plane combo is gone; the plane comes from the viewport. Also stops build_candidate collapsing a face plane to a base plane while editing. - Mass properties and the dead Edit row are wired into the offer; DesignOffer.hpp is regenerated from tool_atlas.json, verified by re-running the generator and diffing. - docs/rig_build_traps.md + scripts/rig-build.sh, which derives its fork identity from project() so it cannot be pointed at the other fork's image or volume. - docs/design_tab.md refreshed (44 commits stale) + a PR description, with this fork's own merge-base and diff shape rather than snaporca's. Built green in the deps container with the new script and verified on the rig: Menu and Shift+F10 both open the offer at the viewport centre with the pointer parked off-canvas, and the sketch card shows no Plane row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
This commit is contained in:
co-authored by
Claude Opus 5
parent
6e9910303b
commit
fd1bc092d8
@@ -51,7 +51,7 @@ public:
|
||||
const std::vector<SketchEntityConstraintDef>& constraints,
|
||||
const SketchPlane& plane);
|
||||
void set_sketch_tool(DesignSketchTool::Mode mode);
|
||||
void set_sketch_plane(const SketchPlane& plane); // re-plane the live sketch when the Plane dropdown changes
|
||||
void set_sketch_plane(const SketchPlane& plane); // re-plane the live sketch when a reference plane is clicked in 3D
|
||||
void set_sketch_construction(bool c);
|
||||
void set_sketch_polygon_sides(int n);
|
||||
void set_sketch_polygon_circumscribed(bool c);
|
||||
|
||||
@@ -108,9 +108,9 @@ static const OfferVerb kOfferVerbs[] = {
|
||||
{"helix", "Helix", 6, nullptr, "fly:plane#3", nullptr, 0x00000405u, 0, 0, false, false},
|
||||
{"project", "Project", 6, nullptr, "fly:plane#4", "Project needs a body — add or import one first", 0x00000482u, 1, 0, false, false},
|
||||
{"measure", "Measure", 6, nullptr, nullptr, nullptr, 0x000b03feu, 0, 0, false, false},
|
||||
{"mass_props", "Mass", 6, nullptr, nullptr, nullptr, 0x00000080u, 1, 0, false, false},
|
||||
{"mass_props", "Mass", 6, nullptr, "btn:mass", nullptr, 0x00000080u, 1, 0, false, false},
|
||||
{"interference", "Interference", 6, nullptr, nullptr, nullptr, 0x00000200u, 2, 0, false, false},
|
||||
{"edit_feature", "Edit", 7, nullptr, nullptr, nullptr, 0x00007d8eu, 0, 0, false, false},
|
||||
{"edit_feature", "Edit", 7, nullptr, "btn:edit", nullptr, 0x00007d8eu, 0, 0, false, false},
|
||||
{"delete_face", "Delete Face", 7, nullptr, "fly:dressup#3", "Delete Face needs a body — add or import one first", 0x0000000eu, 1, 0, false, false},
|
||||
{"colour", "Colour", 7, nullptr, "btn:colour", nullptr, 0x00000180u, 1, 0, false, false},
|
||||
{"delete", "Delete", 7, "Del", "btn:delete", nullptr, 0x000f7f80u, 0, 0, false, false},
|
||||
|
||||
@@ -375,7 +375,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
sk_key('R', DesignSketchTool::Mode::CornerRect, _L("Rectangle — click two opposite corners"));
|
||||
sk_key('C', DesignSketchTool::Mode::CenterCircle, _L("Circle — click center, then radius"));
|
||||
sk_key('A', DesignSketchTool::Mode::ThreePointArc,_L("Arc — click start, end, then a point"));
|
||||
sk_key('S', DesignSketchTool::Mode::Slot, _L("Slot — two centerline ends, then width"));
|
||||
sk_key('S', DesignSketchTool::Mode::Slot, _L("Slot — two centerline ends, then end radius"));
|
||||
sk_key('E', DesignSketchTool::Mode::Ellipse, _L("Ellipse — center, major end, minor point"));
|
||||
sk_key('B', DesignSketchTool::Mode::BSpline, _L("Spline — click control points"));
|
||||
sk_key('P', DesignSketchTool::Mode::Point, _L("Point — click to place"));
|
||||
@@ -533,7 +533,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_status->Refresh();
|
||||
if (m_sketch_hint) { // the card must agree with the status line, not argue with it
|
||||
m_sketch_hint->SetLabel(have_plane
|
||||
? wxString::Format(_L("Drawing on %s.\nPick a tool, or right-click for the list."), where)
|
||||
? wxString::Format(_L("Drawing on %s.\nPick a tool, or press Menu for the list."), where)
|
||||
: _L("Click a face or a reference plane, then a sketch tool."));
|
||||
m_sketch_hint->Refresh();
|
||||
m_cards->Layout();
|
||||
@@ -542,7 +542,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// mode change has settled before a modal menu takes the loop; the menu carries each
|
||||
// tool's shortcut, so pressing the key instead of picking a row costs nothing.
|
||||
if (have_plane)
|
||||
CallAfter([this] { show_offer_menu(wxGetMousePosition()); });
|
||||
CallAfter([this] { show_offer_menu(offer_anchor()); });
|
||||
};
|
||||
b_sketch->Bind(wxEVT_BUTTON, [act_sketch](wxCommandEvent&) { act_sketch(); });
|
||||
m_keys_feature[SHIFT('S')] = act_sketch;
|
||||
@@ -898,6 +898,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
fadd("color", b_color);
|
||||
m_verb_actions["btn:colour"] = [this] { on_set_body_color(); };
|
||||
m_verb_actions["btn:delete"] = [this] { on_delete_feature(); };
|
||||
m_verb_actions["btn:edit"] = [this] { on_edit_feature(); };
|
||||
m_verb_actions["btn:mass"] = [this] { on_mass_properties(); };
|
||||
|
||||
// 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.
|
||||
@@ -1132,7 +1134,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
{"design_tangentarc", DesignSketchTool::Mode::TangentArc, _L("Tangent arc"), _L("Click start (on the last entity) then end")},
|
||||
{"design_arc_center", DesignSketchTool::Mode::CenterArc, _L("Center-point arc"), _L("Click center, then start, then a point for the end angle")} });
|
||||
dropdown("design_slot", _L("Slot"), {
|
||||
{"design_slot", DesignSketchTool::Mode::Slot, _L("Slot"), _L("Click two centerline ends, then a point for width")},
|
||||
{"design_slot", DesignSketchTool::Mode::Slot, _L("Slot"), _L("Click two centerline ends, then a point for the end radius")},
|
||||
{"design_slot_arc", DesignSketchTool::Mode::ArcSlot, _L("Arc slot"), _L("Click center, start, end, then a point for the width")} });
|
||||
dropdown("design_ellipse", _L("Ellipse"), {
|
||||
{"design_ellipse", DesignSketchTool::Mode::Ellipse, _L("Ellipse"), _L("Click center, a major-axis end, then a point for the minor axis")},
|
||||
@@ -1419,13 +1421,10 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
form->Add(new wxStaticText(m_cards, wxID_ANY, _L("Shape")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
form->Add(m_shape, 0, wxEXPAND);
|
||||
|
||||
m_plane = make_combo(m_cards);
|
||||
m_plane->Append(_L("XY"));
|
||||
m_plane->Append(_L("XZ"));
|
||||
m_plane->Append(_L("YZ"));
|
||||
m_plane->SetSelection(0);
|
||||
form->Add(new wxStaticText(m_cards, wxID_ANY, _L("Plane")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
form->Add(m_plane, 0, wxEXPAND);
|
||||
// NO plane row. A sketch takes its plane from what is picked in the VIEWPORT — a planar face
|
||||
// on a solid, or one of the reference-plane ghosts clicked in 3D — resolved by
|
||||
// sketch_plane_from_selection(). A three-row XY/XZ/YZ combo could not express either of those
|
||||
// targets, so it displayed a value that was at best redundant and at worst false. snaporca-e1p.
|
||||
|
||||
m_width = make_spin(m_cards, 20);
|
||||
form->Add(new wxStaticText(m_cards, wxID_ANY, _L("Width / X")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
@@ -3473,6 +3472,20 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
const bool dismissable = m_active != Tool::None || (m_viewport && m_viewport->moving_body());
|
||||
if (key == WXK_ESCAPE && dismissable) { tool_cancel(); return; }
|
||||
|
||||
// The offer from the keyboard (charter 4.1): the Menu key, or Shift+F10 for keyboards that
|
||||
// do not have one. Same menu the right-click opens — show_offer_menu already decides which
|
||||
// half of the map applies via sketch_map_applies(), so nothing about the content is decided
|
||||
// here. With no press to anchor it, offer_anchor() puts it on the viewport.
|
||||
// WXK_MENU is the GTK code for the physical Menu key (GDK_KEY_Menu); wxMSW instead sends
|
||||
// WXK_WINDOWS_MENU for VK_APPS and maps Alt to WXK_ALT, so accepting both is safe
|
||||
// everywhere. CallAfter because the menu is modal: let the key event finish dispatching
|
||||
// before a nested loop takes the queue, the same reason the Sketch entry does it.
|
||||
if (!in_text && !ctrl
|
||||
&& (key == WXK_MENU || key == WXK_WINDOWS_MENU || (key == WXK_F10 && e.ShiftDown()))) {
|
||||
CallAfter([this] { show_offer_menu(offer_anchor()); });
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y — undo/redo handled here (not only in the GL canvas) so
|
||||
// it works even when the canvas lost keyboard focus. In a sketch, undo drops the last entity.
|
||||
if (!in_text && ctrl && (key == 'Z' || key == 'z' || key == WXK_CONTROL_Z ||
|
||||
@@ -4089,13 +4102,14 @@ void DesignPanel::on_add_sketch()
|
||||
{
|
||||
SketchShape shape = (m_shape->GetSelection() == 1) ? SketchShape::Circle
|
||||
: SketchShape::Rectangle;
|
||||
SketchPlane plane = plane_from_choice(m_plane->GetSelection());
|
||||
wxString where; // named for the status line
|
||||
SketchPlane plane = sketch_plane_from_selection(where); // picked face, else the 3D plane click
|
||||
m_feature_counter++;
|
||||
m_doc.add_sketch(shape, plane, m_width->GetValue(), m_height->GetValue(),
|
||||
m_radius->GetValue(), "Sketch" + std::to_string(m_feature_counter));
|
||||
m_doc.recompute(); // a lone sketch yields an empty body; that is expected
|
||||
m_status->SetForegroundColour(wxNullColour);
|
||||
m_status->SetLabel(_L("Sketch added — select it and Extrude"));
|
||||
m_status->SetLabel(wxString::Format(_L("Sketch added on %s — select it and Extrude"), where));
|
||||
refresh_tree();
|
||||
}
|
||||
|
||||
@@ -4712,6 +4726,31 @@ void DesignPanel::on_check_interference()
|
||||
wxMessageBox(msg, _L("Interference"), wxOK, this);
|
||||
}
|
||||
|
||||
// Mass properties of the selected solid. A report, not a feature: it never checkpoints, never
|
||||
// recomputes and never opens a card, which is why it sits beside the interference check rather
|
||||
// than in the on_add_* family. The caller only reaches us with m_sel_solid_body in range.
|
||||
void DesignPanel::on_mass_properties()
|
||||
{
|
||||
const auto mp = GeometryEngine::mass_properties(m_doc.bodies[m_sel_solid_body].shape);
|
||||
if (!mp.valid) {
|
||||
m_status->SetForegroundColour(wxColour(235, 110, 110));
|
||||
m_status->SetLabel(_L("Mass properties could not be computed for this body"));
|
||||
m_status->Refresh();
|
||||
return;
|
||||
}
|
||||
// 1-based, and the body's own name when it has one — the same wording the parts list uses.
|
||||
wxString name = wxString::Format(_L("Body %d"), m_sel_solid_body + 1);
|
||||
if (!m_doc.bodies[m_sel_solid_body].name.empty())
|
||||
name = wxString::FromUTF8(m_doc.bodies[m_sel_solid_body].name);
|
||||
m_status->SetForegroundColour(wxNullColour);
|
||||
m_status->SetLabel(wxString::Format(_L("%s: %.3f cm³, %.2f cm²"),
|
||||
name, mp.volume / 1000.0, mp.surface_area / 100.0));
|
||||
m_status->Refresh();
|
||||
wxMessageBox(wxString::Format(_L("%s\n\nVolume: %.3f cm³\nSurface area: %.2f cm²"),
|
||||
name, mp.volume / 1000.0, mp.surface_area / 100.0),
|
||||
_L("Mass properties"), wxOK, this);
|
||||
}
|
||||
|
||||
// The rows are only the SHEET bodies, so a row index is NOT a body index — with a solid at 0
|
||||
// and a sheet at 1 the single row is row 0 but body 1. Every caller must therefore read the
|
||||
// real body index out of the client data (3-arg Append; the 2-arg form takes a bitmap), never
|
||||
@@ -5016,6 +5055,17 @@ void DesignPanel::run_offer_action(const char* action)
|
||||
it->second();
|
||||
}
|
||||
|
||||
wxPoint DesignPanel::offer_anchor() const
|
||||
{
|
||||
const wxPoint mouse = wxGetMousePosition();
|
||||
if (!m_viewport)
|
||||
return mouse;
|
||||
const wxRect r = m_viewport->GetScreenRect();
|
||||
if (r.Contains(mouse))
|
||||
return mouse;
|
||||
return wxPoint(r.x + r.width / 2, r.y + r.height / 2);
|
||||
}
|
||||
|
||||
void DesignPanel::show_offer_menu(const wxPoint& screen_pos)
|
||||
{
|
||||
const int kind = offer_selection_kind();
|
||||
@@ -7167,7 +7217,6 @@ void DesignPanel::load_feature_into_dialog(const CadFeature& f)
|
||||
switch (f.type) {
|
||||
case CadFeatureType::Sketch:
|
||||
m_shape->SetSelection(f.shape == SketchShape::Circle ? 1 : 0);
|
||||
m_plane->SetSelection(index_from_plane(f.plane));
|
||||
m_width->SetValue(f.width);
|
||||
m_height->SetValue(f.height);
|
||||
m_radius->SetValue(f.radius);
|
||||
@@ -7770,7 +7819,11 @@ CadFeature DesignPanel::build_candidate(Tool t) const
|
||||
case Tool::Sketch:
|
||||
f.type = CadFeatureType::Sketch;
|
||||
f.shape = (m_shape->GetSelection() == 0) ? SketchShape::Rectangle : SketchShape::Circle;
|
||||
f.plane = plane_from_choice(m_plane->GetSelection());
|
||||
// The plane is STRUCTURAL, like Extrude's profile source. While EDITING it is preserved
|
||||
// from the seeded original — the card carries no plane control and the old combo silently
|
||||
// collapsed a face plane to a base plane through the modeling origin. While ADDING it
|
||||
// comes from what is picked in the viewport. snaporca-e1p.
|
||||
if (!editing) { wxString where; f.plane = sketch_plane_from_selection(where); }
|
||||
f.width = m_width->GetValue();
|
||||
f.height = m_height->GetValue();
|
||||
f.radius = m_radius->GetValue();
|
||||
|
||||
@@ -127,6 +127,7 @@ private:
|
||||
void on_add_helix();
|
||||
void on_add_mate();
|
||||
void on_check_interference();
|
||||
void on_mass_properties(); // read-only report on the selected solid; edits nothing
|
||||
// Fill m_bool_target / m_bool_tool / m_cut_target. as_of_feature < 0 = current bodies (add);
|
||||
// >= 0 = the bodies as they existed just before that feature index (Boolean re-edit, so a
|
||||
// consumed tool body still appears and its saved selection round-trips).
|
||||
@@ -405,7 +406,6 @@ private:
|
||||
// from "nobody has chosen anything yet". This does.
|
||||
bool m_plane_picked{false};
|
||||
ComboBox* m_shape{nullptr};
|
||||
ComboBox* m_plane{nullptr};
|
||||
ComboBox* m_mode{nullptr};
|
||||
wxSpinCtrlDouble* m_width{nullptr};
|
||||
wxSpinCtrlDouble* m_height{nullptr};
|
||||
@@ -617,6 +617,11 @@ private:
|
||||
// the verbs that have no keyboard shortcut to route through.
|
||||
std::map<std::string, std::function<void()>> m_verb_actions;
|
||||
void show_offer_menu(const wxPoint& screen_pos);
|
||||
// Where the offer opens when no mouse press anchors it: the keyboard route, and the automatic
|
||||
// open on entering Sketch. The pointer if it is over the viewport, else the viewport's centre.
|
||||
// A raw wxGetMousePosition() can be sitting on the toolbar, on the card column or on another
|
||||
// monitor, and the menu would map there — detached from the geometry it is about.
|
||||
wxPoint offer_anchor() const;
|
||||
int offer_selection_kind() const; // an OfferSel, as int to keep the header light
|
||||
// Does the SKETCH half of the map apply? A mode question, not a session one: begin_sketch
|
||||
// does not run until the first tool is armed, so between "press Sketch" and "pick a tool"
|
||||
|
||||
@@ -1256,7 +1256,7 @@ void DesignSketchTool::open_primary_autoedit()
|
||||
const double Lc = d.norm();
|
||||
double deg = std::atan2(d.y(), d.x()) * 180.0 / M_PI; if (deg < 0.0) deg += 360.0;
|
||||
m_autoedit_dims.push_back({ m_live_slot_len_label, Lc, [this, fi](double v){ const Feature& g = m_features[fi]; set_slot(fi, v, g.param); }, span(fi), "Length" });
|
||||
m_autoedit_dims.push_back({ m_live_slot_w_label, f.param, [this, fi](double v){ const Feature& g = m_features[fi]; set_slot(fi, (g.c1-g.c0).norm(), std::max(1e-3, v)); }, span(fi), "Width" });
|
||||
m_autoedit_dims.push_back({ m_live_slot_w_label, f.param, [this, fi](double v){ const Feature& g = m_features[fi]; set_slot(fi, (g.c1-g.c0).norm(), std::max(1e-3, v)); }, span(fi), "Radius" });
|
||||
m_autoedit_dims.push_back({ m_live_slot_angle_label, deg, [this, fi](double v){ set_slot_angle(fi, v); }, span(fi), "Angle" });
|
||||
}
|
||||
if (m_live_arc_ei >= 0) { // arc Radius is already a scalar step above; add its sweep angle
|
||||
@@ -1838,7 +1838,7 @@ void DesignSketchTool::open_slot_editor(int fi, int which)
|
||||
double deg = std::atan2(d.y(), d.x()) * 180.0 / M_PI; if (deg < 0.0) deg += 360.0;
|
||||
const double v = (which == 0) ? d.norm() : (which == 1) ? f.param : deg;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, v, which == 0 ? "Length" : which == 1 ? "Width" : "Angle",
|
||||
on_inline_edit(px, v, which == 0 ? "Length" : which == 1 ? "Radius" : "Angle",
|
||||
[this, fi, which](double nv) {
|
||||
const Feature& g = m_features[fi];
|
||||
if (which == 0) set_slot(fi, nv, g.param);
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
const std::vector<SketchEntityConstraintDef>& constraints,
|
||||
const SketchPlane& plane);
|
||||
void set_tool(Mode mode); // switch tool, keep accumulated entities
|
||||
void set_plane(const SketchPlane& plane) { m_plane = plane; } // re-plane a live sketch (Plane dropdown changed mid-session); entities are 2D, re-lifted through the new plane
|
||||
void set_plane(const SketchPlane& plane) { m_plane = plane; } // re-plane a live sketch (a reference plane was clicked mid-session); entities are 2D, re-lifted through the new plane
|
||||
void set_construction(bool c) { m_construction = c; }
|
||||
void set_polygon_sides(int n) { m_polygon_sides = (n < 3 ? 3 : n); }
|
||||
void set_polygon_circumscribed(bool c) { m_polygon_circumscribed = c; }
|
||||
|
||||
Reference in New Issue
Block a user