diff --git a/docs/HLSD/design-tab.md b/docs/HLSD/design-tab.md index 58bab4bfcb..f7d48e0ee6 100644 --- a/docs/HLSD/design-tab.md +++ b/docs/HLSD/design-tab.md @@ -175,11 +175,11 @@ handle or a press that draws takes it first, as a gizmo does in Prepare; a whole with a rectangle on plain left-drag only while no camera action is assigned to the left button, and with Shift+left-drag otherwise — Prepare's own rectangle selection. -Entering a sketch changes three things at once so the mode is legible: a banner above the +Entering a sketch changes two things at once so the mode is legible: a banner above the canvas (a sibling of the canvas, not a child over it — on GTK a child window over a -`wxGLCanvas` is a native window and does not reliably stack over GL), the printer bed muted so -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. +`wxGLCanvas` is a native window and does not reliably stack over GL), and `N` to look normal to +the plane. The printer bed stays: there is no sketch grid, so the plate grid is the only ground +reference a sketch has. Code that changes either belongs with a change to this section. Sketch mode is never entered without a plane under it, so the banner, the sketch keys and the sketch offer always have a session to act on. Sketch on a picked flat face or reference plane opens @@ -200,6 +200,10 @@ bases, and nothing for the other methods, where a click on a plane would rewrite references. The `P` and `A` keys are a separate, unpickable view helper and do not follow the Origin row. +The Bed row, pinned under the Origin row, is the printer bed's switch in the same way: it draws or +hides the bed and its plate grid in every mode, and `Ctrl+Shift+B` flips it from the keyboard. It +is a view preference in AppConfig too, and the bed is shown until it is turned off. + ## Rendering the bodies The tab draws its bodies through the same `GLCanvas3D` object path as Prepare, so how they look @@ -273,7 +277,7 @@ The tab is a page of Orca's main window and answers to the same settings as Prep 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 only Show/hide on - the Origin row above the features — and the eye shows whether that row is hidden. + the Origin and Bed rows 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/resources/images/design_bed.svg b/resources/images/design_bed.svg new file mode 100644 index 0000000000..a9441e7b5d --- /dev/null +++ b/resources/images/design_bed.svg @@ -0,0 +1 @@ + diff --git a/scripts/CAD/check-gui-click-edit.py b/scripts/CAD/check-gui-click-edit.py index b0ee63dee9..5eb9b3c4f2 100755 --- a/scripts/CAD/check-gui-click-edit.py +++ b/scripts/CAD/check-gui-click-edit.py @@ -147,6 +147,7 @@ def seed_datadir(datadir): # ~90% case expects. A ladder whose result depends on the developer's own preferences is not # a gate. app["auto_close_sketch_loops"] = True + app["design_show_bed"] = True # SILENCE THE NETWORK PLUGIN PROMPT. Without this, GUI_App::post_init() re-raises "Bambu # Network Plug-in Required" from an IDLE event — after any modal sweep this driver does at # startup — and ShowModal() then runs a nested event loop. The app is alive, its window is diff --git a/scripts/CAD/check-gui-sketching.py b/scripts/CAD/check-gui-sketching.py index 9d8729c49f..b3c4514499 100644 --- a/scripts/CAD/check-gui-sketching.py +++ b/scripts/CAD/check-gui-sketching.py @@ -229,9 +229,18 @@ CHROME_DY = int(os.environ.get("ORCA_CAD_CHROME_DY", "26")) DESIGN_TAB = (128, 29 + CHROME_DY) +# The ribbon's document group lost its Bed checkbox and label when the bed toggle moved to the +# Feature tree, and every group to its right moved left by their width: wxLEFT 6 + the 18 px +# CheckBox + wxLEFT 4 + the "Bed" label (~24 px) + wxRIGHT 4, about 56 px. The x of +# CONSTRUCTION_CHECKBOX, CON_BTN and CON_BTN_SKETCH below is the one measured before that, plus +# this. DERIVED, NOT MEASURED: re-measure on the rig and fold it in. CONFIRM_BTN sits after the +# ribbon's stretch spacer and did not move. +RIBBON_DX = -56 + # 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. +# then 23 px apart. The first two rows are the Origin and the Bed, pinned above the features and +# never deleted, so the first FEATURE row is the third one. Delete and double-click do nothing on +# a pinned row, and reset_document and the reopening double-click both need a feature. # 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. # @@ -239,7 +248,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 + 23 + CHROME_DY) +TREE_ROW0 = (300, 215 + 2 * 23 + CHROME_DY) def go_design(): @@ -742,7 +751,7 @@ def corner_pair(ents): die("no adjacent pair in what should be a rectangle") -CONSTRUCTION_CHECKBOX = (419, 75 + CHROME_DY) +CONSTRUCTION_CHECKBOX = (419 + RIBBON_DX, 75 + CHROME_DY) def draw_line(x0, y0, x1, y1, length, angle): @@ -871,7 +880,7 @@ def rung_extend(): # because the ladder only ever clicks "perpendicular" (3) and "equal" (5), both of which sit # before the first insertion. The next rung to use "tangent" would have clicked "collinear". CON_BTN_Y = 76 + CHROME_DY -CON_BTN = {n: (449 + 42 * i, CON_BTN_Y) for i, n in enumerate( +CON_BTN = {n: (449 + RIBBON_DX + 42 * i, CON_BTN_Y) for i, n in enumerate( ["horizontal", "vertical", "parallel", "perpendicular", "coincident", "equal", "equal_radius", "collinear", "concentric", "tangent", "midpoint", "symmetric", "sym_v", "sym_h", "angle", "radius", "diameter", "fix", "dist_x", "dist_y"])} @@ -885,7 +894,7 @@ CON_BTN = {n: (449 + 42 * i, CON_BTN_Y) for i, n in enumerate( # which already carries CHROME_DY), but the X start depends on how wide the sketch toolbar to the # left of this group renders, and this fork keeps mainline's top row. Re-measure before trusting # D11 here: screenshot in sketch mode and detect the icon columns, do not derive it by offset. -CON_BTN_SKETCH = {n: (677 + 42 * i, CON_BTN_Y) for i, n in enumerate( +CON_BTN_SKETCH = {n: (677 + RIBBON_DX + 42 * i, CON_BTN_Y) for i, n in enumerate( ["horizontal", "vertical", "parallel", "perpendicular", "coincident", "equal", "equal_radius", "collinear", "concentric", "tangent", "midpoint", "symmetric", "sym_v", "sym_h", "angle", "radius", "diameter", "fix", "dist_x", "dist_y"])} diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 663072d866..31cc17b97d 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -367,6 +367,10 @@ void AppConfig::set_defaults() // restores the conventional CAD representation for users who expect it (x0kd). if (get("design_connector_face_glyph").empty()) set_bool("design_connector_face_glyph", true); + + // Design tab: draw the printer bed and its plate grid (the Feature tree's Bed row). + if (get("design_show_bed").empty()) + set_bool("design_show_bed", true); #endif //#ifdef SUPPORT_SHOW_HINTS diff --git a/src/slic3r/GUI/CAD/DesignPanel.cpp b/src/slic3r/GUI/CAD/DesignPanel.cpp index ac9b5d59d6..964e1f23ca 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.cpp +++ b/src/slic3r/GUI/CAD/DesignPanel.cpp @@ -285,9 +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). +// The Feature tree's first two rows are the Origin and the Bed, pinned above the features: +// feature i is row i + kFeatureRow0. +static constexpr int kOriginRow = 0, kBedRow = 1, kFeatureRow0 = 2; +// The feature a Feature tree row shows, or wxNOT_FOUND for a pinned 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. @@ -302,6 +303,13 @@ static wxString origin_toggle_text(bool shown) return shown ? _L("Hide reference planes and axes") : _L("Show reference planes and axes"); } +// What the Bed row's eye and its menu item do. +static wxString bed_toggle_text(bool shown) +{ + return shown ? _L("Hide the printer bed and its plate grid") + : _L("Show the printer bed and its plate grid"); +} + // What Sketch asks for while it waits for a plane (DesignPanel::start_sketch). static wxString sketch_plane_prompt() { @@ -611,17 +619,14 @@ DesignPanel::DesignPanel(wxWindow* parent) set_status(_L("Axonometric view, fitted")); }; - // Commit to Plate and the bed toggle were mouse-only: a toolbar button and a checkbox with - // no accelerator between them, so neither could be reached from the keyboard at all, nor by - // anything driving the keyboard. Ctrl+Shift+P is Plate, Ctrl+Shift+B is Bed; neither + // Commit to Plate and the bed toggle are mouse targets — a toolbar button and the Feature + // tree's Bed row — so without an accelerator neither could be reached from the keyboard, nor + // by anything driving the keyboard. Ctrl+Shift+P is Plate, Ctrl+Shift+B is Bed; neither // collides with Orca's own Ctrl+Shift+S (Save as) or Ctrl+Shift+G (Print plate). m_keys_feature['P' | SC_SHIFT | SC_CTRL] = [this] { on_commit(); }; m_keys_feature['B' | SC_SHIFT | SC_CTRL] = [this] { - if (!m_show_bed) return; - const bool show = !m_show_bed->GetValue(); - m_show_bed->SetValue(show); - if (m_viewport) m_viewport->set_show_bed(show); - set_status(show ? _L("Bed shown") : _L("Bed hidden")); + toggle_bed(); + set_status(m_show_bed ? _L("Bed shown") : _L("Bed hidden")); }; // Flyout rows show the design_* glyphs as they are: drawn in Orca's icon grey (#949494), which @@ -1518,10 +1523,10 @@ DesignPanel::DesignPanel(wxWindow* parent) } m_viewport->set_sketch_construction(m_construction->GetValue()); }); // STAYS on the bar. Construction is not a tool, it is a persistent MODE — the same kind - // of thing as the Bed checkbox — and the sketch bar is already shown only in Sketch mode, - // so it appears exactly while it can apply. Hiding it left Q and the offer's Construction - // row still toggling a checkbox nobody could see: you could not tell whether the next - // line would be construction geometry. A stateful toggle has to show its state. + // of thing as the Feature tree's Bed row — and the sketch bar is already shown only in + // Sketch mode, so it appears exactly while it can apply. Hiding it left Q and the offer's + // Construction row still toggling a checkbox nobody could see: you could not tell whether + // the next line would be construction geometry. A stateful toggle has to show its state. sadd_bar(m_construction); add_sep(m_tb_sketch); auto* b_del = icon_btn("design_delete", _L("Delete selected")); @@ -1629,23 +1634,6 @@ DesignPanel::DesignPanel(wxWindow* parent) b_export->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_export_step(); }); add_doc(b_export); - // View option, not a document action: hide the printer bed to model without it. Lives in - // this row because it must stay reachable with no tool open — a card would come and go. - m_show_bed = new CheckBox(m_toolbar); - m_show_bed->SetValue(true); // bed visible by default, as the tab opens today - m_show_bed->SetToolTip(_L("Show the printer bed and its plate grid")); - // wxEVT_TOGGLEBUTTON, NOT wxEVT_CHECKBOX: Orca's CheckBox derives from - // wxBitmapToggleButton (Widgets/CheckBox.hpp), so a wxEVT_CHECKBOX handler never fires. - // Read the control rather than the event so the state cannot disagree with the glyph. - m_show_bed->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& e) { - if (m_viewport) m_viewport->set_show_bed(m_show_bed->GetValue()); - e.Skip(); - }); - auto* bed_lbl = new wxStaticText(m_toolbar, wxID_ANY, _L("Bed")); - bed_lbl->SetForegroundColour(dp_sec_text()); - m_tb_doc->Add(m_show_bed, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 6); - m_tb_doc->Add(bed_lbl, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 4); - // These act on bodies / the view, so they ride in the feature group, in the slots // the user assigned them (9, 11bis, 16). auto* b_place = doc_btn("toolbar_flatten", _L("Place on Face (F) — lay the picked face on the bed"), @@ -3155,7 +3143,7 @@ DesignPanel::DesignPanel(wxWindow* parent) // "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. The - // Origin row counts: it is a selected row like any other. + // pinned Origin and Bed rows count: they are selected rows 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 @@ -3178,17 +3166,18 @@ DesignPanel::DesignPanel(wxWindow* parent) }; // Double-click a row = Edit, the same gesture that re-opens a committed sketch on the canvas. - // The Origin row has nothing to edit. + // The pinned rows have 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. The Origin row's one + // a body row still selected would be what on_toggle_visibility acts on. A pinned 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 (row == kOriginRow) { toggle_origin(); return; } + if (row == kBedRow) { toggle_bed(); return; } if (m_parts) m_parts->unselect(); switch (id) { case RowEdit: on_edit_feature(); break; @@ -3202,13 +3191,14 @@ DesignPanel::DesignPanel(wxWindow* parent) // 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 + // A pinned 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); + const bool bed = (row == kBedRow); + const int id_toggle = wxWindow::NewControlId(); + menu.Append(id_toggle, bed ? bed_toggle_text(m_show_bed) : origin_toggle_text(m_show_origin)); + menu.Bind(wxEVT_MENU, [this, bed](wxCommandEvent&) { bed ? toggle_bed() : toggle_origin(); }, id_toggle); m_tree->PopupMenu(&menu, m_tree->ScreenToClient(screen)); return; } @@ -4431,8 +4421,10 @@ DesignPanel::DesignPanel(wxWindow* parent) } m_show_origin = wxGetApp().app_config->get_bool("design_show_origin"); + m_show_bed = wxGetApp().app_config->get_bool("design_show_bed"); + if (m_viewport) m_viewport->set_show_bed(m_show_bed); set_ui_mode(UiMode::Feature); - // The Origin row from the first paint: nothing else builds the rows before the first edit, + // The pinned rows from the first paint: nothing else builds the rows before the first edit, // project load or theme switch. refresh_tree(); build_phase("wiring"); @@ -4542,8 +4534,8 @@ void DesignPanel::set_ui_mode(UiMode m) // Seen on the rig: pick XY, arm Line, and the viewport is an empty grey field — no bed, no // grid, no origin, nothing to judge a length or a direction against. The plate grid was // carrying the ground reference for the whole tab. The banner already says where you are; - // taking the floor away as well only made the sketch harder to draw. The Bed checkbox is the - // one thing that governs the bed, in every mode. + // taking the floor away as well only made the sketch harder to draw. The Feature tree's Bed + // row is the one thing that governs the bed, in every mode. if (m_sketch_banner != nullptr) { const bool sketching = (m == UiMode::Sketch); if (sketching && m_sketch_banner_txt != nullptr) @@ -7532,7 +7524,7 @@ 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). By row, so a selected Origin row stays selected too. + // just-touched feature). By row, so a selected pinned 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 @@ -7541,10 +7533,15 @@ void DesignPanel::refresh_tree() update_reference_planes(); std::vector rows; 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 } }); + // Always first, in kOriginRow / kBedRow order, and never removable: the reference planes' and + // the printer bed's own switches, and nothing else. + auto add_pinned = [&rows](const char* icon, const wxString& label, bool shown, const wxString& tip) { + DesignRowList::Action eye = eye_action(shown); + eye.tip = tip; + rows.push_back({ icon, label, {}, dp_item_text(), { eye } }); + }; + add_pinned("design_plane", _L("Origin"), m_show_origin, origin_toggle_text(m_show_origin)); + add_pinned("design_bed", _L("Bed"), m_show_bed, bed_toggle_text(m_show_bed)); for (size_t fi = 0; fi < m_doc.features.size(); ++fi) { const CadFeature& f = m_doc.features[fi]; DesignRowList::Row row; @@ -11128,6 +11125,14 @@ void DesignPanel::toggle_origin() refresh_tree(); // the Origin row's eye, and update_reference_planes(), which repaints } +void DesignPanel::toggle_bed() +{ + m_show_bed = !m_show_bed; + wxGetApp().app_config->set_bool("design_show_bed", m_show_bed); + if (m_viewport) m_viewport->set_show_bed(m_show_bed); // repaints + refresh_tree(); // the Bed row's eye +} + void DesignPanel::start_sketch() { if (m_ui_mode != UiMode::Feature) return; // already sketching or constraining diff --git a/src/slic3r/GUI/CAD/DesignPanel.hpp b/src/slic3r/GUI/CAD/DesignPanel.hpp index 5f31d0fbeb..013de06fc8 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.hpp +++ b/src/slic3r/GUI/CAD/DesignPanel.hpp @@ -385,6 +385,7 @@ private: void refresh_mate_connectors(); // push connector frames so verse + polarity are visible void update_reference_planes(); // the XY/XZ/YZ planes + axes: Origin row, plane choice, Plane tool void toggle_origin(); // the Origin row's eye: keep the reference planes up, or not + void toggle_bed(); // the Bed row's eye (and Ctrl+Shift+B): draw the printer bed, or not // Sketch: open a session on the picked face or plane, or with none picked, put the planes up // and wait for one (m_choosing_sketch_plane). void start_sketch(); @@ -420,7 +421,6 @@ private: ScalableButton* m_commit_btn{nullptr}; // main face: runs the current commit mode DropDown* m_commit_drop{nullptr}; // commit-mode choices, owned via m_flyout_keepalive wxSizer* m_tb_doc{nullptr}; // toolbar document/view actions (new, commit, export, section, place) - CheckBox* m_show_bed{nullptr}; // view option: draw the printer bed + plate grid, or not wxSizer* m_box_move{nullptr}; // Move/Rotate numeric options (distance, axis, angle) wxSizer* m_box_sketch{nullptr}; wxSizer* m_box_extrude{nullptr}; @@ -589,6 +589,9 @@ private: // (update_reference_planes). A view preference (AppConfig "design_show_origin"), not part of // the recipe. bool m_show_origin{false}; + // The Feature tree's Bed row: draws the printer bed and its plate grid. A view preference + // (AppConfig "design_show_bed"), not part of the recipe. + bool m_show_bed{true}; ComboBox* m_shape{nullptr}; ComboBox* m_mode{nullptr}; wxSpinCtrlDouble* m_width{nullptr}; @@ -929,9 +932,10 @@ 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: 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. + // Feature tree: the pinned Origin and Bed rows, 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 + 2; 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. @@ -986,7 +990,7 @@ 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's index, or wxNOT_FOUND (none, or the Origin row) + int tree_selection() const; // selected feature's index, or wxNOT_FOUND (none, or a pinned 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 feature);