From be3e4caf1df995db8bd585343bd864cbd9040b5e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 11 Apr 2016 17:08:30 +0200 Subject: [PATCH] Fills were reimplemented in C++. While reimplementing the FillPlanePath code, the octagon infill was fixed to extrude the right amount of material. --- xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp | 223 ++++++++++++++++++++++ xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp | 24 +++ xs/src/libslic3r/Fill/FillBase.cpp | 83 ++++++++ xs/src/libslic3r/Fill/FillBase.hpp | 102 ++++++++++ xs/src/libslic3r/Fill/FillConcentric.cpp | 60 ++++++ xs/src/libslic3r/Fill/FillConcentric.hpp | 20 ++ xs/src/libslic3r/Fill/FillHoneycomb.cpp | 133 +++++++++++++ xs/src/libslic3r/Fill/FillHoneycomb.hpp | 50 +++++ xs/src/libslic3r/Fill/FillPlanePath.cpp | 197 +++++++++++++++++++ xs/src/libslic3r/Fill/FillPlanePath.hpp | 60 ++++++ xs/src/libslic3r/Fill/FillRectilinear.cpp | 138 +++++++++++++ xs/src/libslic3r/Fill/FillRectilinear.hpp | 72 +++++++ 12 files changed, 1162 insertions(+) create mode 100644 xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp create mode 100644 xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp create mode 100644 xs/src/libslic3r/Fill/FillBase.cpp create mode 100644 xs/src/libslic3r/Fill/FillBase.hpp create mode 100644 xs/src/libslic3r/Fill/FillConcentric.cpp create mode 100644 xs/src/libslic3r/Fill/FillConcentric.hpp create mode 100644 xs/src/libslic3r/Fill/FillHoneycomb.cpp create mode 100644 xs/src/libslic3r/Fill/FillHoneycomb.hpp create mode 100644 xs/src/libslic3r/Fill/FillPlanePath.cpp create mode 100644 xs/src/libslic3r/Fill/FillPlanePath.hpp create mode 100644 xs/src/libslic3r/Fill/FillRectilinear.cpp create mode 100644 xs/src/libslic3r/Fill/FillRectilinear.hpp diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp new file mode 100644 index 000000000..ce3fa3c57 --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -0,0 +1,223 @@ +#include "../ClipperUtils.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + +#include "Fill3DHoneycomb.hpp" + +namespace Slic3r { + +/* +Creates a contiguous sequence of points at a specified height that make +up a horizontal slice of the edges of a space filling truncated +octahedron tesselation. The octahedrons are oriented so that the +square faces are in the horizontal plane with edges parallel to the X +and Y axes. + +Credits: David Eccles (gringer). +*/ + +// Generate an array of points that are in the same direction as the +// basic printing line (i.e. Y points for columns, X points for rows) +// Note: a negative offset only causes a change in the perpendicular +// direction +static std::vector colinearPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) +{ + const coordf_t offset2 = std::abs(offset / coordf_t(2.)); + std::vector points; + points.push_back(baseLocation - offset2); + for (size_t i = 0; i < gridLength; ++i) { + points.push_back(baseLocation + i + offset2); + points.push_back(baseLocation + i + 1 - offset2); + } + points.push_back(baseLocation + gridLength + offset2); + return points; +} + +// Generate an array of points for the dimension that is perpendicular to +// the basic printing line (i.e. X points for columns, Y points for rows) +static std::vector perpendPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength) +{ + coordf_t offset2 = offset / coordf_t(2.); + coord_t side = 2 * (baseLocation & 1) - 1; + std::vector points; + points.push_back(baseLocation - offset2 * side); + for (size_t i = 0; i < gridLength; ++i) { + side = 2*((i+baseLocation) & 1) - 1; + points.push_back(baseLocation + offset2 * side); + points.push_back(baseLocation + offset2 * side); + } + points.push_back(baseLocation - offset2 * side); + return points; +} + +template +static inline T clamp(T low, T high, T x) +{ + return std::max(low, std::min(high, x)); +} + +// Trims an array of points to specified rectangular limits. Point +// components that are outside these limits are set to the limits. +static inline void trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY) +{ + for (Pointfs::iterator it = pts.begin(); it != pts.end(); ++ it) { + it->x = clamp(minX, maxX, it->x); + it->y = clamp(minY, maxY, it->y); + } +} + +static inline Pointfs zip(const std::vector &x, const std::vector &y) +{ + assert(x.size() == y.size()); + Pointfs out; + out.reserve(x.size()); + for (size_t i = 0; i < x.size(); ++ i) + out.push_back(Pointf(x[i], y[i])); + return out; +} + +// Generate a set of curves (array of array of 2d points) that describe a +// horizontal slice of a truncated regular octahedron with edge length 1. +// curveType specifies which lines to print, 1 for vertical lines +// (columns), 2 for horizontal lines (rows), and 3 for both. +static std::vector makeNormalisedGrid(coordf_t z, size_t gridWidth, size_t gridHeight, size_t curveType) +{ + // offset required to create a regular octagram + coordf_t octagramGap = coordf_t(0.5); + + // sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap] + coordf_t a = std::sqrt(coordf_t(2.)); // period + coordf_t wave = abs(fmod(z, a) - a/2.)/a*4. - 1.; + coordf_t offset = wave * octagramGap; + + std::vector points; + if ((curveType & 1) != 0) { + for (size_t x = 0; x <= gridWidth; ++x) { + points.push_back(Pointfs()); + Pointfs &newPoints = points.back(); + newPoints = zip( + perpendPoints(offset, x, gridHeight), + colinearPoints(offset, 0, gridHeight)); + // trim points to grid edges + trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); + if (x & 1) + std::reverse(newPoints.begin(), newPoints.end()); + } + } + if ((curveType & 2) != 0) { + for (size_t y = 0; y <= gridHeight; ++y) { + points.push_back(Pointfs()); + Pointfs &newPoints = points.back(); + newPoints = zip( + colinearPoints(offset, 0, gridWidth), + perpendPoints(offset, y, gridWidth)); + // trim points to grid edges + trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight)); + if (y & 1) + std::reverse(newPoints.begin(), newPoints.end()); + } + } + return points; +} + +// Generate a set of curves (array of array of 2d points) that describe a +// horizontal slice of a truncated regular octahedron with a specified +// grid square size. +static Polylines makeGrid(coord_t z, coord_t gridSize, size_t gridWidth, size_t gridHeight, size_t curveType) +{ + coord_t scaleFactor = gridSize; + coordf_t normalisedZ = coordf_t(z) / coordf_t(scaleFactor); + std::vector polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType); + Polylines result; + result.reserve(polylines.size()); + for (std::vector::const_iterator it_polylines = polylines.begin(); it_polylines != polylines.end(); ++ it_polylines) { + result.push_back(Polyline()); + Polyline &polyline = result.back(); + for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it) + polyline.points.push_back(Point(coord_t(it->x * scaleFactor), coord_t(it->y * scaleFactor))); + } + return result; +} + +Polylines Fill3DHoneycomb::fill_surface(const Surface *surface, const FillParams ¶ms) +{ + ExPolygon expolygon = surface->expolygon; + BoundingBox bb = expolygon.contour.bounding_box(); + Point size = bb.size(); + coord_t distance = coord_t(scale_(this->spacing) / params.density); + + // 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(Point( + bb.min.x - (bb.min.x % (2*distance)), + bb.min.y - (bb.min.y % (2*distance)))); + + // generate pattern + Polylines polylines = makeGrid( + scale_(this->z), + distance, + ceil(size.x / distance) + 1, + ceil(size.y / distance) + 1, + ((this->layer_id / surface->thickness_layers) % 2) + 1); + + // move pattern in place + for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) + it->translate(bb.min.x, bb.min.y); + + // clip pattern to boundaries + intersection(polylines, (Polygons)expolygon, &polylines); + + // connect lines + if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections + ExPolygon expolygon_off; + { + ExPolygons expolygons_off = offset_ex(expolygon, SCALED_EPSILON); + if (! expolygons_off.empty()) { + // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island. + assert(expolygons_off.size() == 1); + std::swap(expolygon_off, expolygons_off.front()); + } + } + Polylines chained = PolylineCollection::chained_path_from( +#if SLIC3R_CPPVER >= 11 + std::move(polylines), +#else + polylines, +#endif + PolylineCollection::leftmost_point(polylines), false); // reverse allowed +#if SLIC3R_CPPVER >= 11 + assert(polylines.empty()); +#else + polylines.clear(); +#endif + for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { + if (! polylines.empty()) { + // Try to connect the lines. + Points &pts_end = polylines.back().points; + const Point &first_point = it_polyline->points.front(); + const Point &last_point = pts_end.back(); + // TODO: we should also check that both points are on a fill_boundary to avoid + // connecting paths on the boundaries of internal regions + if (first_point.distance_to(last_point) <= 1.5 * distance && + expolygon_off.contains(Line(last_point, first_point))) { + // Append the polyline. + pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); + continue; + } + } + // The lines cannot be connected. +#if SLIC3R_CPPVER >= 11 + polylines.push_back(std::move(*it_polyline)); +#else + polylines.push_back(Polyline()); + std::swap(polylines.back(), *it_polyline); +#endif + } + } + + // TODO: return ExtrusionLoop objects to get better chained paths + return polylines; +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp new file mode 100644 index 000000000..1410f5b55 --- /dev/null +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.hpp @@ -0,0 +1,24 @@ +#ifndef slic3r_Fill3DHoneycomb_hpp_ +#define slic3r_Fill3DHoneycomb_hpp_ + +#include + +#include "../libslic3r.h" + +#include "FillBase.hpp" + +namespace Slic3r { + +class Fill3DHoneycomb : public FillWithDirection +{ +public: + virtual ~Fill3DHoneycomb() {} + virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); + + // require bridge flow since most of this pattern hangs in air + virtual bool use_bridge_flow() const { return true; } +}; + +} // namespace Slic3r + +#endif // slic3r_Fill3DHoneycomb_hpp_ diff --git a/xs/src/libslic3r/Fill/FillBase.cpp b/xs/src/libslic3r/Fill/FillBase.cpp new file mode 100644 index 000000000..64282da94 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillBase.cpp @@ -0,0 +1,83 @@ +#include "../Surface.hpp" + +#include "FillBase.hpp" +#include "FillConcentric.hpp" +#include "FillHoneycomb.hpp" +#include "Fill3DHoneycomb.hpp" +#include "FillPlanePath.hpp" +#include "FillRectilinear.hpp" + +namespace Slic3r { + +Fill* Fill::new_from_type(const std::string &type) +{ + if (type == "concentric") + return new FillConcentric(); + if (type == "honeycomb") + return new FillHoneycomb(); + if (type == "3dhoneycomb") + return new Fill3DHoneycomb(); + if (type == "rectilinear") + return new FillRectilinear(); + if (type == "line") + return new FillLine(); + if (type == "grid") + return new FillGrid(); + if (type == "archimedeanchords") + return new FillArchimedeanChords(); + if (type == "hilbertcurve") + return new FillHilbertCurve(); + if (type == "octagramspiral") + return new FillOctagramSpiral(); + CONFESS("unknown type"); + return NULL; +} + +coord_t Fill::adjust_solid_spacing(const coord_t width, const coord_t distance) +{ + coord_t number_of_lines = coord_t(coordf_t(width) / coordf_t(distance)) + 1; + coord_t extra_space = width % distance; + return (number_of_lines <= 1) ? + distance : + distance + extra_space / (number_of_lines - 1); +} + +std::pair FillWithDirection::infill_direction(const Surface *surface) const +{ + // set infill angle + float out_angle = this->angle; + + if (out_angle == FLT_MAX) { + //FIXME Vojtech: Add a warning? + // warn "Using undefined infill angle"; + out_angle = 0.f; + } + + Point out_shift = empty(this->bounding_box) ? + surface->expolygon.contour.bounding_box().center() : + this->bounding_box.center(); + + if (surface->bridge_angle >= 0) { + // use bridge angle + //FIXME Vojtech: Add a debugf? + // Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle); +#if 1 +//#ifdef _DEBUG + printf("Filling bridge with angle %f\n", surface->bridge_angle); +#endif /* _DEBUG */ + out_angle = surface->bridge_angle; + } else if (this->layer_id != size_t(-1)) { + // alternate fill direction + printf("Filling layer %d, thickness %d, id: %d\n", + this->layer_id, surface->thickness_layers, int(this->layer_id / surface->thickness_layers)); + out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers); + } else { + printf("Layer_ID undefined!\n"); + } + + out_angle += float(M_PI/2.); + printf("out_angle: %f", out_angle); + return std::pair(out_angle, out_shift); +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillBase.hpp b/xs/src/libslic3r/Fill/FillBase.hpp new file mode 100644 index 000000000..1a7dbde13 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillBase.hpp @@ -0,0 +1,102 @@ +#ifndef slic3r_FillBase_hpp_ +#define slic3r_FillBase_hpp_ + +#include + +#include "../libslic3r.h" +#include "../BoundingBox.hpp" + +namespace Slic3r { + +class Surface; + +struct FillParams +{ + coordf_t width; + // Fraction in <0, 1> + float density; + coordf_t distance; + + // Don't connect the fill lines around the inner perimeter. + bool dont_connect; + + // Don't adjust spacing to fill the space evenly. + bool dont_adjust; + + // For Honeycomb. + // we were requested to complete each loop; + // in this case we don't try to make more continuous paths + bool complete; +}; + +class Fill +{ +public: + // Index of the layer. + size_t layer_id; + // Height of the layer, in unscaled coordinates + coordf_t z; + // in unscaled coordinates + coordf_t spacing; + // in radians, ccw, 0 = East + float angle; + // in scaled coordinates + coord_t loop_clipping; + // in scaled coordinates + BoundingBox bounding_box; + +public: + virtual ~Fill() {} + + static Fill* new_from_type(const std::string &type); + + void set_bounding_box(const Slic3r::BoundingBox &bbox) { bounding_box = bbox; } + + // Use bridge flow for the fill? + virtual bool use_bridge_flow() const { return false; } + + // Do not sort the fill lines to optimize the print head path? + virtual bool no_sort() const { return false; } + + // Perform the fill. + virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms) = 0; + +protected: + Fill() : + layer_id(size_t(-1)), + z(0.f), + spacing(0.f), + // Initial angle is undefined. + angle(FLT_MAX), + loop_clipping(0), + // The initial bounding box is empty, therefore undefined. + bounding_box(Point(0, 0), Point(-1, -1)) + {} + + static coord_t adjust_solid_spacing(const coord_t width, const coord_t distance); +}; + +// An interface class to Perl, aggregating an instance of a Fill and a FillData. +class Filler +{ +public: + Filler() : fill(NULL) {} + ~Filler() { delete fill; fill = NULL; } + Fill *fill; + FillParams params; +}; + +class FillWithDirection : public Fill +{ +public: + virtual float _layer_angle(size_t idx) const { + bool odd = idx & 1; + printf("_layer_angle: %s\n", odd ? "odd" : "even"); + return (idx & 1) ? float(M_PI/2.) : 0; + } + virtual std::pair infill_direction(const Surface *surface) const ; +}; + +} // namespace Slic3r + +#endif // slic3r_FillBase_hpp_ diff --git a/xs/src/libslic3r/Fill/FillConcentric.cpp b/xs/src/libslic3r/Fill/FillConcentric.cpp new file mode 100644 index 000000000..30b34456d --- /dev/null +++ b/xs/src/libslic3r/Fill/FillConcentric.cpp @@ -0,0 +1,60 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" + +#include "FillConcentric.hpp" + +namespace Slic3r { + +Polylines FillConcentric::fill_surface(const Surface *surface, const FillParams ¶ms) +{ + // no rotation is supported for this infill pattern + ExPolygon expolygon = surface->expolygon; + BoundingBox bounding_box = expolygon.contour.bounding_box(); + + coord_t min_spacing = scale_(this->spacing); + coord_t distance = coord_t(min_spacing / params.density); + + if (params.density > 0.9999f && !params.dont_adjust) { + distance = this->adjust_solid_spacing(bounding_box.size().x, distance); + this->spacing = unscale(distance); + } + + Polygons loops = (Polygons)expolygon; + Polygons last = loops; + while (! last.empty()) { + last = offset2(last, -(distance + min_spacing/2), +min_spacing/2); + loops.insert(loops.end(), last.begin(), last.end()); + } + + // generate paths from the outermost to the innermost, to avoid + // adhesion problems of the first central tiny loops + union_pt_chained(loops, &loops, false); + + // split paths using a nearest neighbor search + Polylines paths; + Point last_pos(0, 0); + for (Polygons::const_iterator it_loop = loops.begin(); it_loop != loops.end(); ++ it_loop) { + paths.push_back(it_loop->split_at_index(last_pos.nearest_point_index(*it_loop))); + last_pos = paths.back().last_point(); + } + + // clip the paths to prevent the extruder from getting exactly on the first point of the loop + // Keep valid paths only. + size_t j = 0; + for (size_t i = 0; i < paths.size(); ++ i) { + paths[i].clip_end(this->loop_clipping); + if (paths[i].is_valid()) { + if (j < i) + std::swap(paths[j], paths[i]); + ++ j; + } + } + if (j < paths.size()) + paths.erase(paths.begin() + j, paths.end()); + + // TODO: return ExtrusionLoop objects to get better chained paths + return paths; +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillConcentric.hpp b/xs/src/libslic3r/Fill/FillConcentric.hpp new file mode 100644 index 000000000..6d58ec089 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillConcentric.hpp @@ -0,0 +1,20 @@ +#ifndef slic3r_FillConcentric_hpp_ +#define slic3r_FillConcentric_hpp_ + +#include "FillBase.hpp" + +namespace Slic3r { + +class FillConcentric : public Fill +{ +public: + virtual ~FillConcentric() {} + virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); + +protected: + virtual bool no_sort() const { return true; } +}; + +} // namespace Slic3r + +#endif // slic3r_FillConcentric_hpp_ diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp new file mode 100644 index 000000000..408a5fa81 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp @@ -0,0 +1,133 @@ +#include "../ClipperUtils.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + +#include "FillHoneycomb.hpp" + +namespace Slic3r { + +Polylines FillHoneycomb::fill_surface(const Surface *surface, const FillParams ¶ms) +{ + std::pair rotate_vector = this->infill_direction(surface); + + // cache hexagons math + CacheID cache_id(params.density, this->spacing); + Cache::iterator it_m = this->cache.find(cache_id); + if (it_m == this->cache.end()) { +#if SLIC3R_CPPVER > 11 + it_m = this->cache.emplace_hint(it_m); +#else + it_m = this->cache.insert(it_m, std::pair(cache_id, CacheData())); +#endif + CacheData &m = it_m->second; + coord_t min_spacing = scale_(this->spacing); + m.distance = min_spacing / params.density; + m.hex_side = m.distance / (sqrt(3)/2); + m.hex_width = m.distance * 2; // $m->{hex_width} == $m->{hex_side} * sqrt(3); + coord_t hex_height = m.hex_side * 2; + m.pattern_height = hex_height + m.hex_side; + m.y_short = m.distance * sqrt(3)/3; + m.x_offset = min_spacing / 2; + m.y_offset = m.x_offset * sqrt(3)/3; + m.hex_center = Point(m.hex_width/2, m.hex_side); + } + CacheData &m = it_m->second; + + Polygons polygons; + { + // adjust actual bounding box to the nearest multiple of our hex pattern + // and align it so that it matches across layers + + BoundingBox bounding_box = surface->expolygon.contour.bounding_box(); + { + // rotate bounding box according to infill direction + Polygon bb_polygon = bounding_box.polygon(); + bb_polygon.rotate(rotate_vector.first, m.hex_center); + bounding_box = bb_polygon.bounding_box(); + + // 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 + bounding_box.merge(Point( + bounding_box.min.x - (bounding_box.min.x % m.hex_width), + bounding_box.min.y - (bounding_box.min.y % m.pattern_height))); + } + + coord_t x = bounding_box.min.x; + while (x <= bounding_box.max.x) { + Polygon p; + coord_t ax[2] = { x + m.x_offset, x + m.distance - m.x_offset }; + for (size_t i = 0; i < 2; ++ i) { + std::reverse(p.points.begin(), p.points.end()); // turn first half upside down + for (coord_t y = bounding_box.min.y; y <= bounding_box.max.y; y += m.y_short + m.hex_side + m.y_short + m.hex_side) { + p.points.push_back(Point(ax[1], y + m.y_offset)); + p.points.push_back(Point(ax[0], y + m.y_short - m.y_offset)); + p.points.push_back(Point(ax[0], y + m.y_short + m.hex_side + m.y_offset)); + p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short - m.y_offset)); + p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short + m.hex_side + m.y_offset)); + } + ax[0] = ax[0] + m.distance; + ax[1] = ax[1] + m.distance; + std::swap(ax[0], ax[1]); // draw symmetrical pattern + x += m.distance; + } + p.rotate(-rotate_vector.first, m.hex_center); + polygons.push_back(p); + } + } + + Polylines paths; + if (params.complete || true) { + // we were requested to complete each loop; + // in this case we don't try to make more continuous paths + Polygons polygons_trimmed = intersection((Polygons)*surface, polygons); + for (Polygons::iterator it = polygons_trimmed.begin(); it != polygons_trimmed.end(); ++ it) + paths.push_back(it->split_at_first_point()); + } else { + // consider polygons as polylines without re-appending the initial point: + // this cuts the last segment on purpose, so that the jump to the next + // path is more straight + { + Polylines p; + for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it) + p.push_back((Polyline)(*it)); + paths = intersection(p, (Polygons)*surface); + } + + // connect paths + if (! paths.empty()) { // prevent calling leftmost_point() on empty collections + Polylines chained = PolylineCollection::chained_path_from( +#if SLIC3R_CPPVER >= 11 + std::move(paths), +#else + paths, +#endif + PolylineCollection::leftmost_point(paths), false); + assert(paths.empty()); + paths.clear(); + for (Polylines::iterator it_path = chained.begin(); it_path != chained.end(); ++ it_path) { + if (! paths.empty()) { + // distance between first point of this path and last point of last path + double distance = paths.back().last_point().distance_to(it_path->first_point()); + if (distance <= m.hex_width) { + paths.back().points.insert(paths.back().points.end(), it_path->points.begin(), it_path->points.end()); + continue; + } + } + // Don't connect the paths. + paths.push_back(*it_path); + } + } + + // clip paths again to prevent connection segments from crossing the expolygon boundaries + Polylines paths_trimmed = intersection(paths, to_polygons(offset_ex(surface->expolygon, SCALED_EPSILON))); +#if SLIC3R_CPPVER >= 11 + paths = std::move(paths_trimmed); +#else + std::swap(paths, paths_trimmed); +#endif + } + + return paths; +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.hpp b/xs/src/libslic3r/Fill/FillHoneycomb.hpp new file mode 100644 index 000000000..63fe1f6f4 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillHoneycomb.hpp @@ -0,0 +1,50 @@ +#ifndef slic3r_FillHoneycomb_hpp_ +#define slic3r_FillHoneycomb_hpp_ + +#include + +#include "../libslic3r.h" + +#include "FillBase.hpp" + +namespace Slic3r { + +class FillHoneycomb : public FillWithDirection +{ +public: + virtual ~FillHoneycomb() {} + virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); + +protected: + // Caching the + struct CacheID + { + CacheID(float adensity, coordf_t aspacing) : + density(adensity), spacing(aspacing) {} + float density; + coordf_t spacing; + bool operator<(const CacheID &other) const + { return (density < other.density) || (density == other.density && spacing < other.spacing); } + bool operator==(const CacheID &other) const + { return density == other.density && spacing == other.spacing; } + }; + struct CacheData + { + coord_t distance; + coord_t hex_side; + coord_t hex_width; + coord_t pattern_height; + coord_t y_short; + coord_t x_offset; + coord_t y_offset; + Point hex_center; + }; + typedef std::map Cache; + Cache cache; + + virtual float _layer_angle(size_t idx) const { return 0.5f * float(M_PI) * (idx % 3); } +}; + +} // namespace Slic3r + +#endif // slic3r_FillHoneycomb_hpp_ diff --git a/xs/src/libslic3r/Fill/FillPlanePath.cpp b/xs/src/libslic3r/Fill/FillPlanePath.cpp new file mode 100644 index 000000000..0a271cbd8 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillPlanePath.cpp @@ -0,0 +1,197 @@ +#include "../ClipperUtils.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + +#include "FillPlanePath.hpp" + +namespace Slic3r { + +Polylines FillPlanePath::fill_surface(const Surface *surface, const FillParams ¶ms) +{ + ExPolygon expolygon = surface->expolygon; + std::pair rotate_vector = this->infill_direction(surface); + expolygon.rotate(- rotate_vector.first); + + coord_t distance_between_lines = scale_(this->spacing) / params.density; + + // align infill across layers using the object's bounding box + Polygon bb_polygon = this->bounding_box.polygon(); + bb_polygon.rotate(- rotate_vector.first); + BoundingBox bounding_box = bb_polygon.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))); + + Polylines polylines; + if (pts.size() >= 2) { + // Convert points to a polyline, upscale. + polylines.push_back(Polyline()); + Polyline &polyline = polylines.back(); + polyline.points.reserve(pts.size()); + for (Pointfs::iterator it = pts.begin(); it != pts.end(); ++ it) + polyline.points.push_back(Point( + coord_t(floor(it->x * distance_between_lines + 0.5)), + coord_t(floor(it->y * distance_between_lines + 0.5)))); +// intersection(polylines_src, offset((Polygons)expolygon, scale_(0.02)), &polylines); + intersection(polylines, (Polygons)expolygon, &polylines); + +/* + if (1) { + require "Slic3r/SVG.pm"; + print "Writing fill.svg\n"; + Slic3r::SVG::output("fill.svg", + no_arrows => 1, + polygons => \@$expolygon, + green_polygons => [ $bounding_box->polygon ], + polylines => [ $polyline ], + red_polylines => \@paths, + ); + } +*/ + + // paths must be repositioned and rotated back + for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) { + it->translate(shift.x, shift.y); + it->rotate(rotate_vector.first); + } + } + + return polylines; +} + +// 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) +{ + // 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. + out.push_back(Pointf(0, 0)); + out.push_back(Pointf(1, 0)); + while (r < rmax) { + theta += 1. / r; + r = a + b * theta; + out.push_back(Pointf(r * cos(theta), r * sin(theta))); + } + return out; +} + +// 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 const int next_state[16] = { 4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0 }; + static const int digit_to_x[16] = { 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0 }; + static 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; + int dirstate = (ndigits & 1) ? 0 : 4; + 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; + if (digit != 3) + dirstate = state; // lowest non-3 digit + x |= digit_to_x[state] << i; + y |= digit_to_y[state] << i; + state = next_state[state]; + } + return Point(x, y); +} + +Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y) +{ + // 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; + Pointfs line; + line.reserve(sz2); + for (size_t i = 0; i < sz2; ++ i) { + Point p = hilbert_n_to_xy(i); + line.push_back(Pointf(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) +{ + // 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.push_back(Pointf(0, 0)); + while (r < rmax) { + r += r_inc; + coordf_t rx = r / sqrt(2.); + coordf_t r2 = r + rx; + out.push_back(Pointf( r, 0.)); + out.push_back(Pointf( r2, rx)); + out.push_back(Pointf( rx, rx)); + out.push_back(Pointf( rx, r2)); + out.push_back(Pointf(0., r)); + out.push_back(Pointf(-rx, r2)); + out.push_back(Pointf(-rx, rx)); + out.push_back(Pointf(-r2, rx)); + out.push_back(Pointf(-r, 0.)); + out.push_back(Pointf(-r2, -rx)); + out.push_back(Pointf(-rx, -rx)); + out.push_back(Pointf(-rx, -r2)); + out.push_back(Pointf(0., -r)); + out.push_back(Pointf( rx, -r2)); + out.push_back(Pointf( rx, -rx)); + out.push_back(Pointf( r2+r_inc, -rx)); + } + return out; +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillPlanePath.hpp b/xs/src/libslic3r/Fill/FillPlanePath.hpp new file mode 100644 index 000000000..79e5522a9 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillPlanePath.hpp @@ -0,0 +1,60 @@ +#ifndef slic3r_FillPlanePath_hpp_ +#define slic3r_FillPlanePath_hpp_ + +#include + +#include "../libslic3r.h" + +#include "FillBase.hpp" + +namespace Slic3r { + +// The original Perl code used path generators from Math::PlanePath library: +// http://user42.tuxfamily.org/math-planepath/ +// http://user42.tuxfamily.org/math-planepath/gallery.html + +class FillPlanePath : public FillWithDirection +{ +public: + virtual ~FillPlanePath() {} + virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); + +protected: + virtual float _layer_angle(size_t idx) const { 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) = 0; +}; + +class FillArchimedeanChords : public FillPlanePath +{ +public: + virtual ~FillArchimedeanChords() {} + +protected: + virtual bool _centered() const { return true; } + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); +}; + +class FillHilbertCurve : public FillPlanePath +{ +public: + virtual ~FillHilbertCurve() {} + +protected: + virtual bool _centered() const { return false; } + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); +}; + +class FillOctagramSpiral : public FillPlanePath +{ +public: + virtual ~FillOctagramSpiral() {} + +protected: + virtual bool _centered() const { return true; } + virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y); +}; + +} // namespace Slic3r + +#endif // slic3r_FillPlanePath_hpp_ diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp new file mode 100644 index 000000000..99b8b346f --- /dev/null +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp @@ -0,0 +1,138 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../PolylineCollection.hpp" +#include "../Surface.hpp" + +#include "FillRectilinear.hpp" + +namespace Slic3r { + +Polylines FillRectilinear::fill_surface(const Surface *surface, const FillParams ¶ms) +{ + // rotate polygons so that we can work with vertical lines here + ExPolygon expolygon = surface->expolygon; + std::pair rotate_vector = this->infill_direction(surface); + expolygon.rotate(- rotate_vector.first); + // No need to translate the polygon anyhow for the infill. + // The infill will be performed inside a bounding box of the expolygon and its absolute position does not matter. +// expolygon.translate(rotate_vector.second.x, rotate_vector.second.y); + + this->_min_spacing = scale_(this->spacing); + assert(params.density > 0.0001f && params.density <= 1.f); + this->_line_spacing = coord_t(coordf_t(this->_min_spacing) / params.density); + this->_diagonal_distance = this->_line_spacing * 2; + this->_line_oscillation = this->_line_spacing - this->_min_spacing; // only for Line infill + BoundingBox bounding_box = expolygon.contour.bounding_box(); + + // define flow spacing according to requested density + if (params.density > 0.9999f && !params.dont_adjust) { + this->_line_spacing = this->adjust_solid_spacing(bounding_box.size().x, this->_line_spacing); + this->spacing = unscale(this->_line_spacing); + } else { + // extend bounding box so that our pattern will be aligned with other layers + bounding_box.merge(Point( + bounding_box.min.x - (bounding_box.min.x % this->_line_spacing), + bounding_box.min.y - (bounding_box.min.y % this->_line_spacing))); + } + + // generate the basic pattern + coord_t x_max = bounding_box.max.x + SCALED_EPSILON; + Lines lines; + for (coord_t x = bounding_box.min.x; x <= x_max; x += this->_line_spacing) + lines.push_back(this->_line(lines.size(), x, bounding_box.min.y, bounding_box.max.y)); + if (this->_horizontal_lines()) { + coord_t y_max = bounding_box.max.y + SCALED_EPSILON; + for (coord_t y = bounding_box.min.y; y <= y_max; y += this->_line_spacing) + lines.push_back(Line(Point(bounding_box.min.x, y), Point(bounding_box.max.x, y))); + } + + // clip paths against a slightly larger expolygon, so that the first and last paths + // are kept even if the expolygon has vertical sides + // the minimum offset for preventing edge lines from being clipped is SCALED_EPSILON; + // however we use a larger offset to support expolygons with slightly skewed sides and + // not perfectly straight + //FIXME Vojtech: Update the intersecton function to work directly with lines. + Polylines polylines_src; + polylines_src.reserve(lines.size()); + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) { + polylines_src.push_back(Polyline()); + Points &pts = polylines_src.back().points; + pts.reserve(2); + pts.push_back(it->a); + pts.push_back(it->b); + } + Polylines polylines = intersection(polylines_src, offset((Polygons)expolygon, scale_(0.02)), false); + + // FIXME Vojtech: This is only performed for horizontal lines, not for the vertical lines! + const float INFILL_OVERLAP_OVER_SPACING = 0.3f; + coord_t extra = coord_t(floor(this->_min_spacing * INFILL_OVERLAP_OVER_SPACING + 0.5f)); + for (Polylines::iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { + Point *first_point = &it_polyline->points.front(); + Point *last_point = &it_polyline->points.back(); + if (first_point->y > last_point->y) + std::swap(first_point, last_point); + first_point->y -= extra; + last_point->y += extra; + } + + // connect lines + if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections + // offset the expolygon by max(min_spacing/2, extra) + ExPolygon expolygon_off; + { + ExPolygons expolygons_off = offset_ex(expolygon, this->_min_spacing/2); + if (! expolygons_off.empty()) { + // When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island. + assert(expolygons_off.size() == 1); + std::swap(expolygon_off, expolygons_off.front()); + } + } + Polylines chained = PolylineCollection::chained_path_from( +#if SLIC3R_CPPVER >= 11 + std::move(polylines), +#else + polylines, +#endif + PolylineCollection::leftmost_point(polylines), false); // reverse allowed +#if SLIC3R_CPPVER >= 11 + assert(polylines.empty()); +#else + polylines.clear(); +#endif + for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { + if (! polylines.empty()) { + // Try to connect the lines. + Points &pts_end = polylines.back().points; + const Point &first_point = it_polyline->points.front(); + const Point &last_point = pts_end.back(); + // Distance in X, Y. + const Vector distance = first_point.vector_to(last_point); + // TODO: we should also check that both points are on a fill_boundary to avoid + // connecting paths on the boundaries of internal regions + if (this->_can_connect(std::abs(distance.x), std::abs(distance.y)) && + expolygon_off.contains(Line(last_point, first_point))) { + // Append the polyline. + pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); + continue; + } + } + // The lines cannot be connected. +#if SLIC3R_CPPVER >= 11 + polylines.push_back(std::move(*it_polyline)); +#else + polylines.push_back(Polyline()); + std::swap(polylines.back(), *it_polyline); +#endif + } + } + + // paths must be rotated back + for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) { + // No need to translate, the absolute position is irrelevant. + // it->translate(- rotate_vector.second.x, - rotate_vector.second.y); + it->rotate(rotate_vector.first); + } + return polylines; +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillRectilinear.hpp b/xs/src/libslic3r/Fill/FillRectilinear.hpp new file mode 100644 index 000000000..d10c4ea65 --- /dev/null +++ b/xs/src/libslic3r/Fill/FillRectilinear.hpp @@ -0,0 +1,72 @@ +#ifndef slic3r_FillRectilinear_hpp_ +#define slic3r_FillRectilinear_hpp_ + +#include "../libslic3r.h" + +#include "FillBase.hpp" + +namespace Slic3r { + +class Surface; + +class FillRectilinear : public FillWithDirection +{ +public: + virtual ~FillRectilinear() {} + virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); + +protected: + coord_t _min_spacing; + coord_t _line_spacing; + // distance threshold for allowing the horizontal infill lines to be connected into a continuous path + coord_t _diagonal_distance; + // only for line infill + coord_t _line_oscillation; + + // Enabled for the grid infill, disabled for the rectilinear and line infill. + virtual bool _horizontal_lines() const { return false; } + + virtual Line _line(int i, coord_t x, coord_t y_min, coord_t y_max) const + { return Line(Point(x, y_min), Point(x, y_max)); } + + virtual bool _can_connect(coord_t dist_X, coord_t dist_Y) { + return dist_X <= this->_diagonal_distance + && dist_Y <= this->_diagonal_distance; + } +}; + +class FillLine : public FillRectilinear +{ +public: + virtual ~FillLine() {} + +protected: + virtual Line _line(int i, coord_t x, coord_t y_min, coord_t y_max) const { + coord_t osc = (i & 1) ? this->_line_oscillation : 0; + return Line(Point(x - osc, y_min), Point(x + osc, y_max)); + } + + virtual bool _can_connect(coord_t dist_X, coord_t dist_Y) + { + coord_t TOLERANCE = 10 * SCALED_EPSILON; + return (dist_X >= (this->_line_spacing - this->_line_oscillation) - TOLERANCE) + && (dist_X <= (this->_line_spacing + this->_line_oscillation) + TOLERANCE) + && (dist_Y <= this->_diagonal_distance); + } +}; + +class FillGrid : public FillRectilinear +{ +public: + virtual ~FillGrid() {} + +protected: + // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill::Base. + virtual float _layer_angle(size_t idx) const { return 0.f; } + // Flag for Slic3r::Fill::Rectilinear to fill both directions. + virtual bool _horizontal_lines() const { return true; } +}; + +}; // namespace Slic3r + +#endif // slic3r_FillRectilinear_hpp_