#include "../ClipperUtils.hpp" #include "../ShortestPath.hpp" #include "../Surface.hpp" #include "FillPlanePath.hpp" namespace Slic3r { class InfillPolylineClipper : public InfillPolylineOutput { public: InfillPolylineClipper(const BoundingBox bbox, const double scale_out) : 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, const std::pair &direction, ExPolygon expolygon, Polylines &polylines_out) { expolygon.rotate(-direction.first); //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()); 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 (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)); else connect_infill(std::move(polylines), expolygon, chained, this->spacing, params); // paths must be repositioned and rotated back for (Polyline &pl : chained) { pl.translate(shift.x(), shift.y()); pl.rotate(direction.first); } append(polylines_out, std::move(chained)); } } // Follow an Archimedean spiral, in polar coordinates: r=a+b\theta 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; // Now unwind the spiral. coordf_t a = 1.; coordf_t b = 1./(2.*M_PI); coordf_t theta = 0.; coordf_t r = 1; Pointfs out; //FIXME Vojtech: If used as a solid infill, there is a gap left at the center. 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; output.add_point({ r * cos(theta), r * sin(theta) }); } } 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 // http://cpansearch.perl.org/src/KRYDE/Math-PlanePath-122/lib/Math/PlanePath/HilbertCurve.pm // // state=0 3--2 plain // | // 0--1 // // state=4 1--2 transpose // | | // 0 3 // // state=8 // // state=12 3 0 rot180 + transpose // | | // 2--1 // static inline Point hilbert_n_to_xy(const size_t n) { static constexpr const int next_state[16] { 4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0 }; static constexpr const int digit_to_x[16] { 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0 }; static constexpr const int digit_to_y[16] { 0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1 }; // Number of 2 bit digits. size_t ndigits = 0; { size_t nc = n; while(nc > 0) { nc >>= 2; ++ ndigits; } } int state = (ndigits & 1) ? 4 : 0; coord_t x = 0; coord_t y = 0; for (int i = (int)ndigits - 1; i >= 0; -- i) { int digit = (n >> (i * 2)) & 3; state += digit; x |= digit_to_x[state] << i; y |= digit_to_y[state] << i; state = next_state[state]; } return Point(x, y); } 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; size_t pw = 1; { size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y); while (sz < sz0) { sz = sz << 1; ++ pw; } } size_t sz2 = sz * sz; output.reserve(sz2); for (size_t i = 0; i < sz2; ++ i) { Point p = hilbert_n_to_xy(i); output.add_point({ p.x() + min_x, p.y() + min_y }); } } 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.); output.add_point({ 0., 0. }); while (r < rmax) { r += r_inc; coordf_t rx = r / sqrt(2.); coordf_t r2 = r + 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 }); } } 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