mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
Design tab: value-label titles + 5 sketch/UX fixes
Six fixes to the Design tab, all live-verified on :10: - Datum-plane re-pick: wxEVT_CHOICE on m_draw_plane re-planes the live sketch - Delete-feature dismisses its lingering settings card (on_delete_feature) - Revert rotation-orbit regression (no feed_bodies/reload on close_tool) - Sketch undo/delete via focus-independent CHAR_HOOK (Ctrl+Z/Y, Delete) - Fix undo hang: reset_autoedit() clears dangling auto-edit sequence - Titled value fields: each inline dim field shows its role (Width, Height, Radius, Angle, Length, Side, Distance, Major, Minor) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5a005d4728
commit
de16d566b2
@@ -74,6 +74,7 @@ DesignCanvas::DesignCanvas(wxWindow* parent)
|
||||
// px and wrap the callbacks so each one re-solves and re-renders the viewport.
|
||||
m_inline_editor = std::make_unique<SketchInlineEditor>(m_canvas_widget);
|
||||
m_sketch_tool.on_inline_edit = [this](wxPoint screen_px, double current,
|
||||
const std::string& title,
|
||||
std::function<void(double)> commit,
|
||||
std::function<void()> cancel) {
|
||||
if (!m_inline_editor) { if (cancel) cancel(); return; }
|
||||
@@ -85,7 +86,7 @@ DesignCanvas::DesignCanvas(wxWindow* parent)
|
||||
// Freeze the sketch tool while the field is open so a stray click/move on the GL
|
||||
// canvas can't draw under the floating editor; released on commit or cancel.
|
||||
m_sketch_tool.set_inline_busy(true);
|
||||
m_inline_editor->open(scr, current,
|
||||
m_inline_editor->open(scr, current, title,
|
||||
[this, commit](double v) {
|
||||
m_sketch_tool.set_inline_busy(false);
|
||||
if (commit) commit(v);
|
||||
@@ -336,6 +337,13 @@ void DesignCanvas::begin_sketch(const SketchPlane& plane, DesignSketchTool::Mode
|
||||
if (m_canvas_widget) m_canvas_widget->Refresh();
|
||||
}
|
||||
|
||||
void DesignCanvas::set_sketch_plane(const SketchPlane& plane)
|
||||
{
|
||||
m_sketch_tool.set_plane(plane); // keeps the 2D entities; only the carrier plane changes
|
||||
if (m_canvas) m_canvas->set_as_dirty();
|
||||
if (m_canvas_widget) m_canvas_widget->Refresh();
|
||||
}
|
||||
|
||||
void DesignCanvas::edit_sketch(const std::vector<SketchEntity>& entities,
|
||||
const std::vector<SketchEntityConstraintDef>& constraints,
|
||||
const SketchPlane& plane)
|
||||
@@ -833,6 +841,25 @@ void DesignCanvas::delete_selected_sketch_entities()
|
||||
request_repaint();
|
||||
}
|
||||
|
||||
bool DesignCanvas::inline_busy() const
|
||||
{
|
||||
return m_sketch_tool.inline_busy();
|
||||
}
|
||||
|
||||
bool DesignCanvas::undo_last_sketch_entity()
|
||||
{
|
||||
const bool did = m_sketch_tool.undo_last_entity();
|
||||
if (did) request_repaint();
|
||||
return did;
|
||||
}
|
||||
|
||||
bool DesignCanvas::delete_selected_or_last_sketch_entity()
|
||||
{
|
||||
const bool did = m_sketch_tool.delete_selected_or_last();
|
||||
if (did) request_repaint();
|
||||
return did;
|
||||
}
|
||||
|
||||
void DesignCanvas::clear_sketch_selection()
|
||||
{
|
||||
m_sketch_tool.clear_selection();
|
||||
@@ -879,7 +906,7 @@ void DesignCanvas::open_inline_value(double current, std::function<void(double)>
|
||||
// Freeze the canvas so focus-follows-mouse can't steal keyboard focus off the field — the
|
||||
// same fix the draw-then-edit path uses (cursor focus stays on the field, no pre-click).
|
||||
m_sketch_tool.set_inline_busy(true);
|
||||
m_inline_editor->open(scr, current,
|
||||
m_inline_editor->open(scr, current, "",
|
||||
[this, commit](double v) {
|
||||
m_sketch_tool.set_inline_busy(false);
|
||||
if (commit) commit(v);
|
||||
|
||||
@@ -51,6 +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_construction(bool c);
|
||||
void set_sketch_polygon_sides(int n);
|
||||
void set_sketch_polygon_circumscribed(bool c);
|
||||
@@ -178,6 +179,9 @@ public:
|
||||
void set_body_hidden(bool on); // preview-only: hide base bodies, show only the result ghost
|
||||
void set_on_move_exit(std::function<void()> cb); // right-click finished the move-body gizmo
|
||||
void delete_selected_sketch_entities();
|
||||
bool inline_busy() const; // a sketch value field is open (guard keys)
|
||||
bool undo_last_sketch_entity(); // Ctrl+Z in a sketch: drop the last entity
|
||||
bool delete_selected_or_last_sketch_entity(); // Delete in a sketch: selected, else last
|
||||
void clear_sketch_selection();
|
||||
|
||||
// Dimension tool: act on the current sketch selection.
|
||||
|
||||
@@ -1389,6 +1389,14 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_draw_plane = new wxChoice(m_form, wxID_ANY);
|
||||
m_draw_plane->Append(_L("XY")); m_draw_plane->Append(_L("XZ")); m_draw_plane->Append(_L("YZ"));
|
||||
m_draw_plane->SetSelection(0);
|
||||
// Live re-plane: begin_sketch captures the plane only at first-tool-pick, so changing the
|
||||
// dropdown afterwards used to be inert (sketch stayed on its original plane while the
|
||||
// committed feature would silently land on the new one). Honour the change immediately —
|
||||
// the 2D entities are re-lifted through the chosen plane, matching what Finish commits.
|
||||
m_draw_plane->Bind(wxEVT_CHOICE, [this](wxCommandEvent&) {
|
||||
if (m_viewport && m_viewport->is_sketching())
|
||||
m_viewport->set_sketch_plane(plane_from_choice(m_draw_plane->GetSelection()));
|
||||
});
|
||||
prow->Add(m_draw_plane, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_box_sketch_session->Add(prow, 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
auto* hint = new wxStaticText(m_form, wxID_ANY,
|
||||
@@ -1952,8 +1960,33 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// Sketch/Constrain keep the viewport's per-gesture Esc (abort the current point first),
|
||||
// so we only intercept Esc here when a feature/insert card is the thing to dismiss.
|
||||
Bind(wxEVT_CHAR_HOOK, [this](wxKeyEvent& e) {
|
||||
const int key = e.GetKeyCode();
|
||||
const bool ctrl = e.ControlDown() || e.CmdDown();
|
||||
const bool sketching = (m_ui_mode == UiMode::Sketch) && m_viewport && m_viewport->is_sketching();
|
||||
// Never steal editing keys from a focused text field or an open in-canvas value field —
|
||||
// Delete/Ctrl+Z there must edit the text, not the model.
|
||||
const bool in_text = (dynamic_cast<wxTextCtrl*>(wxWindow::FindFocus()) != nullptr)
|
||||
|| (m_viewport && m_viewport->inline_busy());
|
||||
|
||||
const bool dismissable = m_active != Tool::None || (m_viewport && m_viewport->moving_body());
|
||||
if (e.GetKeyCode() == WXK_ESCAPE && dismissable) { tool_cancel(); return; }
|
||||
if (key == WXK_ESCAPE && dismissable) { tool_cancel(); 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 ||
|
||||
key == 'Y' || key == 'y' || key == WXK_CONTROL_Y)) {
|
||||
const bool redo = (key == 'Y' || key == 'y' || key == WXK_CONTROL_Y) || e.ShiftDown();
|
||||
if (sketching) { if (!redo) m_viewport->undo_last_sketch_entity(); }
|
||||
else { do_undo_redo(redo); }
|
||||
return;
|
||||
}
|
||||
// Delete — the selected sketch entities (or the last drawn one if none is selected), or the
|
||||
// selected feature in Feature mode. Focus-independent, same reason as undo above.
|
||||
if (!in_text && key == WXK_DELETE) {
|
||||
if (sketching) { m_viewport->delete_selected_or_last_sketch_entity(); return; }
|
||||
if (m_ui_mode == UiMode::Feature && m_active == Tool::None
|
||||
&& tree_selection() != wxNOT_FOUND) { on_delete_feature(); return; }
|
||||
}
|
||||
e.Skip();
|
||||
});
|
||||
|
||||
@@ -3205,6 +3238,13 @@ void DesignPanel::on_delete_feature()
|
||||
m_status->Refresh();
|
||||
return;
|
||||
}
|
||||
// If a feature dialog is open (e.g. the feature is being edited), dismiss it first —
|
||||
// otherwise the deleted feature's settings card lingers in the left panel, out of sync
|
||||
// with the tree. reset_edit_state() drops the stale m_edit_index; close_tool() hides the card.
|
||||
if (m_active != Tool::None || m_edit_index >= 0) {
|
||||
reset_edit_state();
|
||||
close_tool();
|
||||
}
|
||||
m_doc.checkpoint(); // undo boundary: deleting a feature
|
||||
after_tree_edit(m_doc.remove_feature(sel));
|
||||
}
|
||||
|
||||
@@ -989,7 +989,7 @@ void DesignSketchTool::open_value_editor(int di)
|
||||
const std::array<int, 4>& vp = cam.get_viewport();
|
||||
if (lp.x >= vp[0] && lp.y >= vp[1] && lp.x <= vp[0] + vp[2] && lp.y <= vp[1] + vp[3])
|
||||
px = lp;
|
||||
on_inline_edit(px, a.value,
|
||||
on_inline_edit(px, a.value, dimtype_title(a.kind),
|
||||
[this](double v) { set_dimension_value(v); },
|
||||
[this]() { cancel_dimension_value(); });
|
||||
}
|
||||
@@ -1006,7 +1006,7 @@ void DesignSketchTool::open_angle_editor(int ei)
|
||||
if (!on_inline_edit) return;
|
||||
DimAnnot a; a.kind = DimType::Angle; a.ea = ei;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, measure_dim(a),
|
||||
on_inline_edit(px, measure_dim(a), "Angle",
|
||||
[this, ei](double deg) { set_line_angle(ei, deg); },
|
||||
[]() {});
|
||||
}
|
||||
@@ -1051,7 +1051,7 @@ void DesignSketchTool::open_next_autoedit_dim()
|
||||
wxPoint px(m_last_mouse_x, m_last_mouse_y + m_autoedit_dim_idx * 34);
|
||||
if (lp.x >= vp[0] && lp.y >= vp[1] && lp.x <= vp[0] + vp[2] && lp.y <= vp[1] + vp[3])
|
||||
px = lp;
|
||||
on_inline_edit(px, step.value,
|
||||
on_inline_edit(px, step.value, step.title,
|
||||
[this, step](double v) { // commit: apply this dimension, then next
|
||||
if (step.apply) step.apply(v);
|
||||
++m_autoedit_dim_idx;
|
||||
@@ -1083,18 +1083,30 @@ void DesignSketchTool::arm_polyline_segment_edit()
|
||||
Vec2d dd = m_points[k] - a; const double n = dd.norm();
|
||||
if (n > 1e-9 && len > 1e-9) m_points[k] = a + (len / n) * dd;
|
||||
}
|
||||
} });
|
||||
}, {}, "Length" });
|
||||
m_autoedit_dims.push_back({ mid, deg, [this, k, a](double dg) { // Angle
|
||||
if (k < int(m_points.size())) {
|
||||
const double len = (m_points[k] - a).norm();
|
||||
const double r = dg * M_PI / 180.0;
|
||||
m_points[k] = a + len * Vec2d(std::cos(r), std::sin(r));
|
||||
}
|
||||
} });
|
||||
}, {}, "Angle" });
|
||||
m_autoedit_dim_idx = 0;
|
||||
wxGetApp().CallAfter([this] { open_next_autoedit_dim(); });
|
||||
}
|
||||
|
||||
std::string DesignSketchTool::dimtype_title(DimType k) const {
|
||||
switch (k) {
|
||||
case DimType::Length: return "Length";
|
||||
case DimType::Diameter: return "Diameter";
|
||||
case DimType::Radius: return "Radius";
|
||||
case DimType::Angle: return "Angle";
|
||||
case DimType::Distance: return "Distance";
|
||||
case DimType::DistanceToLine: return "Distance";
|
||||
default: return "Value";
|
||||
}
|
||||
}
|
||||
|
||||
// Draw-then-edit dispatcher: mirror the Select-mode quote-click logic, but target the
|
||||
// freshly-drawn selection's PRIMARY value and use the tentative (clean-cancel) path for
|
||||
// scalar quotes. Runs after render_live_quotes, so the live-quote state is populated.
|
||||
@@ -1118,7 +1130,7 @@ void DesignSketchTool::open_primary_autoedit()
|
||||
if (q.kind == DimType::Angle) {
|
||||
const int ei = q.ea;
|
||||
m_autoedit_dims.push_back({ q.label_pos, measure_dim(q),
|
||||
[this, ei](double v) { set_line_angle(ei, v); }, { ei } });
|
||||
[this, ei](double v) { set_line_angle(ei, v); }, { ei }, "Angle" });
|
||||
continue;
|
||||
}
|
||||
DimAnnot a = q;
|
||||
@@ -1130,7 +1142,7 @@ void DesignSketchTool::open_primary_autoedit()
|
||||
m_constraints.push_back(constraint_for(a));
|
||||
m_dimensions.push_back(a);
|
||||
resolve_live();
|
||||
}, { a.ea, a.eb } });
|
||||
}, { a.ea, a.eb }, dimtype_title(a.kind) });
|
||||
}
|
||||
|
||||
// (2) Geometric editors (mutate geometry directly, no constraint). Each reads the CURRENT
|
||||
@@ -1150,8 +1162,8 @@ void DesignSketchTool::open_primary_autoedit()
|
||||
const double side = (m_entities[f.begin].p1 - m_entities[f.begin].p0).norm();
|
||||
const Vec2d sp = m_entities[f.begin].p0 - f.c0;
|
||||
double deg = std::atan2(sp.y(), sp.x()) * 180.0 / M_PI; if (deg < 0.0) deg += 360.0;
|
||||
m_autoedit_dims.push_back({ m_live_poly_side_label, side, [this, fi](double v){ set_polygon_side(fi, v); }, span(fi) });
|
||||
m_autoedit_dims.push_back({ m_live_poly_angle_label, deg, [this, fi](double v){ set_polygon_angle(fi, v); }, span(fi) });
|
||||
m_autoedit_dims.push_back({ m_live_poly_side_label, side, [this, fi](double v){ set_polygon_side(fi, v); }, span(fi), "Side" });
|
||||
m_autoedit_dims.push_back({ m_live_poly_angle_label, deg, [this, fi](double v){ set_polygon_angle(fi, v); }, span(fi), "Angle" });
|
||||
}
|
||||
}
|
||||
if (m_live_rrect_fi >= 0) {
|
||||
@@ -1161,16 +1173,16 @@ void DesignSketchTool::open_primary_autoedit()
|
||||
auto rr_w = [this, fi](double v){ const Feature& g = m_features[fi]; set_rounded_rect(fi, v, std::abs(g.c1.y()-g.c0.y()), g.param); };
|
||||
auto rr_h = [this, fi](double v){ const Feature& g = m_features[fi]; set_rounded_rect(fi, std::abs(g.c1.x()-g.c0.x()), v, g.param); };
|
||||
auto rr_r = [this, fi](double v){ const Feature& g = m_features[fi]; set_rounded_rect(fi, std::abs(g.c1.x()-g.c0.x()), std::abs(g.c1.y()-g.c0.y()), v); };
|
||||
m_autoedit_dims.push_back({ m_live_rrect_w_label, w, rr_w, span(fi) });
|
||||
m_autoedit_dims.push_back({ m_live_rrect_h_label, h, rr_h, span(fi) });
|
||||
m_autoedit_dims.push_back({ m_live_rrect_r_label, r, rr_r, span(fi) });
|
||||
m_autoedit_dims.push_back({ m_live_rrect_w_label, w, rr_w, span(fi), "Width" });
|
||||
m_autoedit_dims.push_back({ m_live_rrect_h_label, h, rr_h, span(fi), "Height" });
|
||||
m_autoedit_dims.push_back({ m_live_rrect_r_label, r, rr_r, span(fi), "Radius" });
|
||||
}
|
||||
if (m_live_aslot_fi >= 0) {
|
||||
const Feature& f = m_features[m_live_aslot_fi];
|
||||
const int fi = m_live_aslot_fi;
|
||||
const double Rc = (f.c1 - f.c0).norm(), fw = 2.0 * f.param;
|
||||
m_autoedit_dims.push_back({ m_live_aslot_r_label, Rc, [this, fi](double v){ const Feature& g = m_features[fi]; set_arc_slot(fi, v, g.param); }, span(fi) });
|
||||
m_autoedit_dims.push_back({ m_live_aslot_w_label, fw, [this, fi](double v){ const Feature& g = m_features[fi]; set_arc_slot(fi, (g.c1-g.c0).norm(), std::max(1e-3, v*0.5)); }, span(fi) });
|
||||
m_autoedit_dims.push_back({ m_live_aslot_r_label, Rc, [this, fi](double v){ const Feature& g = m_features[fi]; set_arc_slot(fi, v, g.param); }, span(fi), "Radius" });
|
||||
m_autoedit_dims.push_back({ m_live_aslot_w_label, fw, [this, fi](double v){ const Feature& g = m_features[fi]; set_arc_slot(fi, (g.c1-g.c0).norm(), std::max(1e-3, v*0.5)); }, span(fi), "Width" });
|
||||
}
|
||||
if (m_live_slot_fi >= 0) {
|
||||
const Feature& f = m_features[m_live_slot_fi];
|
||||
@@ -1179,25 +1191,25 @@ void DesignSketchTool::open_primary_autoedit()
|
||||
const Vec2d d = f.c1 - f.c0;
|
||||
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) });
|
||||
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) });
|
||||
m_autoedit_dims.push_back({ m_live_slot_angle_label, deg, [this, fi](double v){ set_slot_angle(fi, v); }, span(fi) });
|
||||
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_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
|
||||
const int ei = m_live_arc_ei;
|
||||
const SketchEntity& e = m_entities[ei];
|
||||
const double swdeg = std::abs(e.end_angle - e.start_angle) * 180.0 / M_PI;
|
||||
m_autoedit_dims.push_back({ m_live_arc_angle_label, swdeg, [this, ei](double v){ set_arc_sweep(ei, v); }, { ei } });
|
||||
m_autoedit_dims.push_back({ m_live_arc_angle_label, swdeg, [this, ei](double v){ set_arc_sweep(ei, v); }, { ei }, "Angle" });
|
||||
}
|
||||
if (m_live_ellipse_ei >= 0) {
|
||||
const int ei = m_live_ellipse_ei;
|
||||
const SketchEntity& e = m_entities[ei];
|
||||
m_autoedit_dims.push_back({ m_live_ellipse_major_label, e.radius, [this, ei](double v){ set_ellipse_axis(ei, true, v); }, { ei } });
|
||||
m_autoedit_dims.push_back({ m_live_ellipse_minor_label, e.rminor, [this, ei](double v){ set_ellipse_axis(ei, false, v); }, { ei } });
|
||||
m_autoedit_dims.push_back({ m_live_ellipse_major_label, e.radius, [this, ei](double v){ set_ellipse_axis(ei, true, v); }, { ei }, "Major" });
|
||||
m_autoedit_dims.push_back({ m_live_ellipse_minor_label, e.rminor, [this, ei](double v){ set_ellipse_axis(ei, false, v); }, { ei }, "Minor" });
|
||||
if (e.type == SketchEntity::Type::EllipseArc) { // + included sweep
|
||||
const double swdeg = std::abs(e.end_angle - e.start_angle) * 180.0 / M_PI;
|
||||
m_autoedit_dims.push_back({ m_live_ellipsearc_sweep_label, swdeg,
|
||||
[this, ei](double v){ set_ellipsearc_sweep(ei, v); }, { ei } });
|
||||
[this, ei](double v){ set_ellipsearc_sweep(ei, v); }, { ei }, "Angle" });
|
||||
}
|
||||
}
|
||||
if (m_live_obrect_fi >= 0) { // oblique rect: W,H already added as scalars; + orientation
|
||||
@@ -1207,7 +1219,7 @@ void DesignSketchTool::open_primary_autoedit()
|
||||
double adeg = std::atan2(e0.p1.y() - e0.p0.y(), e0.p1.x() - e0.p0.x()) * 180.0 / M_PI;
|
||||
if (adeg < 0.0) adeg += 360.0;
|
||||
m_autoedit_dims.push_back({ m_live_obrect_angle_label, adeg,
|
||||
[this, fi](double v){ set_rect_angle(fi, v); }, span(fi) });
|
||||
[this, fi](double v){ set_rect_angle(fi, v); }, span(fi), "Angle" });
|
||||
}
|
||||
|
||||
if (!m_autoedit_dims.empty()) {
|
||||
@@ -1225,7 +1237,7 @@ void DesignSketchTool::open_polygon_side_editor(int fi)
|
||||
if (f.begin < 0 || f.begin >= int(m_entities.size())) return;
|
||||
const double side = (m_entities[f.begin].p1 - m_entities[f.begin].p0).norm();
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, side,
|
||||
on_inline_edit(px, side, "Side",
|
||||
[this, fi](double v) { set_polygon_side(fi, v); },
|
||||
[]() {});
|
||||
}
|
||||
@@ -1239,7 +1251,7 @@ void DesignSketchTool::open_polygon_angle_editor(int fi)
|
||||
double deg = std::atan2(sp.y(), sp.x()) * 180.0 / M_PI;
|
||||
if (deg < 0.0) deg += 360.0;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, deg,
|
||||
on_inline_edit(px, deg, "Angle",
|
||||
[this, fi](double v) { set_polygon_angle(fi, v); },
|
||||
[]() {});
|
||||
}
|
||||
@@ -1494,7 +1506,7 @@ void DesignSketchTool::open_arc_angle_editor(int ei)
|
||||
if (e.type != SketchEntity::Type::Arc) return;
|
||||
double swdeg = std::abs(e.end_angle - e.start_angle) * 180.0 / M_PI;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, swdeg,
|
||||
on_inline_edit(px, swdeg, "Angle",
|
||||
[this, ei](double v) { set_arc_sweep(ei, v); },
|
||||
[]() {});
|
||||
}
|
||||
@@ -1579,7 +1591,7 @@ void DesignSketchTool::open_ellipse_axis_editor(int ei, bool major)
|
||||
if (e.type != SketchEntity::Type::Ellipse && e.type != SketchEntity::Type::EllipseArc) return;
|
||||
const double v = major ? e.radius : e.rminor;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, v,
|
||||
on_inline_edit(px, v, major ? "Major" : "Minor",
|
||||
[this, ei, major](double nv) { set_ellipse_axis(ei, major, nv); },
|
||||
[]() {});
|
||||
}
|
||||
@@ -1674,7 +1686,7 @@ void DesignSketchTool::open_rounded_rect_editor(int fi, int which)
|
||||
const double r = f.param;
|
||||
const double v = (which == 0) ? w : (which == 1) ? h : r;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, v,
|
||||
on_inline_edit(px, v, which == 0 ? "Width" : which == 1 ? "Height" : "Radius",
|
||||
[this, fi, which](double nv) {
|
||||
const Feature& g = m_features[fi];
|
||||
double gw = std::abs(g.c1.x() - g.c0.x());
|
||||
@@ -1715,7 +1727,7 @@ void DesignSketchTool::open_arc_slot_editor(int fi, bool radius)
|
||||
const double Rc = (f.c1 - f.c0).norm();
|
||||
const double v = radius ? Rc : (2.0 * f.param); // width quote shows the FULL width
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, v,
|
||||
on_inline_edit(px, v, radius ? "Radius" : "Width",
|
||||
[this, fi, radius](double nv) {
|
||||
const Feature& g = m_features[fi];
|
||||
const double gRc = (g.c1 - g.c0).norm();
|
||||
@@ -1762,7 +1774,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,
|
||||
on_inline_edit(px, v, which == 0 ? "Length" : which == 1 ? "Width" : "Angle",
|
||||
[this, fi, which](double nv) {
|
||||
const Feature& g = m_features[fi];
|
||||
if (which == 0) set_slot(fi, nv, g.param);
|
||||
@@ -3040,7 +3052,7 @@ void DesignSketchTool::open_extrude_editor(int which)
|
||||
if (!on_inline_edit) return;
|
||||
const double cur = (which == 1) ? m_ex_depth2 : m_ex_depth;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, cur,
|
||||
on_inline_edit(px, cur, "",
|
||||
[this, which](double v) {
|
||||
const double d = std::max(0.01, v);
|
||||
if (which == 1) m_ex_depth2 = d; else m_ex_depth = d;
|
||||
@@ -3466,7 +3478,7 @@ void DesignSketchTool::open_move_editor(int axis)
|
||||
{
|
||||
if (!on_inline_edit) return;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, m_mv_offset[axis],
|
||||
on_inline_edit(px, m_mv_offset[axis], "",
|
||||
[this, axis](double v) {
|
||||
m_mv_offset[axis] = v;
|
||||
if (on_body_move_changed) on_body_move_changed(m_mv_body, compose_move_xform());
|
||||
@@ -3660,7 +3672,7 @@ void DesignSketchTool::open_fillet_editor()
|
||||
{
|
||||
if (!on_inline_edit) return;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, m_fl_radius,
|
||||
on_inline_edit(px, m_fl_radius, "",
|
||||
[this](double v) {
|
||||
m_fl_radius = std::max(0.01, v);
|
||||
if (on_fillet_radius_changed) on_fillet_radius_changed(m_fl_radius);
|
||||
@@ -3890,14 +3902,14 @@ void DesignSketchTool::open_hole_editor(int which)
|
||||
if (!on_inline_edit) return;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
if (which == 1) {
|
||||
on_inline_edit(px, m_hl_diameter,
|
||||
on_inline_edit(px, m_hl_diameter, "",
|
||||
[this](double v) {
|
||||
m_hl_diameter = std::max(0.01, v);
|
||||
if (on_hole_changed) on_hole_changed(m_hl_x, m_hl_y, m_hl_diameter, m_hl_depth);
|
||||
},
|
||||
[]() {});
|
||||
} else if (which == 2) {
|
||||
on_inline_edit(px, m_hl_depth,
|
||||
on_inline_edit(px, m_hl_depth, "",
|
||||
[this](double v) {
|
||||
m_hl_depth = std::max(0.01, v);
|
||||
if (on_hole_changed) on_hole_changed(m_hl_x, m_hl_y, m_hl_diameter, m_hl_depth);
|
||||
@@ -3905,7 +3917,7 @@ void DesignSketchTool::open_hole_editor(int which)
|
||||
[]() {});
|
||||
} else if (which == 3) { // #2 Part B: edit the distance from the u-side
|
||||
const double ru = m_hl_has_bounds ? m_hl_umin : 0.0;
|
||||
on_inline_edit(px, m_hl_x - ru,
|
||||
on_inline_edit(px, m_hl_x - ru, "",
|
||||
[this, ru](double v) {
|
||||
m_hl_x = ru + v;
|
||||
if (on_hole_changed) on_hole_changed(m_hl_x, m_hl_y, m_hl_diameter, m_hl_depth);
|
||||
@@ -3913,7 +3925,7 @@ void DesignSketchTool::open_hole_editor(int which)
|
||||
[]() {});
|
||||
} else if (which == 4) { // edit the distance from the v-side
|
||||
const double rv = m_hl_has_bounds ? m_hl_vmin : 0.0;
|
||||
on_inline_edit(px, m_hl_y - rv,
|
||||
on_inline_edit(px, m_hl_y - rv, "",
|
||||
[this, rv](double v) {
|
||||
m_hl_y = rv + v;
|
||||
if (on_hole_changed) on_hole_changed(m_hl_x, m_hl_y, m_hl_diameter, m_hl_depth);
|
||||
@@ -4089,14 +4101,14 @@ void DesignSketchTool::open_thread_editor(int which)
|
||||
if (!on_inline_edit) return;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
if (which == 1) {
|
||||
on_inline_edit(px, m_th_radius,
|
||||
on_inline_edit(px, m_th_radius, "",
|
||||
[this](double v) {
|
||||
m_th_radius = std::max(0.01, v);
|
||||
if (on_thread_changed) on_thread_changed(m_th_x, m_th_y, m_th_radius, m_th_height);
|
||||
},
|
||||
[]() {});
|
||||
} else if (which == 2) {
|
||||
on_inline_edit(px, m_th_height,
|
||||
on_inline_edit(px, m_th_height, "",
|
||||
[this](double v) {
|
||||
m_th_height = std::max(0.01, v);
|
||||
if (on_thread_changed) on_thread_changed(m_th_x, m_th_y, m_th_radius, m_th_height);
|
||||
@@ -4191,7 +4203,7 @@ void DesignSketchTool::open_shell_editor()
|
||||
{
|
||||
if (!on_inline_edit) return;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, m_sh_thickness,
|
||||
on_inline_edit(px, m_sh_thickness, "",
|
||||
[this](double v) {
|
||||
m_sh_thickness = std::max(0.01, v);
|
||||
if (on_shell_thickness_changed) on_shell_thickness_changed(m_sh_thickness);
|
||||
@@ -4572,7 +4584,7 @@ void DesignSketchTool::open_revolve_editor()
|
||||
{
|
||||
if (!on_inline_edit) return;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, m_rv_angle,
|
||||
on_inline_edit(px, m_rv_angle, "",
|
||||
[this](double v) {
|
||||
m_rv_angle = std::min(360.0, std::max(1.0, v));
|
||||
if (on_revolve_angle_changed) on_revolve_angle_changed(m_rv_angle);
|
||||
@@ -4758,7 +4770,7 @@ void DesignSketchTool::open_pattern_editor()
|
||||
if (!on_inline_edit) return;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
const double cur = m_pt_circular ? m_pt_angle : m_pt_spacing;
|
||||
on_inline_edit(px, cur,
|
||||
on_inline_edit(px, cur, "",
|
||||
[this](double v) {
|
||||
if (m_pt_circular) m_pt_angle = std::min(360.0, std::max(1.0, v));
|
||||
else m_pt_spacing = std::max(0.1, v);
|
||||
@@ -6014,7 +6026,7 @@ void DesignSketchTool::open_op_editor()
|
||||
if (!on_inline_edit || !op_ready() || m_mode == Mode::Mirror) return;
|
||||
const double sign = (m_mode == Mode::Offset && m_op_value < 0) ? -1.0 : 1.0;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, std::abs(m_op_value),
|
||||
on_inline_edit(px, std::abs(m_op_value), "",
|
||||
[this, sign](double v) {
|
||||
m_op_value = (m_mode == Mode::Offset) ? sign * std::abs(v) : std::max(0.001, v);
|
||||
recompute_op_ghost();
|
||||
@@ -6321,7 +6333,7 @@ void DesignSketchTool::open_tf_editor_a()
|
||||
else cur = m_tf_delta.norm();
|
||||
Vec2d dir = m_tf_delta; if (dir.norm() > 1e-9) dir.normalize(); else dir = Vec2d(1, 0);
|
||||
const double sgn = (m_tf_angle < 0) ? -1.0 : 1.0;
|
||||
on_inline_edit(px, cur,
|
||||
on_inline_edit(px, cur, "",
|
||||
[this, dir, sgn](double v) {
|
||||
switch (m_mode) {
|
||||
case Mode::Move: case Mode::Array: m_tf_delta = dir * v; break;
|
||||
@@ -6339,7 +6351,7 @@ void DesignSketchTool::open_tf_editor_count()
|
||||
if (!on_inline_edit || !tf_ready()) return;
|
||||
if (m_mode != Mode::Array && m_mode != Mode::PolarArray) return;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, double(std::max(2, m_tf_count)),
|
||||
on_inline_edit(px, double(std::max(2, m_tf_count)), "",
|
||||
[this](double v) { m_tf_count = std::max(2, int(v + 0.5)); recompute_tf_ghost(); },
|
||||
[]() {});
|
||||
}
|
||||
|
||||
@@ -78,6 +78,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_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; }
|
||||
@@ -320,6 +321,36 @@ public:
|
||||
const std::vector<int>& selection() const { return m_selection; }
|
||||
void clear_selection();
|
||||
void delete_selected(); // erase selected entities
|
||||
// Abort any pending/queued draw-then-edit value-field sequence. Removing an entity that
|
||||
// still has a deferred auto-edit would otherwise open a field on a now-deleted entity and
|
||||
// freeze the flow (its live quote label also lingers). Mirrors set_tool's resync.
|
||||
void reset_autoedit() {
|
||||
m_awaiting_length = false;
|
||||
m_autoedit_pending = false;
|
||||
m_autoedit_dims.clear();
|
||||
m_autoedit_dim_idx = -1;
|
||||
m_autoedit_seen = int(m_entities.size());
|
||||
m_live_quotes.clear(); // rebuilt from current geometry on the next render
|
||||
}
|
||||
// Ctrl+Z while sketching: drop the last drawn entity (reuses delete_selected's remap).
|
||||
bool undo_last_entity() {
|
||||
if (!m_active || m_entities.empty()) return false;
|
||||
m_selection.assign(1, int(m_entities.size()) - 1);
|
||||
delete_selected();
|
||||
reset_autoedit();
|
||||
return true;
|
||||
}
|
||||
// Delete while sketching: the selected entities, or the last drawn one if none is selected.
|
||||
bool delete_selected_or_last() {
|
||||
if (!m_active) return false;
|
||||
if (m_selection.empty()) {
|
||||
if (m_entities.empty()) return false;
|
||||
m_selection.assign(1, int(m_entities.size()) - 1);
|
||||
}
|
||||
delete_selected();
|
||||
reset_autoedit();
|
||||
return true;
|
||||
}
|
||||
std::function<void(int count)> on_selection_changed;
|
||||
|
||||
// Dimension tool: infer a driving dimension from the current selection and set
|
||||
@@ -348,9 +379,9 @@ public:
|
||||
// screen pixel, pre-filled with `current`; commit applies the value, cancel keeps
|
||||
// it. The owner (DesignCanvas) hosts the wxTextCtrl over the GL canvas. This is the
|
||||
// single numeric-entry path for all sketch dimensions (replaces the modal cards).
|
||||
std::function<void(wxPoint screen_px, double current,
|
||||
std::function<void(double)> commit,
|
||||
std::function<void()> cancel)> on_inline_edit;
|
||||
std::function<void(wxPoint screen_px, double current, const std::string& title,
|
||||
std::function<void(double)> commit,
|
||||
std::function<void()> cancel)> on_inline_edit;
|
||||
// Force-close any open inline field (runs its cancel = keep-as-drawn). Used by the polyline
|
||||
// terminators (right-click / double-click) to end the chain even mid per-segment edit.
|
||||
std::function<void()> on_inline_dismiss;
|
||||
@@ -562,6 +593,7 @@ private:
|
||||
// circumradius + orientation together).
|
||||
void drag_polygon_vertex(int fi, int ei, SketchPointRole role, const Vec2d& target);
|
||||
double measure_dim(const DimAnnot& a) const; // value from geometry
|
||||
std::string dimtype_title(DimType k) const;
|
||||
SketchEntityConstraintDef constraint_for(const DimAnnot& a) const; // driving def
|
||||
int place_dimension(DimAnnot a); // create+drive+notify
|
||||
std::string dim_text(const DimAnnot& a) const; // rendered label string
|
||||
@@ -697,6 +729,7 @@ private:
|
||||
double value; // initial value shown
|
||||
std::function<void(double)> apply; // commit: set the dimension
|
||||
std::vector<int> hi; // entities to highlight while THIS field is open
|
||||
std::string title; // label shown above the value field
|
||||
};
|
||||
std::vector<AutoEditStep> m_autoedit_dims; // queued steps to edit in sequence
|
||||
int m_autoedit_dim_idx{-1}; // index into m_autoedit_dims (-1 = idle)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/window.h>
|
||||
#include <wx/toplevel.h>
|
||||
@@ -47,8 +48,12 @@ SketchInlineEditor::SketchInlineEditor(wxWindow* parent_canvas)
|
||||
wxFRAME_NO_TASKBAR | wxBORDER_NONE | wxSTAY_ON_TOP);
|
||||
m_ctrl = new wxTextCtrl(m_frame, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(82, -1),
|
||||
wxTE_PROCESS_ENTER | wxTE_RIGHT | wxBORDER_SIMPLE);
|
||||
m_frame->SetBackgroundColour(wxColour(40, 42, 46));
|
||||
m_title = new wxStaticText(m_frame, wxID_ANY, wxEmptyString);
|
||||
m_title->SetForegroundColour(wxColour(160, 162, 168));
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(m_ctrl, 1, wxEXPAND);
|
||||
sizer->Add(m_title, 0, wxLEFT | wxRIGHT | wxTOP, 3);
|
||||
sizer->Add(m_ctrl, 1, wxEXPAND | wxALL, 2);
|
||||
m_frame->SetSizerAndFit(sizer);
|
||||
m_frame->Hide();
|
||||
|
||||
@@ -60,6 +65,7 @@ SketchInlineEditor::SketchInlineEditor(wxWindow* parent_canvas)
|
||||
}
|
||||
|
||||
void SketchInlineEditor::open(const wxPoint& screen_px, double value,
|
||||
const std::string& title,
|
||||
std::function<void(double)> on_commit,
|
||||
std::function<void()> on_cancel)
|
||||
{
|
||||
@@ -68,6 +74,10 @@ void SketchInlineEditor::open(const wxPoint& screen_px, double value,
|
||||
m_commit = std::move(on_commit);
|
||||
m_cancel = std::move(on_cancel);
|
||||
m_ctrl->ChangeValue(en_format(value));
|
||||
if (m_title) {
|
||||
m_title->SetLabel(wxString::FromUTF8(title.c_str()));
|
||||
m_title->Show(!title.empty());
|
||||
}
|
||||
m_frame->Fit();
|
||||
const wxSize sz = m_frame->GetSize();
|
||||
wxPoint pos(screen_px.x - sz.GetWidth() / 2, screen_px.y - sz.GetHeight() / 2);
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
#define slic3r_SketchInlineEditor_hpp_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
class wxWindow;
|
||||
class wxFrame;
|
||||
class wxTextCtrl;
|
||||
class wxStaticText;
|
||||
class wxPoint;
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -24,7 +26,7 @@ public:
|
||||
|
||||
// Show the editor centred on `screen_px` (absolute screen coords), pre-filled with
|
||||
// `value`. on_commit(parsed) fires on Enter with a valid number; on_cancel() on Esc.
|
||||
void open(const wxPoint& screen_px, double value,
|
||||
void open(const wxPoint& screen_px, double value, const std::string& title,
|
||||
std::function<void(double)> on_commit,
|
||||
std::function<void()> on_cancel);
|
||||
void close();
|
||||
@@ -37,6 +39,7 @@ private:
|
||||
|
||||
wxFrame* m_frame{nullptr};
|
||||
wxTextCtrl* m_ctrl{nullptr};
|
||||
wxStaticText* m_title{nullptr};
|
||||
std::function<void(double)> m_commit;
|
||||
std::function<void()> m_cancel;
|
||||
bool m_open{false};
|
||||
|
||||
Reference in New Issue
Block a user