diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 90f564898..470984844 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -258,6 +258,23 @@ public: using Scalar = Vec3d::Scalar; }; +class Line3float +{ +public: + Line3float() : a(Vec3f::Zero()), b(Vec3f::Zero()) {} + Line3float(const Vec3f &_a, const Vec3f &_b) : a(_a), b(_b) {} + + Vec3f vector() const { return this->b - this->a; } + Vec3f unit_vector() const { return (length() == 0.0) ? Vec3f::Zero() : vector().normalized(); } + double length() const { return vector().norm(); } + + Vec3f a; + Vec3f b; + + static const constexpr int Dim = 3; + using Scalar = Vec3f::Scalar; +}; +typedef std::vector Line3floats; BoundingBox get_extents(const Lines &lines); } // namespace Slic3r @@ -272,7 +289,7 @@ namespace boost { namespace polygon { struct segment_traits { typedef coord_t coordinate_type; typedef Slic3r::Point point_type; - + static inline point_type get(const Slic3r::Line& line, direction_1d dir) { return dir.to_int() ? line.b : line.a; } diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 39ede77b3..8167db220 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -218,7 +218,10 @@ public: // Create new object on a TriangleMesh. The referenced mesh must // stay valid, a ptr to it is saved and used. explicit TriangleSelector(const TriangleMesh& mesh, float edge_limit = 0.6f); - + int get_orig_size_vertices() { return m_orig_size_vertices; } + const std::vector &get_triangles() { return m_triangles; } + const std::vector& get_vertices() { return m_vertices; } + const std::vector& get_neighbors() { return m_neighbors; } // Returns the facet_idx of the unsplit triangle containing the "hit". Returns -1 if the triangle isn't found. [[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx) const; [[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f1581e91e..dd3601883 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3501,6 +3501,9 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) evt.ShiftDown() && evt.AltDown() && keyCode == WXK_RETURN) { wxGetApp().plater()->toggle_show_wireframe(); m_dirty = true; + } else if ((evt.ShiftDown() && evt.ControlDown() && keyCode == 'L')) { + wxGetApp().plater()->toggle_non_manifold_edges(); + m_dirty = true; } else if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { // Enable switching between 3D and Preview with Tab diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 06fa16af6..8fc097a5c 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -696,6 +696,23 @@ bool GLModel::init_model_from_lines(const Lines3 &lines, bool generate_mesh) return true; } +bool GLModel::init_model_from_lines(const Line3floats &lines, bool generate_mesh) +{ + GLModel::Geometry init_data; + init_data.format = {GLModel::PrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3}; + init_data.reserve_vertices(2 * lines.size()); + init_data.reserve_indices(2 * lines.size()); + + for (const auto &l : lines) { + init_data.add_vertex(l.a); + init_data.add_vertex(l.b); + const unsigned int vertices_counter = (unsigned int) init_data.vertices_count(); + init_data.add_line(vertices_counter - 2, vertices_counter - 1); + } + init_from(std::move(init_data), generate_mesh); + return true; +} + void GLModel::set_color(int entity_id, const std::array& color) { for (size_t i = 0; i < m_render_data.size(); ++i) { diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index ec6456dd7..60ef81111 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -187,6 +187,7 @@ namespace GUI { bool init_model_from_poly(const std::vector &triangles, float z, bool generate_mesh = false); bool init_model_from_lines(const Lines &lines, float z, bool generate_mesh = false); bool init_model_from_lines(const Lines3 &lines, bool generate_mesh = false); + bool init_model_from_lines(const Line3floats &lines, bool generate_mesh = false); // if entity_id == -1 set the color of all entities void set_color(int entity_id, const std::array& color); void set_color(const ColorRGBA &color); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 8c9d16643..83d008cd4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -143,7 +143,9 @@ bool GLGizmoMmuSegmentation::on_init() //add toggle wire frame hint m_desc["toggle_wireframe_caption"] = alt + _L("Shift + Enter"); - m_desc["toggle_wireframe"] = _L("Toggle Wireframe"); + m_desc["toggle_wireframe"] = _L("Toggle Wireframe");//"show_wireframe" in shader + m_desc["toggle_non_manifold_edges_caption"] = ctrl + _L("Shift + L"); + m_desc["toggle_non_manifold_edges"] = _L("Toggle non-manifold edges"); init_extruders_data(); @@ -167,10 +169,54 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const m_c->object_clipper()->render_cut(); m_c->instances_hider()->render_cut(); render_cursor(); - + render_non_manifold_edges(); glsafe(::glDisable(GL_BLEND)); } +void GLGizmoMmuSegmentation::render_non_manifold_edges() const { + if (wxGetApp().plater()->is_show_non_manifold_edges()) { + if (!m_non_manifold_edges_model.is_initialized()) { + const Selection & selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + Line3floats non_manifold_edges; + int idx = -1; + for (ModelVolume *mv : mo->volumes) { + if (mv->is_model_part()) { + ++idx; + auto &triangle_selector = m_triangle_selectors[idx]; + int max_orig_size_vertices = triangle_selector->get_orig_size_vertices(); + auto neighbors = triangle_selector->get_neighbors(); + auto vertices = triangle_selector->get_vertices(); + auto triangles = triangle_selector->get_triangles(); + auto world_tran = (mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix()).cast(); + for (size_t i = 0; i < neighbors.size(); i++) { + auto nei = neighbors[i]; + for (int j = 0; j < 3; j++) { + if (nei[j] < 0) { + auto jj = next_idx_modulo(j, 3); + auto v = world_tran * vertices[triangles[i].verts_idxs[j]].v; + auto next_v = world_tran * vertices[triangles[i].verts_idxs[jj]].v; + non_manifold_edges.emplace_back(Line3float(v, next_v)); + } + } + } + } + } + m_non_manifold_edges_model.init_model_from_lines(non_manifold_edges); + m_non_manifold_edges_model.set_color(ColorRGBA::RED()); + } + const Camera & camera = wxGetApp().plater()->get_camera(); + auto view_mat = camera.get_view_matrix(); + auto proj_mat = camera.get_projection_matrix(); + GLShaderProgram *shader = wxGetApp().get_shader("flat"); + shader->start_using(); + shader->set_uniform("view_model_matrix", view_mat); + shader->set_uniform("projection_matrix", proj_mat); + m_non_manifold_edges_model.render_geometry(); + shader->stop_using(); + } +} + void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) { GLGizmoPainterBase::set_painter_gizmo_data(selection); @@ -364,17 +410,17 @@ void GLGizmoMmuSegmentation::show_tooltip_information(float caption_max, float x std::vector tip_items; switch (m_tool_type) { - case ToolType::BRUSH: - tip_items = {"paint", "erase", "cursor_size", "clipping_of_view", "toggle_wireframe"}; + case ToolType::BRUSH: + tip_items = {"paint", "erase", "cursor_size", "clipping_of_view", "toggle_wireframe", "toggle_non_manifold_edges"}; break; - case ToolType::BUCKET_FILL: - tip_items = {"paint", "erase", "smart_fill_angle", "clipping_of_view", "toggle_wireframe"}; + case ToolType::BUCKET_FILL: + tip_items = {"paint", "erase", "smart_fill_angle", "clipping_of_view", "toggle_wireframe", "toggle_non_manifold_edges"}; break; case ToolType::SMART_FILL: // TODO: break; case ToolType::GAP_FILL: - tip_items = {"gap_area", "toggle_wireframe"}; + tip_items = {"gap_area", "toggle_wireframe", "toggle_non_manifold_edges"}; break; default: break; @@ -958,9 +1004,10 @@ void GLGizmoMmuSegmentation::on_set_state() GLGizmoPainterBase::on_set_state(); if (get_state() == On) { size_t n_extruder_colors = std::min((size_t) EnforcerBlockerType::ExtruderMax, m_extruders_colors.size()); - if (n_extruder_colors>=2) { + if (n_extruder_colors>=2) { m_selected_extruder_idx = 1; } + m_non_manifold_edges_model.reset(); } else if (get_state() == Off) { ModelObject* mo = m_c->selection_info()->model_object(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index a825be3a3..8915e13ea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -68,7 +68,7 @@ public: ~GLGizmoMmuSegmentation() override = default; void render_painter_gizmo() const override; - + void render_non_manifold_edges() const; void set_painter_gizmo_data(const Selection& selection) override; void render_triangles(const Selection& selection) const override; @@ -139,6 +139,7 @@ private: // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. std::map m_desc; + mutable GLModel m_non_manifold_edges_model; }; } // namespace Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a3967c8c6..a58b03adb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2296,7 +2296,7 @@ struct Plater::priv bool show_render_statistic_dialog{ false }; bool show_wireframe{ false }; bool wireframe_enabled{ true }; - + bool show_non_manifold_edges{false}; static const std::regex pattern_bundle; static const std::regex pattern_3mf; static const std::regex pattern_zip_amf; @@ -13965,8 +13965,14 @@ bool Plater::is_render_statistic_dialog_visible() const return p->show_render_statistic_dialog; } -void Plater::toggle_show_wireframe() -{ +void Plater::toggle_non_manifold_edges() { + p->show_non_manifold_edges = !p->show_non_manifold_edges; } + +bool Plater::is_show_non_manifold_edges() { + return p->show_non_manifold_edges; +} + +void Plater::toggle_show_wireframe() { p->show_wireframe = !p->show_wireframe; } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 14ce5befd..f56bc6272 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -699,6 +699,8 @@ public: void toggle_render_statistic_dialog(); bool is_render_statistic_dialog_visible() const; + void toggle_non_manifold_edges(); + bool is_show_non_manifold_edges(); void toggle_show_wireframe(); bool is_show_wireframe() const; void enable_wireframe(bool status);