Fills were reimplemented in C++.

While reimplementing the FillPlanePath code, the octagon infill was fixed to extrude the right amount of material.
This commit is contained in:
bubnikv 2016-04-11 17:08:30 +02:00
parent 7da68c91a5
commit be3e4caf1d
12 changed files with 1162 additions and 0 deletions

View file

@ -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<coordf_t> colinearPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength)
{
const coordf_t offset2 = std::abs(offset / coordf_t(2.));
std::vector<coordf_t> 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<coordf_t> 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<coordf_t> 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<typename T>
static inline T clamp(T low, T high, T x)
{
return std::max<T>(low, std::min<T>(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<coordf_t> &x, const std::vector<coordf_t> &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<Pointfs> 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<Pointfs> 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<Pointfs> polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType);
Polylines result;
result.reserve(polylines.size());
for (std::vector<Pointfs>::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 &params)
{
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

View file

@ -0,0 +1,24 @@
#ifndef slic3r_Fill3DHoneycomb_hpp_
#define slic3r_Fill3DHoneycomb_hpp_
#include <map>
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class Fill3DHoneycomb : public FillWithDirection
{
public:
virtual ~Fill3DHoneycomb() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
// 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_

View file

@ -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<float, Point> 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<float, Point>(out_angle, out_shift);
}
} // namespace Slic3r

View file

@ -0,0 +1,102 @@
#ifndef slic3r_FillBase_hpp_
#define slic3r_FillBase_hpp_
#include <float.h>
#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 &params) = 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<float, Point> infill_direction(const Surface *surface) const ;
};
} // namespace Slic3r
#endif // slic3r_FillBase_hpp_

View file

@ -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 &params)
{
// 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

View file

@ -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 &params);
protected:
virtual bool no_sort() const { return true; }
};
} // namespace Slic3r
#endif // slic3r_FillConcentric_hpp_

View file

@ -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 &params)
{
std::pair<float, Point> 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<CacheID, CacheData>(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

View file

@ -0,0 +1,50 @@
#ifndef slic3r_FillHoneycomb_hpp_
#define slic3r_FillHoneycomb_hpp_
#include <map>
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class FillHoneycomb : public FillWithDirection
{
public:
virtual ~FillHoneycomb() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
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<CacheID, CacheData> 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_

View file

@ -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 &params)
{
ExPolygon expolygon = surface->expolygon;
std::pair<float, Point> 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

View file

@ -0,0 +1,60 @@
#ifndef slic3r_FillPlanePath_hpp_
#define slic3r_FillPlanePath_hpp_
#include <map>
#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 &params);
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_

View file

@ -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 &params)
{
// rotate polygons so that we can work with vertical lines here
ExPolygon expolygon = surface->expolygon;
std::pair<float, Point> 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

View file

@ -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 &params);
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_