diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index d03cb5a70..650cfca15 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -710,6 +710,8 @@ Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygon { return _clipper_pl_closed(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip) { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::SinglePathProvider(clip.points)); } +Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip) + { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip) { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip)); } Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 0388af9a3..50d96142d 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -437,6 +437,7 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip); +Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygons &clip); diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index 49b02b542..183d4bf96 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -6,6 +6,66 @@ namespace Slic3r { +class InfillPolylineClipper : public FillPlanePath::InfillPolylineOutput { +public: + InfillPolylineClipper(const BoundingBox bbox, const double scale_out) : FillPlanePath::InfillPolylineOutput(scale_out), m_bbox(bbox) {} + + void add_point(const Vec2d &pt); + Points&& result() { return std::move(m_out); } + bool clips() const override { return true; } + +private: + enum class Side { + Left = 1, + Right = 2, + Top = 4, + Bottom = 8 + }; + + int sides(const Point &p) const { + return int(p.x() < m_bbox.min.x()) * int(Side::Left) + + int(p.x() > m_bbox.max.x()) * int(Side::Right) + + int(p.y() < m_bbox.min.y()) * int(Side::Bottom) + + int(p.y() > m_bbox.max.y()) * int(Side::Top); + }; + + // Bounding box to clip the polyline with. + BoundingBox m_bbox; + + // Classification of the two last points processed. + int m_sides_prev; + int m_sides_this; +}; + +void InfillPolylineClipper::add_point(const Vec2d &fpt) +{ + const Point pt{ this->scaled(fpt) }; + + if (m_out.size() < 2) { + // Collect the two first points and their status. + (m_out.empty() ? m_sides_prev : m_sides_this) = sides(pt); + m_out.emplace_back(pt); + } else { + // Classify the last inserted point, possibly remove it. + int sides_next = sides(pt); + if (// This point is inside. Take it. + m_sides_this == 0 || + // Either this point is outside and previous or next is inside, or + // the edge possibly cuts corner of the bounding box. + (m_sides_prev & m_sides_this & sides_next) == 0) { + // Keep the last point. + m_sides_prev = m_sides_this; + } else { + // All the three points (this, prev, next) are outside at the same side. + // Ignore the last point. + m_out.pop_back(); + } + // And save the current point. + m_out.emplace_back(pt); + m_sides_this = sides_next; + } +} + void FillPlanePath::_fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, @@ -13,37 +73,52 @@ void FillPlanePath::_fill_surface_single( ExPolygon expolygon, Polylines &polylines_out) { - expolygon.rotate(- direction.first); + expolygon.rotate(-direction.first); - coord_t distance_between_lines = coord_t(scale_(this->spacing) / params.density); - - // align infill across layers using the object's bounding box - // Rotated bounding box of the whole object. - BoundingBox bounding_box = this->bounding_box.rotated(- direction.first); - - Point shift = this->_centered() ? + //FIXME Vojtech: We are not sure whether the user expects the fill patterns on visible surfaces to be aligned across all the islands of a single layer. + // One may align for this->centered() to align the patterns for Archimedean Chords and Octagram Spiral patterns. + const bool align = params.density < 0.995; + + BoundingBox snug_bounding_box = get_extents(expolygon).inflated(SCALED_EPSILON); + + // Rotated bounding box of the area to fill in with the pattern. + BoundingBox bounding_box = align ? + // Sparse infill needs to be aligned across layers. Align infill across layers using the object's bounding box. + this->bounding_box.rotated(-direction.first) : + // Solid infill does not need to be aligned across layers, generate the infill pattern + // around the clipping expolygon only. + snug_bounding_box; + + Point shift = this->centered() ? bounding_box.center() : bounding_box.min; expolygon.translate(-shift.x(), -shift.y()); bounding_box.translate(-shift.x(), -shift.y()); - Pointfs pts = _generate( - coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines)), - coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines)), - coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)), - coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines)), - params.resolution); + Polyline polyline; + { + auto distance_between_lines = scaled(this->spacing) / params.density; + auto min_x = coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines)); + auto min_y = coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines)); + auto max_x = coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)); + auto max_y = coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines)); + auto resolution = scaled(params.resolution) / distance_between_lines; + if (align) { + // Filling in a bounding box over the whole object, clip generated polyline against the snug bounding box. + snug_bounding_box.translate(-shift.x(), -shift.y()); + InfillPolylineClipper output(snug_bounding_box, distance_between_lines); + this->generate(min_x, min_y, max_x, max_y, resolution, output); + polyline.points = std::move(output.result()); + } else { + // Filling in a snug bounding box, no need to clip. + InfillPolylineOutput output(distance_between_lines); + this->generate(min_x, min_y, max_x, max_y, resolution, output); + polyline.points = std::move(output.result()); + } + } - if (pts.size() >= 2) { - // Convert points to a polyline, upscale. - Polylines polylines(1, Polyline()); - Polyline &polyline = polylines.front(); - polyline.points.reserve(pts.size()); - for (const Vec2d &pt : pts) - polyline.points.emplace_back( - coord_t(floor(pt.x() * distance_between_lines + 0.5)), - coord_t(floor(pt.y() * distance_between_lines + 0.5))); - polylines = intersection_pl(polylines, expolygon); + if (polyline.size() >= 2) { + Polylines polylines = intersection_pl(polyline, expolygon); Polylines chained; if (params.dont_connect() || params.density > 0.5 || polylines.size() <= 1) chained = chain_polylines(std::move(polylines)); @@ -59,7 +134,8 @@ void FillPlanePath::_fill_surface_single( } // Follow an Archimedean spiral, in polar coordinates: r=a+b\theta -Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) +template +static void generate_archimedean_chords(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, Output &output) { // Radius to achieve. coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; @@ -70,15 +146,22 @@ Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t m coordf_t r = 1; Pointfs out; //FIXME Vojtech: If used as a solid infill, there is a gap left at the center. - out.emplace_back(0, 0); - out.emplace_back(1, 0); + output.add_point({ 0, 0 }); + output.add_point({ 1, 0 }); while (r < rmax) { // Discretization angle to achieve a discretization error lower than resolution. theta += 2. * acos(1. - resolution / r); r = a + b * theta; - out.emplace_back(r * cos(theta), r * sin(theta)); + output.add_point({ r * cos(theta), r * sin(theta) }); } - return out; +} + +void FillArchimedeanChords::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) +{ + if (output.clips()) + generate_archimedean_chords(min_x, min_y, max_x, max_y, resolution, static_cast(output)); + else + generate_archimedean_chords(min_x, min_y, max_x, max_y, resolution, output); } // Adapted from @@ -126,7 +209,8 @@ static inline Point hilbert_n_to_xy(const size_t n) return Point(x, y); } -Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */) +template +static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output) { // Minimum power of two square to fit the domain. size_t sz = 2; @@ -140,46 +224,59 @@ Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, } size_t sz2 = sz * sz; - Pointfs line; - line.reserve(sz2); + output.reserve(sz2); for (size_t i = 0; i < sz2; ++ i) { Point p = hilbert_n_to_xy(i); - line.emplace_back(p.x() + min_x, p.y() + min_y); + output.add_point({ p.x() + min_x, p.y() + min_y }); } - return line; } -Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */) +void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output) +{ + if (output.clips()) + generate_hilbert_curve(min_x, min_y, max_x, max_y, static_cast(output)); + else + generate_hilbert_curve(min_x, min_y, max_x, max_y, output); +} + +template +static void generate_octagram_spiral(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, Output &output) { // Radius to achieve. coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5; // Now unwind the spiral. coordf_t r = 0; coordf_t r_inc = sqrt(2.); - Pointfs out; - out.emplace_back(0., 0.); + output.add_point({ 0., 0. }); while (r < rmax) { r += r_inc; coordf_t rx = r / sqrt(2.); coordf_t r2 = r + rx; - out.emplace_back( r, 0.); - out.emplace_back( r2, rx); - out.emplace_back( rx, rx); - out.emplace_back( rx, r2); - out.emplace_back( 0., r); - out.emplace_back(-rx, r2); - out.emplace_back(-rx, rx); - out.emplace_back(-r2, rx); - out.emplace_back(- r, 0.); - out.emplace_back(-r2, -rx); - out.emplace_back(-rx, -rx); - out.emplace_back(-rx, -r2); - out.emplace_back( 0., -r); - out.emplace_back( rx, -r2); - out.emplace_back( rx, -rx); - out.emplace_back( r2+r_inc, -rx); + output.add_point({ r, 0. }); + output.add_point({ r2, rx }); + output.add_point({ rx, rx }); + output.add_point({ rx, r2 }); + output.add_point({ 0., r }); + output.add_point({-rx, r2 }); + output.add_point({-rx, rx }); + output.add_point({-r2, rx }); + output.add_point({- r, 0. }); + output.add_point({-r2, -rx }); + output.add_point({-rx, -rx }); + output.add_point({-rx, -r2 }); + output.add_point({ 0., -r }); + output.add_point({ rx, -r2 }); + output.add_point({ rx, -rx }); + output.add_point({ r2+r_inc, -rx }); } - return out; +} + +void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output) +{ + if (output.clips()) + generate_octagram_spiral(min_x, min_y, max_x, max_y, static_cast(output)); + else + generate_octagram_spiral(min_x, min_y, max_x, max_y, output); } } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillPlanePath.hpp b/src/libslic3r/Fill/FillPlanePath.hpp index 075f17433..4c6539f96 100644 --- a/src/libslic3r/Fill/FillPlanePath.hpp +++ b/src/libslic3r/Fill/FillPlanePath.hpp @@ -27,8 +27,30 @@ protected: Polylines &polylines_out) override; float _layer_angle(size_t idx) const override { return 0.f; } - virtual bool _centered() const = 0; - virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) = 0; + virtual bool centered() const = 0; + + friend class InfillPolylineClipper; + class InfillPolylineOutput { + public: + InfillPolylineOutput(const double scale_out) : m_scale_out(scale_out) {} + + void reserve(size_t n) { m_out.reserve(n); } + void add_point(const Vec2d& pt) { m_out.emplace_back(this->scaled(pt)); } + Points&& result() { return std::move(m_out); } + virtual bool clips() const { return false; } + + protected: + const Point scaled(const Vec2d &fpt) const { return { coord_t(floor(fpt.x() * m_scale_out + 0.5)), coord_t(floor(fpt.y() * m_scale_out + 0.5)) }; } + + // Output polyline. + Points m_out; + + private: + // Scaling coefficient of the generated points before tested against m_bbox and clipped by bbox. + double m_scale_out; + }; + + virtual void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) = 0; }; class FillArchimedeanChords : public FillPlanePath @@ -38,8 +60,8 @@ public: ~FillArchimedeanChords() override = default; protected: - bool _centered() const override { return true; } - Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override; + bool centered() const override { return true; } + void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override; }; class FillHilbertCurve : public FillPlanePath @@ -49,8 +71,8 @@ public: ~FillHilbertCurve() override = default; protected: - bool _centered() const override { return false; } - Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override; + bool centered() const override { return false; } + void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override; }; class FillOctagramSpiral : public FillPlanePath @@ -60,8 +82,8 @@ public: ~FillOctagramSpiral() override = default; protected: - bool _centered() const override { return true; } - Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override; + bool centered() const override { return true; } + void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override; }; } // namespace Slic3r