From 18e2cc22987c5f15466e9e13b4bc212e352baaeb Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 20 Jan 2022 13:32:24 +0100 Subject: [PATCH] Tech ENABLE_SHOW_NON_MANIFOLD_EDGES - 1st installment - Calculate and show in 3D view non-manifold edges as lines --- src/libslic3r/AppConfig.cpp | 5 ++ src/libslic3r/Technologies.hpp | 2 + src/libslic3r/TriangleMesh.cpp | 19 +++++- src/libslic3r/TriangleMesh.hpp | 6 ++ src/slic3r/GUI/3DScene.cpp | 88 ++++++++++++++++++++++++-- src/slic3r/GUI/3DScene.hpp | 30 ++++++++- src/slic3r/GUI/GLCanvas3D.cpp | 3 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 8 +-- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 4 +- src/slic3r/GUI/Preferences.cpp | 7 ++ src/slic3r/GUI/Selection.cpp | 12 ++-- 12 files changed, 166 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 5b4bb34a3c..928febffd0 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -150,6 +150,11 @@ void AppConfig::set_defaults() if (get("order_volumes").empty()) set("order_volumes", "1"); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + if (get("non_manifold_edges").empty()) + set("non_manifold_edges", "1"); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + if (get("clear_undo_redo_stack_on_new_project").empty()) set("clear_undo_redo_stack_on_new_project", "1"); } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 03f3071eed..807ca1a5b3 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -64,6 +64,8 @@ #define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) // Enable removal of old OpenGL render calls #define ENABLE_GLBEGIN_GLEND_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) +// Enable show non-manifold edges +#define ENABLE_SHOW_NON_MANIFOLD_EDGES (1 && ENABLE_2_5_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 93a09a0d98..0dffdaab0e 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1239,13 +1239,30 @@ bool its_is_splittable(const indexed_triangle_set &its, const std::vector size_t its_num_open_edges(const std::vector &face_neighbors) { size_t num_open_edges = 0; - for (Vec3i neighbors : face_neighbors) + for (const Vec3i& neighbors : face_neighbors) for (int n : neighbors) if (n < 0) ++ num_open_edges; return num_open_edges; } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES +std::vector> its_get_open_edges(const indexed_triangle_set& its) +{ + std::vector> ret; + std::vector face_neighbors = its_face_neighbors(its); + for (size_t i = 0; i < face_neighbors.size(); ++i) { + for (size_t j = 0; j < 3; ++j) { + if (face_neighbors[i][j] < 0) { + const Vec2i edge_indices = its_triangle_edge(its.indices[i], j); + ret.emplace_back(edge_indices[0], edge_indices[1]); + } + } + } + return ret; +} +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + size_t its_num_open_edges(const indexed_triangle_set &its) { return its_num_open_edges(its_face_neighbors(its)); diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 3a0b2c2cec..3f3af0261c 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -227,6 +227,12 @@ bool its_is_splittable(const indexed_triangle_set &its, const std::vector size_t its_num_open_edges(const indexed_triangle_set &its); size_t its_num_open_edges(const std::vector &face_neighbors); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES +// Calculate and returns the list of unconnected face edges. +// Each edge is represented by the indices of the two endpoint vertices +std::vector> its_get_open_edges(const indexed_triangle_set& its); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + // Shrink the vectors of its.vertices and its.faces to a minimum size by reallocating the two vectors. void its_shrink_to_fit(indexed_triangle_set &its); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 0f72ac0459..bd02bf8f26 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -302,10 +302,10 @@ void GLVolume::SinkingContours::render() void GLVolume::SinkingContours::update() { - int object_idx = m_parent.object_idx(); - Model& model = GUI::wxGetApp().plater()->model(); + const int object_idx = m_parent.object_idx(); + const Model& model = GUI::wxGetApp().plater()->model(); - if (0 <= object_idx && object_idx < (int)model.objects.size() && m_parent.is_sinking() && !m_parent.is_below_printbed()) { + if (0 <= object_idx && object_idx < int(model.objects.size()) && m_parent.is_sinking() && !m_parent.is_below_printbed()) { const BoundingBoxf3& box = m_parent.transformed_convex_hull_bounding_box(); if (!m_old_box.size().isApprox(box.size()) || m_old_box.min.z() != box.min.z()) { m_old_box = box; @@ -317,13 +317,16 @@ void GLVolume::SinkingContours::update() GUI::GLModel::InitializationData init_data; MeshSlicingParams slicing_params; slicing_params.trafo = m_parent.world_matrix(); - Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); - for (ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { + const Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); + for (const ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { GUI::GLModel::InitializationData::Entity entity; entity.type = GUI::GLModel::PrimitiveType::Triangles; const std::vector triangulation = triangulate_expolygon_3d(expoly); + entity.positions.reserve(entity.positions.size() + triangulation.size()); + entity.normals.reserve(entity.normals.size() + triangulation.size()); + entity.indices.reserve(entity.indices.size() + triangulation.size() / 3); for (const Vec3d& v : triangulation) { - entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.015f)); // add a small positive z to avoid z-fighting + entity.positions.emplace_back(v.cast() + 0.015f * Vec3f::UnitZ()); // add a small positive z to avoid z-fighting entity.normals.emplace_back(Vec3f::UnitZ()); const size_t positions_count = entity.positions.size(); if (positions_count % 3 == 0) { @@ -344,6 +347,61 @@ void GLVolume::SinkingContours::update() m_model.reset(); } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES +void GLVolume::NonManifoldEdges::render() +{ + update(); + + glsafe(::glLineWidth(2.0f)); + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(m_parent.world_matrix().data())); + m_model.set_color(-1, complementary(m_parent.render_color)); + m_model.render(); + glsafe(::glPopMatrix()); +} + +void GLVolume::NonManifoldEdges::update() +{ + if (!m_update_needed) + return; + + m_model.reset(); + const int object_idx = m_parent.object_idx(); + const Model& model = GUI::wxGetApp().plater()->model(); + if (0 <= object_idx && object_idx < int(model.objects.size())) { + const ModelObject* model_object = model.objects[object_idx]; + const int volume_idx = m_parent.volume_idx(); + if (0 <= volume_idx && volume_idx < int(model_object->volumes.size())) { + const ModelVolume* model_volume = model_object->volumes[volume_idx]; + const TriangleMesh& mesh = model_volume->mesh(); + const std::vector> edges = its_get_open_edges(mesh.its); + if (!edges.empty()) { + GUI::GLModel::InitializationData init_data; + GUI::GLModel::InitializationData::Entity entity; + entity.type = GUI::GLModel::PrimitiveType::Lines; + + entity.positions.reserve(2 * edges.size()); + entity.normals.reserve(2 * edges.size()); + entity.indices.reserve(2 * edges.size()); + for (const std::pair& edge : edges) { + entity.positions.emplace_back(mesh.its.vertices[edge.first].cast()); + entity.positions.emplace_back(mesh.its.vertices[edge.second].cast()); + entity.normals.emplace_back(Vec3f::UnitZ()); + entity.normals.emplace_back(Vec3f::UnitZ()); + entity.indices.emplace_back(entity.positions.size() - 2); + entity.indices.emplace_back(entity.positions.size() - 1); + } + + init_data.entities.emplace_back(entity); + m_model.init_from(init_data); + } + } + } + + m_update_needed = false; +} +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + const ColorRGBA GLVolume::SELECTED_COLOR = ColorRGBA::GREEN(); const ColorRGBA GLVolume::HOVER_SELECT_COLOR = { 0.4f, 0.9f, 0.1f, 1.0f }; const ColorRGBA GLVolume::HOVER_DESELECT_COLOR = { 1.0f, 0.75f, 0.75f, 1.0f }; @@ -363,6 +421,9 @@ const std::array GLVolume::MODEL_COLOR = { { GLVolume::GLVolume(float r, float g, float b, float a) : m_sla_shift_z(0.0) , m_sinking_contours(*this) +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + , m_non_manifold_edges(*this) +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES // geometry_id == 0 -> invalid , geometry_id(std::pair(0, 0)) , extruder_id(0) @@ -571,6 +632,13 @@ void GLVolume::render_sinking_contours() m_sinking_contours.render(); } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES +void GLVolume::render_non_manifold_edges() +{ + m_non_manifold_edges.render(); +} +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, @@ -883,6 +951,14 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab } } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + if (m_show_non_manifold_edges && GUI::wxGetApp().app_config->get("non_manifold_edges") == "1") { + for (GLVolumeWithIdAndZ& volume : to_render) { + volume.first->render_non_manifold_edges(); + } + } +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + if (disable_cullface) glsafe(::glEnable(GL_CULL_FACE)); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 68538c5e0f..5aaa759f2f 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -304,6 +304,25 @@ private: SinkingContours m_sinking_contours; +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + class NonManifoldEdges + { + GLVolume& m_parent; + GUI::GLModel m_model; + bool m_update_needed{ true }; + + public: + NonManifoldEdges(GLVolume& volume) : m_parent(volume) {} + void render(); + void set_as_dirty() { m_update_needed = true; } + + private: + void update(); + }; + + NonManifoldEdges m_non_manifold_edges; +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + public: // Color of the triangles / quads held by this volume. ColorRGBA color; @@ -500,6 +519,9 @@ public: bool is_sinking() const; bool is_below_printbed() const; void render_sinking_contours(); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + void render_non_manifold_edges(); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES // Return an estimate of the memory consumed by this class. size_t cpu_memory_used() const { @@ -556,7 +578,10 @@ private: }; Slope m_slope; - bool m_show_sinking_contours = false; + bool m_show_sinking_contours{ false }; +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + bool m_show_non_manifold_edges{ true }; +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES public: GLVolumePtrs volumes; @@ -629,6 +654,9 @@ public: void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; } void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); } void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; } +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8b8e92691d..f97dab6259 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5198,6 +5198,9 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + m_volumes.set_show_non_manifold_edges(!m_gizmos.is_hiding_instances() && m_gizmos.get_current_type() != GLGizmosManager::Simplify); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); if (shader != nullptr) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index ee0865ecaa..31c3e12bc6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -115,8 +115,8 @@ void GLGizmoCut::on_render() m_plane.reset(); GLModel::InitializationData init_data; - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::Triangles; + GLModel::InitializationData::Entity entity; + entity.type = GLModel::PrimitiveType::Triangles; entity.positions.reserve(4); entity.positions.emplace_back(Vec3f(min_x, min_y, plane_center.z())); entity.positions.emplace_back(Vec3f(max_x, min_y, plane_center.z())); @@ -170,8 +170,8 @@ void GLGizmoCut::on_render() m_grabber_connection.reset(); GLModel::InitializationData init_data; - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::Lines; + GLModel::InitializationData::Entity entity; + entity.type = GLModel::PrimitiveType::Lines; entity.positions.reserve(2); entity.positions.emplace_back(plane_center.cast()); entity.positions.emplace_back(m_grabbers[0].center.cast()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 0b3265458b..9719c5cf67 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -114,8 +114,8 @@ void GLGizmoMove3D::on_render() m_grabber_connections[id].model.reset(); GLModel::InitializationData init_data; - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::Lines; + GLModel::InitializationData::Entity entity; + entity.type = GLModel::PrimitiveType::Lines; entity.positions.reserve(2); entity.positions.emplace_back(center.cast()); entity.positions.emplace_back(m_grabbers[id].center.cast()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index e56ce7e1e1..953c7dd6c2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -402,8 +402,8 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int m_grabber_connections[id].model.reset(); GLModel::InitializationData init_data; - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::Lines; + GLModel::InitializationData::Entity entity; + entity.type = GLModel::PrimitiveType::Lines; entity.positions.reserve(2); entity.positions.emplace_back(m_grabbers[id_1].center.cast()); entity.positions.emplace_back(m_grabbers[id_2].center.cast()); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 4173f907b1..2af008a52b 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -410,6 +410,13 @@ void PreferencesDialog::build() "If disabled, you can reorder Model Parts, Negative Volumes and Modifiers. But one of the model parts have to be on the first place."), app_config->get("order_volumes") == "1"); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + append_bool_option(m_optgroup_gui, "non_manifold_edges", + L("Show non-manifold edges"), + L("If enabled, shows non-manifold edges."), + app_config->get("non_manifold_edges") == "1"); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + #ifdef _MSW_DARK_MODE append_bool_option(m_optgroup_gui, "tabs_as_menu", L("Set settings tabs as menu items (experimental)"), diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index a7b1731081..0fef5ac643 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1901,8 +1901,8 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con const Vec3f size = 0.2f * box.size().cast(); GLModel::InitializationData init_data; - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::Lines; + GLModel::InitializationData::Entity entity; + entity.type = GLModel::PrimitiveType::Lines; entity.positions.reserve(48); entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y(), b_min.z())); @@ -2234,8 +2234,8 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co m_planes.models[0].reset(); GLModel::InitializationData init_data; - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::Triangles; + GLModel::InitializationData::Entity entity; + entity.type = GLModel::PrimitiveType::Triangles; entity.positions.reserve(4); entity.positions.emplace_back(Vec3f(p1.x(), p1.y(), z1)); entity.positions.emplace_back(Vec3f(p2.x(), p1.y(), z1)); @@ -2264,8 +2264,8 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co m_planes.models[1].reset(); GLModel::InitializationData init_data; - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::Triangles; + GLModel::InitializationData::Entity entity; + entity.type = GLModel::PrimitiveType::Triangles; entity.positions.reserve(4); entity.positions.emplace_back(Vec3f(p1.x(), p1.y(), z2)); entity.positions.emplace_back(Vec3f(p2.x(), p1.y(), z2));