#include "Exception.hpp" #include "TriangleMesh.hpp" #include "TriangleMeshSlicer.hpp" #include "MeshSplitImpl.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "Geometry/ConvexHull.hpp" #include "Point.hpp" #include "Execution/ExecutionTBB.hpp" #include "Execution/ExecutionSeq.hpp" #include "Utils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Slic3r { static void update_bounding_box(const indexed_triangle_set &its, TriangleMeshStats &out) { BoundingBoxf3 bbox = Slic3r::bounding_box(its); out.min = bbox.min.cast(); out.max = bbox.max.cast(); out.size = out.max - out.min; } static void fill_initial_stats(const indexed_triangle_set &its, TriangleMeshStats &out) { out.number_of_facets = its.indices.size(); out.volume = its_volume(its); update_bounding_box(its, out); const std::vector face_neighbors = its_face_neighbors(its); out.number_of_parts = its_number_of_patches(its, face_neighbors); out.open_edges = its_num_open_edges(face_neighbors); } TriangleMesh::TriangleMesh(const std::vector &vertices, const std::vector &faces) : its { faces, vertices } { fill_initial_stats(this->its, m_stats); } TriangleMesh::TriangleMesh(std::vector &&vertices, const std::vector &&faces) : its { std::move(faces), std::move(vertices) } { fill_initial_stats(this->its, m_stats); } TriangleMesh::TriangleMesh(const indexed_triangle_set &its) : its(its) { fill_initial_stats(this->its, m_stats); } TriangleMesh::TriangleMesh(indexed_triangle_set &&its, const RepairedMeshErrors& errors/* = RepairedMeshErrors()*/) : its(std::move(its)) { m_stats.repaired_errors = errors; fill_initial_stats(this->its, m_stats); } // #define SLIC3R_TRACE_REPAIR static void trianglemesh_repair_on_import(stl_file &stl) { // admesh fails when repairing empty meshes if (stl.stats.number_of_facets == 0) return; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started"; // checking exact #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; #endif /* SLIC3R_TRACE_REPAIR */ assert(stl_validate(&stl)); stl_check_facets_exact(&stl); assert(stl_validate(&stl)); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); // checking nearby //int last_edges_fixed = 0; float tolerance = (float)stl.stats.shortest_edge; float increment = (float)stl.stats.bounding_diameter / 10000.0f; int iterations = 2; if (stl.stats.connected_facets_3_edge < int(stl.stats.number_of_facets)) { // Not a manifold, some triangles have unconnected edges. for (int i = 0; i < iterations; ++ i) { if (stl.stats.connected_facets_3_edge < int(stl.stats.number_of_facets)) { // Still not a manifold, some triangles have unconnected edges. //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby"; #endif /* SLIC3R_TRACE_REPAIR */ stl_check_facets_nearby(&stl, tolerance); //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); //last_edges_fixed = stl.stats.edges_fixed; tolerance += increment; } else { break; } } } assert(stl_validate(&stl)); // remove_unconnected if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets"; #endif /* SLIC3R_TRACE_REPAIR */ stl_remove_unconnected_facets(&stl); assert(stl_validate(&stl)); } // fill_holes #if 0 // Don't fill holes, the current algorithm does more harm than good on complex holes. // Rather let the slicing algorithm close gaps in 2D slices. if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_fill_holes"; #endif /* SLIC3R_TRACE_REPAIR */ stl_fill_holes(&stl); stl_clear_error(&stl); } #endif // normal_directions #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions"; #endif /* SLIC3R_TRACE_REPAIR */ stl_fix_normal_directions(&stl); assert(stl_validate(&stl)); // normal_values #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values"; #endif /* SLIC3R_TRACE_REPAIR */ stl_fix_normal_values(&stl); assert(stl_validate(&stl)); // always calculate the volume and reverse all normals if volume is negative #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume"; #endif /* SLIC3R_TRACE_REPAIR */ // If the volume is negative, all the facets are flipped and added to stats.facets_reversed. stl_calculate_volume(&stl); assert(stl_validate(&stl)); // neighbors #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors"; #endif /* SLIC3R_TRACE_REPAIR */ stl_verify_neighbors(&stl); assert(stl_validate(&stl)); //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. if (auto nr_degenerated = stl.stats.degenerate_facets; stl.stats.number_of_facets > 0 && nr_degenerated > 0) stl_check_facets_exact(&stl); BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; } bool TriangleMesh::ReadSTLFile(const char* input_file, bool repair) { stl_file stl; if (! stl_open(&stl, input_file)) return false; if (repair) trianglemesh_repair_on_import(stl); m_stats.number_of_facets = stl.stats.number_of_facets; m_stats.min = stl.stats.min; m_stats.max = stl.stats.max; m_stats.size = stl.stats.size; m_stats.volume = stl.stats.volume; auto facets_w_1_bad_edge = stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge; auto facets_w_2_bad_edge = stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge; auto facets_w_3_bad_edge = stl.stats.number_of_facets - stl.stats.connected_facets_1_edge; m_stats.open_edges = stl.stats.backwards_edges + facets_w_1_bad_edge + facets_w_2_bad_edge * 2 + facets_w_3_bad_edge * 3; m_stats.repaired_errors = { stl.stats.edges_fixed, stl.stats.degenerate_facets, stl.stats.facets_removed, stl.stats.facets_reversed, stl.stats.backwards_edges }; m_stats.number_of_parts = stl.stats.number_of_parts; stl_generate_shared_vertices(&stl, this->its); return true; } bool TriangleMesh::write_ascii(const char* output_file) { return its_write_stl_ascii(output_file, "", this->its); } bool TriangleMesh::write_binary(const char* output_file) { return its_write_stl_binary(output_file, "", this->its); } float TriangleMesh::volume() { if (m_stats.volume == -1) m_stats.volume = its_volume(this->its); return m_stats.volume; } void TriangleMesh::WriteOBJFile(const char* output_file) const { its_write_obj(this->its, output_file); } void TriangleMesh::scale(float factor) { this->scale(Vec3f(factor, factor, factor)); } void TriangleMesh::scale(const Vec3f &versor) { // Scale extents. auto s = versor.array(); m_stats.min.array() *= s; m_stats.max.array() *= s; // Scale size. m_stats.size.array() *= s; // Scale volume. if (m_stats.volume > 0.0) m_stats.volume *= s(0) * s(1) * s(2); if (versor.x() == versor.y() && versor.x() == versor.z()) { float s = versor.x(); for (stl_vertex &v : this->its.vertices) v *= s; } else { for (stl_vertex &v : this->its.vertices) { v.x() *= versor.x(); v.y() *= versor.y(); v.z() *= versor.z(); } } } void TriangleMesh::translate(const Vec3f &displacement) { if (displacement.x() != 0.f || displacement.y() != 0.f || displacement.z() != 0.f) { for (stl_vertex& v : this->its.vertices) v += displacement; m_stats.min += displacement; m_stats.max += displacement; } } void TriangleMesh::translate(float x, float y, float z) { this->translate(Vec3f(x, y, z)); } void TriangleMesh::rotate(float angle, const Axis &axis) { if (angle != 0.f) { angle = Slic3r::Geometry::rad2deg(angle); switch (axis) { case X: its_rotate_x(this->its, angle); break; case Y: its_rotate_y(this->its, angle); break; case Z: its_rotate_z(this->its, angle); break; default: assert(false); return; } update_bounding_box(this->its, this->m_stats); } } void TriangleMesh::rotate(float angle, const Vec3d& axis) { if (angle != 0.f) { Vec3d axis_norm = axis.normalized(); Transform3d m = Transform3d::Identity(); m.rotate(Eigen::AngleAxisd(angle, axis_norm)); its_transform(its, m); update_bounding_box(this->its, this->m_stats); } } void TriangleMesh::mirror(const Axis axis) { switch (axis) { case X: for (stl_vertex &v : its.vertices) v.x() *= -1.f; break; case Y: for (stl_vertex& v : this->its.vertices) v.y() *= -1.0; break; case Z: for (stl_vertex &v : this->its.vertices) v.z() *= -1.0; break; default: assert(false); return; }; its_flip_triangles(this->its); int iaxis = int(axis); std::swap(m_stats.min[iaxis], m_stats.max[iaxis]); m_stats.min[iaxis] *= -1.0; m_stats.max[iaxis] *= -1.0; } void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) { its_transform(its, t); double det = t.matrix().block(0, 0, 3, 3).determinant(); if (fix_left_handed && det < 0.) { its_flip_triangles(its); det = -det; } m_stats.volume *= det; update_bounding_box(this->its, this->m_stats); } void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) { its_transform(its, m); double det = m.block(0, 0, 3, 3).determinant(); if (fix_left_handed && det < 0.) { its_flip_triangles(its); det = -det; } m_stats.volume *= det; update_bounding_box(this->its, this->m_stats); } void TriangleMesh::flip_triangles() { its_flip_triangles(its); m_stats.volume = - m_stats.volume; } void TriangleMesh::align_to_origin() { this->translate(- m_stats.min(0), - m_stats.min(1), - m_stats.min(2)); } void TriangleMesh::rotate(double angle, Point* center) { if (angle != 0.) { Vec2f c = center->cast(); this->translate(-c(0), -c(1), 0); its_rotate_z(this->its, (float)angle); this->translate(c(0), c(1), 0); } } /** * Calculates whether or not the mesh is splittable. */ bool TriangleMesh::is_splittable() const { return its_is_splittable(this->its); } std::vector TriangleMesh::split() const { std::vector itss = its_split(this->its); std::vector out; out.reserve(itss.size()); for (indexed_triangle_set &m : itss) { // The TriangleMesh constructor shall fill in the mesh statistics including volume. out.emplace_back(std::move(m)); if (TriangleMesh &triangle_mesh = out.back(); triangle_mesh.volume() < 0) // Some source mesh parts may be incorrectly oriented. Correct them. triangle_mesh.flip_triangles(); } return out; } void TriangleMesh::merge(const TriangleMesh &mesh) { its_merge(this->its, mesh.its); m_stats = m_stats.merge(mesh.m_stats); } // Calculate projection of the mesh into the XY plane, in scaled coordinates. //FIXME This could be extremely slow! Use it for tiny meshes only! ExPolygons TriangleMesh::horizontal_projection() const { return union_ex(project_mesh(this->its, Transform3d::Identity(), []() {})); } // 2D convex hull of a 3D mesh projected into the Z=0 plane. Polygon TriangleMesh::convex_hull() { Points pp; pp.reserve(this->its.vertices.size()); for (size_t i = 0; i < this->its.vertices.size(); ++ i) { const stl_vertex &v = this->its.vertices[i]; pp.emplace_back(Point::new_scale(v(0), v(1))); } return Slic3r::Geometry::convex_hull(pp); } BoundingBoxf3 TriangleMesh::bounding_box() const { BoundingBoxf3 bb; bb.defined = true; bb.min = m_stats.min.cast(); bb.max = m_stats.max.cast(); return bb; } BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) const { BoundingBoxf3 bbox; for (const stl_vertex &v : this->its.vertices) bbox.merge(trafo * v.cast()); return bbox; } BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& trafod, double world_min_z) const { // 1) Allocate transformed vertices with their position with respect to print bed surface. std::vector sides; size_t num_above = 0; Eigen::AlignedBox bbox; Transform3f trafo = trafod.cast(); sides.reserve(its.vertices.size()); for (const stl_vertex &v : this->its.vertices) { const stl_vertex pt = trafo * v; const int sign = pt.z() > world_min_z ? 1 : pt.z() < world_min_z ? -1 : 0; sides.emplace_back(sign); if (sign >= 0) { // Vertex above or on print bed surface. Test whether it is inside the build volume. ++ num_above; bbox.extend(pt); } } // 2) Calculate intersections of triangle edges with the build surface. if (num_above < its.vertices.size()) { // Not completely above the build surface and status may still change by testing edges intersecting the build platform. for (const stl_triangle_vertex_indices &tri : its.indices) { const int s[3] = { sides[tri(0)], sides[tri(1)], sides[tri(2)] }; if (std::min(s[0], std::min(s[1], s[2])) < 0 && std::max(s[0], std::max(s[1], s[2])) > 0) { // Some edge of this triangle intersects the build platform. Calculate the intersection. int iprev = 2; for (int iedge = 0; iedge < 3; ++ iedge) { if (s[iprev] * s[iedge] == -1) { // edge intersects the build surface. Calculate intersection point. const stl_vertex p1 = trafo * its.vertices[tri(iprev)]; const stl_vertex p2 = trafo * its.vertices[tri(iedge)]; // Edge crosses the z plane. Calculate intersection point with the plane. const float t = (world_min_z - p1.z()) / (p2.z() - p1.z()); bbox.extend(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z)); } iprev = iedge; } } } } BoundingBoxf3 out; if (! bbox.isEmpty()) { out.min = bbox.min().cast(); out.max = bbox.max().cast(); out.defined = true; }; return out; } TriangleMesh TriangleMesh::convex_hull_3d() const { TriangleMesh mesh(its_convex_hull(this->its)); // Quite often qhull produces non-manifold mesh. // assert(mesh.stats().manifold()); return mesh; } std::vector TriangleMesh::slice(const std::vector &z) const { // convert doubles to floats std::vector z_f(z.begin(), z.end()); return slice_mesh_ex(this->its, z_f, 0.0004f); } size_t TriangleMesh::memsize() const { size_t memsize = 8 + this->its.memsize() + sizeof(this->m_stats); return memsize; } // Create a mapping from triangle edge into face. struct EdgeToFace { // Index of the 1st vertex of the triangle edge. vertex_low <= vertex_high. int vertex_low; // Index of the 2nd vertex of the triangle edge. int vertex_high; // Index of a triangular face. int face; // Index of edge in the face, starting with 1. Negative indices if the edge was stored reverse in (vertex_low, vertex_high). int face_edge; bool operator==(const EdgeToFace &other) const { return vertex_low == other.vertex_low && vertex_high == other.vertex_high; } bool operator<(const EdgeToFace &other) const { return vertex_low < other.vertex_low || (vertex_low == other.vertex_low && vertex_high < other.vertex_high); } }; template static std::vector create_edge_map( const indexed_triangle_set &its, FaceFilter face_filter, ThrowOnCancelCallback throw_on_cancel) { std::vector edges_map; edges_map.reserve(its.indices.size() * 3); for (uint32_t facet_idx = 0; facet_idx < its.indices.size(); ++ facet_idx) if (face_filter(facet_idx)) for (int i = 0; i < 3; ++ i) { edges_map.push_back({}); EdgeToFace &e2f = edges_map.back(); e2f.vertex_low = its.indices[facet_idx][i]; e2f.vertex_high = its.indices[facet_idx][(i + 1) % 3]; e2f.face = facet_idx; // 1 based indexing, to be always strictly positive. e2f.face_edge = i + 1; if (e2f.vertex_low > e2f.vertex_high) { // Sort the vertices std::swap(e2f.vertex_low, e2f.vertex_high); // and make the face_edge negative to indicate a flipped edge. e2f.face_edge = - e2f.face_edge; } } throw_on_cancel(); std::sort(edges_map.begin(), edges_map.end()); return edges_map; } // Map from a face edge to a unique edge identifier or -1 if no neighbor exists. // Two neighbor faces share a unique edge identifier even if they are flipped. template static inline std::vector its_face_edge_ids_impl(const indexed_triangle_set &its, FaceFilter face_filter, ThrowOnCancelCallback throw_on_cancel) { std::vector out(its.indices.size(), Vec3i(-1, -1, -1)); std::vector edges_map = create_edge_map(its, face_filter, throw_on_cancel); // Assign a unique common edge id to touching triangle edges. int num_edges = 0; for (size_t i = 0; i < edges_map.size(); ++ i) { EdgeToFace &edge_i = edges_map[i]; if (edge_i.face == -1) // This edge has been connected to some neighbor already. continue; // Unconnected edge. Find its neighbor with the correct orientation. size_t j; bool found = false; for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++ j) if (edge_i.face_edge * edges_map[j].face_edge < 0 && edges_map[j].face != -1) { // Faces touching with opposite oriented edges and none of the edges is connected yet. found = true; break; } if (! found) { //FIXME Vojtech: Trying to find an edge with equal orientation. This smells. // admesh can assign the same edge ID to more than two facets (which is // still topologically correct), so we have to search for a duplicate of // this edge too in case it was already seen in this orientation for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++ j) if (edges_map[j].face != -1) { // Faces touching with equally oriented edges and none of the edges is connected yet. found = true; break; } } // Assign an edge index to the 1st face. out[edge_i.face](std::abs(edge_i.face_edge) - 1) = num_edges; if (found) { EdgeToFace &edge_j = edges_map[j]; out[edge_j.face](std::abs(edge_j.face_edge) - 1) = num_edges; // Mark the edge as connected. edge_j.face = -1; } ++ num_edges; if ((i & 0x0ffff) == 0) throw_on_cancel(); } return out; } std::vector its_face_edge_ids(const indexed_triangle_set &its) { return its_face_edge_ids_impl(its, [](const uint32_t){ return true; }, [](){}); } std::vector its_face_edge_ids(const indexed_triangle_set &its, std::function throw_on_cancel_callback) { return its_face_edge_ids_impl(its, [](const uint32_t){ return true; }, throw_on_cancel_callback); } std::vector its_face_edge_ids(const indexed_triangle_set &its, const std::vector &face_mask) { return its_face_edge_ids_impl(its, [&face_mask](const uint32_t idx){ return face_mask[idx]; }, [](){}); } // Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices. std::vector its_face_edge_ids(const indexed_triangle_set &its, std::vector &face_neighbors, bool assign_unbound_edges, int *num_edges) { // out elements are not initialized! std::vector out(face_neighbors.size()); int last_edge_id = 0; for (int i = 0; i < int(face_neighbors.size()); ++ i) { const stl_triangle_vertex_indices &triangle = its.indices[i]; const Vec3i &neighbors = face_neighbors[i]; for (int j = 0; j < 3; ++ j) { int n = neighbors[j]; if (n > i) { const stl_triangle_vertex_indices &triangle2 = its.indices[n]; int edge_id = last_edge_id ++; Vec2i edge = its_triangle_edge(triangle, j); // First find an edge with opposite orientation. std::swap(edge(0), edge(1)); int k = its_triangle_edge_index(triangle2, edge); //FIXME is the following realistic? Could face_neighbors contain such faces? // And if it does, do we want to produce the same edge ID for those mutually incorrectly oriented edges? if (k == -1) { // Second find an edge with the same orientation (the neighbor triangle may be flipped). std::swap(edge(0), edge(1)); k = its_triangle_edge_index(triangle2, edge); } assert(k >= 0); out[i](j) = edge_id; out[n](k) = edge_id; } else if (n == -1) { out[i](j) = assign_unbound_edges ? last_edge_id ++ : -1; } else { // Triangle shall never be neighbor of itself. assert(n < i); // Don't do anything, the neighbor will assign us an edge ID in later iterations. } } } if (num_edges) *num_edges = last_edge_id; return out; } // Merge duplicate vertices, return number of vertices removed. int its_merge_vertices(indexed_triangle_set &its, bool shrink_to_fit) { // 1) Sort indices to vertices lexicographically by coordinates AND vertex index. auto sorted = reserve_vector(its.vertices.size()); for (int i = 0; i < int(its.vertices.size()); ++ i) sorted.emplace_back(i); std::sort(sorted.begin(), sorted.end(), [&its](int il, int ir) { const Vec3f &l = its.vertices[il]; const Vec3f &r = its.vertices[ir]; // Sort lexicographically by coordinates AND vertex index. return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && (l.z() < r.z() || (l.z() == r.z() && il < ir))))); }); // 2) Map duplicate vertices to the one with the lowest vertex index. // The vertex to stay will have a map_vertices[...] == -1 index assigned, the other vertices will point to it. std::vector map_vertices(its.vertices.size(), -1); for (int i = 0; i < int(sorted.size());) { const int u = sorted[i]; const Vec3f &p = its.vertices[u]; int j = i; for (++ j; j < int(sorted.size()); ++ j) { const int v = sorted[j]; const Vec3f &q = its.vertices[v]; if (p != q) break; assert(v > u); map_vertices[v] = u; } i = j; } // 3) Shrink its.vertices, update map_vertices with the new vertex indices. int k = 0; for (int i = 0; i < int(its.vertices.size()); ++ i) { if (map_vertices[i] == -1) { map_vertices[i] = k; if (k < i) its.vertices[k] = its.vertices[i]; ++ k; } else { assert(map_vertices[i] < i); map_vertices[i] = map_vertices[map_vertices[i]]; } } int num_erased = int(its.vertices.size()) - k; if (num_erased) { // Shrink the vertices. its.vertices.erase(its.vertices.begin() + k, its.vertices.end()); // Remap face indices. for (stl_triangle_vertex_indices &face : its.indices) for (int i = 0; i < 3; ++ i) face(i) = map_vertices[face(i)]; // Optionally shrink to fit (reallocate) vertices. if (shrink_to_fit) its.vertices.shrink_to_fit(); } return num_erased; } void its_flip_triangles(indexed_triangle_set &its) { for (stl_triangle_vertex_indices &face : its.indices) std::swap(face(1), face(2)); } int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit) { auto it = std::remove_if(its.indices.begin(), its.indices.end(), [](auto &face) { return face(0) == face(1) || face(0) == face(2) || face(1) == face(2); }); int removed = std::distance(it, its.indices.end()); its.indices.erase(it, its.indices.end()); if (removed && shrink_to_fit) its.indices.shrink_to_fit(); return removed; } int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit) { // First used to mark referenced vertices, later used for mapping old vertex index to a new one. std::vector vertex_map(its.vertices.size(), 0); // Mark referenced vertices. for (const stl_triangle_vertex_indices &face : its.indices) for (int i = 0; i < 3; ++ i) vertex_map[face(i)] = 1; // Compactify vertices, update map from old vertex index to a new one. int last = 0; for (int i = 0; i < int(vertex_map.size()); ++ i) if (vertex_map[i]) { if (last < i) its.vertices[last] = its.vertices[i]; vertex_map[i] = last ++; } int removed = int(its.vertices.size()) - last; if (removed) { its.vertices.erase(its.vertices.begin() + last, its.vertices.end()); // Update faces with the new vertex indices. for (stl_triangle_vertex_indices &face : its.indices) for (int i = 0; i < 3; ++ i) face(i) = vertex_map[face(i)]; // Optionally shrink the vertices. if (shrink_to_fit) its.vertices.shrink_to_fit(); } return removed; } bool its_store_triangle(const indexed_triangle_set &its, const char * obj_filename, size_t triangle_index) { if (its.indices.size() <= triangle_index) return false; Vec3i t = its.indices[triangle_index]; indexed_triangle_set its2; its2.indices = {{0, 1, 2}}; its2.vertices = {its.vertices[t[0]], its.vertices[t[1]], its.vertices[t[2]]}; return its_write_obj(its2, obj_filename); } bool its_store_triangles(const indexed_triangle_set &its, const char * obj_filename, const std::vector & triangles) { indexed_triangle_set its2; its2.vertices.reserve(triangles.size() * 3); its2.indices.reserve(triangles.size()); std::map vertex_map; for (auto ti : triangles) { if (its.indices.size() <= ti) return false; Vec3i t = its.indices[ti]; Vec3i new_t; for (size_t i = 0; i < 3; ++i) { size_t vi = t[i]; auto it = vertex_map.find(vi); if (it != vertex_map.end()) { new_t[i] = it->second; continue; } size_t new_vi = its2.vertices.size(); its2.vertices.push_back(its.vertices[vi]); vertex_map[vi] = new_vi; new_t[i] = new_vi; } its2.indices.push_back(new_t); } return its_write_obj(its2, obj_filename); } void its_shrink_to_fit(indexed_triangle_set &its) { its.indices.shrink_to_fit(); its.vertices.shrink_to_fit(); } template void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, const TransformVertex &transform_fn, const float z, Points &all_pts) { all_pts.reserve(all_pts.size() + its.indices.size() * 3); for (const stl_triangle_vertex_indices &tri : its.indices) { const Vec3f pts[3] = { transform_fn(its.vertices[tri(0)]), transform_fn(its.vertices[tri(1)]), transform_fn(its.vertices[tri(2)]) }; int iprev = 2; for (int iedge = 0; iedge < 3; ++ iedge) { const Vec3f &p1 = pts[iprev]; const Vec3f &p2 = pts[iedge]; if ((p1.z() < z && p2.z() > z) || (p2.z() < z && p1.z() > z)) { // Edge crosses the z plane. Calculate intersection point with the plane. float t = (z - p1.z()) / (p2.z() - p1.z()); all_pts.emplace_back(scaled(p1.x() + (p2.x() - p1.x()) * t), scaled(p1.y() + (p2.y() - p1.y()) * t)); } if (p2.z() >= z) all_pts.emplace_back(scaled(p2.x()), scaled(p2.y())); iprev = iedge; } } } void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, const Matrix3f &m, const float z, Points &all_pts) { return its_collect_mesh_projection_points_above(its, [m](const Vec3f &p){ return m * p; }, z, all_pts); } void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, const Transform3f &t, const float z, Points &all_pts) { return its_collect_mesh_projection_points_above(its, [t](const Vec3f &p){ return t * p; }, z, all_pts); } template Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const TransformVertex &transform_fn, const float z) { Points all_pts; its_collect_mesh_projection_points_above(its, transform_fn, z, all_pts); return Geometry::convex_hull(std::move(all_pts)); } Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Matrix3f &m, const float z) { return its_convex_hull_2d_above(its, [m](const Vec3f &p){ return m * p; }, z); } Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Transform3f &t, const float z) { return its_convex_hull_2d_above(its, [t](const Vec3f &p){ return t * p; }, z); } // Generate the vertex list for a cube solid of arbitrary size in X/Y/Z. indexed_triangle_set its_make_cube(double xd, double yd, double zd) { auto x = float(xd), y = float(yd), z = float(zd); return { { {0, 1, 2}, {0, 2, 3}, {4, 5, 6}, {4, 6, 7}, {0, 4, 7}, {0, 7, 1}, {1, 7, 6}, {1, 6, 2}, {2, 6, 5}, {2, 5, 3}, {4, 0, 3}, {4, 3, 5} }, { {x, y, 0}, {x, 0, 0}, {0, 0, 0}, {0, y, 0}, {x, y, z}, {0, y, z}, {0, 0, z}, {x, 0, z} } }; } indexed_triangle_set its_make_prism(float width, float length, float height) { // We need two upward facing triangles float x = width / 2.f, y = length / 2.f; return { { {0, 1, 2}, // side 1 {4, 3, 5}, // side 2 {1, 4, 2}, {2, 4, 5}, // roof 1 {0, 2, 5}, {0, 5, 3}, // roof 2 {3, 4, 1}, {3, 1, 0} // bottom }, { {-x, -y, 0.f}, {x, -y, 0.f}, {0.f, -y, height}, {-x, y, 0.f}, {x, y, 0.f}, {0.f, y, height}, } }; } // Generate the mesh for a cylinder and return it, using // the generated angle to calculate the top mesh triangles. // Default is 360 sides, angle fa is in radians. indexed_triangle_set its_make_cylinder(double r, double h, double fa) { indexed_triangle_set mesh; size_t n_steps = (size_t)ceil(2. * PI / fa); double angle_step = 2. * PI / n_steps; auto &vertices = mesh.vertices; auto &facets = mesh.indices; vertices.reserve(2 * n_steps + 2); facets.reserve(4 * n_steps); // 2 special vertices, top and bottom center, rest are relative to this vertices.emplace_back(Vec3f(0.f, 0.f, 0.f)); vertices.emplace_back(Vec3f(0.f, 0.f, float(h))); // for each line along the polygon approximating the top/bottom of the // circle, generate four points and four facets (2 for the wall, 2 for the // top and bottom. // Special case: Last line shares 2 vertices with the first line. Vec2f p = Eigen::Rotation2Df(0.f) * Eigen::Vector2f(0, r); vertices.emplace_back(Vec3f(p(0), p(1), 0.f)); vertices.emplace_back(Vec3f(p(0), p(1), float(h))); for (size_t i = 1; i < n_steps; ++i) { p = Eigen::Rotation2Df(angle_step * i) * Eigen::Vector2f(0, float(r)); vertices.emplace_back(Vec3f(p(0), p(1), 0.f)); vertices.emplace_back(Vec3f(p(0), p(1), float(h))); int id = (int)vertices.size() - 1; facets.emplace_back( 0, id - 1, id - 3); // top facets.emplace_back(id, 1, id - 2); // bottom facets.emplace_back(id, id - 2, id - 3); // upper-right of side facets.emplace_back(id, id - 3, id - 1); // bottom-left of side } // Connect the last set of vertices with the first. int id = (int)vertices.size() - 1; facets.emplace_back( 0, 2, id - 1); facets.emplace_back( 3, 1, id); facets.emplace_back(id, 2, 3); facets.emplace_back(id, id - 1, 2); return mesh; } indexed_triangle_set its_make_cone(double r, double h, double fa) { indexed_triangle_set mesh; auto& vertices = mesh.vertices; auto& facets = mesh.indices; vertices.reserve(3 + 2 * size_t(2 * PI / fa)); // base center and top vertex vertices.emplace_back(Vec3f::Zero()); vertices.emplace_back(Vec3f(0., 0., h)); size_t i = 0; for (double angle=0; angle<2*PI; angle+=fa) { vertices.emplace_back(r*std::cos(angle), r*std::sin(angle), 0.); if (angle > 0.) { facets.emplace_back(0, i+2, i+1); facets.emplace_back(1, i+1, i+2); } ++i; } facets.emplace_back(0, 2, i+1); // close the shape facets.emplace_back(1, i+1, 2); return mesh; } indexed_triangle_set its_make_pyramid(float base, float height) { float a = base / 2.f; return { { {0, 1, 2}, {0, 2, 3}, {0, 1, 4}, {1, 2, 4}, {2, 3, 4}, {3, 0, 4} }, { {-a, -a, 0}, {a, -a, 0}, {a, a, 0}, {-a, a, 0}, {0.f, 0.f, height} } }; } // Generates mesh for a sphere centered about the origin, using the generated angle // to determine the granularity. // Default angle is 1 degree. //FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html indexed_triangle_set its_make_sphere(double radius, double fa) { int sectorCount = int(ceil(2. * M_PI / fa)); int stackCount = int(ceil(M_PI / fa)); float sectorStep = float(2. * M_PI / sectorCount); float stackStep = float(M_PI / stackCount); indexed_triangle_set mesh; auto& vertices = mesh.vertices; vertices.reserve((stackCount - 1) * sectorCount + 2); for (int i = 0; i <= stackCount; ++ i) { // from pi/2 to -pi/2 double stackAngle = 0.5 * M_PI - stackStep * i; double xy = radius * cos(stackAngle); double z = radius * sin(stackAngle); if (i == 0 || i == stackCount) vertices.emplace_back(Vec3f(float(xy), 0.f, float(z))); else for (int j = 0; j < sectorCount; ++ j) { // from 0 to 2pi double sectorAngle = sectorStep * j; vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast()); } } auto& facets = mesh.indices; facets.reserve(2 * (stackCount - 1) * sectorCount); for (int i = 0; i < stackCount; ++ i) { // Beginning of current stack. int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); int k1_first = k1; // Beginning of next stack. int k2 = (i == 0) ? 1 : (k1 + sectorCount); int k2_first = k2; for (int j = 0; j < sectorCount; ++ j) { // 2 triangles per sector excluding first and last stacks int k1_next = k1; int k2_next = k2; if (i != 0) { k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); facets.emplace_back(k1, k2, k1_next); } if (i + 1 != stackCount) { k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); facets.emplace_back(k1_next, k2, k2_next); } k1 = k1_next; k2 = k2_next; } } return mesh; } indexed_triangle_set its_convex_hull(const std::vector &pts) { std::vector dst_vertices; std::vector dst_facets; if (! pts.empty()) { // The qhull call: orgQhull::Qhull qhull; qhull.disableOutputStream(); // we want qhull to be quiet #if ! REALfloat std::vector src_vertices; #endif try { #if REALfloat qhull.runQhull("", 3, (int)pts.size(), (const realT*)(pts.front().data()), "Qt"); #else src_vertices.reserve(pts.size() * 3); // We will now fill the vector with input points for computation: for (const stl_vertex &v : pts) for (int i = 0; i < 3; ++ i) src_vertices.emplace_back(v(i)); qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); #endif } catch (...) { BOOST_LOG_TRIVIAL(error) << "its_convex_hull: Unable to create convex hull"; return {}; } // Let's collect results: // Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices). std::vector map_dst_vertices; #ifndef NDEBUG Vec3f centroid = Vec3f::Zero(); for (const stl_vertex& pt : pts) centroid += pt; centroid /= float(pts.size()); #endif // NDEBUG for (const orgQhull::QhullFacet &facet : qhull.facetList()) { // Collect face vertices first, allocate unique vertices in dst_vertices based on QHull's vertex ID. Vec3i indices; int cnt = 0; for (const orgQhull::QhullVertex vertex : facet.vertices()) { int id = vertex.id(); assert(id >= 0); if (id >= int(map_dst_vertices.size())) map_dst_vertices.resize(next_highest_power_of_2(size_t(id + 1)), -1); if (int i = map_dst_vertices[id]; i == -1) { // Allocate a new vertex. i = int(dst_vertices.size()); map_dst_vertices[id] = i; orgQhull::QhullPoint pt(vertex.point()); dst_vertices.emplace_back(pt[0], pt[1], pt[2]); indices[cnt] = i; } else { // Reuse existing vertex. indices[cnt] = i; } if (cnt ++ == 3) break; } assert(cnt == 3); if (cnt == 3) { // QHull sorts vertices of a face lexicographically by their IDs, not by face normals. // Calculate face normal based on the order of vertices. Vec3f n = (dst_vertices[indices(1)] - dst_vertices[indices(0)]).cross(dst_vertices[indices(2)] - dst_vertices[indices(1)]); auto *n2 = facet.getBaseT()->normal; auto d = n.x() * n2[0] + n.y() * n2[1] + n.z() * n2[2]; #ifndef NDEBUG Vec3f n3 = (dst_vertices[indices(0)] - centroid); auto d3 = n.dot(n3); assert((d < 0.f) == (d3 < 0.f)); #endif // NDEBUG // Get the face normal from QHull. if (d < 0.f) // Fix face orientation. std::swap(indices[1], indices[2]); dst_facets.emplace_back(indices); } } } return { std::move(dst_facets), std::move(dst_vertices) }; } void its_reverse_all_facets(indexed_triangle_set &its) { for (stl_triangle_vertex_indices &face : its.indices) std::swap(face[0], face[1]); } void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B) { auto N = int(A.vertices.size()); auto N_f = A.indices.size(); A.vertices.insert(A.vertices.end(), B.vertices.begin(), B.vertices.end()); A.indices.insert(A.indices.end(), B.indices.begin(), B.indices.end()); for(size_t n = N_f; n < A.indices.size(); n++) A.indices[n] += Vec3i{N, N, N}; } void its_merge(indexed_triangle_set &A, const std::vector &triangles) { const size_t offs = A.vertices.size(); A.vertices.insert(A.vertices.end(), triangles.begin(), triangles.end()); A.indices.reserve(A.indices.size() + A.vertices.size() / 3); for(int i = int(offs); i < int(A.vertices.size()); i += 3) A.indices.emplace_back(i, i + 1, i + 2); } void its_merge(indexed_triangle_set &A, const Pointf3s &triangles) { auto trianglesf = reserve_vector (triangles.size()); for (auto &t : triangles) trianglesf.emplace_back(t.cast()); its_merge(A, trianglesf); } float its_volume(const indexed_triangle_set &its) { if (its.empty()) return 0.; // Choose a point, any point as the reference. auto p0 = its.vertices.front(); float volume = 0.f; for (size_t i = 0; i < its.indices.size(); ++ i) { // Do dot product to get distance from point to plane. its_triangle triangle = its_triangle_vertices(its, i); Vec3f U = triangle[1] - triangle[0]; Vec3f V = triangle[2] - triangle[0]; Vec3f C = U.cross(V); Vec3f normal = C.normalized(); float area = 0.5 * C.norm(); float height = normal.dot(triangle[0] - p0); volume += (area * height) / 3.0f; } return volume; } float its_average_edge_length(const indexed_triangle_set &its) { if (its.indices.empty()) return 0.f; double edge_length = 0.f; for (size_t i = 0; i < its.indices.size(); ++ i) { const its_triangle v = its_triangle_vertices(its, i); edge_length += (v[1] - v[0]).cast().norm() + (v[2] - v[0]).cast().norm() + (v[1] - v[2]).cast().norm(); } return float(edge_length / (3 * its.indices.size())); } std::vector its_split(const indexed_triangle_set &its) { return its_split<>(its); } // Number of disconnected patches (faces are connected if they share an edge, shared edge defined with 2 shared vertex indices). size_t its_number_of_patches(const indexed_triangle_set &its) { return its_number_of_patches<>(its); } size_t its_number_of_patches(const indexed_triangle_set &its, const std::vector &face_neighbors) { return its_number_of_patches<>(ItsNeighborsWrapper{ its, face_neighbors }); } // Same as its_number_of_patches(its) > 1, but faster. bool its_is_splittable(const indexed_triangle_set &its) { return its_is_splittable<>(its); } bool its_is_splittable(const indexed_triangle_set &its, const std::vector &face_neighbors) { return its_is_splittable<>(ItsNeighborsWrapper{ its, face_neighbors }); } size_t its_num_open_edges(const std::vector &face_neighbors) { size_t num_open_edges = 0; for (Vec3i neighbors : face_neighbors) for (int n : neighbors) if (n < 0) ++ num_open_edges; return num_open_edges; } size_t its_num_open_edges(const indexed_triangle_set &its) { return its_num_open_edges(its_face_neighbors(its)); } void VertexFaceIndex::create(const indexed_triangle_set &its) { m_vertex_to_face_start.assign(its.vertices.size() + 1, 0); // 1) Calculate vertex incidence by scatter. for (auto &face : its.indices) { ++ m_vertex_to_face_start[face(0) + 1]; ++ m_vertex_to_face_start[face(1) + 1]; ++ m_vertex_to_face_start[face(2) + 1]; } // 2) Prefix sum to calculate offsets to m_vertex_faces_all. for (size_t i = 2; i < m_vertex_to_face_start.size(); ++ i) m_vertex_to_face_start[i] += m_vertex_to_face_start[i - 1]; // 3) Scatter indices of faces incident to a vertex into m_vertex_faces_all. m_vertex_faces_all.assign(m_vertex_to_face_start.back(), 0); for (size_t face_idx = 0; face_idx < its.indices.size(); ++ face_idx) { auto &face = its.indices[face_idx]; for (int i = 0; i < 3; ++ i) m_vertex_faces_all[m_vertex_to_face_start[face(i)] ++] = face_idx; } // 4) The previous loop modified m_vertex_to_face_start. Revert the change. for (auto i = int(m_vertex_to_face_start.size()) - 1; i > 0; -- i) m_vertex_to_face_start[i] = m_vertex_to_face_start[i - 1]; m_vertex_to_face_start.front() = 0; } std::vector its_face_neighbors(const indexed_triangle_set &its) { return create_face_neighbors_index(ex_seq, its); } std::vector its_face_neighbors_par(const indexed_triangle_set &its) { return create_face_neighbors_index(ex_tbb, its); } std::vector its_face_normals(const indexed_triangle_set &its) { std::vector normals; normals.reserve(its.indices.size()); for (stl_triangle_vertex_indices face : its.indices) normals.push_back(its_face_normal(its, face)); return normals; } #if BOOST_ENDIAN_LITTLE_BYTE static inline void big_endian_reverse_quads(char*, size_t) {} #else // BOOST_ENDIAN_LITTLE_BYTE static inline void big_endian_reverse_quads(char *buf, size_t cnt) { for (size_t i = 0; i < cnt; i += 4) { std::swap(buf[i], buf[i+3]); std::swap(buf[i+1], buf[i+2]); } } #endif // BOOST_ENDIAN_LITTLE_BYTE bool its_write_stl_ascii(const char *file, const char *label, const std::vector &indices, const std::vector &vertices) { FILE *fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "its_write_stl_ascii: Couldn't open " << file << " for writing"; return false; } fprintf(fp, "solid %s\n", label); for (const stl_triangle_vertex_indices& face : indices) { Vec3f vertex[3] = { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; Vec3f normal = (vertex[1] - vertex[0]).cross(vertex[2] - vertex[1]).normalized(); fprintf(fp, " facet normal % .8E % .8E % .8E\n", normal(0), normal(1), normal(2)); fprintf(fp, " outer loop\n"); fprintf(fp, " vertex % .8E % .8E % .8E\n", vertex[0](0), vertex[0](1), vertex[0](2)); fprintf(fp, " vertex % .8E % .8E % .8E\n", vertex[1](0), vertex[1](1), vertex[1](2)); fprintf(fp, " vertex % .8E % .8E % .8E\n", vertex[2](0), vertex[2](1), vertex[2](2)); fprintf(fp, " endloop\n"); fprintf(fp, " endfacet\n"); } fprintf(fp, "endsolid %s\n", label); fclose(fp); return true; } bool its_write_stl_binary(const char *file, const char *label, const std::vector &indices, const std::vector &vertices) { FILE *fp = boost::nowide::fopen(file, "wb"); if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "its_write_stl_binary: Couldn't open " << file << " for writing"; return false; } { static constexpr const int header_size = 80; std::vector header(header_size, 0); if (int header_len = std::min((label == nullptr) ? 0 : int(strlen(label)), header_size); header_len > 0) ::memcpy(header.data(), label, header_len); ::fwrite(header.data(), header_size, 1, fp); } uint32_t nfaces = indices.size(); big_endian_reverse_quads(reinterpret_cast(&nfaces), 4); ::fwrite(&nfaces, 4, 1, fp); stl_facet f; f.extra[0] = 0; f.extra[1] = 0; for (const stl_triangle_vertex_indices& face : indices) { f.vertex[0] = vertices[face(0)]; f.vertex[1] = vertices[face(1)]; f.vertex[2] = vertices[face(2)]; f.normal = (f.vertex[1] - f.vertex[0]).cross(f.vertex[2] - f.vertex[1]).normalized(); big_endian_reverse_quads(reinterpret_cast(&f), 48); fwrite(&f, 50, 1, fp); } fclose(fp); return true; } } // namespace Slic3r