From 8fd731f7a094854770e42d7f075e3946681ed3b1 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 8 Apr 2021 15:29:40 +0200 Subject: [PATCH] New FDM support sparse infill zig-zag algorithm. Fixed some old support and infill issues. Fixes support problem #4295 Fixes Parts of interface layer extends beyond supports and cannot be printed Fixes support missing under horizontal overhang #6058 Fixes Slicer double-traces small sections of Rectilinear Supports, causes Fixes plastic buildup and nozzle crashes #4951 Fixes Add "Angle Interface layers" #2969 --- src/PrusaSlicer_app_msvc.cpp | 5 + src/libslic3r/BoundingBox.cpp | 17 +- src/libslic3r/ExtrusionEntity.hpp | 2 + src/libslic3r/Fill/Fill3DHoneycomb.cpp | 2 +- src/libslic3r/Fill/FillBase.cpp | 1306 +++++++++++++++++++++--- src/libslic3r/Fill/FillBase.hpp | 22 +- src/libslic3r/Fill/FillGyroid.cpp | 2 +- src/libslic3r/Fill/FillHoneycomb.cpp | 2 +- src/libslic3r/Fill/FillLine.cpp | 2 +- src/libslic3r/Fill/FillRectilinear.cpp | 280 +++-- src/libslic3r/Fill/FillRectilinear.hpp | 11 + src/libslic3r/Geometry.hpp | 23 +- src/libslic3r/MultiPoint.hpp | 1 + src/libslic3r/Point.hpp | 19 + src/libslic3r/Print.cpp | 7 +- src/libslic3r/PrintConfig.cpp | 4 +- src/libslic3r/PrintConfig.hpp | 2 +- src/libslic3r/SupportMaterial.cpp | 51 +- 18 files changed, 1473 insertions(+), 285 deletions(-) diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index 7710d9eb7..2ccf2f1ff 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -217,6 +217,11 @@ int APIENTRY wWinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, int wmain(int argc, wchar_t **argv) { #endif + // Allow the asserts to open message box, such message box allows to ignore the assert and continue with the application. + // Without this call, the seemingly same message box is being opened by the abort() function, but that is too late and + // the application will be killed even if "Ignore" button is pressed. + _set_error_mode(_OUT_TO_MSGBOX); + std::vector argv_extended; argv_extended.emplace_back(argv[0]); diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index eb4e042a0..4f52c5108 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -225,24 +225,11 @@ BoundingBox3Base::max_size() const template coordf_t BoundingBox3Base::max_size() const; template coordf_t BoundingBox3Base::max_size() const; -// Align a coordinate to a grid. The coordinate may be negative, -// the aligned value will never be bigger than the original one. -static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { - // Current C++ standard defines the result of integer division to be rounded to zero, - // for both positive and negative numbers. Here we want to round down for negative - // numbers as well. - coord_t aligned = (coord < 0) ? - ((coord - spacing + 1) / spacing) * spacing : - (coord / spacing) * spacing; - assert(aligned <= coord); - return aligned; -} - void BoundingBox::align_to_grid(const coord_t cell_size) { if (this->defined) { - min(0) = _align_to_grid(min(0), cell_size); - min(1) = _align_to_grid(min(1), cell_size); + min(0) = Slic3r::align_to_grid(min(0), cell_size); + min(1) = Slic3r::align_to_grid(min(1), cell_size); } } diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 2c43cd4af..2f3508316 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -203,6 +203,8 @@ public: void reverse() override; const Point& first_point() const override { return this->paths.front().polyline.points.front(); } const Point& last_point() const override { return this->paths.back().polyline.points.back(); } + size_t size() const { return this->paths.size(); } + bool empty() const { return this->paths.empty(); } double length() const override; ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 2ddca7fe4..95c26fbad 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -147,7 +147,7 @@ void Fill3DHoneycomb::_fill_surface_single( // align bounding box to a multiple of our honeycomb grid module // (a module is 2*$distance since one $distance half-module is // growing while the other $distance half-module is shrinking) - bb.merge(_align_to_grid(bb.min, Point(2*distance, 2*distance))); + bb.merge(align_to_grid(bb.min, Point(2*distance, 2*distance))); // generate pattern Polylines polylines = makeGrid( diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index b4afd2591..6d1d94ff8 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -19,6 +19,8 @@ #include "FillRectilinear.hpp" #include "FillAdaptive.hpp" +// #define INFILL_DEBUG_OUTPUT + namespace Slic3r { Fill* Fill::new_from_type(const InfillPattern type) @@ -41,6 +43,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipOctagramSpiral: return new FillOctagramSpiral(); case ipAdaptiveCubic: return new FillAdaptive::Filler(); case ipSupportCubic: return new FillAdaptive::Filler(); + case ipSupportBase: return new FillSupportBase(); default: throw Slic3r::InvalidArgument("unknown type"); } } @@ -253,15 +256,15 @@ std::pair path_lengths_along_contour(const ContourIntersectionPo } // Add contour points from interval (idx_start, idx_end> to polyline. -static inline void take_cw_full(Polyline &pl, const Points& contour, size_t idx_start, size_t idx_end) +static inline void take_cw_full(Polyline &pl, const Points &contour, size_t idx_start, size_t idx_end) { assert(! pl.empty() && pl.points.back() == contour[idx_start]); - size_t i = (idx_end == 0) ? contour.size() - 1 : idx_start - 1; + size_t i = (idx_start == 0) ? contour.size() - 1 : idx_start - 1; while (i != idx_end) { pl.points.emplace_back(contour[i]); if (i == 0) i = contour.size(); - --i; + -- i; } pl.points.emplace_back(contour[i]); } @@ -612,13 +615,13 @@ static inline bool line_rounded_thick_segment_collision( }; // Intersections with the inflated segment end points. - auto ray_circle_intersection_interval_extend = [&extend_interval, &line_v0](const Vec2d &segment_pt, const double offset2, const Vec2d &line_pt, const Vec2d &line_vec) { + auto ray_circle_intersection_interval_extend = [&extend_interval](const Vec2d &segment_pt, const double offset2, const Vec2d &line_pt, const Vec2d &line_vec) { std::pair pts; Vec2d p0 = line_pt - segment_pt; - double c = - line_pt.dot(p0); - if (Geometry::ray_circle_intersections_r2_lv2_c(offset2, line_vec.x(), line_vec.y(), line_vec.squaredNorm(), c, pts)) { - double tmin = (pts.first - p0).dot(line_v0); - double tmax = (pts.second - p0).dot(line_v0); + double lv2 = line_vec.squaredNorm(); + if (Geometry::ray_circle_intersections_r2_lv2_c(offset2, line_vec.y(), - line_vec.x(), lv2, - line_vec.y() * p0.x() + line_vec.x() * p0.y(), pts)) { + double tmin = (pts.first - p0).dot(line_vec) / lv2; + double tmax = (pts.second - p0).dot(line_vec) / lv2; if (tmin > tmax) std::swap(tmin, tmax); tmin = std::max(tmin, 0.); @@ -705,8 +708,6 @@ static inline bool cyclic_interval_inside_interval(double outer_low, double oute } #endif // NDEBUG -// #define INFILL_DEBUG_OUTPUT - #ifdef INFILL_DEBUG_OUTPUT static void export_infill_to_svg( // Boundary contour, along which the perimeter extrusions will be drawn. @@ -1099,61 +1100,216 @@ void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_s connect_infill(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params); } -void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); + +struct BoundaryInfillGraph { - assert(! infill_ordered.empty()); - assert(params.anchor_length >= 0.); - assert(params.anchor_length_max >= 0.01f); - assert(params.anchor_length_max >= params.anchor_length); - const double anchor_length = scale_(params.anchor_length); - const double anchor_length_max = scale_(params.anchor_length_max); + std::vector boundary; + std::vector> boundary_params; + std::vector map_infill_end_point_to_boundary; -#if 0 - append(polylines_out, infill_ordered); - return; -#endif + const Point& point(const ContourIntersectionPoint &cp) const { + assert(cp.contour_idx != size_t(-1)); + assert(cp.point_idx != size_t(-1)); + return this->boundary[cp.contour_idx][cp.point_idx]; + } - // 1) Add the end points of infill_ordered to boundary_src. - std::vector boundary; - std::vector> boundary_params; - boundary.assign(boundary_src.size(), Points()); - boundary_params.assign(boundary_src.size(), std::vector()); - // Mapping the infill_ordered end point to a (contour, point) of boundary. - static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); - std::vector map_infill_end_point_to_boundary(infill_ordered.size() * 2, ContourIntersectionPoint{ boundary_idx_unconnected, boundary_idx_unconnected }); - { - // Project the infill_ordered end points onto boundary_src. - std::vector> intersection_points; - { - EdgeGrid::Grid grid; - grid.set_bbox(bbox.inflated(SCALED_EPSILON)); - grid.create(boundary_src, coord_t(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_signed_distance(*pt, coord_t(SCALED_EPSILON)); - if (cp.valid()) { - // The infill end point shall lie on the contour. - assert(cp.distance <= 3.); - 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(); - std::vector> boundary_intersection_points(boundary.size(), std::vector()); - for (size_t idx_contour = 0; idx_contour < boundary_src.size(); ++ idx_contour) { + const Point& infill_end_point(size_t infill_end_point_idx) const { + return this->point(this->map_infill_end_point_to_boundary[infill_end_point_idx]); + } + + const Point interpolate_contour_point(const ContourIntersectionPoint &cp, double param) { + const Points &contour = this->boundary[cp.contour_idx]; + const std::vector &contour_params = this->boundary_params[cp.contour_idx]; + // Find the start of a contour segment with param. + auto it = std::lower_bound(contour_params.begin(), contour_params.end(), param); + if (*it != param) { + assert(it != contour_params.begin()); + -- it; + } + size_t i = it - contour_params.begin(); + if (i == contour.size()) + i = 0; + double t1 = contour_params[i]; + double t2 = next_value_modulo(i, contour_params); + return lerp(contour[i], next_value_modulo(i, contour), (param - t1) / (t2 - t1)); + } + + enum Direction { + Left, + Right, + Up, + Down, + Taken, + }; + + static Direction dir(const Point &p1, const Point &p2) { + return p1.x() == p2.x() ? + (p1.y() < p2.y() ? Up : Down) : + (p1.x() < p2.x() ? Right : Left); + } + + const Direction dir_prev(const ContourIntersectionPoint &cp) const { + assert(cp.prev_on_contour); + return cp.could_take_prev() ? + dir(this->point(cp), this->point(*cp.prev_on_contour)) : + Taken; + } + + const Direction dir_next(const ContourIntersectionPoint &cp) const { + assert(cp.next_on_contour); + return cp.could_take_next() ? + dir(this->point(cp), this->point(*cp.next_on_contour)) : + Taken; + } + + bool first(const ContourIntersectionPoint &cp) const { + return ((&cp - this->map_infill_end_point_to_boundary.data()) & 1) == 0; + } + + const ContourIntersectionPoint& other(const ContourIntersectionPoint &cp) const { + return this->map_infill_end_point_to_boundary[((&cp - this->map_infill_end_point_to_boundary.data()) ^ 1)]; + } + + ContourIntersectionPoint& other(const ContourIntersectionPoint &cp) { + return this->map_infill_end_point_to_boundary[((&cp - this->map_infill_end_point_to_boundary.data()) ^ 1)]; + } + + bool prev_vertical(const ContourIntersectionPoint &cp) const { + return this->point(cp).x() == this->point(*cp.prev_on_contour).x(); + } + + bool next_vertical(const ContourIntersectionPoint &cp) const { + return this->point(cp).x() == this->point(*cp.next_on_contour).x(); + } + +}; + + +// After mark_boundary_segments_touching_infill() marks boundary segments overlapping trimmed infill lines, +// there are possibly some very short boundary segments unmarked, but overlapping the untrimmed infill lines fully +// Mark those short boundary segments. +static inline void mark_boundary_segments_overlapping_infill( + BoundaryInfillGraph &graph, + // Infill lines, either completely inside the boundary, or touching the boundary. + const Polylines &infill, + // Spacing (width) of the infill lines. + const double spacing) +{ + struct Linef { Vec2d a; Vec2d b; }; + + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + const Points &contour = graph.boundary[cp.contour_idx]; + const std::vector &contour_params = graph.boundary_params[cp.contour_idx]; + const Polyline &infill_polyline = infill[(&cp - graph.map_infill_end_point_to_boundary.data()) / 2]; + const double radius = 0.5 * (spacing + SCALED_EPSILON); + assert(infill_polyline.size() == 2); + const Linef infill_line { infill_polyline.points.front().cast(), infill_polyline.points.back().cast() }; + if (cp.could_take_next()) { + bool inside = true; + for (size_t i = cp.point_idx; i != cp.next_on_contour->point_idx; ) { + size_t j = next_idx_modulo(i, contour); + const Vec2d seg_pt2 = contour[j].cast(); + if (line_alg::distance_to_squared(infill_line, seg_pt2) < radius * radius) { + // The segment is completely inside. + } else { + std::pair interval; + line_rounded_thick_segment_collision(contour[i].cast(), seg_pt2, infill_line.a, infill_line.b, radius, interval); + assert(interval.first == 0.); + double len_out = closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second; + if (len_out < cp.contour_not_taken_length_next) { + // Leaving the infill line region before exiting cp.contour_not_taken_length_next, + // thus at least some of the contour is outside and we will extrude this segment. + inside = false; + break; + } + } + if (closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[j], contour_params.back()) >= cp.contour_not_taken_length_next) + break; + i = j; + } + if (inside) { + if (! cp.next_trimmed) + // The arc from cp to cp.next_on_contour was not trimmed yet, however it is completely overlapping the infill line. + cp.next_on_contour->trim_prev(0); + cp.trim_next(0); + } + } else + cp.trim_next(0); + if (cp.could_take_prev()) { + bool inside = true; + for (size_t i = cp.point_idx; i != cp.prev_on_contour->point_idx; ) { + size_t j = prev_idx_modulo(i, contour); + const Vec2d seg_pt2 = contour[j].cast(); + // Distance of the second segment line from the infill line. + if (line_alg::distance_to_squared(infill_line, seg_pt2) < radius * radius) { + // The segment is completely inside. + } else { + std::pair interval; + line_rounded_thick_segment_collision(contour[i].cast(), seg_pt2, infill_line.a, infill_line.b, radius, interval); + assert(interval.first == 0.); + double len_out = closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second; + if (len_out < cp.contour_not_taken_length_prev) { + // Leaving the infill line region before exiting cp.contour_not_taken_length_next, + // thus at least some of the contour is outside and we will extrude this segment. + inside = false; + break; + } + } + if (closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[j], contour_params.back()) >= cp.contour_not_taken_length_prev) + break; + i = j; + } + if (inside) { + if (! cp.prev_trimmed) + // The arc from cp to cp.prev_on_contour was not trimmed yet, however it is completely overlapping the infill line. + cp.prev_on_contour->trim_next(0); + cp.trim_prev(0); + } + } else + cp.trim_prev(0); + } +} + +BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, const double spacing) +{ + BoundaryInfillGraph out; + out.boundary.assign(boundary_src.size(), Points()); + out.boundary_params.assign(boundary_src.size(), std::vector()); + out.map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, ContourIntersectionPoint{ boundary_idx_unconnected, boundary_idx_unconnected }); + { + // Project the infill_ordered end points onto boundary_src. + std::vector> intersection_points; + { + EdgeGrid::Grid grid; + grid.set_bbox(bbox.inflated(SCALED_EPSILON)); + grid.create(boundary_src, coord_t(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_signed_distance(*pt, coord_t(SCALED_EPSILON)); + if (cp.valid()) { + // The infill end point shall lie on the contour. + assert(cp.distance <= 3.); + 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(); + std::vector> boundary_intersection_points(out.boundary.size(), std::vector()); + for (size_t idx_contour = 0; idx_contour < boundary_src.size(); ++ idx_contour) { // Copy contour_src to contour_dst while adding intersection points. // Map infill end points map_infill_end_point_to_boundary to the newly inserted boundary points of contour_dst. // chain the points of map_infill_end_point_to_boundary along their respective contours. - const Polygon &contour_src = *boundary_src[idx_contour]; - Points &contour_dst = boundary[idx_contour]; + const Polygon &contour_src = *boundary_src[idx_contour]; + Points &contour_dst = out.boundary[idx_contour]; std::vector &contour_intersection_points = boundary_intersection_points[idx_contour]; ContourIntersectionPoint *pfirst = nullptr; ContourIntersectionPoint *pprev = nullptr; @@ -1164,18 +1320,18 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorfirst.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) { - // Add these points to the destination contour. + contour_dst.emplace_back(ipt); + 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 Polyline &infill_line = infill_ordered[it->second / 2]; const Point &pt = (it->second & 1) ? infill_line.points.back() : infill_line.points.front(); //#ifndef NDEBUG // { -// const Vec2d pt1 = ipt.cast(); -// const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); +// const Vec2d pt1 = ipt.cast(); +// const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast(); // const Vec2d ptx = lerp(pt1, pt2, it->first.t); // assert(std::abs(ptx.x() - pt.x()) < SCALED_EPSILON); // assert(std::abs(ptx.y() - pt.y()) < SCALED_EPSILON); @@ -1187,8 +1343,8 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorsecond] = ContourIntersectionPoint{ idx_contour, idx_tjoint_pt }; - ContourIntersectionPoint *pthis = &map_infill_end_point_to_boundary[it->second]; + out.map_infill_end_point_to_boundary[it->second] = ContourIntersectionPoint{ /* it->second, */ idx_contour, idx_tjoint_pt }; + ContourIntersectionPoint *pthis = &out.map_infill_end_point_to_boundary[it->second]; if (pprev) { pprev->next_on_contour = pthis; pthis->prev_on_contour = pprev; @@ -1196,15 +1352,15 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectornext_on_contour = pfirst; pfirst->prev_on_contour = pprev; } - } - // Parametrize the new boundary with the intersection points inserted. - std::vector &contour_params = boundary_params[idx_contour]; - contour_params.assign(contour_dst.size() + 1, 0.); + } + // Parametrize the new boundary with the intersection points inserted. + std::vector &contour_params = out.boundary_params[idx_contour]; + contour_params.assign(contour_dst.size() + 1, 0.); for (size_t i = 1; i < contour_dst.size(); ++i) { contour_params[i] = contour_params[i - 1] + (contour_dst[i].cast() - contour_dst[i - 1].cast()).norm(); assert(contour_params[i] > contour_params[i - 1]); @@ -1225,18 +1381,18 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorcontour_not_taken_length_prev = closed_contour_distance_ccw(ip->prev_on_contour->param, ip->param, contour_length); ip->contour_not_taken_length_next = closed_contour_distance_ccw(ip->param, ip->next_on_contour->param, contour_length); } - } + } - assert(boundary.size() == boundary_src.size()); + assert(out.boundary.size() == boundary_src.size()); #if 0 // Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary. - assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), - [&boundary](const ContourIntersectionPoint &contour_point) { - return contour_point.contour_idx < boundary.size() && contour_point.point_idx < boundary[contour_point.contour_idx].size(); - })); + assert(std::all_of(out.map_infill_end_point_to_boundary.begin(), out.map_infill_end_point_to_boundary.end(), + [&out.boundary](const ContourIntersectionPoint &contour_point) { + return contour_point.contour_idx < out.boundary.size() && contour_point.point_idx < out.boundary[contour_point.contour_idx].size(); + })); #endif - // Mark the points and segments of split boundary as consumed if they are very close to some of the infill line. + // Mark the points and segments of split out.boundary as consumed if they are very close to some of the infill line. { // @supermerill used 2. * scale_(spacing) const double clip_distance = 1.7 * scale_(spacing); @@ -1244,37 +1400,31 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector merged_with(infill_ordered.size()); + return out; +} + +void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +{ + assert(! infill_ordered.empty()); + assert(params.anchor_length >= 0.); + assert(params.anchor_length_max >= 0.01f); + assert(params.anchor_length_max >= params.anchor_length); + const double anchor_length = scale_(params.anchor_length); + const double anchor_length_max = scale_(params.anchor_length_max); + +#if 0 + append(polylines_out, infill_ordered); + return; +#endif + + BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing); + + std::vector merged_with(infill_ordered.size()); std::iota(merged_with.begin(), merged_with.end(), 0); - struct ConnectionCost { - ConnectionCost(size_t idx_first, double cost, bool reversed) : idx_first(idx_first), cost(cost), reversed(reversed) {} - size_t idx_first; - double cost; - bool reversed; - }; - std::vector connections_sorted; - connections_sorted.reserve(infill_ordered.size() * 2 - 2); - for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) { - const ContourIntersectionPoint *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; - const ContourIntersectionPoint *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2]; - if (cp1->contour_idx != boundary_idx_unconnected && cp1->contour_idx == cp2->contour_idx) { - // End points on the same contour. Try to connect them. - std::pair len = path_lengths_along_contour(cp1, cp2, boundary_params[cp1->contour_idx].back()); - if (len.first < length_max) - connections_sorted.emplace_back(idx_chain - 1, len.first, false); - if (len.second < length_max) - connections_sorted.emplace_back(idx_chain - 1, len.second, true); - } - } - std::sort(connections_sorted.begin(), connections_sorted.end(), [](const ConnectionCost& l, const ConnectionCost& r) { return l.cost < r.cost; }); auto get_and_update_merged_with = [&merged_with](size_t polyline_idx) -> size_t { for (size_t last = polyline_idx;;) { @@ -1293,9 +1443,35 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector connections_sorted; + connections_sorted.reserve(infill_ordered.size() * 2 - 2); + for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) { + const ContourIntersectionPoint *cp1 = &graph.map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; + const ContourIntersectionPoint *cp2 = &graph.map_infill_end_point_to_boundary[idx_chain * 2]; + if (cp1->contour_idx != boundary_idx_unconnected && cp1->contour_idx == cp2->contour_idx) { + // End points on the same contour. Try to connect them. + std::pair len = path_lengths_along_contour(cp1, cp2, graph.boundary_params[cp1->contour_idx].back()); + if (len.first < length_max) + connections_sorted.emplace_back(idx_chain - 1, len.first, false); + if (len.second < length_max) + connections_sorted.emplace_back(idx_chain - 1, len.second, true); + } + } + std::sort(connections_sorted.begin(), connections_sorted.end(), [](const ConnectionCost& l, const ConnectionCost& r) { return l.cost < r.cost; }); + for (ConnectionCost &connection_cost : connections_sorted) { - ContourIntersectionPoint *cp1 = &map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1]; - ContourIntersectionPoint *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; + ContourIntersectionPoint *cp1 = &graph.map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1]; + ContourIntersectionPoint *cp2 = &graph.map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; assert(cp1 != cp2); assert(cp1->contour_idx == cp2->contour_idx && cp1->contour_idx != boundary_idx_unconnected); if (cp1->consumed || cp2->consumed) @@ -1306,7 +1482,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorparam, cp_high->param, boundary_params[cp1->contour_idx].back())) < SCALED_EPSILON); + assert(std::abs(length - closed_contour_distance_ccw(cp_low->param, cp_high->param, graph.boundary_params[cp1->contour_idx].back())) < SCALED_EPSILON); could_connect = ! cp_low->next_trimmed && ! cp_high->prev_trimmed; if (could_connect && cp_low->next_on_contour != cp_high) { // Other end of cp1, may or may not be on the same contour as cp1. @@ -1329,14 +1505,14 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorcontour_idx], cp1, cp2, connection_cost.reversed); + take(infill_ordered[idx_first], infill_ordered[idx_second], graph.boundary[cp1->contour_idx], cp1, cp2, connection_cost.reversed); // Mark the second polygon as merged with the first one. merged_with[idx_second] = merged_with[idx_first]; infill_ordered[idx_second].points.clear(); } else { // Try to connect cp1 resp. cp2 with a piece of perimeter line. - take_limited(infill_ordered[idx_first], boundary[cp1->contour_idx], boundary_params[cp1->contour_idx], cp1, cp2, connection_cost.reversed, anchor_length, line_half_width); - take_limited(infill_ordered[idx_second], boundary[cp1->contour_idx], boundary_params[cp1->contour_idx], cp2, cp1, ! connection_cost.reversed, anchor_length, line_half_width); + take_limited(infill_ordered[idx_first], graph.boundary[cp1->contour_idx], graph.boundary_params[cp1->contour_idx], cp1, cp2, connection_cost.reversed, anchor_length, line_half_width); + take_limited(infill_ordered[idx_second], graph.boundary[cp1->contour_idx], graph.boundary_params[cp1->contour_idx], cp2, cp1, ! connection_cost.reversed, anchor_length, line_half_width); } } #endif @@ -1346,10 +1522,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector arches; - arches.reserve(map_infill_end_point_to_boundary.size()); - for (ContourIntersectionPoint &cp : map_infill_end_point_to_boundary) + arches.reserve(graph.map_infill_end_point_to_boundary.size()); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) if (cp.contour_idx != boundary_idx_unconnected && cp.next_on_contour != &cp && cp.could_connect_next()) - arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, boundary_params[cp.contour_idx].back()) }); + arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, graph.boundary_params[cp.contour_idx].back()) }); std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length < r.arc_length; }); //FIXME improve the Traveling Salesman problem with 2-opt and 3-opt local optimization. @@ -1358,10 +1534,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectornext_on_contour; - size_t polyline_idx1 = get_and_update_merged_with(((cp1 - map_infill_end_point_to_boundary.data()) / 2)); - size_t polyline_idx2 = get_and_update_merged_with(((cp2 - map_infill_end_point_to_boundary.data()) / 2)); - const Points &contour = boundary[cp1->contour_idx]; - const std::vector &contour_params = boundary_params[cp1->contour_idx]; + size_t polyline_idx1 = get_and_update_merged_with(((cp1 - graph.map_infill_end_point_to_boundary.data()) / 2)); + size_t polyline_idx2 = get_and_update_merged_with(((cp2 - graph.map_infill_end_point_to_boundary.data()) / 2)); + const Points &contour = graph.boundary[cp1->contour_idx]; + const std::vector &contour_params = graph.boundary_params[cp1->contour_idx]; if (polyline_idx1 != polyline_idx2) { Polyline &polyline1 = infill_ordered[polyline_idx1]; Polyline &polyline2 = infill_ordered[polyline_idx2]; @@ -1392,10 +1568,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &contour_params = boundary_params[contour_point.contour_idx]; + const Points &contour = graph.boundary[contour_point.contour_idx]; + const std::vector &contour_params = graph.boundary_params[contour_point.contour_idx]; double lprev = contour_point.could_connect_prev() ? path_length_along_contour_ccw(contour_point.prev_on_contour, &contour_point, contour_params.back()) : @@ -1403,7 +1579,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector::max(); - size_t polyline_idx = get_and_update_merged_with(((&contour_point - map_infill_end_point_to_boundary.data()) / 2)); + size_t polyline_idx = get_and_update_merged_with(((&contour_point - graph.map_infill_end_point_to_boundary.data()) / 2)); Polyline &polyline = infill_ordered[polyline_idx]; assert(! polyline.empty()); assert(contour[contour_point.point_idx] == polyline.points.front() || contour[contour_point.point_idx] == polyline.points.back()); @@ -1415,7 +1591,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &contour_param = graph.boundary_params[cp.contour_idx]; + const Point &pt = contour[cp.point_idx]; + const bool first = graph.first(cp); + int extend_next_idx = -1; + int extend_prev_idx = -1; + coord_t dist_y_prev; + coord_t dist_y_next; + double arc_len_prev; + double arc_len_next; + + if (! graph.next_vertical(cp)){ + size_t i = cp.point_idx; + size_t j = next_idx_modulo(i, contour); + while (j != cp.next_on_contour->point_idx) { + //const Point &p1 = contour[i]; + const Point &p2 = contour[j]; + if (std::abs(p2.x() - pt.x()) > dist_max_x) + break; + i = j; + j = next_idx_modulo(j, contour); + } + if (i != cp.point_idx) { + const Point &p2 = contour[i]; + coord_t dist_y = p2.y() - pt.y(); + if (first) + dist_y = - dist_y; + if (dist_y > dist_min_y) { + arc_len_next = closed_contour_distance_ccw(contour_param[cp.point_idx], contour_param[i], contour_param.back()); + if (arc_len_next < cp.contour_not_taken_length_next) { + extend_next_idx = i; + dist_y_next = dist_y; + } + } + } + } + + if (! graph.prev_vertical(cp)) { + size_t i = cp.point_idx; + size_t j = prev_idx_modulo(i, contour); + while (j != cp.prev_on_contour->point_idx) { + //const Point &p1 = contour[i]; + const Point &p2 = contour[j]; + if (std::abs(p2.x() - pt.x()) > dist_max_x) + break; + i = j; + j = prev_idx_modulo(j, contour); + } + if (i != cp.point_idx) { + const Point &p2 = contour[i]; + coord_t dist_y = p2.y() - pt.y(); + if (first) + dist_y = - dist_y; + if (dist_y > dist_min_y) { + arc_len_prev = closed_contour_distance_ccw(contour_param[i], contour_param[cp.point_idx], contour_param.back()); + if (arc_len_prev < cp.contour_not_taken_length_prev) { + extend_prev_idx = i; + dist_y_prev = dist_y; + } + } + } + } + + if (extend_prev_idx >= 0 && extend_next_idx >= 0) + // Which side to move the point? + dist_y_prev < dist_y_next ? extend_prev_idx : extend_next_idx = -1; + + assert(cp.prev_trimmed == cp.prev_on_contour->next_trimmed); + assert(cp.next_trimmed == cp.next_on_contour->prev_trimmed); + Polyline &infill_line = infill[(&cp - graph.map_infill_end_point_to_boundary.data()) / 2]; + if (extend_prev_idx >= 0) { + if (first) + infill_line.reverse(); + take_cw_full(infill_line, contour, cp.point_idx, extend_prev_idx); + if (first) + infill_line.reverse(); + cp.point_idx = extend_prev_idx; + if (cp.prev_trimmed) + cp.contour_not_taken_length_prev -= arc_len_prev; + else + cp.contour_not_taken_length_prev = cp.prev_on_contour->contour_not_taken_length_next = + closed_contour_distance_ccw(contour_param[cp.prev_on_contour->point_idx], contour_param[cp.point_idx], contour_param.back()); + cp.trim_next(0); + cp.next_on_contour->prev_trimmed = true; + } else if (extend_next_idx >= 0) { + if (first) + infill_line.reverse(); + take_ccw_full(infill_line, contour, cp.point_idx, extend_next_idx); + if (first) + infill_line.reverse(); + cp.point_idx = extend_next_idx; + cp.trim_prev(0); + cp.prev_on_contour->next_trimmed = true; + if (cp.next_trimmed) + cp.contour_not_taken_length_next -= arc_len_next; + else + cp.contour_not_taken_length_next = cp.next_on_contour->contour_not_taken_length_prev = + closed_contour_distance_ccw(contour_param[cp.point_idx], contour_param[cp.next_on_contour->point_idx], contour_param.back()); + } + } +} + +// Called by Fill::connect_base_support() as part of the sparse support infill generator. +// Emit contour loops tracing the contour from tbegin to tend inside a band of (left, right). +// The contour is supposed to enter the "forbidden" zone outside of the (left, right) band at tbegin and also at tend. +static inline void emit_loops_in_band( + // Vertical band, which will trim the contour between tbegin and tend. + coord_t left, + coord_t right, + // Contour and its parametrization. + const Points &contour, + const std::vector &contour_params, + // Span of the parameters of an arch to trim with the vertical band. + double tbegin, + double tend, + // Minimum arch length to put into polylines_out. Shorter arches are not necessary to support a dense support infill. + double min_length, + Polylines &polylines_out) +{ + assert(left < right); + assert(contour.size() + 1 == contour_params.size()); + assert(contour.size() >= 3); +#ifndef NDEBUG + double contour_length = contour_params.back(); + assert(tbegin >= 0 && tbegin < contour_length); + assert(tend >= 0 && tend < contour_length); + assert(min_length > 0); +#endif // NDEBUG + + // Find iterators of the range of segments, where the first and last segment contains tbegin and tend. + size_t ibegin, iend; + { + auto it_begin = std::lower_bound(contour_params.begin(), contour_params.end(), tbegin); + auto it_end = std::lower_bound(contour_params.begin(), contour_params.end(), tend); + assert(it_begin != contour_params.end()); + assert(it_end != contour_params.end()); + if (*it_begin != tbegin) { + assert(it_begin != contour_params.begin()); + -- it_begin; + } + ibegin = it_begin - contour_params.begin(); + iend = it_end - contour_params.begin(); + } + + if (ibegin == contour.size()) + ibegin = 0; + if (iend == contour.size()) + iend = 0; + assert(ibegin != iend); + + // Trim the start and end segment to calculate start and end points. + Point pbegin, pend; + { + double t1 = contour_params[ibegin]; + double t2 = next_value_modulo(ibegin, contour_params); + pbegin = lerp(contour[ibegin], next_value_modulo(ibegin, contour), (tbegin - t1) / (t2 - t1)); + t1 = contour_params[iend]; + t2 = prev_value_modulo(iend, contour_params); + pend = lerp(contour[iend], prev_value_modulo(iend, contour), (tend - t1) / (t2 - t1)); + } + + // Trace the contour from ibegin to iend. + enum Side { + Left, + Right, + Mid, + Unknown + }; + + enum InOutBand { + Entering, + Leaving, + }; + + class State { + public: + State(coord_t left, coord_t right, double min_length, Polylines &polylines_out) : + m_left(left), m_right(right), m_min_length(min_length), m_polylines_out(polylines_out) {} + + void add_inner_point(const Point* p) + { + m_polyline.points.emplace_back(*p); + } + + void add_outer_point(const Point* p) + { + if (m_polyline_end > 0) + m_polyline.points.emplace_back(*p); + } + + void add_interpolated_point(const Point* p1, const Point* p2, Side side, InOutBand inout) + { + assert(side == Left || side == Right); + + coord_t x = side == Left ? m_left : m_right; + coord_t y = p1->y() + coord_t(double(x - p1->x()) * double(p2->y() - p1->y()) / double(p2->x() - p1->x())); + + if (inout == Leaving) { + assert(m_polyline_end == 0); + m_polyline_end = m_polyline.size(); + m_polyline.points.emplace_back(x, y); + } else { + assert(inout == Entering); + if (m_polyline_end > 0) { + if ((this->side1 == Left) == (y - m_polyline.points[m_polyline_end].y() < 0)) { + // Emit the vertical segment. Remove the point, where the source contour was split the last time at m_left / m_right. + m_polyline.points.erase(m_polyline.points.begin() + m_polyline_end); + } else { + // Don't emit the vertical segment, split the contour. + this->finalize(); + m_polyline.points.emplace_back(x, y); + } + m_polyline_end = 0; + } else + m_polyline.points.emplace_back(x, y); + } + }; + + void finalize() + { + m_polyline.points.erase(m_polyline.points.begin() + m_polyline_end, m_polyline.points.end()); + if (! m_polyline.empty()) { + if (! m_polylines_out.empty() && (m_polylines_out.back().points.back() - m_polyline.points.front()).cast().squaredNorm() < SCALED_EPSILON) + m_polylines_out.back().points.insert(m_polylines_out.back().points.end(), m_polyline.points.begin() + 1, m_polyline.points.end()); + else if (m_polyline.length() > m_min_length) + m_polylines_out.emplace_back(std::move(m_polyline)); + m_polyline.clear(); + } + }; + + private: + coord_t m_left; + coord_t m_right; + double m_min_length; + Polylines &m_polylines_out; + + Polyline m_polyline; + size_t m_polyline_end { 0 }; + Polyline m_overlapping; + + public: + Side side1 { Unknown }; + Side side2 { Unknown }; + }; + + State state { left, right, min_length, polylines_out }; + + const Point *p1 = &pbegin; + auto side = [left, right](const Point* p) { + coord_t x = p->x(); + return x < left ? Left : x > right ? Right : Mid; + }; + state.side1 = side(p1); + if (state.side1 == Mid) + state.add_inner_point(p1); + + for (size_t i = ibegin; i != iend; ) { + size_t inext = i + 1; + if (inext == contour.size()) + inext = 0; + const Point *p2 = inext == iend ? &pend : &contour[inext]; + state.side2 = side(p2); + if (state.side1 == Mid) { + if (state.side2 == Mid) { + // Inside the band. + state.add_inner_point(p2); + } else { + // From intisde the band to the outside of the band. + state.add_interpolated_point(p1, p2, state.side2, Leaving); + state.add_outer_point(p2); + } + } else if (state.side2 == Mid) { + // From outside the band into the band. + state.add_interpolated_point(p1, p2, state.side1, Entering); + state.add_inner_point(p2); + } else if (state.side1 != state.side2) { + // Both points outside the band. + state.add_interpolated_point(p1, p2, state.side1, Entering); + state.add_interpolated_point(p1, p2, state.side2, Leaving); + } else { + // Complete segment is outside. + assert((state.side1 == Left && state.side2 == Left) || (state.side1 == Right && state.side2 == Right)); + state.add_outer_point(p2); + } + state.side1 = state.side2; + p1 = p2; + i = inext; + } + state.finalize(); +} + +#ifdef INFILL_DEBUG_OUTPUT +static void export_partial_infill_to_svg(const std::string &path, const BoundaryInfillGraph &graph, const Polylines &infill, const Polylines &emitted) +{ + Polygons polygons; + for (const Points &pts : graph.boundary) + polygons.emplace_back(pts); + BoundingBox bbox = get_extents(polygons); + bbox.merge(get_extents(infill)); + ::Slic3r::SVG svg(path, bbox); + svg.draw(union_ex(polygons)); + svg.draw(infill, "blue"); + svg.draw(emitted, "darkblue"); + for (const ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) + svg.draw(graph.point(cp), cp.consumed ? "red" : "green", scaled(0.2)); + for (const ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + assert(cp.next_trimmed == cp.next_on_contour->prev_trimmed); + assert(cp.prev_trimmed == cp.prev_on_contour->next_trimmed); + if (cp.contour_not_taken_length_next > SCALED_EPSILON) { + Polyline pl { graph.point(cp) }; + take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); + svg.draw(pl, cp.could_take_next() ? "lime" : "magenta", scaled(0.1)); + } + if (cp.contour_not_taken_length_prev > SCALED_EPSILON) { + Polyline pl { graph.point(cp) }; + take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev); + svg.draw(pl, cp.could_take_prev() ? "lime" : "magenta", scaled(0.1)); + } + } +} +#endif // INFILL_DEBUG_OUTPUT + +// To classify perimeter segments connecting infill lines, whether they are required for structural stability of the supports. +struct SupportArcCost +{ + // Connecting one end of an infill line to the other end of the same infill line. + bool self_loop { false }; + // Some of the arc touches some infill line. + bool open { false }; + // How needed is this arch for support structural stability. + // Zero means don't take. The higher number, the more likely it is to take the arc. + double cost { 0 }; +}; + +static double evaluate_support_arch_cost(const Polyline &pl) +{ + Point front = pl.points.front(); + Point back = pl.points.back(); + + coord_t ymin = front.y(); + coord_t ymax = back.y(); + if (ymin > ymax) + std::swap(ymin, ymax); + + double dmax = 0; + // Maximum distance in Y axis out of the (ymin, ymax) band and from the (front, back) line. + struct Linef { Vec2d a, b; }; + Linef line { front.cast(), back.cast() }; + for (const Point pt : pl.points) + dmax = std::max(std::max(dmax, line_alg::distance_to(line, Vec2d(pt.cast()))), std::max(pt.y() - ymax, ymin - pt.y())); + return dmax; +} + +// Costs for prev / next arch of each infill line end point. +static inline std::vector evaluate_support_arches(Polylines &infill, BoundaryInfillGraph &graph, const double spacing, const FillParams ¶ms) +{ + std::vector arches(graph.map_infill_end_point_to_boundary.size() * 2); + + Polyline pl; + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + // Not a losed loop, such loops should already be consumed. + assert(cp.next_on_contour != &cp); + const size_t infill_line_idx = &cp - graph.map_infill_end_point_to_boundary.data(); + const bool first = (infill_line_idx & 1) == 0; + const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1; + + SupportArcCost &out_prev = arches[infill_line_idx * 2]; + SupportArcCost &out_next = *(&out_prev + 1); + out_prev.self_loop = cp.prev_on_contour == other_end; + out_prev.open = cp.prev_trimmed; + out_next.self_loop = cp.next_on_contour == other_end; + out_next.open = cp.next_trimmed; + + if (cp.contour_not_taken_length_next > SCALED_EPSILON) { + pl.clear(); + pl.points.emplace_back(graph.point(cp)); + if (cp.next_trimmed) + take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); + else + take_ccw_full(pl, graph.boundary[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx); + out_next.cost = evaluate_support_arch_cost(pl); + } + + if (cp.contour_not_taken_length_prev > SCALED_EPSILON) { + pl.clear(); + pl.points.emplace_back(graph.point(cp)); + if (cp.prev_trimmed) + take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev); + else + take_cw_full(pl, graph.boundary[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx); + out_prev.cost = evaluate_support_arch_cost(pl); + } + } + + return arches; +} + +// Both the poly_with_offset and polylines_out are rotated, so the infill lines are strictly vertical. +void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +{ +// assert(! infill_ordered.empty()); + assert(params.anchor_length >= 0.); + assert(params.anchor_length_max >= 0.01f); + assert(params.anchor_length_max >= params.anchor_length); + + BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing); + +#ifdef INFILL_DEBUG_OUTPUT + static int iRun = 0; + ++ iRun; + export_partial_infill_to_svg(debug_out_path("connect_base_support-initial-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + const double line_half_width = 0.5 * scale_(spacing); + const double line_spacing = scale_(spacing) / params.density; + const double min_arch_length = 1.3 * line_spacing; + const double trim_length = line_half_width * 0.3; + +// After mark_boundary_segments_touching_infill() marks boundary segments overlapping trimmed infill lines, +// there are possibly some very short boundary segments unmarked, but overlapping the untrimmed infill lines fully. +// Mark those short boundary segments. + mark_boundary_segments_overlapping_infill(graph, infill_ordered, scale_(spacing)); + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-marked-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Detect loops with zero infill end points connected. + // Extrude these loops as perimeters. + { + std::vector num_boundary_contour_infill_points(graph.boundary.size(), 0); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) + ++ num_boundary_contour_infill_points[cp.contour_idx]; + for (size_t i = 0; i < num_boundary_contour_infill_points.size(); ++ i) + if (num_boundary_contour_infill_points[i] == 0 && graph.boundary_params[i].back() > trim_length + 0.5 * line_spacing) { + // Emit a perimeter. + Polyline pl(graph.boundary[i]); + pl.points.emplace_back(pl.points.front()); + pl.clip_end(trim_length); + if (pl.size() > 1) + polylines_out.emplace_back(std::move(pl)); + } + } + + // Before processing the boundary arches, emit those arches, which were trimmed by the infill lines at both sides, but which + // depart from the infill line at least once after touching the infill line. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.next_on_contour && cp.next_trimmed && cp.next_on_contour->prev_trimmed) { + // The arch is leaving one infill line to end up at the same infill line or at the neighbouring one. + // The arch is touching one of those infill lines at least once. + // Trace those arches and emit their parts, which are not attached to the end points and they are not overlapping with the two infill lines mentioned. + bool first = graph.first(cp); + coord_t left = graph.point(cp).x(); + coord_t right = left; + if (first) { + left += line_half_width; + right += line_spacing - line_half_width; + } else { + left -= line_spacing - line_half_width; + right -= line_half_width; + } + double param_start = cp.param + cp.contour_not_taken_length_next; + double param_end = cp.next_on_contour->param - cp.next_on_contour->contour_not_taken_length_prev; + double contour_length = graph.boundary_params[cp.contour_idx].back(); + if (param_start >= contour_length) + param_start -= contour_length; + if (param_end < 0) + param_end += contour_length; + // Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param). + assert(cyclic_interval_inside_interval(cp.param, cp.next_on_contour->param, param_start, param_end, contour_length)); + emit_loops_in_band(left, right, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], param_start, param_end, 0.5 * line_spacing, polylines_out); + } + } +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-excess-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + base_support_extend_infill_lines(infill_ordered, graph, spacing, params); + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-extended-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + std::vector merged_with(infill_ordered.size()); + std::iota(merged_with.begin(), merged_with.end(), 0); + auto get_and_update_merged_with = [&graph, &merged_with](const ContourIntersectionPoint *cp) -> size_t { + size_t polyline_idx = (cp - graph.map_infill_end_point_to_boundary.data()) / 2; + for (size_t last = polyline_idx;;) { + size_t lower = merged_with[last]; + assert(lower <= last); + if (lower == last) { + merged_with[polyline_idx] = last; + return last; + } + last = lower; + } + assert(false); + return std::numeric_limits::max(); + }; + + auto vertical = [](BoundaryInfillGraph::Direction dir) { + return dir == BoundaryInfillGraph::Up || dir == BoundaryInfillGraph::Down; + }; + // When both left / right arch connected to cp is vertical (ends up at the same vertical infill line), which one to take? + auto take_vertical_prev = [](const ContourIntersectionPoint &cp) { + return cp.prev_trimmed == cp.next_trimmed ? + // Both are either trimmed or not trimmed. Take the longer contour. + cp.contour_not_taken_length_prev > cp.contour_not_taken_length_next : + // One is trimmed, the other is not trimmed. Take the not trimmed. + ! cp.prev_trimmed && cp.next_trimmed; + }; + + // Connect infill lines at cp and cpo_next_on_contour. + // If the complete arch cannot be taken, then + // if (take_first) + // take the infill line at cp and an arc from cp towards cp.next_on_contour. + // else + // take the infill line at cp_next_on_contour and an arc from cp.next_on_contour towards cp. + // If cp1 == next_on_contour (a single infill line is connected to a contour, this is a valid case for contours with holes), + // then extrude the full circle. + // Nothing is done if the arch could no more be taken (one of it end points were consumed already). + auto take_next = [&graph, &infill_ordered, &merged_with, get_and_update_merged_with, line_half_width, trim_length](ContourIntersectionPoint &cp, bool take_first) { + // Indices of the polylines to be connected by a perimeter segment. + ContourIntersectionPoint *cp1 = &cp; + ContourIntersectionPoint *cp2 = cp.next_on_contour; + assert(cp1->next_trimmed == cp2->prev_trimmed); + //assert(cp1->next_trimmed || cp1->consumed == cp2->consumed); + if (take_first ? cp1->consumed : cp2->consumed) + return; + size_t polyline_idx1 = get_and_update_merged_with(cp1); + size_t polyline_idx2 = get_and_update_merged_with(cp2); + Polyline &polyline1 = infill_ordered[polyline_idx1]; + Polyline &polyline2 = infill_ordered[polyline_idx2]; + const Points &contour = graph.boundary[cp1->contour_idx]; + const std::vector &contour_params = graph.boundary_params[cp1->contour_idx]; + assert(cp1->consumed || contour[cp1->point_idx] == polyline1.points.front() || contour[cp1->point_idx] == polyline1.points.back()); + assert(cp2->consumed || contour[cp2->point_idx] == polyline2.points.front() || contour[cp2->point_idx] == polyline2.points.back()); + bool trimmed = take_first ? cp1->next_trimmed : cp2->prev_trimmed; + if (! trimmed) { + // Trim the end if closing a loop or making a T-joint. + trimmed = cp1 == cp2 || polyline_idx1 == polyline_idx2 || (take_first ? cp2->consumed : cp1->consumed); + if (! trimmed) { + const bool cp1_first = ((cp1 - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + const ContourIntersectionPoint* cp1_other = cp1_first ? cp1 + 1 : cp1 - 1; + // Self loop, connecting the end points of the same infill line. + trimmed = cp2 == cp1_other; + } + if (trimmed) /* [[unlikely]] */ { + // Single end point on a contour. This may happen on contours with holes. Extrude a loop. + // Or a self loop, connecting the end points of the same infill line. + // Or closing a chain of infill lines. This may happen if infilling a contour with a hole. + double len = cp1 == cp2 ? contour_params.back() : path_length_along_contour_ccw(cp1, cp2, contour_params.back()); + if (take_first) { + cp1->trim_next(std::max(0., len - trim_length - SCALED_EPSILON)); + cp2->trim_prev(0); + } else { + cp1->trim_next(0); + cp2->trim_prev(std::max(0., len - trim_length - SCALED_EPSILON)); + } + } + } + if (trimmed) { + if (take_first) + take_limited(polyline1, contour, contour_params, cp1, cp2, false, 1e10, line_half_width); + else + take_limited(polyline2, contour, contour_params, cp2, cp1, true, 1e10, line_half_width); + } else if (! cp1->consumed && ! cp2->consumed) { + if (contour[cp1->point_idx] == polyline1.points.front()) + polyline1.reverse(); + if (contour[cp2->point_idx] == polyline2.points.back()) + polyline2.reverse(); + take(polyline1, polyline2, contour, cp1, cp2, false); + // Mark the second polygon as merged with the first one. + if (polyline_idx2 < polyline_idx1) { + polyline2 = std::move(polyline1); + polyline1.points.clear(); + merged_with[polyline_idx1] = merged_with[polyline_idx2]; + } else { + polyline2.points.clear(); + merged_with[polyline_idx2] = merged_with[polyline_idx1]; + } + } + }; + + // Consume all vertical arches. If a vertical arch is touching a neighboring vertical infill line, thus the vertical arch is trimmed, + // only consume the trimmed part if it is longer than min_arch_length. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + assert(cp.contour_idx != boundary_idx_unconnected); + if (cp.consumed) + continue; + const ContourIntersectionPoint &cp_other = graph.other(cp); + assert((cp.next_on_contour == &cp_other) == (cp_other.prev_on_contour == &cp)); + assert((cp.prev_on_contour == &cp_other) == (cp_other.next_on_contour == &cp)); + BoundaryInfillGraph::Direction dir_prev = graph.dir_prev(cp); + BoundaryInfillGraph::Direction dir_next = graph.dir_next(cp); + // Following code will also consume contours with just a single infill line attached. (cp1->next_on_contour == cp1). + assert((cp.next_on_contour == &cp) == (cp.prev_on_contour == &cp)); + bool can_take_prev = vertical(dir_prev) && ! cp.prev_on_contour->consumed && cp.prev_on_contour != &cp_other; + bool can_take_next = vertical(dir_next) && ! cp.next_on_contour->consumed && cp.next_on_contour != &cp_other; + if (can_take_prev && (! can_take_next || take_vertical_prev(cp))) { + if (! cp.prev_trimmed || cp.contour_not_taken_length_prev > min_arch_length) + // take previous + take_next(*cp.prev_on_contour, false); + } else if (can_take_next) { + if (! cp.next_trimmed || cp.contour_not_taken_length_next > min_arch_length) + // take next + take_next(cp, true); + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-vertical-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + const std::vector arches = evaluate_support_arches(infill_ordered, graph, spacing, params); + static const double cost_low = line_spacing * 1.3; + static const double cost_high = line_spacing * 2.; + static const double cost_veryhigh = line_spacing * 3.; + + { + std::vector selected; + selected.reserve(graph.map_infill_end_point_to_boundary.size()); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.consumed) + continue; + const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]; + const SupportArcCost &cost_next = *(&cost_prev + 1); + double cost_min = cost_prev.cost; + double cost_max = cost_next.cost; + if (cost_min > cost_max) + std::swap(cost_min, cost_max); + if (cost_max < cost_low || cost_min > cost_high) + // Don't take any of the prev / next arches now, take zig-zag instead. It does not matter which one will be taken. + continue; + const double cost_diff_relative = (cost_max - cost_min) / cost_max; + if (cost_diff_relative < 0.25) + // Don't take any of the prev / next arches now, take zig-zag instead. It does not matter which one will be taken. + continue; + if (cost_prev.cost > cost_low) + selected.emplace_back(&cost_prev); + if (cost_next.cost > cost_low) + selected.emplace_back(&cost_next); + } + // Take the longest arch first. + std::sort(selected.begin(), selected.end(), [](const auto *l, const auto *r) { return l->cost > r->cost; }); + // And connect along the arches. + for (const SupportArcCost *arc : selected) { + ContourIntersectionPoint &cp = graph.map_infill_end_point_to_boundary[(arc - arches.data()) / 2]; + if (! cp.consumed) { + bool prev = ((arc - arches.data()) & 1) == 0; + if (prev) + take_next(*cp.prev_on_contour, false); + else + take_next(cp, true); + } + } + } + +#if 0 + { + // Connect infill lines with long horizontal arches. Only take a horizontal arch, if it will not block + // the end caps (vertical arches) at the other side of the infill line. + struct Arc { + ContourIntersectionPoint *intersection; + double arc_length; + bool take_next; + }; + std::vector arches; + arches.reserve(graph.map_infill_end_point_to_boundary.size()); + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.consumed) + continue; + // Not a losed loop, such loops should already be consumed. + assert(cp.next_on_contour != &cp); + const bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1; + const bool loop_next = cp.next_on_contour == other_end; + if (! loop_next && cp.could_connect_next()) { + if (cp.contour_not_taken_length_next > min_arch_length) { + // Try both directions. This is useful to be able to close a loop back to the same line to take a long arch. + arches.push_back({ &cp, cp.contour_not_taken_length_next, true }); + arches.push_back({ cp.next_on_contour, cp.contour_not_taken_length_next, false }); + } + } else { + //bool first = ((&cp - graph.map_infill_end_point_to_boundary) & 1) == 0; + if (cp.prev_trimmed && cp.could_take_prev()) { + //FIXME trace the trimmed line to decide what priority to assign to it. + // Is the end point close to the current vertical line or to the other vertical line? + const Point &pt = graph.point(cp); + const Point &prev = graph.point(*cp.prev_on_contour); + if (std::abs(pt.x() - prev.x()) < coord_t(0.5 * line_spacing)) { + // End point on the same line. + // Measure maximum distance from the current vertical line. + if (cp.contour_not_taken_length_prev > 0.5 * line_spacing) + arches.push_back({ &cp, cp.contour_not_taken_length_prev, false }); + } else { + // End point on the other line. + if (cp.contour_not_taken_length_prev > min_arch_length) + arches.push_back({ &cp, cp.contour_not_taken_length_prev, false }); + } + } + if (cp.next_trimmed && cp.could_take_next()) { + //FIXME trace the trimmed line to decide what priority to assign to it. + const Point &pt = graph.point(cp); + const Point &next = graph.point(*cp.next_on_contour); + if (std::abs(pt.x() - next.x()) < coord_t(0.5 * line_spacing)) { + // End point on the same line. + // Measure maximum distance from the current vertical line. + if (cp.contour_not_taken_length_next > 0.5 * line_spacing) + arches.push_back({ &cp, cp.contour_not_taken_length_next, true }); + } else { + // End point on the other line. + if (cp.contour_not_taken_length_next > min_arch_length) + arches.push_back({ &cp, cp.contour_not_taken_length_next, true }); + } + } + } + } + // Take the longest arch first. + std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length > r.arc_length; }); + // And connect along the arches. + for (Arc &arc : arches) + if (arc.take_next) + take_next(*arc.intersection, true); + else + take_next(*arc.intersection->prev_on_contour, false); + } +#endif + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-arches-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Traverse the unconnected lines in a zig-zag fashion, left to right only. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + assert(cp.contour_idx != boundary_idx_unconnected); + if (cp.consumed) + continue; + bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + if (first) { + // Only connect if the two lines are not connected by the same line already. + if (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.next_on_contour)) + take_next(cp, true); + } else { + if (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.prev_on_contour)) + take_next(*cp.prev_on_contour, false); + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-zigzag-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Add the left caps. + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + const bool first = ((&cp - graph.map_infill_end_point_to_boundary.data()) & 1) == 0; + const ContourIntersectionPoint *other_end = first ? &cp + 1 : &cp - 1; + const bool loop_next = cp.next_on_contour == other_end; + const bool loop_prev = other_end->next_on_contour == &cp; +#ifndef NDEBUG + const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]; + const SupportArcCost &cost_next = *(&cost_prev + 1); + assert(cost_prev.self_loop == loop_prev); + assert(cost_next.self_loop == loop_next); +#endif // NDEBUG + if (loop_prev && cp.could_take_prev()) + take_next(*cp.prev_on_contour, false); + if (loop_next && cp.could_take_next()) + take_next(cp, true); + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-caps-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Connect with T joints using long arches. Loops could be created only if a very long arc has to be added. + { + std::vector candidates; + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + if (cp.could_take_prev()) + candidates.emplace_back(&arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]); + if (cp.could_take_next()) + candidates.emplace_back(&arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2 + 1]); + } + std::sort(candidates.begin(), candidates.end(), [](auto *c1, auto *c2) { return c1->cost > c2->cost; }); + for (const SupportArcCost *candidate : candidates) { + ContourIntersectionPoint &cp = graph.map_infill_end_point_to_boundary[(candidate - arches.data()) / 2]; + bool prev = ((candidate - arches.data()) & 1) == 0; + if (prev) { + if (cp.could_take_prev() && (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.prev_on_contour) || candidate->cost > cost_high)) + take_next(*cp.prev_on_contour, false); + } else { + if (cp.could_take_next() && (get_and_update_merged_with(&cp) != get_and_update_merged_with(cp.next_on_contour) || candidate->cost > cost_high)) + take_next(cp, true); + } + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-Tjoints-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + // Add very long arches and reasonably long caps even if both of its end points were already consumed. + const double cap_cost = 0.5 * line_spacing; + for (ContourIntersectionPoint &cp : graph.map_infill_end_point_to_boundary) { + const SupportArcCost &cost_prev = arches[(&cp - graph.map_infill_end_point_to_boundary.data()) * 2]; + const SupportArcCost &cost_next = *(&cost_prev + 1); + if (cp.contour_not_taken_length_prev > SCALED_EPSILON && + (cost_prev.self_loop ? + cost_prev.cost > cap_cost : + cost_prev.cost > cost_veryhigh)) { + assert(cp.consumed && (cp.prev_on_contour->consumed || cp.prev_trimmed)); + Polyline pl { graph.point(cp) }; + if (! cp.prev_trimmed) { + cp.trim_prev(cp.contour_not_taken_length_prev - line_half_width); + cp.prev_on_contour->trim_next(0); + } + if (cp.contour_not_taken_length_prev > SCALED_EPSILON) { + take_cw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.prev_on_contour->point_idx, cp.contour_not_taken_length_prev); + cp.trim_prev(0); + pl.clip_start(line_half_width); + polylines_out.emplace_back(std::move(pl)); + } + } + if (cp.contour_not_taken_length_next > SCALED_EPSILON && + (cost_next.self_loop ? + cost_next.cost > cap_cost : + cost_next.cost > cost_veryhigh)) { + assert(cp.consumed && (cp.next_on_contour->consumed || cp.next_trimmed)); + Polyline pl { graph.point(cp) }; + if (! cp.next_trimmed) { + cp.trim_next(cp.contour_not_taken_length_next - line_half_width); + cp.next_on_contour->trim_prev(0); + } + if (cp.contour_not_taken_length_next > SCALED_EPSILON) { + take_ccw_limited(pl, graph.boundary[cp.contour_idx], graph.boundary_params[cp.contour_idx], cp.point_idx, cp.next_on_contour->point_idx, cp.contour_not_taken_length_next); // line_half_width); + cp.trim_next(0); + pl.clip_start(line_half_width); + polylines_out.emplace_back(std::move(pl)); + } + } + } + +#ifdef INFILL_DEBUG_OUTPUT + export_partial_infill_to_svg(debug_out_path("connect_base_support-final-%03d.svg", iRun), graph, infill_ordered, polylines_out); +#endif // INFILL_DEBUG_OUTPUT + + polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); })); + for (Polyline &pl : infill_ordered) + if (! pl.empty()) + polylines_out.emplace_back(std::move(pl)); +} + +void Fill::connect_base_support(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) +{ + auto polygons_src = reserve_vector(boundary_src.size()); + for (const Polygon &polygon : boundary_src) + polygons_src.emplace_back(&polygon); + + connect_base_support(std::move(infill_ordered), polygons_src, bbox, polylines_out, spacing, params); +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index c09b70bca..eb9c4f1cc 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -133,26 +133,10 @@ public: static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); static void connect_infill(Polylines &&infill_ordered, const std::vector &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams ¶ms); - static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); + static void connect_base_support(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); + static void connect_base_support(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms); - // Align a coordinate to a grid. The coordinate may be negative, - // the aligned value will never be bigger than the original one. - static coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { - // Current C++ standard defines the result of integer division to be rounded to zero, - // for both positive and negative numbers. Here we want to round down for negative - // numbers as well. - coord_t aligned = (coord < 0) ? - ((coord - spacing + 1) / spacing) * spacing : - (coord / spacing) * spacing; - assert(aligned <= coord); - return aligned; - } - static Point _align_to_grid(Point coord, Point spacing) - { return Point(_align_to_grid(coord(0), spacing(0)), _align_to_grid(coord(1), spacing(1))); } - static coord_t _align_to_grid(coord_t coord, coord_t spacing, coord_t base) - { return base + _align_to_grid(coord - base, spacing); } - static Point _align_to_grid(Point coord, Point spacing, Point base) - { return Point(_align_to_grid(coord(0), spacing(0), base(0)), _align_to_grid(coord(1), spacing(1), base(1))); } + static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance); }; } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index 5797c47a5..ff2d049cf 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -166,7 +166,7 @@ void FillGyroid::_fill_surface_single( 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( diff --git a/src/libslic3r/Fill/FillHoneycomb.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp index 5e7000088..f7f79ae83 100644 --- a/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/src/libslic3r/Fill/FillHoneycomb.cpp @@ -47,7 +47,7 @@ void FillHoneycomb::_fill_surface_single( // extend bounding box so that our pattern will be aligned with other layers // $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one // The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough. - bounding_box.merge(_align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height))); + bounding_box.merge(align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height))); } coord_t x = bounding_box.min(0); diff --git a/src/libslic3r/Fill/FillLine.cpp b/src/libslic3r/Fill/FillLine.cpp index 1cb9b2244..6a0a19efd 100644 --- a/src/libslic3r/Fill/FillLine.cpp +++ b/src/libslic3r/Fill/FillLine.cpp @@ -31,7 +31,7 @@ void FillLine::_fill_surface_single( } else { // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. - bounding_box.merge(_align_to_grid( + bounding_box.merge(align_to_grid( bounding_box.min, Point(this->_line_spacing, this->_line_spacing), direction.second.rotated(- direction.first))); diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 07f41de38..baf57f426 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -798,33 +798,44 @@ static std::vector slice_region_by_vertical_lines(con assert(l <= this_x); assert(r >= this_x); // Calculate the intersection position in y axis. x is known. - if (p1(0) == this_x) { - if (p2(0) == this_x) { + if (p1.x() == this_x) { + if (p2.x() == this_x) { // Ignore strictly vertical segments. continue; } - is.pos_p = p1(1); + const Point &p0 = prev_value_modulo(iPrev, contour); + if (int64_t(p0.x() - p1.x()) * int64_t(p2.x() - p1.x()) > 0) { + // Ignore points of a contour touching the infill line from one side. + continue; + } + is.pos_p = p1.y(); is.pos_q = 1; - } else if (p2(0) == this_x) { - is.pos_p = p2(1); + } else if (p2.x() == this_x) { + const Point &p3 = next_value_modulo(iSegment, contour); + if (int64_t(p3.x() - p2.x()) * int64_t(p1.x() - p2.x()) > 0) { + // Ignore points of a contour touching the infill line from one side. + continue; + } + is.pos_p = p2.y(); is.pos_q = 1; } else { // First calculate the intersection parameter 't' as a rational number with non negative denominator. - if (p2(0) > p1(0)) { - is.pos_p = this_x - p1(0); - is.pos_q = p2(0) - p1(0); + if (p2.x() > p1.x()) { + is.pos_p = this_x - p1.x(); + is.pos_q = p2.x() - p1.x(); } else { - is.pos_p = p1(0) - this_x; - is.pos_q = p1(0) - p2(0); + is.pos_p = p1.x() - this_x; + is.pos_q = p1.x() - p2.x(); } - assert(is.pos_p >= 0 && is.pos_p <= is.pos_q); + assert(is.pos_q > 1); + assert(is.pos_p > 0 && is.pos_p < is.pos_q); // Make an intersection point from the 't'. - is.pos_p *= int64_t(p2(1) - p1(1)); - is.pos_p += p1(1) * int64_t(is.pos_q); + is.pos_p *= int64_t(p2.y() - p1.y()); + is.pos_p += p1.y() * int64_t(is.pos_q); } // +-1 to take rounding into account. - assert(is.pos() + 1 >= std::min(p1(1), p2(1))); - assert(is.pos() <= std::max(p1(1), p2(1)) + 1); + assert(is.pos() + 1 >= std::min(p1.y(), p2.y())); + assert(is.pos() <= std::max(p1.y(), p2.y()) + 1); segs[i].intersections.push_back(is); } } @@ -844,55 +855,46 @@ static std::vector slice_region_by_vertical_lines(con size_t j = 0; for (size_t i = 0; i < sil.intersections.size(); ++ i) { // What is the orientation of the segment at the intersection point? - size_t iContour = sil.intersections[i].iContour; - const Points &contour = poly_with_offset.contour(iContour).points; - size_t iSegment = sil.intersections[i].iSegment; - size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1; - coord_t dir = contour[iSegment](0) - contour[iPrev](0); - bool low = dir > 0; - sil.intersections[i].type = poly_with_offset.is_contour_outer(iContour) ? + SegmentIntersection &is = sil.intersections[i]; + const size_t iContour = is.iContour; + const Points &contour = poly_with_offset.contour(iContour).points; + const size_t iSegment = is.iSegment; + const size_t iPrev = prev_idx_modulo(iSegment, contour); + const coord_t dir = contour[iSegment].x() - contour[iPrev].x(); + const bool low = dir > 0; + is.type = poly_with_offset.is_contour_outer(iContour) ? (low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) : (low ? SegmentIntersection::INNER_LOW : SegmentIntersection::INNER_HIGH); - if (j > 0 && sil.intersections[i].iContour == sil.intersections[j-1].iContour) { - // Two successive intersection points on a vertical line with the same contour. This may be a special case. - if (sil.intersections[i].pos() == sil.intersections[j-1].pos()) { - // Two successive segments meet exactly at the vertical line. - #ifdef SLIC3R_DEBUG - // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint. - size_t iSegment2 = sil.intersections[j-1].iSegment; - size_t iPrev2 = ((iSegment2 == 0) ? contour.size() : iSegment2) - 1; - assert(iSegment == iPrev2 || iSegment2 == iPrev); - #endif /* SLIC3R_DEBUG */ - if (sil.intersections[i].type == sil.intersections[j-1].type) { + bool take_next = true; + if (j > 0) { + SegmentIntersection &is2 = sil.intersections[j - 1]; + if (iContour == is2.iContour && is.pos_q == 1 && is2.pos_q == 1) { + // Two successive intersection points on a vertical line with the same contour, both points are end points of their respective contour segments. + if (is.pos_p == is2.pos_p) { + // Two successive segments meet exactly at the vertical line. + // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint. + assert(iSegment == prev_idx_modulo(is2.iSegment, contour) || is2.iSegment == iPrev); + assert(is.type == is2.type); // Two successive segments of the same direction (both to the right or both to the left) // meet exactly at the vertical line. // Remove the second intersection point. - } else { - // This is a loop returning to the same point. - // It may as well be a vertex of a loop touching this vertical line. - // Remove both the lines. - -- j; + take_next = false; + } else if (is.type == is2.type) { + // Two non successive segments of the same direction (both to the right or both to the left) + // meet exactly at the vertical line. That means there is a Z shaped path, where the center segment + // of the Z shaped path is aligned with this vertical line. + // Remove one of the intersection points while maximizing the vertical segment length. + if (low) { + // Remove the second intersection point, keep the first intersection point. + } else { + // Remove the first intersection point, keep the second intersection point. + sil.intersections[j-1] = sil.intersections[i]; + } + take_next = false; } - } else if (sil.intersections[i].type == sil.intersections[j-1].type) { - // Two non successive segments of the same direction (both to the right or both to the left) - // meet exactly at the vertical line. That means there is a Z shaped path, where the center segment - // of the Z shaped path is aligned with this vertical line. - // Remove one of the intersection points while maximizing the vertical segment length. - if (low) { - // Remove the second intersection point, keep the first intersection point. - } else { - // Remove the first intersection point, keep the second intersection point. - sil.intersections[j-1] = sil.intersections[i]; - } - } else { - // Vertical line intersects a contour segment at a general position (not at one of its end points). - // or the contour just touches this vertical line with a vertical segment or a sequence of vertical segments. - // Keep both intersection points. - if (j < i) - sil.intersections[j] = sil.intersections[i]; - ++ j; } - } else { + } + if (take_next) { // Vertical line intersects a contour segment at a general position (not at one of its end points). if (j < i) sil.intersections[j] = sil.intersections[i]; @@ -905,7 +907,13 @@ static std::vector slice_region_by_vertical_lines(con } // Verify the segments. If something is wrong, give up. -#define ASSERT_THROW(CONDITION) do { assert(CONDITION); if (! (CONDITION)) throw InfillFailedException(); } while (0) +#ifdef INFILL_DEBUG_OUTPUT + #define INFILL_DEBUG_ASSERT(CONDITION) + try { +#else // INFILL_DEBUG_OUTPUT + #define INFILL_DEBUG_ASSERT(CONDITION) assert(CONDITION) +#endif // INFILL_DEBUG_OUTPUT +#define ASSERT_THROW(CONDITION) do { INFILL_DEBUG_ASSERT(CONDITION); if (! (CONDITION)) throw InfillFailedException(); } while (0) for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) { SegmentedIntersectionLine &sil = segs[i_seg]; // The intersection points have to be even. @@ -925,6 +933,56 @@ static std::vector slice_region_by_vertical_lines(con } } #undef ASSERT_THROW +#undef INFILL_DEBUG_ASSERT +#ifdef INFILL_DEBUG_OUTPUT + } catch (const InfillFailedException & /* ex */) { + // Export the buggy result into an SVG file. + static int iRun = 0; + BoundingBox bbox = get_extents(poly_with_offset.polygons_src); + bbox.offset(scale_(3.)); + ::Slic3r::SVG svg(debug_out_path("slice_region_by_vertical_lines-failed-%d.svg", iRun ++), bbox); + svg.draw(poly_with_offset.polygons_src); + svg.draw_outline(poly_with_offset.polygons_src, "green"); + svg.draw_outline(poly_with_offset.polygons_outer, "green"); + svg.draw_outline(poly_with_offset.polygons_inner, "green"); + for (size_t i_seg = 0; i_seg < segs.size(); ++i_seg) { + SegmentedIntersectionLine &sil = segs[i_seg]; + for (size_t i = 0; i < sil.intersections.size();) { + // An intersection segment crossing the bigger contour may cross the inner offsetted contour even number of times. + if (sil.intersections[i].type != SegmentIntersection::OUTER_LOW) { + svg.draw(Point(sil.pos, sil.intersections[i].pos()), "red"); + break; + } + size_t j = i + 1; + if (j == sil.intersections.size()) { + svg.draw(Point(sil.pos, sil.intersections[i].pos()), "magenta"); + break; + } + if (! (sil.intersections[j].type == SegmentIntersection::INNER_LOW || sil.intersections[j].type == SegmentIntersection::OUTER_HIGH)) { + svg.draw(Point(sil.pos, sil.intersections[j].pos()), "blue"); + break; + } + for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++j); + if (j == sil.intersections.size()) { + svg.draw(Point(sil.pos, sil.intersections[j - 1].pos()), "magenta"); + break; + } + if ((j & 1) != 1 || sil.intersections[j].type != SegmentIntersection::OUTER_HIGH) { + svg.draw(Point(sil.pos, sil.intersections[j].pos()), "red"); + break; + } + if (! (i + 1 == j || sil.intersections[j - 1].type == SegmentIntersection::INNER_HIGH)) { + svg.draw(Point(sil.pos, sil.intersections[j].pos()), "red"); + break; + } + svg.draw(Line(Point(sil.pos, sil.intersections[i].pos()), Point(sil.pos, sil.intersections[j].pos())), "black"); + i = j + 1; + } + } + assert(false); + throw; + } +#endif //INFILL_DEBUG_OUTPUT return segs; } @@ -2714,10 +2772,10 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa // extend bounding box so that our pattern will be aligned with other layers // Transform the reference point to the rotated coordinate system. Point refpt = rotate_vector.second.rotated(- rotate_vector.first); - // _align_to_grid will not work correctly with positive pattern_shift. + // align_to_grid will not work correctly with positive pattern_shift. coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing; refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); - bounding_box.merge(_align_to_grid( + bounding_box.merge(align_to_grid( bounding_box.min, Point(line_spacing, line_spacing), refpt)); @@ -2825,6 +2883,45 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa return true; } +void make_fill_lines(const ExPolygonWithOffset &poly_with_offset, Point refpt, double angle, coord_t x_margin, coord_t line_spacing, coord_t pattern_shift, Polylines &fill_lines) +{ + BoundingBox bounding_box = poly_with_offset.bounding_box_src(); + // Don't produce infill lines, which fully overlap with the infill perimeter. + coord_t x_min = bounding_box.min.x() + x_margin; + coord_t x_max = bounding_box.max.x() - x_margin; + // extend bounding box so that our pattern will be aligned with other layers + // align_to_grid will not work correctly with positive pattern_shift. + coord_t pattern_shift_scaled = pattern_shift % line_spacing; + refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); + bounding_box.merge(Slic3r::align_to_grid(bounding_box.min, Point(line_spacing, line_spacing), refpt)); + + // Intersect a set of euqally spaced vertical lines wiht expolygon. + // n_vlines = ceil(bbox_width / line_spacing) + const size_t n_vlines = (bounding_box.max.x() - bounding_box.min.x() + line_spacing - 1) / line_spacing; + const double cos_a = cos(angle); + const double sin_a = sin(angle); + for (const SegmentedIntersectionLine &vline : slice_region_by_vertical_lines(poly_with_offset, n_vlines, bounding_box.min.x(), line_spacing)) + if (vline.pos >= x_min) { + if (vline.pos > x_max) + break; + for (auto it = vline.intersections.begin(); it != vline.intersections.end();) { + auto it_low = it ++; + assert(it_low->type == SegmentIntersection::OUTER_LOW); + if (it_low->type != SegmentIntersection::OUTER_LOW) + continue; + auto it_high = it; + assert(it_high->type == SegmentIntersection::OUTER_HIGH); + if (it_high->type == SegmentIntersection::OUTER_HIGH) { + if (angle == 0.) + fill_lines.emplace_back(Point(vline.pos, it_low->pos()), Point(vline.pos, it_high->pos())); + else + fill_lines.emplace_back(Point(vline.pos, it_low->pos()).rotated(cos_a, sin_a), Point(vline.pos, it_high->pos()).rotated(cos_a, sin_a)); + ++ it; + } + } + } +} + bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out) { assert(sweep_params.size() > 1); @@ -2843,42 +2940,8 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar std::pair rotate_vector = this->_infill_direction(surface); for (const SweepParams &sweep : sweep_params) { // Rotate polygons so that we can work with vertical lines here - double angle = rotate_vector.first + sweep.angle_base; - ExPolygonWithOffset poly_with_offset(poly_with_offset_base, - angle); - BoundingBox bounding_box = poly_with_offset.bounding_box_src(); - // Don't produce infill lines, which fully overlap with the infill perimeter. - coord_t x_min = bounding_box.min.x() + line_width + coord_t(SCALED_EPSILON); - coord_t x_max = bounding_box.max.x() - line_width - coord_t(SCALED_EPSILON); - // extend bounding box so that our pattern will be aligned with other layers - // Transform the reference point to the rotated coordinate system. - Point refpt = rotate_vector.second.rotated(- angle); - // _align_to_grid will not work correctly with positive pattern_shift. - coord_t pattern_shift_scaled = coord_t(scale_(sweep.pattern_shift)) % line_spacing; - refpt.x() -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); - bounding_box.merge(_align_to_grid(bounding_box.min, Point(line_spacing, line_spacing), refpt)); - - // Intersect a set of euqally spaced vertical lines wiht expolygon. - // n_vlines = ceil(bbox_width / line_spacing) - const size_t n_vlines = (bounding_box.max.x() - bounding_box.min.x() + line_spacing - 1) / line_spacing; - const double cos_a = cos(angle); - const double sin_a = sin(angle); - for (const SegmentedIntersectionLine &vline : slice_region_by_vertical_lines(poly_with_offset, n_vlines, bounding_box.min.x(), line_spacing)) - if (vline.pos > x_min) { - if (vline.pos >= x_max) - break; - for (auto it = vline.intersections.begin(); it != vline.intersections.end();) { - auto it_low = it ++; - assert(it_low->type == SegmentIntersection::OUTER_LOW); - if (it_low->type != SegmentIntersection::OUTER_LOW) - continue; - auto it_high = it; - assert(it_high->type == SegmentIntersection::OUTER_HIGH); - if (it_high->type == SegmentIntersection::OUTER_HIGH) { - fill_lines.emplace_back(Point(vline.pos, it_low->pos()).rotated(cos_a, sin_a), Point(vline.pos, it_high->pos()).rotated(cos_a, sin_a)); - ++ it; - } - } - } + float angle = rotate_vector.first + sweep.angle_base; + make_fill_lines(ExPolygonWithOffset(poly_with_offset_base, - angle), rotate_vector.second.rotated(-angle), angle, line_width + coord_t(SCALED_EPSILON), line_spacing, coord_t(scale_(sweep.pattern_shift)), fill_lines); } if (params.dont_connect() || fill_lines.size() <= 1) { @@ -2954,4 +3017,29 @@ Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶ return polylines_out; } +Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams ¶ms) +{ + assert(! params.full_infill()); + + Polylines polylines_out; + std::pair rotate_vector = this->_infill_direction(surface); + ExPolygonWithOffset poly_with_offset(surface->expolygon, - rotate_vector.first, float(scale_(this->overlap - 0.5 * this->spacing))); + if (poly_with_offset.n_contours > 0) { + Polylines fill_lines; + coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); + // Create infill lines, keep them vertical. + make_fill_lines(poly_with_offset, rotate_vector.second.rotated(- rotate_vector.first), 0, 0, line_spacing, 0, fill_lines); + // Both the poly_with_offset and polylines_out are rotated, so the infill lines are strictly vertical. + connect_base_support(std::move(fill_lines), poly_with_offset.polygons_outer, poly_with_offset.bounding_box_outer(), polylines_out, this->spacing, params); + // Rotate back by rotate_vector.first + const double cos_a = cos(rotate_vector.first); + const double sin_a = sin(rotate_vector.first); + for (Polyline &pl : polylines_out) + for (Point &pt : pl.points) + pt.rotate(cos_a, sin_a); + } + return polylines_out; +} + } // namespace Slic3r + diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 692fba2bd..ad32ad20f 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -97,6 +97,17 @@ protected: float _layer_angle(size_t idx) const override { return 0.f; } }; +class FillSupportBase : public FillRectilinear +{ +public: + Fill* clone() const override { return new FillSupportBase(*this); } + ~FillSupportBase() override = default; + Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + +protected: + // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. + float _layer_angle(size_t idx) const override { return 0.f; } +}; } // namespace Slic3r diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 9d98ea6ae..f6f4f5618 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -299,20 +299,23 @@ bool liang_barsky_line_clipping( // Ugly named variant, that accepts the squared line // Don't call me with a nearly zero length vector! +// sympy: +// factor(solve([a * x + b * y + c, x**2 + y**2 - r**2], [x, y])[0]) +// factor(solve([a * x + b * y + c, x**2 + y**2 - r**2], [x, y])[1]) template int ray_circle_intersections_r2_lv2_c(T r2, T a, T b, T lv2, T c, std::pair, Eigen::Matrix> &out) { - T x0 = - a * c / lv2; - T y0 = - b * c / lv2; - T d = r2 - c * c / lv2; - if (d < T(0)) + T x0 = - a * c; + T y0 = - b * c; + T d2 = r2 * lv2 - c * c; + if (d2 < T(0)) return 0; - T mult = sqrt(d / lv2); - out.first.x() = x0 + b * mult; - out.first.y() = y0 - a * mult; - out.second.x() = x0 - b * mult; - out.second.y() = y0 + a * mult; - return mult == T(0) ? 1 : 2; + T d = sqrt(d2); + out.first.x() = (x0 + b * d) / lv2; + out.first.y() = (y0 - a * d) / lv2; + out.second.x() = (x0 - b * d) / lv2; + out.second.y() = (y0 + a * d) / lv2; + return d == T(0) ? 1 : 2; } template int ray_circle_intersections(T r, T a, T b, T c, std::pair, Eigen::Matrix> &out) diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index d5bac7249..d17142bb2 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -64,6 +64,7 @@ public: bool has_duplicate_points() const; // Remove exact duplicates, return true if any duplicate has been removed. bool remove_duplicate_points(); + void clear() { this->points.clear(); } void append(const Point &point) { this->points.push_back(point); } void append(const Points &src) { this->append(src.begin(), src.end()); } void append(const Points::const_iterator &begin, const Points::const_iterator &end) { this->points.insert(this->points.end(), begin, end); } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 84b4a825d..f38a7066b 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -413,6 +413,25 @@ unscaled(const Eigen::Matrix &v) noexcept return v.template cast() * SCALING_FACTOR; } +// Align a coordinate to a grid. The coordinate may be negative, +// the aligned value will never be bigger than the original one. +inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) { + // Current C++ standard defines the result of integer division to be rounded to zero, + // for both positive and negative numbers. Here we want to round down for negative + // numbers as well. + coord_t aligned = (coord < 0) ? + ((coord - spacing + 1) / spacing) * spacing : + (coord / spacing) * spacing; + assert(aligned <= coord); + return aligned; +} +inline Point align_to_grid(Point coord, Point spacing) + { return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); } +inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) + { return base + align_to_grid(coord - base, spacing); } +inline Point align_to_grid(Point coord, Point spacing, Point base) + { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } + } // namespace Slic3r // start Boost diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index ff433a48f..ce5bf1b29 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -180,7 +180,6 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n } else if ( opt_key == "complete_objects" || opt_key == "filament_type" - || opt_key == "filament_soluble" || opt_key == "first_layer_temperature" || opt_key == "filament_loading_speed" || opt_key == "filament_loading_speed_start" @@ -213,6 +212,12 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "z_offset") { steps.emplace_back(psWipeTower); steps.emplace_back(psSkirt); + } else if (opt_key == "filament_soluble") { + steps.emplace_back(psWipeTower); + // Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers. + // Thus switching between soluble / non-soluble interface layer material may require recalculation of supports. + //FIXME Killing supports on any change of "filament_soluble" is rough. We should check for each object whether that is necessary. + osteps.emplace_back(posSupportMaterial); } else if ( opt_key == "first_layer_extrusion_width" || opt_key == "min_layer_height" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fd33f5807..08ab716d2 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1814,8 +1814,8 @@ void PrintConfigDef::init_fff_params() def->category = L("Support material"); def->tooltip = L("Density of the first raft or support layer."); def->sidetext = L("%"); - def->min = 0; - def->max = 150; + def->min = 10; + def->max = 100; def->mode = comExpert; def->set_default_value(new ConfigOptionPercent(90)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 2ee6bc061..7cfd515b0 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -51,7 +51,7 @@ enum class FuzzySkinType { enum InfillPattern : int { ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, - ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount, + ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipCount, }; enum class IroningType { diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index bfd879e52..2b1b5795c 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -1607,7 +1607,7 @@ static inline std::pairheight; bottom_z = (layer_id == 1) ? slicing_params.object_print_z_min : layer.lower_layer->lower_layer->print_z; } else { - print_z = layer.bottom_z() - slicing_params.gap_object_support; + print_z = layer.bottom_z() - slicing_params.gap_support_object; bottom_z = print_z; height = 0.; // Ignore this contact area if it's too low. @@ -3166,7 +3166,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); // Fill in the rest. fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); - if (no_sort) + if (no_sort && ! eec->empty()) dst.emplace_back(eec.release()); } } @@ -3174,8 +3174,13 @@ static inline void fill_expolygons_with_sheath_generate_paths( // Support layers, partially processed. struct MyLayerExtruded { - MyLayerExtruded() : layer(nullptr), m_polygons_to_extrude(nullptr) {} - ~MyLayerExtruded() { delete m_polygons_to_extrude; m_polygons_to_extrude = nullptr; } + MyLayerExtruded& operator=(MyLayerExtruded &&rhs) { + this->layer = rhs.layer; + this->extrusions = std::move(rhs.extrusions); + this->m_polygons_to_extrude = std::move(m_polygons_to_extrude); + rhs.layer = nullptr; + return *this; + } bool empty() const { return layer == nullptr || layer->polygons.empty(); @@ -3183,7 +3188,7 @@ struct MyLayerExtruded void set_polygons_to_extrude(Polygons &&polygons) { if (m_polygons_to_extrude == nullptr) - m_polygons_to_extrude = new Polygons(std::move(polygons)); + m_polygons_to_extrude = std::make_unique(std::move(polygons)); else *m_polygons_to_extrude = std::move(polygons); } @@ -3204,12 +3209,11 @@ struct MyLayerExtruded if (m_polygons_to_extrude == nullptr) { // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). assert(this->extrusions.empty()); - m_polygons_to_extrude = new Polygons(this->layer->polygons); + m_polygons_to_extrude = std::make_unique(this->layer->polygons); } Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude)); *m_polygons_to_extrude = union_(*m_polygons_to_extrude, true); - delete other.m_polygons_to_extrude; - other.m_polygons_to_extrude = nullptr; + other.m_polygons_to_extrude.reset(); } else if (m_polygons_to_extrude != nullptr) { assert(other.m_polygons_to_extrude == nullptr); // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet). @@ -3232,12 +3236,14 @@ struct MyLayerExtruded } // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). - PrintObjectSupportMaterial::MyLayer *layer; + PrintObjectSupportMaterial::MyLayer *layer { nullptr }; // Collect extrusions. They will be exported sorted by the bottom height. ExtrusionEntitiesPtr extrusions; + +private: // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support. // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills. - Polygons *m_polygons_to_extrude; + std::unique_ptr m_polygons_to_extrude; }; typedef std::vector MyLayerExtrudedPtrs; @@ -3763,7 +3769,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Prepare fillers. SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; bool with_sheath = m_object_config->support_material_with_sheath; - InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipRectilinear); + InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipSupportBase); std::vector angles; angles.push_back(base_angle); @@ -3900,7 +3906,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; }); } }; - std::vector layer_caches(support_layers.size(), LayerCache()); + std::vector layer_caches(support_layers.size()); const auto fill_type_interface = @@ -4152,6 +4158,27 @@ void PrintObjectSupportMaterial::generate_toolpaths( } } }); + +#ifndef NDEBUG + struct Test { + static bool verify_nonempty(const ExtrusionEntityCollection *collection) { + for (const ExtrusionEntity *ee : collection->entities) { + if (const ExtrusionPath *path = dynamic_cast(ee)) + assert(! path->empty()); + else if (const ExtrusionMultiPath *multipath = dynamic_cast(ee)) + assert(! multipath->empty()); + else if (const ExtrusionEntityCollection *eecol = dynamic_cast(ee)) { + assert(! eecol->empty()); + return verify_nonempty(eecol); + } else + assert(false); + } + return true; + } + }; + for (const SupportLayer *support_layer : support_layers) + assert(Test::verify_nonempty(&support_layer->support_fills)); +#endif // NDEBUG } /*