From 1d06b8576b5ba5f3478ed5d0adbe8dee99efe04e Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sun, 4 Oct 2026 00:01:05 +0800 Subject: [PATCH] Fix Design tab getting stuck unable to start a new sketch After undo, redo, New Design, a project load, a delete or a reorder, a sketch profile picked beforehand stayed selected even though it was gone. The right-click menu then offered "Sketch profile" over an empty design with Create greyed out. Those operations now clear the selection. The Confirm button no longer stays greyed in a sketch session after a card whose preview was invalid has been closed. Delete and reorder now wait while a sketch or constrain session, the Text dialog, an Insert placement or the move gizmo is open, instead of editing whichever feature took its place in the list. --- src/slic3r/GUI/CAD/DesignCanvas.cpp | 23 +++- src/slic3r/GUI/CAD/DesignCanvas.hpp | 9 +- src/slic3r/GUI/CAD/DesignPanel.cpp | 138 ++++++++++++++---------- src/slic3r/GUI/CAD/DesignPanel.hpp | 12 ++- src/slic3r/GUI/CAD/DesignSketchTool.hpp | 2 + 5 files changed, 120 insertions(+), 64 deletions(-) diff --git a/src/slic3r/GUI/CAD/DesignCanvas.cpp b/src/slic3r/GUI/CAD/DesignCanvas.cpp index adf1ce2a89..c638747b89 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.cpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.cpp @@ -679,12 +679,27 @@ void DesignCanvas::clear_loop_pick() m_sketch_tool.clear_display_pick(); } +void DesignCanvas::clear_solid_pick() +{ + m_sketch_tool.clear_solid_selection(); +} + void DesignCanvas::set_loop_pick(int feature, int region) { m_sketch_tool.set_display_pick(feature, region); request_repaint(); } +int DesignCanvas::loop_pick_feature() const +{ + return m_sketch_tool.display_pick(); +} + +int DesignCanvas::loop_pick_region() const +{ + return m_sketch_tool.display_pick_region(); +} + void DesignCanvas::set_escalate_on_repick(bool on) { m_sketch_tool.set_escalate_on_repick(on); @@ -1472,18 +1487,20 @@ bool DesignCanvas::drawing_in_progress() const bool DesignCanvas::has_any_selection() const { - return m_sketch_tool.has_solid_selection() || m_sketch_tool.sketch_has_selection(); + return m_sketch_tool.has_solid_selection() || m_sketch_tool.sketch_has_selection() + || m_sketch_tool.display_pick() >= 0; } bool DesignCanvas::clear_any_selection() { if (!has_any_selection()) return false; - // Both, unconditionally: which of the two is live depends on the mode, and Esc at idle means - // "nothing is picked" in either of them. clear_selection() reports through the tool's own + // All three, unconditionally: which is live depends on the mode, and Esc at idle means + // "nothing is picked" in any of them. clear_selection() reports through the tool's own // on_selection_changed; the solid side has no such notification, so the panel refreshes what // depends on it (see DesignPanel::escape). m_sketch_tool.clear_selection(); m_sketch_tool.clear_solid_selection(); + m_sketch_tool.clear_display_pick(); // clear_solid_selection() is silent by design (recomputes call it while ids are invalid), but // the panel mirrors the pick to aim Extrude and the dress-up tools. An Esc that cleared the // highlight without telling the panel would leave those aimed at a body nothing points to. diff --git a/src/slic3r/GUI/CAD/DesignCanvas.hpp b/src/slic3r/GUI/CAD/DesignCanvas.hpp index 94cce664d7..5701bd5ec4 100644 --- a/src/slic3r/GUI/CAD/DesignCanvas.hpp +++ b/src/slic3r/GUI/CAD/DesignCanvas.hpp @@ -124,7 +124,12 @@ public: // consumed loop must be compared against. std::vector> region_entity_indices_with_holes(const std::vector& ents) const; void clear_loop_pick(); // drop the click-selected loop highlight (e.g. after extrude) + void clear_solid_pick(); // drop the solid pick and its highlight; no callback, no repaint void set_loop_pick(int feature, int region); // adopt a loop pick made before the commit + // The picked committed sketch (feature index, -1 = none) and its closed region (-1 also when + // the click hit a stroke on no closed loop). The panel reads the pick here and keeps no copy. + int loop_pick_feature() const; + int loop_pick_region() const; void set_escalate_on_repick(bool on); // off while a card has armed a face/edge pick // Solid whole/face/edge selection: point the tool at the bodies + concatenated // tessellation (with per-triangle face & body ids), and a callback fired on each @@ -312,8 +317,8 @@ public: bool sketch_disarm_tool(); // CadLevel::Tool — armed sketch tool falls back to Select bool sketch_confirm_pending(); // Enter — apply a ready edit-op or transform bool drawing_in_progress() const;// clicks or picks are down but nothing is committed yet - bool has_any_selection() const; // model pick or sketch pick - bool clear_any_selection(); // CadLevel::Idle — drop both; true if anything was dropped + bool has_any_selection() const; // model pick, committed-loop pick or sketch pick + bool clear_any_selection(); // CadLevel::Idle — drop all three; true if anything was dropped bool sketch_first_selected_type(SketchEntity::Type& out) const; // Live sketch session (Fase 4.2 live constraint path): the panel reads the in-session // selection and entities, and commits a planned constraint through the tool's diff --git a/src/slic3r/GUI/CAD/DesignPanel.cpp b/src/slic3r/GUI/CAD/DesignPanel.cpp index 617699b376..7d4f83824a 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.cpp +++ b/src/slic3r/GUI/CAD/DesignPanel.cpp @@ -707,7 +707,8 @@ DesignPanel::DesignPanel(wxWindow* parent) // Onshape push/pull: an explicitly picked solid face (Face-level cycle, no loop // selected) is extruded as the profile — this takes priority over re-extruding an // already-consumed sketch (resolve_extrude_sketch always returns the last Sketch). - if (m_sel_solid_face >= 0 && !m_doc.body.IsNull() && m_sel_sketch_region < 0) { + if (m_sel_solid_face >= 0 && !m_doc.body.IsNull() + && (m_viewport == nullptr || m_viewport->loop_pick_region() < 0)) { m_extrude_face_src = m_sel_solid_face; m_extrude_sketch_ref = -1; open_tool(Tool::Extrude); @@ -3634,8 +3635,6 @@ DesignPanel::DesignPanel(wxWindow* parent) open_tool(Tool::Extrude); // AFTER open_tool, not before: opening the tool re-derives the selection state, so a // region recorded ahead of it is wiped before Extrude ever reads it. - m_sel_sketch_feat = m_extrude_sketch_ref; - m_sel_sketch_region = region; m_sel_solid_face = m_sel_solid_edge = -1; m_pick_face = m_pick_face_body = -1; m_viewport->set_loop_pick(m_extrude_sketch_ref, region); @@ -3644,11 +3643,9 @@ DesignPanel::DesignPanel(wxWindow* parent) // Clicking a committed sketch loop on the plate (no live session) selects THAT loop: // the viewport highlights only it (cyan) and its Sketch feature's tree row is selected. - // The (feature, region) pair is remembered so Extrude builds just that one loop. + // The viewport keeps the (feature, region) pair so Extrude builds just that one loop. m_viewport->set_on_display_sketch_selected([this](int feat, int region, int entity) { if (feat < 0 || feat >= int(m_doc.features.size())) return; - m_sel_sketch_feat = feat; - m_sel_sketch_region = region; // Last pick wins (symmetric with the solid-pick handler): selecting a sketch loop drops // any stale solid face/edge pick so Extrude treats this loop as the profile. m_sel_solid_face = m_sel_solid_edge = -1; @@ -3734,12 +3731,9 @@ DesignPanel::DesignPanel(wxWindow* parent) // but the user pointed AT a face and a sketch should be able to use it. 3a2. m_pick_face_body = (level >= 1) ? body : -1; m_pick_face = (level >= 1) ? face : -1; - // Last pick wins: selecting a solid drops any stale committed-sketch loop selection. - // Otherwise a leftover loop keeps `m_sel_sketch_region >= 0`, which blocks the face - // push/pull branch in Extrude (`m_sel_solid_face >= 0 && m_sel_sketch_region < 0`) and - // makes Extrude build a DETACHED new body from the last sketch instead of push/pulling - // the face the user just clicked. - if (level >= 1) { m_sel_sketch_region = -1; m_sel_sketch_feat = -1; } + // Last pick wins: a leftover loop pick would block Extrude's face push/pull branch, so + // Extrude would extrude a sketch instead of push/pulling the clicked face. + if (level >= 1) m_viewport->clear_loop_pick(); // Say what got picked. Without this the ONLY feedback is the viewport highlight, so a // pick that registers but draws faintly is indistinguishable from one that never // happened — which is precisely how this failure was reported and why it resisted @@ -4471,6 +4465,10 @@ void DesignPanel::set_ui_mode(UiMode m) { m_ui_mode = m; if (m != UiMode::Sketch) m_sketch_on.clear(); // no stale "on the picked face" on the next hint + // A committed loop picked before the session means nothing to the sketch map, but it would + // still count as a selection and swallow the first Esc. The double-click that opens a sketch + // for editing makes one with its first click. + if (m == UiMode::Sketch && m_viewport != nullptr) m_viewport->clear_loop_pick(); // The DoF readout describes a SKETCH's constraint state, so it means nothing back in Feature // mode — where it nonetheless stayed on screen after every Confirm, Cancel and Escape // (752). Cleared here rather than at those three exits because this is the one place @@ -5207,9 +5205,9 @@ void DesignPanel::on_add_sketch() bool DesignPanel::extrude_uses_loop() const { return m_viewport != nullptr - && m_sel_sketch_region >= 0 + && m_viewport->loop_pick_region() >= 0 && m_extrude_sketch_ref >= 0 - && m_extrude_sketch_ref == m_sel_sketch_feat + && m_extrude_sketch_ref == m_viewport->loop_pick_feature() && m_extrude_sketch_ref < int(m_doc.features.size()) && !m_viewport->selected_loop_entities().empty(); } @@ -5236,13 +5234,12 @@ void DesignPanel::on_add_extrude() // other loops intact and still selectable. if (::getenv("ORCA_CAD_PICK_TRACE")) std::fprintf(stderr, "[pick] on_add_extrude: feat=%d reg=%d ents=%zu\n", - m_extrude_sketch_ref, m_sel_sketch_region, + m_extrude_sketch_ref, m_viewport->loop_pick_region(), m_viewport->selected_loop_entities().size()); idx = m_doc.add_extrude_entities(m_viewport->selected_loop_entities(), m_doc.features[m_extrude_sketch_ref].plane, m_distance->GetValue(), false, mode, name); - m_sel_sketch_region = -1; // consume the loop selection - m_viewport->clear_loop_pick(); // drop the now-stale loop highlight + m_viewport->clear_loop_pick(); // consume the loop selection } else { idx = m_doc.add_extrude(m_extrude_sketch_ref, m_distance->GetValue(), false, mode, name); } @@ -6245,7 +6242,12 @@ int DesignPanel::offer_selection_kind() const m_sel_solid_face); return int(GeometryEngine::cylinder_of_face(f).ok ? OfferSel::FaceCyl : OfferSel::FaceOther); } - if (m_sel_sketch_region >= 0) + // Validated like the body index above: a loop pick that names no sketch, or no closed region + // of one, must not title the menu "Sketch profile" and grey Create, which is what a stale one + // did over an empty document. + if (const int lf = m_viewport ? m_viewport->loop_pick_feature() : -1; + lf >= 0 && lf < int(m_doc.features.size()) && m_doc.features[lf].type == CadFeatureType::Sketch + && m_viewport->loop_pick_region() >= 0) return int(OfferSel::SkLoop); if (m_sel_solid_body >= 0 && m_sel_solid_body < nb) return int(CadDocument::is_sheet_shape(m_doc.bodies[m_sel_solid_body].shape) @@ -7447,6 +7449,7 @@ void DesignPanel::load_recipe(const std::string& blob) return; } m_feature_counter = int(m_doc.features.size()); + drop_selection(); feed_bodies(); // push the restored bodies into the viewport refresh_tree(); // rebuild the feature tree from the restored recipe set_status_ok(); @@ -7834,14 +7837,52 @@ void DesignPanel::set_tree_selection(int row) m_tree->SelectItem(m_tree_items[row]); } +// The selection (the solid pick, the hit face and the committed-loop pick) names bodies, faces and +// features by index, so replacing or renumbering the feature list (undo/redo, New Design, load, +// delete, reorder) drops it, the viewport's highlights with it. The solid highlight too: a rebuild +// that leaves no body never reaches set_solid_pick. The callers repaint. +void DesignPanel::drop_selection() +{ + m_sel_solid_body = m_sel_solid_face = m_sel_solid_edge = -1; + m_sel_solid_edges.clear(); + m_sel_solid_vertex = false; + m_pick_face = m_pick_face_body = -1; + if (m_viewport != nullptr) { + m_viewport->clear_loop_pick(); + m_viewport->clear_solid_pick(); + } +} + +// The shared front of delete and reorder, which renumber the feature list. A sketch or constrain +// session, the Text dialog, an Insert placement and the standalone move gizmo can each hold a +// feature or body index (m_edit_index, m_constrain_feat, m_text_feat, m_insert_feat, m_move_body) +// that a renumber would point at something else, so they are refused until Finish or Cancel; Undo +// and mcp_busy() refuse most of the same states. An open feature card is closed instead, +// discarding its candidate, rather than linger out of step with the tree. +bool DesignPanel::begin_renumber() +{ + if (m_ui_mode != UiMode::Feature || m_text_dlg != nullptr || m_active == Tool::Insert + || (m_active == Tool::None && m_viewport != nullptr && m_viewport->moving_body())) { + set_status(StatusKind::Info, _L("Finish or cancel what is open first")); + return false; + } + if (m_active != Tool::None || m_edit_index >= 0) cancel_tool(); + m_doc.checkpoint(); // undo boundary + drop_selection(); + return true; +} + void DesignPanel::after_tree_edit(bool ok) { update_undo_redo_buttons(); refresh_tree(); refresh_variables(); if (!ok) { - // The edit was rolled back (recompute failed); the body is unchanged. + // The edit was refused or rolled back; the body is unchanged. Repaint anyway: picks the + // caller dropped before trying it are still drawn, and set_status() repaints only when + // its text changes. set_status(StatusKind::Error, wxString::Format(_L("Edit rejected: %s"), kernel_error_text(m_doc.error))); + if (m_viewport != nullptr) m_viewport->request_repaint(); return; } sync_recipe_to_model(); // deletes, reorders and suppressions change the document too @@ -7887,6 +7928,7 @@ void DesignPanel::clear_document() m_doc.clear(); // features + bodies + meshes + history m_doc.auto_close_loops = wxGetApp().is_auto_close_sketch_loops(); // a new design: today's preference Slic3r::set_sketch_auto_close(m_doc.auto_close_loops); + drop_selection(); m_edit_index = -1; m_move_body = -1; show_move_card(false); @@ -7920,15 +7962,7 @@ void DesignPanel::on_delete_body() const std::string& raw = m_doc.features[src].name; const wxString fname = raw.empty() ? wxString::Format(_L("feature %d"), src + 1) : wxString::FromUTF8(raw); - // A card left open over a feature that is about to vanish goes stale — same reason - // on_delete_feature() closes it. - if (m_active != Tool::None || m_edit_index >= 0) { - reset_edit_state(); - close_tool(); - } - m_doc.checkpoint(); // undo boundary: deleting a body's feature - m_sel_solid_body = m_sel_solid_face = m_sel_solid_edge = -1; // the selection is about to - m_sel_solid_vertex = false; // name a body that is gone + if (!begin_renumber()) return; const bool ok = m_doc.remove_feature(src); after_tree_edit(ok); if (ok) @@ -7948,14 +7982,7 @@ void DesignPanel::on_delete_feature() set_status(StatusKind::Info, _L("Select a feature in the tree first")); 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 + if (!begin_renumber()) return; after_tree_edit(m_doc.remove_feature(sel)); } @@ -8022,7 +8049,7 @@ void DesignPanel::on_move_feature(int delta) int target = sel + delta; if (target < 0 || target >= int(m_doc.features.size())) return; // already at the end - m_doc.checkpoint(); // undo boundary: reordering a feature + if (!begin_renumber()) return; if (m_doc.move_feature(sel, delta)) { after_tree_edit(true); set_tree_selection(target); // keep the moved feature selected @@ -8058,9 +8085,8 @@ void DesignPanel::on_begin_constrain(int sel_override) // 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; + if (sel == wxNOT_FOUND || sel >= int(m_doc.features.size())) { + sel = m_viewport ? m_viewport->loop_pick_feature() : wxNOT_FOUND; // range-checked below set_tree_selection(sel); // keep the tree in step with what the viewport says } if (sel == wxNOT_FOUND || sel >= int(m_doc.features.size())) { @@ -10991,7 +11017,8 @@ void DesignPanel::refresh_preview() default: ready = _L("Sketch ready"); break; } set_status(StatusKind::Ok, ready); - for (::Button* b : m_confirm_btns) if (b) b->Enable(true); + m_candidate_ok = true; + update_confirm_button(); m_status->Refresh(); update_datum_gizmo(); // Plane card: show/refresh the in-canvas resize handles update_helix_gizmo(); // Helix card: draw the live curve + drag handles (no solid ghost) @@ -11034,7 +11061,8 @@ void DesignPanel::refresh_preview() set_status(StatusKind::Error, wxString::Format(_L("Invalid: %s"), kernel_error_text(err))); } } - for (::Button* b : m_confirm_btns) if (b) b->Enable(ok); + m_candidate_ok = ok; + update_confirm_button(); m_status->Refresh(); return; } @@ -11080,8 +11108,8 @@ void DesignPanel::refresh_preview() } // Onshape parity: a broken candidate cannot be committed. Grey the active dialog's // Confirm so the user sees the gate before clicking; the red status says why. - for (::Button* b : m_confirm_btns) - if (b != nullptr) b->Enable(ok); + m_candidate_ok = ok; + update_confirm_button(); // Fillet/Chamfer/Draft: once the target edge/face yields a valid result, show ONLY the // preview (hide the base bodies) so the user sees the finished shape, not the old solid // doubled with the ghost. Before a valid pick the body stays visible so it can be picked. @@ -11178,6 +11206,7 @@ void DesignPanel::push_polygon_params() void DesignPanel::open_tool(Tool t) { m_active = t; + m_candidate_ok = true; // a fresh card is confirmable until its preview says otherwise // Fillet/Chamfer/Draft no longer fade the body see-through; instead, once a valid target // is picked, refresh_preview hides the base bodies entirely (preview-only). Keep it opaque // here so the body is fully visible for picking the edge/face. @@ -11680,11 +11709,14 @@ bool DesignPanel::confirm_enabled() const return (m_viewport && m_viewport->moving_body()) || m_ui_mode == UiMode::Sketch || m_ui_mode == UiMode::Constrain; if (m_active == Tool::Insert) return true; + return m_candidate_ok; +} + +void DesignPanel::update_confirm_button() +{ + const bool ok = confirm_enabled(); for (::Button* b : m_confirm_btns) - if (b != nullptr && b->IsShownOnScreen()) return b->IsEnabled(); - for (::Button* b : m_confirm_btns) - if (b != nullptr) return b->IsEnabled(); - return true; + if (b != nullptr) b->Enable(ok); } // Which level of the interaction stack one Esc press belongs to. The rule itself lives in @@ -11752,8 +11784,6 @@ void DesignPanel::escape() // both of which say which one they are, and never through a key pressed on the way out of // something else. if (m_viewport && m_viewport->clear_any_selection()) { - m_sel_sketch_region = -1; - m_sel_sketch_feat = -1; set_status(StatusKind::Info, wxString()); return; } @@ -11790,6 +11820,7 @@ void DesignPanel::update_undo_redo_buttons() void DesignPanel::update_action_bar() { update_undo_redo_buttons(); // mode/tool changes flip the do_undo_redo gate -> refresh greying + update_confirm_button(); // ...and the ✓'s greying if (m_tb_action == nullptr || m_toolbar == nullptr) return; wxSizer* s = m_toolbar->GetSizer(); if (s == nullptr) return; @@ -11841,10 +11872,9 @@ void DesignPanel::do_undo_redo(bool redo) set_status(StatusKind::Info, redo ? _L("Nothing to redo") : _L("Nothing to undo")); return; } - // The solid whole/face/edge pick and any in-place edit reference ids that recompute() - // invalidates — drop them before refreshing from the restored document. - m_sel_solid_body = m_sel_solid_face = m_sel_solid_edge = -1; - m_pick_face = m_pick_face_body = -1; // recompute() invalidated the face ids too + // The picks and any in-place edit reference ids that recompute() invalidates — drop them + // before refreshing from the restored document. + drop_selection(); reset_edit_state(); after_tree_edit(true); // refresh tree + viewport meshes + status from the restored doc set_status(StatusKind::Info, wxString::Format(redo ? _L("Redo (%zu more)") : _L("Undo (%zu more)"), diff --git a/src/slic3r/GUI/CAD/DesignPanel.hpp b/src/slic3r/GUI/CAD/DesignPanel.hpp index 0cf670c191..1235c98ac3 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.hpp +++ b/src/slic3r/GUI/CAD/DesignPanel.hpp @@ -140,9 +140,10 @@ private: // used to be handled in four places that could not see each other, and that is how two // presses in a row reached past a tool and discarded the sketch under it. CadLevel escape_level() const; - bool confirm_enabled() const; // would the ✓ act right now (and is it not greyed)? + bool confirm_enabled() const; // would the ✓ act right now? (its greying follows this) void escape(); void update_action_bar(); // show the ✓/✗ bar iff a tool or mode is active + void update_confirm_button(); // grey the ✓ to confirm_enabled() void on_shape_changed(); void on_add_sketch(); @@ -294,6 +295,8 @@ private: void after_edit_op(); // shared edit-op refresh tail void on_edit_feature(); // reopen the selected feature's dialog populated void after_tree_edit(bool ok); // shared post-op refresh of tree/viewport/status + void drop_selection(); // forget the selection (solid, hit face, sketch loop): the feature list was replaced or renumbered + bool begin_renumber(); // before delete/reorder: refuse while an index is held, else close the card, checkpoint, drop picks void load_feature_into_dialog(const CadFeature& f); void reset_edit_state(); // back to add-mode (m_edit_index = -1) @@ -760,10 +763,6 @@ private: int m_pl_edgeA_body{-1}, m_pl_edgeA{-1}; int m_pl_edgeB_body{-1}, m_pl_edgeB{-1}; PlanePick m_plane_pick{PlanePick::None}; // which ref the next solid pick fills - // Plate loop selection (click a committed sketch loop): the Sketch feature + the - // clicked closed-region index, so Extrude builds just that one loop. -1 = none. - int m_sel_sketch_feat{-1}; - int m_sel_sketch_region{-1}; // Click-selected solid topology (whole/face/edge cycle): face id for up-to-face / dress-up. int m_sel_solid_body{-1}; // which body the face/edge selection is on int m_sel_solid_face{-1}; @@ -976,6 +975,9 @@ private: int m_feature_counter{0}; std::vector<::Button*> m_confirm_btns; + // refresh_preview's verdict on the open card's candidate (open_tool resets it). Kept here, not + // read back from the ✓, which every card and mode shares; the ✓ is greyed from confirm_enabled(). + bool m_candidate_ok{true}; // Edit-in-place state: add-mode is m_edit_index == -1. Single-feature edit // (Sketch or Extrude independently) uses only m_edit_index as the row to replace. diff --git a/src/slic3r/GUI/CAD/DesignSketchTool.hpp b/src/slic3r/GUI/CAD/DesignSketchTool.hpp index 7bee6abe49..a310a3add1 100644 --- a/src/slic3r/GUI/CAD/DesignSketchTool.hpp +++ b/src/slic3r/GUI/CAD/DesignSketchTool.hpp @@ -254,6 +254,8 @@ public: // sketch there is nothing left that would set this — and selected_loop_entities(), which is // what Extrude consumes, reads exactly these two fields. void set_display_pick(int feature, int region) { m_display_pick = feature; m_display_pick_region = region; } + int display_pick() const { return m_display_pick; } + int display_pick_region() const { return m_display_pick_region; } // Visual Extrude gizmo (C5b). The Extrude tool is a DesignPanel docked card, so the // sketch tool is NOT active during it; the panel feeds the profile plane + a 2D centroid