diff --git a/include/utils/polygon.h b/include/utils/polygon.h index 8bd2303429..5c71e713cf 100644 --- a/include/utils/polygon.h +++ b/include/utils/polygon.h @@ -545,7 +545,7 @@ class PolygonRef : public ConstPolygonRef } } - void removeColinearEdges(const AngleRadians max_deviation_angle); + void removeCollinearPoints(const AngleRadians max_deviation_angle); /*! * Removes consecutive line segments with same orientation and changes this polygon. @@ -1145,12 +1145,12 @@ class Polygons Polygons smooth2(int remove_length, int min_area) const; //!< removes points connected to small lines - void removeColinearEdges(const AngleRadians max_deviation_angle = AngleRadians(0.0005)) + void removeCollinearPoints(const AngleRadians max_deviation_angle = AngleRadians(0.0005)) { Polygons& thiss = *this; for (size_t p = 0; p < size(); p++) { - thiss[p].removeColinearEdges(max_deviation_angle); + thiss[p].removeCollinearPoints(max_deviation_angle); if (thiss[p].size() < 3) { remove(p); diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index ab1094915d..534f235bf7 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -56,30 +56,27 @@ WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0 const std::vector& WallToolPaths::generate() { - const coord_t allowed_distance = settings.get("meshfix_maximum_deviation"); - // Sometimes small slivers of polygons mess up the prepared_outline. By performing an open-close operation // with half the minimum printable feature size or minimum line width, these slivers are removed, while still // keeping enough information to not degrade the print quality; // These features can't be printed anyhow. See PR CuraEngine#1811 for some screenshots - const coord_t open_close_distance = settings.get("fill_outline_gaps") ? settings.get("min_feature_size") / 2 - 5 : settings.get("min_wall_line_width") / 2 - 5; - const coord_t epsilon_offset = (allowed_distance / 2) - 1; + const coord_t close_distance = settings.get("fill_outline_gaps") ? settings.get("min_feature_size") / 2 - 3 : settings.get("min_wall_line_width") / 2 - 3; + const coord_t open_distance = 300; const AngleRadians transitioning_angle = settings.get("wall_transition_angle"); constexpr coord_t discretization_step_size = MM2INT(0.8); - // Simplify outline for boost::voronoi consumption. Absolutely no self intersections or near-self intersections allowed: - // TODO: Open question: Does this indeed fix all (or all-but-one-in-a-million) cases for manifold but otherwise possibly complex polygons? - Polygons prepared_outline = outline.offset(-open_close_distance).offset(open_close_distance * 2).offset(-open_close_distance); - prepared_outline.removeSmallAreas(small_area_length * small_area_length, false); - prepared_outline = Simplify(settings).polygon(prepared_outline); - PolygonUtils::fixSelfIntersections(epsilon_offset, prepared_outline); - prepared_outline.removeDegenerateVerts(); - prepared_outline.removeColinearEdges(AngleRadians(0.005)); - // Removing collinear edges may introduce self intersections, so we need to fix them again - PolygonUtils::fixSelfIntersections(epsilon_offset, prepared_outline); - prepared_outline.removeDegenerateVerts(); - prepared_outline = prepared_outline.unionPolygons(); + // Simplify outline for boost::voronoi consumption. Absolutely no + // - self intersections, + // - near-self intersections or + // - collinear points + // allowed: + Polygons prepared_outline = outline + .offset(open_distance) + .offset(-open_distance - close_distance, ClipperLib::jtRound) + .offset(close_distance, ClipperLib::jtRound); prepared_outline = Simplify(settings).polygon(prepared_outline); + prepared_outline.removeSmallAreas(small_area_length * small_area_length, false); + prepared_outline.removeCollinearPoints(AngleRadians(0.005)); if (prepared_outline.area() <= 0) { diff --git a/src/utils/polygon.cpp b/src/utils/polygon.cpp index 5c8884a6ad..1e1c2f0a5a 100644 --- a/src/utils/polygon.cpp +++ b/src/utils/polygon.cpp @@ -427,80 +427,82 @@ Polygons ConstPolygonRef::offset(int distance, ClipperLib::JoinType join_type, d return ret; } -void PolygonRef::removeColinearEdges(const AngleRadians max_deviation_angle) +void PolygonRef::removeCollinearPoints(const AngleRadians max_deviation_angle) { - // TODO: Can be made more efficient (for example, use pointer-types for process-/skip-indices, so we can swap them without copy). + auto is_collinear = [max_deviation_angle](const Point& p1, const Point& p2, const Point& p3) { + auto angle = LinearAlg2D::getAngleLeft(p1, p2, p3); - size_t num_removed_in_iteration = 0; - do + if (angle >= M_PI) + { + angle -= M_PI; + } + + return angle < max_deviation_angle || angle > M_PI - max_deviation_angle; + }; + + if (path->size() < 3) { + return; + } + else if (path->size() == 3 && is_collinear((*path)[0], (*path)[1], (*path)[2])) { - num_removed_in_iteration = 0; + // if the polygon is a triangle and collinear, remove all points + path->clear(); + return; + } - std::vector process_indices(path->size(), true); + ClipperLib::Path new_path; - bool go = true; - while (go) - { - go = false; + for (int i = 0; i < path->size(); i++) { + auto next = (*path)[i]; + while (new_path.size() >= 2) { + auto prev = new_path[new_path.size() - 2]; + auto pt = new_path[new_path.size() - 1]; - const auto& rpath = *path; - const size_t pathlen = rpath.size(); - if (pathlen <= 3) + if (is_collinear(prev, pt, next)) { - return; + new_path.pop_back(); } - - std::vector skip_indices(path->size(), false); - - ClipperLib::Path new_path; - for (size_t point_idx = 0; point_idx < pathlen; ++point_idx) + else { - // Don't iterate directly over process-indices, but do it this way, because there are points _in_ process-indices that should nonetheless be skipped: - if (! process_indices[point_idx]) - { - new_path.push_back(rpath[point_idx]); - continue; - } - - // Should skip the last point for this iteration if the old first was removed (which can be seen from the fact that the new first was skipped): - if (point_idx == (pathlen - 1) && skip_indices[0]) - { - skip_indices[new_path.size()] = true; - go = true; - new_path.push_back(rpath[point_idx]); - break; - } + break; + } + } + new_path.push_back((*path)[i]); + } - const Point& prev = rpath[(point_idx - 1 + pathlen) % pathlen]; - const Point& pt = rpath[point_idx]; - const Point& next = rpath[(point_idx + 1) % pathlen]; + while (new_path.size() >= 3) + { + auto prev = new_path[new_path.size() - 1]; + auto pt = new_path[0]; + auto next = new_path[1]; - float angle = LinearAlg2D::getAngleLeft(prev, pt, next); // [0 : 2 * pi] - if (angle >= M_PI) {angle -= M_PI;} // map [pi : 2 * pi] to [0 : pi] + if (is_collinear(prev, pt, next)) + { + new_path.erase(new_path.begin()); + } + else + { + break; + } + } - // Check if the angle is within limits for the point to 'make sense', given the maximum deviation. - // If the angle indicates near-parallel segments ignore the point 'pt' - if (angle > max_deviation_angle && angle < M_PI - max_deviation_angle) - { - new_path.push_back(pt); - } - else if (point_idx != (pathlen - 1)) - { - // Skip the next point, since the current one was removed: - skip_indices[new_path.size()] = true; - go = true; - new_path.push_back(next); - ++point_idx; - } - } - *path = new_path; - num_removed_in_iteration += pathlen - path->size(); + while (new_path.size() >= 3) + { + auto prev = new_path[new_path.size() - 2]; + auto pt = new_path[new_path.size() - 1]; + auto next = new_path[0]; - process_indices.clear(); - process_indices.insert(process_indices.end(), skip_indices.begin(), skip_indices.end()); + if (is_collinear(prev, pt, next)) + { + new_path.pop_back(); + } + else + { + break; } } - while (num_removed_in_iteration > 0); + + *path = new_path; } void PolygonRef::applyMatrix(const PointMatrix& matrix)