From e672072071a3c52114077db03235164eb43109dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 20 Mar 2024 15:25:58 +0100 Subject: [PATCH] SPE-2003: Implement the height range tool into the multi-material painting gizmo. The height range tool is inspired by BambuStudio. --- src/libslic3r/TriangleSelector.cpp | 56 ++++++++++- src/libslic3r/TriangleSelector.hpp | 23 ++++- src/libslic3r/libslic3r.h | 5 + .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 78 ++++++++++++---- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 92 ++++++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 13 ++- 6 files changed, 239 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 17c107f980..105eeb5b82 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -1984,9 +1984,9 @@ TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const : source{source_}, trafo{trafo_.cast()}, clipping_plane{clipping_plane_} { Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor(); - if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) { - radius = float(radius_world / sf(0)); - radius_sqr = float(Slic3r::sqr(radius_world / sf(0))); + if (is_approx(sf.x(), sf.y()) && is_approx(sf.y(), sf.z())) { + radius = float(radius_world / sf.x()); + radius_sqr = float(Slic3r::sqr(radius_world / sf.x())); uniform_scaling = true; } else { // In case that the transformation is non-uniform, all checks whether @@ -2220,4 +2220,54 @@ bool TriangleSelector::Capsule2D::is_any_edge_inside_cursor(const Triangle &tr, return false; } +TriangleSelector::HeightRange::HeightRange(const Vec3f &mesh_hit, const BoundingBoxf3 &mesh_bbox, float z_range, const Transform3d &trafo, const ClippingPlane &clipping_plane) + : Cursor(Vec3f::Zero(), 0.f, trafo, clipping_plane) { + m_z_range_top = std::max(mesh_hit.z() + z_range / 2.f, float(mesh_bbox.min.z())); + m_z_range_bottom = std::min(mesh_hit.z() - z_range / 2.f, float(mesh_bbox.max.z())); + m_edge_limit = 0.1f; +} + +bool TriangleSelector::HeightRange::is_mesh_point_inside(const Vec3f &point) const { + const float transformed_point_z = (this->uniform_scaling ? point : Vec3f(this->trafo * point)).z(); + return Slic3r::is_in_range(transformed_point_z, m_z_range_bottom, m_z_range_top); +} + +bool TriangleSelector::HeightRange::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { + const std::array pts = this->transform_triangle(tr, vertices); + // If all vertices are below m_z_range_bottom or all vertices are above m_z_range_top, then it means that no edge + // is inside the height range. Otherwise, there is at least one edge inside the height range. + return !((pts[0].z() < m_z_range_bottom && pts[1].z() < m_z_range_bottom && pts[2].z() < m_z_range_bottom) || + (pts[0].z() > m_z_range_top && pts[1].z() > m_z_range_top && pts[2].z() > m_z_range_top)); +} + +std::vector TriangleSelector::HeightRange::get_facets_to_select(const int facet_idx, const std::vector &vertices, const std::vector &triangles, const int orig_size_vertices, const int orig_size_indices) const { + std::vector facets_to_check; + + // Assigns each vertex a value of -1, 1, or 0. The value -1 indicates a vertex is below m_z_range_bottom, + // while 1 indicates a vertex is above m_z_range_top. The value of 0 indicates that the vertex between + // m_z_range_bottom and m_z_range_top. + std::vector vertex_side(orig_size_vertices, 0); + if (trafo.matrix() == Transform3f::Identity().matrix()) { + for (int i = 0; i < orig_size_vertices; ++i) { + const float z = vertices[i].v.z(); + vertex_side[i] = z < m_z_range_bottom ? int8_t(-1) : z > m_z_range_top ? int8_t(1) : int8_t(0); + } + } else { + for (int i = 0; i < orig_size_vertices; ++i) { + const float z = (this->uniform_scaling ? vertices[i].v : Vec3f(this->trafo * vertices[i].v)).z(); + vertex_side[i] = z < m_z_range_bottom ? int8_t(-1) : z > m_z_range_top ? int8_t(1) : int8_t(0); + } + } + + // Determine if each triangle crosses m_z_range_bottom or m_z_range_top. + for (int i = 0; i < orig_size_indices; ++i) { + const std::array &face = triangles[i].verts_idxs; + const std::array sides = { vertex_side[face[0]], vertex_side[face[1]], vertex_side[face[2]] }; + if ((sides[0] * sides[1] <= 0) || (sides[1] * sides[2] <= 0) || (sides[0] * sides[2] <= 0)) + facets_to_check.emplace_back(i); + } + + return facets_to_check; +} + } // namespace Slic3r diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 7ad9f2cca5..afafc2ad72 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -70,7 +70,8 @@ public: enum class CursorType { CIRCLE, SPHERE, - POINTER + POINTER, + HEIGHT_RANGE }; enum class ForceReselection { @@ -242,6 +243,26 @@ public: } }; + class HeightRange : public Cursor + { + public: + HeightRange() = delete; + + explicit HeightRange(const Vec3f &mesh_hit, const BoundingBoxf3 &mesh_bbox, float z_range, const Transform3d &trafo, const ClippingPlane &clipping_plane); + ~HeightRange() override = default; + + bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override { return false; } + bool is_mesh_point_inside(const Vec3f &point) const override; + bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const override; + bool is_facet_visible(int facet_idx, const std::vector &face_normals) const override { return true; } + + std::vector get_facets_to_select(int facet_idx, const std::vector &vertices, const std::vector &triangles, int orig_size_vertices, int orig_size_indices) const override; + + private: + float m_z_range_top; + float m_z_range_bottom; + }; + struct TriangleBitStreamMapping { // Index of the triangle to which we assign the bitstream containing splitting information. diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index eaeeaf966d..291e1686ec 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -487,6 +487,11 @@ Fn for_each_in_tuple(Fn fn, Tup &&tup) return fn; } +template +inline bool is_in_range(const T &value, const T &low, const T &high) { + return low <= value && value <= high; +} + } // namespace Slic3r #endif // _libslic3r_h_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index fc0ddbb420..ba3283f2e0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -119,11 +119,14 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["tool_brush"] = _u8L("Brush"); m_desc["tool_smart_fill"] = _u8L("Smart fill"); m_desc["tool_bucket_fill"] = _u8L("Bucket fill"); + m_desc["tool_height_range"] = _u8L("Height range"); m_desc["smart_fill_angle"] = _u8L("Smart fill angle"); m_desc["smart_fill_gap_area"] = _u8L("Smart fill gap"); m_desc["split_triangles"] = _u8L("Split triangles"); + m_desc["height_range_z_range"] = _u8L("Height range"); + init_extruders_data(); return true; @@ -264,17 +267,18 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (!m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(23.7f); + const float approx_height = m_imgui->scaled(25.35f); y = std::min(y, bottom_limit - approx_height); ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); ImGuiPureWrap::begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, - ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float clipping_slider_left = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("clipping_of_view")).x, + ImGuiPureWrap::calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); const float cursor_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); const float smart_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float height_range_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("height_range_z_range")).x + m_imgui->scaled(1.f); const float cursor_type_radio_circle = ImGuiPureWrap::calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); const float cursor_type_radio_sphere = ImGuiPureWrap::calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); @@ -287,9 +291,14 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float combo_label_width = std::max(ImGuiPureWrap::calc_text_size(m_desc.at("first_color")).x, ImGuiPureWrap::calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f); - const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_bucket_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_brush = ImGuiPureWrap::calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_bucket_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_smart_fill = ImGuiPureWrap::calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_height_range = ImGuiPureWrap::calc_text_size(m_desc["tool_height_range"]).x + m_imgui->scaled(2.5f); + + const float tool_type_radio_first_line = tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill; + const float tool_type_radio_second_line = tool_type_radio_height_range; + const float tool_type_radio_max_width = std::max(tool_type_radio_first_line, tool_type_radio_second_line); const float split_triangles_checkbox_width = ImGuiPureWrap::calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); @@ -302,15 +311,15 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott total_text_max += caption_max + m_imgui->scaled(1.f); caption_max += m_imgui->scaled(1.f); - const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left)); + const float sliders_left_width = std::max({smart_fill_slider_left, cursor_slider_left, clipping_slider_left, height_range_slider_left}); const float slider_icon_width = ImGuiPureWrap::get_slider_icon_size().x; float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; - window_width = std::max(window_width, total_text_max); - window_width = std::max(window_width, button_width); - window_width = std::max(window_width, split_triangles_checkbox_width); - window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); - window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_smart_fill); - window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); + window_width = std::max(window_width, total_text_max); + window_width = std::max(window_width, button_width); + window_width = std::max(window_width, split_triangles_checkbox_width); + window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); + window_width = std::max(window_width, tool_type_radio_max_width); + window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); auto draw_text_with_caption = [&caption_max](const std::string &caption, const std::string& text) { ImGuiPureWrap::text_colored(ImGuiPureWrap::COL_ORANGE_LIGHT, caption); @@ -361,8 +370,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGuiPureWrap::text(m_desc.at("tool_type")); ImGui::NewLine(); - float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_smart_fill + m_imgui->scaled(1.5f)) / 2.f; - ImGui::SameLine(tool_type_offset); + const float tool_type_first_line_offset = (window_width - tool_type_radio_first_line + m_imgui->scaled(1.5f)) / 2.f; + ImGui::SameLine(tool_type_first_line_offset); ImGui::PushItemWidth(tool_type_radio_brush); if (ImGuiPureWrap::radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) { m_tool_type = ToolType::BRUSH; @@ -375,7 +384,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (ImGui::IsItemHovered()) ImGuiPureWrap::tooltip(_u8L("Paints facets according to the chosen painting brush."), max_tooltip_width); - ImGui::SameLine(tool_type_offset + tool_type_radio_brush); + ImGui::SameLine(tool_type_first_line_offset + tool_type_radio_brush); ImGui::PushItemWidth(tool_type_radio_smart_fill); if (ImGuiPureWrap::radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) { m_tool_type = ToolType::SMART_FILL; @@ -388,7 +397,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (ImGui::IsItemHovered()) ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); - ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_smart_fill); + ImGui::SameLine(tool_type_first_line_offset + tool_type_radio_brush + tool_type_radio_smart_fill); ImGui::PushItemWidth(tool_type_radio_bucket_fill); if (ImGuiPureWrap::radio_button(m_desc["tool_bucket_fill"], m_tool_type == ToolType::BUCKET_FILL)) { m_tool_type = ToolType::BUCKET_FILL; @@ -401,9 +410,25 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott if (ImGui::IsItemHovered()) ImGuiPureWrap::tooltip(_u8L("Paints neighboring facets that have the same color."), max_tooltip_width); + ImGui::NewLine(); + + const float tool_type_second_line_offset = (window_width - tool_type_radio_second_line + m_imgui->scaled(1.5f)) / 2.f; + ImGui::SameLine(tool_type_second_line_offset); + ImGui::PushItemWidth(tool_type_radio_height_range); + if (ImGuiPureWrap::radio_button(m_desc["tool_height_range"], m_tool_type == ToolType::HEIGHT_RANGE)) { + m_tool_type = ToolType::HEIGHT_RANGE; + for (auto &triangle_selector : m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + } + + if (ImGui::IsItemHovered()) + ImGuiPureWrap::tooltip(_u8L("Paints facets within the chosen height range."), max_tooltip_width); + ImGui::Separator(); - if(m_tool_type == ToolType::BRUSH) { + if (m_tool_type == ToolType::BRUSH) { ImGuiPureWrap::text(m_desc.at("cursor_type")); ImGui::NewLine(); @@ -450,7 +475,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott m_imgui->disabled_end(); ImGui::Separator(); - } else if(m_tool_type == ToolType::SMART_FILL) { + } else if (m_tool_type == ToolType::SMART_FILL) { ImGui::AlignTextToFramePadding(); ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":"); std::string format_str_angle = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo," @@ -475,6 +500,21 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } } + ImGui::Separator(); + } else if (m_tool_type == ToolType::HEIGHT_RANGE) { + ImGui::AlignTextToFramePadding(); + ImGuiPureWrap::text(m_desc["height_range_z_range"] + ":"); + std::string format_str_angle = std::string("%.2f ") + I18N::translate_utf8("mm", "Millimeter sign to use in the respective slider in multi-material painting gizmo," + "placed after the number with space in between."); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + if (m_imgui->slider_float("##height_range_z_range", &m_height_range_z_range, HeightRangeZRangeMin, HeightRangeZRangeMax, format_str_angle.data(), 1.0f, true, _L("Alt + Mouse wheel"))) { + for (auto &triangle_selector: m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + } + ImGui::Separator(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index d2b4c50d39..8199bf60ac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -18,7 +18,9 @@ #include "libslic3r/TriangleMesh.hpp" #include +#include #include +#include namespace Slic3r::GUI { @@ -143,6 +145,8 @@ void GLGizmoPainterBase::render_cursor() render_cursor_sphere(trafo_matrices[m_rr.mesh_id]); else if (m_cursor_type == TriangleSelector::CursorType::CIRCLE) render_cursor_circle(); + } else if (m_tool_type == ToolType::HEIGHT_RANGE) { + render_cursor_height_range(trafo_matrices[m_rr.mesh_id]); } } @@ -270,7 +274,6 @@ void GLGizmoPainterBase::render_cursor_circle() glsafe(::glEnable(GL_DEPTH_TEST)); } - void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const { if (s_sphere == nullptr) { @@ -314,6 +317,74 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const shader->stop_using(); } +void GLGizmoPainterBase::render_cursor_height_range(const Transform3d &trafo) const { + const ModelObject &model_object = *m_c->selection_info()->model_object(); + const BoundingBoxf3 mesh_bbox = model_object.volumes[m_rr.mesh_id]->mesh().bounding_box(); + + const std::array z_range = { + std::min(m_rr.hit.z() - m_height_range_z_range / 2.f, float(mesh_bbox.max.z())), + std::max(m_rr.hit.z() + m_height_range_z_range / 2.f, float(mesh_bbox.min.z())) + }; + + std::vector slice_polygons_per_z; + for (const float z: z_range) + slice_polygons_per_z.emplace_back(slice_mesh(model_object.volumes[m_rr.mesh_id]->mesh().its, z, MeshSlicingParams())); + + const size_t max_vertices_cnt = std::accumulate(slice_polygons_per_z.begin(), slice_polygons_per_z.end(), 0, + [](const size_t sum, const Polygons &polygons) { + return sum + count_points(polygons); + }); + + GLModel::Geometry z_range_geometry; + z_range_geometry.format = {GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3}; + z_range_geometry.reserve_vertices(max_vertices_cnt); + z_range_geometry.reserve_indices(max_vertices_cnt); + z_range_geometry.color = ColorRGBA::WHITE(); + + size_t vertices_cnt = 0; + for (const float z: z_range) { + const Polygons slice_polygons = slice_mesh(model_object.volumes[m_rr.mesh_id]->mesh().its, z, MeshSlicingParams()); + for (const Polygon &polygon: slice_polygons) { + for (const Point &pt: polygon.points) + z_range_geometry.add_vertex(Vec3f(unscaled(pt.x()), unscaled(pt.y()), z)); + + for (size_t pt_idx = 1; pt_idx < polygon.points.size(); ++pt_idx) + z_range_geometry.add_line(vertices_cnt + pt_idx - 1, vertices_cnt + pt_idx); + + z_range_geometry.add_line(vertices_cnt + polygon.points.size() - 1, vertices_cnt); + + vertices_cnt += polygon.points.size(); + } + } + + GLModel z_range_model; + if (!z_range_geometry.is_empty()) + z_range_model.init_from(std::move(z_range_geometry)); + + const Camera &camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * trafo; + + GLShaderProgram *shader = wxGetApp().get_shader("mm_contour"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); + + const bool is_left_handed = Geometry::Transformation(view_model_matrix).is_left_handed(); + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + + z_range_model.render(); + + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + + shader->stop_using(); +} bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const { @@ -471,7 +542,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous return true; } else if (m_tool_type == ToolType::SMART_FILL) { m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin) - : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); + : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); m_parent.set_as_dirty(); if (m_rr.mesh_id != -1) { const Selection &selection = m_parent.get_selection(); @@ -485,6 +556,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_seed_fill_last_mesh_id = m_rr.mesh_id; } return true; + } else if (m_tool_type == ToolType::HEIGHT_RANGE) { + m_height_range_z_range = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_height_range_z_range - HeightRangeZRangeStep, HeightRangeZRangeMin) + : std::min(m_height_range_z_range + HeightRangeZRangeStep, HeightRangeZRangeMax); + m_parent.set_as_dirty(); + return true; } return false; @@ -556,7 +632,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous assert(mesh_idx < int(m_triangle_selectors.size())); const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix); if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { - for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) { + for (const ProjectedMousePosition &projected_mouse_position: projected_mouse_positions) { assert(projected_mouse_position.mesh_idx == mesh_idx); const Vec3f mesh_hit = projected_mouse_position.mesh_hit; const int facet_idx = int(projected_mouse_position.facet_idx); @@ -589,6 +665,16 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); } } + } else if (m_tool_type == ToolType::HEIGHT_RANGE) { + for (const ProjectedMousePosition &projected_mouse_position: projected_mouse_positions) { + const Vec3f &mesh_hit = projected_mouse_position.mesh_hit; + const int facet_idx = int(projected_mouse_position.facet_idx); + const BoundingBoxf3 mesh_bbox = mo->volumes[projected_mouse_position.mesh_idx]->mesh().bounding_box(); + + std::unique_ptr cursor = std::make_unique(mesh_hit, mesh_bbox, m_height_range_z_range, trafo_matrix, clp); + m_triangle_selectors[mesh_idx]->select_patch(facet_idx, std::move(cursor), new_state, trafo_matrix_not_translate, + m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); + } } m_triangle_selectors[mesh_idx]->request_update_render_data(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 6da818f88c..95b56e11fc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -112,9 +112,12 @@ public: protected: virtual void render_triangles(const Selection& selection) const; + void render_cursor(); void render_cursor_circle(); - void render_cursor_sphere(const Transform3d& trafo) const; + void render_cursor_sphere(const Transform3d &trafo) const; + void render_cursor_height_range(const Transform3d &trafo) const; + virtual void update_model_object() const = 0; virtual void update_from_model_object() = 0; @@ -137,7 +140,8 @@ protected: enum class ToolType { BRUSH, BUCKET_FILL, - SMART_FILL + SMART_FILL, + HEIGHT_RANGE }; struct ProjectedMousePosition @@ -151,6 +155,7 @@ protected: ToolType m_tool_type = ToolType::BRUSH; float m_smart_fill_angle = 30.f; float m_smart_fill_gap_area = 0.02f; + float m_height_range_z_range = 1.00f; bool m_paint_on_overhangs_only = false; float m_highlight_by_angle_threshold_deg = 0.f; @@ -166,6 +171,10 @@ protected: static constexpr float SmartFillGapAreaMin = 0.0f; static constexpr float SmartFillGapAreaMax = 1.f; + static constexpr float HeightRangeZRangeMin = 0.1f; + static constexpr float HeightRangeZRangeMax = 10.f; + static constexpr float HeightRangeZRangeStep = 0.1f; + // It stores the value of the previous mesh_id to which the seed fill was applied. // It is used to detect when the mouse has moved from one volume to another one. int m_seed_fill_last_mesh_id = -1;