Tech ENABLE_SHOW_NON_MANIFOLD_EDGES - 1st installment - Calculate and show in 3D view non-manifold edges as lines

This commit is contained in:
enricoturri1966 2022-01-20 13:32:24 +01:00
parent 81edc7d752
commit 18e2cc2298
12 changed files with 166 additions and 22 deletions

View File

@ -150,6 +150,11 @@ void AppConfig::set_defaults()
if (get("order_volumes").empty()) if (get("order_volumes").empty())
set("order_volumes", "1"); 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()) if (get("clear_undo_redo_stack_on_new_project").empty())
set("clear_undo_redo_stack_on_new_project", "1"); set("clear_undo_redo_stack_on_new_project", "1");
} }

View File

@ -64,6 +64,8 @@
#define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) #define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1)
// Enable removal of old OpenGL render calls // Enable removal of old OpenGL render calls
#define ENABLE_GLBEGIN_GLEND_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) #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_ #endif // _prusaslicer_technologies_h_

View File

@ -1239,13 +1239,30 @@ bool its_is_splittable(const indexed_triangle_set &its, const std::vector<Vec3i>
size_t its_num_open_edges(const std::vector<Vec3i> &face_neighbors) size_t its_num_open_edges(const std::vector<Vec3i> &face_neighbors)
{ {
size_t num_open_edges = 0; size_t num_open_edges = 0;
for (Vec3i neighbors : face_neighbors) for (const Vec3i& neighbors : face_neighbors)
for (int n : neighbors) for (int n : neighbors)
if (n < 0) if (n < 0)
++ num_open_edges; ++ num_open_edges;
return num_open_edges; return num_open_edges;
} }
#if ENABLE_SHOW_NON_MANIFOLD_EDGES
std::vector<std::pair<int, int>> its_get_open_edges(const indexed_triangle_set& its)
{
std::vector<std::pair<int, int>> ret;
std::vector<Vec3i> 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) size_t its_num_open_edges(const indexed_triangle_set &its)
{ {
return its_num_open_edges(its_face_neighbors(its)); return its_num_open_edges(its_face_neighbors(its));

View File

@ -227,6 +227,12 @@ bool its_is_splittable(const indexed_triangle_set &its, const std::vector<Vec3i>
size_t its_num_open_edges(const indexed_triangle_set &its); size_t its_num_open_edges(const indexed_triangle_set &its);
size_t its_num_open_edges(const std::vector<Vec3i> &face_neighbors); size_t its_num_open_edges(const std::vector<Vec3i> &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<std::pair<int, int>> 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. // 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); void its_shrink_to_fit(indexed_triangle_set &its);

View File

@ -302,10 +302,10 @@ void GLVolume::SinkingContours::render()
void GLVolume::SinkingContours::update() void GLVolume::SinkingContours::update()
{ {
int object_idx = m_parent.object_idx(); const int object_idx = m_parent.object_idx();
Model& model = GUI::wxGetApp().plater()->model(); 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(); 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()) { if (!m_old_box.size().isApprox(box.size()) || m_old_box.min.z() != box.min.z()) {
m_old_box = box; m_old_box = box;
@ -317,13 +317,16 @@ void GLVolume::SinkingContours::update()
GUI::GLModel::InitializationData init_data; GUI::GLModel::InitializationData init_data;
MeshSlicingParams slicing_params; MeshSlicingParams slicing_params;
slicing_params.trafo = m_parent.world_matrix(); slicing_params.trafo = m_parent.world_matrix();
Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); const 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))))) { for (const ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) {
GUI::GLModel::InitializationData::Entity entity; GUI::GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Triangles; entity.type = GUI::GLModel::PrimitiveType::Triangles;
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(expoly); const std::vector<Vec3d> 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) { for (const Vec3d& v : triangulation) {
entity.positions.emplace_back(v.cast<float>() + Vec3f(0.0f, 0.0f, 0.015f)); // add a small positive z to avoid z-fighting entity.positions.emplace_back(v.cast<float>() + 0.015f * Vec3f::UnitZ()); // add a small positive z to avoid z-fighting
entity.normals.emplace_back(Vec3f::UnitZ()); entity.normals.emplace_back(Vec3f::UnitZ());
const size_t positions_count = entity.positions.size(); const size_t positions_count = entity.positions.size();
if (positions_count % 3 == 0) { if (positions_count % 3 == 0) {
@ -344,6 +347,61 @@ void GLVolume::SinkingContours::update()
m_model.reset(); 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<std::pair<int, int>> 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<int, int>& edge : edges) {
entity.positions.emplace_back(mesh.its.vertices[edge.first].cast<float>());
entity.positions.emplace_back(mesh.its.vertices[edge.second].cast<float>());
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::SELECTED_COLOR = ColorRGBA::GREEN();
const ColorRGBA GLVolume::HOVER_SELECT_COLOR = { 0.4f, 0.9f, 0.1f, 1.0f }; 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 }; const ColorRGBA GLVolume::HOVER_DESELECT_COLOR = { 1.0f, 0.75f, 0.75f, 1.0f };
@ -363,6 +421,9 @@ const std::array<ColorRGBA, 4> GLVolume::MODEL_COLOR = { {
GLVolume::GLVolume(float r, float g, float b, float a) GLVolume::GLVolume(float r, float g, float b, float a)
: m_sla_shift_z(0.0) : m_sla_shift_z(0.0)
, m_sinking_contours(*this) , 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 == 0 -> invalid
, geometry_id(std::pair<size_t, size_t>(0, 0)) , geometry_id(std::pair<size_t, size_t>(0, 0))
, extruder_id(0) , extruder_id(0)
@ -571,6 +632,13 @@ void GLVolume::render_sinking_contours()
m_sinking_contours.render(); 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<int> GLVolumeCollection::load_object( std::vector<int> GLVolumeCollection::load_object(
const ModelObject *model_object, const ModelObject *model_object,
int obj_idx, 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) if (disable_cullface)
glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glEnable(GL_CULL_FACE));

View File

@ -304,6 +304,25 @@ private:
SinkingContours m_sinking_contours; 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: public:
// Color of the triangles / quads held by this volume. // Color of the triangles / quads held by this volume.
ColorRGBA color; ColorRGBA color;
@ -500,6 +519,9 @@ public:
bool is_sinking() const; bool is_sinking() const;
bool is_below_printbed() const; bool is_below_printbed() const;
void render_sinking_contours(); 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. // Return an estimate of the memory consumed by this class.
size_t cpu_memory_used() const { size_t cpu_memory_used() const {
@ -556,7 +578,10 @@ private:
}; };
Slope m_slope; 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: public:
GLVolumePtrs volumes; GLVolumePtrs volumes;
@ -629,6 +654,9 @@ public:
void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; } 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_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; } 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 true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null // returns the containment state in the given out_state, if non-null

View File

@ -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_clipping_plane(m_camera_clipping_plane.get_data());
m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); 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"); GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
if (shader != nullptr) { if (shader != nullptr) {

View File

@ -115,8 +115,8 @@ void GLGizmoCut::on_render()
m_plane.reset(); m_plane.reset();
GLModel::InitializationData init_data; GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity; GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Triangles; entity.type = GLModel::PrimitiveType::Triangles;
entity.positions.reserve(4); entity.positions.reserve(4);
entity.positions.emplace_back(Vec3f(min_x, min_y, plane_center.z())); entity.positions.emplace_back(Vec3f(min_x, min_y, plane_center.z()));
entity.positions.emplace_back(Vec3f(max_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(); m_grabber_connection.reset();
GLModel::InitializationData init_data; GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity; GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Lines; entity.type = GLModel::PrimitiveType::Lines;
entity.positions.reserve(2); entity.positions.reserve(2);
entity.positions.emplace_back(plane_center.cast<float>()); entity.positions.emplace_back(plane_center.cast<float>());
entity.positions.emplace_back(m_grabbers[0].center.cast<float>()); entity.positions.emplace_back(m_grabbers[0].center.cast<float>());

View File

@ -114,8 +114,8 @@ void GLGizmoMove3D::on_render()
m_grabber_connections[id].model.reset(); m_grabber_connections[id].model.reset();
GLModel::InitializationData init_data; GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity; GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Lines; entity.type = GLModel::PrimitiveType::Lines;
entity.positions.reserve(2); entity.positions.reserve(2);
entity.positions.emplace_back(center.cast<float>()); entity.positions.emplace_back(center.cast<float>());
entity.positions.emplace_back(m_grabbers[id].center.cast<float>()); entity.positions.emplace_back(m_grabbers[id].center.cast<float>());

View File

@ -402,8 +402,8 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int
m_grabber_connections[id].model.reset(); m_grabber_connections[id].model.reset();
GLModel::InitializationData init_data; GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity; GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Lines; entity.type = GLModel::PrimitiveType::Lines;
entity.positions.reserve(2); entity.positions.reserve(2);
entity.positions.emplace_back(m_grabbers[id_1].center.cast<float>()); entity.positions.emplace_back(m_grabbers[id_1].center.cast<float>());
entity.positions.emplace_back(m_grabbers[id_2].center.cast<float>()); entity.positions.emplace_back(m_grabbers[id_2].center.cast<float>());

View File

@ -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."), "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"); 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 #ifdef _MSW_DARK_MODE
append_bool_option(m_optgroup_gui, "tabs_as_menu", append_bool_option(m_optgroup_gui, "tabs_as_menu",
L("Set settings tabs as menu items (experimental)"), L("Set settings tabs as menu items (experimental)"),

View File

@ -1901,8 +1901,8 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con
const Vec3f size = 0.2f * box.size().cast<float>(); const Vec3f size = 0.2f * box.size().cast<float>();
GLModel::InitializationData init_data; GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity; GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Lines; entity.type = GLModel::PrimitiveType::Lines;
entity.positions.reserve(48); entity.positions.reserve(48);
entity.positions.emplace_back(Vec3f(b_min.x(), b_min.y(), b_min.z())); 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(); m_planes.models[0].reset();
GLModel::InitializationData init_data; GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity; GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Triangles; entity.type = GLModel::PrimitiveType::Triangles;
entity.positions.reserve(4); entity.positions.reserve(4);
entity.positions.emplace_back(Vec3f(p1.x(), p1.y(), z1)); entity.positions.emplace_back(Vec3f(p1.x(), p1.y(), z1));
entity.positions.emplace_back(Vec3f(p2.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(); m_planes.models[1].reset();
GLModel::InitializationData init_data; GLModel::InitializationData init_data;
GUI::GLModel::InitializationData::Entity entity; GLModel::InitializationData::Entity entity;
entity.type = GUI::GLModel::PrimitiveType::Triangles; entity.type = GLModel::PrimitiveType::Triangles;
entity.positions.reserve(4); entity.positions.reserve(4);
entity.positions.emplace_back(Vec3f(p1.x(), p1.y(), z2)); entity.positions.emplace_back(Vec3f(p1.x(), p1.y(), z2));
entity.positions.emplace_back(Vec3f(p2.x(), p1.y(), z2)); entity.positions.emplace_back(Vec3f(p2.x(), p1.y(), z2));