mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-06 07:11:10 +00:00
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.
This commit is contained in:
@@ -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)"),
|
||||
|
||||
Reference in New Issue
Block a user