From ff7eb25c54ac1801d00ccd3371a295afa87365fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 16 Jul 2024 15:26:29 +0200 Subject: [PATCH] NEW: port Zig Zag infill from Prusa Thanks to Prusa! jira: none original commit message: ceb13b1faa33ac096fe7ffd89aa222abca119e02 SPE-2405: Add Zig Zag infill that is rectilinear infill but with a consistent pattern between layers. This Zig Zag infill is inspired by the Zig Zag infill in Cura. Change-Id: I798affa99f4b5c3bd67f47643e67530fb7c3e0cb (cherry picked from commit 2808d04d5deef6f99f9618648e46f11de03efc98) --- resources/images/param_zigzag.svg | 158 ++++++++++++++++++ src/libslic3r/BoundingBox.cpp | 7 + src/libslic3r/BoundingBox.hpp | 4 +- src/libslic3r/Fill/Fill.cpp | 35 ++-- src/libslic3r/Fill/Fill3DHoneycomb.hpp | 4 + src/libslic3r/Fill/FillAdaptive.hpp | 1 + src/libslic3r/Fill/FillBase.cpp | 57 +++---- src/libslic3r/Fill/FillBase.hpp | 11 +- src/libslic3r/Fill/FillConcentric.hpp | 1 + src/libslic3r/Fill/FillConcentricInternal.hpp | 1 + src/libslic3r/Fill/FillCrossHatch.hpp | 1 + src/libslic3r/Fill/FillGyroid.hpp | 1 + src/libslic3r/Fill/FillHoneycomb.hpp | 1 + src/libslic3r/Fill/FillLightning.hpp | 1 + src/libslic3r/Fill/FillLine.hpp | 1 + src/libslic3r/Fill/FillPlanePath.hpp | 1 + src/libslic3r/Fill/FillRectilinear.cpp | 74 ++++---- src/libslic3r/Fill/FillRectilinear.hpp | 24 ++- src/libslic3r/PrintConfig.cpp | 5 +- src/libslic3r/PrintConfig.hpp | 2 +- 20 files changed, 307 insertions(+), 83 deletions(-) create mode 100644 resources/images/param_zigzag.svg diff --git a/resources/images/param_zigzag.svg b/resources/images/param_zigzag.svg new file mode 100644 index 000000000..4294e7af7 --- /dev/null +++ b/resources/images/param_zigzag.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index 15e9145e8..16ea80b2a 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -53,6 +53,13 @@ BoundingBox BoundingBox::rotated(double angle, const Point ¢er) const return out; } +BoundingBox BoundingBox::scaled(double factor) const +{ + BoundingBox out(*this); + out.scale(factor); + return out; +} + template void BoundingBoxBase::scale(double factor) { diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 22767622f..fa0045a08 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -211,7 +211,9 @@ public: BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {} BoundingBox(const Points &points) : BoundingBoxBase(points) {} - BoundingBox inflated(coordf_t delta) const throw() { BoundingBox out(*this); out.offset(delta); return out; } + BoundingBox inflated(coordf_t delta) const noexcept { BoundingBox out(*this); out.offset(delta); return out; } + + BoundingBox scaled(double factor) const; friend BoundingBox get_extents_rotated(const Points &points, double angle); }; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 2532c4896..2a2dad1af 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -543,23 +543,24 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc //case ipEnsuring: continue; break; case ipLightning: case ipAdaptiveCubic: - case ipSupportCubic: - case ipRectilinear: - case ipMonotonic: - case ipAlignedRectilinear: - case ipGrid: - case ipTriangles: - case ipStars: - case ipCubic: - case ipLine: - case ipConcentric: - case ipHoneycomb: - case ip3DHoneycomb: - case ipGyroid: - case ipHilbertCurve: - case ipArchimedeanChords: - case ipOctagramSpiral: break; - } + case ipSupportCubic: + case ipRectilinear: + case ipMonotonic: + case ipAlignedRectilinear: + case ipGrid: + case ipTriangles: + case ipStars: + case ipCubic: + case ipLine: + case ipConcentric: + case ipHoneycomb: + case ip3DHoneycomb: + case ipGyroid: + case ipHilbertCurve: + case ipArchimedeanChords: + case ipOctagramSpiral: + case ipZigZag: break; + } // Create the filler object. std::unique_ptr f = std::unique_ptr(Fill::new_from_type(surface_fill.params.pattern)); diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.hpp b/src/libslic3r/Fill/Fill3DHoneycomb.hpp index f7a27cf4f..ccb37a36a 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.hpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.hpp @@ -15,6 +15,10 @@ public: Fill* clone() const override { return new Fill3DHoneycomb(*this); }; ~Fill3DHoneycomb() override {} + // require bridge flow since most of this pattern hangs in air + bool use_bridge_flow() const override { return true; } + bool is_self_crossing() override { return false; } + protected: void _fill_surface_single( const FillParams ¶ms, diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 0578cc3e1..0132ffaa6 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -71,6 +71,7 @@ protected: // may not be optimal as the internal infill lines may get extruded before the long infill // lines to which the short infill lines are supposed to anchor. bool no_sort() const override { return false; } + bool is_self_crossing() override { return true; } }; } // namespace FillAdaptive diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 27b73a31c..f37a857de 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -56,6 +56,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipConcentricInternal: return new FillConcentricInternal(); // BBS: for bottom and top surface only case ipMonotonicLine: return new FillMonotonicLineWGapFill(); + case ipZigZag: return new FillZigZag(); default: throw Slic3r::InvalidArgument("unknown type"); } } @@ -167,7 +168,7 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para // Calculate a new spacing to fill width with possibly integer number of lines, // the first and last line being centered at the interval ends. -// This function possibly increases the spacing, never decreases, +// This function possibly increases the spacing, never decreases, // and for a narrow width the increase in spacing may become severe, // therefore the adjustment is limited to 20% increase. coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance) @@ -176,8 +177,8 @@ coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance) assert(distance > 0); // floor(width / distance) const auto number_of_intervals = coord_t((width - EPSILON) / distance); - coord_t distance_new = (number_of_intervals == 0) ? - distance : + coord_t distance_new = (number_of_intervals == 0) ? + distance : coord_t((width - EPSILON) / number_of_intervals); const coordf_t factor = coordf_t(distance_new) / coordf_t(distance); assert(factor > 1. - 1e-5); @@ -203,8 +204,8 @@ std::pair Fill::_infill_direction(const Surface *surface) const // Bounding box is the bounding box of a perl object Slic3r::Print::Object (c++ object Slic3r::PrintObject) // The bounding box is only undefined in unit tests. - Point out_shift = empty(this->bounding_box) ? - surface->expolygon.contour.bounding_box().center() : + Point out_shift = empty(this->bounding_box) ? + surface->expolygon.contour.bounding_box().center() : this->bounding_box.center(); #if 0 @@ -278,10 +279,10 @@ struct ContourIntersectionPoint { bool could_take_next() const throw() { return ! this->consumed && this->contour_not_taken_length_next > SCALED_EPSILON; } // Could extrude a complete segment from this to this->prev_on_contour. - bool could_connect_prev() const throw() + bool could_connect_prev() const throw() { return ! this->consumed && this->prev_on_contour != this && ! this->prev_on_contour->consumed && ! this->prev_trimmed && ! this->prev_on_contour->next_trimmed; } // Could extrude a complete segment from this to this->next_on_contour. - bool could_connect_next() const throw() + bool could_connect_next() const throw() { return ! this->consumed && this->next_on_contour != this && ! this->next_on_contour->consumed && ! this->next_trimmed && ! this->next_on_contour->prev_trimmed; } }; @@ -490,7 +491,7 @@ static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, Cont } static void take_limited( - Polyline &pl1, const Points &contour, const std::vector ¶ms, + Polyline &pl1, const Points &contour, const std::vector ¶ms, ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise, double take_max_length, double line_half_width) { #ifndef NDEBUG @@ -654,8 +655,8 @@ static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, do // Calculate intersection of a line with a thick segment. // Returns Eucledian parameters of the line / thick segment overlap. static inline bool line_rounded_thick_segment_collision( - const Vec2d &line_a, const Vec2d &line_b, - const Vec2d &segment_a, const Vec2d &segment_b, const double offset, + const Vec2d &line_a, const Vec2d &line_b, + const Vec2d &segment_a, const Vec2d &segment_b, const double offset, std::pair &out_interval) { const Vec2d line_v0 = line_b - line_a; @@ -718,8 +719,8 @@ static inline bool line_rounded_thick_segment_collision( std::pair interval; if (Geometry::liang_barsky_line_clipping_interval( Vec2d(line_p0.dot(dir_x), line_p0.dot(dir_y)), - Vec2d(line_v0.dot(dir_x), line_v0.dot(dir_y)), - BoundingBoxf(Vec2d(0., - offset), Vec2d(segment_l, offset)), + Vec2d(line_v0.dot(dir_x), line_v0.dot(dir_y)), + BoundingBoxf(Vec2d(0., - offset), Vec2d(segment_l, offset)), interval)) extend_interval(interval.first, interval.second); } else @@ -1079,7 +1080,7 @@ void mark_boundary_segments_touching_infill( // Clip the infill polyline by the Eucledian distance along the polyline. SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance); SegmentPoint end_point = clip_end_segment_and_point(polyline.points, clip_distance); - if (start_point.valid() && end_point.valid() && + if (start_point.valid() && end_point.valid() && (start_point.idx_segment < end_point.idx_segment || (start_point.idx_segment == end_point.idx_segment && start_point.t < end_point.t))) { // The clipped polyline is non-empty. #ifdef INFILL_DEBUG_OUTPUT @@ -1219,21 +1220,21 @@ struct BoundaryInfillGraph }; static Direction dir(const Point &p1, const Point &p2) { - return p1.x() == p2.x() ? + 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() ? + 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() ? + return cp.could_take_next() ? dir(this->point(cp), this->point(*cp.next_on_contour)) : Taken; } @@ -1291,7 +1292,7 @@ static inline void mark_boundary_segments_overlapping_infill( 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, + // 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; @@ -1323,7 +1324,7 @@ static inline void mark_boundary_segments_overlapping_infill( 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, + // 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; @@ -1420,7 +1421,7 @@ BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered ContourIntersectionPoint *pthis = &out.map_infill_end_point_to_boundary[it->second]; if (pprev) { pprev->next_on_contour = pthis; - pthis->prev_on_contour = pprev; + pthis->prev_on_contour = pprev; } else pfirst = pthis; contour_intersection_points.emplace_back(pthis); @@ -1445,7 +1446,7 @@ BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered ip->param = contour_params[ip->point_idx]; // and measure distance to the previous and next intersection point. const double contour_length = contour_params.back(); - for (ContourIntersectionPoint *ip : contour_intersection_points) + for (ContourIntersectionPoint *ip : contour_intersection_points) if (ip->next_on_contour == ip) { assert(ip->prev_on_contour == ip); ip->contour_not_taken_length_prev = ip->contour_not_taken_length_next = contour_length; @@ -1839,14 +1840,14 @@ static inline void base_support_extend_infill_lines(Polylines &infill, BoundaryI // 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 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, + 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) @@ -1902,13 +1903,13 @@ static inline void emit_loops_in_band( }; enum InOutBand { - Entering, + Entering, Leaving, }; class State { public: - State(coord_t left, coord_t right, double min_length, Polylines &polylines_out) : + 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) @@ -2209,7 +2210,7 @@ void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector SCALED_EPSILON && + if (cp.contour_not_taken_length_prev > SCALED_EPSILON && (cost_prev.self_loop ? cost_prev.cost > cap_cost : cost_prev.cost > cost_veryhigh)) { @@ -2555,7 +2556,7 @@ void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector SCALED_EPSILON && + if (cp.contour_not_taken_length_next > SCALED_EPSILON && (cost_next.self_loop ? cost_next.cost > cap_cost : cost_next.cost > cost_veryhigh)) { diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 5672d55e4..4fbe5c367 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -123,6 +123,11 @@ public: // Do not sort the fill lines to optimize the print head path? virtual bool no_sort() const { return false; } + virtual bool is_self_crossing() = 0; + + // Return true if infill has a consistent pattern between layers. + virtual bool has_consistent_pattern() const { return false; } + // Perform the fill. virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); virtual ThickPolylines fill_surface_arachne(const Surface* surface, const FillParams& params); @@ -148,11 +153,11 @@ protected: // The expolygon may be modified by the method to avoid a copy. virtual void _fill_surface_single( - const FillParams & /* params */, + const FillParams & /* params */, unsigned int /* thickness_layers */, - const std::pair & /* direction */, + const std::pair & /* direction */, ExPolygon /* expolygon */, - Polylines & /* polylines_out */) {}; + Polylines & /* polylines_out */) {} // Used for concentric infill to generate ThickPolylines using Arachne. virtual void _fill_surface_single(const FillParams& params, diff --git a/src/libslic3r/Fill/FillConcentric.hpp b/src/libslic3r/Fill/FillConcentric.hpp index 2e63dcdc8..cdbd15204 100644 --- a/src/libslic3r/Fill/FillConcentric.hpp +++ b/src/libslic3r/Fill/FillConcentric.hpp @@ -9,6 +9,7 @@ class FillConcentric : public Fill { public: ~FillConcentric() override = default; + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillConcentric(*this); }; diff --git a/src/libslic3r/Fill/FillConcentricInternal.hpp b/src/libslic3r/Fill/FillConcentricInternal.hpp index 9c8442e25..5d38b6608 100644 --- a/src/libslic3r/Fill/FillConcentricInternal.hpp +++ b/src/libslic3r/Fill/FillConcentricInternal.hpp @@ -10,6 +10,7 @@ class FillConcentricInternal : public Fill public: ~FillConcentricInternal() override = default; void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillConcentricInternal(*this); }; diff --git a/src/libslic3r/Fill/FillCrossHatch.hpp b/src/libslic3r/Fill/FillCrossHatch.hpp index 859d5bd8b..34c46116e 100644 --- a/src/libslic3r/Fill/FillCrossHatch.hpp +++ b/src/libslic3r/Fill/FillCrossHatch.hpp @@ -14,6 +14,7 @@ class FillCrossHatch : public Fill public: Fill *clone() const override { return new FillCrossHatch(*this); }; ~FillCrossHatch() override {} + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillGyroid.hpp b/src/libslic3r/Fill/FillGyroid.hpp index ac66dfca9..751a7c257 100644 --- a/src/libslic3r/Fill/FillGyroid.hpp +++ b/src/libslic3r/Fill/FillGyroid.hpp @@ -15,6 +15,7 @@ public: // require bridge flow since most of this pattern hangs in air bool use_bridge_flow() const override { return false; } + bool is_self_crossing() override { return false; } // Correction applied to regular infill angle to maximize printing // speed in default configuration (degrees) diff --git a/src/libslic3r/Fill/FillHoneycomb.hpp b/src/libslic3r/Fill/FillHoneycomb.hpp index 707e976fd..55be42385 100644 --- a/src/libslic3r/Fill/FillHoneycomb.hpp +++ b/src/libslic3r/Fill/FillHoneycomb.hpp @@ -13,6 +13,7 @@ class FillHoneycomb : public Fill { public: ~FillHoneycomb() override {} + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillHoneycomb(*this); }; diff --git a/src/libslic3r/Fill/FillLightning.hpp b/src/libslic3r/Fill/FillLightning.hpp index 6e672783a..74aa159b0 100644 --- a/src/libslic3r/Fill/FillLightning.hpp +++ b/src/libslic3r/Fill/FillLightning.hpp @@ -20,6 +20,7 @@ class Filler : public Slic3r::Fill { public: ~Filler() override = default; + bool is_self_crossing() override { return false; } Generator *generator { nullptr }; protected: diff --git a/src/libslic3r/Fill/FillLine.hpp b/src/libslic3r/Fill/FillLine.hpp index 9bf2b97e0..4ad9ca49a 100644 --- a/src/libslic3r/Fill/FillLine.hpp +++ b/src/libslic3r/Fill/FillLine.hpp @@ -14,6 +14,7 @@ class FillLine : public Fill public: Fill* clone() const override { return new FillLine(*this); }; ~FillLine() override = default; + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillPlanePath.hpp b/src/libslic3r/Fill/FillPlanePath.hpp index f05f3209a..1371e8506 100644 --- a/src/libslic3r/Fill/FillPlanePath.hpp +++ b/src/libslic3r/Fill/FillPlanePath.hpp @@ -37,6 +37,7 @@ class FillPlanePath : public Fill { public: ~FillPlanePath() override = default; + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 5d5eb0add..6640501a1 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -1351,8 +1351,11 @@ static SegmentIntersection& end_of_vertical_run(SegmentedIntersectionLine &il, S return const_cast(end_of_vertical_run(std::as_const(il), std::as_const(start))); } -static void traverse_graph_generate_polylines( - const ExPolygonWithOffset& poly_with_offset, const FillParams& params, const coord_t link_max_length, std::vector& segs, Polylines& polylines_out) +static void traverse_graph_generate_polylines(const ExPolygonWithOffset &poly_with_offset, + const FillParams ¶ms, + std::vector &segs, + const bool consistent_pattern, + Polylines &polylines_out) { // For each outer only chords, measure their maximum distance to the bow of the outer contour. // Mark an outer only chord as consumed, if the distance is low. @@ -1386,34 +1389,28 @@ static void traverse_graph_generate_polylines( pointLast = polylines_out.back().points.back(); for (;;) { if (i_intersection == -1) { - // The path has been interrupted. Find a next starting point, closest to the previous extruder position. - coordf_t dist2min = std::numeric_limits().max(); - for (int i_vline2 = 0; i_vline2 < int(segs.size()); ++ i_vline2) { + // The path has been interrupted. Find a next starting point. + for (int i_vline2 = 0; i_vline2 < int(segs.size()); ++i_vline2) { const SegmentedIntersectionLine &vline = segs[i_vline2]; - if (! vline.intersections.empty()) { + if (!vline.intersections.empty()) { assert(vline.intersections.size() > 1); // Even number of intersections with the loops. assert((vline.intersections.size() & 1) == 0); assert(vline.intersections.front().type == SegmentIntersection::OUTER_LOW); - for (int i = 0; i < int(vline.intersections.size()); ++ i) { - const SegmentIntersection& intrsctn = vline.intersections[i]; + + // For infill that needs to be consistent between layers (like Zig Zag), + // we are switching between forward and backward passes based on the line index. + const bool forward_pass = !consistent_pattern || (i_vline2 % 2 == 0); + for (int i = 0; i < int(vline.intersections.size()); ++i) { + const int intrsctn_idx = forward_pass ? i : int(vline.intersections.size()) - i - 1; + const SegmentIntersection &intrsctn = vline.intersections[intrsctn_idx]; if (intrsctn.is_outer()) { - assert(intrsctn.is_low() || i > 0); - bool consumed = intrsctn.is_low() ? - intrsctn.consumed_vertical_up : - vline.intersections[i - 1].consumed_vertical_up; - if (! consumed) { - coordf_t dist2 = sqr(coordf_t(pointLast(0) - vline.pos)) + sqr(coordf_t(pointLast(1) - intrsctn.pos())); - if (dist2 < dist2min) { - dist2min = dist2; - i_vline = i_vline2; - i_intersection = i; - //FIXME We are taking the first left point always. Verify, that the caller chains the paths - // by a shortest distance, while reversing the paths if needed. - //if (polylines_out.empty()) - // Initial state, take the first line, which is the first from the left. - goto found; - } + assert(intrsctn.is_low() || intrsctn_idx > 0); + const bool consumed = intrsctn.is_low() ? intrsctn.consumed_vertical_up : vline.intersections[intrsctn_idx - 1].consumed_vertical_up; + if (!consumed) { + i_vline = i_vline2; + i_intersection = intrsctn_idx; + goto found; } } } @@ -1486,9 +1483,13 @@ static void traverse_graph_generate_polylines( // 1) Find possible connection points on the previous / next vertical line. int i_prev = it->left_horizontal(); int i_next = it->right_horizontal(); - bool intersection_prev_valid = intersection_on_prev_vertical_line_valid(segs, i_vline, i_intersection); + + // To ensure pattern consistency between layers for Zig Zag infill, we always + // try to connect to the next vertical line and never to the previous vertical line. + bool intersection_prev_valid = intersection_on_prev_vertical_line_valid(segs, i_vline, i_intersection) && !consistent_pattern; bool intersection_next_valid = intersection_on_next_vertical_line_valid(segs, i_vline, i_intersection); bool intersection_horizontal_valid = intersection_prev_valid || intersection_next_valid; + // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. if (i_prev != -1) segs[i_vline - 1].intersections[i_prev].consumed_perimeter_right = true; @@ -2736,6 +2737,17 @@ static void polylines_from_paths(const std::vector &path, c } } +// The extended bounding box of the whole object that covers any rotation of every layer. +BoundingBox FillRectilinear::extended_object_bounding_box() const { + BoundingBox out = this->bounding_box; + out.merge(Point(out.min.y(), out.min.x())); + out.merge(Point(out.max.y(), out.max.x())); + + // The bounding box is scaled by sqrt(2.) to ensure that the bounding box + // covers any possible rotations. + return out.scaled(sqrt(2.)); +} + bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out) { // At the end, only the new polylines will be rotated back. @@ -2765,11 +2777,14 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa return true; } - BoundingBox bounding_box = poly_with_offset.bounding_box_src(); + // For infill that needs to be consistent between layers (like Zig Zag), + // we use bounding box of whole object to match vertical lines between layers. + BoundingBox bounding_box_src = poly_with_offset.bounding_box_src(); + BoundingBox bounding_box = this->has_consistent_pattern() ? this->extended_object_bounding_box() : bounding_box_src; // define flow spacing according to requested density if (params.full_infill() && !params.dont_adjust) { - line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), line_spacing); + line_spacing = this->_adjust_solid_spacing(bounding_box_src.size().x(), line_spacing); this->spacing = unscale(line_spacing); } else { // extend bounding box so that our pattern will be aligned with other layers @@ -2847,8 +2862,9 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa std::vector path = chain_monotonic_regions(regions, poly_with_offset, segs, rng); polylines_from_paths(path, poly_with_offset, segs, polylines_out); } - } else - traverse_graph_generate_polylines(poly_with_offset, params, this->link_max_length, segs, polylines_out); + } else { + traverse_graph_generate_polylines(poly_with_offset, params, segs, this->has_consistent_pattern(), polylines_out); + } #ifdef SLIC3R_DEBUG { diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index cb9d74845..1655d3119 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -15,6 +15,7 @@ public: Fill* clone() const override { return new FillRectilinear(*this); } ~FillRectilinear() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return false; } protected: // Fill by single directional lines, interconnect the lines along perimeters. @@ -27,6 +28,9 @@ protected: float pattern_shift; }; bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out); + + // The extended bounding box of the whole object that covers any rotation of every layer. + BoundingBox extended_object_bounding_box() const; }; class FillAlignedRectilinear : public FillRectilinear @@ -64,6 +68,7 @@ public: Fill* clone() const override { return new FillGrid(*this); } ~FillGrid() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -76,6 +81,7 @@ public: Fill* clone() const override { return new FillTriangles(*this); } ~FillTriangles() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -88,6 +94,7 @@ public: Fill* clone() const override { return new FillStars(*this); } ~FillStars() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -100,6 +107,7 @@ public: Fill* clone() const override { return new FillCubic(*this); } ~FillCubic() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -123,6 +131,7 @@ class FillMonotonicLineWGapFill : public Fill public: ~FillMonotonicLineWGapFill() override = default; void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillMonotonicLineWGapFill(*this); }; @@ -132,9 +141,18 @@ private: void fill_surface_by_lines(const Surface* surface, const FillParams& params, Polylines& polylines_out); }; -Points sample_grid_pattern(const ExPolygon& expolygon, coord_t spacing, const BoundingBox& global_bounding_box); -Points sample_grid_pattern(const ExPolygons& expolygons, coord_t spacing, const BoundingBox& global_bounding_box); -Points sample_grid_pattern(const Polygons& polygons, coord_t spacing, const BoundingBox& global_bounding_box); +class FillZigZag : public FillRectilinear +{ +public: + Fill* clone() const override { return new FillZigZag(*this); } + ~FillZigZag() override = default; + + bool has_consistent_pattern() const override { return true; } +}; + +Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing, const BoundingBox &global_bounding_box); +Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing, const BoundingBox &global_bounding_box); +Points sample_grid_pattern(const Polygons &polygons, coord_t spacing, const BoundingBox &global_bounding_box); } // namespace Slic3r diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ffaf975e2..2e17d5a23 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -148,7 +148,8 @@ static t_config_enum_values s_keys_map_InfillPattern { { "octagramspiral", ipOctagramSpiral }, { "supportcubic", ipSupportCubic }, { "lightning", ipLightning }, - { "crosshatch", ipCrossHatch} + { "crosshatch", ipCrossHatch}, + { "zigzag", ipZigZag } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) @@ -1963,6 +1964,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("supportcubic"); def->enum_values.push_back("lightning"); def->enum_values.push_back("crosshatch"); + def->enum_values.push_back("zigzag"); def->enum_labels.push_back(L("Concentric")); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Grid")); @@ -1981,6 +1983,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Support Cubic")); def->enum_labels.push_back(L("Lightning")); def->enum_labels.push_back(L("Cross Hatch")); + def->enum_labels.push_back(L("Zig Zag")); def->set_default_value(new ConfigOptionEnum(ipCubic)); def = this->add("top_surface_acceleration", coFloats); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index ced3901d6..ee807bd2a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -54,7 +54,7 @@ enum AuthorizationType { enum InfillPattern : int { ipConcentric, ipRectilinear, ipGrid, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricInternal, - ipLightning, ipCrossHatch, + ipLightning, ipCrossHatch, ipZigZag, ipCount, };