From 78a22fca93b2944a99052f3beafd3c1ddcbdd175 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Tue, 6 Oct 2026 18:38:37 +0800 Subject: [PATCH] Hide the Design tab's reference planes until a sketch needs them, with an Origin row to pin them --- docs/HLSD/design-tab.md | 15 +++- scripts/CAD/check-gui-click-edit.py | 24 ++--- scripts/CAD/check-gui-sketching.py | 20 +++-- src/slic3r/GUI/CAD/DesignPanel.cpp | 130 +++++++++++++++++----------- src/slic3r/GUI/CAD/DesignPanel.hpp | 17 ++-- 5 files changed, 131 insertions(+), 75 deletions(-) diff --git a/docs/HLSD/design-tab.md b/docs/HLSD/design-tab.md index 739e0e47ca..45da2fa607 100644 --- a/docs/HLSD/design-tab.md +++ b/docs/HLSD/design-tab.md @@ -181,6 +181,17 @@ canvas (a sibling of the canvas, not a child over it — on GTK a child window o a plate grid is never read as a sketch grid, and `N` to look normal to the plane. Code that changes any of the three belongs with a change to this section. +The reference planes — XY, XZ and YZ through the modeling origin, with their half-axes — are +drawn on demand, because three translucent squares over every model are noise once they are not +the thing being picked. Sketch mode brings them up, since choosing the sketch plane is exactly +when they are picked; a live session draws none, and Finish or Cancel takes them away. The +Feature tree's Origin row, pinned above the features and never removable, keeps them up outside a +sketch. Its state is a view preference in AppConfig rather than part of the recipe, so it costs +the project format nothing. The Plane tool keeps its own rule: the planes and the datums as Offset +bases, and nothing for the other methods, where a click on a plane would rewrite the datum's +references. The `P` and `A` keys are a separate, unpickable view helper and do not follow the +Origin row. + ## Rendering the bodies The tab draws its bodies through the same `GLCanvas3D` object path as Prepare, so how they look @@ -253,8 +264,8 @@ The tab is a page of Orca's main window and answers to the same settings as Prep constraint-row buttons are Orca's self-painted `Button`, because a native button cannot take a hover background on macOS. The Feature tree and Bodies lists are a custom-drawn `DesignRowList` rather than a `wxTreeCtrl`, so each row carries its own actions — Edit, - Show/hide and Delete on a feature, Move, Show/hide and Delete on a body — and the eye shows - whether that row is hidden. + Show/hide and Delete on a feature, Move, Show/hide and Delete on a body, and only Show/hide on + the Origin row above the features — and the eye shows whether that row is hidden. - **Plates.** This is the one thing the tab does not follow. The canvas has a bed of its own at the printer bed's home position, whichever plate Prepare has current, and a new document's modeling origin is that bed's centre. A bed that followed the current plate would slide out diff --git a/scripts/CAD/check-gui-click-edit.py b/scripts/CAD/check-gui-click-edit.py index 159bdb19f1..9d202a35b0 100755 --- a/scripts/CAD/check-gui-click-edit.py +++ b/scripts/CAD/check-gui-click-edit.py @@ -480,10 +480,13 @@ def type_into_open_field(value, mark): def enter_sketch(timeout=180): """Open a real sketch on a real plane, and PROVE it with the socket before drawing anything. - THE SEQUENCE MATTERS AND IT IS NOT OBVIOUS. Shift+S enters sketch MODE and pops the plane - offer; the offer must be dismissed; and the plane itself is chosen by clicking it in the - viewport BEFORE Shift+S. check-gui-sketching.py has always done all four steps. This ladder - did two of them — Design tab, then Shift+S — and went straight to the tool letters. + THE SEQUENCE MATTERS AND IT IS NOT OBVIOUS. The reference planes are hidden until Shift+S + enters sketch MODE, so the plane is clicked AFTER it. Entering with a plane an earlier rung + chose pops the plane offer at once, and a click outside a popup menu only closes it, so the + plane is clicked twice, apart: the second click always lands on the plane. No Escape — with + no offer up, Escape in an empty sketch leaves it. Then a tool letter starts the session. + check-gui-sketching.py does the same steps. This ladder once did two of them — Design tab, + then Shift+S — and went straight to the tool letters. That intermediate state is the trap. `is_sketching` reads 1, every tool key is accepted and traced, and not one click draws anything, because there is no plane under them. The ladder @@ -499,9 +502,9 @@ def enter_sketch(timeout=180): click(132, 53) # Design tab time.sleep(2.0) dismiss_modals() - click(*PLANE_PX) # pick the plane IN THE VIEWPORT — before Shift+S - key("shift+s", 1.0) - key("Escape", 0.5) # entering sketch mode pops the offer; dismiss it + key("shift+s", 1.0) # sketch mode brings the reference planes up + click(*PLANE_PX, pause=0.8) # closes the offer if an earlier rung's plane popped it... + click(*PLANE_PX) # ...so this is the click that picks the plane IN THE VIEWPORT key("p", 0.6) # any sketch tool starts the session on that plane if try_call("sketch_describe") is not None: # NO Escape here. Every rung already opens with one to drop whatever tool the last @@ -511,14 +514,15 @@ def enter_sketch(timeout=180): # reported all eleven checks failed with "nothing opened" — the tools were arming # into an empty Feature-mode document. return - die("no sketch opened after plane click + Shift+S within " + die("no sketch opened after Shift+S + plane click within " f"{timeout}s — sketch_describe never answered on {A.sock} (trace {TRACE})") # tool key, the clicks that draw it, and one distinct value per queued field. The values are # deliberately nothing like the as-drawn size, so a committed prefill cannot coincide with them. -# Where the plane label sits in the viewport before a sketch is open. Same constant the gesture -# ladder uses; it is a label on the 3D view, not a widget, so it moves only if the camera does. +# Where the plane label sits in the viewport once sketch mode has brought the planes up. Same +# constant the gesture ladder uses; it is a label on the 3D view, not a widget, so it moves only +# if the camera does. PLANE_PX = (913, 359) # Every coordinate below stays inside 1000..1400 x 500..760 — the box check-gui-sketching.py's diff --git a/scripts/CAD/check-gui-sketching.py b/scripts/CAD/check-gui-sketching.py index d4ebd1f1ca..16a7713444 100644 --- a/scripts/CAD/check-gui-sketching.py +++ b/scripts/CAD/check-gui-sketching.py @@ -229,7 +229,9 @@ CHROME_DY = int(os.environ.get("ORCA_CAD_CHROME_DY", "26")) DESIGN_TAB = (128, 29 + CHROME_DY) -# Feature-tree rows, measured on the rig at 1920x1080: first row centre, then 23 px apart. +# Feature-tree rows, measured on the rig at 1920x1080: the list's first row is centred at y=215, +# then 23 px apart. That first row is the Origin, pinned above the features and never deleted, so +# the first FEATURE row is the second one: Delete on the Origin does nothing. # x=300, not the label: a second click ON the label opens the inline rename, and Delete then # edits the text instead of removing the feature. # @@ -237,7 +239,7 @@ DESIGN_TAB = (128, 29 + CHROME_DY) # unshifted click lands 26 px BELOW the first row -- just past its 23 px height -- so the row is # never selected, Delete does nothing, and reset_document spends 40 rounds on it before dying # with "could not empty the feature tree". That names the feature tree, which is not the fault. -TREE_ROW0 = (300, 215 + CHROME_DY) +TREE_ROW0 = (300, 215 + 23 + CHROME_DY) def go_design(): @@ -266,18 +268,20 @@ def reset_document(): def enter_sketch(tool_key, plane_px=(913, 359)): """Enter a sketch the way the design law says: pick the plane in the viewport, then the tool. - Shift+S enters sketch MODE and pops the offer; Escape dismisses it; the tool letter then - starts the session on the plane the click selected. All four steps are real input — nothing - here goes through the socket. + Shift+S enters sketch MODE, which brings the reference planes up; the click picks one; the + tool letter then starts the session on it. Entering with a plane an earlier rung chose pops + the offer at once, and a click outside a popup menu only closes it, so the plane is clicked + twice, apart. No Escape: with no offer up, Escape in an empty sketch leaves it. All of it is + real input — nothing here goes through the socket. """ leave_sketch() - click(*plane_px) key("shift+s", 0.8) - key("Escape", 0.4) # entering sketch mode pops the offer; dismiss it + click(*plane_px, pause=0.8) # closes the offer if an earlier rung's plane popped it... + click(*plane_px) # ...so this is the click that picks the plane key("p", 0.6) if try_call("sketch_describe") is None: shot("/shots/gl-enter-failed.png") - die("no sketch opened after plane click + Shift+S (see /shots/gl-enter-failed.png)") + die("no sketch opened after Shift+S + plane click (see /shots/gl-enter-failed.png)") calibrate_here() # THIS sketch's own camera map, on THIS sketch's own plane key(tool_key, 0.6) diff --git a/src/slic3r/GUI/CAD/DesignPanel.cpp b/src/slic3r/GUI/CAD/DesignPanel.cpp index 3e9c464e1f..4ff5f17642 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.cpp +++ b/src/slic3r/GUI/CAD/DesignPanel.cpp @@ -9,6 +9,7 @@ #include "slic3r/GUI/GUI_Utils.hpp" #include "slic3r/GUI/Widgets/Button.hpp" #include "libslic3r/CAD/GeometryEngine.hpp" // face_by_index for face-extrude gizmo anchor +#include "libslic3r/AppConfig.hpp" // design_* view preferences #include "libslic3r/TriangleMesh.hpp" // mesh import: STL/OBJ -> indexed_triangle_set #include "libslic3r/Format/OBJ.hpp" #include "libslic3r/Format/bbs_3mf.hpp" // put_other_changes: mark the project dirty outside the undo stack @@ -284,6 +285,10 @@ static std::string commit_icon(bool bodies) // The icons on each Feature tree and Bodies row (DesignRowList::Action::id). enum RowAction { RowEdit, RowMove, RowVisibility, RowDelete }; +// The Feature tree's first row is the Origin, pinned above the features: feature i is row i + 1. +static constexpr int kFeatureRow0 = 1; +// The feature a Feature tree row shows, or wxNOT_FOUND for the Origin row (and for no row). +static int feature_of_row(int row) { return row >= kFeatureRow0 ? row - kFeatureRow0 : wxNOT_FOUND; } // A row's eye shows the state the row is in; its tip names what a click does. static DesignRowList::Action eye_action(bool shown) @@ -291,6 +296,12 @@ static DesignRowList::Action eye_action(bool shown) return { RowVisibility, shown ? "design_eye" : "design_eye_off", shown ? _L("Hide") : _L("Show") }; } +// What the Origin row's eye and its menu item do. +static wxString origin_toggle_text(bool shown) +{ + return shown ? _L("Hide reference planes and axes") : _L("Show reference planes and axes"); +} + // Prepare outlines every numeric field (rounded, #4A4A51 on dark). wxSpinCtrlDouble is a // native GTK control that cannot draw that, and Orca's own SpinInput is int-only — it would // silently truncate a 2.5 mm radius. So the double spin keeps its arrows and validation and @@ -1099,8 +1110,8 @@ DesignPanel::DesignPanel(wxWindow* parent) // is editable now (CadBody::user_name), so the verb opens the editor there. if (const int b = tree_body_selection(); b >= 0) m_parts->begin_rename(b); - else if (const int sel = tree_selection(); sel != wxNOT_FOUND) - m_tree->begin_rename(sel); + else if (tree_selection() != wxNOT_FOUND) + m_tree->begin_rename(m_tree->selection()); else set_status(_L("Select a feature, or a body, first — then rename it")); // not an error }; @@ -3164,8 +3175,8 @@ DesignPanel::DesignPanel(wxWindow* parent) FromDIP(SidebarProps::ContentMargin())); tree_inner->Add(new wxStaticLine(m_tree_box), 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, FromDIP(SidebarProps::TitlebarMargin())); - // Sized to its rows, so a short history wastes no block, and scrolling past 9. - m_tree = new DesignRowList(m_tree_box, 9); + // Sized to its rows, so a short history wastes no block, and scrolling past 9 features. + m_tree = new DesignRowList(m_tree_box, 9 + kFeatureRow0); m_tree->SetBackgroundColour(dp_panel_bg()); tree_inner->Add(m_tree, 0, wxEXPAND | wxALL, 12); @@ -3176,8 +3187,9 @@ DesignPanel::DesignPanel(wxWindow* parent) // Picking a feature drops any body selection, so the two lists never both claim to be // "the target" — but ONLY when this tree has a selection. Each list notifies on every // change, so clicking a body row runs apply_body_row, whose m_tree->unselect() fires - // THIS handler, which would otherwise clear the body row the user had just clicked. - if (m_parts && tree_selection() != wxNOT_FOUND) m_parts->unselect(); + // THIS handler, which would otherwise clear the body row the user had just clicked. The + // Origin row counts: it is a selected row like any other. + if (m_parts && m_tree->selection() != wxNOT_FOUND) m_parts->unselect(); const int sel = tree_selection(); // Likewise a viewport pick, which would be drawn just like the feature's faces. Not while // a card is open: the card reads that pick. @@ -3199,12 +3211,17 @@ DesignPanel::DesignPanel(wxWindow* parent) }; // Double-click a row = Edit, the same gesture that re-opens a committed sketch on the canvas. - m_tree->on_activate = [this] { on_edit_feature(); }; + // The Origin row has nothing to edit. + m_tree->on_activate = [this] { + if (tree_selection() != wxNOT_FOUND) on_edit_feature(); + }; // The row's own Edit / Show-hide / Delete, on the row the click selected. The body list is // cleared here too, not left to on_select, which re-clicking the selected row does not run: - // a body row still selected would be what on_toggle_visibility acts on. - m_tree->on_action = [this](int, int id) { + // a body row still selected would be what on_toggle_visibility acts on. The Origin row's one + // icon is its eye. + m_tree->on_action = [this](int row, int id) { + if (feature_of_row(row) == wxNOT_FOUND) { toggle_origin(); return; } if (m_parts) m_parts->unselect(); switch (id) { case RowEdit: on_edit_feature(); break; @@ -3217,11 +3234,21 @@ DesignPanel::DesignPanel(wxWindow* parent) // double-click is Edit, and F2 is a function key nothing announces. A user who wants to name // a sketch tries the row, and the row answers. m_tree->on_menu = [this](int row, const wxPoint& screen) { + wxMenu menu; + // The Origin row is no feature: it cannot be renamed, edited, moved or deleted, and its + // menu holds exactly what its eye does. + const int feat = feature_of_row(row); + if (feat == wxNOT_FOUND) { + const int id_origin = wxWindow::NewControlId(); + menu.Append(id_origin, origin_toggle_text(m_show_origin)); + menu.Bind(wxEVT_MENU, [this](wxCommandEvent&) { toggle_origin(); }, id_origin); + m_tree->PopupMenu(&menu, m_tree->ScreenToClient(screen)); + return; + } // EVERYTHING A ROW CAN DO, in one place. The row's icons are the quick bar, but the menu // is the reference: the element you click answers with what applies to it, and a menu // grows without spending an icon nobody recognises. Split into what the row IS (name, // contents), where it SITS (order, visibility) and what removes it. - wxMenu menu; const int id_rename = wxWindow::NewControlId(); const int id_edit = wxWindow::NewControlId(); const int id_up = wxWindow::NewControlId(); @@ -3234,8 +3261,8 @@ DesignPanel::DesignPanel(wxWindow* parent) // Scale artwork acts on THIS feature's imported outline, so it belongs to the row and // is offered only where it means something. It used to hide inside the header's Move // button, which otherwise moved a body — two different subjects on one icon. - const bool art = row < int(m_doc.features.size()) && - !m_doc.features[row].imported_regions.empty(); + const bool art = feat < int(m_doc.features.size()) && + !m_doc.features[feat].imported_regions.empty(); if (art) menu.Append(id_art, _L("Scale artwork")); menu.AppendSeparator(); menu.Append(id_up, _L("Move up")); @@ -3250,7 +3277,7 @@ DesignPanel::DesignPanel(wxWindow* parent) menu.Bind(wxEVT_MENU, [this](wxCommandEvent&) { on_toggle_visibility(); }, id_vis); menu.Bind(wxEVT_MENU, [this](wxCommandEvent&) { on_delete_feature(); }, id_del); if (art) - menu.Bind(wxEVT_MENU, [this, row](wxCommandEvent&) { on_transform_imported(row); }, id_art); + menu.Bind(wxEVT_MENU, [this, feat](wxCommandEvent&) { on_transform_imported(feat); }, id_art); m_tree->PopupMenu(&menu, m_tree->ScreenToClient(screen)); }; @@ -3259,8 +3286,9 @@ DesignPanel::DesignPanel(wxWindow* parent) // renaming belongs on the row, not in a side-panel field. The list hands the name over after // its editor's events have finished, so rebuilding the rows here is safe. m_tree->on_rename = [this](int row, const wxString& name) { - if (row < 0 || row >= int(m_doc.features.size())) return; - m_doc.features[row].name = std::string(name.ToUTF8().data()); + const int feat = feature_of_row(row); + if (feat == wxNOT_FOUND || feat >= int(m_doc.features.size())) return; + m_doc.features[feat].name = std::string(name.ToUTF8().data()); refresh_tree(); // which syncs the recipe, so the save path persists the name }; @@ -4422,7 +4450,11 @@ DesignPanel::DesignPanel(wxWindow* parent) assert(dead.empty()); // debug builds stop here; release ships the log line } + m_show_origin = wxGetApp().app_config->get_bool("design_show_origin"); set_ui_mode(UiMode::Feature); + // The Origin row from the first paint: nothing else builds the rows before the first edit, + // project load or theme switch. + refresh_tree(); build_phase("wiring"); BOOST_LOG_TRIVIAL(info) << "Design tab build: total " << build_clock.Time() << " ms"; } @@ -4516,9 +4548,9 @@ void DesignPanel::set_ui_mode(UiMode m) m_form->FitInside(); } update_action_bar(); // Sketch/Constrain modes show the unified ✓/✗; Feature idle hides it - // The origin planes follow the mode: entering Sketch offers them even when a body exists, - // leaving it takes them back. Without this they would only refresh on the next tree - // rebuild, which is not an event that happens when you merely press Sketch. + // The reference planes follow the mode (update_reference_planes). Without this they would + // only refresh on the next tree rebuild, which is not an event that happens when you merely + // press Sketch. update_reference_planes(); // Say where you are, in words, across the top of the viewport. @@ -7256,7 +7288,7 @@ wxString DesignPanel::idle_hint() const { return m_doc.features.empty() ? _L("Nothing yet — import a STEP or a mesh from the toolbar,\n" - "or click a reference plane and right-click it to start a sketch.") + "or press Sketch and click a reference plane to start a sketch.") : _L("No solid yet — select a sketch and right-click it to Extrude."); } @@ -7284,7 +7316,7 @@ void DesignPanel::on_tab_shown() if (m_viewport) m_viewport->refresh_bed(); hydrate_from_model(); - update_reference_planes(); // entering the Design tab: show the XY/XZ/YZ planes if no object yet + update_reference_planes(); // entering the Design tab if (show_clock.Time() > 100) // a slow first show is what users report; the usual one is not news BOOST_LOG_TRIVIAL(info) << "Design tab shown: bed, project recipe and planes in " << show_clock.Time() << " ms"; m_laid_out = true; @@ -7515,15 +7547,19 @@ void DesignPanel::refresh_tree() // Preserve the selected row across the rebuild — set_rows() drops the selection, which made // every edit/add feel like it "lost" the selection (and broke Edit/Move/Delete on the - // just-touched feature). - const int keep = tree_selection(); + // just-touched feature). By row, so a selected Origin row stays selected too. + const int keep = m_tree->selection(); // Datum/reference planes carry no solid; feed them to the viewport so they render as // translucent rectangles (otherwise a Plane feature is invisible in the canvas). refresh_datum_planes(); - update_reference_planes(); // body added/removed -> show/hide the XY/XZ/YZ origin planes + update_reference_planes(); std::vector rows; - rows.reserve(m_doc.features.size()); + rows.reserve(m_doc.features.size() + kFeatureRow0); + // Always first and never removable: the reference planes' own switch, and nothing else. + DesignRowList::Action origin_eye = eye_action(m_show_origin); + origin_eye.tip = origin_toggle_text(m_show_origin); + rows.push_back({ "design_plane", _L("Origin"), {}, dp_item_text(), { origin_eye } }); for (size_t fi = 0; fi < m_doc.features.size(); ++fi) { const CadFeature& f = m_doc.features[fi]; DesignRowList::Row row; @@ -7618,7 +7654,7 @@ int DesignPanel::tree_body_selection() const // if a row was selected. bool DesignPanel::deselect_rows() { - const bool any = (m_tree && tree_selection() != wxNOT_FOUND) || tree_body_selection() >= 0; + const bool any = (m_tree && m_tree->selection() != wxNOT_FOUND) || tree_body_selection() >= 0; if (m_tree) m_tree->unselect(); if (m_parts) m_parts->unselect(); return any; @@ -7933,13 +7969,13 @@ bool DesignPanel::place_on_face() int DesignPanel::tree_selection() const { - return m_tree->selection(); + return feature_of_row(m_tree->selection()); } -void DesignPanel::set_tree_selection(int row) +void DesignPanel::set_tree_selection(int feature) { - if (row >= 0 && row < int(m_tree->GetItemCount())) - m_tree->select(row); + if (feature >= 0 && feature + kFeatureRow0 < int(m_tree->GetItemCount())) + m_tree->select(feature + kFeatureRow0); } // The selection (the solid pick, the hit face and the committed-loop pick) names bodies, faces and @@ -11042,10 +11078,9 @@ void DesignPanel::update_rib_gizmo() m_rib_thickness ? m_rib_thickness->GetValue() : 0.0); } -// Onshape default planes: the XY/XZ/YZ reference planes are persistent, transparent, labelled, and -// larger than the bed — shown as the FALLBACK when there is no object yet. When the Plane tool is -// open they additionally surface existing datums so a base can be picked. Single authority for the -// reference-plane overlay (set/clear_base_pick). +// The XY/XZ/YZ reference planes and their axes. Single authority for the overlay +// (set/clear_base_pick): the Plane tool offers them with the datums as Offset bases; otherwise they +// are up while the Feature tree's Origin row shows them, and while a sketch plane is being chosen. void DesignPanel::update_reference_planes() { if (!m_viewport) return; @@ -11072,26 +11107,23 @@ void DesignPanel::update_reference_planes() } return; } - // Fallback (Onshape default planes): show the 3 reference planes while there is no SOLID body - // yet — so they persist through the 2D-sketch phase and reappear after a sketch is confirmed - // (a sketch creates no body). They no longer block selection: clicking existing geometry wins, - // a base-plane pick only fires on a click that hit nothing else (see on_mouse fall-through). - // Available while there is no solid yet OR while the user is actually choosing a sketch - // plane. The second half fixes a dead end: delete a sketch on a document that still has a - // body, press Sketch, and act_sketch says "click a face or a reference plane" — with the - // reference planes already taken away, because a body existed. The instruction was - // impossible to follow and there was no way to start a sketch at all short of finding a - // face to click. - // - // Not simply always-on: m_dbp_active both RENDERS and picks, so three translucent planes - // would otherwise float over every finished model. Tying them to Sketch mode shows them - // exactly when they are the thing being chosen, and hides them again on Finish. - if (m_doc.bodies.empty() || m_ui_mode == UiMode::Sketch) + // Sketch mode is where a plane is the thing being picked (act_sketch asks for "a face or a + // reference plane"); a live session draws none (DesignSketchTool::draws_reference_axes). + // They never block selection: clicking existing geometry wins, and a base-plane pick only + // fires on a click that hit nothing else (see on_mouse fall-through). + if (m_show_origin || m_ui_mode == UiMode::Sketch) m_viewport->set_base_pick(std::move(bp), std::move(bi), std::move(bl)); else m_viewport->clear_base_pick(); } +void DesignPanel::toggle_origin() +{ + m_show_origin = !m_show_origin; + wxGetApp().app_config->set_bool("design_show_origin", m_show_origin); + refresh_tree(); // the Origin row's eye, and update_reference_planes(), which repaints +} + TriangleMesh DesignPanel::ghost_from_bodies(const std::vector& per_body) const { TriangleMesh out; @@ -11693,7 +11725,7 @@ void DesignPanel::close_tool() m_viewport->clear_datum_gizmo(); m_viewport->set_operand_bodies(-1, -1); m_viewport->set_highlight_sketches({}); - update_reference_planes(); // back to no-tool: show the origin planes if there is no object yet + update_reference_planes(); // back to no-tool update_cards_frame(); m_form->Layout(); m_form->FitInside(); update_action_bar(); // no feature tool active -> hide the bar (unless a mode keeps it) diff --git a/src/slic3r/GUI/CAD/DesignPanel.hpp b/src/slic3r/GUI/CAD/DesignPanel.hpp index 4b08afa384..ed8ccaf9f2 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.hpp +++ b/src/slic3r/GUI/CAD/DesignPanel.hpp @@ -383,7 +383,8 @@ private: void update_rib_gizmo(); // in-plane slab footprint + thickness handles (Rib card) void refresh_datum_planes(); // push resolved datum frames + per-plane u/v extents to viewport void refresh_mate_connectors(); // push connector frames so verse + polarity are visible - void update_reference_planes(); // persistent XY/XZ/YZ reference planes (fallback when no object) + void update_reference_planes(); // the XY/XZ/YZ planes + axes: Origin row, Sketch mode, Plane tool + void toggle_origin(); // the Origin row's eye: keep the reference planes up, or not CadDocument m_doc; @@ -573,6 +574,10 @@ private: // m_ref_plane is always a VALID plane, so it cannot itself distinguish "the user chose XY" // from "nobody has chosen anything yet". This does. bool m_plane_picked{false}; + // The Feature tree's Origin row: keeps the reference planes and their axes up + // (update_reference_planes). A view preference (AppConfig "design_show_origin"), not part of + // the recipe. + bool m_show_origin{false}; ComboBox* m_shape{nullptr}; ComboBox* m_mode{nullptr}; wxSpinCtrlDouble* m_width{nullptr}; @@ -913,9 +918,9 @@ private: std::function m_value_cont; // deferred apply, run on Confirm std::function m_value_cancel; // optional action when the card is cancelled - // Feature tree: one row per feature, in feature order, with a per-type icon and the row's - // own Edit / Show-hide / Delete icons. Callers use row indices via - // tree_selection()/set_tree_selection(); refresh_tree() rebuilds the rows. + // Feature tree: the Origin row, then one row per feature, in feature order, with a per-type + // icon and the row's own Edit / Show-hide / Delete icons. Feature i is row i + 1; callers use + // feature indices via tree_selection()/set_tree_selection(); refresh_tree() rebuilds the rows. DesignRowList* m_tree{nullptr}; // The faces the selected feature row made (CadDocument::faces_made_by), drawn as selected. // Finding them replays the history, so they are kept per row and topology generation. @@ -969,10 +974,10 @@ private: void arm_transform_gizmo(); // arm the move gizmo on the Transform card's body (add mode only) void on_set_body_color(); // Color tool: pick a per-body display colour override void on_boolean_tool(); // Boolean (combine bodies): needs two solids, then opens the tool - int tree_selection() const; // selected feature row, or wxNOT_FOUND + int tree_selection() const; // selected feature's index, or wxNOT_FOUND (none, or the Origin row) int tree_body_selection() const; // selected Parts-list body index, or -1 void refresh_parts(); // rebuild the Bodies list under the feature tree - void set_tree_selection(int row); + void set_tree_selection(int feature); static const char* tree_icon_for(CadFeatureType t); wxStaticText* m_status{nullptr};