From 2879f92cab0df3371b065aed0d044ad2db805fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 19 Mar 2024 20:21:43 +0100 Subject: [PATCH 01/10] Fix the painting gizmos overlaps the view toolbar. --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 1efb9035c7..fd9d3daf6c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -103,7 +103,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(25.f); + const float approx_height = m_imgui->scaled(26.3f); y = std::min(y, bottom_limit - approx_height); ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index c39eb4edc1..4fb5a5a6bd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -263,7 +263,7 @@ 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(22.0f); + const float approx_height = m_imgui->scaled(23.7f); y = std::min(y, bottom_limit - approx_height); ImGuiPureWrap::set_next_window_pos(x, y, ImGuiCond_Always); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 8998f33755..5f90c3459d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -79,7 +79,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit) if (! m_c->selection_info()->model_object()) return; - const float approx_height = m_imgui->scaled(12.5f); + const float approx_height = m_imgui->scaled(13.45f); 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); From 6d53b34efca00350268f6f266a78d3f7e1a26e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 19 Mar 2024 20:49:34 +0100 Subject: [PATCH 02/10] Fix very short sliders in the FDM support painting gizmo. It was broken by ad1510a30c3ebb29ef5118dbac40e0b5b023f6ed. --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index fd9d3daf6c..5ccad0bbba 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -115,7 +115,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l 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 autoset_slider_label_max_width = m_imgui->scaled(7.5f); - const float autoset_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); + const float autoset_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("highlight_by_angle"), false, autoset_slider_label_max_width).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); From d23e114aeda97f97f83646c95298df4aef36ace7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 19 Mar 2024 20:57:31 +0100 Subject: [PATCH 03/10] Fix clang warnings in the painting gizmos. --- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 7 +++---- src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 5ccad0bbba..4b61965454 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -459,7 +459,7 @@ void GLGizmoFdmSupports::update_model_object() const if (! mv->is_model_part()) continue; ++idx; - updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->supported_facets.set(*m_triangle_selectors[idx]); } if (updated) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 4fb5a5a6bd..adb89913e8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -509,7 +509,7 @@ void GLGizmoMmuSegmentation::update_model_object() const if (! mv->is_model_part()) continue; ++idx; - updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx]); } if (updated) { @@ -699,7 +699,7 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id)); const GLint position_id = shader->get_attrib_location("v_position"); if (position_id != -1) { - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)0)); + glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (GLvoid*)nullptr)); glsafe(::glEnableVertexAttribArray(position_id)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 7bec4c78b6..a8e51962dd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -104,7 +104,7 @@ public: // will be also extended to support additional states, requiring at least one state to remain free out of 19 states. static const constexpr size_t EXTRUDERS_LIMIT = 16; - const float get_cursor_radius_min() const override { return CursorRadiusMin; } + float get_cursor_radius_min() const override { return CursorRadiusMin; } protected: ColorRGBA get_cursor_sphere_left_button_color() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 31f38cc743..7ae82e4333 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -97,9 +97,9 @@ public: // after all volumes (including transparent ones) are rendered. virtual void render_painter_gizmo() = 0; - virtual const float get_cursor_radius_min() const { return CursorRadiusMin; } - virtual const float get_cursor_radius_max() const { return CursorRadiusMax; } - virtual const float get_cursor_radius_step() const { return CursorRadiusStep; } + virtual float get_cursor_radius_min() const { return CursorRadiusMin; } + virtual float get_cursor_radius_max() const { return CursorRadiusMax; } + virtual float get_cursor_radius_step() const { return CursorRadiusStep; } /// /// Implement when want to process mouse events in gizmo @@ -192,7 +192,6 @@ private: static std::shared_ptr s_sphere; - bool m_internal_stack_active = false; bool m_schedule_update = false; Vec2d m_last_mouse_click = Vec2d::Zero(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 5f90c3459d..080c2cb2ec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -201,7 +201,7 @@ void GLGizmoSeam::update_model_object() const if (! mv->is_model_part()) continue; ++idx; - updated |= mv->seam_facets.set(*m_triangle_selectors[idx].get()); + updated |= mv->seam_facets.set(*m_triangle_selectors[idx]); } if (updated) { From f253822707a814d114028ae2f782d1e60528a4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 28 Mar 2024 15:15:12 +0100 Subject: [PATCH 04/10] Small refactoring of Cursors in TriangleSelector. --- src/libslic3r/TriangleSelector.cpp | 82 ++++++++------------ src/libslic3r/TriangleSelector.hpp | 63 +++++++++------ src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 23 +++--- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 2 +- 4 files changed, 85 insertions(+), 85 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 302ce06fe5..ad292023bb 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -247,21 +247,15 @@ void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&c // have to pass it around. m_cursor = std::move(cursor); - // In case user changed cursor size since last time, update triangle edge limit. - // It is necessary to compare the internal radius in m_cursor! radius is in - // world coords and does not change after scaling. - if (m_old_cursor_radius_sqr != m_cursor->radius_sqr) { - set_edge_limit(std::sqrt(m_cursor->radius_sqr) / 5.f); - m_old_cursor_radius_sqr = m_cursor->radius_sqr; - } + // In case user changed cursor parameters size since last time, update triangle edge limit. + set_edge_limit(m_cursor->get_edge_limit()); const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); // Now start with the facet the pointer points to and check all adjacent facets. - std::vector facets_to_check; + std::vector facets_to_check = m_cursor->get_facets_to_select(facet_start, m_vertices, m_triangles, m_orig_size_vertices, m_orig_size_indices); facets_to_check.reserve(16); - facets_to_check.emplace_back(facet_start); // Keep track of facets of the original mesh we already processed. std::vector visited(m_orig_size_indices, false); // Breadth-first search around the hit point. facets_to_check may grow significantly large. @@ -293,13 +287,13 @@ bool TriangleSelector::is_facet_clipped(int facet_idx, const ClippingPlane &clp) } void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, const Transform3d& trafo_no_translate, - const ClippingPlane &clp, float seed_fill_angle, float highlight_by_angle_deg, - bool force_reselection) + const ClippingPlane &clp, float seed_fill_angle, + float highlight_by_angle_deg, ForceReselection force_reselection) { assert(facet_start < m_orig_size_indices); // Recompute seed fill only if the cursor is pointing on facet unselected by seed fill or a clipping plane is active. - if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active()) + if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active()) return; this->seed_fill_unselect_all_triangles(); @@ -451,19 +445,20 @@ void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int ver process_subtriangle(touching.second, Partition::Second); } -void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, const ClippingPlane &clp, bool propagate, bool force_reselection) -{ +void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_start, const ClippingPlane &clp, + BucketFillPropagate propagate, + ForceReselection force_reselection) { int start_facet_idx = select_unsplit_triangle(hit, facet_start); assert(start_facet_idx != -1); // Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill or a clipping plane is active. - if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection && !clp.is_active())) + if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active())) return; assert(!m_triangles[start_facet_idx].is_split()); TriangleStateType start_facet_state = m_triangles[start_facet_idx].get_state(); this->seed_fill_unselect_all_triangles(); - if (!propagate) { + if (propagate == BucketFillPropagate::NO) { m_triangles[start_facet_idx].select_by_seed_fill(); return; } @@ -878,7 +873,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei if (num_of_inside_vertices == 0 && ! m_cursor->is_pointer_in_triangle(*tr, m_vertices) - && ! m_cursor->is_edge_inside_cursor(*tr, m_vertices)) + && ! m_cursor->is_any_edge_inside_cursor(*tr, m_vertices)) return false; if (num_of_inside_vertices == 3) { @@ -949,8 +944,8 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors) // In case the object is non-uniformly scaled, transform the // points to world coords. - if (! m_cursor->uniform_scaling) { - for (size_t i=0; iuniform_scaling) { + for (size_t i = 0; i < pts.size(); ++i) { pts_transformed[i] = m_cursor->trafo * (*pts[i]); pts[i] = &pts_transformed[i]; } @@ -1011,16 +1006,21 @@ int TriangleSelector::Cursor::vertices_inside(const Triangle &tr, const std::vec return inside; } -// Is any edge inside Sphere cursor? -bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const -{ +inline std::array TriangleSelector::Cursor::transform_triangle(const Triangle &tr, const std::vector &vertices) const { std::array pts; - for (int i = 0; i < 3; ++i) { + for (size_t i = 0; i < 3; ++i) { pts[i] = vertices[tr.verts_idxs[i]].v; if (!this->uniform_scaling) pts[i] = this->trafo * pts[i]; } + return pts; +} + +// Is any edge inside Sphere cursor? +bool TriangleSelector::Sphere::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +{ + const std::array pts = this->transform_triangle(tr, vertices); for (int side = 0; side < 3; ++side) { const Vec3f &edge_a = pts[side]; const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0]; @@ -1031,16 +1031,10 @@ bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const s } // Is edge inside cursor? -bool TriangleSelector::Circle::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +bool TriangleSelector::Circle::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { - std::array pts; - for (int i = 0; i < 3; ++i) { - pts[i] = vertices[tr.verts_idxs[i]].v; - if (!this->uniform_scaling) - pts[i] = this->trafo * pts[i]; - } - - const Vec3f &p = this->center; + const std::array pts = this->transform_triangle(tr, vertices); + const Vec3f &p = this->center; for (int side = 0; side < 3; ++side) { const Vec3f &a = pts[side]; const Vec3f &b = pts[side < 2 ? side + 1 : 0]; @@ -1216,7 +1210,7 @@ void TriangleSelector::reset() void TriangleSelector::set_edge_limit(float edge_limit) { - m_edge_limit_sqr = std::pow(edge_limit, 2.f); + m_edge_limit_sqr = Slic3r::sqr(edge_limit); } int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const TriangleStateType state) { @@ -1890,6 +1884,8 @@ TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const radius_sqr = Slic3r::sqr(radius_world); trafo_normal = trafo.linear().inverse().transpose(); } + + m_edge_limit = std::sqrt(radius_sqr) / 5.f; } TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, const Vec3f& source_, float radius_world, const Transform3d& trafo_, const ClippingPlane &clipping_plane_) @@ -2048,15 +2044,9 @@ bool line_plane_intersection(const Vec3f &line_a, const Vec3f &line_b, const Vec return false; } -bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +bool TriangleSelector::Capsule3D::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { - std::array pts; - for (int i = 0; i < 3; ++i) { - pts[i] = vertices[tr.verts_idxs[i]].v; - if (!this->uniform_scaling) - pts[i] = this->trafo * pts[i]; - } - + const std::array pts = this->transform_triangle(tr, vertices); for (int side = 0; side < 3; ++side) { const Vec3f &edge_a = pts[side]; const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0]; @@ -2068,15 +2058,8 @@ bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, cons } // Is edge inside cursor? -bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const +bool TriangleSelector::Capsule2D::is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const { - std::array pts; - for (int i = 0; i < 3; ++i) { - pts[i] = vertices[tr.verts_idxs[i]].v; - if (!this->uniform_scaling) - pts[i] = this->trafo * pts[i]; - } - const Vec3f centers_diff = this->second_center - this->first_center; // Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center. const Vec3f rectangle_da_dir = centers_diff.cross(this->dir); @@ -2095,6 +2078,7 @@ bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, cons return false; }; + const std::array pts = this->transform_triangle(tr, vertices); for (int side = 0; side < 3; ++side) { const Vec3f &edge_a = pts[side]; const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0]; diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 2cd5a0a980..2605d28a52 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -67,12 +67,22 @@ protected: struct Vertex; public: - enum CursorType { + enum class CursorType { CIRCLE, SPHERE, POINTER }; + enum class ForceReselection { + NO, + YES + }; + + enum class BucketFillPropagate { + NO, + YES + }; + struct ClippingPlane { Vec3f normal; @@ -91,14 +101,21 @@ public: Cursor() = delete; virtual ~Cursor() = default; + float get_edge_limit() { return m_edge_limit; }; + bool is_pointer_in_triangle(const Triangle &tr, const std::vector &vertices) const; + std::array transform_triangle(const Triangle &tr, const std::vector &vertices) const; virtual bool is_mesh_point_inside(const Vec3f &point) const = 0; virtual bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const = 0; virtual int vertices_inside(const Triangle &tr, const std::vector &vertices) const; - virtual bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const = 0; + virtual bool is_any_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) const = 0; virtual bool is_facet_visible(int facet_idx, const std::vector &face_normals) const = 0; + virtual 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 { + return { facet_idx }; + }; + static bool is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector &face_normals); protected: @@ -115,6 +132,8 @@ public: ClippingPlane clipping_plane; // Clipping plane to limit painting to not clipped facets only + float m_edge_limit; + friend TriangleSelector; }; @@ -174,7 +193,7 @@ public: ~Sphere() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) 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; } }; @@ -187,9 +206,8 @@ public: ~Circle() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_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 - { + 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 TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals); } }; @@ -204,7 +222,7 @@ public: ~Capsule3D() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_edge_inside_cursor(const Triangle &tr, const std::vector &vertices) 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; } }; @@ -218,9 +236,8 @@ public: ~Capsule2D() override = default; bool is_mesh_point_inside(const Vec3f &point) const override; - bool is_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 - { + 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 TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals); } }; @@ -298,19 +315,19 @@ public: bool triangle_splitting, // If triangles will be split base on the cursor or not float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. - void seed_fill_select_triangles(const Vec3f &hit, // point where to start - int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to - const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation - const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only - float seed_fill_angle, // the maximal angle between two facets to be painted by the same color - float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. - bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle + void seed_fill_select_triangles(const Vec3f &hit, // point where to start + int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to + const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation + const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only + float seed_fill_angle, // the maximal angle between two facets to be painted by the same color + float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. + ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle - void bucket_fill_select_triangles(const Vec3f &hit, // point where to start - int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to - const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only - bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to. - bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle + void bucket_fill_select_triangles(const Vec3f &hit, // point where to start + int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to + const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only + BucketFillPropagate propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to. + ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle bool has_facets(TriangleStateType state) const; static bool has_facets(const TriangleSplittingData &data, TriangleStateType test_state); @@ -450,8 +467,6 @@ protected: int m_orig_size_indices = 0; std::unique_ptr m_cursor; - // Zero indicates an uninitialized state. - float m_old_cursor_radius_sqr = 0; // Private functions: private: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index e871e1a539..c130973eec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -139,9 +139,9 @@ void GLGizmoPainterBase::render_cursor() return; if (m_tool_type == ToolType::BRUSH) { - if (m_cursor_type == TriangleSelector::SPHERE) + if (m_cursor_type == TriangleSelector::CursorType::SPHERE) render_cursor_sphere(trafo_matrices[m_rr.mesh_id]); - else if (m_cursor_type == TriangleSelector::CIRCLE) + else if (m_cursor_type == TriangleSelector::CursorType::CIRCLE) render_cursor_circle(); } } @@ -480,7 +480,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix(); m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle, - m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; } @@ -561,13 +561,14 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const Vec3f mesh_hit = projected_mouse_position.mesh_hit; const int facet_idx = int(projected_mouse_position.facet_idx); m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state); - if (m_tool_type == ToolType::SMART_FILL) + if (m_tool_type == ToolType::SMART_FILL) { m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, - m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); - else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true); - else if (m_tool_type == ToolType::BUCKET_FILL) - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true); + (m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f), TriangleSelector::ForceReselection::YES); + } else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) { + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES); + } else if (m_tool_type == ToolType::BUCKET_FILL) { + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES); + } m_seed_fill_last_mesh_id = -1; } @@ -649,9 +650,9 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) - m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false); + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, TriangleSelector::BucketFillPropagate::NO); else if (m_tool_type == ToolType::BUCKET_FILL) - m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true); + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, TriangleSelector::BucketFillPropagate::YES); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; return true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 7ae82e4333..2f209ca327 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -132,7 +132,7 @@ protected: // For each model-part volume, store status and division of the triangles. std::vector> m_triangle_selectors; - TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE; + TriangleSelector::CursorType m_cursor_type = TriangleSelector::CursorType::SPHERE; enum class ToolType { BRUSH, From 535cbb2567ba7c2daefc8519908f01b01e93ace5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 19 Mar 2024 20:59:41 +0100 Subject: [PATCH 05/10] SPE-2003: Improve the smart fill in the multi-material painting gizmo to automatically select tiny triangles even if they exceed the angle limit. --- src/libslic3r/TriangleSelector.cpp | 128 +++++++++++++++++- src/libslic3r/TriangleSelector.hpp | 12 ++ src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 5 +- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 21 ++- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 4 + 6 files changed, 160 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index ad292023bb..17c107f980 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -241,7 +241,7 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&cursor, TriangleStateType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg) { - assert(facet_start < m_orig_size_indices); + assert(this->is_original_triangle(facet_start)); // Save current cursor center, squared radius and camera direction, so we don't // have to pass it around. @@ -286,11 +286,25 @@ bool TriangleSelector::is_facet_clipped(int facet_idx, const ClippingPlane &clp) return false; } +bool TriangleSelector::is_any_neighbor_selected_by_seed_fill(const Triangle &triangle) { + size_t triangle_idx = &triangle - m_triangles.data(); + assert(triangle_idx < m_triangles.size()); + + for (int neighbor_idx: m_neighbors[triangle_idx]) { + assert(neighbor_idx >= -1); + + if (neighbor_idx >= 0 && m_triangles[neighbor_idx].is_selected_by_seed_fill()) + return true; + } + + return false; +} + void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, const Transform3d& trafo_no_translate, - const ClippingPlane &clp, float seed_fill_angle, + const ClippingPlane &clp, float seed_fill_angle, float seed_fill_gap_area, float highlight_by_angle_deg, ForceReselection force_reselection) { - assert(facet_start < m_orig_size_indices); + assert(this->is_original_triangle(facet_start)); // Recompute seed fill only if the cursor is pointing on facet unselected by seed fill or a clipping plane is active. if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && force_reselection == ForceReselection::NO && !clp.is_active()) @@ -306,6 +320,9 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); + // Facets that need to be checked for gap filling. + std::vector gap_fill_candidate_facets; + // Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor. while (!facet_queue.empty()) { int current_facet = facet_queue.front(); @@ -317,14 +334,15 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) { assert(split_triangle_idx < int(m_triangles[current_facet].children.size())); assert(m_triangles[current_facet].children[split_triangle_idx] < int(m_triangles.size())); - if (int child = m_triangles[current_facet].children[split_triangle_idx]; !visited[child]) + if (int child = m_triangles[current_facet].children[split_triangle_idx]; !visited[child]) { // Child triangle shares normal with its parent. Select it. facet_queue.push(child); + } } } else m_triangles[current_facet].select_by_seed_fill(); - if (current_facet < m_orig_size_indices) + if (this->is_original_triangle(current_facet)) { // Propagate over the original triangles. for (int neighbor_idx : m_neighbors[current_facet]) { assert(neighbor_idx >= -1); @@ -332,13 +350,102 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st // Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do. const Vec3f &n1 = m_face_normals[m_triangles[neighbor_idx].source_triangle]; const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle]; - if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) + if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) { facet_queue.push(neighbor_idx); + } else if (seed_fill_gap_area > 0. && get_triangle_area(m_triangles[neighbor_idx]) <= seed_fill_gap_area) { + gap_fill_candidate_facets.emplace_back(neighbor_idx); + } } } + } } + visited[current_facet] = true; } + + seed_fill_fill_gaps(gap_fill_candidate_facets, seed_fill_gap_area); +} + +void TriangleSelector::seed_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, const float seed_fill_gap_area) { + std::vector visited(m_triangles.size(), false); + + for (const int starting_facet_idx: gap_fill_candidate_facets) { + const Triangle &starting_facet = m_triangles[starting_facet_idx]; + + // If starting_facet_idx was visited from any facet, then we can skip it. + if (visited[starting_facet_idx]) + continue; + + // In the way how gap_fill_candidate_facets is filled, neither of the following two conditions should ever be met. + // But both of those conditions are here to allow more general usage of this method. + if (starting_facet.is_selected_by_seed_fill() || starting_facet.is_split()) { + // Already selected by seed fill or split facet, so no additional actions are required. + visited[starting_facet_idx] = true; + continue; + } else if (!is_any_neighbor_selected_by_seed_fill(starting_facet)) { + // No neighbor triangles are selected by seed fill, so we will skip them for now. + continue; + } + + // Now we have a triangle that has at least one neighbor selected by seed fill. + // So we start depth-first (it doesn't need to be depth-first) traversal of neighbors to check + // if the total area of unselected triangles by seed fill meets the threshold. + double total_gap_area = 0.; + std::queue facet_queue; + std::vector gap_facets; + + facet_queue.push(starting_facet_idx); + while (!facet_queue.empty()) { + const int current_facet_idx = facet_queue.front(); + const Triangle ¤t_facet = m_triangles[current_facet_idx]; + facet_queue.pop(); + + if (visited[current_facet_idx]) + continue; + + if (this->is_original_triangle(current_facet_idx)) + total_gap_area += get_triangle_area(current_facet); + + // We exceed maximum gap area. + if (total_gap_area > seed_fill_gap_area) { + // It is necessary to set every facet inside gap_facets unvisited. + // Otherwise, we incorrectly select facets that are in a gap that is bigger + // than seed_fill_gap_area. + for (const int gap_facet_idx : gap_facets) + visited[gap_facet_idx] = false; + + gap_facets.clear(); + break; + } + + if (current_facet.is_split()) { + for (int split_triangle_idx = 0; split_triangle_idx <= current_facet.number_of_split_sides(); ++split_triangle_idx) { + assert(split_triangle_idx < int(current_facet.children.size())); + assert(current_facet.children[split_triangle_idx] < int(m_triangles.size())); + if (int child = current_facet.children[split_triangle_idx]; !visited[child]) + facet_queue.push(child); + } + } else if (total_gap_area < seed_fill_gap_area) { + gap_facets.emplace_back(current_facet_idx); + } + + if (this->is_original_triangle(current_facet_idx)) { + // Propagate over the original triangles. + for (int neighbor_idx: m_neighbors[current_facet_idx]) { + assert(neighbor_idx >= -1); + if (neighbor_idx >= 0 && !visited[neighbor_idx] && !m_triangles[neighbor_idx].is_selected_by_seed_fill()) + facet_queue.push(neighbor_idx); + } + } + + visited[current_facet_idx] = true; + } + + for (int to_seed_idx : gap_facets) + m_triangles[to_seed_idx].select_by_seed_fill(); + + gap_facets.clear(); + } } void TriangleSelector::precompute_all_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &neighbors_out, std::vector &neighbors_propagated_out) const @@ -913,7 +1020,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei } void TriangleSelector::set_facet(int facet_idx, TriangleStateType state) { - assert(facet_idx < m_orig_size_indices); + assert(this->is_original_triangle(facet_idx)); undivide_triangle(facet_idx); assert(! m_triangles[facet_idx].is_split()); m_triangles[facet_idx].set_state(state); @@ -1866,6 +1973,13 @@ void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) } } +double TriangleSelector::get_triangle_area(const Triangle &triangle) const { + const stl_vertex &v0 = m_vertices[triangle.verts_idxs[0]].v; + const stl_vertex &v1 = m_vertices[triangle.verts_idxs[1]].v; + const stl_vertex &v2 = m_vertices[triangle.verts_idxs[2]].v; + return (v1 - v0).cross(v2 - v0).norm() / 2.; +} + TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_) : source{source_}, trafo{trafo_.cast()}, clipping_plane{clipping_plane_} { diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 2605d28a52..7ad9f2cca5 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -320,6 +320,7 @@ public: const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only float seed_fill_angle, // the maximal angle between two facets to be painted by the same color + float seed_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected. float highlight_by_angle_deg = 0.f, // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle @@ -381,6 +382,9 @@ public: // The operation may merge split triangles if they are being assigned the same color. void seed_fill_apply_on_triangles(TriangleStateType new_state); + // Compute total area of the triangle. + double get_triangle_area(const Triangle &triangle) const; + protected: // Triangle and info about how it's split. class Triangle { @@ -498,6 +502,9 @@ private: void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector &touching_subtriangles_out) const; void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector &touching_edges_out) const; + // Check if the triangle index is the original triangle from mesh, or it was additionally created by splitting. + bool is_original_triangle(int triangle_idx) const { return triangle_idx < m_orig_size_indices; } + #ifndef NDEBUG bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const; bool verify_triangle_midpoints(const Triangle& tr) const; @@ -516,6 +523,11 @@ private: void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &edges_out) const; + bool is_any_neighbor_selected_by_seed_fill(const Triangle &triangle); + + void seed_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, // Facet of the original mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected). + float seed_fill_gap_area); // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + int m_free_triangles_head { -1 }; int m_free_vertices_head { -1 }; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 4b61965454..dde015484d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -301,11 +301,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) - for (auto &triangle_selector : m_triangle_selectors) { + if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.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/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index adb89913e8..fc0ddbb420 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -121,6 +121,7 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["tool_bucket_fill"] = _u8L("Bucket fill"); 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"); init_extruders_data(); @@ -452,15 +453,27 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } else if(m_tool_type == ToolType::SMART_FILL) { ImGui::AlignTextToFramePadding(); ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":"); - std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo," - "placed after the number with no whitespace in between."); + std::string format_str_angle = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo," + "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) - for (auto &triangle_selector : m_triangle_selectors) { + if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, 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::AlignTextToFramePadding(); + ImGuiPureWrap::text(m_desc["smart_fill_gap_area"] + ":"); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + if (m_imgui->slider_float("##smart_fill_gap_area", &m_smart_fill_gap_area, SmartFillGapAreaMin, SmartFillGapAreaMax, "%.2f", 1.0f, true)) { + 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 c130973eec..d2b4c50d39 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -480,7 +480,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix(); m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle, - m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES); + m_smart_fill_gap_area, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; } @@ -562,7 +562,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const int facet_idx = int(projected_mouse_position.facet_idx); m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state); if (m_tool_type == ToolType::SMART_FILL) { - m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, + m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, m_smart_fill_gap_area, (m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f), TriangleSelector::ForceReselection::YES); } else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) { m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES); @@ -647,7 +647,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous assert(m_rr.mesh_id < 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_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, m_smart_fill_gap_area, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, TriangleSelector::BucketFillPropagate::NO); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 2f209ca327..6da818f88c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -150,6 +150,7 @@ protected: bool m_triangle_splitting_enabled = true; ToolType m_tool_type = ToolType::BRUSH; float m_smart_fill_angle = 30.f; + float m_smart_fill_gap_area = 0.02f; bool m_paint_on_overhangs_only = false; float m_highlight_by_angle_threshold_deg = 0.f; @@ -162,6 +163,9 @@ protected: static constexpr float SmartFillAngleMax = 90.f; static constexpr float SmartFillAngleStep = 1.f; + static constexpr float SmartFillGapAreaMin = 0.0f; + static constexpr float SmartFillGapAreaMax = 1.f; + // 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; 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 06/10] 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; From d25ceeeb7a2e6476326744699724a69bb6ef1bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 28 Mar 2024 23:11:32 +0100 Subject: [PATCH 07/10] SPE-2003: Add an option to limit selection by the bucket fill using the angle between triangles. Also, it adds to the bucket fill the automatic selection of tiny triangles even if they exceed the angle limit. --- src/libslic3r/TriangleSelector.cpp | 133 +++++++++++++++--- src/libslic3r/TriangleSelector.hpp | 11 ++ .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 10 +- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 33 +++-- 4 files changed, 150 insertions(+), 37 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 105eeb5b82..3c4cd62838 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -316,7 +316,7 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_st std::queue facet_queue; facet_queue.push(facet_start); - const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; + const float facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON; const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg)); Vec3f vec_down = (trafo_no_translate.inverse() * -Vec3d::UnitZ()).normalized().cast(); @@ -552,9 +552,29 @@ void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int ver process_subtriangle(touching.second, Partition::Second); } +// Returns all triangles that are touching the given facet. +std::vector TriangleSelector::get_all_touching_triangles(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) const { + assert(facet_idx != -1 && facet_idx < int(m_triangles.size())); + assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); + + const Vec3i vertices = { m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2] }; + + std::vector touching_triangles; + append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles); + append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles); + append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles); + + for (int neighbor_idx: neighbors_propagated) { + if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split()) + touching_triangles.emplace_back(neighbor_idx); + } + + return touching_triangles; +} + void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_start, const ClippingPlane &clp, - BucketFillPropagate propagate, - ForceReselection force_reselection) { + float bucket_fill_angle, float bucket_fill_gap_area, + BucketFillPropagate propagate, ForceReselection force_reselection) { int start_facet_idx = select_unsplit_triangle(hit, facet_start); assert(start_facet_idx != -1); // Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill or a clipping plane is active. @@ -570,26 +590,15 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_ return; } - auto get_all_touching_triangles = [this](int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) -> std::vector { - assert(facet_idx != -1 && facet_idx < int(m_triangles.size())); - assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors)); - std::vector touching_triangles; - Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]}; - append_touching_subtriangles(neighbors(0), vertices(1), vertices(0), touching_triangles); - append_touching_subtriangles(neighbors(1), vertices(2), vertices(1), touching_triangles); - append_touching_subtriangles(neighbors(2), vertices(0), vertices(2), touching_triangles); - - for (int neighbor_idx : neighbors_propagated) - if (neighbor_idx != -1 && !m_triangles[neighbor_idx].is_split()) - touching_triangles.emplace_back(neighbor_idx); - - return touching_triangles; - }; + const float facet_angle_limit = std::cos(Geometry::deg2rad(bucket_fill_angle)) - EPSILON; auto [neighbors, neighbors_propagated] = this->precompute_all_neighbors(); std::vector visited(m_triangles.size(), false); std::queue facet_queue; + // Facets that need to be checked for gap filling. + std::vector gap_fill_candidate_facets; + facet_queue.push(start_facet_idx); while (!facet_queue.empty()) { int current_facet = facet_queue.front(); @@ -599,18 +608,98 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_ if (!visited[current_facet]) { m_triangles[current_facet].select_by_seed_fill(); - std::vector touching_triangles = get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]); - for(const int tr_idx : touching_triangles) { + std::vector touching_triangles = this->get_all_touching_triangles(current_facet, neighbors[current_facet], neighbors_propagated[current_facet]); + for (const int tr_idx: touching_triangles) { if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || is_facet_clipped(tr_idx, clp)) continue; - assert(!m_triangles[tr_idx].is_split()); - facet_queue.push(tr_idx); + // Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do. + const Vec3f &n1 = m_face_normals[m_triangles[tr_idx].source_triangle]; + const Vec3f &n2 = m_face_normals[m_triangles[current_facet].source_triangle]; + if (std::clamp(n1.dot(n2), 0.f, 1.f) >= facet_angle_limit) { + assert(!m_triangles[tr_idx].is_split()); + facet_queue.push(tr_idx); + } else if (bucket_fill_gap_area > 0. && get_triangle_area(m_triangles[tr_idx]) <= bucket_fill_gap_area) { + gap_fill_candidate_facets.emplace_back(tr_idx); + } } } visited[current_facet] = true; } + + bucket_fill_fill_gaps(gap_fill_candidate_facets, bucket_fill_gap_area, start_facet_state, neighbors, neighbors_propagated); +} + +void TriangleSelector::bucket_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, const float bucket_fill_gap_area, + const TriangleStateType start_facet_state, const std::vector &neighbors, + const std::vector &neighbors_propagated) { + std::vector visited(m_triangles.size(), false); + + for (const int starting_facet_idx: gap_fill_candidate_facets) { + const Triangle &starting_facet = m_triangles[starting_facet_idx]; + + // If starting_facet_idx was visited from any facet, then we can skip it. + if (visited[starting_facet_idx]) + continue; + + // In the way how gap_fill_candidate_facets is filled, neither of the following two conditions should ever be met. + // But both of those conditions are here to allow more general usage of this method. + if (starting_facet.is_selected_by_seed_fill() || starting_facet.is_split()) { + // Already selected by seed fill or split facet, so no additional actions are required. + visited[starting_facet_idx] = true; + continue; + } + + // In the way how bucket_fill_select_triangles() is implemented, all gap_fill_candidate_facets + // have at least one neighbor selected by seed fill. + // So we start depth-first (it doesn't need to be depth-first) traversal of neighbors to check + // if the total area of unselected triangles by seed fill meets the threshold. + double total_gap_area = 0.; + std::queue facet_queue; + std::vector gap_facets; + + facet_queue.push(starting_facet_idx); + while (!facet_queue.empty()) { + const int current_facet_idx = facet_queue.front(); + const Triangle ¤t_facet = m_triangles[current_facet_idx]; + facet_queue.pop(); + + if (visited[current_facet_idx]) + continue; + + total_gap_area += get_triangle_area(current_facet); + + // We exceed maximum gap area. + if (total_gap_area > bucket_fill_gap_area) { + // It is necessary to set every facet inside gap_facets unvisited. + // Otherwise, we incorrectly select facets that are in a gap that is bigger + // than bucket_fill_gap_area. + for (const int gap_facet_idx : gap_facets) + visited[gap_facet_idx] = false; + + gap_facets.clear(); + break; + } + + gap_facets.emplace_back(current_facet_idx); + + std::vector touching_triangles = this->get_all_touching_triangles(current_facet_idx, neighbors[current_facet_idx], neighbors_propagated[current_facet_idx]); + for (const int tr_idx: touching_triangles) { + if (tr_idx < 0 || visited[tr_idx] || m_triangles[tr_idx].get_state() != start_facet_state || m_triangles[tr_idx].is_selected_by_seed_fill()) + continue; + + facet_queue.push(tr_idx); + } + + visited[current_facet_idx] = true; + } + + for (int to_seed_idx : gap_facets) + m_triangles[to_seed_idx].select_by_seed_fill(); + + gap_facets.clear(); + } } // Selects either the whole triangle (discarding any children it had), or divides diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index afafc2ad72..e0849fd13b 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -348,6 +348,8 @@ public: void bucket_fill_select_triangles(const Vec3f &hit, // point where to start int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only + float bucket_fill_angle, // the maximal angle between two facets to be painted by the same color + float bucket_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected. BucketFillPropagate propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to. ForceReselection force_reselection = ForceReselection::NO); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle @@ -523,6 +525,9 @@ private: void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector &touching_subtriangles_out) const; void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector &touching_edges_out) const; + // Returns all triangles that are touching the given facet. + std::vector get_all_touching_triangles(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) const; + // Check if the triangle index is the original triangle from mesh, or it was additionally created by splitting. bool is_original_triangle(int triangle_idx) const { return triangle_idx < m_orig_size_indices; } @@ -549,6 +554,12 @@ private: void seed_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, // Facet of the original mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected). float seed_fill_gap_area); // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + void bucket_fill_fill_gaps(const std::vector &gap_fill_candidate_facets, // Facet of the mesh (unsplit), which needs to be checked if the surrounding gap can be filled (selected). + float bucket_fill_gap_area, // The maximal area that will be automatically selected when the surrounding triangles have already been selected. + TriangleStateType start_facet_state, // The state of the starting facet that determines which neighbors to consider. + const std::vector &neighbors, + const std::vector &neighbors_propagate); + int m_free_triangles_head { -1 }; int m_free_vertices_head { -1 }; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index ba3283f2e0..a2465bfafa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -123,6 +123,10 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["smart_fill_angle"] = _u8L("Smart fill angle"); m_desc["smart_fill_gap_area"] = _u8L("Smart fill gap"); + + m_desc["bucket_fill_angle"] = _u8L("Bucket fill angle"); + m_desc["bucket_fill_gap_area"] = _u8L("Bucket fill gap"); + m_desc["split_triangles"] = _u8L("Split triangles"); m_desc["height_range_z_range"] = _u8L("Height range"); @@ -475,9 +479,9 @@ 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 || m_tool_type == ToolType::BUCKET_FILL) { ImGui::AlignTextToFramePadding(); - ImGuiPureWrap::text(m_desc["smart_fill_angle"] + ":"); + ImGuiPureWrap::text((m_tool_type == ToolType::SMART_FILL ? m_desc["smart_fill_angle"] : m_desc["bucket_fill_angle"]) + ":"); std::string format_str_angle = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo," "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_left_width); @@ -490,7 +494,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott } ImGui::AlignTextToFramePadding(); - ImGuiPureWrap::text(m_desc["smart_fill_gap_area"] + ":"); + ImGuiPureWrap::text((m_tool_type == ToolType::SMART_FILL ? m_desc["smart_fill_gap_area"] : m_desc["bucket_fill_gap_area"]) + ":"); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); if (m_imgui->slider_float("##smart_fill_gap_area", &m_smart_fill_gap_area, SmartFillGapAreaMin, SmartFillGapAreaMax, "%.2f", 1.0f, true)) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 8199bf60ac..36c309c3f3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -540,18 +540,27 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous : std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max()); m_parent.set_as_dirty(); return true; - } else if (m_tool_type == ToolType::SMART_FILL) { + } else if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL) { m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin) : 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(); - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); - const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix(); - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle, - m_smart_fill_gap_area, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix(); + const TriangleSelector::ClippingPlane &clipping_plane = this->get_clipping_plane_in_volume_coordinates(trafo_matrix); + + if (m_tool_type == ToolType::SMART_FILL) { + const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clipping_plane, m_smart_fill_angle, m_smart_fill_gap_area, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES); + } else { + assert(m_tool_type == ToolType::BUCKET_FILL); + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clipping_plane, m_smart_fill_angle, m_smart_fill_gap_area, + TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES); + } + m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; } @@ -641,9 +650,9 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, m_smart_fill_gap_area, (m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f), TriangleSelector::ForceReselection::YES); } else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) { - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES); + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_smart_fill_angle, m_smart_fill_gap_area, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES); } else if (m_tool_type == ToolType::BUCKET_FILL) { - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES); + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_smart_fill_angle, m_smart_fill_gap_area, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES); } m_seed_fill_last_mesh_id = -1; @@ -736,9 +745,9 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, m_smart_fill_gap_area, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) - m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, TriangleSelector::BucketFillPropagate::NO); + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_smart_fill_angle, m_smart_fill_gap_area, TriangleSelector::BucketFillPropagate::NO); else if (m_tool_type == ToolType::BUCKET_FILL) - m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, TriangleSelector::BucketFillPropagate::YES); + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_smart_fill_angle, m_smart_fill_gap_area, TriangleSelector::BucketFillPropagate::YES); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; return true; From 8cd2e8264a86778f5ad63faf0f0673b9869f06ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 28 Mar 2024 23:45:38 +0100 Subject: [PATCH 08/10] Fix a few MSVC compiler warnings. --- bundled_deps/miniz/miniz.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundled_deps/miniz/miniz.c b/bundled_deps/miniz/miniz.c index 09794bea02..4905189aa4 100644 --- a/bundled_deps/miniz/miniz.c +++ b/bundled_deps/miniz/miniz.c @@ -7945,7 +7945,7 @@ mz_uint mz_zip_reader_get_filename_from_extra(mz_zip_archive* pZip, mz_uint file while (p_nf + 4 < e) { mz_uint16 len = ((mz_uint16)p_nf[2]) | ((mz_uint16)p_nf[3] << 8); if (p_nf[0] == '\x75' && p_nf[1] == '\x70' && len >= 5 && p_nf + 4 + len < e && p_nf[4] == '\x01') { - mz_uint length = MZ_MIN(len - 5, extra_buf_size - 1); + mz_uint length = MZ_MIN((mz_uint)len - 5, extra_buf_size - 1); memcpy(buffer, p_nf + 9, length); return length; } From 72f47f79630496955010e6653e0f7e2c353887b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 2 Apr 2024 12:24:22 +0200 Subject: [PATCH 09/10] Optimize painting using single triangle brush. Especially on the detail models, painting using the single triangle brush is noticeably faster. --- src/libslic3r/TriangleSelector.cpp | 57 +++++++++++++++----- src/libslic3r/TriangleSelector.hpp | 12 ++++- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 6 ++- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 3c4cd62838..197e84dc81 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -583,11 +583,17 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f &hit, int facet_ assert(!m_triangles[start_facet_idx].is_split()); TriangleStateType start_facet_state = m_triangles[start_facet_idx].get_state(); - this->seed_fill_unselect_all_triangles(); if (propagate == BucketFillPropagate::NO) { + if (m_triangle_selected_by_seed_fill != -1) + this->seed_fill_unselect_triangle(m_triangle_selected_by_seed_fill); + m_triangles[start_facet_idx].select_by_seed_fill(); + m_triangle_selected_by_seed_fill = start_facet_idx; return; + } else { + m_triangle_selected_by_seed_fill = -1; + this->seed_fill_unselect_all_triangles(); } const float facet_angle_limit = std::cos(Geometry::deg2rad(bucket_fill_angle)) - EPSILON; @@ -1286,42 +1292,43 @@ void TriangleSelector::undivide_triangle(int facet_idx) } } -void TriangleSelector::remove_useless_children(int facet_idx) -{ +// Returns true when some triangle during recursive descending was removed (undivided). +bool TriangleSelector::remove_useless_children(int facet_idx) { // Check that all children are leafs of the same type. If not, try to - // make them (recursive call). Remove them if sucessful. + // make them (recursive call). Remove them if successful. assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid()); - Triangle& tr = m_triangles[facet_idx]; + Triangle &tr = m_triangles[facet_idx]; - if (! tr.is_split()) { + if (!tr.is_split()) { // This is a leaf, there nothing to do. This can happen during the // first (non-recursive call). Shouldn't otherwise. - return; + return false; } // Call this for all non-leaf children. - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + bool children_removed = false; + for (int child_idx = 0; child_idx <= tr.number_of_split_sides(); ++child_idx) { assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid()); if (m_triangles[tr.children[child_idx]].is_split()) - remove_useless_children(tr.children[child_idx]); + children_removed |= remove_useless_children(tr.children[child_idx]); } - // Return if a child is not leaf or two children differ in type. TriangleStateType first_child_type = TriangleStateType::NONE; - for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + for (int child_idx = 0; child_idx <= tr.number_of_split_sides(); ++child_idx) { if (m_triangles[tr.children[child_idx]].is_split()) - return; + return children_removed; if (child_idx == 0) first_child_type = m_triangles[tr.children[0]].get_state(); else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) - return; + return children_removed; } // If we got here, the children can be removed. undivide_triangle(facet_idx); tr.set_state(first_child_type); + return true; } void TriangleSelector::garbage_collect() @@ -2050,7 +2057,22 @@ void TriangleSelector::seed_fill_unselect_all_triangles() triangle.unselect_by_seed_fill(); } +void TriangleSelector::seed_fill_unselect_triangle(const int facet_idx) { + assert(facet_idx > 0 && facet_idx < m_triangles.size()); + Triangle &triangle = m_triangles[facet_idx]; + + assert(!triangle.is_split()); + if (!triangle.is_split()) + triangle.unselect_by_seed_fill(); +} + void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) { + if (m_triangle_selected_by_seed_fill != -1) { + this->seed_fill_apply_on_single_triangle(new_state, m_triangle_selected_by_seed_fill); + m_triangle_selected_by_seed_fill = -1; + return; + } + for (Triangle &triangle : m_triangles) if (!triangle.is_split() && triangle.is_selected_by_seed_fill()) triangle.set_state(new_state); @@ -2062,6 +2084,15 @@ void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) } } +void TriangleSelector::seed_fill_apply_on_single_triangle(TriangleStateType new_state, const int facet_idx) { + assert(facet_idx > 0 && facet_idx < m_triangles.size()); + + if (Triangle &triangle = m_triangles[facet_idx]; !triangle.is_split() && triangle.is_selected_by_seed_fill()) { + triangle.set_state(new_state); + remove_useless_children(triangle.source_triangle); + } +} + double TriangleSelector::get_triangle_area(const Triangle &triangle) const { const stl_vertex &v0 = m_vertices[triangle.verts_idxs[0]].v; const stl_vertex &v1 = m_vertices[triangle.verts_idxs[1]].v; diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index e0849fd13b..534aa33ec7 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -401,10 +401,17 @@ public: // For all triangles, remove the flag indicating that the triangle was selected by seed fill. void seed_fill_unselect_all_triangles(); + // Remove the flag indicating that the triangle was selected by seed fill. + void seed_fill_unselect_triangle(int facet_idx); + // For all triangles selected by seed fill, set new TriangleStateType and remove flag indicating that triangle was selected by seed fill. // The operation may merge split triangles if they are being assigned the same color. void seed_fill_apply_on_triangles(TriangleStateType new_state); + // For the triangle selected by seed fill, set a new TriangleStateType and remove the flag indicating that the triangle was selected by seed fill. + // The operation may merge a split triangle if it is being assigned the same color. + void seed_fill_apply_on_single_triangle(TriangleStateType new_state, const int facet_idx); + // Compute total area of the triangle. double get_triangle_area(const Triangle &triangle) const; @@ -495,13 +502,16 @@ protected: std::unique_ptr m_cursor; + // Single triangle selected by seed fill. It is used to optimize painting using a single triangle brush. + int m_triangle_selected_by_seed_fill = -1; + // Private functions: private: bool select_triangle(int facet_idx, TriangleStateType type, bool triangle_splitting); bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, TriangleStateType type, bool triangle_splitting); void undivide_triangle(int facet_idx); void split_triangle(int facet_idx, const Vec3i &neighbors); - void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. + bool remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const; int push_triangle(int a, int b, int c, int source_triangle, TriangleStateType state = TriangleStateType::NONE); void perform_split(int facet_idx, const Vec3i &neighbors, TriangleStateType old_state); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 36c309c3f3..5d608aad86 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -643,9 +643,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous 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) { 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); + const Vec3f mesh_hit = projected_mouse_position.mesh_hit; + const int facet_idx = int(projected_mouse_position.facet_idx); + m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state); + if (m_tool_type == ToolType::SMART_FILL) { m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, m_smart_fill_gap_area, (m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f), TriangleSelector::ForceReselection::YES); From 17c3e31f72561f4e30566b2595e524b80af2d2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 14 Oct 2024 11:04:30 +0200 Subject: [PATCH 10/10] SPE-2003: Several improvements to multi-material painting gizmo. --- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 44 ++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 22 +++++----- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp | 8 ++-- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index a2465bfafa..abc840baf4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -110,6 +110,12 @@ bool GLGizmoMmuSegmentation::on_init() m_desc["second_color"] = _u8L("Second color"); m_desc["remove_caption"] = _u8L("Shift + Left mouse button") + ": "; m_desc["remove"] = _u8L("Remove painted color"); + + m_desc["alt_caption"] = _u8L("Alt + Mouse wheel") + ": "; + m_desc["alt_brush"] = _u8L("Change brush size"); + m_desc["alt_fill"] = _u8L("Change angle"); + m_desc["alt_height_range"] = _u8L("Change height range"); + m_desc["remove_all"] = _u8L("Clear all"); m_desc["circle"] = _u8L("Circle"); m_desc["sphere"] = _u8L("Sphere"); @@ -122,10 +128,7 @@ bool GLGizmoMmuSegmentation::on_init() 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["bucket_fill_angle"] = _u8L("Bucket fill angle"); - m_desc["bucket_fill_gap_area"] = _u8L("Bucket fill gap"); m_desc["split_triangles"] = _u8L("Split triangles"); @@ -282,6 +285,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott 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 bucket_fill_slider_left = ImGuiPureWrap::calc_text_size(m_desc.at("bucket_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); @@ -306,16 +310,20 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott const float split_triangles_checkbox_width = ImGuiPureWrap::calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); - float caption_max = 0.f; + float caption_max = 0.f; + for (const std::string t : {"first_color", "second_color", "remove", "alt"}) { + caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x); + } + float total_text_max = 0.f; - for (const auto &t : std::array{"first_color", "second_color", "remove"}) { - caption_max = std::max(caption_max, ImGuiPureWrap::calc_text_size(m_desc[t + "_caption"]).x); + for (const std::string t : {"first_color", "second_color", "remove", "alt_brush", "alt_fill", "alt_height_range"}) { total_text_max = std::max(total_text_max, ImGuiPureWrap::calc_text_size(m_desc[t]).x); } + 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, cursor_slider_left, clipping_slider_left, height_range_slider_left}); + const float sliders_left_width = std::max({smart_fill_slider_left, bucket_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); @@ -331,8 +339,14 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott ImGuiPureWrap::text(text); }; - for (const auto &t : std::array{"first_color", "second_color", "remove"}) + for (const std::string t : {"first_color", "second_color", "remove"}) { draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); + } + + std::string alt_hint_text = (m_tool_type == ToolType::BRUSH) ? "alt_brush" : + (m_tool_type == ToolType::HEIGHT_RANGE) ? "alt_height_range" + : "alt_fill"; + draw_text_with_caption(m_desc.at("alt_caption"), m_desc.at(alt_hint_text)); ImGui::Separator(); @@ -486,18 +500,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott "placed after the number with no whitespace in between."); ImGui::SameLine(sliders_left_width); ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, 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::AlignTextToFramePadding(); - ImGuiPureWrap::text((m_tool_type == ToolType::SMART_FILL ? m_desc["smart_fill_gap_area"] : m_desc["bucket_fill_gap_area"]) + ":"); - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##smart_fill_gap_area", &m_smart_fill_gap_area, SmartFillGapAreaMin, SmartFillGapAreaMax, "%.2f", 1.0f, true)) { + float &fill_angle = (m_tool_type == ToolType::SMART_FILL) ? m_smart_fill_angle : m_bucket_fill_angle; + if (m_imgui->slider_float("##fill_angle", &fill_angle, SmartFillAngleMin, SmartFillAngleMax, 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(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 5d608aad86..54fa9b3369 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -541,8 +541,10 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_parent.set_as_dirty(); return true; } else if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL) { - m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin) - : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); + float &fill_angle = (m_tool_type == ToolType::SMART_FILL) ? m_smart_fill_angle : m_bucket_fill_angle; + fill_angle = (action == SLAGizmoEventType::MouseWheelDown) ? std::max(fill_angle - SmartFillAngleStep, SmartFillAngleMin) + : std::min(fill_angle + SmartFillAngleStep, SmartFillAngleMax); + m_parent.set_as_dirty(); if (m_rr.mesh_id != -1) { const Selection &selection = m_parent.get_selection(); @@ -553,11 +555,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_tool_type == ToolType::SMART_FILL) { const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clipping_plane, m_smart_fill_angle, m_smart_fill_gap_area, + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clipping_plane, m_smart_fill_angle, SmartFillGapArea, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, TriangleSelector::ForceReselection::YES); } else { assert(m_tool_type == ToolType::BUCKET_FILL); - m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clipping_plane, m_smart_fill_angle, m_smart_fill_gap_area, + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clipping_plane, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES); } @@ -649,12 +651,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state); if (m_tool_type == ToolType::SMART_FILL) { - m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, m_smart_fill_gap_area, + m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, SmartFillGapArea, (m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f), TriangleSelector::ForceReselection::YES); } else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) { - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_smart_fill_angle, m_smart_fill_gap_area, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES); + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::NO, TriangleSelector::ForceReselection::YES); } else if (m_tool_type == ToolType::BUCKET_FILL) { - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_smart_fill_angle, m_smart_fill_gap_area, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES); + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::YES, TriangleSelector::ForceReselection::YES); } m_seed_fill_last_mesh_id = -1; @@ -744,12 +746,12 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous assert(m_rr.mesh_id < 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_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, m_smart_fill_gap_area, + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, SmartFillGapArea, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) - m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_smart_fill_angle, m_smart_fill_gap_area, TriangleSelector::BucketFillPropagate::NO); + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::NO); else if (m_tool_type == ToolType::BUCKET_FILL) - m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_smart_fill_angle, m_smart_fill_gap_area, TriangleSelector::BucketFillPropagate::YES); + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, m_bucket_fill_angle, BucketFillGapArea, TriangleSelector::BucketFillPropagate::YES); m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); m_seed_fill_last_mesh_id = m_rr.mesh_id; return true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index 95b56e11fc..ea600ad60e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -154,7 +154,7 @@ protected: bool m_triangle_splitting_enabled = true; ToolType m_tool_type = ToolType::BRUSH; float m_smart_fill_angle = 30.f; - float m_smart_fill_gap_area = 0.02f; + float m_bucket_fill_angle = 90.f; float m_height_range_z_range = 1.00f; bool m_paint_on_overhangs_only = false; @@ -168,13 +168,13 @@ protected: static constexpr float SmartFillAngleMax = 90.f; static constexpr float SmartFillAngleStep = 1.f; - 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; + static constexpr float SmartFillGapArea = 0.02f; + static constexpr float BucketFillGapArea = 0.02f; + // 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;