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();