diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 6329172669..b387845215 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -421,7 +421,8 @@ void divide_triangle(const DivFace &face, Fn &&visitor) divide_triangle(child2, std::forward(visitor)); } -void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, + const std::vector &exclude_mask) { enum TrPos { posInside, posTouch, posOutside }; @@ -429,6 +430,11 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) auto &vertices = mesh.its.vertices; auto bb = mesh.bounding_box(); + bool use_exclude_mask = faces.size() == exclude_mask.size(); + auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) { + return use_exclude_mask && exclude_mask[face_id]; + }; + // TODO: Parallel mode not working yet using exec_policy = ccr_seq; @@ -518,6 +524,10 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) { const Vec3i &face = faces[face_idx]; + // If the triangle is excluded, we need to keep it. + if (is_excluded(face_idx)) + return; + std::array pts = { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index caa7d7b6ba..5d2181e7a6 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -81,7 +81,16 @@ void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0); // Hollowing prepared in "interior", merge with original mesh void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0); -void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior); +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, + const std::vector &exclude_mask = {}); + +double get_distance(const Vec3f &p, const Interior &interior); + +template +FloatingOnly get_distance(const Vec<3, T> &p, const Interior &interior) +{ + return get_distance(Vec3f(p.template cast()), interior); +} void cut_drainholes(std::vector & obj_slices, const std::vector &slicegrid, diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index fe5d7505f9..455141051b 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -142,6 +144,136 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) } } + +struct FaceHash { + + // A hash is created for each triangle to be identifiable. The hash uses + // only the triangle's geometric traits, not the index in a particular mesh. + std::unordered_set facehash; + + static std::string facekey(const Vec3i &face, + const std::vector &vertices) + { + // Scale to integer to avoid floating points + std::array, 3> pts = { + scaled(vertices[face(0)]), + scaled(vertices[face(1)]), + scaled(vertices[face(2)]) + }; + + // Get the first two sides of the triangle, do a cross product and move + // that vector to the center of the triangle. This encodes all + // information to identify an identical triangle at the same position. + Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2]; + Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3; + + // Return a concatenated string representation of the coordinates + return std::to_string(c(0)) + std::to_string(c(1)) + std::to_string(c(2)); + }; + + FaceHash(const indexed_triangle_set &its) + { + for (const Vec3i &face : its.indices) { + std::string keystr = facekey(face, its.vertices); + facehash.insert(keystr); + } + } + + bool find(const std::string &key) + { + auto it = facehash.find(key); + return it != facehash.end(); + } +}; + +// Create exclude mask for triangle removal inside hollowed interiors. +// This is necessary when the interior is already part of the mesh which was +// drilled using CGAL mesh boolean operation. Excluded will be the triangles +// originally part of the interior mesh and triangles that make up the drilled +// hole walls. +static std::vector create_exclude_mask( + const indexed_triangle_set &its, + const sla::Interior &interior, + const std::vector &holes) +{ + FaceHash interior_hash{sla::get_mesh(interior).its}; + + std::vector exclude_mask(its.indices.size(), false); + + std::vector< std::vector > neighbor_index = + create_neighbor_index(its); + + auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face) + { + for (int i = 0; i < 3; ++i) { + const std::vector &neighbors = neighbor_index[face(i)]; + for (size_t fi_n : neighbors) exclude_mask[fi_n] = true; + } + }; + + for (size_t fi = 0; fi < its.indices.size(); ++fi) { + auto &face = its.indices[fi]; + + std::string key = + FaceHash::facekey(face, its.vertices); + + if (interior_hash.find(key)) { + exclude_mask[fi] = true; + continue; + } + + if (exclude_mask[fi]) { + exclude_neighbors(face); + continue; + } + + // Lets deal with the holes. All the triangles of a hole and all the + // neighbors of these triangles need to be kept. The neigbors were + // created by CGAL mesh boolean operation that modified the original + // interior inside the input mesh to contain the holes. + Vec3d tr_center = ( + its.vertices[face(0)] + + its.vertices[face(1)] + + its.vertices[face(2)] + ).cast() / 3.; + + // If the center is more than half a mm inside the interior, + // it cannot possibly be part of a hole wall. + if (sla::get_distance(tr_center, interior) < -0.5) + continue; + + Vec3f U = its.vertices[face(1)] - its.vertices[face(0)]; + Vec3f V = its.vertices[face(2)] - its.vertices[face(0)]; + Vec3f C = U.cross(V); + Vec3f face_normal = C.normalized(); + + for (const sla::DrainHole &dh : holes) { + Vec3d dhpos = dh.pos.cast(); + Vec3d dhend = dhpos + dh.normal.cast() * dh.height; + + Linef3 holeaxis{dhpos, dhend}; + + double D_hole_center = line_alg::distance_to(holeaxis, tr_center); + double D_hole = std::abs(D_hole_center - dh.radius); + float dot = dh.normal.dot(face_normal); + + // Empiric tolerances for center distance and normals angle. + // For triangles that are part of a hole wall the angle of + // triangle normal and the hole axis is around 90 degrees, + // so the dot product is around zero. + double D_tol = dh.radius / sla::DrainHole::steps; + float normal_angle_tol = 1.f / sla::DrainHole::steps; + + if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) { + exclude_mask[fi] = true; + exclude_neighbors(face); + } + } + } + + return exclude_mask; +} + // Drill holes into the hollowed/original mesh. void SLAPrint::Steps::drill_holes(SLAPrintObject &po) { @@ -207,10 +339,13 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); mesh_view = hollowed_mesh; - if (is_hollowed) - sla::remove_inside_triangles(mesh_view, - *po.m_hollowing_data->interior, - drainholes); + if (is_hollowed) { + auto &interior = *po.m_hollowing_data->interior; + std::vector exclude_mask = + create_exclude_mask(mesh_view.its, interior, drainholes); + + sla::remove_inside_triangles(mesh_view, interior, exclude_mask); + } } catch (const std::runtime_error &) { throw Slic3r::SlicingError(L( diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index adb9be64d1..d5a3490877 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -2063,4 +2063,22 @@ TriangleMesh make_sphere(double radius, double fa) return mesh; } +std::vector > create_neighbor_index(const indexed_triangle_set &its) +{ + if (its.vertices.empty()) return {}; + + size_t res = its.indices.size() / its.vertices.size(); + std::vector< std::vector > index(its.vertices.size(), + reserve_vector(res)); + + for (size_t fi = 0; fi < its.indices.size(); ++fi) { + auto &face = its.indices[fi]; + index[face(0)].emplace_back(fi); + index[face(1)].emplace_back(fi); + index[face(2)].emplace_back(fi); + } + + return index; +} + } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 9625298f4e..e6f6dc84b8 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -89,6 +89,12 @@ private: std::deque find_unvisited_neighbors(std::vector &facet_visited) const; }; +// Create an index of faces belonging to each vertex. The returned vector can +// be indexed with vertex indices and contains a list of face indices for each +// vertex. +std::vector< std::vector > +create_neighbor_index(const indexed_triangle_set &its); + enum FacetEdgeType { // A general case, the cutting plane intersect a face at two different edges. feGeneral,