diff --git a/resources/images/toolbar_meshboolean.svg b/resources/images/toolbar_meshboolean.svg
new file mode 100644
index 000000000..ecfc8349d
--- /dev/null
+++ b/resources/images/toolbar_meshboolean.svg
@@ -0,0 +1,13 @@
+
diff --git a/resources/images/toolbar_meshboolean_dark.svg b/resources/images/toolbar_meshboolean_dark.svg
new file mode 100644
index 000000000..7643f7173
--- /dev/null
+++ b/resources/images/toolbar_meshboolean_dark.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 5e66eed39..6e54b56f2 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -137,6 +137,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoSeam.hpp
GUI/Gizmos/GLGizmoText.cpp
GUI/Gizmos/GLGizmoText.hpp
+ GUI/Gizmos/GLGizmoMeshBoolean.cpp
+ GUI/Gizmos/GLGizmoMeshBoolean.hpp
GUI/GLSelectionRectangle.cpp
GUI/GLSelectionRectangle.hpp
GUI/Gizmos/GizmoObjectManipulation.cpp
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 86bce64de..630454d53 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -4280,7 +4280,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
wxGetApp().plater()->select_plate_by_hover_id(hover_idx);
//wxGetApp().plater()->get_partplate_list().select_plate_view();
//deselect all the objects
- if (m_hover_volume_idxs.empty())
+ if (m_gizmos.get_current_type() != GLGizmosManager::MeshBoolean && m_hover_volume_idxs.empty())
deselect_all();
}
else if (evt.RightUp() && !is_layers_editing_enabled()) {
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp
new file mode 100644
index 000000000..db50e1f82
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.cpp
@@ -0,0 +1,431 @@
+#include "GLGizmoMeshBoolean.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/ImGuiWrapper.hpp"
+#include "slic3r/GUI/GUI.hpp"
+#include "libslic3r/MeshBoolean.hpp"
+#include "slic3r/GUI/GUI_ObjectList.hpp"
+#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/Camera.hpp"
+#ifndef IMGUI_DEFINE_MATH_OPERATORS
+#define IMGUI_DEFINE_MATH_OPERATORS
+#endif
+#include
+namespace Slic3r {
+namespace GUI {
+
+GLGizmoMeshBoolean::GLGizmoMeshBoolean(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
+ : GLGizmoBase(parent, icon_filename, sprite_id)
+{
+}
+
+GLGizmoMeshBoolean::~GLGizmoMeshBoolean()
+{
+}
+
+bool GLGizmoMeshBoolean::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
+{
+ if (action == SLAGizmoEventType::LeftDown) {
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ if (mo == nullptr)
+ return true;
+ const ModelInstance* mi = mo->instances[m_parent.get_selection().get_instance_idx()];
+ std::vector trafo_matrices;
+ for (const ModelVolume* mv : mo->volumes) {
+ //if (mv->is_model_part()) {
+ trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix());
+ //}
+ }
+
+ const Camera& camera = wxGetApp().plater()->get_camera();
+ Vec3f normal = Vec3f::Zero();
+ Vec3f hit = Vec3f::Zero();
+ size_t facet = 0;
+ Vec3f closest_hit = Vec3f::Zero();
+ Vec3f closest_normal = Vec3f::Zero();
+ double closest_hit_squared_distance = std::numeric_limits::max();
+ int closest_hit_mesh_id = -1;
+
+ // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
+ for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) {
+ MeshRaycaster mesh_raycaster = MeshRaycaster(mo->volumes[mesh_id]->mesh());
+ if (mesh_raycaster.unproject_on_mesh(mouse_position, trafo_matrices[mesh_id], camera, hit, normal,
+ m_c->object_clipper()->get_clipping_plane(), &facet)) {
+ // Is this hit the closest to the camera so far?
+ double hit_squared_distance = (camera.get_position() - trafo_matrices[mesh_id] * hit.cast()).squaredNorm();
+ if (hit_squared_distance < closest_hit_squared_distance) {
+ closest_hit_squared_distance = hit_squared_distance;
+ closest_hit_mesh_id = mesh_id;
+ closest_hit = hit;
+ closest_normal = normal;
+ }
+ }
+ }
+
+ if (closest_hit == Vec3f::Zero() && closest_normal == Vec3f::Zero())
+ return true;
+
+ if (get_selecting_state() == MeshBooleanSelectingState::SelectTool) {
+ m_tool.trafo = trafo_matrices[closest_hit_mesh_id];
+ m_tool.volume_idx = closest_hit_mesh_id;
+ set_tool_volume(mo->volumes[closest_hit_mesh_id]);
+ return true;
+ }
+ if (get_selecting_state() == MeshBooleanSelectingState::SelectSource) {
+ m_src.trafo = trafo_matrices[closest_hit_mesh_id];
+ m_src.volume_idx = closest_hit_mesh_id;
+ set_src_volume(mo->volumes[closest_hit_mesh_id]);
+ m_selecting_state = MeshBooleanSelectingState::SelectTool;
+ return true;
+ }
+ }
+ return true;
+}
+
+bool GLGizmoMeshBoolean::on_init()
+{
+ m_shortcut_key = WXK_CONTROL_B;
+ return true;
+}
+
+std::string GLGizmoMeshBoolean::on_get_name() const
+{
+ return _u8L("Mesh Boolean");
+}
+
+bool GLGizmoMeshBoolean::on_is_activable() const
+{
+ return m_parent.get_selection().is_single_full_instance() && m_parent.get_selection().get_volume_idxs().size() > 1;
+}
+
+void GLGizmoMeshBoolean::on_render()
+{
+ if (m_parent.get_selection().get_object_idx() < 0)
+ return;
+ static ModelObject* last_mo = nullptr;
+ ModelObject* curr_mo = m_parent.get_selection().get_model()->objects[m_parent.get_selection().get_object_idx()];
+ if (last_mo != curr_mo) {
+ last_mo = curr_mo;
+ m_src.reset();
+ m_tool.reset();
+ m_operation_mode = MeshBooleanOperation::Union;
+ m_selecting_state = MeshBooleanSelectingState::SelectSource;
+ return;
+ }
+
+ BoundingBoxf3 src_bb;
+ BoundingBoxf3 tool_bb;
+ const ModelObject* mo = m_c->selection_info()->model_object();
+ const ModelInstance* mi = mo->instances[m_parent.get_selection().get_instance_idx()];
+ const Selection& selection = m_parent.get_selection();
+ const Selection::IndicesList& idxs = selection.get_volume_idxs();
+ for (unsigned int i : idxs) {
+ const GLVolume* volume = selection.get_volume(i);
+ if(volume->volume_idx() == m_src.volume_idx) {
+ src_bb = volume->transformed_convex_hull_bounding_box();
+ }
+ if (volume->volume_idx() == m_tool.volume_idx) {
+ tool_bb = volume->transformed_convex_hull_bounding_box();
+ }
+ }
+
+ float src_color[3] = { 1.0f, 1.0f, 1.0f };
+ float tool_color[3] = { 0.0f, 174.0f / 255.0f, 66.0f / 255.0f };
+ m_parent.get_selection().render_bounding_box(src_bb, src_color, m_parent.get_scale());
+ m_parent.get_selection().render_bounding_box(tool_bb, tool_color, m_parent.get_scale());
+}
+
+void GLGizmoMeshBoolean::on_set_state()
+{
+ if (m_state == EState::On) {
+ m_src.reset();
+ m_tool.reset();
+ bool m_diff_delete_input = false;
+ bool m_inter_delete_input = false;
+ m_operation_mode = MeshBooleanOperation::Union;
+ m_selecting_state = MeshBooleanSelectingState::SelectSource;
+ }
+ else if (m_state == EState::Off) {
+ m_src.reset();
+ m_tool.reset();
+ bool m_diff_delete_input = false;
+ bool m_inter_delete_input = false;
+ m_operation_mode = MeshBooleanOperation::Undef;
+ m_selecting_state = MeshBooleanSelectingState::Undef;
+ }
+}
+
+CommonGizmosDataID GLGizmoMeshBoolean::on_get_requirements() const
+{
+ return CommonGizmosDataID(
+ int(CommonGizmosDataID::SelectionInfo)
+ | int(CommonGizmosDataID::InstancesHider)
+ | int(CommonGizmosDataID::Raycaster)
+ | int(CommonGizmosDataID::ObjectClipper));
+}
+
+void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_limit)
+{
+ y = std::min(y, bottom_limit - ImGui::GetWindowHeight());
+
+ static float last_y = 0.0f;
+ static float last_w = 0.0f;
+
+ const float currt_scale = m_parent.get_scale();
+ ImGuiWrapper::push_toolbar_style(currt_scale);
+ GizmoImguiSetNextWIndowPos(x, y, ImGuiCond_Always, 0.0f, 0.0f);
+ GizmoImguiBegin("MeshBoolean", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
+
+ const int max_tab_length = 2 * ImGui::GetStyle().FramePadding.x + std::max(ImGui::CalcTextSize(_L("Union").c_str()).x,
+ std::max(ImGui::CalcTextSize(_L("Difference").c_str()).x, ImGui::CalcTextSize(_L("Intersection").c_str()).x));
+ const int max_cap_length = ImGui::GetStyle().WindowPadding.x + ImGui::GetStyle().ItemSpacing.x + std::max(ImGui::CalcTextSize(_L("Source Volume").c_str()).x, ImGui::CalcTextSize(_L("Tool Volume").c_str()).x);
+ const int select_btn_length = 2 * ImGui::GetStyle().FramePadding.x + std::max(ImGui::CalcTextSize(("1 " + _L("selected")).c_str()).x, ImGui::CalcTextSize(_L("Select").c_str()).x);
+
+ auto selectable = [this](const wxString& label, bool selected, const ImVec2& size_arg) {
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 0,0 });
+
+ ImGuiWindow* window = ImGui::GetCurrentWindow();
+ const ImVec2 label_size = ImGui::CalcTextSize(label.c_str(), NULL, true);
+ ImVec2 pos = window->DC.CursorPos;
+ ImVec2 size = ImGui::CalcItemSize(size_arg, label_size.x + ImGui::GetStyle().FramePadding.x * 2.0f, label_size.y + ImGui::GetStyle().FramePadding.y * 2.0f);
+ bool hovered = ImGui::IsMouseHoveringRect(pos, pos + size);
+
+ if (selected || hovered) {
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f));
+ ImGui::PushStyleColor(ImGuiCol_Button, { 0, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f });
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f });
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f });
+ }
+ else {
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f });
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0, 174.0f / 255.0f, 66.0f / 255.0f, 1.0f });
+ }
+
+ bool res = ImGui::Button(label.c_str(), size_arg);
+
+ if (selected || hovered) {
+ ImGui::PopStyleColor(4);
+ }
+ else {
+ ImGui::PopStyleColor(2);
+ }
+
+ ImGui::PopStyleVar(1);
+ return res;
+ };
+
+ auto operate_button = [this](const wxString& label, bool enable) {
+ if (!enable) {
+ ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
+ if (m_is_dark_mode) {
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(39.0f / 255.0f, 39.0f / 255.0f, 39.0f / 255.0f, 1.0f));
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(108.0f / 255.0f, 108.0f / 255.0f, 108.0f / 255.0f, 1.0f));
+ }
+ else {
+ ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(230.0f / 255.0f, 230.0f / 255.0f, 230.0f / 255.0f, 1.0f));
+ ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(163.0f / 255.0f, 163.0f / 255.0f, 163.0f / 255.0f, 1.0f));
+ }
+ }
+
+ bool res = m_imgui->button(label.c_str());
+
+ if (!enable) {
+ ImGui::PopItemFlag();
+ ImGui::PopStyleColor(2);
+ }
+ return res;
+ };
+
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
+ if (selectable(_L("Union").c_str(), m_operation_mode == MeshBooleanOperation::Union, ImVec2(max_tab_length, 0.0f))) {
+ m_operation_mode = MeshBooleanOperation::Union;
+ }
+ ImGui::SameLine(0, 0);
+ if (selectable(_L("Difference").c_str(), m_operation_mode == MeshBooleanOperation::Difference, ImVec2(max_tab_length, 0.0f))) {
+ m_operation_mode = MeshBooleanOperation::Difference;
+ }
+ ImGui::SameLine(0, 0);
+ if (selectable(_L("Intersection").c_str(), m_operation_mode == MeshBooleanOperation::Intersection, ImVec2(max_tab_length, 0.0f))) {
+ m_operation_mode = MeshBooleanOperation::Intersection;
+ }
+ ImGui::PopStyleVar();
+
+ ImGui::AlignTextToFramePadding();
+ wxString cap_str1 = m_operation_mode != MeshBooleanOperation::Difference ? _L("Part 1") : _L("Subtract from");
+ m_imgui->text(cap_str1);
+ ImGui::SameLine(max_cap_length);
+ wxString select_src_str = m_src.mv ? "1 " + _L("selected") : _L("Select");
+ select_src_str << "##select_source_volume";
+ ImGui::PushItemWidth(select_btn_length);
+ if (selectable(select_src_str.c_str(), m_selecting_state == MeshBooleanSelectingState::SelectSource, ImVec2(select_btn_length, 0)))
+ m_selecting_state = MeshBooleanSelectingState::SelectSource;
+ ImGui::PopItemWidth();
+ if (m_src.mv) {
+ ImGui::SameLine();
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_src.mv->name);
+
+ ImGui::SameLine();
+ ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 });
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_Button));
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_Button));
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_Button));
+ ImGui::PushStyleColor(ImGuiCol_Border, { 0, 0, 0, 0 });
+ if (ImGui::Button((into_u8(ImGui::TextSearchCloseIcon) + "##src").c_str(), {18, 18}))
+ {
+ m_src.reset();
+ }
+ ImGui::PopStyleColor(5);
+ }
+
+ ImGui::AlignTextToFramePadding();
+ wxString cap_str2 = m_operation_mode != MeshBooleanOperation::Difference ? _L("Part 2") : _L("Subtract with");
+ m_imgui->text(cap_str2);
+ ImGui::SameLine(max_cap_length);
+ wxString select_tool_str = m_tool.mv ? "1 " + _L("selected") : _L("Select");
+ select_tool_str << "##select_tool_volume";
+ ImGui::PushItemWidth(select_btn_length);
+ if (selectable(select_tool_str.c_str(), m_selecting_state == MeshBooleanSelectingState::SelectTool, ImVec2(select_btn_length, 0)))
+ m_selecting_state = MeshBooleanSelectingState::SelectTool;
+ ImGui::PopItemWidth();
+ if (m_tool.mv) {
+ ImGui::SameLine();
+ ImGui::AlignTextToFramePadding();
+ m_imgui->text(m_tool.mv->name);
+
+ ImGui::SameLine();
+ ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 });
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_Button));
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_Button));
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_Button));
+ ImGui::PushStyleColor(ImGuiCol_Border, { 0, 0, 0, 0 });
+ if (ImGui::Button((into_u8(ImGui::TextSearchCloseIcon) + "tool").c_str(), {18, 18}))
+ {
+ m_tool.reset();
+ }
+ ImGui::PopStyleColor(5);
+ }
+
+ bool enable_button = m_src.mv && m_tool.mv;
+ if (m_operation_mode == MeshBooleanOperation::Union)
+ {
+ if (operate_button(_L("Union") + "##btn", enable_button)) {
+ TriangleMesh temp_src_mesh = m_src.mv->mesh();
+ temp_src_mesh.transform(m_src.trafo);
+ TriangleMesh temp_tool_mesh = m_tool.mv->mesh();
+ temp_tool_mesh.transform(m_tool.trafo);
+ std::vector temp_mesh_resuls;
+ Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "UNION");
+ if (temp_mesh_resuls.size() != 0)
+ generate_new_volume(true, *temp_mesh_resuls.begin());
+ }
+ }
+ else if (m_operation_mode == MeshBooleanOperation::Difference) {
+ m_imgui->bbl_checkbox(_L("Delete input"), m_diff_delete_input);
+ if (operate_button(_L("Difference") + "##btn", enable_button)) {
+ TriangleMesh temp_src_mesh = m_src.mv->mesh();
+ temp_src_mesh.transform(m_src.trafo);
+ TriangleMesh temp_tool_mesh = m_tool.mv->mesh();
+ temp_tool_mesh.transform(m_tool.trafo);
+ std::vector temp_mesh_resuls;
+ Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "A_NOT_B");
+ if (temp_mesh_resuls.size() != 0)
+ generate_new_volume(m_diff_delete_input, *temp_mesh_resuls.begin());
+ }
+ }
+ else if (m_operation_mode == MeshBooleanOperation::Intersection){
+ m_imgui->bbl_checkbox(_L("Delete input"), m_inter_delete_input);
+ if (operate_button(_L("Intersection") + "##btn", enable_button)) {
+ TriangleMesh temp_src_mesh = m_src.mv->mesh();
+ temp_src_mesh.transform(m_src.trafo);
+ TriangleMesh temp_tool_mesh = m_tool.mv->mesh();
+ temp_tool_mesh.transform(m_tool.trafo);
+ std::vector temp_mesh_resuls;
+ Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "INTERSECTION");
+ if (temp_mesh_resuls.size() != 0)
+ generate_new_volume(m_inter_delete_input, *temp_mesh_resuls.begin());
+ }
+ }
+
+ float win_w = ImGui::GetWindowWidth();
+ if (last_w != win_w || last_y != y) {
+ // ask canvas for another frame to render the window in the correct position
+ m_imgui->set_requires_extra_frame();
+ m_parent.set_as_dirty();
+ m_parent.request_extra_frame();
+ if (last_w != win_w)
+ last_w = win_w;
+ if (last_y != y)
+ last_y = y;
+ }
+
+ GizmoImguiEnd();
+ ImGuiWrapper::pop_toolbar_style();
+}
+
+void GLGizmoMeshBoolean::generate_new_volume(bool delete_input, const TriangleMesh& mesh_result) {
+ ModelObject* curr_model_object = m_c->selection_info()->model_object();
+
+ // generate new volume
+ ModelVolume* new_volume = curr_model_object->add_volume(std::move(mesh_result));
+
+ // assign to new_volume from old_volume
+ ModelVolume* old_volume = m_src.mv;
+ std::string suffix;
+ switch (m_operation_mode)
+ {
+ case MeshBooleanOperation::Union:
+ suffix = "union";
+ break;
+ case MeshBooleanOperation::Difference:
+ suffix = "difference";
+ break;
+ case MeshBooleanOperation::Intersection:
+ suffix = "intersection";
+ break;
+ }
+ new_volume->name = old_volume->name + " - " + suffix;
+ new_volume->set_new_unique_id();
+ new_volume->config.apply(old_volume->config);
+ new_volume->set_type(old_volume->type());
+ new_volume->set_material_id(old_volume->material_id());
+ new_volume->set_transformation(old_volume->get_transformation());
+ //Vec3d translate_z = { 0,0, (new_volume->source.mesh_offset - old_volume->source.mesh_offset).z() };
+ //new_volume->translate(new_volume->get_transformation().get_matrix(true) * translate_z);
+ //new_volume->supported_facets.assign(old_volume->supported_facets);
+ //new_volume->seam_facets.assign(old_volume->seam_facets);
+ //new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets);
+
+ // delete old_volume
+ std::swap(curr_model_object->volumes[m_src.volume_idx], curr_model_object->volumes.back());
+ curr_model_object->delete_volume(curr_model_object->volumes.size() - 1);
+
+ if (delete_input) {
+ std::vector items;
+ const Selection& selection = m_parent.get_selection();
+ items.emplace_back(ItemType::itVolume, selection.get_volume(0)->object_idx(), m_tool.volume_idx);
+ wxGetApp().obj_list()->delete_from_model_and_list(items);
+ }
+
+ //bool sinking = curr_model_object->bounding_box().min.z() < SINKING_Z_THRESHOLD;
+ //if (!sinking)
+ // curr_model_object->ensure_on_bed();
+ //curr_model_object->sort_volumes(true);
+
+ wxGetApp().plater()->update();
+ wxGetApp().obj_list()->select_item([this, new_volume]() {
+ wxDataViewItem sel_item;
+
+ wxDataViewItemArray items = wxGetApp().obj_list()->reorder_volumes_and_get_selection(m_parent.get_selection().get_object_idx(), [new_volume](const ModelVolume* volume) { return volume == new_volume; });
+ if (!items.IsEmpty())
+ sel_item = items.front();
+
+ return sel_item;
+ });
+
+ m_src.reset();
+ m_tool.reset();
+ m_selecting_state = MeshBooleanSelectingState::SelectSource;
+}
+
+
+}}
\ No newline at end of file
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp
new file mode 100644
index 000000000..5389b0243
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp
@@ -0,0 +1,81 @@
+#ifndef slic3r_GLGizmoMeshBoolean_hpp_
+#define slic3r_GLGizmoMeshBoolean_hpp_
+
+#include "GLGizmoBase.hpp"
+#include "GLGizmosCommon.hpp"
+#include "libslic3r/Model.hpp"
+
+namespace Slic3r {
+
+namespace GUI {
+
+enum class MeshBooleanSelectingState {
+ Undef,
+ SelectSource,
+ SelectTool,
+
+};
+enum class MeshBooleanOperation{
+ Undef,
+ Union,
+ Difference,
+ Intersection,
+};
+struct VolumeInfo {
+ ModelVolume* mv{ nullptr };
+ int volume_idx{-1};
+ Transform3d trafo;
+ void reset() {
+ mv = nullptr;
+ volume_idx = -1;
+ trafo = Transform3d::Identity();
+ };
+};
+class GLGizmoMeshBoolean : public GLGizmoBase
+{
+public:
+ GLGizmoMeshBoolean(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
+ ~GLGizmoMeshBoolean();
+
+ void set_enable(bool enable) { m_enable = enable; }
+ bool get_enable() { return m_enable; }
+ MeshBooleanSelectingState get_selecting_state() { return m_selecting_state; }
+ void set_src_volume(ModelVolume* mv) {
+ m_src.mv = mv;
+ if (m_src.mv == m_tool.mv)
+ m_tool.mv = nullptr;
+ }
+ void set_tool_volume(ModelVolume* mv) {
+ m_tool.mv = mv;
+ if (m_tool.mv == m_src.mv)
+ m_src.mv = nullptr;
+ }
+
+ bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
+
+protected:
+ virtual bool on_init() override;
+ virtual std::string on_get_name() const override;
+ virtual bool on_is_activable() const override;
+ virtual void on_render() override;
+ virtual void on_render_for_picking() override {}
+ virtual void on_set_state() override;
+ virtual CommonGizmosDataID on_get_requirements() const override;
+ virtual void on_render_input_window(float x, float y, float bottom_limit);
+
+private:
+ bool m_enable{ false };
+ MeshBooleanOperation m_operation_mode;
+ MeshBooleanSelectingState m_selecting_state;
+ bool m_diff_delete_input = false;
+ bool m_inter_delete_input = false;
+ VolumeInfo m_src;
+ VolumeInfo m_tool;
+
+ void generate_new_volume(bool delete_input, const TriangleMesh& mesh_result);
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLGizmoMeshBoolean_hpp_
\ No newline at end of file
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index 947895f09..7890487f9 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -23,6 +23,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoText.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/Model.hpp"
@@ -170,7 +171,11 @@ void GLGizmosManager::switch_gizmos_icon_filename()
case(EType::MmuSegmentation):
gizmo->set_icon_filename(m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg");
break;
+ case(EType::MeshBoolean):
+ gizmo->set_icon_filename(m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg");
+ break;
}
+
}
}
@@ -200,6 +205,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, m_is_dark ? "toolbar_scale_dark.svg" : "toolbar_scale.svg", EType::Scale, &m_object_manipulation));
m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, m_is_dark ? "toolbar_flatten_dark.svg" : "toolbar_flatten.svg", EType::Flatten));
m_gizmos.emplace_back(new GLGizmoAdvancedCut(m_parent, m_is_dark ? "toolbar_cut_dark.svg" : "toolbar_cut.svg", EType::Cut));
+ m_gizmos.emplace_back(new GLGizmoMeshBoolean(m_parent, m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg", EType::MeshBoolean));
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, m_is_dark ? "toolbar_support_dark.svg" : "toolbar_support.svg", EType::FdmSupports));
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg", EType::Seam));
m_gizmos.emplace_back(new GLGizmoText(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Text));
@@ -638,6 +644,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
return dynamic_cast(m_gizmos[Text].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Cut)
return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
+ else if (m_current == MeshBoolean)
+ return dynamic_cast(m_gizmos[MeshBoolean].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else
return false;
}
@@ -884,7 +892,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
if (evt.LeftDown() && (!control_down || grabber_contains_mouse())) {
if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports ||
- m_current == Seam || m_current == MmuSegmentation || m_current == Text || m_current == Cut)
+ m_current == Seam || m_current == MmuSegmentation || m_current == Text || m_current == Cut || m_current == MeshBoolean)
&& gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown()))
// the gizmo got the event and took some action, there is no need to do anything more
processed = true;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
index 4d13c3d07..ef0280dd6 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp
@@ -70,6 +70,7 @@ public:
Scale,
Flatten,
Cut,
+ MeshBoolean,
FdmSupports,
Seam,
// BBS
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 1ba2d1f0c..fcc362dd2 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -4198,6 +4198,7 @@ void Plater::priv::reset(bool apply_presets_change)
if (view3D->is_layers_editing_enabled())
view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
+ view3D->get_canvas3d()->reset_all_gizmos();
reset_gcode_toolpaths();
//BBS: update gcode to current partplate's
diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp
index 5c1669b1b..bfee49bd2 100644
--- a/src/slic3r/GUI/Selection.hpp
+++ b/src/slic3r/GUI/Selection.hpp
@@ -362,6 +362,11 @@ public:
bool requires_local_axes() const;
+ void render_bounding_box(const BoundingBoxf3& box, float* color, float scale) {
+ m_scale_factor = scale;
+ render_bounding_box(box, color);
+ }
+
//BBS
void cut_to_clipboard();
void copy_to_clipboard();