diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 52ac4a0aa..065a69ba3 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -46,11 +46,29 @@ void EdgeGrid::Grid::create(const Polygons &polygons, coord_t resolution) ++ ncontours; // Collect the contours. - m_contours.assign(ncontours, NULL); + m_contours.assign(ncontours, nullptr); ncontours = 0; for (size_t j = 0; j < polygons.size(); ++ j) if (! polygons[j].points.empty()) - m_contours[ncontours++] = &polygons[j].points; + m_contours[ncontours ++] = &polygons[j].points; + + create_from_m_contours(resolution); +} + +void EdgeGrid::Grid::create(const std::vector &polygons, coord_t resolution) +{ + // Count the contours. + size_t ncontours = 0; + for (size_t j = 0; j < polygons.size(); ++ j) + if (! polygons[j].empty()) + ++ ncontours; + + // Collect the contours. + m_contours.assign(ncontours, nullptr); + ncontours = 0; + for (size_t j = 0; j < polygons.size(); ++ j) + if (! polygons[j].empty()) + m_contours[ncontours ++] = &polygons[j]; create_from_m_contours(resolution); } @@ -66,7 +84,7 @@ void EdgeGrid::Grid::create(const ExPolygon &expoly, coord_t resolution) ++ ncontours; // Collect the contours. - m_contours.assign(ncontours, NULL); + m_contours.assign(ncontours, nullptr); ncontours = 0; if (! expoly.contour.points.empty()) m_contours[ncontours++] = &expoly.contour.points; @@ -91,7 +109,7 @@ void EdgeGrid::Grid::create(const ExPolygons &expolygons, coord_t resolution) } // Collect the contours. - m_contours.assign(ncontours, NULL); + m_contours.assign(ncontours, nullptr); ncontours = 0; for (size_t i = 0; i < expolygons.size(); ++ i) { const ExPolygon &expoly = expolygons[i]; @@ -1122,7 +1140,7 @@ EdgeGrid::Grid::ClosestPointResult EdgeGrid::Grid::closest_point(const Point &pt Vec2d vfoot = foot - pt.cast(); double dist_foot = vfoot.norm(); double dist_foot_err = dist_foot - d_min; - assert(std::abs(dist_foot_err) < 1e-7 * d_min); + assert(std::abs(dist_foot_err) < 1e-7 || std::abs(dist_foot_err) < 1e-7 * d_min); #endif /* NDEBUG */ } } @@ -1145,7 +1163,7 @@ EdgeGrid::Grid::ClosestPointResult EdgeGrid::Grid::closest_point(const Point &pt vfoot = p1.cast() * (1. - result.t) + p2.cast() * result.t - pt.cast(); double dist_foot = vfoot.norm(); double dist_foot_err = dist_foot - std::abs(result.distance); - assert(std::abs(dist_foot_err) < 1e-7 * std::abs(result.distance)); + assert(std::abs(dist_foot_err) < 1e-7 || std::abs(dist_foot_err) < 1e-7 * std::abs(result.distance)); } #endif /* NDEBUG */ } else diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 92cee8362..1a51f3439 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -21,6 +21,7 @@ public: void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; } void create(const Polygons &polygons, coord_t resolution); + void create(const std::vector &polygons, coord_t resolution); void create(const ExPolygon &expoly, coord_t resolution); void create(const ExPolygons &expolygons, coord_t resolution); void create(const ExPolygonCollection &expolygons, coord_t resolution); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 7833c9c91..4ee8974f4 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -77,6 +77,11 @@ public: void triangulate_pp(Points *triangles) const; void triangulate_p2t(Polygons* polygons) const; Lines lines() const; + + // Number of contours (outer contour with holes). + size_t num_contours() const { return this->holes.size() + 1; } + Polygon& contour_or_hole(size_t idx) { return (idx == 0) ? this->contour : this->holes[idx - 1]; } + const Polygon& contour_or_hole(size_t idx) const { return (idx == 0) ? this->contour : this->holes[idx - 1]; } }; inline bool operator==(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour == rhs.contour && lhs.holes == rhs.holes; } diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 820f0008b..6c4b4d903 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -158,43 +158,18 @@ void Fill3DHoneycomb::_fill_surface_single( ((this->layer_id/thickness_layers) % 2) + 1); // move pattern in place - for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) - it->translate(bb.min(0), bb.min(1)); + for (Polyline &pl : polylines) + pl.translate(bb.min); - // clip pattern to boundaries - polylines = intersection_pl(polylines, (Polygons)expolygon); + // clip pattern to boundaries, chain the clipped polylines + Polylines polylines_chained = chain_polylines(intersection_pl(polylines, to_polygons(expolygon))); - // connect lines - if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections - ExPolygon expolygon_off; - { - ExPolygons expolygons_off = offset_ex(expolygon, SCALED_EPSILON); - if (! expolygons_off.empty()) { - // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island. - assert(expolygons_off.size() == 1); - std::swap(expolygon_off, expolygons_off.front()); - } - } - bool first = true; - for (Polyline &polyline : chain_polylines(std::move(polylines))) { - if (! first) { - // Try to connect the lines. - Points &pts_end = polylines_out.back().points; - const Point &first_point = polyline.points.front(); - const Point &last_point = pts_end.back(); - // TODO: we should also check that both points are on a fill_boundary to avoid - // connecting paths on the boundaries of internal regions - if ((last_point - first_point).cast().norm() <= 1.5 * distance && - expolygon_off.contains(Line(last_point, first_point))) { - // Append the polyline. - pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); - continue; - } - } - // The lines cannot be connected. - polylines_out.emplace_back(std::move(polyline)); - first = false; - } + // connect lines if needed + if (! polylines_chained.empty()) { + if (params.dont_connect) + append(polylines_out, std::move(polylines_chained)); + else + this->connect_infill(std::move(polylines_chained), expolygon, polylines_out, params); } } diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index c16d76415..0ba75465f 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -1,8 +1,10 @@ #include #include "../ClipperUtils.hpp" +#include "../EdgeGrid.hpp" #include "../Surface.hpp" #include "../PrintConfig.hpp" +#include "../libslic3r.h" #include "FillBase.hpp" #include "FillConcentric.hpp" @@ -148,4 +150,814 @@ std::pair Fill::_infill_direction(const Surface *surface) const return std::pair(out_angle, out_shift); } +#if 0 +// From pull request "Gyroid improvements" #2730 by @supermerill + +/// cut poly between poly.point[idx_1] & poly.point[idx_1+1] +/// add p1+-width to one part and p2+-width to the other one. +/// add the "new" polyline to polylines (to part cut from poly) +/// p1 & p2 have to be between poly.point[idx_1] & poly.point[idx_1+1] +/// if idx_1 is ==0 or == size-1, then we don't need to create a new polyline. +static void cut_polyline(Polyline &poly, Polylines &polylines, size_t idx_1, Point p1, Point p2) { + //reorder points + if (p1.distance_to_square(poly.points[idx_1]) > p2.distance_to_square(poly.points[idx_1])) { + Point temp = p2; + p2 = p1; + p1 = temp; + } + if (idx_1 == poly.points.size() - 1) { + //shouldn't be possible. + poly.points.erase(poly.points.end() - 1); + } else { + // create new polyline + Polyline new_poly; + //put points in new_poly + new_poly.points.push_back(p2); + new_poly.points.insert(new_poly.points.end(), poly.points.begin() + idx_1 + 1, poly.points.end()); + //erase&put points in poly + poly.points.erase(poly.points.begin() + idx_1 + 1, poly.points.end()); + poly.points.push_back(p1); + //safe test + if (poly.length() == 0) + poly.points = new_poly.points; + else + polylines.emplace_back(new_poly); + } +} + +/// the poly is like a polygon but with first_point != last_point (already removed) +static void cut_polygon(Polyline &poly, size_t idx_1, Point p1, Point p2) { + //reorder points + if (p1.distance_to_square(poly.points[idx_1]) > p2.distance_to_square(poly.points[idx_1])) { + Point temp = p2; + p2 = p1; + p1 = temp; + } + //check if we need to rotate before cutting + if (idx_1 != poly.size() - 1) { + //put points in new_poly + poly.points.insert(poly.points.end(), poly.points.begin(), poly.points.begin() + idx_1 + 1); + poly.points.erase(poly.points.begin(), poly.points.begin() + idx_1 + 1); + } + //put points in poly + poly.points.push_back(p1); + poly.points.insert(poly.points.begin(), p2); +} + +/// check if the polyline from pts_to_check may be at 'width' distance of a point in polylines_blocker +/// it use equally_spaced_points with width/2 precision, so don't worry with pts_to_check number of points. +/// it use the given polylines_blocker points, be sure to put enough of them to be reliable. +/// complexity : N(pts_to_check.equally_spaced_points(width / 2)) x N(polylines_blocker.points) +static bool collision(const Points &pts_to_check, const Polylines &polylines_blocker, const coordf_t width) { + //check if it's not too close to a polyline + coordf_t min_dist_square = width * width * 0.9 - SCALED_EPSILON; + Polyline better_polylines(pts_to_check); + Points better_pts = better_polylines.equally_spaced_points(width / 2); + for (const Point &p : better_pts) { + for (const Polyline &poly2 : polylines_blocker) { + for (const Point &p2 : poly2.points) { + if (p.distance_to_square(p2) < min_dist_square) { + return true; + } + } + } + } + return false; +} + +/// Try to find a path inside polylines that allow to go from p1 to p2. +/// width if the width of the extrusion +/// polylines_blockers are the array of polylines to check if the path isn't blocked by something. +/// complexity: N(polylines.points) + a collision check after that if we finded a path: N(2(p2-p1)/width) x N(polylines_blocker.points) +static Points get_frontier(Polylines &polylines, const Point& p1, const Point& p2, const coord_t width, const Polylines &polylines_blockers, coord_t max_size = -1) { + for (size_t idx_poly = 0; idx_poly < polylines.size(); ++idx_poly) { + Polyline &poly = polylines[idx_poly]; + if (poly.size() <= 1) continue; + + //loop? + if (poly.first_point() == poly.last_point()) { + //polygon : try to find a line for p1 & p2. + size_t idx_11, idx_12, idx_21, idx_22; + idx_11 = poly.closest_point_index(p1); + idx_12 = idx_11; + if (Line(poly.points[idx_11], poly.points[(idx_11 + 1) % (poly.points.size() - 1)]).distance_to(p1) < SCALED_EPSILON) { + idx_12 = (idx_11 + 1) % (poly.points.size() - 1); + } else if (Line(poly.points[(idx_11 > 0) ? (idx_11 - 1) : (poly.points.size() - 2)], poly.points[idx_11]).distance_to(p1) < SCALED_EPSILON) { + idx_11 = (idx_11 > 0) ? (idx_11 - 1) : (poly.points.size() - 2); + } else { + continue; + } + idx_21 = poly.closest_point_index(p2); + idx_22 = idx_21; + if (Line(poly.points[idx_21], poly.points[(idx_21 + 1) % (poly.points.size() - 1)]).distance_to(p2) < SCALED_EPSILON) { + idx_22 = (idx_21 + 1) % (poly.points.size() - 1); + } else if (Line(poly.points[(idx_21 > 0) ? (idx_21 - 1) : (poly.points.size() - 2)], poly.points[idx_21]).distance_to(p2) < SCALED_EPSILON) { + idx_21 = (idx_21 > 0) ? (idx_21 - 1) : (poly.points.size() - 2); + } else { + continue; + } + + + //edge case: on the same line + if (idx_11 == idx_21 && idx_12 == idx_22) { + if (collision(Points() = { p1, p2 }, polylines_blockers, width)) return Points(); + //break loop + poly.points.erase(poly.points.end() - 1); + cut_polygon(poly, idx_11, p1, p2); + return Points() = { Line(p1, p2).midpoint() }; + } + + //compute distance & array for the ++ path + Points ret_1_to_2; + double dist_1_to_2 = p1.distance_to(poly.points[idx_12]); + ret_1_to_2.push_back(poly.points[idx_12]); + size_t max = idx_12 <= idx_21 ? idx_21+1 : poly.points.size(); + for (size_t i = idx_12 + 1; i < max; i++) { + dist_1_to_2 += poly.points[i - 1].distance_to(poly.points[i]); + ret_1_to_2.push_back(poly.points[i]); + } + if (idx_12 > idx_21) { + dist_1_to_2 += poly.points.back().distance_to(poly.points.front()); + ret_1_to_2.push_back(poly.points[0]); + for (size_t i = 1; i <= idx_21; i++) { + dist_1_to_2 += poly.points[i - 1].distance_to(poly.points[i]); + ret_1_to_2.push_back(poly.points[i]); + } + } + dist_1_to_2 += p2.distance_to(poly.points[idx_21]); + + //compute distance & array for the -- path + Points ret_2_to_1; + double dist_2_to_1 = p1.distance_to(poly.points[idx_11]); + ret_2_to_1.push_back(poly.points[idx_11]); + size_t min = idx_22 <= idx_11 ? idx_22 : 0; + for (size_t i = idx_11; i > min; i--) { + dist_2_to_1 += poly.points[i - 1].distance_to(poly.points[i]); + ret_2_to_1.push_back(poly.points[i - 1]); + } + if (idx_22 > idx_11) { + dist_2_to_1 += poly.points.back().distance_to(poly.points.front()); + ret_2_to_1.push_back(poly.points[poly.points.size() - 1]); + for (size_t i = poly.points.size() - 1; i > idx_22; i--) { + dist_2_to_1 += poly.points[i - 1].distance_to(poly.points[i]); + ret_2_to_1.push_back(poly.points[i - 1]); + } + } + dist_2_to_1 += p2.distance_to(poly.points[idx_22]); + + if (max_size < dist_2_to_1 && max_size < dist_1_to_2) { + return Points(); + } + + //choose between the two direction (keep the short one) + if (dist_1_to_2 < dist_2_to_1) { + if (collision(ret_1_to_2, polylines_blockers, width)) return Points(); + //break loop + poly.points.erase(poly.points.end() - 1); + //remove points + if (idx_12 <= idx_21) { + poly.points.erase(poly.points.begin() + idx_12, poly.points.begin() + idx_21 + 1); + if (idx_12 != 0) { + cut_polygon(poly, idx_11, p1, p2); + } //else : already cut at the good place + } else { + poly.points.erase(poly.points.begin() + idx_12, poly.points.end()); + poly.points.erase(poly.points.begin(), poly.points.begin() + idx_21); + cut_polygon(poly, poly.points.size() - 1, p1, p2); + } + return ret_1_to_2; + } else { + if (collision(ret_2_to_1, polylines_blockers, width)) return Points(); + //break loop + poly.points.erase(poly.points.end() - 1); + //remove points + if (idx_22 <= idx_11) { + poly.points.erase(poly.points.begin() + idx_22, poly.points.begin() + idx_11 + 1); + if (idx_22 != 0) { + cut_polygon(poly, idx_21, p1, p2); + } //else : already cut at the good place + } else { + poly.points.erase(poly.points.begin() + idx_22, poly.points.end()); + poly.points.erase(poly.points.begin(), poly.points.begin() + idx_11); + cut_polygon(poly, poly.points.size() - 1, p1, p2); + } + return ret_2_to_1; + } + } else { + //polyline : try to find a line for p1 & p2. + size_t idx_1, idx_2; + idx_1 = poly.closest_point_index(p1); + if (idx_1 < poly.points.size() - 1 && Line(poly.points[idx_1], poly.points[idx_1 + 1]).distance_to(p1) < SCALED_EPSILON) { + } else if (idx_1 > 0 && Line(poly.points[idx_1 - 1], poly.points[idx_1]).distance_to(p1) < SCALED_EPSILON) { + idx_1 = idx_1 - 1; + } else { + continue; + } + idx_2 = poly.closest_point_index(p2); + if (idx_2 < poly.points.size() - 1 && Line(poly.points[idx_2], poly.points[idx_2 + 1]).distance_to(p2) < SCALED_EPSILON) { + } else if (idx_2 > 0 && Line(poly.points[idx_2 - 1], poly.points[idx_2]).distance_to(p2) < SCALED_EPSILON) { + idx_2 = idx_2 - 1; + } else { + continue; + } + + //edge case: on the same line + if (idx_1 == idx_2) { + if (collision(Points() = { p1, p2 }, polylines_blockers, width)) return Points(); + cut_polyline(poly, polylines, idx_1, p1, p2); + return Points() = { Line(p1, p2).midpoint() }; + } + + //create ret array + size_t first_idx = idx_1; + size_t last_idx = idx_2 + 1; + if (idx_1 > idx_2) { + first_idx = idx_2; + last_idx = idx_1 + 1; + } + Points p_ret; + p_ret.insert(p_ret.end(), poly.points.begin() + first_idx + 1, poly.points.begin() + last_idx); + + coordf_t length = 0; + for (size_t i = 1; i < p_ret.size(); i++) length += p_ret[i - 1].distance_to(p_ret[i]); + + if (max_size < length) { + return Points(); + } + + if (collision(p_ret, polylines_blockers, width)) return Points(); + //cut polyline + poly.points.erase(poly.points.begin() + first_idx + 1, poly.points.begin() + last_idx); + cut_polyline(poly, polylines, first_idx, p1, p2); + //order the returned array to be p1->p2 + if (idx_1 > idx_2) { + std::reverse(p_ret.begin(), p_ret.end()); + } + return p_ret; + } + + } + + return Points(); +} + +/// Connect the infill_ordered polylines, in this order, from the back point to the next front point. +/// It uses only the boundary polygons to do so, and can't pass two times at the same place. +/// It avoid passing over the infill_ordered's polylines (preventing local over-extrusion). +/// return the connected polylines in polylines_out. Can output polygons (stored as polylines with first_point = last_point). +/// complexity: worst: N(infill_ordered.points) x N(boundary.points) +/// typical: N(infill_ordered) x ( N(boundary.points) + N(infill_ordered.points) ) +void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const FillParams ¶ms) { + + //TODO: fallback to the quick & dirty old algorithm when n(points) is too high. + Polylines polylines_frontier = to_polylines(((Polygons)boundary)); + + Polylines polylines_blocker; + coord_t clip_size = scale_(this->spacing) * 2; + for (const Polyline &polyline : infill_ordered) { + if (polyline.length() > 2.01 * clip_size) { + polylines_blocker.push_back(polyline); + polylines_blocker.back().clip_end(clip_size); + polylines_blocker.back().clip_start(clip_size); + } + } + + //length between two lines + coordf_t ideal_length = (1 / params.density) * this->spacing; + + Polylines polylines_connected_first; + bool first = true; + for (const Polyline &polyline : infill_ordered) { + if (!first) { + // Try to connect the lines. + Points &pts_end = polylines_connected_first.back().points; + const Point &last_point = pts_end.back(); + const Point &first_point = polyline.points.front(); + if (last_point.distance_to(first_point) < scale_(this->spacing) * 10) { + Points pts_frontier = get_frontier(polylines_frontier, last_point, first_point, scale_(this->spacing), polylines_blocker, (coord_t)scale_(ideal_length) * 2); + if (!pts_frontier.empty()) { + // The lines can be connected. + pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end()); + pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); + continue; + } + } + } + // The lines cannot be connected. + polylines_connected_first.emplace_back(std::move(polyline)); + + first = false; + } + + Polylines polylines_connected; + first = true; + for (const Polyline &polyline : polylines_connected_first) { + if (!first) { + // Try to connect the lines. + Points &pts_end = polylines_connected.back().points; + const Point &last_point = pts_end.back(); + const Point &first_point = polyline.points.front(); + + Polylines before = polylines_frontier; + Points pts_frontier = get_frontier(polylines_frontier, last_point, first_point, scale_(this->spacing), polylines_blocker); + if (!pts_frontier.empty()) { + // The lines can be connected. + pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end()); + pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); + continue; + } + } + // The lines cannot be connected. + polylines_connected.emplace_back(std::move(polyline)); + + first = false; + } + + //try to link to nearest point if possible + for (size_t idx1 = 0; idx1 < polylines_connected.size(); idx1++) { + size_t min_idx = 0; + coordf_t min_length = 0; + bool switch_id1 = false; + bool switch_id2 = false; + for (size_t idx2 = idx1 + 1; idx2 < polylines_connected.size(); idx2++) { + double last_first = polylines_connected[idx1].last_point().distance_to_square(polylines_connected[idx2].first_point()); + double first_first = polylines_connected[idx1].first_point().distance_to_square(polylines_connected[idx2].first_point()); + double first_last = polylines_connected[idx1].first_point().distance_to_square(polylines_connected[idx2].last_point()); + double last_last = polylines_connected[idx1].last_point().distance_to_square(polylines_connected[idx2].last_point()); + double min = std::min(std::min(last_first, last_last), std::min(first_first, first_last)); + if (min < min_length || min_length == 0) { + min_idx = idx2; + switch_id1 = (std::min(last_first, last_last) > std::min(first_first, first_last)); + switch_id2 = (std::min(last_first, first_first) > std::min(last_last, first_last)); + min_length = min; + } + } + if (min_idx > idx1 && min_idx < polylines_connected.size()){ + Points pts_frontier = get_frontier(polylines_frontier, + switch_id1 ? polylines_connected[idx1].first_point() : polylines_connected[idx1].last_point(), + switch_id2 ? polylines_connected[min_idx].last_point() : polylines_connected[min_idx].first_point(), + scale_(this->spacing), polylines_blocker); + if (!pts_frontier.empty()) { + if (switch_id1) polylines_connected[idx1].reverse(); + if (switch_id2) polylines_connected[min_idx].reverse(); + Points &pts_end = polylines_connected[idx1].points; + pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end()); + pts_end.insert(pts_end.end(), polylines_connected[min_idx].points.begin(), polylines_connected[min_idx].points.end()); + polylines_connected.erase(polylines_connected.begin() + min_idx); + } + } + } + + //try to create some loops if possible + for (Polyline &polyline : polylines_connected) { + Points pts_frontier = get_frontier(polylines_frontier, polyline.last_point(), polyline.first_point(), scale_(this->spacing), polylines_blocker); + if (!pts_frontier.empty()) { + polyline.points.insert(polyline.points.end(), pts_frontier.begin(), pts_frontier.end()); + polyline.points.insert(polyline.points.begin(), polyline.points.back()); + } + polylines_out.emplace_back(polyline); + } +} + +#else + +struct ContourPointData { + ContourPointData(float param) : param(param) {} + // Eucleidean position of the contour point along the contour. + float param = 0.f; + // Was the segment starting with this contour point extruded? + bool segment_consumed = false; + // Was this point extruded over? + bool point_consumed = false; +}; + +// Verify whether the contour from point idx_start to point idx_end could be taken (whether all segments along the contour were not yet extruded). +static bool could_take(const std::vector &contour_data, size_t idx_start, size_t idx_end) +{ + for (size_t i = idx_start; i < idx_end; ) { + if (contour_data[i].segment_consumed || contour_data[i].point_consumed) + return false; + if (++ i == contour_data.size()) + i = 0; + } + return ! contour_data[idx_end].point_consumed; +} + +// Connect end of pl1 to the start of pl2 using the perimeter contour. +// The idx_start and idx_end are ordered so that the connecting polyline points will be taken with increasing indices. +static void take(Polyline &pl1, Polyline &&pl2, const Points &contour, std::vector &contour_data, size_t idx_start, size_t idx_end, bool reversed) +{ +#ifndef NDEBUG + size_t num_points_initial = pl1.points.size(); + assert(idx_start != idx_end); +#endif /* NDEBUG */ + + { + // Reserve memory at pl1 for the connecting contour and pl2. + int new_points = int(idx_end) - int(idx_start) - 1; + if (new_points < 0) + new_points += int(contour.size()); + pl1.points.reserve(pl1.points.size() + size_t(new_points) + pl2.points.size()); + } + + contour_data[idx_start].point_consumed = true; + contour_data[idx_start].segment_consumed = true; + contour_data[idx_end ].point_consumed = true; + + if (reversed) { + size_t i = (idx_end == 0) ? contour_data.size() - 1 : idx_end - 1; + while (i != idx_start) { + contour_data[i].point_consumed = true; + contour_data[i].segment_consumed = true; + pl1.points.emplace_back(contour[i]); + if (i == 0) + i = contour_data.size(); + -- i; + } + } else { + size_t i = idx_start; + if (++ i == contour_data.size()) + i = 0; + while (i != idx_end) { + contour_data[i].point_consumed = true; + contour_data[i].segment_consumed = true; + pl1.points.emplace_back(contour[i]); + if (++ i == contour_data.size()) + i = 0; + } + } + + append(pl1.points, std::move(pl2.points)); +} + +// Return an index of start of a segment and a point of the clipping point at distance from the end of polyline. +struct SegmentPoint { + // Segment index, defining a line ::max(); + // Parameter of point in <0, 1) along the line ::max(); } +}; + +static inline SegmentPoint clip_start_segment_and_point(const Points &polyline, double distance) +{ + assert(polyline.size() >= 2); + assert(distance > 0.); + // Initialized to "invalid". + SegmentPoint out; + if (polyline.size() >= 2) { + const double d2 = distance * distance; + Vec2d pt_prev = polyline.front().cast(); + for (int i = 1; i < polyline.size(); ++ i) { + Vec2d pt = polyline[i].cast(); + Vec2d v = pt - pt_prev; + double l2 = v.squaredNorm(); + if (l2 > d2) { + out.idx_segment = i; + out.t = distance / sqrt(l2); + out.point = pt + out.t * v; + break; + } + distance -= sqrt(l2); + pt_prev = pt; + } + } + return out; +} + +static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, double distance) +{ + assert(polyline.size() >= 2); + assert(distance > 0.); + // Initialized to "invalid". + SegmentPoint out; + if (polyline.size() >= 2) { + const double d2 = distance * distance; + Vec2d pt_next = polyline.back().cast(); + for (int i = int(polyline.size()) - 2; i >= 0; -- i) { + Vec2d pt = polyline[i].cast(); + Vec2d v = pt - pt_next; + double l2 = v.squaredNorm(); + if (l2 > d2) { + out.idx_segment = i; + out.t = distance / sqrt(l2); + out.point = pt + out.t * v; + break; + } + distance -= sqrt(l2); + pt_next = pt; + } + } + return out; +} + +static inline double segment_point_distance_squared(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2) +{ + const Vec2d v = p1b - p1a; + const Vec2d va = p2 - p1a; + const double l2 = v.squaredNorm(); + if (l2 < EPSILON) + // p1a == p1b + return va.squaredNorm(); + // Project p2 onto the (p1a, p1b) segment. + const double t = va.dot(v); + if (t < 0.) + return va.squaredNorm(); + else if (t > l2) + return (p2 - p1b).squaredNorm(); + return ((t / l2) * v - va).squaredNorm(); +} + +// Distance to the closest point of line. +static inline double min_distance_of_segments(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2a, const Vec2d &p2b) +{ + Vec2d v1 = p1b - p1a; + double l1_2 = v1.squaredNorm(); + if (l1_2 < EPSILON) + // p1a == p1b: Return distance of p1a from the (p2a, p2b) segment. + return segment_point_distance_squared(p2a, p2b, p1a); + + Vec2d v2 = p2b - p2a; + double l2_2 = v2.squaredNorm(); + if (l2_2 < EPSILON) + // p2a == p2b: Return distance of p2a from the (p1a, p1b) segment. + return segment_point_distance_squared(p1a, p1b, p2a); + + // Project p2a, p2b onto the (p1a, p1b) segment. + auto project_p2a_p2b_onto_seg_p1a_p1b = [](const Vec2d& p1a, const Vec2d& p1b, const Vec2d& p2a, const Vec2d& p2b, const Vec2d& v1, const double l1_2) { + Vec2d v1a2a = p2a - p1a; + Vec2d v1a2b = p2b - p1a; + double t1 = v1a2a.dot(v1); + double t2 = v1a2b.dot(v1); + if (t1 <= 0.) { + if (t2 <= 0.) + // Both p2a and p2b are left of v1. + return (((t1 < t2) ? p2b : p2a) - p1a).squaredNorm(); + else if (t2 < l1_2) + // Project p2b onto the (p1a, p1b) segment. + return ((t2 / l1_2) * v1 - v1a2b).squaredNorm(); + } + else if (t1 >= l1_2) { + if (t2 >= l1_2) + // Both p2a and p2b are right of v1. + return (((t1 < t2) ? p2a : p2b) - p1b).squaredNorm(); + else if (t2 < l1_2) + // Project p2b onto the (p1a, p1b) segment. + return ((t2 / l1_2) * v1 - v1a2b).squaredNorm(); + } + else { + // Project p1b onto the (p1a, p1b) segment. + double dist_min = ((t2 / l1_2) * v1 - v1a2a).squaredNorm(); + if (t2 > 0. && t2 < l1_2) + dist_min = std::min(dist_min, ((t2 / l1_2) * v1 - v1a2b).squaredNorm()); + return dist_min; + } + return std::numeric_limits::max(); + }; + + return std::min( + project_p2a_p2b_onto_seg_p1a_p1b(p1a, p1b, p2a, p2b, v1, l1_2), + project_p2a_p2b_onto_seg_p1a_p1b(p2a, p2b, p1a, p1b, v2, l2_2)); +} + +// Mark the segments of split boundary as consumed if they are very close to some of the infill line. +void mark_boundary_segments_touching_infill( + const std::vector &boundary, + std::vector> &boundary_data, + const BoundingBox &boundary_bbox, + const Polylines &infill, + const double clip_distance, + const double distance_colliding) +{ + EdgeGrid::Grid grid; + grid.set_bbox(boundary_bbox); + // Inflate the bounding box by a thick line width. + grid.create(boundary, clip_distance + scale_(10.)); + + struct Visitor { + Visitor(const EdgeGrid::Grid &grid, const std::vector &boundary, std::vector> &boundary_data, const double dist2_max) : + grid(grid), boundary(boundary), boundary_data(boundary_data), dist2_max(dist2_max) {} + + void init(const Vec2d &pt1, const Vec2d &pt2) { + this->pt1 = &pt1; + this->pt2 = &pt2; + } + + bool operator()(coord_t iy, coord_t ix) { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = this->grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = this->grid.segment(*it_contour_and_segment); + const Vec2d seg_pt1 = segment.first.cast(); + const Vec2d seg_pt2 = segment.second.cast(); + if (min_distance_of_segments(seg_pt1, seg_pt2, *this->pt1, *this->pt2) < this->dist2_max) { + // Mark this boundary segment as touching the infill line. + ContourPointData&bdp = boundary_data[it_contour_and_segment->first][it_contour_and_segment->second]; + bdp.segment_consumed = true; + // There is no need for checking seg_pt2 as it will be checked the next time. + if (segment_point_distance_squared(*this->pt1, *this->pt2, seg_pt1) < this->dist2_max) + bdp.point_consumed = true; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const std::vector &boundary; + std::vector> &boundary_data; + // Maximum distance between the boundary and the infill line allowed to consider the boundary not touching the infill line. + const double dist2_max; + + const Vec2d *pt1; + const Vec2d *pt2; + } visitor(grid, boundary, boundary_data, distance_colliding * distance_colliding); + + for (const Polyline &polyline : infill) { + // Clip the infill polyline by the Eucledian distance along the polyline. + SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance); + SegmentPoint end_point = clip_end_segment_and_point(polyline.points, clip_distance); + if (start_point.valid() && end_point.valid() && + (start_point.idx_segment < end_point.idx_segment || (start_point.idx_segment == end_point.idx_segment && start_point.t < end_point.t))) { + // The clipped polyline is non-empty. + for (size_t point_idx = start_point.idx_segment; point_idx <= end_point.idx_segment; ++ point_idx) { +//FIXME extend the EdgeGrid to suport tracing a thick line. +#if 0 + Point pt1, pt2; + Vec2d pt1d, pt2d; + if (point_idx == start_point.idx_segment) { + pt1d = start_point.point; + pt1 = pt1d.cast(); + } else { + pt1 = polyline.points[point_idx]; + pt1d = pt1.cast(); + } + if (point_idx == start_point.idx_segment) { + pt2d = end_point.point; + pt2 = pt1d.cast(); + } else { + pt2 = polyline.points[point_idx]; + pt2d = pt2.cast(); + } + visitor.init(pt1d, pt2d); + grid.visit_cells_intersecting_thick_line(pt1, pt2, distance_colliding, visitor); +#else + Vec2d pt1 = (point_idx == start_point.idx_segment) ? start_point.point : polyline.points[point_idx].cast(); + Vec2d pt2 = (point_idx == end_point .idx_segment) ? end_point .point : polyline.points[point_idx].cast(); + visitor.init(pt1, pt2); + // Simulate tracing of a thick line. This only works reliably if distance_colliding <= grid cell size. + Vec2d v = (pt2 - pt1).normalized() * distance_colliding; + Vec2d vperp(-v.y(), v.x()); + Vec2d a = pt1 - v - vperp; + Vec2d b = pt1 + v - vperp; + grid.visit_cells_intersecting_line(a.cast(), b.cast(), visitor); + a = pt1 - v + vperp; + b = pt1 + v + vperp; + grid.visit_cells_intersecting_line(a.cast(), b.cast(), visitor); +#endif + } + } + } +} + +void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const FillParams ¶ms) +{ + assert(! infill_ordered.empty()); + assert(! boundary_src.contour.points.empty()); + + BoundingBox bbox = get_extents(boundary_src.contour); + bbox.offset(SCALED_EPSILON); + + // 1) Add the end points of infill_ordered to boundary_src. + std::vector boundary; + std::vector> boundary_data; + boundary.assign(boundary_src.holes.size() + 1, Points()); + boundary_data.assign(boundary_src.holes.size() + 1, std::vector()); + // Mapping the infill_ordered end point to a (contour, point) of boundary. + std::vector> map_infill_end_point_to_boundary; + map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair(std::numeric_limits::max(), std::numeric_limits::max())); + { + // Project the infill_ordered end points onto boundary_src. + std::vector> intersection_points; + { + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(boundary_src, scale_(10.)); + intersection_points.reserve(infill_ordered.size() * 2); + for (const Polyline &pl : infill_ordered) + for (const Point *pt : { &pl.points.front(), &pl.points.back() }) { + EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point(*pt, SCALED_EPSILON); + if (cp.valid()) { + // The infill end point shall lie on the contour. + assert(cp.distance < 2.); + intersection_points.emplace_back(cp, (&pl - infill_ordered.data()) * 2 + (pt == &pl.points.front() ? 0 : 1)); + } + } + std::sort(intersection_points.begin(), intersection_points.end(), [](const std::pair &cp1, const std::pair &cp2) { + return cp1.first.contour_idx < cp2.first.contour_idx || + (cp1.first.contour_idx == cp2.first.contour_idx && + (cp1.first.start_point_idx < cp2.first.start_point_idx || + (cp1.first.start_point_idx == cp2.first.start_point_idx && cp1.first.t < cp2.first.t))); + }); + } + auto it = intersection_points.begin(); + auto it_end = intersection_points.end(); + for (size_t idx_contour = 0; idx_contour <= boundary_src.holes.size(); ++ idx_contour) { + const Polygon &contour_src = (idx_contour == 0) ? boundary_src.contour : boundary_src.holes[idx_contour - 1]; + Points &contour_dst = boundary[idx_contour]; + for (size_t idx_point = 0; idx_point < contour_src.points.size(); ++ idx_point) { + contour_dst.emplace_back(contour_src.points[idx_point]); + for (; it != it_end && it->first.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) { + // Add these points to the destination contour. + const Vec2d pt1 = contour_src[idx_point].cast(); + const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); + const Vec2d pt = lerp(pt1, pt2, it->first.t); + map_infill_end_point_to_boundary[it->second] = std::make_pair(idx_contour, contour_dst.size()); + contour_dst.emplace_back(pt.cast()); + } + } + // Parametrize the curve. + std::vector &contour_data = boundary_data[idx_contour]; + contour_data.reserve(contour_dst.size()); + contour_data.emplace_back(ContourPointData(0.f)); + for (size_t i = 1; i < contour_dst.size(); ++ i) + contour_data.emplace_back(contour_data.back().param + (contour_dst[i].cast() - contour_dst[i - 1].cast()).norm()); + contour_data.front().param = contour_data.back().param + (contour_dst.back().cast() - contour_dst.front().cast()).norm(); + } + +#ifndef NDEBUG + assert(boundary.size() == boundary_src.num_contours()); + assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), + [&boundary](const std::pair &contour_point) { + return contour_point.first < boundary.size() && contour_point.second < boundary[contour_point.first].size(); + })); +#endif /* NDEBUG */ + } + + // Mark the points and segments of split boundary as consumed if they are very close to some of the infill line. + { + const double clip_distance = scale_(this->spacing); + const double distance_colliding = scale_(this->spacing); + mark_boundary_segments_touching_infill(boundary, boundary_data, bbox, infill_ordered, clip_distance, distance_colliding); + } + + // Chain infill_ordered. + //FIXME run the following loop through a heap sorted by the shortest perimeter edge that could be taken. + //length between two lines + //const float length_max = scale_(this->spacing); + const float length_max = scale_((2. / params.density) * this->spacing); + size_t idx_chain_last = 0; + for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) { + Polyline &pl1 = infill_ordered[idx_chain_last]; + Polyline &pl2 = infill_ordered[idx_chain]; + const std::pair *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; + const std::pair *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2]; + const Points &contour = boundary[cp1->first]; + std::vector &contour_data = boundary_data[cp1->first]; + bool valid = false; + bool reversed = false; + if (cp1->first == cp2->first) { + // End points on the same contour. Try to connect them. + float param_lo = (cp1->second == 0) ? 0.f : contour_data[cp1->second].param; + float param_hi = (cp2->second == 0) ? 0.f : contour_data[cp2->second].param; + float param_end = contour_data.front().param; + if (param_lo > param_hi) { + std::swap(param_lo, param_hi); + std::swap(cp1, cp2); + reversed = true; + } + assert(param_lo >= 0.f && param_lo <= param_end); + assert(param_hi >= 0.f && param_hi <= param_end); + float dist1 = param_hi - param_lo; + float dist2 = param_lo + param_end - param_hi; + if (dist1 > dist2) { + std::swap(dist1, dist2); + std::swap(cp1, cp2); + reversed = ! reversed; + } + if (dist1 < length_max) { + // Try to connect the shorter path. + valid = could_take(contour_data, cp1->second, cp2->second); + // Try to connect the longer path. + if (! valid && dist2 < length_max) { + std::swap(cp1, cp2); + reversed = ! reversed; + valid = could_take(contour_data, cp1->second, cp2->second); + } + } + } + if (valid) + take(pl1, std::move(pl2), contour, contour_data, cp1->second, cp2->second, reversed); + else if (++ idx_chain_last < idx_chain) + infill_ordered[idx_chain_last] = std::move(pl2); + } + infill_ordered.erase(infill_ordered.begin() + idx_chain_last + 1, infill_ordered.end()); + append(polylines_out, std::move(infill_ordered)); +} + +#endif + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 3b834dede..d005f6e4a 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -111,6 +111,8 @@ protected: virtual std::pair _infill_direction(const Surface *surface) const; + void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const FillParams ¶ms); + public: static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index 7cd955892..e7b4706ac 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -31,19 +31,26 @@ static inline double f(double x, double z_sin, double z_cos, bool vertical, bool static inline Polyline make_wave( const std::vector& one_period, double width, double height, double offset, double scaleFactor, - double z_cos, double z_sin, bool vertical) + double z_cos, double z_sin, bool vertical, bool flip) { std::vector points = one_period; double period = points.back()(0); - points.pop_back(); - int n = points.size(); - do { - points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1))); - } while (points.back()(0) < width); - points.back()(0) = width; + if (width != period) // do not extend if already truncated + { + points.reserve(one_period.size() * floor(width / period)); + points.pop_back(); + + int n = points.size(); + do { + points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1))); + } while (points.back()(0) < width - EPSILON); + + points.emplace_back(Vec2d(width, f(width, z_sin, z_cos, vertical, flip))); + } // and construct the final polyline to return: Polyline polyline; + polyline.points.reserve(points.size()); for (auto& point : points) { point(1) += offset; point(1) = clamp(0., height, double(point(1))); @@ -55,45 +62,56 @@ static inline Polyline make_wave( return polyline; } -static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip) +static std::vector make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance) { std::vector points; - double dx = M_PI_4; // very coarse spacing to begin with + double dx = M_PI_2; // exact coordinates on main inflexion lobes double limit = std::min(2*M_PI, width); - for (double x = 0.; x < limit + EPSILON; x += dx) { // so the last point is there too - x = std::min(x, limit); - points.emplace_back(Vec2d(x,f(x, z_sin,z_cos, vertical, flip))); - } + points.reserve(ceil(limit / tolerance / 3)); - // now we will check all internal points and in case some are too far from the line connecting its neighbours, - // we will add one more point on each side: - const double tolerance = .1; - for (unsigned int i=1;i(scaleFactor) * std::abs(cross2(rp, lp) - cross2(rp - lp, tp)) / lrv.norm(); - if (dist_mm > tolerance) { // if the difference from straight line is more than this - double x = 0.5f * (points[i-1](0) + points[i](0)); - points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip))); - x = 0.5f * (points[i+1](0) + points[i](0)); - points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip))); - // we added the points to the end, but need them all in order - std::sort(points.begin(), points.end(), [](const Vec2d &lhs, const Vec2d &rhs){ return lhs < rhs; }); - // decrement i so we also check the first newly added point - --i; + for (double x = 0.; x < limit - EPSILON; x += dx) { + points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip))); + } + points.emplace_back(Vec2d(limit, f(limit, z_sin, z_cos, vertical, flip))); + + // piecewise increase in resolution up to requested tolerance + for(;;) + { + size_t size = points.size(); + for (unsigned int i = 1;i < size; ++i) { + auto& lp = points[i-1]; // left point + auto& rp = points[i]; // right point + double x = lp(0) + (rp(0) - lp(0)) / 2; + double y = f(x, z_sin, z_cos, vertical, flip); + Vec2d ip = {x, y}; + if (std::abs(cross2(Vec2d(ip - lp), Vec2d(ip - rp))) > sqr(tolerance)) { + points.emplace_back(std::move(ip)); + } + } + + if (size == points.size()) + break; + else + { + // insert new points in order + std::sort(points.begin(), points.end(), + [](const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0); }); } } + return points; } static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height) { const double scaleFactor = scale_(line_spacing) / density_adjusted; - //scale factor for 5% : 8 712 388 - // 1z = 10^-6 mm ? + + // tolerance in scaled units. clamp the maximum tolerance as there's + // no processing-speed benefit to do so beyond a certain point + const double tolerance = std::min(line_spacing / 2, FillGyroid::PatternTolerance) / unscale(scaleFactor); + + //scale factor for 5% : 8 712 388 + // 1z = 10^-6 mm ? const double z = gridZ / scaleFactor; const double z_sin = sin(z); const double z_cos = cos(z); @@ -109,16 +127,20 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double std::swap(width,height); } - std::vector one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time + std::vector one_period_odd = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time + flip = !flip; // even polylines are a bit shifted + std::vector one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); Polylines result; - for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates odd polylines - result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical)); - - flip = !flip; // even polylines are a bit shifted - one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // updates the one period sample - for (double y0 = lower_bound + M_PI; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates even polylines - result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical)); + for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) { + // creates odd polylines + result.emplace_back(make_wave(one_period_odd, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip)); + // creates even polylines + y0 += M_PI; + if (y0 < upper_bound + EPSILON) { + result.emplace_back(make_wave(one_period_even, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip)); + } + } return result; } @@ -130,63 +152,49 @@ void FillGyroid::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { - // no rotation is supported for this infill pattern (yet) + float infill_angle = this->angle + (CorrectionAngle * 2*M_PI) / 360.; + if(abs(infill_angle) >= EPSILON) + expolygon.rotate(-infill_angle); + BoundingBox bb = expolygon.contour.bounding_box(); // Density adjusted to have a good %of weight. - double density_adjusted = std::max(0., params.density * 2.44); + double density_adjusted = std::max(0., params.density * DensityAdjust); // Distance between the gyroid waves in scaled coordinates. coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); // align bounding box to a multiple of our grid module - bb.merge(_align_to_grid(bb.min, Point(2.*M_PI*distance, 2.*M_PI*distance))); + bb.merge(_align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); // generate pattern - Polylines polylines = make_gyroid_waves( + Polylines polylines_square = make_gyroid_waves( scale_(this->z), density_adjusted, this->spacing, ceil(bb.size()(0) / distance) + 1., ceil(bb.size()(1) / distance) + 1.); - - // move pattern in place - for (Polyline &polyline : polylines) - polyline.translate(bb.min(0), bb.min(1)); - // clip pattern to boundaries - polylines = intersection_pl(polylines, (Polygons)expolygon); + // shift the polyline to the grid origin + for (Polyline &pl : polylines_square) + pl.translate(bb.min); - // connect lines - if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections - ExPolygon expolygon_off; - { - ExPolygons expolygons_off = offset_ex(expolygon, (float)SCALED_EPSILON); - if (! expolygons_off.empty()) { - // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island. - assert(expolygons_off.size() == 1); - std::swap(expolygon_off, expolygons_off.front()); - } - } - bool first = true; - for (Polyline &polyline : chain_polylines(std::move(polylines))) { - if (! first) { - // Try to connect the lines. - Points &pts_end = polylines_out.back().points; - const Point &first_point = polyline.points.front(); - const Point &last_point = pts_end.back(); - // TODO: we should also check that both points are on a fill_boundary to avoid - // connecting paths on the boundaries of internal regions - // TODO: avoid crossing current infill path - if ((last_point - first_point).cast().norm() <= 5 * distance && - expolygon_off.contains(Line(last_point, first_point))) { - // Append the polyline. - pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end()); - continue; - } - } - // The lines cannot be connected. - polylines_out.emplace_back(std::move(polyline)); - first = false; - } + Polylines polylines_chained = chain_polylines(intersection_pl(polylines_square, to_polygons(expolygon))); + + size_t polylines_out_first_idx = polylines_out.size(); + if (! polylines_chained.empty()) { + // connect lines + if (params.dont_connect) + append(polylines_out, std::move(polylines_chained)); + else + this->connect_infill(std::move(polylines_chained), expolygon, polylines_out, params); + // remove too small bits (larger than longer) + polylines_out.erase( + std::remove_if(polylines_out.begin() + polylines_out_first_idx, polylines_out.end(), [this](const Polyline &pl){ return pl.length() < scale_(this->spacing * 3); }), + polylines_out.end()); + // new paths must be rotated back + if (abs(infill_angle) >= EPSILON) { + for (auto it = polylines_out.begin() + polylines_out_first_idx; it != polylines_out.end(); ++ it) + it->rotate(infill_angle); + } } } diff --git a/src/libslic3r/Fill/FillGyroid.hpp b/src/libslic3r/Fill/FillGyroid.hpp index 9c3cef940..37babb25e 100644 --- a/src/libslic3r/Fill/FillGyroid.hpp +++ b/src/libslic3r/Fill/FillGyroid.hpp @@ -16,6 +16,17 @@ public: // require bridge flow since most of this pattern hangs in air virtual bool use_bridge_flow() const { return false; } + // Correction applied to regular infill angle to maximize printing + // speed in default configuration (degrees) + static constexpr float CorrectionAngle = -45.; + + // Density adjustment to have a good %of weight. + static constexpr double DensityAdjust = 2.44; + + // Gyroid upper resolution tolerance (mm^-2) + static constexpr double PatternTolerance = 0.2; + + protected: virtual void _fill_surface_single( const FillParams ¶ms,