diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index f005f40036..644e3bd6fb 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -59,6 +59,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 4978f9b2ad..dd3b604094 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -220,7 +220,9 @@ public: BoundingBox(const BoundingBoxBase &bb): BoundingBox(bb.min, bb.max) {} 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 14d7197acb..b49ee57a0b 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -679,7 +679,8 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc case ipGyroid: case ipHilbertCurve: case ipArchimedeanChords: - case ipOctagramSpiral: break; + case ipOctagramSpiral: + case ipZigZag: break; } // Create the filler object. diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 0575e27e48..065b2f98b7 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -67,6 +67,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipSupportBase: return new FillSupportBase(); case ipLightning: return new FillLightning::Filler(); case ipEnsuring: return new FillEnsuring(); + case ipZigZag: return new FillZigZag(); default: throw Slic3r::InvalidArgument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index ed9cc31b85..5e05e5f048 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -138,6 +138,9 @@ public: 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 ¶ms); diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 37244c7ceb..23885fbd02 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -1363,8 +1363,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. @@ -1398,34 +1401,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; } } } @@ -1498,9 +1495,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; @@ -2748,6 +2749,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. @@ -2777,11 +2789,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 @@ -2859,8 +2874,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 1bca772b2a..b756fe94e8 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -48,6 +48,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 @@ -143,6 +146,15 @@ protected: float _layer_angle(size_t idx) const override { return 0.f; } }; +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); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 6c16e919bf..1f7bab5885 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -146,7 +146,8 @@ static const t_config_enum_values s_keys_map_InfillPattern { { "octagramspiral", ipOctagramSpiral }, { "adaptivecubic", ipAdaptiveCubic }, { "supportcubic", ipSupportCubic }, - { "lightning", ipLightning } + { "lightning", ipLightning }, + { "zigzag", ipZigZag } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) @@ -1482,7 +1483,8 @@ void PrintConfigDef::init_fff_params() { "octagramspiral", L("Octagram Spiral")}, { "adaptivecubic", L("Adaptive Cubic")}, { "supportcubic", L("Support Cubic")}, - { "lightning", L("Lightning")} + { "lightning", L("Lightning")}, + { "zigzag", L("Zig Zag")} }); def->set_default_value(new ConfigOptionEnum(ipStars)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index fb89debbd6..a9d5c5f403 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -106,6 +106,7 @@ enum InfillPattern : int { ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipLightning, ipEnsuring, + ipZigZag, ipCount, };