diff --git a/src/slic3r/GUI/CAD/DesignPanel.cpp b/src/slic3r/GUI/CAD/DesignPanel.cpp index 33326fd5ca..6d9454a829 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.cpp +++ b/src/slic3r/GUI/CAD/DesignPanel.cpp @@ -997,17 +997,7 @@ DesignPanel::DesignPanel(wxWindow* parent) }); auto* b_boolean = icon_btn("design_boolean", _L("Boolean (combine bodies)")); - std::function act_boolean = [this] { - // A body-body boolean needs at least two solids to combine. - if (m_doc.bodies.size() < 2) { - m_status->SetForegroundColour(wxColour(235, 110, 110)); - set_status(_L("Boolean needs two bodies — create or import a second solid")); - m_status->Refresh(); - return; - } - populate_body_choices(); - open_tool(Tool::Boolean); - }; + std::function act_boolean = [this] { on_boolean_tool(); }; b_boolean->Bind(wxEVT_BUTTON, [act_boolean](wxCommandEvent&) { act_boolean(); }); m_keys_feature[SHIFT('B')] = act_boolean; fadd("boolean", b_boolean); @@ -1820,7 +1810,11 @@ DesignPanel::DesignPanel(wxWindow* parent) { auto* eform = two_col_form(); - m_distance = make_spin(m_cards, 10); + // A negative distance extrudes the other way — Onshape behaviour, where a negative + // depth IS the flip. The default 0.1 floor made that impossible to type, so the only + // way inward was the Flip box, and a user who typed "-5" silently got +0.1 instead. + // Flip and a negative distance double-negate, which is what setting both asked for. + m_distance = make_spin(m_cards, 10, -1000.0, 1000.0); eform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Extrude dist")), 0, wxALIGN_CENTER_VERTICAL); eform->Add(spin_frame(m_distance), 0, wxEXPAND); @@ -3288,7 +3282,8 @@ DesignPanel::DesignPanel(wxWindow* parent) // header, where it had to guess whether its subject was a body or a feature; Show/hide, // Delete and Colour are deliberate COPIES of feature-tree actions, because a body row is a // different subject and a user working in this list should not have to travel to another - // card to hide or recolour what they have selected. Each one already resolves the body row + // card to hide or recolour what they have selected. Boolean is not a copy — it is the one + // body-body operation, gated through on_boolean_tool. Each one already resolves the body row // itself (on_toggle_visibility, on_delete_body, on_set_body_color), so nothing here decides // policy — the card only gives them a home next to the rows they act on. { @@ -3308,6 +3303,10 @@ DesignPanel::DesignPanel(wxWindow* parent) m_status->Refresh(); } }); + // Boolean lives here as well as on the toolbar: combining two bodies is a body action, + // and a user working in the body list should not have to leave it to find this. + auto* bbool = body_btn("design_boolean", _L("Boolean — join, subtract or intersect with another body")); + bbool->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_boolean_tool(); }); auto* bvis = body_btn("design_eye", _L("Show / hide")); bvis->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { on_toggle_visibility(); }); auto* bdel = body_btn("design_delete", _L("Delete")); @@ -3317,6 +3316,7 @@ DesignPanel::DesignPanel(wxWindow* parent) const int bgap = FromDIP(SidebarProps::ElementSpacing()); m_parts_hdr->AddStretchSpacer(1); m_parts_hdr->Add(bmove, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, bgap); + m_parts_hdr->Add(bbool, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, bgap); m_parts_hdr->Add(bvis, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, bgap); m_parts_hdr->Add(bdel, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, bgap); m_parts_hdr->Add(bcol, 0, wxALIGN_CENTER_VERTICAL); @@ -7340,6 +7340,21 @@ void DesignPanel::feed_bodies() m_viewport->set_bodies(m_disp_body_meshes, m_body_visible); } +// Boolean (combine bodies) — one gate for every door onto the tool. A body-body operation +// needs two solids, and saying so in one place means the toolbar button, its Shift+B binding +// and the Bodies card cannot drift apart on what "available" means. +void DesignPanel::on_boolean_tool() +{ + if (m_doc.bodies.size() < 2) { + m_status->SetForegroundColour(wxColour(235, 110, 110)); + set_status(_L("Boolean needs two bodies — create or import a second solid")); + m_status->Refresh(); + return; + } + populate_body_choices(); + open_tool(Tool::Boolean); +} + void DesignPanel::on_move_body() { const int b = m_sel_solid_body; diff --git a/src/slic3r/GUI/CAD/DesignPanel.hpp b/src/slic3r/GUI/CAD/DesignPanel.hpp index da812f3c2e..88b445534d 100644 --- a/src/slic3r/GUI/CAD/DesignPanel.hpp +++ b/src/slic3r/GUI/CAD/DesignPanel.hpp @@ -870,6 +870,7 @@ private: void on_move_body(); // start the move gizmo on the selected body 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_body_selection() const; // selected Parts-list body index, or -1 void refresh_parts(); // rebuild the Bodies list under the feature tree