WIP: Moved sources int src/, separated most of the source code from Perl.

The XS was left only for the unit / integration tests, and it links
libslic3r only. No wxWidgets are allowed to be used from Perl starting
from now.
This commit is contained in:
bubnikv 2018-09-19 11:02:24 +02:00
parent 3ddaccb641
commit 0558b53493
1706 changed files with 7413 additions and 7638 deletions

View file

@ -0,0 +1,283 @@
#include "BoundingBox.hpp"
#include <algorithm>
#include <assert.h>
#include <Eigen/Dense>
namespace Slic3r {
template BoundingBoxBase<Point>::BoundingBoxBase(const std::vector<Point> &points);
template BoundingBoxBase<Vec2d>::BoundingBoxBase(const std::vector<Vec2d> &points);
template BoundingBox3Base<Vec3d>::BoundingBox3Base(const std::vector<Vec3d> &points);
BoundingBox::BoundingBox(const Lines &lines)
{
Points points;
points.reserve(lines.size());
for (const Line &line : lines) {
points.emplace_back(line.a);
points.emplace_back(line.b);
}
*this = BoundingBox(points);
}
void BoundingBox::polygon(Polygon* polygon) const
{
polygon->points.clear();
polygon->points.resize(4);
polygon->points[0](0) = this->min(0);
polygon->points[0](1) = this->min(1);
polygon->points[1](0) = this->max(0);
polygon->points[1](1) = this->min(1);
polygon->points[2](0) = this->max(0);
polygon->points[2](1) = this->max(1);
polygon->points[3](0) = this->min(0);
polygon->points[3](1) = this->max(1);
}
Polygon BoundingBox::polygon() const
{
Polygon p;
this->polygon(&p);
return p;
}
BoundingBox BoundingBox::rotated(double angle) const
{
BoundingBox out;
out.merge(this->min.rotated(angle));
out.merge(this->max.rotated(angle));
out.merge(Point(this->min(0), this->max(1)).rotated(angle));
out.merge(Point(this->max(0), this->min(1)).rotated(angle));
return out;
}
BoundingBox BoundingBox::rotated(double angle, const Point &center) const
{
BoundingBox out;
out.merge(this->min.rotated(angle, center));
out.merge(this->max.rotated(angle, center));
out.merge(Point(this->min(0), this->max(1)).rotated(angle, center));
out.merge(Point(this->max(0), this->min(1)).rotated(angle, center));
return out;
}
template <class PointClass> void
BoundingBoxBase<PointClass>::scale(double factor)
{
this->min *= factor;
this->max *= factor;
}
template void BoundingBoxBase<Point>::scale(double factor);
template void BoundingBoxBase<Vec2d>::scale(double factor);
template void BoundingBoxBase<Vec3d>::scale(double factor);
template <class PointClass> void
BoundingBoxBase<PointClass>::merge(const PointClass &point)
{
if (this->defined) {
this->min = this->min.cwiseMin(point);
this->max = this->max.cwiseMax(point);
} else {
this->min = point;
this->max = point;
this->defined = true;
}
}
template void BoundingBoxBase<Point>::merge(const Point &point);
template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
template <class PointClass> void
BoundingBoxBase<PointClass>::merge(const std::vector<PointClass> &points)
{
this->merge(BoundingBoxBase(points));
}
template void BoundingBoxBase<Point>::merge(const Points &points);
template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
template <class PointClass> void
BoundingBoxBase<PointClass>::merge(const BoundingBoxBase<PointClass> &bb)
{
assert(bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1));
if (bb.defined) {
if (this->defined) {
this->min = this->min.cwiseMin(bb.min);
this->max = this->max.cwiseMax(bb.max);
} else {
this->min = bb.min;
this->max = bb.max;
this->defined = true;
}
}
}
template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
template <class PointClass> void
BoundingBox3Base<PointClass>::merge(const PointClass &point)
{
if (this->defined) {
this->min = this->min.cwiseMin(point);
this->max = this->max.cwiseMax(point);
} else {
this->min = point;
this->max = point;
this->defined = true;
}
}
template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
template <class PointClass> void
BoundingBox3Base<PointClass>::merge(const std::vector<PointClass> &points)
{
this->merge(BoundingBox3Base(points));
}
template void BoundingBox3Base<Vec3d>::merge(const Pointf3s &points);
template <class PointClass> void
BoundingBox3Base<PointClass>::merge(const BoundingBox3Base<PointClass> &bb)
{
assert(bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2));
if (bb.defined) {
if (this->defined) {
this->min = this->min.cwiseMin(bb.min);
this->max = this->max.cwiseMax(bb.max);
} else {
this->min = bb.min;
this->max = bb.max;
this->defined = true;
}
}
}
template void BoundingBox3Base<Vec3d>::merge(const BoundingBox3Base<Vec3d> &bb);
template <class PointClass> PointClass
BoundingBoxBase<PointClass>::size() const
{
return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1));
}
template Point BoundingBoxBase<Point>::size() const;
template Vec2d BoundingBoxBase<Vec2d>::size() const;
template <class PointClass> PointClass
BoundingBox3Base<PointClass>::size() const
{
return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1), this->max(2) - this->min(2));
}
template Vec3d BoundingBox3Base<Vec3d>::size() const;
template <class PointClass> double BoundingBoxBase<PointClass>::radius() const
{
assert(this->defined);
double x = this->max(0) - this->min(0);
double y = this->max(1) - this->min(1);
return 0.5 * sqrt(x*x+y*y);
}
template double BoundingBoxBase<Point>::radius() const;
template double BoundingBoxBase<Vec2d>::radius() const;
template <class PointClass> double BoundingBox3Base<PointClass>::radius() const
{
double x = this->max(0) - this->min(0);
double y = this->max(1) - this->min(1);
double z = this->max(2) - this->min(2);
return 0.5 * sqrt(x*x+y*y+z*z);
}
template double BoundingBox3Base<Vec3d>::radius() const;
template <class PointClass> void
BoundingBoxBase<PointClass>::offset(coordf_t delta)
{
PointClass v(delta, delta);
this->min -= v;
this->max += v;
}
template void BoundingBoxBase<Point>::offset(coordf_t delta);
template void BoundingBoxBase<Vec2d>::offset(coordf_t delta);
template <class PointClass> void
BoundingBox3Base<PointClass>::offset(coordf_t delta)
{
PointClass v(delta, delta, delta);
this->min -= v;
this->max += v;
}
template void BoundingBox3Base<Vec3d>::offset(coordf_t delta);
template <class PointClass> PointClass
BoundingBoxBase<PointClass>::center() const
{
return (this->min + this->max) / 2;
}
template Point BoundingBoxBase<Point>::center() const;
template Vec2d BoundingBoxBase<Vec2d>::center() const;
template <class PointClass> PointClass
BoundingBox3Base<PointClass>::center() const
{
return (this->min + this->max) / 2;
}
template Vec3d BoundingBox3Base<Vec3d>::center() const;
template <class PointClass> coordf_t
BoundingBox3Base<PointClass>::max_size() const
{
PointClass s = size();
return std::max(s(0), std::max(s(1), s(2)));
}
template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
// Align a coordinate to a grid. The coordinate may be negative,
// the aligned value will never be bigger than the original one.
static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) {
// Current C++ standard defines the result of integer division to be rounded to zero,
// for both positive and negative numbers. Here we want to round down for negative
// numbers as well.
coord_t aligned = (coord < 0) ?
((coord - spacing + 1) / spacing) * spacing :
(coord / spacing) * spacing;
assert(aligned <= coord);
return aligned;
}
void BoundingBox::align_to_grid(const coord_t cell_size)
{
if (this->defined) {
min(0) = _align_to_grid(min(0), cell_size);
min(1) = _align_to_grid(min(1), cell_size);
}
}
BoundingBoxf3 BoundingBoxf3::transformed(const Transform3d& matrix) const
{
typedef Eigen::Matrix<double, 3, 8, Eigen::DontAlign> Vertices;
Vertices src_vertices;
src_vertices(0, 0) = min(0); src_vertices(1, 0) = min(1); src_vertices(2, 0) = min(2);
src_vertices(0, 1) = max(0); src_vertices(1, 1) = min(1); src_vertices(2, 1) = min(2);
src_vertices(0, 2) = max(0); src_vertices(1, 2) = max(1); src_vertices(2, 2) = min(2);
src_vertices(0, 3) = min(0); src_vertices(1, 3) = max(1); src_vertices(2, 3) = min(2);
src_vertices(0, 4) = min(0); src_vertices(1, 4) = min(1); src_vertices(2, 4) = max(2);
src_vertices(0, 5) = max(0); src_vertices(1, 5) = min(1); src_vertices(2, 5) = max(2);
src_vertices(0, 6) = max(0); src_vertices(1, 6) = max(1); src_vertices(2, 6) = max(2);
src_vertices(0, 7) = min(0); src_vertices(1, 7) = max(1); src_vertices(2, 7) = max(2);
Vertices dst_vertices = matrix * src_vertices.colwise().homogeneous();
Vec3d v_min(dst_vertices(0, 0), dst_vertices(1, 0), dst_vertices(2, 0));
Vec3d v_max = v_min;
for (int i = 1; i < 8; ++i)
{
for (int j = 0; j < 3; ++j)
{
v_min(j) = std::min(v_min(j), dst_vertices(j, i));
v_max(j) = std::max(v_max(j), dst_vertices(j, i));
}
}
return BoundingBoxf3(v_min, v_max);
}
}

View file

@ -0,0 +1,163 @@
#ifndef slic3r_BoundingBox_hpp_
#define slic3r_BoundingBox_hpp_
#include "libslic3r.h"
#include "Point.hpp"
#include "Polygon.hpp"
namespace Slic3r {
template <class PointClass>
class BoundingBoxBase
{
public:
PointClass min;
PointClass max;
bool defined;
BoundingBoxBase() : defined(false), min(PointClass::Zero()), max(PointClass::Zero()) {}
BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) :
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero())
{
if (points.empty())
throw std::invalid_argument("Empty point set supplied to BoundingBoxBase constructor");
typename std::vector<PointClass>::const_iterator it = points.begin();
this->min = *it;
this->max = *it;
for (++ it; it != points.end(); ++ it) {
this->min = this->min.cwiseMin(*it);
this->max = this->max.cwiseMax(*it);
}
this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1));
}
void merge(const PointClass &point);
void merge(const std::vector<PointClass> &points);
void merge(const BoundingBoxBase<PointClass> &bb);
void scale(double factor);
PointClass size() const;
double radius() const;
void translate(coordf_t x, coordf_t y) { assert(this->defined); PointClass v(x, y); this->min += v; this->max += v; }
void translate(const Vec2d &v) { this->min += v; this->max += v; }
void offset(coordf_t delta);
PointClass center() const;
bool contains(const PointClass &point) const {
return point(0) >= this->min(0) && point(0) <= this->max(0)
&& point(1) >= this->min(1) && point(1) <= this->max(1);
}
bool overlap(const BoundingBoxBase<PointClass> &other) const {
return ! (this->max(0) < other.min(0) || this->min(0) > other.max(0) ||
this->max(1) < other.min(1) || this->min(1) > other.max(1));
}
bool operator==(const BoundingBoxBase<PointClass> &rhs) { return this->min == rhs.min && this->max == rhs.max; }
bool operator!=(const BoundingBoxBase<PointClass> &rhs) { return ! (*this == rhs); }
};
template <class PointClass>
class BoundingBox3Base : public BoundingBoxBase<PointClass>
{
public:
BoundingBox3Base() : BoundingBoxBase<PointClass>() {};
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
BoundingBoxBase<PointClass>(pmin, pmax)
{ if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; }
BoundingBox3Base(const std::vector<PointClass>& points)
{
if (points.empty())
throw std::invalid_argument("Empty point set supplied to BoundingBox3Base constructor");
typename std::vector<PointClass>::const_iterator it = points.begin();
this->min = *it;
this->max = *it;
for (++ it; it != points.end(); ++ it) {
this->min = this->min.cwiseMin(*it);
this->max = this->max.cwiseMax(*it);
}
this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2));
}
void merge(const PointClass &point);
void merge(const std::vector<PointClass> &points);
void merge(const BoundingBox3Base<PointClass> &bb);
PointClass size() const;
double radius() const;
void translate(coordf_t x, coordf_t y, coordf_t z) { assert(this->defined); PointClass v(x, y, z); this->min += v; this->max += v; }
void translate(const Vec3d &v) { this->min += v; this->max += v; }
void offset(coordf_t delta);
PointClass center() const;
coordf_t max_size() const;
bool contains(const PointClass &point) const {
return BoundingBoxBase<PointClass>::contains(point) && point(2) >= this->min(2) && point(2) <= this->max(2);
}
bool contains(const BoundingBox3Base<PointClass>& other) const {
return contains(other.min) && contains(other.max);
}
bool intersects(const BoundingBox3Base<PointClass>& other) const {
return (this->min(0) < other.max(0)) && (this->max(0) > other.min(0)) && (this->min(1) < other.max(1)) && (this->max(1) > other.min(1)) && (this->min(2) < other.max(2)) && (this->max(2) > other.min(2));
}
};
class BoundingBox : public BoundingBoxBase<Point>
{
public:
void polygon(Polygon* polygon) const;
Polygon polygon() const;
BoundingBox rotated(double angle) const;
BoundingBox rotated(double angle, const Point &center) const;
void rotate(double angle) { (*this) = this->rotated(angle); }
void rotate(double angle, const Point &center) { (*this) = this->rotated(angle, center); }
// Align the min corner to a grid of cell_size x cell_size cells,
// to encompass the original bounding box.
void align_to_grid(const coord_t cell_size);
BoundingBox() : BoundingBoxBase<Point>() {};
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {};
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {};
BoundingBox(const Lines &lines);
friend BoundingBox get_extents_rotated(const Points &points, double angle);
};
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
{
public:
BoundingBox3() : BoundingBox3Base<Vec3crd>() {};
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {};
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {};
};
class BoundingBoxf : public BoundingBoxBase<Vec2d>
{
public:
BoundingBoxf() : BoundingBoxBase<Vec2d>() {};
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {};
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {};
};
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
{
public:
BoundingBoxf3() : BoundingBox3Base<Vec3d>() {};
BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {};
BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {};
BoundingBoxf3 transformed(const Transform3d& matrix) const;
};
template<typename VT>
inline bool empty(const BoundingBoxBase<VT> &bb)
{
return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1);
}
template<typename VT>
inline bool empty(const BoundingBox3Base<VT> &bb)
{
return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2);
}
} // namespace Slic3r
#endif

View file

@ -0,0 +1,317 @@
#include "BridgeDetector.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include <algorithm>
namespace Slic3r {
BridgeDetector::BridgeDetector(
ExPolygon _expolygon,
const ExPolygonCollection &_lower_slices,
coord_t _spacing) :
// The original infill polygon, not inflated.
expolygons(expolygons_owned),
// All surfaces of the object supporting this region.
lower_slices(_lower_slices),
spacing(_spacing)
{
this->expolygons_owned.push_back(std::move(_expolygon));
initialize();
}
BridgeDetector::BridgeDetector(
const ExPolygons &_expolygons,
const ExPolygonCollection &_lower_slices,
coord_t _spacing) :
// The original infill polygon, not inflated.
expolygons(_expolygons),
// All surfaces of the object supporting this region.
lower_slices(_lower_slices),
spacing(_spacing)
{
initialize();
}
void BridgeDetector::initialize()
{
// 5 degrees stepping
this->resolution = PI/36.0;
// output angle not known
this->angle = -1.;
// Outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors.
Polygons grown = offset(to_polygons(this->expolygons), float(this->spacing));
// Detect possible anchoring edges of this bridging region.
// Detect what edges lie on lower slices by turning bridge contour and holes
// into polylines and then clipping them with each lower slice's contour.
// Currently _edges are only used to set a candidate direction of the bridge (see bridge_direction_candidates()).
this->_edges = intersection_pl(to_polylines(grown), this->lower_slices.contours());
#ifdef SLIC3R_DEBUG
printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size());
#endif
// detect anchors as intersection between our bridge expolygon and the lower slices
// safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges
this->_anchor_regions = intersection_ex(grown, to_polygons(this->lower_slices.expolygons), true);
/*
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("bridge.svg",
expolygons => [ $self->expolygon ],
red_expolygons => $self->lower_slices,
polylines => $self->_edges,
);
}
*/
}
bool BridgeDetector::detect_angle(double bridge_direction_override)
{
if (this->_edges.empty() || this->_anchor_regions.empty())
// The bridging region is completely in the air, there are no anchors available at the layer below.
return false;
std::vector<BridgeDirection> candidates;
if (bridge_direction_override == 0.) {
std::vector<double> angles = bridge_direction_candidates();
candidates.reserve(angles.size());
for (size_t i = 0; i < angles.size(); ++ i)
candidates.emplace_back(BridgeDirection(angles[i]));
} else
candidates.emplace_back(BridgeDirection(bridge_direction_override));
/* Outset the bridge expolygon by half the amount we used for detecting anchors;
we'll use this one to clip our test lines and be sure that their endpoints
are inside the anchors and not on their contours leading to false negatives. */
Polygons clip_area = offset(this->expolygons, 0.5f * float(this->spacing));
/* we'll now try several directions using a rudimentary visibility check:
bridge in several directions and then sum the length of lines having both
endpoints within anchors */
bool have_coverage = false;
for (size_t i_angle = 0; i_angle < candidates.size(); ++ i_angle)
{
const double angle = candidates[i_angle].angle;
Lines lines;
{
// Get an oriented bounding box around _anchor_regions.
BoundingBox bbox = get_extents_rotated(this->_anchor_regions, - angle);
// Cover the region with line segments.
lines.reserve((bbox.max(1) - bbox.min(1) + this->spacing) / this->spacing);
double s = sin(angle);
double c = cos(angle);
//FIXME Vojtech: The lines shall be spaced half the line width from the edge, but then
// some of the test cases fail. Need to adjust the test cases then?
// for (coord_t y = bbox.min(1) + this->spacing / 2; y <= bbox.max(1); y += this->spacing)
for (coord_t y = bbox.min(1); y <= bbox.max(1); y += this->spacing)
lines.push_back(Line(
Point((coord_t)round(c * bbox.min(0) - s * y), (coord_t)round(c * y + s * bbox.min(0))),
Point((coord_t)round(c * bbox.max(0) - s * y), (coord_t)round(c * y + s * bbox.max(0)))));
}
double total_length = 0;
double max_length = 0;
{
Lines clipped_lines = intersection_ln(lines, clip_area);
for (size_t i = 0; i < clipped_lines.size(); ++i) {
const Line &line = clipped_lines[i];
if (expolygons_contain(this->_anchor_regions, line.a) && expolygons_contain(this->_anchor_regions, line.b)) {
// This line could be anchored.
double len = line.length();
total_length += len;
max_length = std::max(max_length, len);
}
}
}
if (total_length == 0.)
continue;
have_coverage = true;
// Sum length of bridged lines.
candidates[i_angle].coverage = total_length;
/* The following produces more correct results in some cases and more broken in others.
TODO: investigate, as it looks more reliable than line clipping. */
// $directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0;
// max length of bridged lines
candidates[i_angle].max_length = max_length;
}
// if no direction produced coverage, then there's no bridge direction
if (! have_coverage)
return false;
// sort directions by coverage - most coverage first
std::sort(candidates.begin(), candidates.end());
// if any other direction is within extrusion width of coverage, prefer it if shorter
// TODO: There are two options here - within width of the angle with most coverage, or within width of the currently perferred?
size_t i_best = 0;
for (size_t i = 1; i < candidates.size() && candidates[i_best].coverage - candidates[i].coverage < this->spacing; ++ i)
if (candidates[i].max_length < candidates[i_best].max_length)
i_best = i;
this->angle = candidates[i_best].angle;
if (this->angle >= PI)
this->angle -= PI;
#ifdef SLIC3R_DEBUG
printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle));
#endif
return true;
}
std::vector<double> BridgeDetector::bridge_direction_candidates() const
{
// we test angles according to configured resolution
std::vector<double> angles;
for (int i = 0; i <= PI/this->resolution; ++i)
angles.push_back(i * this->resolution);
// we also test angles of each bridge contour
{
Lines lines = to_lines(this->expolygons);
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line)
angles.push_back(line->direction());
}
/* we also test angles of each open supporting edge
(this finds the optimal angle for C-shaped supports) */
for (const Polyline &edge : this->_edges)
if (edge.first_point() != edge.last_point())
angles.push_back(Line(edge.first_point(), edge.last_point()).direction());
// remove duplicates
double min_resolution = PI/180.0; // 1 degree
std::sort(angles.begin(), angles.end());
for (size_t i = 1; i < angles.size(); ++i) {
if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) {
angles.erase(angles.begin() + i);
--i;
}
}
/* compare first value with last one and remove the greatest one (PI)
in case they are parallel (PI, 0) */
if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution))
angles.pop_back();
return angles;
}
Polygons BridgeDetector::coverage(double angle) const
{
if (angle == -1)
angle = this->angle;
Polygons covered;
if (angle != -1) {
// Get anchors, convert them to Polygons and rotate them.
Polygons anchors = to_polygons(this->_anchor_regions);
polygons_rotate(anchors, PI/2.0 - angle);
for (ExPolygon expolygon : this->expolygons) {
// Clone our expolygon and rotate it so that we work with vertical lines.
expolygon.rotate(PI/2.0 - angle);
// Outset the bridge expolygon by half the amount we used for detecting anchors;
// we'll use this one to generate our trapezoids and be sure that their vertices
// are inside the anchors and not on their contours leading to false negatives.
for (ExPolygon &expoly : offset_ex(expolygon, 0.5f * float(this->spacing))) {
// Compute trapezoids according to a vertical orientation
Polygons trapezoids;
expoly.get_trapezoids2(&trapezoids, PI/2.0);
for (const Polygon &trapezoid : trapezoids) {
// not nice, we need a more robust non-numeric check
size_t n_supported = 0;
for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors))
if (supported_line.length() >= this->spacing)
++ n_supported;
if (n_supported >= 2)
covered.push_back(std::move(trapezoid));
}
}
}
// Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids
// instead of exact overlaps.
covered = union_(covered);
// Intersect trapezoids with actual bridge area to remove extra margins and append it to result.
polygons_rotate(covered, -(PI/2.0 - angle));
covered = intersection(covered, to_polygons(this->expolygons));
#if 0
{
my @lines = map @{$_->lines}, @$trapezoids;
$_->rotate(-(PI/2 - $angle), [0,0]) for @lines;
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"coverage_" . rad2deg($angle) . ".svg",
expolygons => [$self->expolygon],
green_expolygons => $self->_anchor_regions,
red_expolygons => $coverage,
lines => \@lines,
);
}
#endif
}
return covered;
}
/* This method returns the bridge edges (as polylines) that are not supported
but would allow the entire bridge area to be bridged with detected angle
if supported too */
void
BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const
{
if (angle == -1) angle = this->angle;
if (angle == -1) return;
Polygons grown_lower = offset(this->lower_slices.expolygons, float(this->spacing));
for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) {
// get unsupported bridge edges (both contour and holes)
Lines unsupported_lines = to_lines(diff_pl(to_polylines(*it_expoly), grown_lower));
/* Split into individual segments and filter out edges parallel to the bridging angle
TODO: angle tolerance should probably be based on segment length and flow width,
so that we build supports whenever there's a chance that at least one or two bridge
extrusions would be anchored within such length (i.e. a slightly non-parallel bridging
direction might still benefit from anchors if long enough)
double angle_tolerance = PI / 180.0 * 5.0; */
for (const Line &line : unsupported_lines)
if (! Slic3r::Geometry::directions_parallel(line.direction(), angle)) {
unsupported->emplace_back(Polyline());
unsupported->back().points.emplace_back(line.a);
unsupported->back().points.emplace_back(line.b);
}
}
/*
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"unsupported_" . rad2deg($angle) . ".svg",
expolygons => [$self->expolygon],
green_expolygons => $self->_anchor_regions,
red_expolygons => union_ex($grown_lower),
no_arrows => 1,
polylines => \@bridge_edges,
red_polylines => $unsupported,
);
}
*/
}
Polylines
BridgeDetector::unsupported_edges(double angle) const
{
Polylines pp;
this->unsupported_edges(angle, &pp);
return pp;
}
}

View file

@ -0,0 +1,69 @@
#ifndef slic3r_BridgeDetector_hpp_
#define slic3r_BridgeDetector_hpp_
#include "libslic3r.h"
#include "ExPolygon.hpp"
#include "ExPolygonCollection.hpp"
#include <string>
namespace Slic3r {
// The bridge detector optimizes a direction of bridges over a region or a set of regions.
// A bridge direction is considered optimal, if the length of the lines strang over the region is maximal.
// This is optimal if the bridge is supported in a single direction only, but
// it may not likely be optimal, if the bridge region is supported from all sides. Then an optimal
// solution would find a direction with shortest bridges.
// The bridge orientation is measured CCW from the X axis.
class BridgeDetector {
public:
// The non-grown holes.
const ExPolygons &expolygons;
// In case the caller gaves us the input polygons by a value, make a copy.
ExPolygons expolygons_owned;
// Lower slices, all regions.
const ExPolygonCollection &lower_slices;
// Scaled extrusion width of the infill.
coord_t spacing;
// Angle resolution for the brute force search of the best bridging angle.
double resolution;
// The final optimal angle.
double angle;
BridgeDetector(ExPolygon _expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width);
BridgeDetector(const ExPolygons &_expolygons, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width);
// If bridge_direction_override != 0, then the angle is used instead of auto-detect.
bool detect_angle(double bridge_direction_override = 0.);
Polygons coverage(double angle = -1) const;
void unsupported_edges(double angle, Polylines* unsupported) const;
Polylines unsupported_edges(double angle = -1) const;
private:
// Suppress warning "assignment operator could not be generated"
BridgeDetector& operator=(const BridgeDetector &);
void initialize();
struct BridgeDirection {
BridgeDirection(double a = -1.) : angle(a), coverage(0.), max_length(0.) {}
// the best direction is the one causing most lines to be bridged (thus most coverage)
bool operator<(const BridgeDirection &other) const {
// Initial sort by coverage only - comparator must obey strict weak ordering
return this->coverage > other.coverage;
};
double angle;
double coverage;
double max_length;
};
// Get possible briging direction candidates.
std::vector<double> bridge_direction_candidates() const;
// Open lines representing the supporting edges.
Polylines _edges;
// Closed polygons representing the supporting areas.
ExPolygons _anchor_regions;
};
}
#endif

View file

@ -0,0 +1,173 @@
project(libslic3r)
cmake_minimum_required(VERSION 2.6)
add_library(libslic3r STATIC
BoundingBox.cpp
BoundingBox.hpp
BridgeDetector.cpp
BridgeDetector.hpp
ClipperUtils.cpp
ClipperUtils.hpp
Config.cpp
Config.hpp
EdgeGrid.cpp
EdgeGrid.hpp
ExPolygon.cpp
ExPolygon.hpp
ExPolygonCollection.cpp
ExPolygonCollection.hpp
Extruder.cpp
Extruder.hpp
ExtrusionEntity.cpp
ExtrusionEntity.hpp
ExtrusionEntityCollection.cpp
ExtrusionEntityCollection.hpp
ExtrusionSimulator.cpp
ExtrusionSimulator.hpp
FileParserError.hpp
Fill/Fill.cpp
Fill/Fill.hpp
Fill/Fill3DHoneycomb.cpp
Fill/Fill3DHoneycomb.hpp
Fill/FillBase.cpp
Fill/FillBase.hpp
Fill/FillConcentric.cpp
Fill/FillConcentric.hpp
Fill/FillHoneycomb.cpp
Fill/FillHoneycomb.hpp
Fill/FillGyroid.cpp
Fill/FillGyroid.hpp
Fill/FillPlanePath.cpp
Fill/FillPlanePath.hpp
Fill/FillRectilinear.cpp
Fill/FillRectilinear.hpp
Fill/FillRectilinear2.cpp
Fill/FillRectilinear2.hpp
Fill/FillRectilinear3.cpp
Fill/FillRectilinear3.hpp
Flow.cpp
Flow.hpp
Format/3mf.cpp
Format/3mf.hpp
Format/AMF.cpp
Format/AMF.hpp
Format/OBJ.cpp
Format/OBJ.hpp
Format/objparser.cpp
Format/objparser.hpp
Format/PRUS.cpp
Format/PRUS.hpp
Format/STL.cpp
Format/STL.hpp
GCode/Analyzer.cpp
GCode/Analyzer.hpp
GCode/CoolingBuffer.cpp
GCode/CoolingBuffer.hpp
GCode/PostProcessor.cpp
GCode/PostProcessor.hpp
GCode/PressureEqualizer.cpp
GCode/PressureEqualizer.hpp
GCode/PreviewData.cpp
GCode/PreviewData.hpp
GCode/PrintExtents.cpp
GCode/PrintExtents.hpp
GCode/SpiralVase.cpp
GCode/SpiralVase.hpp
GCode/ToolOrdering.cpp
GCode/ToolOrdering.hpp
GCode/WipeTower.hpp
GCode/WipeTowerPrusaMM.cpp
GCode/WipeTowerPrusaMM.hpp
GCode.cpp
GCode.hpp
GCodeReader.cpp
GCodeReader.hpp
GCodeSender.cpp
GCodeSender.hpp
GCodeTimeEstimator.cpp
GCodeTimeEstimator.hpp
GCodeWriter.cpp
GCodeWriter.hpp
Geometry.cpp
Geometry.hpp
Int128.hpp
# KdTree.hpp
Layer.cpp
Layer.hpp
LayerRegion.cpp
libslic3r.h
Line.cpp
Line.hpp
Model.cpp
Model.hpp
ModelArrange.hpp
MotionPlanner.cpp
MotionPlanner.hpp
MultiPoint.cpp
MultiPoint.hpp
MutablePriorityQueue.hpp
PerimeterGenerator.cpp
PerimeterGenerator.hpp
PlaceholderParser.cpp
PlaceholderParser.hpp
Point.cpp
Point.hpp
Polygon.cpp
Polygon.hpp
Polyline.cpp
Polyline.hpp
PolylineCollection.cpp
PolylineCollection.hpp
Print.cpp
Print.hpp
PrintExport.hpp
PrintConfig.cpp
PrintConfig.hpp
PrintObject.cpp
PrintRegion.cpp
Rasterizer/Rasterizer.hpp
Rasterizer/Rasterizer.cpp
Slicing.cpp
Slicing.hpp
SlicingAdaptive.cpp
SlicingAdaptive.hpp
SupportMaterial.cpp
SupportMaterial.hpp
Surface.cpp
Surface.hpp
SurfaceCollection.cpp
SurfaceCollection.hpp
SVG.cpp
SVG.hpp
Technologies.hpp
TriangleMesh.cpp
TriangleMesh.hpp
SLABasePool.hpp
SLABasePool.cpp
utils.cpp
Utils.hpp
)
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB ${PNG_DEFINITIONS})
target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES} ${PNG_INCLUDE_DIRS})
target_link_libraries(libslic3r
${LIBNEST2D_LIBRARIES}
admesh
miniz
${Boost_LIBRARIES}
clipper
nowide
${EXPAT_LIBRARIES}
${GLEW_LIBRARIES}
${PNG_LIBRARIES}
polypartition
poly2tri
qhull
semver
${TBB_LIBRARIES}
# ${wxWidgets_LIBRARIES}
)
if(SLIC3R_PROFILE)
target_link_libraries(slic3r Shiny)
endif()

View file

@ -0,0 +1,798 @@
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
// #define CLIPPER_UTILS_DEBUG
#ifdef CLIPPER_UTILS_DEBUG
#include "SVG.hpp"
#endif /* CLIPPER_UTILS_DEBUG */
#include <Shiny/Shiny.h>
#define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f)
namespace Slic3r {
#ifdef CLIPPER_UTILS_DEBUG
bool clipper_export_enabled = false;
// For debugging the Clipper library, for providing bug reports to the Clipper author.
bool export_clipper_input_polygons_bin(const char *path, const ClipperLib::Paths &input_subject, const ClipperLib::Paths &input_clip)
{
FILE *pfile = fopen(path, "wb");
if (pfile == NULL)
return false;
uint32_t sz = uint32_t(input_subject.size());
fwrite(&sz, 1, sizeof(sz), pfile);
for (size_t i = 0; i < input_subject.size(); ++i) {
const ClipperLib::Path &path = input_subject[i];
sz = uint32_t(path.size());
::fwrite(&sz, 1, sizeof(sz), pfile);
::fwrite(path.data(), sizeof(ClipperLib::IntPoint), sz, pfile);
}
sz = uint32_t(input_clip.size());
::fwrite(&sz, 1, sizeof(sz), pfile);
for (size_t i = 0; i < input_clip.size(); ++i) {
const ClipperLib::Path &path = input_clip[i];
sz = uint32_t(path.size());
::fwrite(&sz, 1, sizeof(sz), pfile);
::fwrite(path.data(), sizeof(ClipperLib::IntPoint), sz, pfile);
}
::fclose(pfile);
return true;
err:
::fclose(pfile);
return false;
}
#endif /* CLIPPER_UTILS_DEBUG */
void scaleClipperPolygon(ClipperLib::Path &polygon)
{
PROFILE_FUNC();
for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) {
pit->X <<= CLIPPER_OFFSET_POWER_OF_2;
pit->Y <<= CLIPPER_OFFSET_POWER_OF_2;
}
}
void scaleClipperPolygons(ClipperLib::Paths &polygons)
{
PROFILE_FUNC();
for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it)
for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) {
pit->X <<= CLIPPER_OFFSET_POWER_OF_2;
pit->Y <<= CLIPPER_OFFSET_POWER_OF_2;
}
}
void unscaleClipperPolygon(ClipperLib::Path &polygon)
{
PROFILE_FUNC();
for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) {
pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
pit->X >>= CLIPPER_OFFSET_POWER_OF_2;
pit->Y >>= CLIPPER_OFFSET_POWER_OF_2;
}
}
void unscaleClipperPolygons(ClipperLib::Paths &polygons)
{
PROFILE_FUNC();
for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it)
for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) {
pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
pit->X >>= CLIPPER_OFFSET_POWER_OF_2;
pit->Y >>= CLIPPER_OFFSET_POWER_OF_2;
}
}
//-----------------------------------------------------------
// legacy code from Clipper documentation
void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, ExPolygons* expolygons)
{
size_t cnt = expolygons->size();
expolygons->resize(cnt + 1);
(*expolygons)[cnt].contour = ClipperPath_to_Slic3rPolygon(polynode.Contour);
(*expolygons)[cnt].holes.resize(polynode.ChildCount());
for (int i = 0; i < polynode.ChildCount(); ++i)
{
(*expolygons)[cnt].holes[i] = ClipperPath_to_Slic3rPolygon(polynode.Childs[i]->Contour);
//Add outer polygons contained by (nested within) holes ...
for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++j)
AddOuterPolyNodeToExPolygons(*polynode.Childs[i]->Childs[j], expolygons);
}
}
ExPolygons
PolyTreeToExPolygons(ClipperLib::PolyTree& polytree)
{
ExPolygons retval;
for (int i = 0; i < polytree.ChildCount(); ++i)
AddOuterPolyNodeToExPolygons(*polytree.Childs[i], &retval);
return retval;
}
//-----------------------------------------------------------
Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input)
{
Polygon retval;
for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit)
retval.points.push_back(Point( (*pit).X, (*pit).Y ));
return retval;
}
Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input)
{
Polyline retval;
for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit)
retval.points.push_back(Point( (*pit).X, (*pit).Y ));
return retval;
}
Slic3r::Polygons ClipperPaths_to_Slic3rPolygons(const ClipperLib::Paths &input)
{
Slic3r::Polygons retval;
retval.reserve(input.size());
for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it)
retval.push_back(ClipperPath_to_Slic3rPolygon(*it));
return retval;
}
Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input)
{
Slic3r::Polylines retval;
retval.reserve(input.size());
for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it)
retval.push_back(ClipperPath_to_Slic3rPolyline(*it));
return retval;
}
ExPolygons
ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input)
{
// init Clipper
ClipperLib::Clipper clipper;
clipper.Clear();
// perform union
clipper.AddPaths(input, ClipperLib::ptSubject, true);
ClipperLib::PolyTree polytree;
clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero
// write to ExPolygons object
return PolyTreeToExPolygons(polytree);
}
ClipperLib::Path
Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input)
{
ClipperLib::Path retval;
for (Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit)
retval.push_back(ClipperLib::IntPoint( (*pit)(0), (*pit)(1) ));
return retval;
}
ClipperLib::Path
Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input)
{
ClipperLib::Path output;
output.reserve(input.points.size());
for (Slic3r::Points::const_reverse_iterator pit = input.points.rbegin(); pit != input.points.rend(); ++pit)
output.push_back(ClipperLib::IntPoint( (*pit)(0), (*pit)(1) ));
return output;
}
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input)
{
ClipperLib::Paths retval;
for (Polygons::const_iterator it = input.begin(); it != input.end(); ++it)
retval.push_back(Slic3rMultiPoint_to_ClipperPath(*it));
return retval;
}
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input)
{
ClipperLib::Paths retval;
for (Polylines::const_iterator it = input.begin(); it != input.end(); ++it)
retval.push_back(Slic3rMultiPoint_to_ClipperPath(*it));
return retval;
}
ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{
// scale input
scaleClipperPolygons(input);
// perform offset
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE);
co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.AddPaths(input, joinType, endType);
ClipperLib::Paths retval;
co.Execute(retval, delta_scaled);
// unscale output
unscaleClipperPolygons(retval);
return retval;
}
ClipperLib::Paths _offset(ClipperLib::Path &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit)
{
ClipperLib::Paths paths;
paths.push_back(std::move(input));
return _offset(std::move(paths), endType, delta, joinType, miterLimit);
}
// This is a safe variant of the polygon offset, tailored for a single ExPolygon:
// a single polygon with multiple non-overlapping holes.
// Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours.
ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta,
ClipperLib::JoinType joinType, double miterLimit)
{
// printf("new ExPolygon offset\n");
// 1) Offset the outer contour.
const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE);
ClipperLib::Paths contours;
{
ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(expolygon.contour);
scaleClipperPolygon(input);
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
co.Execute(contours, delta_scaled);
}
// 2) Offset the holes one by one, collect the results.
ClipperLib::Paths holes;
{
holes.reserve(expolygon.holes.size());
for (Polygons::const_iterator it_hole = expolygon.holes.begin(); it_hole != expolygon.holes.end(); ++ it_hole) {
ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole);
scaleClipperPolygon(input);
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths out;
co.Execute(out, - delta_scaled);
holes.insert(holes.end(), out.begin(), out.end());
}
}
// 3) Subtract holes from the contours.
ClipperLib::Paths output;
if (holes.empty()) {
output = std::move(contours);
} else {
ClipperLib::Clipper clipper;
clipper.Clear();
clipper.AddPaths(contours, ClipperLib::ptSubject, true);
clipper.AddPaths(holes, ClipperLib::ptClip, true);
clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
}
// 4) Unscale the output.
unscaleClipperPolygons(output);
return output;
}
// This is a safe variant of the polygons offset, tailored for multiple ExPolygons.
// It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours.
// Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united.
ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta,
ClipperLib::JoinType joinType, double miterLimit)
{
const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE);
// Offsetted ExPolygons before they are united.
ClipperLib::Paths contours_cummulative;
contours_cummulative.reserve(expolygons.size());
// How many non-empty offsetted expolygons were actually collected into contours_cummulative?
// If only one, then there is no need to do a final union.
size_t expolygons_collected = 0;
for (Slic3r::ExPolygons::const_iterator it_expoly = expolygons.begin(); it_expoly != expolygons.end(); ++ it_expoly) {
// 1) Offset the outer contour.
ClipperLib::Paths contours;
{
ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(it_expoly->contour);
scaleClipperPolygon(input);
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
co.Execute(contours, delta_scaled);
}
if (contours.empty())
// No need to try to offset the holes.
continue;
if (it_expoly->holes.empty()) {
// No need to subtract holes from the offsetted expolygon, we are done.
contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end());
++ expolygons_collected;
} else {
// 2) Offset the holes one by one, collect the offsetted holes.
ClipperLib::Paths holes;
{
for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) {
ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole);
scaleClipperPolygon(input);
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths out;
co.Execute(out, - delta_scaled);
holes.insert(holes.end(), out.begin(), out.end());
}
}
// 3) Subtract holes from the contours.
if (holes.empty()) {
// No hole remaining after an offset. Just copy the outer contour.
contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end());
++ expolygons_collected;
} else if (delta < 0) {
// Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
// Subtract the offsetted holes from the offsetted contours.
ClipperLib::Clipper clipper;
clipper.Clear();
clipper.AddPaths(contours, ClipperLib::ptSubject, true);
clipper.AddPaths(holes, ClipperLib::ptClip, true);
ClipperLib::Paths output;
clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
if (! output.empty()) {
contours_cummulative.insert(contours_cummulative.end(), output.begin(), output.end());
++ expolygons_collected;
} else {
// The offsetted holes have eaten up the offsetted outer contour.
}
} else {
// Positive offset. As long as the Clipper offset does what one expects it to do, the offsetted hole will have a smaller
// area than the original hole or even disappear, therefore there will be no new intersections.
// Just collect the reversed holes.
contours_cummulative.reserve(contours.size() + holes.size());
contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end());
// Reverse the holes in place.
for (size_t i = 0; i < holes.size(); ++ i)
std::reverse(holes[i].begin(), holes[i].end());
contours_cummulative.insert(contours_cummulative.end(), holes.begin(), holes.end());
++ expolygons_collected;
}
}
}
// 4) Unite the offsetted expolygons.
ClipperLib::Paths output;
if (expolygons_collected > 1 && delta > 0) {
// There is a chance that the outwards offsetted expolygons may intersect. Perform a union.
ClipperLib::Clipper clipper;
clipper.Clear();
clipper.AddPaths(contours_cummulative, ClipperLib::ptSubject, true);
clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
} else {
// Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output.
output = std::move(contours_cummulative);
}
// 4) Unscale the output.
unscaleClipperPolygons(output);
return output;
}
ClipperLib::Paths
_offset2(const Polygons &polygons, const float delta1, const float delta2,
const ClipperLib::JoinType joinType, const double miterLimit)
{
// read input
ClipperLib::Paths input = Slic3rMultiPoints_to_ClipperPaths(polygons);
// scale input
scaleClipperPolygons(input);
// prepare ClipperOffset object
ClipperLib::ClipperOffset co;
if (joinType == jtRound) {
co.ArcTolerance = miterLimit;
} else {
co.MiterLimit = miterLimit;
}
float delta_scaled1 = delta1 * float(CLIPPER_OFFSET_SCALE);
float delta_scaled2 = delta2 * float(CLIPPER_OFFSET_SCALE);
co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR);
// perform first offset
ClipperLib::Paths output1;
co.AddPaths(input, joinType, ClipperLib::etClosedPolygon);
co.Execute(output1, delta_scaled1);
// perform second offset
co.Clear();
co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths retval;
co.Execute(retval, delta_scaled2);
// unscale output
unscaleClipperPolygons(retval);
return retval;
}
Polygons
offset2(const Polygons &polygons, const float delta1, const float delta2,
const ClipperLib::JoinType joinType, const double miterLimit)
{
// perform offset
ClipperLib::Paths output = _offset2(polygons, delta1, delta2, joinType, miterLimit);
// convert into ExPolygons
return ClipperPaths_to_Slic3rPolygons(output);
}
ExPolygons
offset2_ex(const Polygons &polygons, const float delta1, const float delta2,
const ClipperLib::JoinType joinType, const double miterLimit)
{
// perform offset
ClipperLib::Paths output = _offset2(polygons, delta1, delta2, joinType, miterLimit);
// convert into ExPolygons
return ClipperPaths_to_Slic3rExPolygons(output);
}
//FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper
// conversions and unnecessary Clipper calls.
ExPolygons offset2_ex(const ExPolygons &expolygons, const float delta1,
const float delta2, ClipperLib::JoinType joinType, double miterLimit)
{
Polygons polys;
for (const ExPolygon &expoly : expolygons)
append(polys,
offset(offset_ex(expoly, delta1, joinType, miterLimit),
delta2, joinType, miterLimit));
return union_ex(polys);
}
template <class T>
T
_clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject,
const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_)
{
// read input
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip);
// perform safety offset
if (safety_offset_) {
if (clipType == ClipperLib::ctUnion) {
safety_offset(&input_subject);
} else {
safety_offset(&input_clip);
}
}
// init Clipper
ClipperLib::Clipper clipper;
clipper.Clear();
// add polygons
clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
clipper.AddPaths(input_clip, ClipperLib::ptClip, true);
// perform operation
T retval;
clipper.Execute(clipType, retval, fillType, fillType);
return retval;
}
// Fix of #117: A large fractal pyramid takes ages to slice
// The Clipper library has difficulties processing overlapping polygons.
// Namely, the function Clipper::JoinCommonEdges() has potentially a terrible time complexity if the output
// of the operation is of the PolyTree type.
// This function implmenets a following workaround:
// 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time.
// 2) Run Clipper Union once again to extract the PolyTree from the result of 1).
inline ClipperLib::PolyTree _clipper_do_polytree2(const ClipperLib::ClipType clipType, const Polygons &subject,
const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_)
{
// read input
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip);
// perform safety offset
if (safety_offset_)
safety_offset((clipType == ClipperLib::ctUnion) ? &input_subject : &input_clip);
ClipperLib::Clipper clipper;
clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
clipper.AddPaths(input_clip, ClipperLib::ptClip, true);
// Perform the operation with the output to input_subject.
// This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library
// if there are overapping edges.
clipper.Execute(clipType, input_subject, fillType, fillType);
// Perform an additional Union operation to generate the PolyTree ordering.
clipper.Clear();
clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
ClipperLib::PolyTree retval;
clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType);
return retval;
}
ClipperLib::PolyTree _clipper_do_pl(const ClipperLib::ClipType clipType, const Polylines &subject,
const Polygons &clip, const ClipperLib::PolyFillType fillType,
const bool safety_offset_)
{
// read input
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip);
// perform safety offset
if (safety_offset_) safety_offset(&input_clip);
// init Clipper
ClipperLib::Clipper clipper;
clipper.Clear();
// add polygons
clipper.AddPaths(input_subject, ClipperLib::ptSubject, false);
clipper.AddPaths(input_clip, ClipperLib::ptClip, true);
// perform operation
ClipperLib::PolyTree retval;
clipper.Execute(clipType, retval, fillType, fillType);
return retval;
}
Polygons _clipper(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_)
{
return ClipperPaths_to_Slic3rPolygons(_clipper_do<ClipperLib::Paths>(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_));
}
ExPolygons _clipper_ex(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_)
{
ClipperLib::PolyTree polytree = _clipper_do_polytree2(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_);
return PolyTreeToExPolygons(polytree);
}
Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polylines &subject, const Polygons &clip, bool safety_offset_)
{
ClipperLib::Paths output;
ClipperLib::PolyTreeToPaths(_clipper_do_pl(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_), output);
return ClipperPaths_to_Slic3rPolylines(output);
}
Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_)
{
// transform input polygons into polylines
Polylines polylines;
polylines.reserve(subject.size());
for (Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon)
polylines.push_back(*polygon); // implicit call to split_at_first_point()
// perform clipping
Polylines retval = _clipper_pl(clipType, polylines, clip, safety_offset_);
/* If the split_at_first_point() call above happens to split the polygon inside the clipping area
we would get two consecutive polylines instead of a single one, so we go through them in order
to recombine continuous polylines. */
for (size_t i = 0; i < retval.size(); ++i) {
for (size_t j = i+1; j < retval.size(); ++j) {
if (retval[i].points.back() == retval[j].points.front()) {
/* If last point of i coincides with first point of j,
append points of j to i and delete j */
retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end());
retval.erase(retval.begin() + j);
--j;
} else if (retval[i].points.front() == retval[j].points.back()) {
/* If first point of i coincides with last point of j,
prepend points of j to i and delete j */
retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1);
retval.erase(retval.begin() + j);
--j;
} else if (retval[i].points.front() == retval[j].points.front()) {
/* Since Clipper does not preserve orientation of polylines,
also check the case when first point of i coincides with first point of j. */
retval[j].reverse();
retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1);
retval.erase(retval.begin() + j);
--j;
} else if (retval[i].points.back() == retval[j].points.back()) {
/* Since Clipper does not preserve orientation of polylines,
also check the case when last point of i coincides with last point of j. */
retval[j].reverse();
retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end());
retval.erase(retval.begin() + j);
--j;
}
}
}
return retval;
}
Lines
_clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip,
bool safety_offset_)
{
// convert Lines to Polylines
Polylines polylines;
polylines.reserve(subject.size());
for (const Line &line : subject)
polylines.emplace_back(Polyline(line.a, line.b));
// perform operation
polylines = _clipper_pl(clipType, polylines, clip, safety_offset_);
// convert Polylines to Lines
Lines retval;
for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline)
retval.push_back(*polyline);
return retval;
}
ClipperLib::PolyTree
union_pt(const Polygons &subject, bool safety_offset_)
{
return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
}
Polygons
union_pt_chained(const Polygons &subject, bool safety_offset_)
{
ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_);
Polygons retval;
traverse_pt(polytree.Childs, &retval);
return retval;
}
void traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval)
{
/* use a nearest neighbor search to order these children
TODO: supply start_near to chained_path() too? */
// collect ordering points
Points ordering_points;
ordering_points.reserve(nodes.size());
for (ClipperLib::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
Point p((*it)->Contour.front().X, (*it)->Contour.front().Y);
ordering_points.push_back(p);
}
// perform the ordering
ClipperLib::PolyNodes ordered_nodes;
Slic3r::Geometry::chained_path_items(ordering_points, nodes, ordered_nodes);
// push results recursively
for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) {
// traverse the next depth
traverse_pt((*it)->Childs, retval);
retval->push_back(ClipperPath_to_Slic3rPolygon((*it)->Contour));
if ((*it)->IsHole()) retval->back().reverse(); // ccw
}
}
Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear)
{
// convert into Clipper polygons
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
ClipperLib::Paths output;
if (preserve_collinear) {
ClipperLib::Clipper c;
c.PreserveCollinear(true);
c.StrictlySimple(true);
c.AddPaths(input_subject, ClipperLib::ptSubject, true);
c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
} else {
ClipperLib::SimplifyPolygons(input_subject, output, ClipperLib::pftNonZero);
}
// convert into Slic3r polygons
return ClipperPaths_to_Slic3rPolygons(output);
}
ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear)
{
if (! preserve_collinear)
return union_ex(simplify_polygons(subject, false));
// convert into Clipper polygons
ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
ClipperLib::PolyTree polytree;
ClipperLib::Clipper c;
c.PreserveCollinear(true);
c.StrictlySimple(true);
c.AddPaths(input_subject, ClipperLib::ptSubject, true);
c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
// convert into ExPolygons
return PolyTreeToExPolygons(polytree);
}
void safety_offset(ClipperLib::Paths* paths)
{
PROFILE_FUNC();
// scale input
scaleClipperPolygons(*paths);
// perform offset (delta = scale 1e-05)
ClipperLib::ClipperOffset co;
#ifdef CLIPPER_UTILS_DEBUG
if (clipper_export_enabled) {
static int iRun = 0;
export_clipper_input_polygons_bin(debug_out_path("safety_offset-polygons-%d", ++iRun).c_str(), *paths, ClipperLib::Paths());
}
#endif /* CLIPPER_UTILS_DEBUG */
ClipperLib::Paths out;
for (size_t i = 0; i < paths->size(); ++ i) {
ClipperLib::Path &path = (*paths)[i];
co.Clear();
co.MiterLimit = 2;
bool ccw = ClipperLib::Orientation(path);
if (! ccw)
std::reverse(path.begin(), path.end());
{
PROFILE_BLOCK(safety_offset_AddPaths);
co.AddPath((*paths)[i], ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
}
{
PROFILE_BLOCK(safety_offset_Execute);
// offset outside by 10um
ClipperLib::Paths out_this;
co.Execute(out_this, ccw ? 10.f * float(CLIPPER_OFFSET_SCALE) : -10.f * float(CLIPPER_OFFSET_SCALE));
if (! ccw) {
// Reverse the resulting contours once again.
for (ClipperLib::Paths::iterator it = out_this.begin(); it != out_this.end(); ++ it)
std::reverse(it->begin(), it->end());
}
if (out.empty())
out = std::move(out_this);
else
std::move(std::begin(out_this), std::end(out_this), std::back_inserter(out));
}
}
*paths = std::move(out);
// unscale output
unscaleClipperPolygons(*paths);
}
Polygons top_level_islands(const Slic3r::Polygons &polygons)
{
// init Clipper
ClipperLib::Clipper clipper;
clipper.Clear();
// perform union
clipper.AddPaths(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::ptSubject, true);
ClipperLib::PolyTree polytree;
clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd);
// Convert only the top level islands to the output.
Polygons out;
out.reserve(polytree.ChildCount());
for (int i = 0; i < polytree.ChildCount(); ++i)
out.push_back(ClipperPath_to_Slic3rPolygon(polytree.Childs[i]->Contour));
return out;
}
}

View file

@ -0,0 +1,231 @@
#ifndef slic3r_ClipperUtils_hpp_
#define slic3r_ClipperUtils_hpp_
#include <libslic3r.h>
#include "clipper.hpp"
#include "ExPolygon.hpp"
#include "Polygon.hpp"
#include "Surface.hpp"
// import these wherever we're included
using ClipperLib::jtMiter;
using ClipperLib::jtRound;
using ClipperLib::jtSquare;
// Factor to convert from coord_t (which is int32) to an int64 type used by the Clipper library
// for general offsetting (the offset(), offset2(), offset_ex() functions) and for the safety offset,
// which is optionally executed by other functions (union, intersection, diff).
// This scaling (cca 130t) is applied over the usual SCALING_FACTOR.
// By the way, is the scalling for offset needed at all?
// The reason to apply this scaling may be to match the resolution of the double mantissa.
#define CLIPPER_OFFSET_POWER_OF_2 17
// 2^17=131072
#define CLIPPER_OFFSET_SCALE (1 << CLIPPER_OFFSET_POWER_OF_2)
#define CLIPPER_OFFSET_SCALE_ROUNDING_DELTA ((1 << (CLIPPER_OFFSET_POWER_OF_2 - 1)) - 1)
#define CLIPPER_MAX_COORD_UNSCALED (ClipperLib::hiRange / CLIPPER_OFFSET_SCALE)
namespace Slic3r {
//-----------------------------------------------------------
// legacy code from Clipper documentation
void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, Slic3r::ExPolygons& expolygons);
void PolyTreeToExPolygons(ClipperLib::PolyTree& polytree, Slic3r::ExPolygons& expolygons);
//-----------------------------------------------------------
ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input);
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input);
ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input);
Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input);
Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input);
Slic3r::Polygons ClipperPaths_to_Slic3rPolygons(const ClipperLib::Paths &input);
Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input);
Slic3r::ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input);
// offset Polygons
ClipperLib::Paths _offset(ClipperLib::Path &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit);
ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit);
inline Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3)
{ return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polygon), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); }
inline Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3)
{ return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); }
// offset Polylines
inline Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3)
{ return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polyline), ClipperLib::etOpenButt, delta, joinType, miterLimit)); }
inline Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3)
{ return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); }
// offset expolygons and surfaces
ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit);
ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit);
inline Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3)
{ return ClipperPaths_to_Slic3rPolygons(_offset(expolygon, delta, joinType, miterLimit)); }
inline Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3)
{ return ClipperPaths_to_Slic3rPolygons(_offset(expolygons, delta, joinType, miterLimit)); }
inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polygon), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); }
inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); }
inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); }
inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3)
{ return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); }
ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1,
const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1,
const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1,
const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1,
const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter,
double miterLimit = 3);
Slic3r::Polygons _clipper(ClipperLib::ClipType clipType,
const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
Slic3r::ExPolygons _clipper_ex(ClipperLib::ClipType clipType,
const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType,
const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType,
const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType,
const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
// diff
inline Slic3r::Polygons
diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
return _clipper(ClipperLib::ctDifference, subject, clip, safety_offset_);
}
inline Slic3r::ExPolygons
diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
return _clipper_ex(ClipperLib::ctDifference, subject, clip, safety_offset_);
}
inline Slic3r::ExPolygons
diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false)
{
return _clipper_ex(ClipperLib::ctDifference, to_polygons(subject), to_polygons(clip), safety_offset_);
}
inline Slic3r::Polygons
diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false)
{
return _clipper(ClipperLib::ctDifference, to_polygons(subject), to_polygons(clip), safety_offset_);
}
inline Slic3r::Polylines
diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
return _clipper_pl(ClipperLib::ctDifference, subject, clip, safety_offset_);
}
inline Slic3r::Polylines
diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
return _clipper_pl(ClipperLib::ctDifference, subject, clip, safety_offset_);
}
inline Slic3r::Lines
diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
return _clipper_ln(ClipperLib::ctDifference, subject, clip, safety_offset_);
}
// intersection
inline Slic3r::Polygons
intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
return _clipper(ClipperLib::ctIntersection, subject, clip, safety_offset_);
}
inline Slic3r::ExPolygons
intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
return _clipper_ex(ClipperLib::ctIntersection, subject, clip, safety_offset_);
}
inline Slic3r::ExPolygons
intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false)
{
return _clipper_ex(ClipperLib::ctIntersection, to_polygons(subject), to_polygons(clip), safety_offset_);
}
inline Slic3r::Polygons
intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false)
{
return _clipper(ClipperLib::ctIntersection, to_polygons(subject), to_polygons(clip), safety_offset_);
}
inline Slic3r::Polylines
intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
return _clipper_pl(ClipperLib::ctIntersection, subject, clip, safety_offset_);
}
inline Slic3r::Polylines
intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
return _clipper_pl(ClipperLib::ctIntersection, subject, clip, safety_offset_);
}
inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
return _clipper_ln(ClipperLib::ctIntersection, subject, clip, safety_offset_);
}
inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false)
{
Slic3r::Lines lines;
lines.emplace_back(subject);
return _clipper_ln(ClipperLib::ctIntersection, lines, clip, safety_offset_);
}
// union
inline Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool safety_offset_ = false)
{
return _clipper(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_);
}
inline Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool safety_offset_ = false)
{
return _clipper(ClipperLib::ctUnion, subject, subject2, safety_offset_);
}
inline Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool safety_offset_ = false)
{
return _clipper_ex(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_);
}
inline Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, bool safety_offset_ = false)
{
return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_);
}
inline Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_offset_ = false)
{
return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_);
}
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false);
Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false);
void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval);
/* OTHER */
Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false);
Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject, bool preserve_collinear = false);
void safety_offset(ClipperLib::Paths* paths);
Polygons top_level_islands(const Slic3r::Polygons &polygons);
}
#endif

602
src/libslic3r/Config.cpp Normal file
View file

@ -0,0 +1,602 @@
#include "Config.hpp"
#include "Utils.hpp"
#include <assert.h>
#include <fstream>
#include <iostream>
#include <exception> // std::runtime_error
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/erase.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/config.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <string.h>
namespace Slic3r {
// Escape \n, \r and backslash
std::string escape_string_cstyle(const std::string &str)
{
// Allocate a buffer twice the input string length,
// so the output will fit even if all input characters get escaped.
std::vector<char> out(str.size() * 2, 0);
char *outptr = out.data();
for (size_t i = 0; i < str.size(); ++ i) {
char c = str[i];
if (c == '\r') {
(*outptr ++) = '\\';
(*outptr ++) = 'r';
} else if (c == '\n') {
(*outptr ++) = '\\';
(*outptr ++) = 'n';
} else if (c == '\\') {
(*outptr ++) = '\\';
(*outptr ++) = '\\';
} else
(*outptr ++) = c;
}
return std::string(out.data(), outptr - out.data());
}
std::string escape_strings_cstyle(const std::vector<std::string> &strs)
{
// 1) Estimate the output buffer size to avoid buffer reallocation.
size_t outbuflen = 0;
for (size_t i = 0; i < strs.size(); ++ i)
// Reserve space for every character escaped + quotes + semicolon.
outbuflen += strs[i].size() * 2 + 3;
// 2) Fill in the buffer.
std::vector<char> out(outbuflen, 0);
char *outptr = out.data();
for (size_t j = 0; j < strs.size(); ++ j) {
if (j > 0)
// Separate the strings.
(*outptr ++) = ';';
const std::string &str = strs[j];
// Is the string simple or complex? Complex string contains spaces, tabs, new lines and other
// escapable characters. Empty string shall be quoted as well, if it is the only string in strs.
bool should_quote = strs.size() == 1 && str.empty();
for (size_t i = 0; i < str.size(); ++ i) {
char c = str[i];
if (c == ' ' || c == '\t' || c == '\\' || c == '"' || c == '\r' || c == '\n') {
should_quote = true;
break;
}
}
if (should_quote) {
(*outptr ++) = '"';
for (size_t i = 0; i < str.size(); ++ i) {
char c = str[i];
if (c == '\\' || c == '"') {
(*outptr ++) = '\\';
(*outptr ++) = c;
} else if (c == '\r') {
(*outptr ++) = '\\';
(*outptr ++) = 'r';
} else if (c == '\n') {
(*outptr ++) = '\\';
(*outptr ++) = 'n';
} else
(*outptr ++) = c;
}
(*outptr ++) = '"';
} else {
memcpy(outptr, str.data(), str.size());
outptr += str.size();
}
}
return std::string(out.data(), outptr - out.data());
}
// Unescape \n, \r and backslash
bool unescape_string_cstyle(const std::string &str, std::string &str_out)
{
std::vector<char> out(str.size(), 0);
char *outptr = out.data();
for (size_t i = 0; i < str.size(); ++ i) {
char c = str[i];
if (c == '\\') {
if (++ i == str.size())
return false;
c = str[i];
if (c == 'r')
(*outptr ++) = '\r';
else if (c == 'n')
(*outptr ++) = '\n';
else
(*outptr ++) = c;
} else
(*outptr ++) = c;
}
str_out.assign(out.data(), outptr - out.data());
return true;
}
bool unescape_strings_cstyle(const std::string &str, std::vector<std::string> &out)
{
if (str.empty())
return true;
size_t i = 0;
for (;;) {
// Skip white spaces.
char c = str[i];
while (c == ' ' || c == '\t') {
if (++ i == str.size())
return true;
c = str[i];
}
// Start of a word.
std::vector<char> buf;
buf.reserve(16);
// Is it enclosed in quotes?
c = str[i];
if (c == '"') {
// Complex case, string is enclosed in quotes.
for (++ i; i < str.size(); ++ i) {
c = str[i];
if (c == '"') {
// End of string.
break;
}
if (c == '\\') {
if (++ i == str.size())
return false;
c = str[i];
if (c == 'r')
c = '\r';
else if (c == 'n')
c = '\n';
}
buf.push_back(c);
}
if (i == str.size())
return false;
++ i;
} else {
for (; i < str.size(); ++ i) {
c = str[i];
if (c == ';')
break;
buf.push_back(c);
}
}
// Store the string into the output vector.
out.push_back(std::string(buf.data(), buf.size()));
if (i == str.size())
return true;
// Skip white spaces.
c = str[i];
while (c == ' ' || c == '\t') {
if (++ i == str.size())
// End of string. This is correct.
return true;
c = str[i];
}
if (c != ';')
return false;
if (++ i == str.size()) {
// Emit one additional empty string.
out.push_back(std::string());
return true;
}
}
}
void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent)
{
// loop through options and apply them
for (const t_config_option_key &opt_key : keys) {
// Create a new option with default value for the key.
// If the key is not in the parameter definition, or this ConfigBase is a static type and it does not support the parameter,
// an exception is thrown if not ignore_nonexistent.
ConfigOption *my_opt = this->option(opt_key, true);
if (my_opt == nullptr) {
// opt_key does not exist in this ConfigBase and it cannot be created, because it is not defined by this->def().
// This is only possible if other is of DynamicConfig type.
if (ignore_nonexistent)
continue;
throw UnknownOptionException(opt_key);
}
const ConfigOption *other_opt = other.option(opt_key);
if (other_opt == nullptr) {
// The key was not found in the source config, therefore it will not be initialized!
// printf("Not found, therefore not initialized: %s\n", opt_key.c_str());
} else
my_opt->set(other_opt);
}
}
// this will *ignore* options not present in both configs
t_config_option_keys ConfigBase::diff(const ConfigBase &other) const
{
t_config_option_keys diff;
for (const t_config_option_key &opt_key : this->keys()) {
const ConfigOption *this_opt = this->option(opt_key);
const ConfigOption *other_opt = other.option(opt_key);
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
diff.emplace_back(opt_key);
}
return diff;
}
template<class T>
void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase *this_c)
{
const T* opt_init = static_cast<const T*>(other.option(opt_key));
const T* opt_cur = static_cast<const T*>(this_c->option(opt_key));
int opt_init_max_id = opt_init->values.size() - 1;
for (int i = 0; i < opt_cur->values.size(); i++)
{
int init_id = i <= opt_init_max_id ? i : 0;
if (opt_cur->values[i] != opt_init->values[init_id])
vec.emplace_back(opt_key + "#" + std::to_string(i));
}
}
t_config_option_keys ConfigBase::deep_diff(const ConfigBase &other) const
{
t_config_option_keys diff;
for (const t_config_option_key &opt_key : this->keys()) {
const ConfigOption *this_opt = this->option(opt_key);
const ConfigOption *other_opt = other.option(opt_key);
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
{
if (opt_key == "bed_shape"){ diff.emplace_back(opt_key); continue; }
switch (other_opt->type())
{
case coInts: add_correct_opts_to_diff<ConfigOptionInts >(opt_key, diff, other, this); break;
case coBools: add_correct_opts_to_diff<ConfigOptionBools >(opt_key, diff, other, this); break;
case coFloats: add_correct_opts_to_diff<ConfigOptionFloats >(opt_key, diff, other, this); break;
case coStrings: add_correct_opts_to_diff<ConfigOptionStrings >(opt_key, diff, other, this); break;
case coPercents:add_correct_opts_to_diff<ConfigOptionPercents >(opt_key, diff, other, this); break;
case coPoints: add_correct_opts_to_diff<ConfigOptionPoints >(opt_key, diff, other, this); break;
default: diff.emplace_back(opt_key); break;
}
}
}
return diff;
}
t_config_option_keys ConfigBase::equal(const ConfigBase &other) const
{
t_config_option_keys equal;
for (const t_config_option_key &opt_key : this->keys()) {
const ConfigOption *this_opt = this->option(opt_key);
const ConfigOption *other_opt = other.option(opt_key);
if (this_opt != nullptr && other_opt != nullptr && *this_opt == *other_opt)
equal.emplace_back(opt_key);
}
return equal;
}
std::string ConfigBase::serialize(const t_config_option_key &opt_key) const
{
const ConfigOption* opt = this->option(opt_key);
assert(opt != nullptr);
return opt->serialize();
}
bool ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append)
{
t_config_option_key opt_key = opt_key_src;
std::string value = value_src;
// Both opt_key and value may be modified by _handle_legacy().
// If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by _handle_legacy().
this->handle_legacy(opt_key, value);
if (opt_key.empty())
// Ignore the option.
return true;
return this->set_deserialize_raw(opt_key, value, append);
}
bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append)
{
t_config_option_key opt_key = opt_key_src;
// Try to deserialize the option by its name.
const ConfigDef *def = this->def();
if (def == nullptr)
throw NoDefinitionException(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr) {
// If we didn't find an option, look for any other option having this as an alias.
for (const auto &opt : def->options) {
for (const t_config_option_key &opt_key2 : opt.second.aliases) {
if (opt_key2 == opt_key) {
opt_key = opt.first;
optdef = &opt.second;
break;
}
}
if (optdef != nullptr)
break;
}
if (optdef == nullptr)
throw UnknownOptionException(opt_key);
}
if (! optdef->shortcut.empty()) {
// Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers".
for (const t_config_option_key &shortcut : optdef->shortcut)
// Recursive call.
if (! this->set_deserialize_raw(shortcut, value, append))
return false;
return true;
}
ConfigOption *opt = this->option(opt_key, true);
assert(opt != nullptr);
return opt->deserialize(value, append);
}
// Return an absolute value of a possibly relative config variable.
// For example, return absolute infill extrusion width, either from an absolute value, or relative to the layer height.
double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
{
// Get stored option value.
const ConfigOption *raw_opt = this->option(opt_key);
assert(raw_opt != nullptr);
if (raw_opt->type() == coFloat)
return static_cast<const ConfigOptionFloat*>(raw_opt)->value;
if (raw_opt->type() == coFloatOrPercent) {
// Get option definition.
const ConfigDef *def = this->def();
if (def == nullptr)
throw NoDefinitionException(opt_key);
const ConfigOptionDef *opt_def = def->get(opt_key);
assert(opt_def != nullptr);
// Compute absolute value over the absolute value of the base option.
//FIXME there are some ratio_over chains, which end with empty ratio_with.
// For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly.
return opt_def->ratio_over.empty() ? 0. :
static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over));
}
throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
}
// Return an absolute value of a possibly relative config variable.
// For example, return absolute infill extrusion width, either from an absolute value, or relative to a provided value.
double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) const
{
// Get stored option value.
const ConfigOption *raw_opt = this->option(opt_key);
assert(raw_opt != nullptr);
if (raw_opt->type() != coFloatOrPercent)
throw std::runtime_error("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
// Compute absolute value.
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
}
void ConfigBase::setenv_()
{
t_config_option_keys opt_keys = this->keys();
for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) {
// prepend the SLIC3R_ prefix
std::ostringstream ss;
ss << "SLIC3R_";
ss << *it;
std::string envname = ss.str();
// capitalize environment variable name
for (size_t i = 0; i < envname.size(); ++i)
envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i]-('a'-'A') : envname[i];
boost::nowide::setenv(envname.c_str(), this->serialize(*it).c_str(), 1);
}
}
void ConfigBase::load(const std::string &file)
{
if (boost::iends_with(file, ".gcode") || boost::iends_with(file, ".g"))
this->load_from_gcode_file(file);
else
this->load_from_ini(file);
}
void ConfigBase::load_from_ini(const std::string &file)
{
boost::property_tree::ptree tree;
boost::nowide::ifstream ifs(file);
boost::property_tree::read_ini(ifs, tree);
this->load(tree);
}
void ConfigBase::load(const boost::property_tree::ptree &tree)
{
for (const boost::property_tree::ptree::value_type &v : tree) {
try {
t_config_option_key opt_key = v.first;
this->set_deserialize(opt_key, v.second.get_value<std::string>());
} catch (UnknownOptionException & /* e */) {
// ignore
}
}
}
// Load the config keys from the tail of a G-code file.
void ConfigBase::load_from_gcode_file(const std::string &file)
{
// Read a 64k block from the end of the G-code.
boost::nowide::ifstream ifs(file);
{
const char slic3r_gcode_header[] = "; generated by Slic3r ";
std::string firstline;
std::getline(ifs, firstline);
if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0)
throw std::runtime_error("Not a Slic3r generated g-code.");
}
ifs.seekg(0, ifs.end);
auto file_length = ifs.tellg();
auto data_length = std::min<std::fstream::streampos>(65535, file_length);
ifs.seekg(file_length - data_length, ifs.beg);
std::vector<char> data(size_t(data_length) + 1, 0);
ifs.read(data.data(), data_length);
ifs.close();
load_from_gcode_string(data.data());
}
// Load the config keys from the given string.
void ConfigBase::load_from_gcode_string(const char* str)
{
if (str == nullptr)
return;
// Walk line by line in reverse until a non-configuration key appears.
char *data_start = const_cast<char*>(str);
// boost::nowide::ifstream seems to cook the text data somehow, so less then the 64k of characters may be retrieved.
char *end = data_start + strlen(str);
size_t num_key_value_pairs = 0;
for (;;) {
// Extract next line.
for (--end; end > data_start && (*end == '\r' || *end == '\n'); --end);
if (end == data_start)
break;
char *start = end;
*(++end) = 0;
for (; start > data_start && *start != '\r' && *start != '\n'; --start);
if (start == data_start)
break;
// Extracted a line from start to end. Extract the key = value pair.
if (end - (++start) < 10 || start[0] != ';' || start[1] != ' ')
break;
char *key = start + 2;
if (!(*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z'))
// A key must start with a letter.
break;
char *sep = strchr(key, '=');
if (sep == nullptr || sep[-1] != ' ' || sep[1] != ' ')
break;
char *value = sep + 2;
if (value > end)
break;
char *key_end = sep - 1;
if (key_end - key < 3)
break;
*key_end = 0;
// The key may contain letters, digits and underscores.
for (char *c = key; c != key_end; ++c)
if (!((*c >= 'a' && *c <= 'z') || (*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9') || *c == '_')) {
key = nullptr;
break;
}
if (key == nullptr)
break;
try {
this->set_deserialize(key, value);
++num_key_value_pairs;
}
catch (UnknownOptionException & /* e */) {
// ignore
}
end = start;
}
if (num_key_value_pairs < 90) {
char msg[80];
sprintf(msg, "Suspiciously low number of configuration values extracted: %d", num_key_value_pairs);
throw std::runtime_error(msg);
}
}
void ConfigBase::save(const std::string &file) const
{
boost::nowide::ofstream c;
c.open(file, std::ios::out | std::ios::trunc);
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
for (const std::string &opt_key : this->keys())
c << opt_key << " = " << this->serialize(opt_key) << std::endl;
c.close();
}
bool DynamicConfig::operator==(const DynamicConfig &rhs) const
{
t_options_map::const_iterator it1 = this->options.begin();
t_options_map::const_iterator it1_end = this->options.end();
t_options_map::const_iterator it2 = rhs.options.begin();
t_options_map::const_iterator it2_end = rhs.options.end();
for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2)
if (*it1->second != *it2->second)
return false;
return it1 == it1_end && it2 == it2_end;
}
ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create)
{
t_options_map::iterator it = options.find(opt_key);
if (it != options.end())
// Option was found.
return it->second;
if (! create)
// Option was not found and a new option shall not be created.
return nullptr;
// Try to create a new ConfigOption.
const ConfigDef *def = this->def();
if (def == nullptr)
throw NoDefinitionException(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr)
// throw std::runtime_error(std::string("Invalid option name: ") + opt_key);
// Let the parent decide what to do if the opt_key is not defined by this->def().
return nullptr;
ConfigOption *opt = nullptr;
switch (optdef->type) {
case coFloat: opt = new ConfigOptionFloat(); break;
case coFloats: opt = new ConfigOptionFloats(); break;
case coInt: opt = new ConfigOptionInt(); break;
case coInts: opt = new ConfigOptionInts(); break;
case coString: opt = new ConfigOptionString(); break;
case coStrings: opt = new ConfigOptionStrings(); break;
case coPercent: opt = new ConfigOptionPercent(); break;
case coPercents: opt = new ConfigOptionPercents(); break;
case coFloatOrPercent: opt = new ConfigOptionFloatOrPercent(); break;
case coPoint: opt = new ConfigOptionPoint(); break;
case coPoints: opt = new ConfigOptionPoints(); break;
case coBool: opt = new ConfigOptionBool(); break;
case coBools: opt = new ConfigOptionBools(); break;
case coEnum: opt = new ConfigOptionEnumGeneric(optdef->enum_keys_map); break;
default: throw std::runtime_error(std::string("Unknown option type for option ") + opt_key);
}
this->options[opt_key] = opt;
return opt;
}
t_config_option_keys DynamicConfig::keys() const
{
t_config_option_keys keys;
keys.reserve(this->options.size());
for (const auto &opt : this->options)
keys.emplace_back(opt.first);
return keys;
}
void StaticConfig::set_defaults()
{
// use defaults from definition
auto *defs = this->def();
if (defs != nullptr) {
for (const std::string &key : this->keys()) {
const ConfigOptionDef *def = defs->get(key);
ConfigOption *opt = this->option(key);
if (def != nullptr && opt != nullptr && def->default_value != nullptr)
opt->set(def->default_value);
}
}
}
t_config_option_keys StaticConfig::keys() const
{
t_config_option_keys keys;
assert(this->def() != nullptr);
for (const auto &opt_def : this->def()->options)
if (this->option(opt_def.first) != nullptr)
keys.push_back(opt_def.first);
return keys;
}
}

1264
src/libslic3r/Config.hpp Normal file

File diff suppressed because it is too large Load diff

1453
src/libslic3r/EdgeGrid.cpp Normal file

File diff suppressed because it is too large Load diff

118
src/libslic3r/EdgeGrid.hpp Normal file
View file

@ -0,0 +1,118 @@
#ifndef slic3r_EdgeGrid_hpp_
#define slic3r_EdgeGrid_hpp_
#include <stdint.h>
#include <math.h>
#include "Point.hpp"
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "ExPolygonCollection.hpp"
namespace Slic3r {
namespace EdgeGrid {
class Grid
{
public:
Grid();
~Grid();
void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; }
void create(const Polygons &polygons, coord_t resolution);
void create(const ExPolygon &expoly, coord_t resolution);
void create(const ExPolygons &expolygons, coord_t resolution);
void create(const ExPolygonCollection &expolygons, coord_t resolution);
#if 0
// Test, whether the edges inside the grid intersect with the polygons provided.
bool intersect(const MultiPoint &polyline, bool closed);
bool intersect(const Polygon &polygon) { return intersect(static_cast<const MultiPoint&>(polygon), true); }
bool intersect(const Polygons &polygons) { for (size_t i = 0; i < polygons.size(); ++ i) if (intersect(polygons[i])) return true; return false; }
bool intersect(const ExPolygon &expoly) { if (intersect(expoly.contour)) return true; for (size_t i = 0; i < expoly.holes.size(); ++ i) if (intersect(expoly.holes[i])) return true; return false; }
bool intersect(const ExPolygons &expolygons) { for (size_t i = 0; i < expolygons.size(); ++ i) if (intersect(expolygons[i])) return true; return false; }
bool intersect(const ExPolygonCollection &expolygons) { return intersect(expolygons.expolygons); }
// Test, whether a point is inside a contour.
bool inside(const Point &pt);
#endif
// Fill in a rough m_signed_distance_field from the edge grid.
// The rough SDF is used by signed_distance() for distances outside of the search_radius.
void calculate_sdf();
// Return an estimate of the signed distance based on m_signed_distance_field grid.
float signed_distance_bilinear(const Point &pt) const;
// Calculate a signed distance to the contours in search_radius from the point.
bool signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment = NULL) const;
// Calculate a signed distance to the contours in search_radius from the point. If no edge is found in search_radius,
// return an interpolated value from m_signed_distance_field, if it exists.
bool signed_distance(const Point &pt, coord_t search_radius, coordf_t &result_min_dist) const;
const BoundingBox& bbox() const { return m_bbox; }
const coord_t resolution() const { return m_resolution; }
const size_t rows() const { return m_rows; }
const size_t cols() const { return m_cols; }
// For supports: Contours enclosing the rasterized edges.
Polygons contours_simplified(coord_t offset, bool fill_holes) const;
protected:
struct Cell {
Cell() : begin(0), end(0) {}
size_t begin;
size_t end;
};
void create_from_m_contours(coord_t resolution);
#if 0
bool line_cell_intersect(const Point &p1, const Point &p2, const Cell &cell);
#endif
bool cell_inside_or_crossing(int r, int c) const
{
if (r < 0 || r >= m_rows ||
c < 0 || c >= m_cols)
// The cell is outside the domain. Hoping that the contours were correctly oriented, so
// there is a CCW outmost contour so the out of domain cells are outside.
return false;
const Cell &cell = m_cells[r * m_cols + c];
return
(cell.begin < cell.end) ||
(! m_signed_distance_field.empty() && m_signed_distance_field[r * (m_cols + 1) + c] <= 0.f);
}
// Bounding box around the contours.
BoundingBox m_bbox;
// Grid dimensions.
coord_t m_resolution;
size_t m_rows;
size_t m_cols;
// Referencing the source contours.
// This format allows one to work with any Slic3r fixed point contour format
// (Polygon, ExPolygon, ExPolygonCollection etc).
std::vector<const Slic3r::Points*> m_contours;
// Referencing a contour and a line segment of m_contours.
std::vector<std::pair<size_t, size_t> > m_cell_data;
// Full grid of cells.
std::vector<Cell> m_cells;
// Distance field derived from the edge grid, seed filled by the Danielsson chamfer metric.
// May be empty.
std::vector<float> m_signed_distance_field;
};
#if 0
// Debugging utility. Save the signed distance field.
extern void save_png(const Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path);
#endif /* SLIC3R_GUI */
} // namespace EdgeGrid
} // namespace Slic3r
#endif /* slic3r_EdgeGrid_hpp_ */

555
src/libslic3r/ExPolygon.cpp Normal file
View file

@ -0,0 +1,555 @@
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Geometry.hpp"
#include "Polygon.hpp"
#include "Line.hpp"
#include "ClipperUtils.hpp"
#include "SVG.hpp"
#include "polypartition.h"
#include "poly2tri/poly2tri.h"
#include <algorithm>
#include <cassert>
#include <list>
namespace Slic3r {
ExPolygon::operator Points() const
{
Points points;
Polygons pp = *this;
for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) {
for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point)
points.push_back(*point);
}
return points;
}
ExPolygon::operator Polygons() const
{
return to_polygons(*this);
}
ExPolygon::operator Polylines() const
{
return to_polylines(*this);
}
void ExPolygon::scale(double factor)
{
contour.scale(factor);
for (Polygon &hole : holes)
hole.scale(factor);
}
void ExPolygon::translate(double x, double y)
{
contour.translate(x, y);
for (Polygon &hole : holes)
hole.translate(x, y);
}
void ExPolygon::rotate(double angle)
{
contour.rotate(angle);
for (Polygon &hole : holes)
hole.rotate(angle);
}
void ExPolygon::rotate(double angle, const Point &center)
{
contour.rotate(angle, center);
for (Polygon &hole : holes)
hole.rotate(angle, center);
}
double ExPolygon::area() const
{
double a = this->contour.area();
for (const Polygon &hole : holes)
a -= - hole.area(); // holes have negative area
return a;
}
bool ExPolygon::is_valid() const
{
if (!this->contour.is_valid() || !this->contour.is_counter_clockwise()) return false;
for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) {
if (!(*it).is_valid() || (*it).is_counter_clockwise()) return false;
}
return true;
}
bool ExPolygon::contains(const Line &line) const
{
return this->contains(Polyline(line.a, line.b));
}
bool ExPolygon::contains(const Polyline &polyline) const
{
return diff_pl((Polylines)polyline, *this).empty();
}
bool ExPolygon::contains(const Polylines &polylines) const
{
#if 0
BoundingBox bbox = get_extents(polylines);
bbox.merge(get_extents(*this));
SVG svg(debug_out_path("ExPolygon_contains.svg"), bbox);
svg.draw(*this);
svg.draw_outline(*this);
svg.draw(polylines, "blue");
#endif
Polylines pl_out = diff_pl(polylines, *this);
#if 0
svg.draw(pl_out, "red");
#endif
return pl_out.empty();
}
bool ExPolygon::contains(const Point &point) const
{
if (!this->contour.contains(point)) return false;
for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) {
if (it->contains(point)) return false;
}
return true;
}
// inclusive version of contains() that also checks whether point is on boundaries
bool ExPolygon::contains_b(const Point &point) const
{
return this->contains(point) || this->has_boundary_point(point);
}
bool
ExPolygon::has_boundary_point(const Point &point) const
{
if (this->contour.has_boundary_point(point)) return true;
for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) {
if (h->has_boundary_point(point)) return true;
}
return false;
}
bool
ExPolygon::overlaps(const ExPolygon &other) const
{
#if 0
BoundingBox bbox = get_extents(other);
bbox.merge(get_extents(*this));
static int iRun = 0;
SVG svg(debug_out_path("ExPolygon_overlaps-%d.svg", iRun ++), bbox);
svg.draw(*this);
svg.draw_outline(*this);
svg.draw_outline(other, "blue");
#endif
Polylines pl_out = intersection_pl((Polylines)other, *this);
#if 0
svg.draw(pl_out, "red");
#endif
if (! pl_out.empty())
return true;
return ! other.contour.points.empty() && this->contains_b(other.contour.points.front());
}
void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const
{
Polygons pp = this->simplify_p(tolerance);
polygons->insert(polygons->end(), pp.begin(), pp.end());
}
Polygons ExPolygon::simplify_p(double tolerance) const
{
Polygons pp;
pp.reserve(this->holes.size() + 1);
// contour
{
Polygon p = this->contour;
p.points.push_back(p.points.front());
p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
p.points.pop_back();
pp.emplace_back(std::move(p));
}
// holes
for (Polygon p : this->holes) {
p.points.push_back(p.points.front());
p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
p.points.pop_back();
pp.emplace_back(std::move(p));
}
return simplify_polygons(pp);
}
ExPolygons ExPolygon::simplify(double tolerance) const
{
return union_ex(this->simplify_p(tolerance));
}
void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
{
append(*expolygons, this->simplify(tolerance));
}
void
ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const
{
// init helper object
Slic3r::Geometry::MedialAxis ma(max_width, min_width, this);
ma.lines = this->lines();
// compute the Voronoi diagram and extract medial axis polylines
ThickPolylines pp;
ma.build(&pp);
/*
SVG svg("medial_axis.svg");
svg.draw(*this);
svg.draw(pp);
svg.Close();
*/
/* Find the maximum width returned; we're going to use this for validating and
filtering the output segments. */
double max_w = 0;
for (ThickPolylines::const_iterator it = pp.begin(); it != pp.end(); ++it)
max_w = fmaxf(max_w, *std::max_element(it->width.begin(), it->width.end()));
/* Loop through all returned polylines in order to extend their endpoints to the
expolygon boundaries */
bool removed = false;
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
// extend initial and final segments of each polyline if they're actual endpoints
/* We assign new endpoints to temporary variables because in case of a single-line
polyline, after we extend the start point it will be caught by the intersection()
call, so we keep the inner point until we perform the second intersection() as well */
Point new_front = polyline.points.front();
Point new_back = polyline.points.back();
if (polyline.endpoints.first && !this->has_boundary_point(new_front)) {
Vec2d p1 = polyline.points.front().cast<double>();
Vec2d p2 = polyline.points[1].cast<double>();
// prevent the line from touching on the other side, otherwise intersection() might return that solution
if (polyline.points.size() == 2)
p2 = (p1 + p2) * 0.5;
// Extend the start of the segment.
p1 -= (p2 - p1).normalized() * max_width;
this->contour.intersection(Line(p1.cast<coord_t>(), p2.cast<coord_t>()), &new_front);
}
if (polyline.endpoints.second && !this->has_boundary_point(new_back)) {
Vec2d p1 = (polyline.points.end() - 2)->cast<double>();
Vec2d p2 = polyline.points.back().cast<double>();
// prevent the line from touching on the other side, otherwise intersection() might return that solution
if (polyline.points.size() == 2)
p1 = (p1 + p2) * 0.5;
// Extend the start of the segment.
p2 += (p2 - p1).normalized() * max_width;
this->contour.intersection(Line(p1.cast<coord_t>(), p2.cast<coord_t>()), &new_back);
}
polyline.points.front() = new_front;
polyline.points.back() = new_back;
/* remove too short polylines
(we can't do this check before endpoints extension and clipping because we don't
know how long will the endpoints be extended since it depends on polygon thickness
which is variable - extension will be <= max_width/2 on each side) */
if ((polyline.endpoints.first || polyline.endpoints.second)
&& polyline.length() < max_w*2) {
pp.erase(pp.begin() + i);
--i;
removed = true;
continue;
}
}
/* If we removed any short polylines we now try to connect consecutive polylines
in order to allow loop detection. Note that this algorithm is greedier than
MedialAxis::process_edge_neighbors() as it will connect random pairs of
polylines even when more than two start from the same point. This has no
drawbacks since we optimize later using nearest-neighbor which would do the
same, but should we use a more sophisticated optimization algorithm we should
not connect polylines when more than two meet. */
if (removed) {
for (size_t i = 0; i < pp.size(); ++i) {
ThickPolyline& polyline = pp[i];
if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization
// find another polyline starting here
for (size_t j = i+1; j < pp.size(); ++j) {
ThickPolyline& other = pp[j];
if (polyline.last_point() == other.last_point()) {
other.reverse();
} else if (polyline.first_point() == other.last_point()) {
polyline.reverse();
other.reverse();
} else if (polyline.first_point() == other.first_point()) {
polyline.reverse();
} else if (polyline.last_point() != other.first_point()) {
continue;
}
polyline.points.insert(polyline.points.end(), other.points.begin() + 1, other.points.end());
polyline.width.insert(polyline.width.end(), other.width.begin(), other.width.end());
polyline.endpoints.second = other.endpoints.second;
assert(polyline.width.size() == polyline.points.size()*2 - 2);
pp.erase(pp.begin() + j);
j = i; // restart search from i+1
}
}
}
polylines->insert(polylines->end(), pp.begin(), pp.end());
}
void
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
{
ThickPolylines tp;
this->medial_axis(max_width, min_width, &tp);
polylines->insert(polylines->end(), tp.begin(), tp.end());
}
void
ExPolygon::get_trapezoids(Polygons* polygons) const
{
ExPolygons expp;
expp.push_back(*this);
boost::polygon::get_trapezoids(*polygons, expp);
}
void
ExPolygon::get_trapezoids(Polygons* polygons, double angle) const
{
ExPolygon clone = *this;
clone.rotate(PI/2 - angle, Point(0,0));
clone.get_trapezoids(polygons);
for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon)
polygon->rotate(-(PI/2 - angle), Point(0,0));
}
// This algorithm may return more trapezoids than necessary
// (i.e. it may break a single trapezoid in several because
// other parts of the object have x coordinates in the middle)
void
ExPolygon::get_trapezoids2(Polygons* polygons) const
{
// get all points of this ExPolygon
Points pp = *this;
// build our bounding box
BoundingBox bb(pp);
// get all x coordinates
std::vector<coord_t> xx;
xx.reserve(pp.size());
for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p)
xx.push_back(p->x());
std::sort(xx.begin(), xx.end());
// find trapezoids by looping from first to next-to-last coordinate
for (std::vector<coord_t>::const_iterator x = xx.begin(); x != xx.end()-1; ++x) {
coord_t next_x = *(x + 1);
if (*x == next_x) continue;
// build rectangle
Polygon poly;
poly.points.resize(4);
poly[0](0) = *x;
poly[0](1) = bb.min(1);
poly[1](0) = next_x;
poly[1](1) = bb.min(1);
poly[2](0) = next_x;
poly[2](1) = bb.max(1);
poly[3](0) = *x;
poly[3](1) = bb.max(1);
// intersect with this expolygon
// append results to return value
polygons_append(*polygons, intersection(poly, to_polygons(*this)));
}
}
void
ExPolygon::get_trapezoids2(Polygons* polygons, double angle) const
{
ExPolygon clone = *this;
clone.rotate(PI/2 - angle, Point(0,0));
clone.get_trapezoids2(polygons);
for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon)
polygon->rotate(-(PI/2 - angle), Point(0,0));
}
// While this triangulates successfully, it's NOT a constrained triangulation
// as it will create more vertices on the boundaries than the ones supplied.
void
ExPolygon::triangulate(Polygons* polygons) const
{
// first make trapezoids
Polygons trapezoids;
this->get_trapezoids2(&trapezoids);
// then triangulate each trapezoid
for (Polygons::iterator polygon = trapezoids.begin(); polygon != trapezoids.end(); ++polygon)
polygon->triangulate_convex(polygons);
}
void
ExPolygon::triangulate_pp(Polygons* polygons) const
{
// convert polygons
std::list<TPPLPoly> input;
ExPolygons expp = union_ex(simplify_polygons(to_polygons(*this), true));
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) {
// contour
{
TPPLPoly p;
p.Init(int(ex->contour.points.size()));
//printf(PRINTF_ZU "\n0\n", ex->contour.points.size());
for (const Point &point : ex->contour.points) {
size_t i = &point - &ex->contour.points.front();
p[i].x = point(0);
p[i].y = point(1);
//printf("%ld %ld\n", point->x(), point->y());
}
p.SetHole(false);
input.push_back(p);
}
// holes
for (Polygons::const_iterator hole = ex->holes.begin(); hole != ex->holes.end(); ++hole) {
TPPLPoly p;
p.Init(hole->points.size());
//printf(PRINTF_ZU "\n1\n", hole->points.size());
for (const Point &point : hole->points) {
size_t i = &point - &hole->points.front();
p[i].x = point(0);
p[i].y = point(1);
//printf("%ld %ld\n", point->x(), point->y());
}
p.SetHole(true);
input.push_back(p);
}
}
// perform triangulation
std::list<TPPLPoly> output;
int res = TPPLPartition().Triangulate_MONO(&input, &output);
if (res != 1)
throw std::runtime_error("Triangulation failed");
// convert output polygons
for (std::list<TPPLPoly>::iterator poly = output.begin(); poly != output.end(); ++poly) {
long num_points = poly->GetNumPoints();
Polygon p;
p.points.resize(num_points);
for (long i = 0; i < num_points; ++i) {
p.points[i](0) = coord_t((*poly)[i].x);
p.points[i](1) = coord_t((*poly)[i].y);
}
polygons->push_back(p);
}
}
void
ExPolygon::triangulate_p2t(Polygons* polygons) const
{
ExPolygons expp = simplify_polygons_ex(*this, true);
for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) {
// TODO: prevent duplicate points
// contour
std::vector<p2t::Point*> ContourPoints;
for (const Point &pt : ex->contour.points)
// We should delete each p2t::Point object
ContourPoints.push_back(new p2t::Point(pt(0), pt(1)));
p2t::CDT cdt(ContourPoints);
// holes
for (Polygons::const_iterator hole = ex->holes.begin(); hole != ex->holes.end(); ++hole) {
std::vector<p2t::Point*> points;
for (const Point &pt : hole->points)
// will be destructed in SweepContext::~SweepContext
points.push_back(new p2t::Point(pt(0), pt(1)));
cdt.AddHole(points);
}
// perform triangulation
cdt.Triangulate();
std::vector<p2t::Triangle*> triangles = cdt.GetTriangles();
for (std::vector<p2t::Triangle*>::const_iterator triangle = triangles.begin(); triangle != triangles.end(); ++triangle) {
Polygon p;
for (int i = 0; i <= 2; ++i) {
p2t::Point* point = (*triangle)->GetPoint(i);
p.points.push_back(Point(point->x, point->y));
}
polygons->push_back(p);
}
for (p2t::Point *ptr : ContourPoints)
delete ptr;
}
}
Lines
ExPolygon::lines() const
{
Lines lines = this->contour.lines();
for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) {
Lines hole_lines = h->lines();
lines.insert(lines.end(), hole_lines.begin(), hole_lines.end());
}
return lines;
}
BoundingBox get_extents(const ExPolygon &expolygon)
{
return get_extents(expolygon.contour);
}
BoundingBox get_extents(const ExPolygons &expolygons)
{
BoundingBox bbox;
if (! expolygons.empty()) {
for (size_t i = 0; i < expolygons.size(); ++ i)
if (! expolygons[i].contour.points.empty())
bbox.merge(get_extents(expolygons[i]));
}
return bbox;
}
BoundingBox get_extents_rotated(const ExPolygon &expolygon, double angle)
{
return get_extents_rotated(expolygon.contour, angle);
}
BoundingBox get_extents_rotated(const ExPolygons &expolygons, double angle)
{
BoundingBox bbox;
if (! expolygons.empty()) {
bbox = get_extents_rotated(expolygons.front().contour, angle);
for (size_t i = 1; i < expolygons.size(); ++ i)
bbox.merge(get_extents_rotated(expolygons[i].contour, angle));
}
return bbox;
}
extern std::vector<BoundingBox> get_extents_vector(const ExPolygons &polygons)
{
std::vector<BoundingBox> out;
out.reserve(polygons.size());
for (ExPolygons::const_iterator it = polygons.begin(); it != polygons.end(); ++ it)
out.push_back(get_extents(*it));
return out;
}
bool remove_sticks(ExPolygon &poly)
{
return remove_sticks(poly.contour) || remove_sticks(poly.holes);
}
} // namespace Slic3r

406
src/libslic3r/ExPolygon.hpp Normal file
View file

@ -0,0 +1,406 @@
#ifndef slic3r_ExPolygon_hpp_
#define slic3r_ExPolygon_hpp_
#include "libslic3r.h"
#include "Polygon.hpp"
#include "Polyline.hpp"
#include <vector>
namespace Slic3r {
class ExPolygon;
typedef std::vector<ExPolygon> ExPolygons;
class ExPolygon
{
public:
ExPolygon() {}
ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {}
ExPolygon(ExPolygon &&other) : contour(std::move(other.contour)), holes(std::move(other.holes)) {}
ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; }
ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; }
Polygon contour;
Polygons holes;
operator Points() const;
operator Polygons() const;
operator Polylines() const;
void clear() { contour.points.clear(); holes.clear(); }
void scale(double factor);
void translate(double x, double y);
void rotate(double angle);
void rotate(double angle, const Point &center);
double area() const;
bool empty() const { return contour.points.empty(); }
bool is_valid() const;
// Contains the line / polyline / polylines etc COMPLETELY.
bool contains(const Line &line) const;
bool contains(const Polyline &polyline) const;
bool contains(const Polylines &polylines) const;
bool contains(const Point &point) const;
bool contains_b(const Point &point) const;
bool has_boundary_point(const Point &point) const;
// Does this expolygon overlap another expolygon?
// Either the ExPolygons intersect, or one is fully inside the other,
// and it is not inside a hole of the other expolygon.
bool overlaps(const ExPolygon &other) const;
void simplify_p(double tolerance, Polygons* polygons) const;
Polygons simplify_p(double tolerance) const;
ExPolygons simplify(double tolerance) const;
void simplify(double tolerance, ExPolygons* expolygons) const;
void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const;
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
void get_trapezoids(Polygons* polygons) const;
void get_trapezoids(Polygons* polygons, double angle) const;
void get_trapezoids2(Polygons* polygons) const;
void get_trapezoids2(Polygons* polygons, double angle) const;
void triangulate(Polygons* polygons) const;
void triangulate_pp(Polygons* polygons) const;
void triangulate_p2t(Polygons* polygons) const;
Lines lines() const;
};
// Count a nuber of polygons stored inside the vector of expolygons.
// Useful for allocating space for polygons when converting expolygons to polygons.
inline size_t number_polygons(const ExPolygons &expolys)
{
size_t n_polygons = 0;
for (ExPolygons::const_iterator it = expolys.begin(); it != expolys.end(); ++ it)
n_polygons += it->holes.size() + 1;
return n_polygons;
}
inline Lines to_lines(const ExPolygon &src)
{
size_t n_lines = src.contour.points.size();
for (size_t i = 0; i < src.holes.size(); ++ i)
n_lines += src.holes[i].points.size();
Lines lines;
lines.reserve(n_lines);
for (size_t i = 0; i <= src.holes.size(); ++ i) {
const Polygon &poly = (i == 0) ? src.contour : src.holes[i - 1];
for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it)
lines.push_back(Line(*it, *(it + 1)));
lines.push_back(Line(poly.points.back(), poly.points.front()));
}
return lines;
}
inline Lines to_lines(const ExPolygons &src)
{
size_t n_lines = 0;
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
n_lines += it_expoly->contour.points.size();
for (size_t i = 0; i < it_expoly->holes.size(); ++ i)
n_lines += it_expoly->holes[i].points.size();
}
Lines lines;
lines.reserve(n_lines);
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
for (size_t i = 0; i <= it_expoly->holes.size(); ++ i) {
const Points &points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points;
for (Points::const_iterator it = points.begin(); it != points.end()-1; ++it)
lines.push_back(Line(*it, *(it + 1)));
lines.push_back(Line(points.back(), points.front()));
}
}
return lines;
}
inline Polylines to_polylines(const ExPolygon &src)
{
Polylines polylines;
polylines.assign(src.holes.size() + 1, Polyline());
size_t idx = 0;
Polyline &pl = polylines[idx ++];
pl.points = src.contour.points;
pl.points.push_back(pl.points.front());
for (Polygons::const_iterator ith = src.holes.begin(); ith != src.holes.end(); ++ith) {
Polyline &pl = polylines[idx ++];
pl.points = ith->points;
pl.points.push_back(ith->points.front());
}
assert(idx == polylines.size());
return polylines;
}
inline Polylines to_polylines(const ExPolygons &src)
{
Polylines polylines;
polylines.assign(number_polygons(src), Polyline());
size_t idx = 0;
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) {
Polyline &pl = polylines[idx ++];
pl.points = it->contour.points;
pl.points.push_back(pl.points.front());
for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) {
Polyline &pl = polylines[idx ++];
pl.points = ith->points;
pl.points.push_back(ith->points.front());
}
}
assert(idx == polylines.size());
return polylines;
}
inline Polylines to_polylines(ExPolygon &&src)
{
Polylines polylines;
polylines.assign(src.holes.size() + 1, Polyline());
size_t idx = 0;
Polyline &pl = polylines[idx ++];
pl.points = std::move(src.contour.points);
pl.points.push_back(pl.points.front());
for (Polygons::const_iterator ith = src.holes.begin(); ith != src.holes.end(); ++ith) {
Polyline &pl = polylines[idx ++];
pl.points = std::move(ith->points);
pl.points.push_back(ith->points.front());
}
assert(idx == polylines.size());
return polylines;
}
inline Polylines to_polylines(ExPolygons &&src)
{
Polylines polylines;
polylines.assign(number_polygons(src), Polyline());
size_t idx = 0;
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) {
Polyline &pl = polylines[idx ++];
pl.points = std::move(it->contour.points);
pl.points.push_back(pl.points.front());
for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) {
Polyline &pl = polylines[idx ++];
pl.points = std::move(ith->points);
pl.points.push_back(ith->points.front());
}
}
assert(idx == polylines.size());
return polylines;
}
inline Polygons to_polygons(const ExPolygon &src)
{
Polygons polygons;
polygons.reserve(src.holes.size() + 1);
polygons.push_back(src.contour);
polygons.insert(polygons.end(), src.holes.begin(), src.holes.end());
return polygons;
}
inline Polygons to_polygons(const ExPolygons &src)
{
Polygons polygons;
polygons.reserve(number_polygons(src));
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) {
polygons.push_back(it->contour);
polygons.insert(polygons.end(), it->holes.begin(), it->holes.end());
}
return polygons;
}
inline Polygons to_polygons(ExPolygon &&src)
{
Polygons polygons;
polygons.reserve(src.holes.size() + 1);
polygons.push_back(std::move(src.contour));
std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(polygons));
src.holes.clear();
return polygons;
}
inline Polygons to_polygons(ExPolygons &&src)
{
Polygons polygons;
polygons.reserve(number_polygons(src));
for (ExPolygons::iterator it = src.begin(); it != src.end(); ++it) {
polygons.push_back(std::move(it->contour));
std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(polygons));
it->holes.clear();
}
return polygons;
}
inline void polygons_append(Polygons &dst, const ExPolygon &src)
{
dst.reserve(dst.size() + src.holes.size() + 1);
dst.push_back(src.contour);
dst.insert(dst.end(), src.holes.begin(), src.holes.end());
}
inline void polygons_append(Polygons &dst, const ExPolygons &src)
{
dst.reserve(dst.size() + number_polygons(src));
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) {
dst.push_back(it->contour);
dst.insert(dst.end(), it->holes.begin(), it->holes.end());
}
}
inline void polygons_append(Polygons &dst, ExPolygon &&src)
{
dst.reserve(dst.size() + src.holes.size() + 1);
dst.push_back(std::move(src.contour));
std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(dst));
src.holes.clear();
}
inline void polygons_append(Polygons &dst, ExPolygons &&src)
{
dst.reserve(dst.size() + number_polygons(src));
for (ExPolygons::iterator it = src.begin(); it != src.end(); ++ it) {
dst.push_back(std::move(it->contour));
std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(dst));
it->holes.clear();
}
}
inline void expolygons_append(ExPolygons &dst, const ExPolygons &src)
{
dst.insert(dst.end(), src.begin(), src.end());
}
inline void expolygons_append(ExPolygons &dst, ExPolygons &&src)
{
if (dst.empty()) {
dst = std::move(src);
} else {
std::move(std::begin(src), std::end(src), std::back_inserter(dst));
src.clear();
}
}
inline void expolygons_rotate(ExPolygons &expolys, double angle)
{
for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p)
p->rotate(angle);
}
inline bool expolygons_contain(ExPolygons &expolys, const Point &pt)
{
for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p)
if (p->contains(pt))
return true;
return false;
}
extern BoundingBox get_extents(const ExPolygon &expolygon);
extern BoundingBox get_extents(const ExPolygons &expolygons);
extern BoundingBox get_extents_rotated(const ExPolygon &poly, double angle);
extern BoundingBox get_extents_rotated(const ExPolygons &polygons, double angle);
extern std::vector<BoundingBox> get_extents_vector(const ExPolygons &polygons);
extern bool remove_sticks(ExPolygon &poly);
} // namespace Slic3r
// start Boost
#include <boost/polygon/polygon.hpp>
namespace boost { namespace polygon {
template <>
struct polygon_traits<Slic3r::ExPolygon> {
typedef coord_t coordinate_type;
typedef Slic3r::Points::const_iterator iterator_type;
typedef Slic3r::Point point_type;
// Get the begin iterator
static inline iterator_type begin_points(const Slic3r::ExPolygon& t) {
return t.contour.points.begin();
}
// Get the end iterator
static inline iterator_type end_points(const Slic3r::ExPolygon& t) {
return t.contour.points.end();
}
// Get the number of sides of the polygon
static inline std::size_t size(const Slic3r::ExPolygon& t) {
return t.contour.points.size();
}
// Get the winding direction of the polygon
static inline winding_direction winding(const Slic3r::ExPolygon& /* t */) {
return unknown_winding;
}
};
template <>
struct polygon_mutable_traits<Slic3r::ExPolygon> {
//expects stl style iterators
template <typename iT>
static inline Slic3r::ExPolygon& set_points(Slic3r::ExPolygon& expolygon, iT input_begin, iT input_end) {
expolygon.contour.points.assign(input_begin, input_end);
// skip last point since Boost will set last point = first point
expolygon.contour.points.pop_back();
return expolygon;
}
};
template <>
struct geometry_concept<Slic3r::ExPolygon> { typedef polygon_with_holes_concept type; };
template <>
struct polygon_with_holes_traits<Slic3r::ExPolygon> {
typedef Slic3r::Polygons::const_iterator iterator_holes_type;
typedef Slic3r::Polygon hole_type;
static inline iterator_holes_type begin_holes(const Slic3r::ExPolygon& t) {
return t.holes.begin();
}
static inline iterator_holes_type end_holes(const Slic3r::ExPolygon& t) {
return t.holes.end();
}
static inline unsigned int size_holes(const Slic3r::ExPolygon& t) {
return (int)t.holes.size();
}
};
template <>
struct polygon_with_holes_mutable_traits<Slic3r::ExPolygon> {
template <typename iT>
static inline Slic3r::ExPolygon& set_holes(Slic3r::ExPolygon& t, iT inputBegin, iT inputEnd) {
t.holes.assign(inputBegin, inputEnd);
return t;
}
};
//first we register CPolygonSet as a polygon set
template <>
struct geometry_concept<Slic3r::ExPolygons> { typedef polygon_set_concept type; };
//next we map to the concept through traits
template <>
struct polygon_set_traits<Slic3r::ExPolygons> {
typedef coord_t coordinate_type;
typedef Slic3r::ExPolygons::const_iterator iterator_type;
typedef Slic3r::ExPolygons operator_arg_type;
static inline iterator_type begin(const Slic3r::ExPolygons& polygon_set) {
return polygon_set.begin();
}
static inline iterator_type end(const Slic3r::ExPolygons& polygon_set) {
return polygon_set.end();
}
//don't worry about these, just return false from them
static inline bool clean(const Slic3r::ExPolygons& /* polygon_set */) { return false; }
static inline bool sorted(const Slic3r::ExPolygons& /* polygon_set */) { return false; }
};
template <>
struct polygon_set_mutable_traits<Slic3r::ExPolygons> {
template <typename input_iterator_type>
static inline void set(Slic3r::ExPolygons& expolygons, input_iterator_type input_begin, input_iterator_type input_end) {
expolygons.assign(input_begin, input_end);
}
};
} }
// end Boost
#endif

View file

@ -0,0 +1,135 @@
#include "ExPolygonCollection.hpp"
#include "Geometry.hpp"
namespace Slic3r {
ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon)
{
this->expolygons.push_back(expolygon);
}
ExPolygonCollection::operator Points() const
{
Points points;
Polygons pp = *this;
for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) {
for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point)
points.push_back(*point);
}
return points;
}
ExPolygonCollection::operator Polygons() const
{
Polygons polygons;
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
polygons.push_back(it->contour);
for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) {
polygons.push_back(*ith);
}
}
return polygons;
}
ExPolygonCollection::operator ExPolygons&()
{
return this->expolygons;
}
void
ExPolygonCollection::scale(double factor)
{
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
(*it).scale(factor);
}
}
void
ExPolygonCollection::translate(double x, double y)
{
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
(*it).translate(x, y);
}
}
void
ExPolygonCollection::rotate(double angle, const Point &center)
{
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
(*it).rotate(angle, center);
}
}
template <class T>
bool ExPolygonCollection::contains(const T &item) const
{
for (const ExPolygon &poly : this->expolygons)
if (poly.contains(item))
return true;
return false;
}
template bool ExPolygonCollection::contains<Point>(const Point &item) const;
template bool ExPolygonCollection::contains<Line>(const Line &item) const;
template bool ExPolygonCollection::contains<Polyline>(const Polyline &item) const;
bool
ExPolygonCollection::contains_b(const Point &point) const
{
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
if (it->contains_b(point)) return true;
}
return false;
}
void
ExPolygonCollection::simplify(double tolerance)
{
ExPolygons expp;
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
it->simplify(tolerance, &expp);
}
this->expolygons = expp;
}
Polygon
ExPolygonCollection::convex_hull() const
{
Points pp;
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it)
pp.insert(pp.end(), it->contour.points.begin(), it->contour.points.end());
return Slic3r::Geometry::convex_hull(pp);
}
Lines
ExPolygonCollection::lines() const
{
Lines lines;
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
Lines ex_lines = it->lines();
lines.insert(lines.end(), ex_lines.begin(), ex_lines.end());
}
return lines;
}
Polygons
ExPolygonCollection::contours() const
{
Polygons contours;
contours.reserve(this->expolygons.size());
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it)
contours.push_back(it->contour);
return contours;
}
void
ExPolygonCollection::append(const ExPolygons &expp)
{
this->expolygons.insert(this->expolygons.end(), expp.begin(), expp.end());
}
BoundingBox get_extents(const ExPolygonCollection &expolygon)
{
return get_extents(expolygon.expolygons);
}
}

View file

@ -0,0 +1,41 @@
#ifndef slic3r_ExPolygonCollection_hpp_
#define slic3r_ExPolygonCollection_hpp_
#include "libslic3r.h"
#include "ExPolygon.hpp"
#include "Line.hpp"
#include "Polyline.hpp"
namespace Slic3r {
class ExPolygonCollection;
typedef std::vector<ExPolygonCollection> ExPolygonCollections;
class ExPolygonCollection
{
public:
ExPolygons expolygons;
ExPolygonCollection() {};
ExPolygonCollection(const ExPolygon &expolygon);
ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {};
operator Points() const;
operator Polygons() const;
operator ExPolygons&();
void scale(double factor);
void translate(double x, double y);
void rotate(double angle, const Point &center);
template <class T> bool contains(const T &item) const;
bool contains_b(const Point &point) const;
void simplify(double tolerance);
Polygon convex_hull() const;
Lines lines() const;
Polygons contours() const;
void append(const ExPolygons &expolygons);
};
extern BoundingBox get_extents(const ExPolygonCollection &expolygon);
}
#endif

138
src/libslic3r/Extruder.cpp Normal file
View file

@ -0,0 +1,138 @@
#include "Extruder.hpp"
namespace Slic3r {
Extruder::Extruder(unsigned int id, GCodeConfig *config) :
m_id(id),
m_config(config)
{
reset();
// cache values that are going to be called often
m_e_per_mm3 = this->extrusion_multiplier();
if (! m_config->use_volumetric_e)
m_e_per_mm3 /= this->filament_crossection();
}
double Extruder::extrude(double dE)
{
// in case of relative E distances we always reset to 0 before any output
if (m_config->use_relative_e_distances)
m_E = 0.;
m_E += dE;
m_absolute_E += dE;
if (dE < 0.)
m_retracted -= dE;
return dE;
}
/* This method makes sure the extruder is retracted by the specified amount
of filament and returns the amount of filament retracted.
If the extruder is already retracted by the same or a greater amount,
this method is a no-op.
The restart_extra argument sets the extra length to be used for
unretraction. If we're actually performing a retraction, any restart_extra
value supplied will overwrite the previous one if any. */
double Extruder::retract(double length, double restart_extra)
{
// in case of relative E distances we always reset to 0 before any output
if (m_config->use_relative_e_distances)
m_E = 0.;
double to_retract = std::max(0., length - m_retracted);
if (to_retract > 0.) {
m_E -= to_retract;
m_absolute_E -= to_retract;
m_retracted += to_retract;
m_restart_extra = restart_extra;
}
return to_retract;
}
double Extruder::unretract()
{
double dE = m_retracted + m_restart_extra;
this->extrude(dE);
m_retracted = 0.;
m_restart_extra = 0.;
return dE;
}
// Used filament volume in mm^3.
double Extruder::extruded_volume() const
{
return m_config->use_volumetric_e ?
m_absolute_E + m_retracted :
this->used_filament() * this->filament_crossection();
}
// Used filament length in mm.
double Extruder::used_filament() const
{
return m_config->use_volumetric_e ?
this->extruded_volume() / this->filament_crossection() :
m_absolute_E + m_retracted;
}
double Extruder::filament_diameter() const
{
return m_config->filament_diameter.get_at(m_id);
}
double Extruder::filament_density() const
{
return m_config->filament_density.get_at(m_id);
}
double Extruder::filament_cost() const
{
return m_config->filament_cost.get_at(m_id);
}
double Extruder::extrusion_multiplier() const
{
return m_config->extrusion_multiplier.get_at(m_id);
}
// Return a "retract_before_wipe" percentage as a factor clamped to <0, 1>
double Extruder::retract_before_wipe() const
{
return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_id) * 0.01));
}
double Extruder::retract_length() const
{
return m_config->retract_length.get_at(m_id);
}
double Extruder::retract_lift() const
{
return m_config->retract_lift.get_at(m_id);
}
int Extruder::retract_speed() const
{
return int(floor(m_config->retract_speed.get_at(m_id)+0.5));
}
int Extruder::deretract_speed() const
{
int speed = int(floor(m_config->deretract_speed.get_at(m_id)+0.5));
return (speed > 0) ? speed : this->retract_speed();
}
double Extruder::retract_restart_extra() const
{
return m_config->retract_restart_extra.get_at(m_id);
}
double Extruder::retract_length_toolchange() const
{
return m_config->retract_length_toolchange.get_at(m_id);
}
double Extruder::retract_restart_extra_toolchange() const
{
return m_config->retract_restart_extra_toolchange.get_at(m_id);
}
}

View file

@ -0,0 +1,81 @@
#ifndef slic3r_Extruder_hpp_
#define slic3r_Extruder_hpp_
#include "libslic3r.h"
#include "Point.hpp"
#include "PrintConfig.hpp"
namespace Slic3r {
class Extruder
{
public:
Extruder(unsigned int id, GCodeConfig *config);
virtual ~Extruder() {}
void reset() {
m_E = 0;
m_absolute_E = 0;
m_retracted = 0;
m_restart_extra = 0;
}
unsigned int id() const { return m_id; }
double extrude(double dE);
double retract(double length, double restart_extra);
double unretract();
double E() const { return m_E; }
void reset_E() { m_E = 0.; }
double e_per_mm(double mm3_per_mm) const { return mm3_per_mm * m_e_per_mm3; }
double e_per_mm3() const { return m_e_per_mm3; }
// Used filament volume in mm^3.
double extruded_volume() const;
// Used filament length in mm.
double used_filament() const;
double filament_diameter() const;
double filament_crossection() const { return this->filament_diameter() * this->filament_diameter() * 0.25 * PI; }
double filament_density() const;
double filament_cost() const;
double extrusion_multiplier() const;
double retract_before_wipe() const;
double retract_length() const;
double retract_lift() const;
int retract_speed() const;
int deretract_speed() const;
double retract_restart_extra() const;
double retract_length_toolchange() const;
double retract_restart_extra_toolchange() const;
// Constructor for a key object, to be used by the stdlib search functions.
static Extruder key(unsigned int id) { return Extruder(id); }
private:
// Private constructor to create a key for a search in std::set.
Extruder(unsigned int id) : m_id(id) {}
// Reference to GCodeWriter instance owned by GCodeWriter.
GCodeConfig *m_config;
// Print-wide global ID of this extruder.
unsigned int m_id;
// Current state of the extruder axis, may be resetted if use_relative_e_distances.
double m_E;
// Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics.
double m_absolute_E;
// Current positive amount of retraction.
double m_retracted;
// When retracted, this value stores the extra amount of priming on deretraction.
double m_restart_extra;
double m_e_per_mm3;
};
// Sort Extruder objects by the extruder id by default.
inline bool operator==(const Extruder &e1, const Extruder &e2) { return e1.id() == e2.id(); }
inline bool operator!=(const Extruder &e1, const Extruder &e2) { return e1.id() != e2.id(); }
inline bool operator< (const Extruder &e1, const Extruder &e2) { return e1.id() < e2.id(); }
inline bool operator> (const Extruder &e1, const Extruder &e2) { return e1.id() > e2.id(); }
}
#endif

View file

@ -0,0 +1,321 @@
#include "ExtrusionEntity.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "ExPolygonCollection.hpp"
#include "ClipperUtils.hpp"
#include "Extruder.hpp"
#include "Flow.hpp"
#include <cmath>
#include <limits>
#include <sstream>
namespace Slic3r {
void
ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
{
this->_inflate_collection(intersection_pl(this->polyline, collection), retval);
}
void
ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
{
this->_inflate_collection(diff_pl(this->polyline, collection), retval);
}
void
ExtrusionPath::clip_end(double distance)
{
this->polyline.clip_end(distance);
}
void
ExtrusionPath::simplify(double tolerance)
{
this->polyline.simplify(tolerance);
}
double
ExtrusionPath::length() const
{
return this->polyline.length();
}
void
ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
{
for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) {
ExtrusionPath* path = this->clone();
path->polyline = *it;
collection->entities.push_back(path);
}
}
void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon));
}
void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
{
// Instantiating the Flow class to get the line spacing.
// Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler.
Flow flow(this->width, this->height, 0.f, is_bridge(this->role()));
polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
}
void ExtrusionMultiPath::reverse()
{
for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->reverse();
std::reverse(this->paths.begin(), this->paths.end());
}
double ExtrusionMultiPath::length() const
{
double len = 0;
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
len += path->polyline.length();
return len;
}
void ExtrusionMultiPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->polygons_covered_by_width(out, scaled_epsilon);
}
void ExtrusionMultiPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->polygons_covered_by_spacing(out, scaled_epsilon);
}
double ExtrusionMultiPath::min_mm3_per_mm() const
{
double min_mm3_per_mm = std::numeric_limits<double>::max();
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm);
return min_mm3_per_mm;
}
Polyline ExtrusionMultiPath::as_polyline() const
{
Polyline out;
if (! paths.empty()) {
size_t len = 0;
for (size_t i_path = 0; i_path < paths.size(); ++ i_path) {
assert(! paths[i_path].polyline.points.empty());
assert(i_path == 0 || paths[i_path - 1].polyline.points.back() == paths[i_path].polyline.points.front());
len += paths[i_path].polyline.points.size();
}
// The connecting points between the segments are equal.
len -= paths.size() - 1;
assert(len > 0);
out.points.reserve(len);
out.points.push_back(paths.front().polyline.points.front());
for (size_t i_path = 0; i_path < paths.size(); ++ i_path)
out.points.insert(out.points.end(), paths[i_path].polyline.points.begin() + 1, paths[i_path].polyline.points.end());
}
return out;
}
bool
ExtrusionLoop::make_clockwise()
{
bool was_ccw = this->polygon().is_counter_clockwise();
if (was_ccw) this->reverse();
return was_ccw;
}
bool
ExtrusionLoop::make_counter_clockwise()
{
bool was_cw = this->polygon().is_clockwise();
if (was_cw) this->reverse();
return was_cw;
}
void
ExtrusionLoop::reverse()
{
for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->reverse();
std::reverse(this->paths.begin(), this->paths.end());
}
Polygon
ExtrusionLoop::polygon() const
{
Polygon polygon;
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
// for each polyline, append all points except the last one (because it coincides with the first one of the next polyline)
polygon.points.insert(polygon.points.end(), path->polyline.points.begin(), path->polyline.points.end()-1);
}
return polygon;
}
double
ExtrusionLoop::length() const
{
double len = 0;
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
len += path->polyline.length();
return len;
}
bool
ExtrusionLoop::split_at_vertex(const Point &point)
{
for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
int idx = path->polyline.find_point(point);
if (idx != -1) {
if (this->paths.size() == 1) {
// just change the order of points
path->polyline.points.insert(path->polyline.points.end(), path->polyline.points.begin() + 1, path->polyline.points.begin() + idx + 1);
path->polyline.points.erase(path->polyline.points.begin(), path->polyline.points.begin() + idx);
} else {
// new paths list starts with the second half of current path
ExtrusionPaths new_paths;
new_paths.reserve(this->paths.size() + 1);
{
ExtrusionPath p = *path;
p.polyline.points.erase(p.polyline.points.begin(), p.polyline.points.begin() + idx);
if (p.polyline.is_valid()) new_paths.push_back(p);
}
// then we add all paths until the end of current path list
new_paths.insert(new_paths.end(), path+1, this->paths.end()); // not including this path
// then we add all paths since the beginning of current list up to the previous one
new_paths.insert(new_paths.end(), this->paths.begin(), path); // not including this path
// finally we add the first half of current path
{
ExtrusionPath p = *path;
p.polyline.points.erase(p.polyline.points.begin() + idx + 1, p.polyline.points.end());
if (p.polyline.is_valid()) new_paths.push_back(p);
}
// we can now override the old path list with the new one and stop looping
std::swap(this->paths, new_paths);
}
return true;
}
}
return false;
}
// Splitting an extrusion loop, possibly made of multiple segments, some of the segments may be bridging.
void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
{
if (this->paths.empty())
return;
// Find the closest path and closest point belonging to that path. Avoid overhangs, if asked for.
size_t path_idx = 0;
Point p;
{
double min = std::numeric_limits<double>::max();
Point p_non_overhang;
size_t path_idx_non_overhang = 0;
double min_non_overhang = std::numeric_limits<double>::max();
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
Point p_tmp = point.projection_onto(path->polyline);
double dist = (p_tmp - point).cast<double>().norm();
if (dist < min) {
p = p_tmp;
min = dist;
path_idx = path - this->paths.begin();
}
if (prefer_non_overhang && ! is_bridge(path->role()) && dist < min_non_overhang) {
p_non_overhang = p_tmp;
min_non_overhang = dist;
path_idx_non_overhang = path - this->paths.begin();
}
}
if (prefer_non_overhang && min_non_overhang != std::numeric_limits<double>::max()) {
// Only apply the non-overhang point if there is one.
path_idx = path_idx_non_overhang;
p = p_non_overhang;
}
}
// now split path_idx in two parts
const ExtrusionPath &path = this->paths[path_idx];
ExtrusionPath p1(path.role(), path.mm3_per_mm, path.width, path.height);
ExtrusionPath p2(path.role(), path.mm3_per_mm, path.width, path.height);
path.polyline.split_at(p, &p1.polyline, &p2.polyline);
if (this->paths.size() == 1) {
if (! p1.polyline.is_valid())
std::swap(this->paths.front().polyline.points, p2.polyline.points);
else if (! p2.polyline.is_valid())
std::swap(this->paths.front().polyline.points, p1.polyline.points);
else {
p2.polyline.points.insert(p2.polyline.points.end(), p1.polyline.points.begin() + 1, p1.polyline.points.end());
std::swap(this->paths.front().polyline.points, p2.polyline.points);
}
} else {
// install the two paths
this->paths.erase(this->paths.begin() + path_idx);
if (p2.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p2);
if (p1.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p1);
}
// split at the new vertex
this->split_at_vertex(p);
}
void
ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const
{
*paths = this->paths;
while (distance > 0 && !paths->empty()) {
ExtrusionPath &last = paths->back();
double len = last.length();
if (len <= distance) {
paths->pop_back();
distance -= len;
} else {
last.polyline.clip_end(distance);
break;
}
}
}
bool
ExtrusionLoop::has_overhang_point(const Point &point) const
{
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
int pos = path->polyline.find_point(point);
if (pos != -1) {
// point belongs to this path
// we consider it overhang only if it's not an endpoint
return (is_bridge(path->role()) && pos > 0 && pos != (int)(path->polyline.points.size())-1);
}
}
return false;
}
void ExtrusionLoop::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->polygons_covered_by_width(out, scaled_epsilon);
}
void ExtrusionLoop::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->polygons_covered_by_spacing(out, scaled_epsilon);
}
double
ExtrusionLoop::min_mm3_per_mm() const
{
double min_mm3_per_mm = std::numeric_limits<double>::max();
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm);
return min_mm3_per_mm;
}
}

View file

@ -0,0 +1,321 @@
#ifndef slic3r_ExtrusionEntity_hpp_
#define slic3r_ExtrusionEntity_hpp_
#include "libslic3r.h"
#include "Polygon.hpp"
#include "Polyline.hpp"
namespace Slic3r {
class ExPolygonCollection;
class ExtrusionEntityCollection;
class Extruder;
/* Each ExtrusionRole value identifies a distinct set of { extruder, speed } */
enum ExtrusionRole {
erNone,
erPerimeter,
erExternalPerimeter,
erOverhangPerimeter,
erInternalInfill,
erSolidInfill,
erTopSolidInfill,
erBridgeInfill,
erGapFill,
erSkirt,
erSupportMaterial,
erSupportMaterialInterface,
erWipeTower,
erCustom,
// Extrusion role for a collection with multiple extrusion roles.
erMixed,
};
inline bool is_perimeter(ExtrusionRole role)
{
return role == erPerimeter
|| role == erExternalPerimeter
|| role == erOverhangPerimeter;
}
inline bool is_infill(ExtrusionRole role)
{
return role == erBridgeInfill
|| role == erInternalInfill
|| role == erSolidInfill
|| role == erTopSolidInfill;
}
inline bool is_solid_infill(ExtrusionRole role)
{
return role == erBridgeInfill
|| role == erSolidInfill
|| role == erTopSolidInfill;
}
inline bool is_bridge(ExtrusionRole role) {
return role == erBridgeInfill
|| role == erOverhangPerimeter;
}
/* Special flags describing loop */
enum ExtrusionLoopRole {
elrDefault,
elrContourInternalPerimeter,
elrSkirt,
};
class ExtrusionEntity
{
public:
virtual ExtrusionRole role() const = 0;
virtual bool is_collection() const { return false; }
virtual bool is_loop() const { return false; }
virtual bool can_reverse() const { return true; }
virtual ExtrusionEntity* clone() const = 0;
virtual ~ExtrusionEntity() {};
virtual void reverse() = 0;
virtual Point first_point() const = 0;
virtual Point last_point() const = 0;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
virtual void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const = 0;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
virtual double min_mm3_per_mm() const = 0;
virtual Polyline as_polyline() const = 0;
virtual void collect_polylines(Polylines &dst) const = 0;
virtual Polylines as_polylines() const { Polylines dst; this->collect_polylines(dst); return dst; }
virtual double length() const = 0;
virtual double total_volume() const = 0;
};
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
class ExtrusionPath : public ExtrusionEntity
{
public:
Polyline polyline;
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
double mm3_per_mm;
// Width of the extrusion, used for visualization purposes.
float width;
// Height of the extrusion, used for visualization purposed.
float height;
// Feedrate of the extrusion, used for visualization purposed.
float feedrate;
// Id of the extruder, used for visualization purposed.
unsigned int extruder_id;
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), m_role(role) {};
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), m_role(role) {};
ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), m_role(rhs.m_role) {}
ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), m_role(rhs.m_role) {}
// ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {};
ExtrusionPath& operator=(const ExtrusionPath &rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->polyline = rhs.polyline; return *this; }
ExtrusionPath& operator=(ExtrusionPath &&rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->polyline = std::move(rhs.polyline); return *this; }
ExtrusionPath* clone() const { return new ExtrusionPath (*this); }
void reverse() { this->polyline.reverse(); }
Point first_point() const override { return this->polyline.points.front(); }
Point last_point() const override { return this->polyline.points.back(); }
size_t size() const { return this->polyline.size(); }
bool empty() const { return this->polyline.empty(); }
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
// Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection.
// Currently not used.
void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
// Produce a list of extrusion paths into retval by removing parts of this path by ExPolygonCollection.
// Currently not used.
void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
void clip_end(double distance);
void simplify(double tolerance);
double length() const override;
ExtrusionRole role() const override { return m_role; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const { return this->mm3_per_mm; }
Polyline as_polyline() const { return this->polyline; }
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
double total_volume() const override { return mm3_per_mm * unscale<double>(length()); }
private:
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
ExtrusionRole m_role;
};
typedef std::vector<ExtrusionPath> ExtrusionPaths;
// Single continuous extrusion path, possibly with varying extrusion thickness, extrusion height or bridging / non bridging.
class ExtrusionMultiPath : public ExtrusionEntity
{
public:
ExtrusionPaths paths;
ExtrusionMultiPath() {};
ExtrusionMultiPath(const ExtrusionMultiPath &rhs) : paths(rhs.paths) {}
ExtrusionMultiPath(ExtrusionMultiPath &&rhs) : paths(std::move(rhs.paths)) {}
ExtrusionMultiPath(const ExtrusionPaths &paths) : paths(paths) {};
ExtrusionMultiPath(const ExtrusionPath &path) { this->paths.push_back(path); }
ExtrusionMultiPath& operator=(const ExtrusionMultiPath &rhs) { this->paths = rhs.paths; return *this; }
ExtrusionMultiPath& operator=(ExtrusionMultiPath &&rhs) { this->paths = std::move(rhs.paths); return *this; }
bool is_loop() const { return false; }
bool can_reverse() const { return true; }
ExtrusionMultiPath* clone() const { return new ExtrusionMultiPath(*this); }
void reverse();
Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { return this->paths.back().polyline.points.back(); }
double length() const override;
ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const;
Polyline as_polyline() const;
void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
};
// Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging.
class ExtrusionLoop : public ExtrusionEntity
{
public:
ExtrusionPaths paths;
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {};
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {};
ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {};
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
{ this->paths.push_back(path); };
ExtrusionLoop(const ExtrusionPath &&path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
{ this->paths.emplace_back(std::move(path)); };
bool is_loop() const { return true; }
bool can_reverse() const { return false; }
ExtrusionLoop* clone() const { return new ExtrusionLoop (*this); }
bool make_clockwise();
bool make_counter_clockwise();
void reverse();
Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); }
Polygon polygon() const;
double length() const override;
bool split_at_vertex(const Point &point);
void split_at(const Point &point, bool prefer_non_overhang);
void clip_end(double distance, ExtrusionPaths* paths) const;
// Test, whether the point is extruded by a bridging flow.
// This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead.
bool has_overhang_point(const Point &point) const;
ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
ExtrusionLoopRole loop_role() const { return m_loop_role; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const;
Polyline as_polyline() const { return this->polygon().split_at_first_point(); }
void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
private:
ExtrusionLoopRole m_loop_role;
};
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
dst.back().polyline = polyline;
}
}
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
dst.back().polyline = std::move(polyline);
}
polylines.clear();
}
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = polyline;
}
}
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = std::move(polyline);
}
polylines.clear();
}
inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, ExtrusionRole role, double mm3_per_mm, float width, float height)
{
dst.reserve(dst.size() + loops.size());
for (Polygon &poly : loops) {
if (poly.is_valid()) {
ExtrusionPath path(role, mm3_per_mm, width, height);
path.polyline.points = std::move(poly.points);
path.polyline.points.push_back(path.polyline.points.front());
dst.emplace_back(new ExtrusionLoop(std::move(path)));
}
}
loops.clear();
}
}
#endif

View file

@ -0,0 +1,220 @@
#include "ExtrusionEntityCollection.hpp"
#include <algorithm>
#include <cmath>
#include <map>
namespace Slic3r {
ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths)
: no_sort(false)
{
this->append(paths);
}
ExtrusionEntityCollection& ExtrusionEntityCollection::operator= (const ExtrusionEntityCollection &other)
{
this->entities = other.entities;
for (size_t i = 0; i < this->entities.size(); ++i)
this->entities[i] = this->entities[i]->clone();
this->orig_indices = other.orig_indices;
this->no_sort = other.no_sort;
return *this;
}
void
ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c)
{
std::swap(this->entities, c.entities);
std::swap(this->orig_indices, c.orig_indices);
std::swap(this->no_sort, c.no_sort);
}
void ExtrusionEntityCollection::clear()
{
for (size_t i = 0; i < this->entities.size(); ++i)
delete this->entities[i];
this->entities.clear();
}
ExtrusionEntityCollection::operator ExtrusionPaths() const
{
ExtrusionPaths paths;
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(*it))
paths.push_back(*path);
}
return paths;
}
ExtrusionEntityCollection*
ExtrusionEntityCollection::clone() const
{
ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this);
for (size_t i = 0; i < coll->entities.size(); ++i)
coll->entities[i] = this->entities[i]->clone();
return coll;
}
void
ExtrusionEntityCollection::reverse()
{
for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
// Don't reverse it if it's a loop, as it doesn't change anything in terms of elements ordering
// and caller might rely on winding order
if (!(*it)->is_loop()) (*it)->reverse();
}
std::reverse(this->entities.begin(), this->entities.end());
}
void
ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity)
{
delete this->entities[i];
this->entities[i] = entity.clone();
}
void
ExtrusionEntityCollection::remove(size_t i)
{
delete this->entities[i];
this->entities.erase(this->entities.begin() + i);
}
ExtrusionEntityCollection
ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const
{
ExtrusionEntityCollection coll;
this->chained_path(&coll, no_reverse, role);
return coll;
}
void
ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
{
if (this->entities.empty()) return;
this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role, orig_indices);
}
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(Point start_near, bool no_reverse, ExtrusionRole role) const
{
ExtrusionEntityCollection coll;
this->chained_path_from(start_near, &coll, no_reverse, role);
return coll;
}
void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
{
if (this->no_sort) {
*retval = *this;
return;
}
retval->entities.reserve(this->entities.size());
retval->orig_indices.reserve(this->entities.size());
// if we're asked to return the original indices, build a map
std::map<ExtrusionEntity*,size_t> indices_map;
ExtrusionEntitiesPtr my_paths;
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if (role != erMixed) {
// The caller wants only paths with a specific extrusion role.
auto role2 = (*it)->role();
if (role != role2) {
// This extrusion entity does not match the role asked.
assert(role2 != erMixed);
continue;
}
}
ExtrusionEntity* entity = (*it)->clone();
my_paths.push_back(entity);
if (orig_indices != NULL) indices_map[entity] = it - this->entities.begin();
}
Points endpoints;
for (ExtrusionEntitiesPtr::iterator it = my_paths.begin(); it != my_paths.end(); ++it) {
endpoints.push_back((*it)->first_point());
if (no_reverse || !(*it)->can_reverse()) {
endpoints.push_back((*it)->first_point());
} else {
endpoints.push_back((*it)->last_point());
}
}
while (!my_paths.empty()) {
// find nearest point
int start_index = start_near.nearest_point_index(endpoints);
int path_index = start_index/2;
ExtrusionEntity* entity = my_paths.at(path_index);
// never reverse loops, since it's pointless for chained path and callers might depend on orientation
if (start_index % 2 && !no_reverse && entity->can_reverse()) {
entity->reverse();
}
retval->entities.push_back(my_paths.at(path_index));
if (orig_indices != NULL) orig_indices->push_back(indices_map[entity]);
my_paths.erase(my_paths.begin() + path_index);
endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2);
start_near = retval->entities.back()->last_point();
}
}
void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it)
(*it)->polygons_covered_by_width(out, scaled_epsilon);
}
void ExtrusionEntityCollection::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it)
(*it)->polygons_covered_by_spacing(out, scaled_epsilon);
}
/* Recursively count paths and loops contained in this collection */
size_t
ExtrusionEntityCollection::items_count() const
{
size_t count = 0;
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if ((*it)->is_collection()) {
ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(*it);
count += collection->items_count();
} else {
++count;
}
}
return count;
}
/* Returns a single vector of pointers to all non-collection items contained in this one */
void
ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const
{
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if ((*it)->is_collection()) {
ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(*it);
retval->append(collection->flatten().entities);
} else {
retval->append(**it);
}
}
}
ExtrusionEntityCollection
ExtrusionEntityCollection::flatten() const
{
ExtrusionEntityCollection coll;
this->flatten(&coll);
return coll;
}
double
ExtrusionEntityCollection::min_mm3_per_mm() const
{
double min_mm3_per_mm = std::numeric_limits<double>::max();
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it)
min_mm3_per_mm = std::min(min_mm3_per_mm, (*it)->min_mm3_per_mm());
return min_mm3_per_mm;
}
}

View file

@ -0,0 +1,108 @@
#ifndef slic3r_ExtrusionEntityCollection_hpp_
#define slic3r_ExtrusionEntityCollection_hpp_
#include "libslic3r.h"
#include "ExtrusionEntity.hpp"
namespace Slic3r {
class ExtrusionEntityCollection : public ExtrusionEntity
{
public:
ExtrusionEntityCollection* clone() const;
ExtrusionEntitiesPtr entities; // we own these entities
std::vector<size_t> orig_indices; // handy for XS
bool no_sort;
ExtrusionEntityCollection(): no_sort(false) {};
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : orig_indices(other.orig_indices), no_sort(other.no_sort) { this->append(other.entities); }
ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), orig_indices(std::move(other.orig_indices)), no_sort(other.no_sort) {}
explicit ExtrusionEntityCollection(const ExtrusionPaths &paths);
ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other);
ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other)
{ this->entities = std::move(other.entities); this->orig_indices = std::move(other.orig_indices); this->no_sort = other.no_sort; return *this; }
~ExtrusionEntityCollection() { clear(); }
explicit operator ExtrusionPaths() const;
bool is_collection() const { return true; };
ExtrusionRole role() const override {
ExtrusionRole out = erNone;
for (const ExtrusionEntity *ee : entities) {
ExtrusionRole er = ee->role();
out = (out == erNone || out == er) ? er : erMixed;
}
return out;
}
bool can_reverse() const { return !this->no_sort; };
bool empty() const { return this->entities.empty(); };
void clear();
void swap (ExtrusionEntityCollection &c);
void append(const ExtrusionEntity &entity) { this->entities.push_back(entity.clone()); }
void append(const ExtrusionEntitiesPtr &entities) {
this->entities.reserve(this->entities.size() + entities.size());
for (ExtrusionEntitiesPtr::const_iterator ptr = entities.begin(); ptr != entities.end(); ++ptr)
this->entities.push_back((*ptr)->clone());
}
void append(ExtrusionEntitiesPtr &&src) {
if (entities.empty())
entities = std::move(src);
else {
std::move(std::begin(src), std::end(src), std::back_inserter(entities));
src.clear();
}
}
void append(const ExtrusionPaths &paths) {
this->entities.reserve(this->entities.size() + paths.size());
for (const ExtrusionPath &path : paths)
this->entities.emplace_back(path.clone());
}
void append(ExtrusionPaths &&paths) {
this->entities.reserve(this->entities.size() + paths.size());
for (ExtrusionPath &path : paths)
this->entities.emplace_back(new ExtrusionPath(std::move(path)));
}
void replace(size_t i, const ExtrusionEntity &entity);
void remove(size_t i);
ExtrusionEntityCollection chained_path(bool no_reverse = false, ExtrusionRole role = erMixed) const;
void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector<size_t>* orig_indices = nullptr) const;
ExtrusionEntityCollection chained_path_from(Point start_near, bool no_reverse = false, ExtrusionRole role = erMixed) const;
void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector<size_t>* orig_indices = nullptr) const;
void reverse();
Point first_point() const { return this->entities.front()->first_point(); }
Point last_point() const { return this->entities.back()->last_point(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
size_t items_count() const;
void flatten(ExtrusionEntityCollection* retval) const;
ExtrusionEntityCollection flatten() const;
double min_mm3_per_mm() const;
double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
// Following methods shall never be called on an ExtrusionEntityCollection.
Polyline as_polyline() const {
throw std::runtime_error("Calling as_polyline() on a ExtrusionEntityCollection");
return Polyline();
};
void collect_polylines(Polylines &dst) const override {
for (ExtrusionEntity* extrusion_entity : this->entities)
extrusion_entity->collect_polylines(dst);
}
double length() const override {
throw std::runtime_error("Calling length() on a ExtrusionEntityCollection");
return 0.;
}
};
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,59 @@
#ifndef slic3r_ExtrusionSimulator_hpp_
#define slic3r_ExtrusionSimulator_hpp_
#include "libslic3r.h"
#include "ExtrusionEntity.hpp"
#include "BoundingBox.hpp"
namespace Slic3r {
enum ExtrusionSimulationType
{
ExtrusionSimulationSimple,
ExtrusionSimulationDontSpread,
ExtrisopmSimulationSpreadNotOverfilled,
ExtrusionSimulationSpreadFull,
ExtrusionSimulationSpreadExcess
};
// An opaque class, to keep the boost stuff away from the header.
class ExtrusionSimulatorImpl;
class ExtrusionSimulator
{
public:
ExtrusionSimulator();
~ExtrusionSimulator();
// Size of the image, that will be returned by image_ptr().
// The image may be bigger than the viewport as many graphics drivers
// expect the size of a texture to be rounded to a power of two.
void set_image_size(const Point &image_size);
// Which part of the image shall be rendered to?
void set_viewport(const BoundingBox &viewport);
// Shift and scale of the rendered extrusion paths into the viewport.
void set_bounding_box(const BoundingBox &bbox);
// Reset the extrusion accumulator to zero for all buckets.
void reset_accumulator();
// Paint a thick path into an extrusion buffer.
// A simple implementation is provided now, splatting a rectangular extrusion for each linear segment.
// In the future, spreading and suqashing of a material will be simulated.
void extrude_to_accumulator(const ExtrusionPath &path, const Point &shift, ExtrusionSimulationType simulationType);
// Evaluate the content of the accumulator and paint it into the viewport.
// After this call the image_ptr() call will return a valid image.
void evaluate_accumulator(ExtrusionSimulationType simulationType);
// An RGBA image of image_size, to be loaded into a GPU texture.
const void* image_ptr() const;
private:
Point image_size;
BoundingBox viewport;
BoundingBox bbox;
ExtrusionSimulatorImpl *pimpl;
};
}
#endif /* slic3r_ExtrusionSimulator_hpp_ */

View file

@ -0,0 +1,52 @@
#ifndef slic3r_FileParserError_hpp_
#define slic3r_FileParserError_hpp_
#include "libslic3r.h"
#include <string>
#include <boost/filesystem/path.hpp>
#include <stdexcept>
namespace Slic3r {
// Generic file parser error, mostly copied from boost::property_tree::file_parser_error
class file_parser_error: public std::runtime_error
{
public:
file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) :
std::runtime_error(format_what(msg, file, line)),
m_message(msg), m_filename(file), m_line(line) {}
file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) :
std::runtime_error(format_what(msg, file.string(), line)),
m_message(msg), m_filename(file.string()), m_line(line) {}
// gcc 3.4.2 complains about lack of throw specifier on compiler
// generated dtor
~file_parser_error() throw() {}
// Get error message (without line and file - use what() to get full message)
std::string message() const { return m_message; }
// Get error filename
std::string filename() const { return m_filename; }
// Get error line number
unsigned long line() const { return m_line; }
private:
std::string m_message;
std::string m_filename;
unsigned long m_line;
// Format error message to be returned by std::runtime_error::what()
static std::string format_what(const std::string &msg, const std::string &file, unsigned long l)
{
std::stringstream stream;
stream << (file.empty() ? "<unspecified file>" : file.c_str());
if (l > 0)
stream << '(' << l << ')';
stream << ": " << msg;
return stream.str();
}
};
}; // Slic3r
#endif // slic3r_FileParserError_hpp_

271
src/libslic3r/Fill/Fill.cpp Normal file
View file

@ -0,0 +1,271 @@
#include <assert.h>
#include <stdio.h>
#include <memory>
#include "../ClipperUtils.hpp"
#include "../Geometry.hpp"
#include "../Layer.hpp"
#include "../Print.hpp"
#include "../PrintConfig.hpp"
#include "../Surface.hpp"
#include "FillBase.hpp"
namespace Slic3r {
struct SurfaceGroupAttrib
{
SurfaceGroupAttrib() : is_solid(false), flow_width(0.f), pattern(-1) {}
bool operator==(const SurfaceGroupAttrib &other) const
{ return is_solid == other.is_solid && flow_width == other.flow_width && pattern == other.pattern; }
bool is_solid;
float flow_width;
// pattern is of type InfillPattern, -1 for an unset pattern.
int pattern;
};
// Generate infills for Slic3r::Layer::Region.
// The Slic3r::Layer::Region at this point of time may contain
// surfaces of various types (internal/bridge/top/bottom/solid).
// The infills are generated on the groups of surfaces with a compatible type.
// Returns an array of Slic3r::ExtrusionPath::Collection objects containing the infills generaed now
// and the thin fills generated by generate_perimeters().
void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
{
// Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id;
double fill_density = layerm.region()->config().fill_density;
Flow infill_flow = layerm.flow(frInfill);
Flow solid_infill_flow = layerm.flow(frSolidInfill);
Flow top_solid_infill_flow = layerm.flow(frTopSolidInfill);
Surfaces surfaces;
// merge adjacent surfaces
// in case of bridge surfaces, the ones with defined angle will be attached to the ones
// without any angle (shouldn't this logic be moved to process_external_surfaces()?)
{
Polygons polygons_bridged;
polygons_bridged.reserve(layerm.fill_surfaces.surfaces.size());
for (Surfaces::iterator it = layerm.fill_surfaces.surfaces.begin(); it != layerm.fill_surfaces.surfaces.end(); ++ it)
if (it->bridge_angle >= 0)
polygons_append(polygons_bridged, *it);
// group surfaces by distinct properties (equal surface_type, thickness, thickness_layers, bridge_angle)
// group is of type Slic3r::SurfaceCollection
//FIXME: Use some smart heuristics to merge similar surfaces to eliminate tiny regions.
std::vector<SurfacesPtr> groups;
layerm.fill_surfaces.group(&groups);
// merge compatible groups (we can generate continuous infill for them)
{
// cache flow widths and patterns used for all solid groups
// (we'll use them for comparing compatible groups)
std::vector<SurfaceGroupAttrib> group_attrib(groups.size());
for (size_t i = 0; i < groups.size(); ++ i) {
// we can only merge solid non-bridge surfaces, so discard
// non-solid surfaces
const Surface &surface = *groups[i].front();
if (surface.is_solid() && (!surface.is_bridge() || layerm.layer()->id() == 0)) {
group_attrib[i].is_solid = true;
group_attrib[i].flow_width = (surface.surface_type == stTop) ? top_solid_infill_flow.width : solid_infill_flow.width;
group_attrib[i].pattern = surface.is_external() ? layerm.region()->config().external_fill_pattern.value : ipRectilinear;
}
}
// Loop through solid groups, find compatible groups and append them to this one.
for (size_t i = 0; i < groups.size(); ++ i) {
if (! group_attrib[i].is_solid)
continue;
for (size_t j = i + 1; j < groups.size();) {
if (group_attrib[i] == group_attrib[j]) {
// groups are compatible, merge them
groups[i].insert(groups[i].end(), groups[j].begin(), groups[j].end());
groups.erase(groups.begin() + j);
group_attrib.erase(group_attrib.begin() + j);
} else
++ j;
}
}
}
// Give priority to bridges. Process the bridges in the first round, the rest of the surfaces in the 2nd round.
for (size_t round = 0; round < 2; ++ round) {
for (std::vector<SurfacesPtr>::iterator it_group = groups.begin(); it_group != groups.end(); ++ it_group) {
const SurfacesPtr &group = *it_group;
bool is_bridge = group.front()->bridge_angle >= 0;
if (is_bridge != (round == 0))
continue;
// Make a union of polygons defining the infiill regions of a group, use a safety offset.
Polygons union_p = union_(to_polygons(*it_group), true);
// Subtract surfaces having a defined bridge_angle from any other, use a safety offset.
if (! polygons_bridged.empty() && ! is_bridge)
union_p = diff(union_p, polygons_bridged, true);
// subtract any other surface already processed
//FIXME Vojtech: Because the bridge surfaces came first, they are subtracted twice!
// Using group.front() as a template.
surfaces_append(surfaces, diff_ex(union_p, to_polygons(surfaces), true), *group.front());
}
}
}
// we need to detect any narrow surfaces that might collapse
// when adding spacing below
// such narrow surfaces are often generated in sloping walls
// by bridge_over_infill() and combine_infill() as a result of the
// subtraction of the combinable area from the layer infill area,
// which leaves small areas near the perimeters
// we are going to grow such regions by overlapping them with the void (if any)
// TODO: detect and investigate whether there could be narrow regions without
// any void neighbors
{
coord_t distance_between_surfaces = std::max(
std::max(infill_flow.scaled_spacing(), solid_infill_flow.scaled_spacing()),
top_solid_infill_flow.scaled_spacing());
Polygons surfaces_polygons = to_polygons(surfaces);
Polygons collapsed = diff(
surfaces_polygons,
offset2(surfaces_polygons, -distance_between_surfaces/2, +distance_between_surfaces/2),
true);
Polygons to_subtract;
to_subtract.reserve(collapsed.size() + number_polygons(surfaces));
for (Surfaces::const_iterator it_surface = surfaces.begin(); it_surface != surfaces.end(); ++ it_surface)
if (it_surface->surface_type == stInternalVoid)
polygons_append(to_subtract, *it_surface);
polygons_append(to_subtract, collapsed);
surfaces_append(
surfaces,
intersection_ex(
offset(collapsed, distance_between_surfaces),
to_subtract,
true),
stInternalSolid);
}
if (0) {
// require "Slic3r/SVG.pm";
// Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg",
// expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ],
// red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ],
// );
}
for (const Surface &surface : surfaces) {
if (surface.surface_type == stInternalVoid)
continue;
InfillPattern fill_pattern = layerm.region()->config().fill_pattern.value;
double density = fill_density;
FlowRole role = (surface.surface_type == stTop) ? frTopSolidInfill :
(surface.is_solid() ? frSolidInfill : frInfill);
bool is_bridge = layerm.layer()->id() > 0 && surface.is_bridge();
if (surface.is_solid()) {
density = 100.;
fill_pattern = (surface.is_external() && ! is_bridge) ?
layerm.region()->config().external_fill_pattern.value :
ipRectilinear;
} else if (density <= 0)
continue;
// get filler object
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(fill_pattern));
f->set_bounding_box(layerm.layer()->object()->bounding_box());
// calculate the actual flow we'll be using for this infill
coordf_t h = (surface.thickness == -1) ? layerm.layer()->height : surface.thickness;
Flow flow = layerm.region()->flow(
role,
h,
is_bridge || f->use_bridge_flow(), // bridge flow?
layerm.layer()->id() == 0, // first layer?
-1, // auto width
*layerm.layer()->object()
);
// calculate flow spacing for infill pattern generation
bool using_internal_flow = false;
if (! surface.is_solid() && ! is_bridge) {
// it's internal infill, so we can calculate a generic flow spacing
// for all layers, for avoiding the ugly effect of
// misaligned infill on first layer because of different extrusion width and
// layer height
Flow internal_flow = layerm.region()->flow(
frInfill,
layerm.layer()->object()->config().layer_height.value, // TODO: handle infill_every_layers?
false, // no bridge
false, // no first layer
-1, // auto width
*layerm.layer()->object()
);
f->spacing = internal_flow.spacing();
using_internal_flow = true;
} else {
f->spacing = flow.spacing();
}
double link_max_length = 0.;
if (! is_bridge) {
#if 0
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
#else
if (density > 80.) // 80%
link_max_length = 3. * f->spacing;
#endif
}
f->layer_id = layerm.layer()->id();
f->z = layerm.layer()->print_z;
f->angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value));
// Maximum length of the perimeter segment linking two infill lines.
f->link_max_length = scale_(link_max_length);
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER;
// f->layer_height = h;
// apply half spacing using this flow's own spacing and generate infill
FillParams params;
params.density = 0.01 * density;
// params.dont_adjust = true;
params.dont_adjust = false;
Polylines polylines = f->fill_surface(&surface, params);
if (polylines.empty())
continue;
// calculate actual flow from spacing (which might have been adjusted by the infill
// pattern generator)
if (using_internal_flow) {
// if we used the internal flow we're not doing a solid infill
// so we can safely ignore the slight variation that might have
// been applied to $f->flow_spacing
} else {
flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow());
}
// Save into layer.
auto *eec = new ExtrusionEntityCollection();
out.entities.push_back(eec);
// Only concentric fills are not sorted.
eec->no_sort = f->no_sort();
extrusion_entities_append_paths(
eec->entities, STDMOVE(polylines),
is_bridge ?
erBridgeInfill :
(surface.is_solid() ?
((surface.surface_type == stTop) ? erTopSolidInfill : erSolidInfill) :
erInternalInfill),
flow.mm3_per_mm(), flow.width, flow.height);
}
// add thin fill regions
// thin_fills are of C++ Slic3r::ExtrusionEntityCollection, perl type Slic3r::ExtrusionPath::Collection
// Unpacks the collection, creates multiple collections per path.
// The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection.
// Why the paths are unpacked?
for (const ExtrusionEntity *thin_fill : layerm.thin_fills.entities) {
ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection());
out.entities.push_back(&collection);
collection.entities.push_back(thin_fill->clone());
}
}
} // namespace Slic3r

View file

@ -0,0 +1,36 @@
#ifndef slic3r_Fill_hpp_
#define slic3r_Fill_hpp_
#include <memory.h>
#include <float.h>
#include <stdint.h>
#include "../libslic3r.h"
#include "../BoundingBox.hpp"
#include "../PrintConfig.hpp"
#include "FillBase.hpp"
namespace Slic3r {
class ExtrusionEntityCollection;
class LayerRegion;
// 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;
};
void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out);
} // namespace Slic3r
#endif // slic3r_Fill_hpp_

View file

@ -0,0 +1,204 @@
#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;
}
// 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 (Vec2d &pt : pts) {
pt(0) = clamp(minX, maxX, pt(0));
pt(1) = clamp(minY, maxY, pt(1));
}
}
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(Vec2d(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 = fabs(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)(0) * scaleFactor), coord_t((*it)(1) * scaleFactor)));
}
return result;
}
void Fill3DHoneycomb::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out)
{
// no rotation is supported for this infill pattern
BoundingBox bb = expolygon.contour.bounding_box();
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(_align_to_grid(bb.min, Point(2*distance, 2*distance)));
// generate pattern
Polylines polylines = makeGrid(
scale_(this->z),
distance,
ceil(bb.size()(0) / distance) + 1,
ceil(bb.size()(1) / distance) + 1,
((this->layer_id/thickness_layers) % 2) + 1);
// move pattern in place
for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it)
it->translate(bb.min(0), bb.min(1));
// clip pattern to boundaries
polylines = intersection_pl(polylines, (Polygons)expolygon);
// 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(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true;
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.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 ((last_point - first_point).cast<double>().norm() <= 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.
polylines_out.emplace_back(std::move(*it_polyline));
first = false;
}
}
}
} // namespace Slic3r

View file

@ -0,0 +1,32 @@
#ifndef slic3r_Fill3DHoneycomb_hpp_
#define slic3r_Fill3DHoneycomb_hpp_
#include <map>
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class Fill3DHoneycomb : public Fill
{
public:
virtual Fill* clone() const { return new Fill3DHoneycomb(*this); };
virtual ~Fill3DHoneycomb() {}
// require bridge flow since most of this pattern hangs in air
virtual bool use_bridge_flow() const { return true; }
protected:
virtual void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
};
} // namespace Slic3r
#endif // slic3r_Fill3DHoneycomb_hpp_

View file

@ -0,0 +1,133 @@
#include <stdio.h>
#include "../ClipperUtils.hpp"
#include "../Surface.hpp"
#include "../PrintConfig.hpp"
#include "FillBase.hpp"
#include "FillConcentric.hpp"
#include "FillHoneycomb.hpp"
#include "Fill3DHoneycomb.hpp"
#include "FillGyroid.hpp"
#include "FillPlanePath.hpp"
#include "FillRectilinear.hpp"
#include "FillRectilinear2.hpp"
#include "FillRectilinear3.hpp"
namespace Slic3r {
Fill* Fill::new_from_type(const InfillPattern type)
{
switch (type) {
case ipConcentric: return new FillConcentric();
case ipHoneycomb: return new FillHoneycomb();
case ip3DHoneycomb: return new Fill3DHoneycomb();
case ipGyroid: return new FillGyroid();
case ipRectilinear: return new FillRectilinear2();
// case ipRectilinear: return new FillRectilinear();
case ipLine: return new FillLine();
case ipGrid: return new FillGrid2();
case ipTriangles: return new FillTriangles();
case ipStars: return new FillStars();
case ipCubic: return new FillCubic();
// case ipGrid: return new FillGrid();
case ipArchimedeanChords: return new FillArchimedeanChords();
case ipHilbertCurve: return new FillHilbertCurve();
case ipOctagramSpiral: return new FillOctagramSpiral();
default: throw std::invalid_argument("unknown type");;
}
}
Fill* Fill::new_from_type(const std::string &type)
{
const t_config_enum_values &enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values();
t_config_enum_values::const_iterator it = enum_keys_map.find(type);
return (it == enum_keys_map.end()) ? nullptr : new_from_type(InfillPattern(it->second));
}
Polylines Fill::fill_surface(const Surface *surface, const FillParams &params)
{
// Perform offset.
Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing)));
// Create the infills for each of the regions.
Polylines polylines_out;
for (size_t i = 0; i < expp.size(); ++ i)
_fill_surface_single(
params,
surface->thickness_layers,
_infill_direction(surface),
expp[i],
polylines_out);
return polylines_out;
}
// Calculate a new spacing to fill width with possibly integer number of lines,
// the first and last line being centered at the interval ends.
// This function possibly increases the spacing, never decreases,
// and for a narrow width the increase in spacing may become severe,
// therefore the adjustment is limited to 20% increase.
coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance)
{
assert(width >= 0);
assert(distance > 0);
// floor(width / distance)
coord_t number_of_intervals = (width - EPSILON) / distance;
coord_t distance_new = (number_of_intervals == 0) ?
distance :
((width - EPSILON) / number_of_intervals);
const coordf_t factor = coordf_t(distance_new) / coordf_t(distance);
assert(factor > 1. - 1e-5);
// How much could the extrusion width be increased? By 20%.
const coordf_t factor_max = 1.2;
if (factor > factor_max)
distance_new = coord_t(floor((coordf_t(distance) * factor_max + 0.5)));
return distance_new;
}
// Returns orientation of the infill and the reference point of the infill pattern.
// For a normal print, the reference point is the center of a bounding box of the STL.
std::pair<float, Point> Fill::_infill_direction(const Surface *surface) const
{
// set infill angle
float out_angle = this->angle;
if (out_angle == FLT_MAX) {
//FIXME Vojtech: Add a warning?
printf("Using undefined infill angle\n");
out_angle = 0.f;
}
// Bounding box is the bounding box of a perl object Slic3r::Print::Object (c++ object Slic3r::PrintObject)
// The bounding box is only undefined in unit tests.
Point out_shift = empty(this->bounding_box) ?
surface->expolygon.contour.bounding_box().center() :
this->bounding_box.center();
#if 0
if (empty(this->bounding_box)) {
printf("Fill::_infill_direction: empty bounding box!");
} else {
printf("Fill::_infill_direction: reference point %d, %d\n", out_shift.x, out_shift.y);
}
#endif
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);
#ifdef SLIC3R_DEBUG
printf("Filling bridge with angle %f\n", surface->bridge_angle);
#endif /* SLIC3R_DEBUG */
out_angle = surface->bridge_angle;
} else if (this->layer_id != size_t(-1)) {
// alternate fill direction
out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers);
} else {
// printf("Layer_ID undefined!\n");
}
out_angle += float(M_PI/2.);
return std::pair<float, Point>(out_angle, out_shift);
}
} // namespace Slic3r

View file

@ -0,0 +1,133 @@
#ifndef slic3r_FillBase_hpp_
#define slic3r_FillBase_hpp_
#include <assert.h>
#include <memory.h>
#include <float.h>
#include <stdint.h>
#include "../libslic3r.h"
#include "../BoundingBox.hpp"
#include "../PrintConfig.hpp"
namespace Slic3r {
class Surface;
struct FillParams
{
FillParams() {
memset(this, 0, sizeof(FillParams));
// Adjustment does not work.
dont_adjust = true;
}
bool full_infill() const { return density > 0.9999f; }
// Fill density, fraction in <0, 1>
float density;
// 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;
// Z coordinate of the top print surface, in unscaled coordinates
coordf_t z;
// in unscaled coordinates
coordf_t spacing;
// infill / perimeter overlap, in unscaled coordinates
coordf_t overlap;
// in radians, ccw, 0 = East
float angle;
// In scaled coordinates. Maximum lenght of a perimeter segment connecting two infill lines.
// Used by the FillRectilinear2, FillGrid2, FillTriangles, FillStars and FillCubic.
// If left to zero, the links will not be limited.
coord_t link_max_length;
// In scaled coordinates. Used by the concentric infill pattern to clip the loops to create extrusion paths.
coord_t loop_clipping;
// In scaled coordinates. Bounding box of the 2D projection of the object.
BoundingBox bounding_box;
public:
virtual ~Fill() {}
static Fill* new_from_type(const InfillPattern type);
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);
protected:
Fill() :
layer_id(size_t(-1)),
z(0.),
spacing(0.),
// Infill / perimeter overlap.
overlap(0.),
// Initial angle is undefined.
angle(FLT_MAX),
link_max_length(0),
loop_clipping(0),
// The initial bounding box is empty, therefore undefined.
bounding_box(Point(0, 0), Point(-1, -1))
{}
// The expolygon may be modified by the method to avoid a copy.
virtual void _fill_surface_single(
const FillParams & /* params */,
unsigned int /* thickness_layers */,
const std::pair<float, Point> & /* direction */,
ExPolygon & /* expolygon */,
Polylines & /* polylines_out */) {};
virtual float _layer_angle(size_t idx) const { return (idx & 1) ? float(M_PI/2.) : 0; }
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
public:
static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance);
// Align a coordinate to a grid. The coordinate may be negative,
// the aligned value will never be bigger than the original one.
static coord_t _align_to_grid(const coord_t coord, const coord_t spacing) {
// Current C++ standard defines the result of integer division to be rounded to zero,
// for both positive and negative numbers. Here we want to round down for negative
// numbers as well.
coord_t aligned = (coord < 0) ?
((coord - spacing + 1) / spacing) * spacing :
(coord / spacing) * spacing;
assert(aligned <= coord);
return aligned;
}
static Point _align_to_grid(Point coord, Point spacing)
{ return Point(_align_to_grid(coord(0), spacing(0)), _align_to_grid(coord(1), spacing(1))); }
static coord_t _align_to_grid(coord_t coord, coord_t spacing, coord_t base)
{ return base + _align_to_grid(coord - base, spacing); }
static Point _align_to_grid(Point coord, Point spacing, Point base)
{ return Point(_align_to_grid(coord(0), spacing(0), base(0)), _align_to_grid(coord(1), spacing(1), base(1))); }
};
} // namespace Slic3r
#endif // slic3r_FillBase_hpp_

View file

@ -0,0 +1,64 @@
#include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp"
#include "../Surface.hpp"
#include "FillConcentric.hpp"
namespace Slic3r {
void FillConcentric::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out)
{
// no rotation is supported for this infill pattern
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()(0), distance);
this->spacing = unscale<double>(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
loops = union_pt_chained(loops, false);
// split paths using a nearest neighbor search
size_t iPathFirst = polylines_out.size();
Point last_pos(0, 0);
for (const Polygon &loop : loops) {
polylines_out.push_back(loop.split_at_index(last_pos.nearest_point_index(loop)));
last_pos = polylines_out.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 = iPathFirst;
for (size_t i = iPathFirst; i < polylines_out.size(); ++ i) {
polylines_out[i].clip_end(this->loop_clipping);
if (polylines_out[i].is_valid()) {
if (j < i)
polylines_out[j] = std::move(polylines_out[i]);
++ j;
}
}
if (j < polylines_out.size())
polylines_out.erase(polylines_out.begin() + j, polylines_out.end());
//TODO: return ExtrusionLoop objects to get better chained paths,
// otherwise the outermost loop starts at the closest point to (0, 0).
// We want the loops to be split inside the G-code generator to get optimum path planning.
}
} // namespace Slic3r

View file

@ -0,0 +1,27 @@
#ifndef slic3r_FillConcentric_hpp_
#define slic3r_FillConcentric_hpp_
#include "FillBase.hpp"
namespace Slic3r {
class FillConcentric : public Fill
{
public:
virtual ~FillConcentric() {}
protected:
virtual Fill* clone() const { return new FillConcentric(*this); };
virtual void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
virtual bool no_sort() const { return true; }
};
} // namespace Slic3r
#endif // slic3r_FillConcentric_hpp_

View file

@ -0,0 +1,196 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../Surface.hpp"
#include <cmath>
#include <algorithm>
#include <iostream>
#include "FillGyroid.hpp"
namespace Slic3r {
static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
{
if (vertical) {
double phase_offset = (z_cos < 0 ? M_PI : 0) + M_PI;
double a = sin(x + phase_offset);
double b = - z_cos;
double res = z_sin * cos(x + phase_offset + (flip ? M_PI : 0.));
double r = sqrt(sqr(a) + sqr(b));
return asin(a/r) + asin(res/r) + M_PI;
}
else {
double phase_offset = z_sin < 0 ? M_PI : 0.;
double a = cos(x + phase_offset);
double b = - z_sin;
double res = z_cos * sin(x + phase_offset + (flip ? 0 : M_PI));
double r = sqrt(sqr(a) + sqr(b));
return (asin(a/r) + asin(res/r) + 0.5 * M_PI);
}
}
static inline Polyline make_wave(
const std::vector<Vec2d>& one_period, double width, double height, double offset, double scaleFactor,
double z_cos, double z_sin, bool vertical)
{
std::vector<Vec2d> points = one_period;
double period = points.back()(0);
points.pop_back();
int n = points.size();
do {
points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1)));
} while (points.back()(0) < width);
points.back()(0) = width;
// and construct the final polyline to return:
Polyline polyline;
for (auto& point : points) {
point(1) += offset;
point(1) = clamp(0., height, double(point(1)));
if (vertical)
std::swap(point(0), point(1));
polyline.points.emplace_back((point * scaleFactor).cast<coord_t>());
}
return polyline;
}
static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip)
{
std::vector<Vec2d> points;
double dx = M_PI_4; // very coarse spacing to begin with
double limit = std::min(2*M_PI, width);
for (double x = 0.; x < limit + EPSILON; x += dx) { // so the last point is there too
x = std::min(x, limit);
points.emplace_back(Vec2d(x,f(x, z_sin,z_cos, vertical, flip)));
}
// now we will check all internal points and in case some are too far from the line connecting its neighbours,
// we will add one more point on each side:
const double tolerance = .1;
for (unsigned int i=1;i<points.size()-1;++i) {
auto& lp = points[i-1]; // left point
auto& tp = points[i]; // this point
Vec2d lrv = tp - lp;
auto& rp = points[i+1]; // right point
// calculate distance of the point to the line:
double dist_mm = unscale<double>(scaleFactor) * std::abs(cross2(rp, lp) - cross2(rp - lp, tp)) / lrv.norm();
if (dist_mm > tolerance) { // if the difference from straight line is more than this
double x = 0.5f * (points[i-1](0) + points[i](0));
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
x = 0.5f * (points[i+1](0) + points[i](0));
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
// we added the points to the end, but need them all in order
std::sort(points.begin(), points.end(), [](const Vec2d &lhs, const Vec2d &rhs){ return lhs < rhs; });
// decrement i so we also check the first newly added point
--i;
}
}
return points;
}
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
{
const double scaleFactor = scale_(line_spacing) / density_adjusted;
//scale factor for 5% : 8 712 388
// 1z = 10^-6 mm ?
const double z = gridZ / scaleFactor;
const double z_sin = sin(z);
const double z_cos = cos(z);
bool vertical = (std::abs(z_sin) <= std::abs(z_cos));
double lower_bound = 0.;
double upper_bound = height;
bool flip = true;
if (vertical) {
flip = false;
lower_bound = -M_PI;
upper_bound = width - M_PI_2;
std::swap(width,height);
}
std::vector<Vec2d> one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time
Polylines result;
for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates odd polylines
result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical));
flip = !flip; // even polylines are a bit shifted
one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // updates the one period sample
for (double y0 = lower_bound + M_PI; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates even polylines
result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical));
return result;
}
void FillGyroid::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out)
{
// no rotation is supported for this infill pattern (yet)
BoundingBox bb = expolygon.contour.bounding_box();
// Density adjusted to have a good %of weight.
double density_adjusted = std::max(0., params.density * 2.);
// Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
// align bounding box to a multiple of our grid module
bb.merge(_align_to_grid(bb.min, Point(2.*M_PI*distance, 2.*M_PI*distance)));
// generate pattern
Polylines polylines = make_gyroid_waves(
scale_(this->z),
density_adjusted,
this->spacing,
ceil(bb.size()(0) / distance) + 1.,
ceil(bb.size()(1) / distance) + 1.);
// move pattern in place
for (Polyline &polyline : polylines)
polyline.translate(bb.min(0), bb.min(1));
// clip pattern to boundaries
polylines = intersection_pl(polylines, (Polygons)expolygon);
// connect lines
if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
ExPolygon expolygon_off;
{
ExPolygons expolygons_off = offset_ex(expolygon, (float)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(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true;
for (Polyline &polyline : chained) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;
const Point &first_point = 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
// TODO: avoid crossing current infill path
if ((last_point - first_point).cast<double>().norm() <= 5 * distance &&
expolygon_off.contains(Line(last_point, first_point))) {
// Append the polyline.
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue;
}
}
// The lines cannot be connected.
polylines_out.emplace_back(std::move(polyline));
first = false;
}
}
}
} // namespace Slic3r

View file

@ -0,0 +1,30 @@
#ifndef slic3r_FillGyroid_hpp_
#define slic3r_FillGyroid_hpp_
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class FillGyroid : public Fill
{
public:
FillGyroid() {}
virtual Fill* clone() const { return new FillGyroid(*this); }
// require bridge flow since most of this pattern hangs in air
virtual bool use_bridge_flow() const { return true; }
protected:
virtual void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
};
} // namespace Slic3r
#endif // slic3r_FillGyroid_hpp_

View file

@ -0,0 +1,125 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../Surface.hpp"
#include "FillHoneycomb.hpp"
namespace Slic3r {
void FillHoneycomb::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out)
{
// 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()) {
it_m = this->cache.insert(it_m, std::pair<CacheID, CacheData>(cache_id, CacheData()));
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 = expolygon.contour.bounding_box();
{
// rotate bounding box according to infill direction
Polygon bb_polygon = bounding_box.polygon();
bb_polygon.rotate(direction.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
// The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough.
bounding_box.merge(_align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height)));
}
coord_t x = bounding_box.min(0);
while (x <= bounding_box.max(0)) {
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(1); y <= bounding_box.max(1); 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(-direction.first, m.hex_center);
polygons.push_back(p);
}
}
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)expolygon, polygons);
for (Polygons::iterator it = polygons_trimmed.begin(); it != polygons_trimmed.end(); ++ it)
polylines_out.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 paths;
{
Polylines p;
for (Polygon &poly : polygons)
p.emplace_back(poly.points);
paths = intersection_pl(p, to_polygons(expolygon));
}
// connect paths
if (! paths.empty()) { // prevent calling leftmost_point() on empty collections
Polylines chained = PolylineCollection::chained_path_from(
std::move(paths),
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 = (it_path->first_point() - paths.back().last_point()).cast<double>().norm();
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
paths = intersection_pl(paths, to_polygons(offset_ex(expolygon, SCALED_EPSILON)));
// Move the polylines to the output, avoid a deep copy.
size_t j = polylines_out.size();
polylines_out.resize(j + paths.size(), Polyline());
for (size_t i = 0; i < paths.size(); ++ i)
std::swap(polylines_out[j ++], paths[i]);
}
}
} // namespace Slic3r

View file

@ -0,0 +1,57 @@
#ifndef slic3r_FillHoneycomb_hpp_
#define slic3r_FillHoneycomb_hpp_
#include <map>
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class FillHoneycomb : public Fill
{
public:
virtual ~FillHoneycomb() {}
protected:
virtual Fill* clone() const { return new FillHoneycomb(*this); };
virtual void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
// 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 float(M_PI/3.) * (idx % 3); }
};
} // namespace Slic3r
#endif // slic3r_FillHoneycomb_hpp_

View file

@ -0,0 +1,203 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../Surface.hpp"
#include "FillPlanePath.hpp"
namespace Slic3r {
void FillPlanePath::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out)
{
expolygon.rotate(- direction.first);
coord_t distance_between_lines = 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() ?
bounding_box.center() :
bounding_box.min;
expolygon.translate(-shift(0), -shift(1));
bounding_box.translate(-shift(0), -shift(1));
Pointfs pts = _generate(
coord_t(ceil(coordf_t(bounding_box.min(0)) / distance_between_lines)),
coord_t(ceil(coordf_t(bounding_box.min(1)) / distance_between_lines)),
coord_t(ceil(coordf_t(bounding_box.max(0)) / distance_between_lines)),
coord_t(ceil(coordf_t(bounding_box.max(1)) / 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)(0) * distance_between_lines + 0.5)),
coord_t(floor((*it)(1) * distance_between_lines + 0.5))));
// intersection(polylines_src, offset((Polygons)expolygon, scale_(0.02)), &polylines);
polylines = intersection_pl(polylines, to_polygons(expolygon));
/*
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(0), shift(1));
it->rotate(direction.first);
}
}
// Move the polylines to the output, avoid a deep copy.
size_t j = polylines_out.size();
polylines_out.resize(j + polylines.size(), Polyline());
for (size_t i = 0; i < polylines.size(); ++ i)
std::swap(polylines_out[j ++], polylines[i]);
}
// 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(Vec2d(0, 0));
out.push_back(Vec2d(1, 0));
while (r < rmax) {
theta += 1. / r;
r = a + b * theta;
out.push_back(Vec2d(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(Vec2d(p(0) + min_x, p(1) + 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(Vec2d(0, 0));
while (r < rmax) {
r += r_inc;
coordf_t rx = r / sqrt(2.);
coordf_t r2 = r + rx;
out.push_back(Vec2d( r, 0.));
out.push_back(Vec2d( r2, rx));
out.push_back(Vec2d( rx, rx));
out.push_back(Vec2d( rx, r2));
out.push_back(Vec2d(0., r));
out.push_back(Vec2d(-rx, r2));
out.push_back(Vec2d(-rx, rx));
out.push_back(Vec2d(-r2, rx));
out.push_back(Vec2d(-r, 0.));
out.push_back(Vec2d(-r2, -rx));
out.push_back(Vec2d(-rx, -rx));
out.push_back(Vec2d(-rx, -r2));
out.push_back(Vec2d(0., -r));
out.push_back(Vec2d( rx, -r2));
out.push_back(Vec2d( rx, -rx));
out.push_back(Vec2d( r2+r_inc, -rx));
}
return out;
}
} // namespace Slic3r

View file

@ -0,0 +1,69 @@
#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 Fill
{
public:
virtual ~FillPlanePath() {}
protected:
virtual void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
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 Fill* clone() const { return new FillArchimedeanChords(*this); };
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 Fill* clone() const { return new FillHilbertCurve(*this); };
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 Fill* clone() const { return new FillOctagramSpiral(*this); };
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,130 @@
#include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp"
#include "../PolylineCollection.hpp"
#include "../Surface.hpp"
#include "FillRectilinear.hpp"
namespace Slic3r {
void FillRectilinear::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out)
{
// rotate polygons so that we can work with vertical lines here
expolygon.rotate(- direction.first);
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()(0), this->_line_spacing);
this->spacing = unscale<double>(this->_line_spacing);
} else {
// extend bounding box so that our pattern will be aligned with other layers
// Transform the reference point to the rotated coordinate system.
bounding_box.merge(_align_to_grid(
bounding_box.min,
Point(this->_line_spacing, this->_line_spacing),
direction.second.rotated(- direction.first)));
}
// generate the basic pattern
coord_t x_max = bounding_box.max(0) + SCALED_EPSILON;
Lines lines;
for (coord_t x = bounding_box.min(0); x <= x_max; x += this->_line_spacing)
lines.push_back(this->_line(lines.size(), x, bounding_box.min(1), bounding_box.max(1)));
if (this->_horizontal_lines()) {
coord_t y_max = bounding_box.max(1) + SCALED_EPSILON;
for (coord_t y = bounding_box.min(1); y <= y_max; y += this->_line_spacing)
lines.push_back(Line(Point(bounding_box.min(0), y), Point(bounding_box.max(0), 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_pl(polylines_src, offset(to_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;
// How much to extend an infill path from expolygon outside?
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;
}
size_t n_polylines_out_old = polylines_out.size();
// 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(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true;
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;
const Point &first_point = it_polyline->points.front();
const Point &last_point = pts_end.back();
// Distance in X, Y.
const Vector distance = last_point - first_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(0)), std::abs(distance(1))) &&
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.
polylines_out.emplace_back(std::move(*it_polyline));
first = false;
}
}
// paths must be rotated back
for (Polylines::iterator it = polylines_out.begin() + n_polylines_out_old; it != polylines_out.end(); ++ it) {
// No need to translate, the absolute position is irrelevant.
// it->translate(- direction.second(0), - direction.second(1));
it->rotate(direction.first);
}
}
} // namespace Slic3r

View file

@ -0,0 +1,79 @@
#ifndef slic3r_FillRectilinear_hpp_
#define slic3r_FillRectilinear_hpp_
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class Surface;
class FillRectilinear : public Fill
{
public:
virtual Fill* clone() const { return new FillRectilinear(*this); };
virtual ~FillRectilinear() {}
protected:
virtual void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon &expolygon,
Polylines &polylines_out);
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.
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_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,74 @@
#ifndef slic3r_FillRectilinear2_hpp_
#define slic3r_FillRectilinear2_hpp_
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class Surface;
class FillRectilinear2 : public Fill
{
public:
virtual Fill* clone() const { return new FillRectilinear2(*this); };
virtual ~FillRectilinear2() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
bool fill_surface_by_lines(const Surface *surface, const FillParams &params, float angleBase, float pattern_shift, Polylines &polylines_out);
};
class FillGrid2 : public FillRectilinear2
{
public:
virtual Fill* clone() const { return new FillGrid2(*this); };
virtual ~FillGrid2() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t idx) const { return 0.f; }
};
class FillTriangles : public FillRectilinear2
{
public:
virtual Fill* clone() const { return new FillTriangles(*this); };
virtual ~FillTriangles() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t idx) const { return 0.f; }
};
class FillStars : public FillRectilinear2
{
public:
virtual Fill* clone() const { return new FillStars(*this); };
virtual ~FillStars() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t idx) const { return 0.f; }
};
class FillCubic : public FillRectilinear2
{
public:
virtual Fill* clone() const { return new FillCubic(*this); };
virtual ~FillCubic() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t idx) const { return 0.f; }
};
}; // namespace Slic3r
#endif // slic3r_FillRectilinear2_hpp_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,83 @@
#ifndef slic3r_FillRectilinear3_hpp_
#define slic3r_FillRectilinear3_hpp_
#include "../libslic3r.h"
#include "FillBase.hpp"
namespace Slic3r {
class Surface;
class FillRectilinear3 : public Fill
{
public:
virtual Fill* clone() const { return new FillRectilinear3(*this); };
virtual ~FillRectilinear3() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
struct FillDirParams
{
FillDirParams(coordf_t spacing, double angle, coordf_t pattern_shift = 0.f) :
spacing(spacing), angle(angle), pattern_shift(pattern_shift) {}
coordf_t spacing;
double angle;
coordf_t pattern_shift;
};
protected:
bool fill_surface_by_lines(const Surface *surface, const FillParams &params, std::vector<FillDirParams> &fill_dir_params, Polylines &polylines_out);
};
class FillGrid3 : public FillRectilinear3
{
public:
virtual Fill* clone() const { return new FillGrid3(*this); };
virtual ~FillGrid3() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
};
class FillTriangles3 : public FillRectilinear3
{
public:
virtual Fill* clone() const { return new FillTriangles3(*this); };
virtual ~FillTriangles3() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
};
class FillStars3 : public FillRectilinear3
{
public:
virtual Fill* clone() const { return new FillStars3(*this); };
virtual ~FillStars3() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
};
class FillCubic3 : public FillRectilinear3
{
public:
virtual Fill* clone() const { return new FillCubic3(*this); };
virtual ~FillCubic3() {}
virtual Polylines fill_surface(const Surface *surface, const FillParams &params);
protected:
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
virtual float _layer_angle(size_t /* idx */) const { return 0.f; }
};
}; // namespace Slic3r
#endif // slic3r_FillRectilinear3_hpp_

148
src/libslic3r/Flow.cpp Normal file
View file

@ -0,0 +1,148 @@
#include "Flow.hpp"
#include "Print.hpp"
#include <cmath>
#include <assert.h>
namespace Slic3r {
// This static method returns a sane extrusion width default.
static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, float height)
{
switch (role) {
case frSupportMaterial:
case frSupportMaterialInterface:
case frTopSolidInfill:
return nozzle_diameter;
default:
case frExternalPerimeter:
case frPerimeter:
case frSolidInfill:
case frInfill:
return 1.125f * nozzle_diameter;
}
}
// This constructor builds a Flow object from an extrusion width config setting
// and other context properties.
Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio)
{
// we need layer height unless it's a bridge
if (height <= 0 && bridge_flow_ratio == 0)
throw std::invalid_argument("Invalid flow height supplied to new_from_config_width()");
float w;
if (bridge_flow_ratio > 0) {
// If bridge flow was requested, calculate the bridge width.
height = w = (bridge_flow_ratio == 1.) ?
// optimization to avoid sqrt()
nozzle_diameter :
sqrt(bridge_flow_ratio) * nozzle_diameter;
} else if (! width.percent && width.value == 0.) {
// If user left option to 0, calculate a sane default width.
w = auto_extrusion_width(role, nozzle_diameter, height);
} else {
// If user set a manual value, use it.
w = float(width.get_abs_value(height));
}
return Flow(w, height, nozzle_diameter, bridge_flow_ratio > 0);
}
// This constructor builds a Flow object from a given centerline spacing.
Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge)
{
// we need layer height unless it's a bridge
if (height <= 0 && !bridge)
throw std::invalid_argument("Invalid flow height supplied to new_from_spacing()");
// Calculate width from spacing.
// For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions.
// For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads.
float width = float(bridge ?
(spacing - BRIDGE_EXTRA_SPACING) :
#ifdef HAS_PERIMETER_LINE_OVERLAP
(spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI));
#else
(spacing + height * (1. - 0.25 * PI)));
#endif
return Flow(width, bridge ? width : height, nozzle_diameter, bridge);
}
// This method returns the centerline spacing between two adjacent extrusions
// having the same extrusion width (and other properties).
float Flow::spacing() const
{
#ifdef HAS_PERIMETER_LINE_OVERLAP
if (this->bridge)
return this->width + BRIDGE_EXTRA_SPACING;
// rectangle with semicircles at the ends
float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI);
return this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing);
#else
return float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI)));
#endif
}
// This method returns the centerline spacing between an extrusion using this
// flow and another one using another flow.
// this->spacing(other) shall return the same value as other.spacing(*this)
float Flow::spacing(const Flow &other) const
{
assert(this->height == other.height);
assert(this->bridge == other.bridge);
return float(this->bridge ?
0.5 * this->width + 0.5 * other.width + BRIDGE_EXTRA_SPACING :
0.5 * this->spacing() + 0.5 * other.spacing());
}
// This method returns extrusion volume per head move unit.
double Flow::mm3_per_mm() const
{
double res = this->bridge ?
// Area of a circle with dmr of this->width.
(this->width * this->width) * 0.25 * PI :
// Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
this->height * (this->width - this->height * (1. - 0.25 * PI));
assert(res > 0.);
return res;
}
Flow support_material_flow(const PrintObject *object, float layer_height)
{
return Flow::new_from_config_width(
frSupportMaterial,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width,
// if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value),
// bridge_flow_ratio
0.f);
}
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
{
const auto &width = (object->print()->config().first_layer_extrusion_width.value > 0) ? object->print()->config().first_layer_extrusion_width : object->config().support_material_extrusion_width;
return Flow::new_from_config_width(
frSupportMaterial,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(width.value > 0) ? width : object->config().extrusion_width,
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)),
// bridge_flow_ratio
0.f);
}
Flow support_material_interface_flow(const PrintObject *object, float layer_height)
{
return Flow::new_from_config_width(
frSupportMaterialInterface,
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(object->config().support_material_extrusion_width > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width,
// if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_interface_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value),
// bridge_flow_ratio
0.f);
}
}

67
src/libslic3r/Flow.hpp Normal file
View file

@ -0,0 +1,67 @@
#ifndef slic3r_Flow_hpp_
#define slic3r_Flow_hpp_
#include "libslic3r.h"
#include "Config.hpp"
#include "ExtrusionEntity.hpp"
namespace Slic3r {
class PrintObject;
// Extra spacing of bridge threads, in mm.
#define BRIDGE_EXTRA_SPACING 0.05
// Overlap factor of perimeter lines. Currently no overlap.
#ifdef HAS_PERIMETER_LINE_OVERLAP
#define PERIMETER_LINE_OVERLAP_FACTOR 1.0
#endif
enum FlowRole {
frExternalPerimeter,
frPerimeter,
frInfill,
frSolidInfill,
frTopSolidInfill,
frSupportMaterial,
frSupportMaterialInterface,
};
class Flow
{
public:
// Non bridging flow: Maximum width of an extrusion with semicircles at the ends.
// Bridging flow: Bridge thread diameter.
float width;
// Non bridging flow: Layer height.
// Bridging flow: Bridge thread diameter = layer height.
float height;
// Nozzle diameter.
float nozzle_diameter;
// Is it a bridge?
bool bridge;
Flow(float _w, float _h, float _nd, bool _bridge = false) :
width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {};
float spacing() const;
float spacing(const Flow &other) const;
double mm3_per_mm() const;
coord_t scaled_width() const { return coord_t(scale_(this->width)); };
coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); };
coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); };
static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio);
// Create a flow from the spacing of extrusion lines.
// This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale
// to fit a region with integer number of lines.
static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
};
extern Flow support_material_flow(const PrintObject *object, float layer_height = 0.f);
extern Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height = 0.f);
extern Flow support_material_interface_flow(const PrintObject *object, float layer_height = 0.f);
}
#endif

2022
src/libslic3r/Format/3mf.cpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
#ifndef slic3r_Format_3mf_hpp_
#define slic3r_Format_3mf_hpp_
namespace Slic3r {
class Model;
class Print;
class PresetBundle;
// Load the content of a 3mf file into the given model and preset bundle.
extern bool load_3mf(const char* path, PresetBundle* bundle, Model* model);
// Save the given model and the config data contained in the given Print into a 3mf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
extern bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config);
}; // namespace Slic3r
#endif /* slic3r_Format_3mf_hpp_ */

View file

@ -0,0 +1,901 @@
#include <string.h>
#include <map>
#include <string>
#include <expat/expat.h>
#include <boost/nowide/cstdio.hpp>
#include "../libslic3r.h"
#include "../Model.hpp"
#include "../GCode.hpp"
#include "../Utils.hpp"
#include "../slic3r/GUI/PresetBundle.hpp"
#include "AMF.hpp"
#include <boost/filesystem/operations.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/nowide/fstream.hpp>
#include <miniz/miniz_zip.h>
#if 0
// Enable debugging and assert in this file.
#define DEBUG
#define _DEBUG
#undef NDEBUG
#endif
#include <assert.h>
// VERSION NUMBERS
// 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them.
// 1 : Introduction of amf versioning. No other change in data saved into amf files.
#if ENABLE_MODELINSTANCE_3D_OFFSET
// 2 : Added z component of offset.
const unsigned int VERSION_AMF = 2;
#else
const unsigned int VERSION_AMF = 1;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version";
const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config";
namespace Slic3r
{
struct AMFParserContext
{
AMFParserContext(XML_Parser parser, const std::string& archive_filename, PresetBundle* preset_bundle, Model *model) :
m_version(0),
m_parser(parser),
m_model(*model),
m_object(nullptr),
m_volume(nullptr),
m_material(nullptr),
m_instance(nullptr),
m_preset_bundle(preset_bundle),
m_archive_filename(archive_filename)
{
m_path.reserve(12);
}
void stop()
{
XML_StopParser(m_parser, 0);
}
void startElement(const char *name, const char **atts);
void endElement(const char *name);
void endDocument();
void characters(const XML_Char *s, int len);
static void XMLCALL startElement(void *userData, const char *name, const char **atts)
{
AMFParserContext *ctx = (AMFParserContext*)userData;
ctx->startElement(name, atts);
}
static void XMLCALL endElement(void *userData, const char *name)
{
AMFParserContext *ctx = (AMFParserContext*)userData;
ctx->endElement(name);
}
/* s is not 0 terminated. */
static void XMLCALL characters(void *userData, const XML_Char *s, int len)
{
AMFParserContext *ctx = (AMFParserContext*)userData;
ctx->characters(s, len);
}
static const char* get_attribute(const char **atts, const char *id) {
if (atts == nullptr)
return nullptr;
while (*atts != nullptr) {
if (strcmp(*(atts ++), id) == 0)
return *atts;
++ atts;
}
return nullptr;
}
enum AMFNodeType {
NODE_TYPE_INVALID = 0,
NODE_TYPE_UNKNOWN,
NODE_TYPE_AMF, // amf
// amf/metadata
NODE_TYPE_MATERIAL, // amf/material
// amf/material/metadata
NODE_TYPE_OBJECT, // amf/object
// amf/object/metadata
NODE_TYPE_MESH, // amf/object/mesh
NODE_TYPE_VERTICES, // amf/object/mesh/vertices
NODE_TYPE_VERTEX, // amf/object/mesh/vertices/vertex
NODE_TYPE_COORDINATES, // amf/object/mesh/vertices/vertex/coordinates
NODE_TYPE_COORDINATE_X, // amf/object/mesh/vertices/vertex/coordinates/x
NODE_TYPE_COORDINATE_Y, // amf/object/mesh/vertices/vertex/coordinates/y
NODE_TYPE_COORDINATE_Z, // amf/object/mesh/vertices/vertex/coordinates/z
NODE_TYPE_VOLUME, // amf/object/mesh/volume
// amf/object/mesh/volume/metadata
NODE_TYPE_TRIANGLE, // amf/object/mesh/volume/triangle
NODE_TYPE_VERTEX1, // amf/object/mesh/volume/triangle/v1
NODE_TYPE_VERTEX2, // amf/object/mesh/volume/triangle/v2
NODE_TYPE_VERTEX3, // amf/object/mesh/volume/triangle/v3
NODE_TYPE_CONSTELLATION, // amf/constellation
NODE_TYPE_INSTANCE, // amf/constellation/instance
NODE_TYPE_DELTAX, // amf/constellation/instance/deltax
NODE_TYPE_DELTAY, // amf/constellation/instance/deltay
#if ENABLE_MODELINSTANCE_3D_OFFSET
NODE_TYPE_DELTAZ, // amf/constellation/instance/deltaz
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
NODE_TYPE_RZ, // amf/constellation/instance/rz
NODE_TYPE_SCALE, // amf/constellation/instance/scale
NODE_TYPE_METADATA, // anywhere under amf/*/metadata
};
struct Instance {
#if ENABLE_MODELINSTANCE_3D_OFFSET
Instance() : deltax_set(false), deltay_set(false), deltaz_set(false), rz_set(false), scale_set(false) {}
#else
Instance() : deltax_set(false), deltay_set(false), rz_set(false), scale_set(false) {}
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
// Shift in the X axis.
float deltax;
bool deltax_set;
// Shift in the Y axis.
float deltay;
bool deltay_set;
#if ENABLE_MODELINSTANCE_3D_OFFSET
// Shift in the Z axis.
float deltaz;
bool deltaz_set;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
// Rotation around the Z axis.
float rz;
bool rz_set;
// Scaling factor
float scale;
bool scale_set;
};
struct Object {
Object() : idx(-1) {}
int idx;
std::vector<Instance> instances;
};
// Version of the amf file
unsigned int m_version;
// Current Expat XML parser instance.
XML_Parser m_parser;
// Model to receive objects extracted from an AMF file.
Model &m_model;
// Current parsing path in the XML file.
std::vector<AMFNodeType> m_path;
// Current object allocated for an amf/object XML subtree.
ModelObject *m_object;
// Map from obect name to object idx & instances.
std::map<std::string, Object> m_object_instances_map;
// Vertices parsed for the current m_object.
std::vector<float> m_object_vertices;
// Current volume allocated for an amf/object/mesh/volume subtree.
ModelVolume *m_volume;
// Faces collected for the current m_volume.
std::vector<int> m_volume_facets;
// Current material allocated for an amf/metadata subtree.
ModelMaterial *m_material;
// Current instance allocated for an amf/constellation/instance subtree.
Instance *m_instance;
// Generic string buffer for vertices, face indices, metadata etc.
std::string m_value[3];
// Pointer to preset bundle to update if config data are stored inside the amf file
PresetBundle* m_preset_bundle;
// Fullpath name of the amf file
std::string m_archive_filename;
private:
AMFParserContext& operator=(AMFParserContext&);
};
void AMFParserContext::startElement(const char *name, const char **atts)
{
AMFNodeType node_type_new = NODE_TYPE_UNKNOWN;
switch (m_path.size()) {
case 0:
// An AMF file must start with an <amf> tag.
node_type_new = NODE_TYPE_AMF;
if (strcmp(name, "amf") != 0)
this->stop();
break;
case 1:
if (strcmp(name, "metadata") == 0) {
const char *type = get_attribute(atts, "type");
if (type != nullptr) {
m_value[0] = type;
node_type_new = NODE_TYPE_METADATA;
}
} else if (strcmp(name, "material") == 0) {
const char *material_id = get_attribute(atts, "id");
m_material = m_model.add_material((material_id == nullptr) ? "_" : material_id);
node_type_new = NODE_TYPE_MATERIAL;
} else if (strcmp(name, "object") == 0) {
const char *object_id = get_attribute(atts, "id");
if (object_id == nullptr)
this->stop();
else {
assert(m_object_vertices.empty());
m_object = m_model.add_object();
m_object_instances_map[object_id].idx = int(m_model.objects.size())-1;
node_type_new = NODE_TYPE_OBJECT;
}
} else if (strcmp(name, "constellation") == 0) {
node_type_new = NODE_TYPE_CONSTELLATION;
}
break;
case 2:
if (strcmp(name, "metadata") == 0) {
if (m_path[1] == NODE_TYPE_MATERIAL || m_path[1] == NODE_TYPE_OBJECT) {
m_value[0] = get_attribute(atts, "type");
node_type_new = NODE_TYPE_METADATA;
}
} else if (strcmp(name, "mesh") == 0) {
if (m_path[1] == NODE_TYPE_OBJECT)
node_type_new = NODE_TYPE_MESH;
} else if (strcmp(name, "instance") == 0) {
if (m_path[1] == NODE_TYPE_CONSTELLATION) {
const char *object_id = get_attribute(atts, "objectid");
if (object_id == nullptr)
this->stop();
else {
m_object_instances_map[object_id].instances.push_back(AMFParserContext::Instance());
m_instance = &m_object_instances_map[object_id].instances.back();
node_type_new = NODE_TYPE_INSTANCE;
}
}
else
this->stop();
}
break;
case 3:
if (m_path[2] == NODE_TYPE_MESH) {
assert(m_object);
if (strcmp(name, "vertices") == 0)
node_type_new = NODE_TYPE_VERTICES;
else if (strcmp(name, "volume") == 0) {
assert(! m_volume);
m_volume = m_object->add_volume(TriangleMesh());
node_type_new = NODE_TYPE_VOLUME;
}
} else if (m_path[2] == NODE_TYPE_INSTANCE) {
assert(m_instance);
if (strcmp(name, "deltax") == 0)
node_type_new = NODE_TYPE_DELTAX;
else if (strcmp(name, "deltay") == 0)
node_type_new = NODE_TYPE_DELTAY;
#if ENABLE_MODELINSTANCE_3D_OFFSET
else if (strcmp(name, "deltaz") == 0)
node_type_new = NODE_TYPE_DELTAZ;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
else if (strcmp(name, "rz") == 0)
node_type_new = NODE_TYPE_RZ;
else if (strcmp(name, "scale") == 0)
node_type_new = NODE_TYPE_SCALE;
}
break;
case 4:
if (m_path[3] == NODE_TYPE_VERTICES) {
if (strcmp(name, "vertex") == 0)
node_type_new = NODE_TYPE_VERTEX;
} else if (m_path[3] == NODE_TYPE_VOLUME) {
if (strcmp(name, "metadata") == 0) {
const char *type = get_attribute(atts, "type");
if (type == nullptr)
this->stop();
else {
m_value[0] = type;
node_type_new = NODE_TYPE_METADATA;
}
} else if (strcmp(name, "triangle") == 0)
node_type_new = NODE_TYPE_TRIANGLE;
}
break;
case 5:
if (strcmp(name, "coordinates") == 0) {
if (m_path[4] == NODE_TYPE_VERTEX) {
node_type_new = NODE_TYPE_COORDINATES;
} else
this->stop();
} else if (name[0] == 'v' && name[1] >= '1' && name[1] <= '3' && name[2] == 0) {
if (m_path[4] == NODE_TYPE_TRIANGLE) {
node_type_new = AMFNodeType(NODE_TYPE_VERTEX1 + name[1] - '1');
} else
this->stop();
}
break;
case 6:
if ((name[0] == 'x' || name[0] == 'y' || name[0] == 'z') && name[1] == 0) {
if (m_path[5] == NODE_TYPE_COORDINATES)
node_type_new = AMFNodeType(NODE_TYPE_COORDINATE_X + name[0] - 'x');
else
this->stop();
}
break;
default:
break;
}
m_path.push_back(node_type_new);
}
void AMFParserContext::characters(const XML_Char *s, int len)
{
if (m_path.back() == NODE_TYPE_METADATA) {
m_value[1].append(s, len);
}
else
{
switch (m_path.size()) {
case 4:
#if ENABLE_MODELINSTANCE_3D_OFFSET
if (m_path.back() == NODE_TYPE_DELTAX ||
m_path.back() == NODE_TYPE_DELTAY ||
m_path.back() == NODE_TYPE_DELTAZ ||
m_path.back() == NODE_TYPE_RZ ||
m_path.back() == NODE_TYPE_SCALE)
#else
if (m_path.back() == NODE_TYPE_DELTAX || m_path.back() == NODE_TYPE_DELTAY || m_path.back() == NODE_TYPE_RZ || m_path.back() == NODE_TYPE_SCALE)
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
m_value[0].append(s, len);
break;
case 6:
switch (m_path.back()) {
case NODE_TYPE_VERTEX1: m_value[0].append(s, len); break;
case NODE_TYPE_VERTEX2: m_value[1].append(s, len); break;
case NODE_TYPE_VERTEX3: m_value[2].append(s, len); break;
default: break;
}
case 7:
switch (m_path.back()) {
case NODE_TYPE_COORDINATE_X: m_value[0].append(s, len); break;
case NODE_TYPE_COORDINATE_Y: m_value[1].append(s, len); break;
case NODE_TYPE_COORDINATE_Z: m_value[2].append(s, len); break;
default: break;
}
default:
break;
}
}
}
void AMFParserContext::endElement(const char * /* name */)
{
switch (m_path.back()) {
// Constellation transformation:
case NODE_TYPE_DELTAX:
assert(m_instance);
m_instance->deltax = float(atof(m_value[0].c_str()));
m_instance->deltax_set = true;
m_value[0].clear();
break;
case NODE_TYPE_DELTAY:
assert(m_instance);
m_instance->deltay = float(atof(m_value[0].c_str()));
m_instance->deltay_set = true;
m_value[0].clear();
break;
#if ENABLE_MODELINSTANCE_3D_OFFSET
case NODE_TYPE_DELTAZ:
assert(m_instance);
m_instance->deltaz = float(atof(m_value[0].c_str()));
m_instance->deltaz_set = true;
m_value[0].clear();
break;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
case NODE_TYPE_RZ:
assert(m_instance);
m_instance->rz = float(atof(m_value[0].c_str()));
m_instance->rz_set = true;
m_value[0].clear();
break;
case NODE_TYPE_SCALE:
assert(m_instance);
m_instance->scale = float(atof(m_value[0].c_str()));
m_instance->scale_set = true;
m_value[0].clear();
break;
// Object vertices:
case NODE_TYPE_VERTEX:
assert(m_object);
// Parse the vertex data
m_object_vertices.emplace_back((float)atof(m_value[0].c_str()));
m_object_vertices.emplace_back((float)atof(m_value[1].c_str()));
m_object_vertices.emplace_back((float)atof(m_value[2].c_str()));
m_value[0].clear();
m_value[1].clear();
m_value[2].clear();
break;
// Faces of the current volume:
case NODE_TYPE_TRIANGLE:
assert(m_object && m_volume);
m_volume_facets.push_back(atoi(m_value[0].c_str()));
m_volume_facets.push_back(atoi(m_value[1].c_str()));
m_volume_facets.push_back(atoi(m_value[2].c_str()));
m_value[0].clear();
m_value[1].clear();
m_value[2].clear();
break;
// Closing the current volume. Create an STL from m_volume_facets pointing to m_object_vertices.
case NODE_TYPE_VOLUME:
{
assert(m_object && m_volume);
stl_file &stl = m_volume->mesh.stl;
stl.stats.type = inmemory;
stl.stats.number_of_facets = int(m_volume_facets.size() / 3);
stl.stats.original_num_facets = stl.stats.number_of_facets;
stl_allocate(&stl);
for (size_t i = 0; i < m_volume_facets.size();) {
stl_facet &facet = stl.facet_start[i/3];
for (unsigned int v = 0; v < 3; ++ v)
memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float));
}
stl_get_size(&stl);
m_volume->mesh.repair();
m_volume->calculate_convex_hull();
m_volume_facets.clear();
m_volume = nullptr;
break;
}
case NODE_TYPE_OBJECT:
assert(m_object);
m_object_vertices.clear();
m_object = nullptr;
break;
case NODE_TYPE_MATERIAL:
assert(m_material);
m_material = nullptr;
break;
case NODE_TYPE_INSTANCE:
assert(m_instance);
m_instance = nullptr;
break;
case NODE_TYPE_METADATA:
if ((m_preset_bundle != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) {
m_preset_bundle->load_config_string(m_value[1].c_str(), m_archive_filename.c_str());
}
else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
const char *opt_key = m_value[0].c_str() + 7;
if (print_config_def.options.find(opt_key) != print_config_def.options.end()) {
DynamicPrintConfig *config = nullptr;
if (m_path.size() == 3) {
if (m_path[1] == NODE_TYPE_MATERIAL && m_material)
config = &m_material->config;
else if (m_path[1] == NODE_TYPE_OBJECT && m_object)
config = &m_object->config;
} else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume)
config = &m_volume->config;
if (config)
config->set_deserialize(opt_key, m_value[1]);
} else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) {
// Parse object's layer height profile, a semicolon separated list of floats.
char *p = const_cast<char*>(m_value[1].c_str());
for (;;) {
char *end = strchr(p, ';');
if (end != nullptr)
*end = 0;
m_object->layer_height_profile.push_back(float(atof(p)));
if (end == nullptr)
break;
p = end + 1;
}
m_object->layer_height_profile_valid = true;
} else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) {
if (strcmp(opt_key, "modifier") == 0) {
// Is this volume a modifier volume?
// "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag.
m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART);
} else if (strcmp(opt_key, "volume_type") == 0) {
m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
}
}
} else if (m_path.size() == 3) {
if (m_path[1] == NODE_TYPE_MATERIAL) {
if (m_material)
m_material->attributes[m_value[0]] = m_value[1];
} else if (m_path[1] == NODE_TYPE_OBJECT) {
if (m_object && m_value[0] == "name")
m_object->name = std::move(m_value[1]);
}
} else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) {
if (m_volume && m_value[0] == "name")
m_volume->name = std::move(m_value[1]);
}
else if (strncmp(m_value[0].c_str(), SLIC3RPE_AMF_VERSION, strlen(SLIC3RPE_AMF_VERSION)) == 0) {
m_version = (unsigned int)atoi(m_value[1].c_str());
}
m_value[0].clear();
m_value[1].clear();
break;
default:
break;
}
m_path.pop_back();
}
void AMFParserContext::endDocument()
{
for (const auto &object : m_object_instances_map) {
if (object.second.idx == -1) {
printf("Undefined object %s referenced in constellation\n", object.first.c_str());
continue;
}
for (const Instance &instance : object.second.instances)
if (instance.deltax_set && instance.deltay_set) {
ModelInstance *mi = m_model.objects[object.second.idx]->add_instance();
#if ENABLE_MODELINSTANCE_3D_OFFSET
mi->set_offset(Vec3d((double)instance.deltax, (double)instance.deltay, (double)instance.deltaz));
#else
mi->offset(0) = instance.deltax;
mi->offset(1) = instance.deltay;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
mi->rotation = instance.rz_set ? instance.rz : 0.f;
mi->scaling_factor = instance.scale_set ? instance.scale : 1.f;
}
}
}
// Load an AMF file into a provided model.
bool load_amf_file(const char *path, PresetBundle* bundle, Model *model)
{
if ((path == nullptr) || (model == nullptr))
return false;
XML_Parser parser = XML_ParserCreate(nullptr); // encoding
if (!parser) {
printf("Couldn't allocate memory for parser\n");
return false;
}
FILE *pFile = boost::nowide::fopen(path, "rt");
if (pFile == nullptr) {
printf("Cannot open file %s\n", path);
return false;
}
AMFParserContext ctx(parser, path, bundle, model);
XML_SetUserData(parser, (void*)&ctx);
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
char buff[8192];
bool result = false;
for (;;) {
int len = (int)fread(buff, 1, 8192, pFile);
if (ferror(pFile)) {
printf("AMF parser: Read error\n");
break;
}
int done = feof(pFile);
if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR) {
printf("AMF parser: Parse error at line %ul:\n%s\n",
XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
break;
}
if (done) {
result = true;
break;
}
}
XML_ParserFree(parser);
::fclose(pFile);
if (result)
ctx.endDocument();
return result;
}
bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, const char* path, PresetBundle* bundle, Model* model, unsigned int& version)
{
if (stat.m_uncomp_size == 0)
{
printf("Found invalid size\n");
mz_zip_reader_end(&archive);
return false;
}
XML_Parser parser = XML_ParserCreate(nullptr); // encoding
if (!parser) {
printf("Couldn't allocate memory for parser\n");
mz_zip_reader_end(&archive);
return false;
}
AMFParserContext ctx(parser, path, bundle, model);
XML_SetUserData(parser, (void*)&ctx);
XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
void* parser_buffer = XML_GetBuffer(parser, (int)stat.m_uncomp_size);
if (parser_buffer == nullptr)
{
printf("Unable to create buffer\n");
mz_zip_reader_end(&archive);
return false;
}
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0);
if (res == 0)
{
printf("Error while reading model data to buffer\n");
mz_zip_reader_end(&archive);
return false;
}
if (!XML_ParseBuffer(parser, (int)stat.m_uncomp_size, 1))
{
printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser));
mz_zip_reader_end(&archive);
return false;
}
ctx.endDocument();
version = ctx.m_version;
return true;
}
// Load an AMF archive into a provided model.
bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model)
{
if ((path == nullptr) || (model == nullptr))
return false;
unsigned int version = 0;
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
mz_bool res = mz_zip_reader_init_file(&archive, path, 0);
if (res == 0)
{
printf("Unable to init zip reader\n");
return false;
}
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
// we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
for (mz_uint i = 0; i < num_entries; ++i)
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
{
if (boost::iends_with(stat.m_filename, ".amf"))
{
if (!extract_model_from_archive(archive, stat, path, bundle, model, version))
{
mz_zip_reader_end(&archive);
printf("Archive does not contain a valid model");
return false;
}
break;
}
}
}
#if 0 // forward compatibility
// we then loop again the entries to read other files stored in the archive
for (mz_uint i = 0; i < num_entries; ++i)
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
{
// add code to extract the file
}
}
#endif // forward compatibility
mz_zip_reader_end(&archive);
return true;
}
// Load an AMF file into a provided model.
// If bundle is not a null pointer, updates it if the amf file/archive contains config data
bool load_amf(const char *path, PresetBundle* bundle, Model *model)
{
if (boost::iends_with(path, ".amf.xml"))
// backward compatibility with older slic3r output
return load_amf_file(path, bundle, model);
else if (boost::iends_with(path, ".amf"))
{
boost::nowide::ifstream file(path, boost::nowide::ifstream::binary);
if (!file.good())
return false;
std::string zip_mask(2, '\0');
file.read(const_cast<char*>(zip_mask.data()), 2);
file.close();
return (zip_mask == "PK") ? load_amf_archive(path, bundle, model) : load_amf_file(path, bundle, model);
}
else
return false;
}
bool store_amf(const char *path, Model *model, Print* print, bool export_print_config)
{
if ((path == nullptr) || (model == nullptr) || (print == nullptr))
return false;
// forces ".zip.amf" extension
std::string export_path = path;
if (!boost::iends_with(export_path, ".zip.amf"))
export_path = boost::filesystem::path(export_path).replace_extension(".zip.amf").string();
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
mz_bool res = mz_zip_writer_init_file(&archive, export_path.c_str(), 0);
if (res == 0)
return false;
std::stringstream stream;
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<amf unit=\"millimeter\">\n";
stream << "<metadata type=\"cad\">Slic3r " << SLIC3R_VERSION << "</metadata>\n";
stream << "<metadata type=\"" << SLIC3RPE_AMF_VERSION << "\">" << VERSION_AMF << "</metadata>\n";
if (export_print_config)
{
std::string config = "\n";
GCode::append_full_config(*print, config);
stream << "<metadata type=\"" << SLIC3R_CONFIG_TYPE << "\">" << xml_escape(config) << "</metadata>\n";
}
for (const auto &material : model->materials) {
if (material.first.empty())
continue;
// note that material-id must never be 0 since it's reserved by the AMF spec
stream << " <material id=\"" << material.first << "\">\n";
for (const auto &attr : material.second->attributes)
stream << " <metadata type=\"" << attr.first << "\">" << attr.second << "</metadata>\n";
for (const std::string &key : material.second->config.keys())
stream << " <metadata type=\"slic3r." << key << "\">" << material.second->config.serialize(key) << "</metadata>\n";
stream << " </material>\n";
}
std::string instances;
for (size_t object_id = 0; object_id < model->objects.size(); ++ object_id) {
ModelObject *object = model->objects[object_id];
stream << " <object id=\"" << object_id << "\">\n";
for (const std::string &key : object->config.keys())
stream << " <metadata type=\"slic3r." << key << "\">" << object->config.serialize(key) << "</metadata>\n";
if (!object->name.empty())
stream << " <metadata type=\"name\">" << xml_escape(object->name) << "</metadata>\n";
std::vector<double> layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector<double>();
if (layer_height_profile.size() >= 4 && (layer_height_profile.size() % 2) == 0) {
// Store the layer height profile as a single semicolon separated list.
stream << " <metadata type=\"slic3r.layer_height_profile\">";
stream << layer_height_profile.front();
for (size_t i = 1; i < layer_height_profile.size(); ++i)
stream << ";" << layer_height_profile[i];
stream << "\n </metadata>\n";
}
//FIXME Store the layer height ranges (ModelObject::layer_height_ranges)
stream << " <mesh>\n";
stream << " <vertices>\n";
std::vector<int> vertices_offsets;
int num_vertices = 0;
for (ModelVolume *volume : object->volumes) {
vertices_offsets.push_back(num_vertices);
if (! volume->mesh.repaired)
throw std::runtime_error("store_amf() requires repair()");
auto &stl = volume->mesh.stl;
if (stl.v_shared == nullptr)
stl_generate_shared_vertices(&stl);
for (size_t i = 0; i < stl.stats.shared_vertices; ++ i) {
stream << " <vertex>\n";
stream << " <coordinates>\n";
stream << " <x>" << stl.v_shared[i](0) << "</x>\n";
stream << " <y>" << stl.v_shared[i](1) << "</y>\n";
stream << " <z>" << stl.v_shared[i](2) << "</z>\n";
stream << " </coordinates>\n";
stream << " </vertex>\n";
}
num_vertices += stl.stats.shared_vertices;
}
stream << " </vertices>\n";
for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) {
ModelVolume *volume = object->volumes[i_volume];
int vertices_offset = vertices_offsets[i_volume];
if (volume->material_id().empty())
stream << " <volume>\n";
else
stream << " <volume materialid=\"" << volume->material_id() << "\">\n";
for (const std::string &key : volume->config.keys())
stream << " <metadata type=\"slic3r." << key << "\">" << volume->config.serialize(key) << "</metadata>\n";
if (!volume->name.empty())
stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n";
if (volume->is_modifier())
stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) {
stream << " <triangle>\n";
for (int j = 0; j < 3; ++j)
stream << " <v" << j + 1 << ">" << volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset << "</v" << j + 1 << ">\n";
stream << " </triangle>\n";
}
stream << " </volume>\n";
}
stream << " </mesh>\n";
stream << " </object>\n";
if (!object->instances.empty()) {
for (ModelInstance *instance : object->instances) {
char buf[512];
sprintf(buf,
" <instance objectid=\"" PRINTF_ZU "\">\n"
" <deltax>%lf</deltax>\n"
" <deltay>%lf</deltay>\n"
#if ENABLE_MODELINSTANCE_3D_OFFSET
" <deltaz>%lf</deltaz>\n"
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
" <rz>%lf</rz>\n"
" <scale>%lf</scale>\n"
" </instance>\n",
object_id,
#if ENABLE_MODELINSTANCE_3D_OFFSET
instance->get_offset(X),
instance->get_offset(Y),
instance->get_offset(Z),
#else
instance->offset(0),
instance->offset(1),
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
instance->rotation,
instance->scaling_factor);
//FIXME missing instance->scaling_factor
instances.append(buf);
}
}
}
if (! instances.empty()) {
stream << " <constellation id=\"1\">\n";
stream << instances;
stream << " </constellation>\n";
}
stream << "</amf>\n";
std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(export_path).filename().string(), ".zip.amf", ".amf");
std::string out = stream.str();
if (!mz_zip_writer_add_mem(&archive, internal_amf_filename.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
{
mz_zip_writer_end(&archive);
boost::filesystem::remove(export_path);
return false;
}
if (!mz_zip_writer_finalize_archive(&archive))
{
mz_zip_writer_end(&archive);
boost::filesystem::remove(export_path);
return false;
}
mz_zip_writer_end(&archive);
return true;
}
}; // namespace Slic3r

View file

@ -0,0 +1,19 @@
#ifndef slic3r_Format_AMF_hpp_
#define slic3r_Format_AMF_hpp_
namespace Slic3r {
class Model;
class Print;
class PresetBundle;
// Load the content of an amf file into the given model and preset bundle.
extern bool load_amf(const char *path, PresetBundle* bundle, Model *model);
// Save the given model and the config data contained in the given Print into an amf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
extern bool store_amf(const char *path, Model *model, Print* print, bool export_print_config);
}; // namespace Slic3r
#endif /* slic3r_Format_AMF_hpp_ */

View file

@ -0,0 +1,118 @@
#include "../libslic3r.h"
#include "../Model.hpp"
#include "../TriangleMesh.hpp"
#include "OBJ.hpp"
#include "objparser.hpp"
#include <string>
#ifdef _WIN32
#define DIR_SEPARATOR '\\'
#else
#define DIR_SEPARATOR '/'
#endif
namespace Slic3r {
bool load_obj(const char *path, Model *model, const char *object_name_in)
{
// Parse the OBJ file.
ObjParser::ObjData data;
if (! ObjParser::objparse(path, data)) {
// die "Failed to parse $file\n" if !-e $path;
return false;
}
// Count the faces and verify, that all faces are triangular.
size_t num_faces = 0;
size_t num_quads = 0;
for (size_t i = 0; i < data.vertices.size(); ) {
size_t j = i;
for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ;
if (i == j)
continue;
size_t face_vertices = j - i;
if (face_vertices != 3 && face_vertices != 4) {
// Non-triangular and non-quad faces are not supported as of now.
return false;
}
if (face_vertices == 4)
++ num_quads;
++ num_faces;
i = j + 1;
}
// Convert ObjData into STL.
TriangleMesh mesh;
stl_file &stl = mesh.stl;
stl.stats.type = inmemory;
stl.stats.number_of_facets = int(num_faces + num_quads);
stl.stats.original_num_facets = int(num_faces + num_quads);
// stl_allocate clears all the allocated data to zero, all normals are set to zeros as well.
stl_allocate(&stl);
size_t i_face = 0;
for (size_t i = 0; i < data.vertices.size(); ++ i) {
if (data.vertices[i].coordIdx == -1)
continue;
stl_facet &facet = stl.facet_start[i_face ++];
size_t num_normals = 0;
stl_normal normal(stl_normal::Zero());
for (unsigned int v = 0; v < 3; ++ v) {
const ObjParser::ObjVertex &vertex = data.vertices[i++];
memcpy(facet.vertex[v].data(), &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float));
if (vertex.normalIdx != -1) {
normal(0) += data.normals[vertex.normalIdx*3];
normal(1) += data.normals[vertex.normalIdx*3+1];
normal(2) += data.normals[vertex.normalIdx*3+2];
++ num_normals;
}
}
if (data.vertices[i].coordIdx != -1) {
// This is a quad. Produce the other triangle.
stl_facet &facet2 = stl.facet_start[i_face++];
facet2.vertex[0] = facet.vertex[0];
facet2.vertex[1] = facet.vertex[2];
const ObjParser::ObjVertex &vertex = data.vertices[i++];
memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
if (vertex.normalIdx != -1) {
normal(0) += data.normals[vertex.normalIdx*3];
normal(1) += data.normals[vertex.normalIdx*3+1];
normal(2) += data.normals[vertex.normalIdx*3+2];
++ num_normals;
}
if (num_normals == 4) {
// Normalize an average normal of a quad.
float len = facet.normal.norm();
if (len > EPSILON) {
normal /= len;
facet.normal = normal;
facet2.normal = normal;
}
}
} else if (num_normals == 3) {
// Normalize an average normal of a triangle.
float len = facet.normal.norm();
if (len > EPSILON)
facet.normal = normal / len;
}
}
stl_get_size(&stl);
mesh.repair();
if (mesh.facets_count() == 0) {
// die "This STL file couldn't be read because it's empty.\n"
return false;
}
std::string object_name;
if (object_name_in == nullptr) {
const char *last_slash = strrchr(path, DIR_SEPARATOR);
object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
} else
object_name.assign(object_name_in);
model->add_object(object_name.c_str(), path, std::move(mesh));
return true;
}
}; // namespace Slic3r

View file

@ -0,0 +1,14 @@
#ifndef slic3r_Format_OBJ_hpp_
#define slic3r_Format_OBJ_hpp_
namespace Slic3r {
class TriangleMesh;
class Model;
// Load an OBJ file into a provided model.
extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr);
}; // namespace Slic3r
#endif /* slic3r_Format_OBJ_hpp_ */

View file

@ -0,0 +1,399 @@
#ifdef SLIC3R_PRUS
#include <string.h>
#include <boost/nowide/convert.hpp>
#include <wx/string.h>
#include <wx/wfstream.h>
#include <wx/zipstrm.h>
#include <Eigen/Geometry>
#include "../libslic3r.h"
#include "../Model.hpp"
#include "PRUS.hpp"
#if 0
// Enable debugging and assert in this file.
#define DEBUG
#define _DEBUG
#undef NDEBUG
#endif
#include <assert.h>
namespace Slic3r
{
struct StlHeader
{
char comment[80];
uint32_t nTriangles;
};
static_assert(sizeof(StlHeader) == 84, "StlHeader size not correct");
// Buffered line reader for the wxInputStream.
class LineReader
{
public:
LineReader(wxInputStream &input_stream, const char *initial_data, int initial_len) :
m_input_stream(input_stream),
m_pos(0),
m_len(initial_len)
{
assert(initial_len >= 0 && initial_len < m_bufsize);
memcpy(m_buffer, initial_data, initial_len);
}
const char* next_line() {
for (;;) {
// Skip empty lines.
while (m_pos < m_len && (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n'))
++ m_pos;
if (m_pos == m_len) {
// Empty buffer, fill it from the input stream.
m_pos = 0;
m_input_stream.Read(m_buffer, m_bufsize - 1);
m_len = m_input_stream.LastRead();
assert(m_len >= 0 && m_len < m_bufsize);
if (m_len == 0)
// End of file.
return nullptr;
// Skip empty lines etc.
continue;
}
// The buffer is nonempty and it does not start with end of lines. Find the first end of line.
int end = m_pos + 1;
while (end < m_len && m_buffer[end] != '\r' && m_buffer[end] != '\n')
++ end;
if (end == m_len && ! m_input_stream.Eof() && m_len < m_bufsize) {
// Move the buffer content to the buffer start and fill the rest of the buffer.
assert(m_pos > 0);
memmove(m_buffer, m_buffer + m_pos, m_len - m_pos);
m_len -= m_pos;
assert(m_len >= 0 && m_len < m_bufsize);
m_pos = 0;
m_input_stream.Read(m_buffer + m_len, m_bufsize - 1 - m_len);
int new_data = m_input_stream.LastRead();
if (new_data > 0) {
m_len += new_data;
assert(m_len >= 0 && m_len < m_bufsize);
continue;
}
}
char *ptr_out = m_buffer + m_pos;
m_pos = end + 1;
m_buffer[end] = 0;
if (m_pos >= m_len) {
m_pos = 0;
m_len = 0;
}
return ptr_out;
}
}
int next_line_scanf(const char *format, ...)
{
const char *line = next_line();
if (line == nullptr)
return -1;
int result;
va_list arglist;
va_start(arglist, format);
result = vsscanf(line, format, arglist);
va_end(arglist);
return result;
}
private:
wxInputStream &m_input_stream;
static const int m_bufsize = 4096;
char m_buffer[m_bufsize];
int m_pos = 0;
int m_len = 0;
};
// Load a PrusaControl project file into a provided model.
bool load_prus(const char *path, Model *model)
{
// To receive the content of the zipped 'scene.xml' file.
std::vector<char> scene_xml_data;
wxFFileInputStream in(
#ifdef WIN32
// On Windows, convert to a 16bit unicode string.
boost::nowide::widen(path).c_str()
#else
path
#endif
);
wxZipInputStream zip(in);
std::unique_ptr<wxZipEntry> entry;
size_t num_models = 0;
std::map<int, ModelObject*> group_to_model_object;
while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) {
wxString name = entry->GetName();
if (name == "scene.xml") {
if (! scene_xml_data.empty()) {
// scene.xml has been found more than once in the archive.
return false;
}
size_t size_last = 0;
size_t size_incr = 4096;
scene_xml_data.resize(size_incr);
while (! zip.Read(scene_xml_data.data() + size_last, size_incr).Eof()) {
size_last += zip.LastRead();
if (scene_xml_data.size() < size_last + size_incr)
scene_xml_data.resize(size_last + size_incr);
}
size_last += zip.LastRead();
if (scene_xml_data.size() == size_last)
scene_xml_data.resize(size_last + 1);
else if (scene_xml_data.size() > size_last + 1)
scene_xml_data.erase(scene_xml_data.begin() + size_last + 1, scene_xml_data.end());
scene_xml_data[size_last] = 0;
}
else if (name.EndsWith(".stl") || name.EndsWith(".STL")) {
// Find the model entry in the XML data.
const wxScopedCharBuffer name_utf8 = name.ToUTF8();
char model_name_tag[1024];
sprintf(model_name_tag, "<model name=\"%s\">", name_utf8.data());
const char *model_xml = strstr(scene_xml_data.data(), model_name_tag);
const char *zero_tag = "<zero>";
const char *zero_xml = strstr(scene_xml_data.data(), zero_tag);
float trafo[3][4] = { 0 };
double instance_rotation = 0.;
double instance_scaling_factor = 1.f;
#if ENABLE_MODELINSTANCE_3D_OFFSET
Vec3d instance_offset = Vec3d::Zero();
#else
Vec2d instance_offset(0., 0.);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
bool trafo_set = false;
unsigned int group_id = (unsigned int)-1;
unsigned int extruder_id = (unsigned int)-1;
ModelObject *model_object = nullptr;
if (model_xml != nullptr) {
model_xml += strlen(model_name_tag);
const char *position_tag = "<position>";
const char *position_xml = strstr(model_xml, position_tag);
const char *rotation_tag = "<rotation>";
const char *rotation_xml = strstr(model_xml, rotation_tag);
const char *scale_tag = "<scale>";
const char *scale_xml = strstr(model_xml, scale_tag);
float position[3], rotation[3], scale[3], zero[3];
if (position_xml != nullptr && rotation_xml != nullptr && scale_xml != nullptr && zero_xml != nullptr &&
sscanf(position_xml+strlen(position_tag),
"[%f, %f, %f]", position, position+1, position+2) == 3 &&
sscanf(rotation_xml+strlen(rotation_tag),
"[%f, %f, %f]", rotation, rotation+1, rotation+2) == 3 &&
sscanf(scale_xml+strlen(scale_tag),
"[%f, %f, %f]", scale, scale+1, scale+2) == 3 &&
sscanf(zero_xml+strlen(zero_tag),
"[%f, %f, %f]", zero, zero+1, zero+2) == 3) {
if (scale[0] == scale[1] && scale[1] == scale[2]) {
instance_scaling_factor = scale[0];
scale[0] = scale[1] = scale[2] = 1.;
}
if (rotation[0] == 0. && rotation[1] == 0.) {
instance_rotation = - rotation[2];
rotation[2] = 0.;
}
Eigen::Matrix3f mat_rot, mat_scale, mat_trafo;
mat_rot = Eigen::AngleAxisf(-rotation[2], Eigen::Vector3f::UnitZ()) *
Eigen::AngleAxisf(-rotation[1], Eigen::Vector3f::UnitY()) *
Eigen::AngleAxisf(-rotation[0], Eigen::Vector3f::UnitX());
mat_scale = Eigen::Scaling(scale[0], scale[1], scale[2]);
mat_trafo = mat_rot * mat_scale;
for (size_t r = 0; r < 3; ++ r) {
for (size_t c = 0; c < 3; ++ c)
trafo[r][c] += mat_trafo(r, c);
}
#if ENABLE_MODELINSTANCE_3D_OFFSET
instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2]));
#else
instance_offset(0) = position[0] - zero[0];
instance_offset(1) = position[1] - zero[1];
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
trafo[2][3] = position[2] / instance_scaling_factor;
trafo_set = true;
}
const char *group_tag = "<group>";
const char *group_xml = strstr(model_xml, group_tag);
const char *extruder_tag = "<extruder>";
const char *extruder_xml = strstr(model_xml, extruder_tag);
if (group_xml != nullptr) {
int group = atoi(group_xml + strlen(group_tag));
if (group > 0) {
group_id = group;
auto it = group_to_model_object.find(group_id);
if (it != group_to_model_object.end())
model_object = it->second;
}
}
if (extruder_xml != nullptr) {
int e = atoi(extruder_xml + strlen(extruder_tag));
if (e > 0)
extruder_id = e;
}
}
if (trafo_set) {
// Extract the STL.
StlHeader header;
TriangleMesh mesh;
bool mesh_valid = false;
bool stl_ascii = false;
if (!zip.Read((void*)&header, sizeof(StlHeader)).Eof()) {
if (strncmp(header.comment, "solid ", 6) == 0)
stl_ascii = true;
else {
// Header has been extracted. Now read the faces.
stl_file &stl = mesh.stl;
stl.error = 0;
stl.stats.type = inmemory;
stl.stats.number_of_facets = header.nTriangles;
stl.stats.original_num_facets = header.nTriangles;
stl_allocate(&stl);
if (header.nTriangles > 0 && zip.ReadAll((void*)stl.facet_start, 50 * header.nTriangles)) {
if (sizeof(stl_facet) > SIZEOF_STL_FACET) {
// The stl.facet_start is not packed tightly. Unpack the array of stl_facets.
unsigned char *data = (unsigned char*)stl.facet_start;
for (size_t i = header.nTriangles - 1; i > 0; -- i)
memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET);
}
// All the faces have been read.
stl_get_size(&stl);
mesh.repair();
// Transform the model.
stl_transform(&stl, &trafo[0][0]);
if (std::abs(stl.stats.min(2)) < EPSILON)
stl.stats.min(2) = 0.;
// Add a mesh to a model.
if (mesh.facets_count() > 0)
mesh_valid = true;
}
}
} else
stl_ascii = true;
if (stl_ascii) {
// Try to parse ASCII STL.
char normal_buf[3][32];
stl_facet facet;
std::vector<stl_facet> facets;
LineReader line_reader(zip, (char*)&header, zip.LastRead());
std::string solid_name;
facet.extra[0] = facet.extra[1] = 0;
for (;;) {
const char *line = line_reader.next_line();
if (line == nullptr)
// End of file.
break;
if (strncmp(line, "solid", 5) == 0) {
// Opening the "solid" block.
if (! solid_name.empty()) {
// Error, solid block is already open.
facets.clear();
break;
}
solid_name = line + 5;
if (solid_name.empty())
solid_name = "unknown";
continue;
}
if (strncmp(line, "endsolid", 8) == 0) {
// Closing the "solid" block.
if (solid_name.empty()) {
// Error, no solid block is open.
facets.clear();
break;
}
solid_name.clear();
continue;
}
// Line has to start with the word solid.
int res_normal = sscanf(line, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]);
assert(res_normal == 3);
int res_outer_loop = line_reader.next_line_scanf(" outer loop");
assert(res_outer_loop == 0);
int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2));
assert(res_vertex1 == 3);
int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2));
assert(res_vertex2 == 3);
int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2));
assert(res_vertex3 == 3);
int res_endloop = line_reader.next_line_scanf(" endloop");
assert(res_endloop == 0);
int res_endfacet = line_reader.next_line_scanf(" endfacet");
if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) {
// perror("Something is syntactically very wrong with this ASCII STL!");
facets.clear();
break;
}
// The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 ||
sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 ||
sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
// Normal was mangled. Maybe denormals or "not a number" were stored?
// Just reset the normal and silently ignore it.
memset(&facet.normal, 0, sizeof(facet.normal));
}
facets.emplace_back(facet);
}
if (! facets.empty() && solid_name.empty()) {
stl_file &stl = mesh.stl;
stl.stats.type = inmemory;
stl.stats.number_of_facets = facets.size();
stl.stats.original_num_facets = facets.size();
stl_allocate(&stl);
memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50);
stl_get_size(&stl);
mesh.repair();
// Transform the model.
stl_transform(&stl, &trafo[0][0]);
// Add a mesh to a model.
if (mesh.facets_count() > 0)
mesh_valid = true;
}
}
if (mesh_valid) {
// Add this mesh to the model.
ModelVolume *volume = nullptr;
if (model_object == nullptr) {
// This is a first mesh of a group. Create a new object & volume.
model_object = model->add_object(name_utf8.data(), path, std::move(mesh));
volume = model_object->volumes.front();
ModelInstance *instance = model_object->add_instance();
instance->rotation = instance_rotation;
instance->scaling_factor = instance_scaling_factor;
#if ENABLE_MODELINSTANCE_3D_OFFSET
instance->set_offset(instance_offset);
#else
instance->offset = instance_offset;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
++num_models;
if (group_id != (size_t)-1)
group_to_model_object[group_id] = model_object;
} else {
// This is not the 1st mesh of a group. Add it to the ModelObject.
volume = model_object->add_volume(std::move(mesh));
volume->name = name_utf8.data();
}
// Set the extruder to the volume.
if (extruder_id != (unsigned int)-1) {
char str_extruder[64];
sprintf(str_extruder, "%ud", extruder_id);
volume->config.set_deserialize("extruder", str_extruder);
}
}
}
}
}
return num_models > 0;
}
}; // namespace Slic3r
#endif /* SLIC3R_PRUS */

View file

@ -0,0 +1,14 @@
#if defined(SLIC3R_PRUS) && ! defined(slic3r_Format_PRUS_hpp_)
#define slic3r_Format_PRUS_hpp_
namespace Slic3r {
class TriangleMesh;
class Model;
// Load a PrusaControl project file into a provided model.
extern bool load_prus(const char *path, Model *model);
}; // namespace Slic3r
#endif /* SLIC3R_PRUS && ! defined(slic3r_Format_PRUS_hpp_) */

View file

@ -0,0 +1,58 @@
#include "../libslic3r.h"
#include "../Model.hpp"
#include "../TriangleMesh.hpp"
#include "STL.hpp"
#include <string>
#ifdef _WIN32
#define DIR_SEPARATOR '\\'
#else
#define DIR_SEPARATOR '/'
#endif
namespace Slic3r {
bool load_stl(const char *path, Model *model, const char *object_name_in)
{
TriangleMesh mesh;
mesh.ReadSTLFile(path);
if (mesh.stl.error) {
// die "Failed to open $file\n" if !-e $path;
return false;
}
mesh.repair();
if (mesh.facets_count() == 0) {
// die "This STL file couldn't be read because it's empty.\n"
return false;
}
std::string object_name;
if (object_name_in == nullptr) {
const char *last_slash = strrchr(path, DIR_SEPARATOR);
object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
} else
object_name.assign(object_name_in);
model->add_object(object_name.c_str(), path, std::move(mesh));
return true;
}
bool store_stl(const char *path, TriangleMesh *mesh, bool binary)
{
if (binary)
mesh->write_binary(path);
else
mesh->write_ascii(path);
//FIXME returning false even if write failed.
return true;
}
bool store_stl(const char *path, ModelObject *model_object, bool binary)
{
TriangleMesh mesh = model_object->mesh();
return store_stl(path, &mesh, binary);
}
}; // namespace Slic3r

View file

@ -0,0 +1,17 @@
#ifndef slic3r_Format_STL_hpp_
#define slic3r_Format_STL_hpp_
namespace Slic3r {
class TriangleMesh;
class ModelObject;
// Load an STL file into a provided model.
extern bool load_stl(const char *path, Model *model, const char *object_name = nullptr);
extern bool store_stl(const char *path, TriangleMesh *mesh, bool binary);
extern bool store_stl(const char *path, ModelObject *model_object, bool binary);
}; // namespace Slic3r
#endif /* slic3r_Format_STL_hpp_ */

View file

@ -0,0 +1,540 @@
#include <stdlib.h>
#include <string.h>
#include <boost/nowide/cstdio.hpp>
#include "objparser.hpp"
namespace ObjParser {
static bool obj_parseline(const char *line, ObjData &data)
{
#define EATWS() while (*line == ' ' || *line == '\t') ++ line
if (*line == 0)
return true;
// Ignore whitespaces at the beginning of the line.
//FIXME is this a good idea?
EATWS();
char c1 = *line ++;
switch (c1) {
case '#':
// Comment, ignore the rest of the line.
break;
case 'v':
{
// Parse vertex geometry (position, normal, texture coordinates)
char c2 = *line ++;
switch (c2) {
case 't':
{
// vt - vertex texture parameter
// u v [w], w == 0 (or w == 1)
char c2 = *line ++;
if (c2 != ' ' && c2 != '\t')
return false;
EATWS();
char *endptr = 0;
double u = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t'))
return false;
line = endptr;
EATWS();
double v = 0;
if (*line != 0) {
v = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
return false;
line = endptr;
EATWS();
}
double w = 0;
if (*line != 0) {
w = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
return false;
line = endptr;
EATWS();
}
if (*line != 0)
return false;
data.textureCoordinates.push_back((float)u);
data.textureCoordinates.push_back((float)v);
data.textureCoordinates.push_back((float)w);
break;
}
case 'n':
{
// vn - vertex normal
// x y z
char c2 = *line ++;
if (c2 != ' ' && c2 != '\t')
return false;
EATWS();
char *endptr = 0;
double x = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t'))
return false;
line = endptr;
EATWS();
double y = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t'))
return false;
line = endptr;
EATWS();
double z = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
return false;
line = endptr;
EATWS();
if (*line != 0)
return false;
data.normals.push_back((float)x);
data.normals.push_back((float)y);
data.normals.push_back((float)z);
break;
}
case 'p':
{
// vp - vertex parameter
char c2 = *line ++;
if (c2 != ' ' && c2 != '\t')
return false;
EATWS();
char *endptr = 0;
double u = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
return false;
line = endptr;
EATWS();
double v = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
return false;
line = endptr;
EATWS();
double w = 0;
if (*line != 0) {
w = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
return false;
line = endptr;
EATWS();
}
if (*line != 0)
return false;
data.parameters.push_back((float)u);
data.parameters.push_back((float)v);
data.parameters.push_back((float)w);
break;
}
default:
{
// v - vertex geometry
if (c2 != ' ' && c2 != '\t')
return false;
EATWS();
char *endptr = 0;
double x = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t'))
return false;
line = endptr;
EATWS();
double y = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t'))
return false;
line = endptr;
EATWS();
double z = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
return false;
line = endptr;
EATWS();
double w = 1.0;
if (*line != 0) {
w = strtod(line, &endptr);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
return false;
line = endptr;
EATWS();
}
if (*line != 0)
return false;
data.coordinates.push_back((float)x);
data.coordinates.push_back((float)y);
data.coordinates.push_back((float)z);
data.coordinates.push_back((float)w);
break;
}
}
break;
}
case 'f':
{
// face
EATWS();
if (*line == 0)
return false;
// number of vertices of this face
int n = 0;
// current vertex to be parsed
ObjVertex vertex;
char *endptr = 0;
while (*line != 0) {
// Parse a single vertex reference.
vertex.coordIdx = 0;
vertex.normalIdx = 0;
vertex.textureCoordIdx = 0;
vertex.coordIdx = strtol(line, &endptr, 10);
// Coordinate has to be defined
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != '/' && *endptr != 0))
return false;
line = endptr;
if (*line == '/') {
++ line;
// Texture coordinate index may be missing after a 1st slash, but then the normal index has to be present.
if (*line != '/') {
// Parse the texture coordinate index.
vertex.textureCoordIdx = strtol(line, &endptr, 10);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != '/' && *endptr != 0))
return false;
line = endptr;
}
if (*line == '/') {
// Parse normal index.
++ line;
vertex.normalIdx = strtol(line, &endptr, 10);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
return false;
line = endptr;
}
}
if (vertex.coordIdx < 0)
vertex.coordIdx += data.coordinates.size() / 4;
else
-- vertex.coordIdx;
if (vertex.normalIdx < 0)
vertex.normalIdx += data.normals.size() / 3;
else
-- vertex.normalIdx;
if (vertex.textureCoordIdx < 0)
vertex.textureCoordIdx += data.textureCoordinates.size() / 3;
else
-- vertex.textureCoordIdx;
data.vertices.push_back(vertex);
EATWS();
}
vertex.coordIdx = -1;
vertex.normalIdx = -1;
vertex.textureCoordIdx = -1;
data.vertices.push_back(vertex);
break;
}
case 'm':
{
if (*(line ++) != 't' ||
*(line ++) != 'l' ||
*(line ++) != 'l' ||
*(line ++) != 'i' ||
*(line ++) != 'b')
return false;
// mtllib [external .mtl file name]
// printf("mtllib %s\r\n", line);
EATWS();
data.mtllibs.push_back(std::string(line));
break;
}
case 'u':
{
if (*(line ++) != 's' ||
*(line ++) != 'e' ||
*(line ++) != 'm' ||
*(line ++) != 't' ||
*(line ++) != 'l')
return false;
// usemtl [material name]
// printf("usemtl %s\r\n", line);
EATWS();
ObjUseMtl usemtl;
usemtl.vertexIdxFirst = data.vertices.size();
usemtl.name = line;
data.usemtls.push_back(usemtl);
break;
}
case 'o':
{
// o [object name]
EATWS();
const char *name = line;
while (*line != ' ' && *line != '\t' && *line != 0)
++ line;
// copy name to line.
EATWS();
if (*line != 0)
return false;
ObjObject object;
object.vertexIdxFirst = data.vertices.size();
object.name = line;
data.objects.push_back(object);
break;
}
case 'g':
{
// g [group name]
// printf("group %s\r\n", line);
ObjGroup group;
group.vertexIdxFirst = data.vertices.size();
group.name = line;
data.groups.push_back(group);
break;
}
case 's':
{
// s 1 / off
char c2 = *line ++;
if (c2 != ' ' && c2 != '\t')
return false;
EATWS();
char *endptr = 0;
long g = strtol(line, &endptr, 10);
if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
return false;
line = endptr;
EATWS();
if (*line != 0)
return false;
ObjSmoothingGroup group;
group.vertexIdxFirst = data.vertices.size();
group.smoothingGroupID = g;
data.smoothingGroups.push_back(group);
break;
}
default:
printf("ObjParser: Unknown command: %c\r\n", c1);
break;
}
return true;
}
bool objparse(const char *path, ObjData &data)
{
FILE *pFile = boost::nowide::fopen(path, "rt");
if (pFile == 0)
return false;
try {
char buf[65536 * 2];
size_t len = 0;
size_t lenPrev = 0;
while ((len = ::fread(buf + lenPrev, 1, 65536, pFile)) != 0) {
len += lenPrev;
size_t lastLine = 0;
for (size_t i = 0; i < len; ++ i)
if (buf[i] == '\r' || buf[i] == '\n') {
buf[i] = 0;
char *c = buf + lastLine;
while (*c == ' ' || *c == '\t')
++ c;
obj_parseline(c, data);
lastLine = i + 1;
}
lenPrev = len - lastLine;
memmove(buf, buf + lastLine, lenPrev);
}
} catch (std::bad_alloc &ex) {
printf("Out of memory\r\n");
}
::fclose(pFile);
// printf("vertices: %d\r\n", data.vertices.size() / 4);
// printf("coords: %d\r\n", data.coordinates.size());
return true;
}
template<typename T>
bool savevector(FILE *pFile, const std::vector<T> &v)
{
size_t cnt = v.size();
::fwrite(&cnt, 1, sizeof(cnt), pFile);
//FIXME sizeof(T) works for data types leaving no gaps in the allocated vector because of alignment of the T type.
if (! v.empty())
::fwrite(&v.front(), 1, sizeof(T) * cnt, pFile);
return true;
}
bool savevector(FILE *pFile, const std::vector<std::string> &v)
{
size_t cnt = v.size();
::fwrite(&cnt, 1, sizeof(cnt), pFile);
for (size_t i = 0; i < cnt; ++ i) {
size_t len = v[i].size();
::fwrite(&len, 1, sizeof(cnt), pFile);
::fwrite(v[i].c_str(), 1, len, pFile);
}
return true;
}
template<typename T>
bool savevectornameidx(FILE *pFile, const std::vector<T> &v)
{
size_t cnt = v.size();
::fwrite(&cnt, 1, sizeof(cnt), pFile);
for (size_t i = 0; i < cnt; ++ i) {
::fwrite(&v[i].vertexIdxFirst, 1, sizeof(int), pFile);
size_t len = v[i].name.size();
::fwrite(&len, 1, sizeof(cnt), pFile);
::fwrite(v[i].name.c_str(), 1, len, pFile);
}
return true;
}
template<typename T>
bool loadvector(FILE *pFile, std::vector<T> &v)
{
v.clear();
size_t cnt = 0;
if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1)
return false;
//FIXME sizeof(T) works for data types leaving no gaps in the allocated vector because of alignment of the T type.
if (cnt != 0) {
v.assign(cnt, T());
if (::fread(&v.front(), sizeof(T), cnt, pFile) != cnt)
return false;
}
return true;
}
bool loadvector(FILE *pFile, std::vector<std::string> &v)
{
v.clear();
size_t cnt = 0;
if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1)
return false;
v.reserve(cnt);
for (size_t i = 0; i < cnt; ++ i) {
size_t len = 0;
if (::fread(&len, sizeof(len), 1, pFile) != 1)
return false;
std::string s(" ", len);
if (::fread(const_cast<char*>(s.c_str()), 1, len, pFile) != len)
return false;
v.push_back(std::move(s));
}
return true;
}
template<typename T>
bool loadvectornameidx(FILE *pFile, std::vector<T> &v)
{
v.clear();
size_t cnt = 0;
if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1)
return false;
v.assign(cnt, T());
for (size_t i = 0; i < cnt; ++ i) {
if (::fread(&v[i].vertexIdxFirst, sizeof(int), 1, pFile) != 1)
return false;
size_t len = 0;
if (::fread(&len, sizeof(len), 1, pFile) != 1)
return false;
v[i].name.assign(" ", len);
if (::fread(const_cast<char*>(v[i].name.c_str()), 1, len, pFile) != len)
return false;
}
return true;
}
bool objbinsave(const char *path, const ObjData &data)
{
FILE *pFile = boost::nowide::fopen(path, "wb");
if (pFile == 0)
return false;
size_t version = 1;
::fwrite(&version, 1, sizeof(version), pFile);
bool result =
savevector(pFile, data.coordinates) &&
savevector(pFile, data.textureCoordinates) &&
savevector(pFile, data.normals) &&
savevector(pFile, data.parameters) &&
savevector(pFile, data.mtllibs) &&
savevectornameidx(pFile, data.usemtls) &&
savevectornameidx(pFile, data.objects) &&
savevectornameidx(pFile, data.groups) &&
savevector(pFile, data.smoothingGroups) &&
savevector(pFile, data.vertices);
::fclose(pFile);
return result;
}
bool objbinload(const char *path, ObjData &data)
{
FILE *pFile = boost::nowide::fopen(path, "rb");
if (pFile == 0)
return false;
data.version = 0;
if (::fread(&data.version, sizeof(data.version), 1, pFile) != 1)
return false;
if (data.version != 1)
return false;
bool result =
loadvector(pFile, data.coordinates) &&
loadvector(pFile, data.textureCoordinates) &&
loadvector(pFile, data.normals) &&
loadvector(pFile, data.parameters) &&
loadvector(pFile, data.mtllibs) &&
loadvectornameidx(pFile, data.usemtls) &&
loadvectornameidx(pFile, data.objects) &&
loadvectornameidx(pFile, data.groups) &&
loadvector(pFile, data.smoothingGroups) &&
loadvector(pFile, data.vertices);
::fclose(pFile);
return result;
}
template<typename T>
bool vectorequal(const std::vector<T> &v1, const std::vector<T> &v2)
{
if (v1.size() != v2.size())
return false;
for (size_t i = 0; i < v1.size(); ++ i)
if (! (v1[i] == v2[i]))
return false;
return true;
}
bool vectorequal(const std::vector<std::string> &v1, const std::vector<std::string> &v2)
{
if (v1.size() != v2.size())
return false;
for (size_t i = 0; i < v1.size(); ++ i)
if (v1[i].compare(v2[i]) != 0)
return false;
return true;
}
extern bool objequal(const ObjData &data1, const ObjData &data2)
{
//FIXME ignore version number
// version;
return
vectorequal(data1.coordinates, data2.coordinates) &&
vectorequal(data1.textureCoordinates, data2.textureCoordinates) &&
vectorequal(data1.normals, data2.normals) &&
vectorequal(data1.parameters, data2.parameters) &&
vectorequal(data1.mtllibs, data2.mtllibs) &&
vectorequal(data1.usemtls, data2.usemtls) &&
vectorequal(data1.objects, data2.objects) &&
vectorequal(data1.groups, data2.groups) &&
vectorequal(data1.vertices, data2.vertices);
}
} // namespace ObjParser

View file

@ -0,0 +1,109 @@
#ifndef slic3r_Format_objparser_hpp_
#define slic3r_Format_objparser_hpp_
#include <string>
#include <vector>
namespace ObjParser {
struct ObjVertex
{
int coordIdx;
int textureCoordIdx;
int normalIdx;
};
inline bool operator==(const ObjVertex &v1, const ObjVertex &v2)
{
return
v1.coordIdx == v2.coordIdx &&
v1.textureCoordIdx == v2.textureCoordIdx &&
v1.normalIdx == v2.normalIdx;
}
struct ObjUseMtl
{
int vertexIdxFirst;
std::string name;
};
inline bool operator==(const ObjUseMtl &v1, const ObjUseMtl &v2)
{
return
v1.vertexIdxFirst == v2.vertexIdxFirst &&
v1.name.compare(v2.name) == 0;
}
struct ObjObject
{
int vertexIdxFirst;
std::string name;
};
inline bool operator==(const ObjObject &v1, const ObjObject &v2)
{
return
v1.vertexIdxFirst == v2.vertexIdxFirst &&
v1.name.compare(v2.name) == 0;
}
struct ObjGroup
{
int vertexIdxFirst;
std::string name;
};
inline bool operator==(const ObjGroup &v1, const ObjGroup &v2)
{
return
v1.vertexIdxFirst == v2.vertexIdxFirst &&
v1.name.compare(v2.name) == 0;
}
struct ObjSmoothingGroup
{
int vertexIdxFirst;
int smoothingGroupID;
};
inline bool operator==(const ObjSmoothingGroup &v1, const ObjSmoothingGroup &v2)
{
return
v1.vertexIdxFirst == v2.vertexIdxFirst &&
v1.smoothingGroupID == v2.smoothingGroupID;
}
struct ObjData {
// Version of the data structure for load / store in the private binary format.
int version;
// x, y, z, w
std::vector<float> coordinates;
// u, v, w
std::vector<float> textureCoordinates;
// x, y, z
std::vector<float> normals;
// u, v, w
std::vector<float> parameters;
std::vector<std::string> mtllibs;
std::vector<ObjUseMtl> usemtls;
std::vector<ObjObject> objects;
std::vector<ObjGroup> groups;
std::vector<ObjSmoothingGroup> smoothingGroups;
// List of faces, delimited by an ObjVertex with all members set to -1.
std::vector<ObjVertex> vertices;
};
extern bool objparse(const char *path, ObjData &data);
extern bool objbinsave(const char *path, const ObjData &data);
extern bool objbinload(const char *path, ObjData &data);
extern bool objequal(const ObjData &data1, const ObjData &data2);
} // namespace ObjParser
#endif /* slic3r_Format_objparser_hpp_ */

2730
src/libslic3r/GCode.cpp Normal file

File diff suppressed because it is too large Load diff

365
src/libslic3r/GCode.hpp Normal file
View file

@ -0,0 +1,365 @@
#ifndef slic3r_GCode_hpp_
#define slic3r_GCode_hpp_
#include "libslic3r.h"
#include "ExPolygon.hpp"
#include "GCodeWriter.hpp"
#include "Layer.hpp"
#include "MotionPlanner.hpp"
#include "Point.hpp"
#include "PlaceholderParser.hpp"
#include "Print.hpp"
#include "PrintConfig.hpp"
#include "GCode/CoolingBuffer.hpp"
#include "GCode/PressureEqualizer.hpp"
#include "GCode/SpiralVase.hpp"
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
#include "GCodeTimeEstimator.hpp"
#include "EdgeGrid.hpp"
#include "GCode/Analyzer.hpp"
#include <memory>
#include <string>
namespace Slic3r {
// Forward declarations.
class GCode;
class GCodePreviewData;
class AvoidCrossingPerimeters {
public:
// this flag triggers the use of the external configuration space
bool use_external_mp;
bool use_external_mp_once; // just for the next travel move
// this flag disables avoid_crossing_perimeters just for the next travel move
// we enable it by default for the first travel move in print
bool disable_once;
AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {}
~AvoidCrossingPerimeters() {}
void init_external_mp(const ExPolygons &islands) { m_external_mp = Slic3r::make_unique<MotionPlanner>(islands); }
void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique<MotionPlanner>(islands); }
Polyline travel_to(const GCode &gcodegen, const Point &point);
private:
std::unique_ptr<MotionPlanner> m_external_mp;
std::unique_ptr<MotionPlanner> m_layer_mp;
};
class OozePrevention {
public:
bool enable;
Points standby_points;
OozePrevention() : enable(false) {}
std::string pre_toolchange(GCode &gcodegen);
std::string post_toolchange(GCode &gcodegen);
private:
int _get_temp(GCode &gcodegen);
};
class Wipe {
public:
bool enable;
Polyline path;
Wipe() : enable(false) {}
bool has_path() const { return !this->path.points.empty(); }
void reset_path() { this->path = Polyline(); }
std::string wipe(GCode &gcodegen, bool toolchange = false);
};
class WipeTowerIntegration {
public:
WipeTowerIntegration(
const PrintConfig &print_config,
const WipeTower::ToolChangeResult &priming,
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
const WipeTower::ToolChangeResult &final_purge) :
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
m_priming(priming),
m_tool_changes(tool_changes),
m_final_purge(final_purge),
m_layer_idx(-1),
m_tool_change_idx(0),
m_brim_done(false) {}
std::string prime(GCode &gcodegen);
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer);
std::string finalize(GCode &gcodegen);
std::vector<float> used_filament_length() const;
private:
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const;
// Postprocesses gcode: rotates and moves all G1 extrusions and returns result
std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const;
// Left / right edges of the wipe tower, for the planning of wipe moves.
const float m_left;
const float m_right;
const WipeTower::xy m_wipe_tower_pos;
const float m_wipe_tower_rotation;
// Reference to cached values at the Printer class.
const WipeTower::ToolChangeResult &m_priming;
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
const WipeTower::ToolChangeResult &m_final_purge;
// Current layer index.
int m_layer_idx;
int m_tool_change_idx;
bool m_brim_done;
bool i_have_brim = false;
};
class GCode {
public:
GCode() :
m_origin(Vec2d::Zero()),
m_enable_loop_clipping(true),
m_enable_cooling_markers(false),
m_enable_extrusion_role_markers(false),
m_enable_analyzer(false),
m_last_analyzer_extrusion_role(erNone),
m_layer_count(0),
m_layer_index(-1),
m_layer(nullptr),
m_volumetric_speed(0),
m_last_pos_defined(false),
m_last_extrusion_role(erNone),
m_last_mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm),
m_last_width(GCodeAnalyzer::Default_Width),
m_last_height(GCodeAnalyzer::Default_Height),
m_brim_done(false),
m_second_layer_things_done(false),
m_normal_time_estimator(GCodeTimeEstimator::Normal),
m_silent_time_estimator(GCodeTimeEstimator::Silent),
m_silent_time_estimator_enabled(false),
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max()))
{}
~GCode() {}
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
void do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr);
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
const Vec2d& origin() const { return m_origin; }
void set_origin(const Vec2d &pointf);
void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); }
const Point& last_pos() const { return m_last_pos; }
Vec2d point_to_gcode(const Point &point) const;
Point gcode_to_point(const Vec2d &point) const;
const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; }
GCodeWriter& writer() { return m_writer; }
PlaceholderParser& placeholder_parser() { return m_placeholder_parser; }
const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; }
// Process a template through the placeholder parser, collect error messages to be reported
// inside the generated string and after the G-code export finishes.
std::string placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr);
bool enable_cooling_markers() const { return m_enable_cooling_markers; }
// For Perl bindings, to be used exclusively by unit tests.
unsigned int layer_count() const { return m_layer_count; }
void set_layer_count(unsigned int value) { m_layer_count = value; }
void apply_print_config(const PrintConfig &print_config);
// append full config to the given string
static void append_full_config(const Print& print, std::string& str);
protected:
void _do_export(Print &print, FILE *file, GCodePreviewData *preview_data);
// Object and support extrusions of the same PrintObject at the same print_z.
struct LayerToPrint
{
LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {}
const Layer *object_layer;
const SupportLayer *support_layer;
const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; }
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
};
static std::vector<GCode::LayerToPrint> collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
void process_layer(
// Write into the output file.
FILE *file,
const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers,
const LayerTools &layer_tools,
// If set to size_t(-1), then print all copies of all objects.
// Otherwise print a single copy of a single object.
const size_t single_object_idx = size_t(-1));
void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; }
bool last_pos_defined() const { return m_last_pos_defined; }
void set_extruders(const std::vector<unsigned int> &extruder_ids);
std::string preamble();
std::string change_layer(coordf_t print_z);
std::string extrude_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1., std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid = nullptr);
std::string extrude_loop(ExtrusionLoop loop, std::string description, double speed = -1., std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid = nullptr);
std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.);
std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.);
typedef std::vector<int> ExtruderPerCopy;
// Extruding multiple objects with soluble / non-soluble / combined supports
// on a multi-material printer, trying to minimize tool switches.
// Following structures sort extrusions by the extruder ID, by an order of objects and object islands.
struct ObjectByExtruder
{
ObjectByExtruder() : support(nullptr), support_extrusion_role(erNone) {}
const ExtrusionEntityCollection *support;
// erSupportMaterial / erSupportMaterialInterface or erMixed.
ExtrusionRole support_extrusion_role;
struct Island
{
struct Region {
ExtrusionEntityCollection perimeters;
ExtrusionEntityCollection infills;
std::vector<const ExtruderPerCopy*> infills_overrides;
std::vector<const ExtruderPerCopy*> perimeters_overrides;
// Appends perimeter/infill entities and writes don't indices of those that are not to be extruder as part of perimeter/infill wiping
void append(const std::string& type, const ExtrusionEntityCollection* eec, const ExtruderPerCopy* copy_extruders, unsigned int object_copies_num);
};
std::vector<Region> by_region; // all extrusions for this island, grouped by regions
const std::vector<Region>& by_region_per_copy(unsigned int copy, int extruder, bool wiping_entities = false); // returns reference to subvector of by_region
private:
std::vector<Region> by_region_per_copy_cache; // caches vector generated by function above to avoid copying and recalculating
};
std::vector<Island> islands;
};
std::string extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid);
std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region);
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone);
std::string retract(bool toolchange = false);
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); }
std::string set_extruder(unsigned int extruder_id);
/* Origin of print coordinates expressed in unscaled G-code coordinates.
This affects the input arguments supplied to the extrude*() and travel_to()
methods. */
Vec2d m_origin;
FullPrintConfig m_config;
GCodeWriter m_writer;
PlaceholderParser m_placeholder_parser;
// Collection of templates, on which the placeholder substitution failed.
std::set<std::string> m_placeholder_parser_failed_templates;
OozePrevention m_ooze_prevention;
Wipe m_wipe;
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
bool m_enable_loop_clipping;
// If enabled, the G-code generator will put following comments at the ends
// of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END
// Those comments are received and consumed (removed from the G-code) by the CoolingBuffer.pm Perl module.
bool m_enable_cooling_markers;
// Markers for the Pressure Equalizer to recognize the extrusion type.
// The Pressure Equalizer removes the markers from the final G-code.
bool m_enable_extrusion_role_markers;
// Enableds the G-code Analyzer.
// Extended markers will be added during G-code generation.
// The G-code Analyzer will remove these comments from the final G-code.
bool m_enable_analyzer;
ExtrusionRole m_last_analyzer_extrusion_role;
// How many times will change_layer() be called?
// change_layer() will update the progress bar.
unsigned int m_layer_count;
// Progress bar indicator. Increments from -1 up to layer_count.
int m_layer_index;
// Current layer processed. Insequential printing mode, only a single copy will be printed.
// In non-sequential mode, all its copies will be printed.
const Layer* m_layer;
std::map<const PrintObject*,Point> m_seam_position;
double m_volumetric_speed;
// Support for the extrusion role markers. Which marker is active?
ExtrusionRole m_last_extrusion_role;
// Support for G-Code Analyzer
double m_last_mm3_per_mm;
float m_last_width;
float m_last_height;
Point m_last_pos;
bool m_last_pos_defined;
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
std::unique_ptr<SpiralVase> m_spiral_vase;
std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
std::unique_ptr<WipeTowerIntegration> m_wipe_tower;
// Heights at which the skirt has already been extruded.
std::vector<coordf_t> m_skirt_done;
// Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print.
bool m_brim_done;
// Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
bool m_second_layer_things_done;
// Index of a last object copy extruded.
std::pair<const PrintObject*, Point> m_last_obj_copy;
// Time estimators
GCodeTimeEstimator m_normal_time_estimator;
GCodeTimeEstimator m_silent_time_estimator;
bool m_silent_time_estimator_enabled;
// Analyzer
GCodeAnalyzer m_analyzer;
// Write a string into a file.
void _write(FILE* file, const std::string& what) { this->_write(file, what.c_str()); }
void _write(FILE* file, const char *what);
// Write a string into a file.
// Add a newline, if the string does not end with a newline already.
// Used to export a custom G-code section processed by the PlaceholderParser.
void _writeln(FILE* file, const std::string& what);
// Formats and write into a file the given data.
void _write_format(FILE* file, const char* format, ...);
std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1);
void print_machine_envelope(FILE *file, Print &print);
void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
// this flag triggers first layer speeds
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; }
friend ObjectByExtruder& object_by_extruder(
std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder,
unsigned int extruder_id,
size_t object_idx,
size_t num_objects);
friend std::vector<ObjectByExtruder::Island>& object_islands_by_extruder(
std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder,
unsigned int extruder_id,
size_t object_idx,
size_t num_objects,
size_t num_islands);
friend class WipeTowerIntegration;
};
}
#endif

View file

@ -0,0 +1,842 @@
#include <memory.h>
#include <string.h>
#include <float.h>
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "Print.hpp"
#include "Analyzer.hpp"
#include "PreviewData.hpp"
static const std::string AXIS_STR = "XYZE";
static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
static const float INCHES_TO_MM = 25.4f;
static const float DEFAULT_FEEDRATE = 0.0f;
static const unsigned int DEFAULT_EXTRUDER_ID = 0;
static const Slic3r::Vec3d DEFAULT_START_POSITION = Slic3r::Vec3d(0.0f, 0.0f, 0.0f);
static const float DEFAULT_START_EXTRUSION = 0.0f;
namespace Slic3r {
const std::string GCodeAnalyzer::Extrusion_Role_Tag = "_ANALYZER_EXTR_ROLE:";
const std::string GCodeAnalyzer::Mm3_Per_Mm_Tag = "_ANALYZER_MM3_PER_MM:";
const std::string GCodeAnalyzer::Width_Tag = "_ANALYZER_WIDTH:";
const std::string GCodeAnalyzer::Height_Tag = "_ANALYZER_HEIGHT:";
const double GCodeAnalyzer::Default_mm3_per_mm = 0.0;
const float GCodeAnalyzer::Default_Width = 0.0f;
const float GCodeAnalyzer::Default_Height = 0.0f;
GCodeAnalyzer::Metadata::Metadata()
: extrusion_role(erNone)
, extruder_id(DEFAULT_EXTRUDER_ID)
, mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm)
, width(GCodeAnalyzer::Default_Width)
, height(GCodeAnalyzer::Default_Height)
, feedrate(DEFAULT_FEEDRATE)
{
}
GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate)
: extrusion_role(extrusion_role)
, extruder_id(extruder_id)
, mm3_per_mm(mm3_per_mm)
, width(width)
, height(height)
, feedrate(feedrate)
{
}
bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other) const
{
if (extrusion_role != other.extrusion_role)
return true;
if (extruder_id != other.extruder_id)
return true;
if (mm3_per_mm != other.mm3_per_mm)
return true;
if (width != other.width)
return true;
if (height != other.height)
return true;
if (feedrate != other.feedrate)
return true;
return false;
}
GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder)
: type(type)
, data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate)
, start_position(start_position)
, end_position(end_position)
, delta_extruder(delta_extruder)
{
}
GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, const GCodeAnalyzer::Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder)
: type(type)
, data(data)
, start_position(start_position)
, end_position(end_position)
, delta_extruder(delta_extruder)
{
}
GCodeAnalyzer::GCodeAnalyzer()
{
reset();
}
void GCodeAnalyzer::reset()
{
_set_units(Millimeters);
_set_global_positioning_type(Absolute);
_set_e_local_positioning_type(Absolute);
_set_extrusion_role(erNone);
_set_extruder_id(DEFAULT_EXTRUDER_ID);
_set_mm3_per_mm(Default_mm3_per_mm);
_set_width(Default_Width);
_set_height(Default_Height);
_set_feedrate(DEFAULT_FEEDRATE);
_set_start_position(DEFAULT_START_POSITION);
_set_start_extrusion(DEFAULT_START_EXTRUSION);
_reset_axes_position();
m_moves_map.clear();
}
const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode)
{
m_process_output = "";
m_parser.parse_buffer(gcode,
[this](GCodeReader& reader, const GCodeReader::GCodeLine& line)
{ this->_process_gcode_line(reader, line); });
return m_process_output;
}
void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data)
{
// resets preview data
preview_data.reset();
// calculates extrusion layers
_calc_gcode_preview_extrusion_layers(preview_data);
// calculates travel
_calc_gcode_preview_travel(preview_data);
// calculates retractions
_calc_gcode_preview_retractions(preview_data);
// calculates unretractions
_calc_gcode_preview_unretractions(preview_data);
}
bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role)
{
return ((erPerimeter <= role) && (role < erMixed));
}
void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line)
{
// processes 'special' comments contained in line
if (_process_tags(line))
{
#if 0
// DEBUG ONLY: puts the line back into the gcode
m_process_output += line.raw() + "\n";
#endif
return;
}
// sets new start position/extrusion
_set_start_position(_get_end_position());
_set_start_extrusion(_get_axis_position(E));
// processes 'normal' gcode lines
std::string cmd = line.cmd();
if (cmd.length() > 1)
{
switch (::toupper(cmd[0]))
{
case 'G':
{
switch (::atoi(&cmd[1]))
{
case 1: // Move
{
_processG1(line);
break;
}
case 10: // Retract
{
_processG10(line);
break;
}
case 11: // Unretract
{
_processG11(line);
break;
}
case 22: // Firmware controlled Retract
{
_processG22(line);
break;
}
case 23: // Firmware controlled Unretract
{
_processG23(line);
break;
}
case 90: // Set to Absolute Positioning
{
_processG90(line);
break;
}
case 91: // Set to Relative Positioning
{
_processG91(line);
break;
}
case 92: // Set Position
{
_processG92(line);
break;
}
}
break;
}
case 'M':
{
switch (::atoi(&cmd[1]))
{
case 82: // Set extruder to absolute mode
{
_processM82(line);
break;
}
case 83: // Set extruder to relative mode
{
_processM83(line);
break;
}
}
break;
}
case 'T': // Select Tools
{
_processT(line);
break;
}
}
}
// puts the line back into the gcode
m_process_output += line.raw() + "\n";
}
// Returns the new absolute position on the given axis in dependence of the given parameters
float axis_absolute_position_from_G1_line(GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeAnalyzer::EUnits units, bool is_relative, float current_absolute_position)
{
float lengthsScaleFactor = (units == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f;
if (lineG1.has(Slic3r::Axis(axis)))
{
float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor;
return is_relative ? current_absolute_position + ret : ret;
}
else
return current_absolute_position;
}
void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line)
{
// updates axes positions from line
EUnits units = _get_units();
float new_pos[Num_Axis];
for (unsigned char a = X; a < Num_Axis; ++a)
{
bool is_relative = (_get_global_positioning_type() == Relative);
if (a == E)
is_relative |= (_get_e_local_positioning_type() == Relative);
new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, is_relative, _get_axis_position((EAxis)a));
}
// updates feedrate from line, if present
if (line.has_f())
_set_feedrate(line.f() * MMMIN_TO_MMSEC);
// calculates movement deltas
float delta_pos[Num_Axis];
for (unsigned char a = X; a < Num_Axis; ++a)
{
delta_pos[a] = new_pos[a] - _get_axis_position((EAxis)a);
}
// Detects move type
GCodeMove::EType type = GCodeMove::Noop;
if (delta_pos[E] < 0.0f)
{
if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f))
type = GCodeMove::Move;
else
type = GCodeMove::Retract;
}
else if (delta_pos[E] > 0.0f)
{
if ((delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f))
type = GCodeMove::Unretract;
else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f))
type = GCodeMove::Extrude;
}
else if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f))
type = GCodeMove::Move;
ExtrusionRole role = _get_extrusion_role();
if ((type == GCodeMove::Extrude) && ((_get_width() == 0.0f) || (_get_height() == 0.0f) || !is_valid_extrusion_role(role)))
type = GCodeMove::Move;
// updates axis positions
for (unsigned char a = X; a < Num_Axis; ++a)
{
_set_axis_position((EAxis)a, new_pos[a]);
}
// stores the move
if (type != GCodeMove::Noop)
_store_move(type);
}
void GCodeAnalyzer::_processG10(const GCodeReader::GCodeLine& line)
{
// stores retract move
_store_move(GCodeMove::Retract);
}
void GCodeAnalyzer::_processG11(const GCodeReader::GCodeLine& line)
{
// stores unretract move
_store_move(GCodeMove::Unretract);
}
void GCodeAnalyzer::_processG22(const GCodeReader::GCodeLine& line)
{
// stores retract move
_store_move(GCodeMove::Retract);
}
void GCodeAnalyzer::_processG23(const GCodeReader::GCodeLine& line)
{
// stores unretract move
_store_move(GCodeMove::Unretract);
}
void GCodeAnalyzer::_processG90(const GCodeReader::GCodeLine& line)
{
_set_global_positioning_type(Absolute);
}
void GCodeAnalyzer::_processG91(const GCodeReader::GCodeLine& line)
{
_set_global_positioning_type(Relative);
}
void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line)
{
float lengthsScaleFactor = (_get_units() == Inches) ? INCHES_TO_MM : 1.0f;
bool anyFound = false;
if (line.has_x())
{
_set_axis_position(X, line.x() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_y())
{
_set_axis_position(Y, line.y() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_z())
{
_set_axis_position(Z, line.z() * lengthsScaleFactor);
anyFound = true;
}
if (line.has_e())
{
_set_axis_position(E, line.e() * lengthsScaleFactor);
anyFound = true;
}
if (!anyFound)
{
for (unsigned char a = X; a < Num_Axis; ++a)
{
_set_axis_position((EAxis)a, 0.0f);
}
}
}
void GCodeAnalyzer::_processM82(const GCodeReader::GCodeLine& line)
{
_set_e_local_positioning_type(Absolute);
}
void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line)
{
_set_e_local_positioning_type(Relative);
}
void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line)
{
std::string cmd = line.cmd();
if (cmd.length() > 1)
{
unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10);
if (_get_extruder_id() != id)
{
_set_extruder_id(id);
// stores tool change move
_store_move(GCodeMove::Tool_change);
}
}
}
bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line)
{
std::string comment = line.comment();
// extrusion role tag
size_t pos = comment.find(Extrusion_Role_Tag);
if (pos != comment.npos)
{
_process_extrusion_role_tag(comment, pos);
return true;
}
// mm3 per mm tag
pos = comment.find(Mm3_Per_Mm_Tag);
if (pos != comment.npos)
{
_process_mm3_per_mm_tag(comment, pos);
return true;
}
// width tag
pos = comment.find(Width_Tag);
if (pos != comment.npos)
{
_process_width_tag(comment, pos);
return true;
}
// height tag
pos = comment.find(Height_Tag);
if (pos != comment.npos)
{
_process_height_tag(comment, pos);
return true;
}
return false;
}
void GCodeAnalyzer::_process_extrusion_role_tag(const std::string& comment, size_t pos)
{
int role = (int)::strtol(comment.substr(pos + Extrusion_Role_Tag.length()).c_str(), nullptr, 10);
if (_is_valid_extrusion_role(role))
_set_extrusion_role((ExtrusionRole)role);
else
{
// todo: show some error ?
}
}
void GCodeAnalyzer::_process_mm3_per_mm_tag(const std::string& comment, size_t pos)
{
_set_mm3_per_mm(::strtod(comment.substr(pos + Mm3_Per_Mm_Tag.length()).c_str(), nullptr));
}
void GCodeAnalyzer::_process_width_tag(const std::string& comment, size_t pos)
{
_set_width((float)::strtod(comment.substr(pos + Width_Tag.length()).c_str(), nullptr));
}
void GCodeAnalyzer::_process_height_tag(const std::string& comment, size_t pos)
{
_set_height((float)::strtod(comment.substr(pos + Height_Tag.length()).c_str(), nullptr));
}
void GCodeAnalyzer::_set_units(GCodeAnalyzer::EUnits units)
{
m_state.units = units;
}
GCodeAnalyzer::EUnits GCodeAnalyzer::_get_units() const
{
return m_state.units;
}
void GCodeAnalyzer::_set_global_positioning_type(GCodeAnalyzer::EPositioningType type)
{
m_state.global_positioning_type = type;
}
GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_global_positioning_type() const
{
return m_state.global_positioning_type;
}
void GCodeAnalyzer::_set_e_local_positioning_type(GCodeAnalyzer::EPositioningType type)
{
m_state.e_local_positioning_type = type;
}
GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_e_local_positioning_type() const
{
return m_state.e_local_positioning_type;
}
void GCodeAnalyzer::_set_extrusion_role(ExtrusionRole extrusion_role)
{
m_state.data.extrusion_role = extrusion_role;
}
ExtrusionRole GCodeAnalyzer::_get_extrusion_role() const
{
return m_state.data.extrusion_role;
}
void GCodeAnalyzer::_set_extruder_id(unsigned int id)
{
m_state.data.extruder_id = id;
}
unsigned int GCodeAnalyzer::_get_extruder_id() const
{
return m_state.data.extruder_id;
}
void GCodeAnalyzer::_set_mm3_per_mm(double value)
{
m_state.data.mm3_per_mm = value;
}
double GCodeAnalyzer::_get_mm3_per_mm() const
{
return m_state.data.mm3_per_mm;
}
void GCodeAnalyzer::_set_width(float width)
{
m_state.data.width = width;
}
float GCodeAnalyzer::_get_width() const
{
return m_state.data.width;
}
void GCodeAnalyzer::_set_height(float height)
{
m_state.data.height = height;
}
float GCodeAnalyzer::_get_height() const
{
return m_state.data.height;
}
void GCodeAnalyzer::_set_feedrate(float feedrate_mm_sec)
{
m_state.data.feedrate = feedrate_mm_sec;
}
float GCodeAnalyzer::_get_feedrate() const
{
return m_state.data.feedrate;
}
void GCodeAnalyzer::_set_axis_position(EAxis axis, float position)
{
m_state.position[axis] = position;
}
float GCodeAnalyzer::_get_axis_position(EAxis axis) const
{
return m_state.position[axis];
}
void GCodeAnalyzer::_reset_axes_position()
{
::memset((void*)m_state.position, 0, Num_Axis * sizeof(float));
}
void GCodeAnalyzer::_set_start_position(const Vec3d& position)
{
m_state.start_position = position;
}
const Vec3d& GCodeAnalyzer::_get_start_position() const
{
return m_state.start_position;
}
void GCodeAnalyzer::_set_start_extrusion(float extrusion)
{
m_state.start_extrusion = extrusion;
}
float GCodeAnalyzer::_get_start_extrusion() const
{
return m_state.start_extrusion;
}
float GCodeAnalyzer::_get_delta_extrusion() const
{
return _get_axis_position(E) - m_state.start_extrusion;
}
Vec3d GCodeAnalyzer::_get_end_position() const
{
return Vec3d(m_state.position[X], m_state.position[Y], m_state.position[Z]);
}
void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type)
{
// if type non mapped yet, map it
TypeToMovesMap::iterator it = m_moves_map.find(type);
if (it == m_moves_map.end())
it = m_moves_map.insert(TypeToMovesMap::value_type(type, GCodeMovesList())).first;
// store move
it->second.emplace_back(type, _get_extrusion_role(), _get_extruder_id(), _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), _get_start_position(), _get_end_position(), _get_delta_extrusion());
}
bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const
{
return ((int)erNone <= value) && (value <= (int)erMixed);
}
void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data)
{
struct Helper
{
static GCodePreviewData::Extrusion::Layer& get_layer_at_z(GCodePreviewData::Extrusion::LayersList& layers, float z)
{
for (GCodePreviewData::Extrusion::Layer& layer : layers)
{
// if layer found, return it
if (layer.z == z)
return layer;
}
// if layer not found, create and return it
layers.emplace_back(z, ExtrusionPaths());
return layers.back();
}
static void store_polyline(const Polyline& polyline, const Metadata& data, float z, GCodePreviewData& preview_data)
{
// if the polyline is valid, create the extrusion path from it and store it
if (polyline.is_valid())
{
ExtrusionPath path(data.extrusion_role, data.mm3_per_mm, data.width, data.height);
path.polyline = polyline;
path.feedrate = data.feedrate;
path.extruder_id = data.extruder_id;
get_layer_at_z(preview_data.extrusion.layers, z).paths.push_back(path);
}
}
};
TypeToMovesMap::iterator extrude_moves = m_moves_map.find(GCodeMove::Extrude);
if (extrude_moves == m_moves_map.end())
return;
Metadata data;
float z = FLT_MAX;
Polyline polyline;
Vec3d position(FLT_MAX, FLT_MAX, FLT_MAX);
float volumetric_rate = FLT_MAX;
GCodePreviewData::Range height_range;
GCodePreviewData::Range width_range;
GCodePreviewData::Range feedrate_range;
GCodePreviewData::Range volumetric_rate_range;
// constructs the polylines while traversing the moves
for (const GCodeMove& move : extrude_moves->second)
{
if ((data != move.data) || (z != move.start_position.z()) || (position != move.start_position) || (volumetric_rate != move.data.feedrate * (float)move.data.mm3_per_mm))
{
// store current polyline
polyline.remove_duplicate_points();
Helper::store_polyline(polyline, data, z, preview_data);
// reset current polyline
polyline = Polyline();
// add both vertices of the move
polyline.append(Point(scale_(move.start_position.x()), scale_(move.start_position.y())));
polyline.append(Point(scale_(move.end_position.x()), scale_(move.end_position.y())));
// update current values
data = move.data;
z = move.start_position.z();
volumetric_rate = move.data.feedrate * (float)move.data.mm3_per_mm;
height_range.update_from(move.data.height);
width_range.update_from(move.data.width);
feedrate_range.update_from(move.data.feedrate);
volumetric_rate_range.update_from(volumetric_rate);
}
else
// append end vertex of the move to current polyline
polyline.append(Point(scale_(move.end_position.x()), scale_(move.end_position.y())));
// update current values
position = move.end_position;
}
// store last polyline
polyline.remove_duplicate_points();
Helper::store_polyline(polyline, data, z, preview_data);
// updates preview ranges data
preview_data.ranges.height.update_from(height_range);
preview_data.ranges.width.update_from(width_range);
preview_data.ranges.feedrate.update_from(feedrate_range);
preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range);
}
void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data)
{
struct Helper
{
static void store_polyline(const Polyline3& polyline, GCodePreviewData::Travel::EType type, GCodePreviewData::Travel::Polyline::EDirection direction,
float feedrate, unsigned int extruder_id, GCodePreviewData& preview_data)
{
// if the polyline is valid, store it
if (polyline.is_valid())
preview_data.travel.polylines.emplace_back(type, direction, feedrate, extruder_id, polyline);
}
};
TypeToMovesMap::iterator travel_moves = m_moves_map.find(GCodeMove::Move);
if (travel_moves == m_moves_map.end())
return;
Polyline3 polyline;
Vec3d position(FLT_MAX, FLT_MAX, FLT_MAX);
GCodePreviewData::Travel::EType type = GCodePreviewData::Travel::Num_Types;
GCodePreviewData::Travel::Polyline::EDirection direction = GCodePreviewData::Travel::Polyline::Num_Directions;
float feedrate = FLT_MAX;
unsigned int extruder_id = -1;
GCodePreviewData::Range height_range;
GCodePreviewData::Range width_range;
GCodePreviewData::Range feedrate_range;
// constructs the polylines while traversing the moves
for (const GCodeMove& move : travel_moves->second)
{
GCodePreviewData::Travel::EType move_type = (move.delta_extruder < 0.0f) ? GCodePreviewData::Travel::Retract : ((move.delta_extruder > 0.0f) ? GCodePreviewData::Travel::Extrude : GCodePreviewData::Travel::Move);
GCodePreviewData::Travel::Polyline::EDirection move_direction = ((move.start_position.x() != move.end_position.x()) || (move.start_position.y() != move.end_position.y())) ? GCodePreviewData::Travel::Polyline::Generic : GCodePreviewData::Travel::Polyline::Vertical;
if ((type != move_type) || (direction != move_direction) || (feedrate != move.data.feedrate) || (position != move.start_position) || (extruder_id != move.data.extruder_id))
{
// store current polyline
polyline.remove_duplicate_points();
Helper::store_polyline(polyline, type, direction, feedrate, extruder_id, preview_data);
// reset current polyline
polyline = Polyline3();
// add both vertices of the move
polyline.append(Vec3crd(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())));
polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z())));
}
else
// append end vertex of the move to current polyline
polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z())));
// update current values
position = move.end_position;
type = move_type;
feedrate = move.data.feedrate;
extruder_id = move.data.extruder_id;
height_range.update_from(move.data.height);
width_range.update_from(move.data.width);
feedrate_range.update_from(move.data.feedrate);
}
// store last polyline
polyline.remove_duplicate_points();
Helper::store_polyline(polyline, type, direction, feedrate, extruder_id, preview_data);
// updates preview ranges data
preview_data.ranges.height.update_from(height_range);
preview_data.ranges.width.update_from(width_range);
preview_data.ranges.feedrate.update_from(feedrate_range);
}
void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data)
{
TypeToMovesMap::iterator retraction_moves = m_moves_map.find(GCodeMove::Retract);
if (retraction_moves == m_moves_map.end())
return;
for (const GCodeMove& move : retraction_moves->second)
{
// store position
Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height);
}
}
void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data)
{
TypeToMovesMap::iterator unretraction_moves = m_moves_map.find(GCodeMove::Unretract);
if (unretraction_moves == m_moves_map.end())
return;
for (const GCodeMove& move : unretraction_moves->second)
{
// store position
Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()));
preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height);
}
}
GCodePreviewData::Color operator + (const GCodePreviewData::Color& c1, const GCodePreviewData::Color& c2)
{
return GCodePreviewData::Color(clamp(0.0f, 1.0f, c1.rgba[0] + c2.rgba[0]),
clamp(0.0f, 1.0f, c1.rgba[1] + c2.rgba[1]),
clamp(0.0f, 1.0f, c1.rgba[2] + c2.rgba[2]),
clamp(0.0f, 1.0f, c1.rgba[3] + c2.rgba[3]));
}
GCodePreviewData::Color operator * (float f, const GCodePreviewData::Color& color)
{
return GCodePreviewData::Color(clamp(0.0f, 1.0f, f * color.rgba[0]),
clamp(0.0f, 1.0f, f * color.rgba[1]),
clamp(0.0f, 1.0f, f * color.rgba[2]),
clamp(0.0f, 1.0f, f * color.rgba[3]));
}
} // namespace Slic3r

View file

@ -0,0 +1,233 @@
#ifndef slic3r_GCode_Analyzer_hpp_
#define slic3r_GCode_Analyzer_hpp_
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "../ExtrusionEntity.hpp"
#include "Point.hpp"
#include "GCodeReader.hpp"
namespace Slic3r {
class GCodePreviewData;
class GCodeAnalyzer
{
public:
static const std::string Extrusion_Role_Tag;
static const std::string Mm3_Per_Mm_Tag;
static const std::string Width_Tag;
static const std::string Height_Tag;
static const double Default_mm3_per_mm;
static const float Default_Width;
static const float Default_Height;
enum EUnits : unsigned char
{
Millimeters,
Inches
};
enum EAxis : unsigned char
{
X,
Y,
Z,
E,
Num_Axis
};
enum EPositioningType : unsigned char
{
Absolute,
Relative
};
struct Metadata
{
ExtrusionRole extrusion_role;
unsigned int extruder_id;
double mm3_per_mm;
float width; // mm
float height; // mm
float feedrate; // mm/s
Metadata();
Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate);
bool operator != (const Metadata& other) const;
};
struct GCodeMove
{
enum EType : unsigned char
{
Noop,
Retract,
Unretract,
Tool_change,
Move,
Extrude,
Num_Types
};
EType type;
Metadata data;
Vec3d start_position;
Vec3d end_position;
float delta_extruder;
GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder);
GCodeMove(EType type, const Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder);
};
typedef std::vector<GCodeMove> GCodeMovesList;
typedef std::map<GCodeMove::EType, GCodeMovesList> TypeToMovesMap;
private:
struct State
{
EUnits units;
EPositioningType global_positioning_type;
EPositioningType e_local_positioning_type;
Metadata data;
Vec3d start_position = Vec3d::Zero();
float start_extrusion;
float position[Num_Axis];
};
private:
State m_state;
GCodeReader m_parser;
TypeToMovesMap m_moves_map;
// The output of process_layer()
std::string m_process_output;
public:
GCodeAnalyzer();
// Reinitialize the analyzer
void reset();
// Adds the gcode contained in the given string to the analysis and returns it after removing the workcodes
const std::string& process_gcode(const std::string& gcode);
// Calculates all data needed for gcode visualization
void calc_gcode_preview_data(GCodePreviewData& preview_data);
static bool is_valid_extrusion_role(ExtrusionRole role);
private:
// Processes the given gcode line
void _process_gcode_line(GCodeReader& reader, const GCodeReader::GCodeLine& line);
// Move
void _processG1(const GCodeReader::GCodeLine& line);
// Retract
void _processG10(const GCodeReader::GCodeLine& line);
// Unretract
void _processG11(const GCodeReader::GCodeLine& line);
// Firmware controlled Retract
void _processG22(const GCodeReader::GCodeLine& line);
// Firmware controlled Unretract
void _processG23(const GCodeReader::GCodeLine& line);
// Set to Absolute Positioning
void _processG90(const GCodeReader::GCodeLine& line);
// Set to Relative Positioning
void _processG91(const GCodeReader::GCodeLine& line);
// Set Position
void _processG92(const GCodeReader::GCodeLine& line);
// Set extruder to absolute mode
void _processM82(const GCodeReader::GCodeLine& line);
// Set extruder to relative mode
void _processM83(const GCodeReader::GCodeLine& line);
// Processes T line (Select Tool)
void _processT(const GCodeReader::GCodeLine& line);
// Processes the tags
// Returns true if any tag has been processed
bool _process_tags(const GCodeReader::GCodeLine& line);
// Processes extrusion role tag
void _process_extrusion_role_tag(const std::string& comment, size_t pos);
// Processes mm3_per_mm tag
void _process_mm3_per_mm_tag(const std::string& comment, size_t pos);
// Processes width tag
void _process_width_tag(const std::string& comment, size_t pos);
// Processes height tag
void _process_height_tag(const std::string& comment, size_t pos);
void _set_units(EUnits units);
EUnits _get_units() const;
void _set_global_positioning_type(EPositioningType type);
EPositioningType _get_global_positioning_type() const;
void _set_e_local_positioning_type(EPositioningType type);
EPositioningType _get_e_local_positioning_type() const;
void _set_extrusion_role(ExtrusionRole extrusion_role);
ExtrusionRole _get_extrusion_role() const;
void _set_extruder_id(unsigned int id);
unsigned int _get_extruder_id() const;
void _set_mm3_per_mm(double value);
double _get_mm3_per_mm() const;
void _set_width(float width);
float _get_width() const;
void _set_height(float height);
float _get_height() const;
void _set_feedrate(float feedrate_mm_sec);
float _get_feedrate() const;
void _set_axis_position(EAxis axis, float position);
float _get_axis_position(EAxis axis) const;
// Sets axes position to zero
void _reset_axes_position();
void _set_start_position(const Vec3d& position);
const Vec3d& _get_start_position() const;
void _set_start_extrusion(float extrusion);
float _get_start_extrusion() const;
float _get_delta_extrusion() const;
// Returns current xyz position (from m_state.position[])
Vec3d _get_end_position() const;
// Adds a new move with the given data
void _store_move(GCodeMove::EType type);
// Checks if the given int is a valid extrusion role (contained into enum ExtrusionRole)
bool _is_valid_extrusion_role(int value) const;
void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data);
void _calc_gcode_preview_travel(GCodePreviewData& preview_data);
void _calc_gcode_preview_retractions(GCodePreviewData& preview_data);
void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data);
};
} // namespace Slic3r
#endif /* slic3r_GCode_Analyzer_hpp_ */

View file

@ -0,0 +1,749 @@
#include "../GCode.hpp"
#include "CoolingBuffer.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <iostream>
#include <float.h>
#if 0
#define DEBUG
#define _DEBUG
#undef NDEBUG
#endif
#include <assert.h>
namespace Slic3r {
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen), m_current_extruder(0)
{
this->reset();
}
void CoolingBuffer::reset()
{
m_current_pos.assign(5, 0.f);
Vec3d pos = m_gcodegen.writer().get_position();
m_current_pos[0] = float(pos(0));
m_current_pos[1] = float(pos(1));
m_current_pos[2] = float(pos(2));
m_current_pos[4] = float(m_gcodegen.config().travel_speed.value);
}
struct CoolingLine
{
enum Type {
TYPE_SET_TOOL = 1 << 0,
TYPE_EXTRUDE_END = 1 << 1,
TYPE_BRIDGE_FAN_START = 1 << 2,
TYPE_BRIDGE_FAN_END = 1 << 3,
TYPE_G0 = 1 << 4,
TYPE_G1 = 1 << 5,
TYPE_ADJUSTABLE = 1 << 6,
TYPE_EXTERNAL_PERIMETER = 1 << 7,
// The line sets a feedrate.
TYPE_HAS_F = 1 << 8,
TYPE_WIPE = 1 << 9,
TYPE_G4 = 1 << 10,
TYPE_G92 = 1 << 11,
};
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
type(type), line_start(line_start), line_end(line_end),
length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {}
bool adjustable(bool slowdown_external_perimeters) const {
return (this->type & TYPE_ADJUSTABLE) &&
(! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) &&
this->time < this->time_max;
}
bool adjustable() const {
return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max;
}
size_t type;
// Start of this line at the G-code snippet.
size_t line_start;
// End of this line at the G-code snippet.
size_t line_end;
// XY Euclidian length of this segment.
float length;
// Current feedrate, possibly adjusted.
float feedrate;
// Current duration of this segment.
float time;
// Maximum duration of this segment.
float time_max;
// If marked with the "slowdown" flag, the line has been slowed down.
bool slowdown;
};
// Calculate the required per extruder time stretches.
struct PerExtruderAdjustments
{
// Calculate the total elapsed time per this extruder, adjusted for the slowdown.
float elapsed_time_total() {
float time_total = 0.f;
for (const CoolingLine &line : lines)
time_total += line.time;
return time_total;
}
// Calculate the total elapsed time when slowing down
// to the minimum extrusion feed rate defined for the current material.
float maximum_time_after_slowdown(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (const CoolingLine &line : lines)
if (line.adjustable(slowdown_external_perimeters)) {
if (line.time_max == FLT_MAX)
return FLT_MAX;
else
time_total += line.time_max;
} else
time_total += line.time;
return time_total;
}
// Calculate the adjustable part of the total time.
float adjustable_time(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (const CoolingLine &line : lines)
if (line.adjustable(slowdown_external_perimeters))
time_total += line.time;
return time_total;
}
// Calculate the non-adjustable part of the total time.
float non_adjustable_time(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (const CoolingLine &line : lines)
if (! line.adjustable(slowdown_external_perimeters))
time_total += line.time;
return time_total;
}
// Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material.
// Used by both proportional and non-proportional slow down.
float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) {
float time_total = 0.f;
for (CoolingLine &line : lines) {
if (line.adjustable(slowdown_external_perimeters)) {
assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
line.slowdown = true;
line.time = line.time_max;
line.feedrate = line.length / line.time;
}
time_total += line.time;
}
return time_total;
}
// Slow down each adjustable G-code line proportionally by a factor.
// Used by the proportional slow down.
float slow_down_proportional(float factor, bool slowdown_external_perimeters) {
assert(factor >= 1.f);
float time_total = 0.f;
for (CoolingLine &line : lines) {
if (line.adjustable(slowdown_external_perimeters)) {
line.slowdown = true;
line.time = std::min(line.time_max, line.time * factor);
line.feedrate = line.length / line.time;
}
time_total += line.time;
}
return time_total;
}
// Sort the lines, adjustable first, higher feedrate first.
// Used by non-proportional slow down.
void sort_lines_by_decreasing_feedrate() {
std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) {
bool adj1 = l1.adjustable();
bool adj2 = l2.adjustable();
return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1;
});
for (n_lines_adjustable = 0;
n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable();
++ n_lines_adjustable);
time_non_adjustable = 0.f;
for (size_t i = n_lines_adjustable; i < lines.size(); ++ i)
time_non_adjustable += lines[i].time;
}
// Calculate the maximum time stretch when slowing down to min_feedrate.
// Slowdown to min_feedrate shall be allowed for this extruder's material.
// Used by non-proportional slow down.
float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) {
float time_stretch = 0.f;
assert(this->min_print_speed < min_feedrate + EPSILON);
for (size_t i = 0; i < n_lines_adjustable; ++ i) {
const CoolingLine &line = lines[i];
if (line.feedrate > min_feedrate)
time_stretch += line.time * (line.feedrate / min_feedrate - 1.f);
}
return time_stretch;
}
// Slow down all adjustable lines down to min_feedrate.
// Slowdown to min_feedrate shall be allowed for this extruder's material.
// Used by non-proportional slow down.
void slow_down_to_feedrate(float min_feedrate) {
assert(this->min_print_speed < min_feedrate + EPSILON);
for (size_t i = 0; i < n_lines_adjustable; ++ i) {
CoolingLine &line = lines[i];
if (line.feedrate > min_feedrate) {
line.time *= std::max(1.f, line.feedrate / min_feedrate);
line.feedrate = min_feedrate;
line.slowdown = true;
}
}
}
// Extruder, for which the G-code will be adjusted.
unsigned int extruder_id = 0;
// Is the cooling slow down logic enabled for this extruder's material?
bool cooling_slow_down_enabled = false;
// Slow down the print down to min_print_speed if the total layer time is below slowdown_below_layer_time.
float slowdown_below_layer_time = 0.f;
// Minimum print speed allowed for this extruder.
float min_print_speed = 0.f;
// Parsed lines.
std::vector<CoolingLine> lines;
// The following two values are set by sort_lines_by_decreasing_feedrate():
// Number of adjustable lines, at the start of lines.
size_t n_lines_adjustable = 0;
// Non-adjustable time of lines starting with n_lines_adjustable.
float time_non_adjustable = 0;
// Current total time for this extruder.
float time_total = 0;
// Maximum time for this extruder, when the maximum slow down is applied.
float time_maximum = 0;
// Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable.
size_t idx_line_begin = 0;
size_t idx_line_end = 0;
};
std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id)
{
std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(gcode, m_current_pos);
float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments);
return this->apply_layer_cooldown(gcode, layer_id, layer_time_stretched, per_extruder_adjustments);
}
// Parse the layer G-code for the moves, which could be adjusted.
// Return the list of parsed lines, bucketed by an extruder.
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const
{
const FullPrintConfig &config = m_gcodegen.config();
const std::vector<Extruder> &extruders = m_gcodegen.writer().extruders();
unsigned int num_extruders = 0;
for (const Extruder &ex : extruders)
num_extruders = std::max(ex.id() + 1, num_extruders);
std::vector<PerExtruderAdjustments> per_extruder_adjustments(extruders.size());
std::vector<size_t> map_extruder_to_per_extruder_adjustment(num_extruders, 0);
for (size_t i = 0; i < extruders.size(); ++ i) {
PerExtruderAdjustments &adj = per_extruder_adjustments[i];
unsigned int extruder_id = extruders[i].id();
adj.extruder_id = extruder_id;
adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id);
adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id);
adj.min_print_speed = config.min_print_speed.get_at(extruder_id);
map_extruder_to_per_extruder_adjustment[extruder_id] = i;
}
const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
unsigned int current_extruder = m_current_extruder;
PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
const char *line_start = gcode.c_str();
const char *line_end = line_start;
const char extrusion_axis = config.get_extrusion_axis()[0];
// Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command
// for a sequence of extrusion moves.
size_t active_speed_modifier = size_t(-1);
for (; *line_start != 0; line_start = line_end)
{
while (*line_end != '\n' && *line_end != 0)
++ line_end;
// sline will not contain the trailing '\n'.
std::string sline(line_start, line_end);
// CoolingLine will contain the trailing '\n'.
if (*line_end == '\n')
++ line_end;
CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
if (boost::starts_with(sline, "G0 "))
line.type = CoolingLine::TYPE_G0;
else if (boost::starts_with(sline, "G1 "))
line.type = CoolingLine::TYPE_G1;
else if (boost::starts_with(sline, "G92 "))
line.type = CoolingLine::TYPE_G92;
if (line.type) {
// G0, G1 or G92
// Parse the G-code line.
std::vector<float> new_pos(current_pos);
const char *c = sline.data() + 3;
for (;;) {
// Skip whitespaces.
for (; *c == ' ' || *c == '\t'; ++ c);
if (*c == 0 || *c == ';')
break;
// Parse the axis.
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
if (axis != size_t(-1)) {
new_pos[axis] = float(atof(++c));
if (axis == 4) {
// Convert mm/min to mm/sec.
new_pos[4] /= 60.f;
if ((line.type & CoolingLine::TYPE_G92) == 0)
// This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
line.type |= CoolingLine::TYPE_HAS_F;
}
}
// Skip this word.
for (; *c != ' ' && *c != '\t' && *c != 0; ++ c);
}
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
bool wipe = boost::contains(sline, ";_WIPE");
if (external_perimeter)
line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER;
if (wipe)
line.type |= CoolingLine::TYPE_WIPE;
if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) {
line.type |= CoolingLine::TYPE_ADJUSTABLE;
active_speed_modifier = adjustment->lines.size();
}
if ((line.type & CoolingLine::TYPE_G92) == 0) {
// G0 or G1. Calculate the duration.
if (config.use_relative_e_distances.value)
// Reset extruder accumulator.
current_pos[3] = 0.f;
float dif[4];
for (size_t i = 0; i < 4; ++ i)
dif[i] = new_pos[i] - current_pos[i];
float dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
float dxyz2 = dxy2 + dif[2] * dif[2];
if (dxyz2 > 0.f) {
// Movement in xyz, calculate time from the xyz Euclidian distance.
line.length = sqrt(dxyz2);
} else if (std::abs(dif[3]) > 0.f) {
// Movement in the extruder axis.
line.length = std::abs(dif[3]);
}
line.feedrate = new_pos[4];
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
if (line.length > 0)
line.time = line.length / line.feedrate;
line.time_max = line.time;
if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) {
// Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
CoolingLine &sm = adjustment->lines[active_speed_modifier];
assert(sm.feedrate > 0.f);
sm.length += line.length;
sm.time += line.time;
if (sm.time_max != FLT_MAX) {
if (line.time_max == FLT_MAX)
sm.time_max = FLT_MAX;
else
sm.time_max += line.time_max;
}
// Don't store this line.
line.type = 0;
}
}
current_pos = std::move(new_pos);
} else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
line.type = CoolingLine::TYPE_EXTRUDE_END;
active_speed_modifier = size_t(-1);
} else if (boost::starts_with(sline, toolchange_prefix)) {
// Switch the tool.
line.type = CoolingLine::TYPE_SET_TOOL;
unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size());
if (new_extruder != current_extruder) {
current_extruder = new_extruder;
adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
}
} else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) {
line.type = CoolingLine::TYPE_BRIDGE_FAN_START;
} else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) {
line.type = CoolingLine::TYPE_BRIDGE_FAN_END;
} else if (boost::starts_with(sline, "G4 ")) {
// Parse the wait time.
line.type = CoolingLine::TYPE_G4;
size_t pos_S = sline.find('S', 3);
size_t pos_P = sline.find('P', 3);
line.time = line.time_max = float(
(pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
(pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
}
if (line.type != 0)
adjustment->lines.emplace_back(std::move(line));
}
return per_extruder_adjustments;
}
// Slow down an extruder range proportionally down to slowdown_below_layer_time.
// Return the total time for the complete layer.
static inline float extruder_range_slow_down_proportional(
std::vector<PerExtruderAdjustments*>::iterator it_begin,
std::vector<PerExtruderAdjustments*>::iterator it_end,
// Elapsed time for the extruders already processed.
float elapsed_time_total0,
// Initial total elapsed time before slow down.
float elapsed_time_before_slowdown,
// Target time for the complete layer (all extruders applied).
float slowdown_below_layer_time)
{
// Total layer time after the slow down has been applied.
float total_after_slowdown = elapsed_time_before_slowdown;
// Now decide, whether the external perimeters shall be slowed down as well.
float max_time_nep = elapsed_time_total0;
for (auto it = it_begin; it != it_end; ++ it)
max_time_nep += (*it)->maximum_time_after_slowdown(false);
if (max_time_nep > slowdown_below_layer_time) {
// It is sufficient to slow down the non-external perimeter moves to reach the target layer time.
// Slow down the non-external perimeters proportionally.
float non_adjustable_time = elapsed_time_total0;
for (auto it = it_begin; it != it_end; ++ it)
non_adjustable_time += (*it)->non_adjustable_time(false);
// The following step is a linear programming task due to the minimum movement speeds of the print moves.
// Run maximum 5 iterations until a good enough approximation is reached.
for (size_t iter = 0; iter < 5; ++ iter) {
float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
assert(factor > 1.f);
total_after_slowdown = elapsed_time_total0;
for (auto it = it_begin; it != it_end; ++ it)
total_after_slowdown += (*it)->slow_down_proportional(factor, false);
if (total_after_slowdown > 0.95f * slowdown_below_layer_time)
break;
}
} else {
// Slow down everything. First slow down the non-external perimeters to maximum.
for (auto it = it_begin; it != it_end; ++ it)
(*it)->slowdown_to_minimum_feedrate(false);
// Slow down the external perimeters proportionally.
float non_adjustable_time = elapsed_time_total0;
for (auto it = it_begin; it != it_end; ++ it)
non_adjustable_time += (*it)->non_adjustable_time(true);
for (size_t iter = 0; iter < 5; ++ iter) {
float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
assert(factor > 1.f);
total_after_slowdown = elapsed_time_total0;
for (auto it = it_begin; it != it_end; ++ it)
total_after_slowdown += (*it)->slow_down_proportional(factor, true);
if (total_after_slowdown > 0.95f * slowdown_below_layer_time)
break;
}
}
return total_after_slowdown;
}
// Slow down an extruder range to slowdown_below_layer_time.
// Return the total time for the complete layer.
static inline void extruder_range_slow_down_non_proportional(
std::vector<PerExtruderAdjustments*>::iterator it_begin,
std::vector<PerExtruderAdjustments*>::iterator it_end,
float time_stretch)
{
// Slow down. Try to equalize the feedrates.
std::vector<PerExtruderAdjustments*> by_min_print_speed(it_begin, it_end);
// Find the next highest adjustable feedrate among the extruders.
float feedrate = 0;
for (PerExtruderAdjustments *adj : by_min_print_speed) {
adj->idx_line_begin = 0;
adj->idx_line_end = 0;
assert(adj->idx_line_begin < adj->n_lines_adjustable);
if (adj->lines[adj->idx_line_begin].feedrate > feedrate)
feedrate = adj->lines[adj->idx_line_begin].feedrate;
}
assert(feedrate > 0.f);
// Sort by min_print_speed, maximum speed first.
std::sort(by_min_print_speed.begin(), by_min_print_speed.end(),
[](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; });
// Slow down, fast moves first.
for (;;) {
// For each extruder, find the span of lines with a feedrate close to feedrate.
for (PerExtruderAdjustments *adj : by_min_print_speed) {
for (adj->idx_line_end = adj->idx_line_begin;
adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON;
++ adj->idx_line_end) ;
}
// Find the next highest adjustable feedrate among the extruders.
float feedrate_next = 0.f;
for (PerExtruderAdjustments *adj : by_min_print_speed)
if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next)
feedrate_next = adj->lines[adj->idx_line_end].feedrate;
// Slow down, limited by max(feedrate_next, min_print_speed).
for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) {
// Slow down at most by time_stretch.
if ((*adj)->min_print_speed == 0.f) {
// All the adjustable speeds are now lowered to the same speed,
// and the minimum speed is set to zero.
float time_adjustable = 0.f;
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
time_adjustable += (*it)->adjustable_time(true);
float rate = (time_adjustable + time_stretch) / time_adjustable;
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
(*it)->slow_down_proportional(rate, true);
return;
} else {
float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed);
bool done = false;
float time_stretch_max = 0.f;
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit);
if (time_stretch_max >= time_stretch) {
feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max;
done = true;
} else
time_stretch -= time_stretch_max;
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
(*it)->slow_down_to_feedrate(feedrate_limit);
if (done)
return;
}
// Skip the other extruders with nearly the same min_print_speed, as they have been processed already.
auto next = adj;
for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next);
adj = next;
}
if (feedrate_next == 0.f)
// There are no other extrusions available for slow down.
break;
for (PerExtruderAdjustments *adj : by_min_print_speed) {
adj->idx_line_begin = adj->idx_line_end;
feedrate = feedrate_next;
}
}
}
// Calculate slow down for all the extruders.
float CoolingBuffer::calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
{
// Sort the extruders by an increasing slowdown_below_layer_time.
// The layers with a lower slowdown_below_layer_time are slowed down
// together with all the other layers with slowdown_below_layer_time above.
std::vector<PerExtruderAdjustments*> by_slowdown_time;
by_slowdown_time.reserve(per_extruder_adjustments.size());
// Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time).
// Collect total print time of non-adjustable extruders.
float elapsed_time_total0 = 0.f;
for (PerExtruderAdjustments &adj : per_extruder_adjustments) {
// Curren total time for this extruder.
adj.time_total = adj.elapsed_time_total();
// Maximum time for this extruder, when all extrusion moves are slowed down to min_extrusion_speed.
adj.time_maximum = adj.maximum_time_after_slowdown(true);
if (adj.cooling_slow_down_enabled && adj.lines.size() > 0) {
by_slowdown_time.emplace_back(&adj);
if (! m_cooling_logic_proportional)
// sorts the lines, also sets adj.time_non_adjustable
adj.sort_lines_by_decreasing_feedrate();
} else
elapsed_time_total0 += adj.elapsed_time_total();
}
std::sort(by_slowdown_time.begin(), by_slowdown_time.end(),
[](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2)
{ return adj1->slowdown_below_layer_time < adj2->slowdown_below_layer_time; });
for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++ cur_begin) {
PerExtruderAdjustments &adj = *(*cur_begin);
// Calculate the current adjusted elapsed_time_total over the non-finalized extruders.
float total = elapsed_time_total0;
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
total += (*it)->time_total;
float slowdown_below_layer_time = adj.slowdown_below_layer_time * 1.001f;
if (total > slowdown_below_layer_time) {
// The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything.
} else {
// Adjust this and all the following (higher config.slowdown_below_layer_time) extruders.
// Sum maximum slow down time as if everything was slowed down including the external perimeters.
float max_time = elapsed_time_total0;
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
max_time += (*it)->time_maximum;
if (max_time > slowdown_below_layer_time) {
if (m_cooling_logic_proportional)
extruder_range_slow_down_proportional(cur_begin, by_slowdown_time.end(), elapsed_time_total0, total, slowdown_below_layer_time);
else
extruder_range_slow_down_non_proportional(cur_begin, by_slowdown_time.end(), slowdown_below_layer_time - total);
} else {
// Slow down to maximum possible.
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
(*it)->slowdown_to_minimum_feedrate(true);
}
}
elapsed_time_total0 += adj.elapsed_time_total();
}
return elapsed_time_total0;
}
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
// Returns the adjusted G-code.
std::string CoolingBuffer::apply_layer_cooldown(
// Source G-code for the current layer.
const std::string &gcode,
// ID of the current layer, used to disable fan for the first n layers.
size_t layer_id,
// Total time of this layer after slow down, used to control the fan.
float layer_time,
// Per extruder list of G-code lines and their cool down attributes.
std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
{
// First sort the adjustment lines by of multiple extruders by their position in the source G-code.
std::vector<const CoolingLine*> lines;
{
size_t n_lines = 0;
for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
n_lines += adj.lines.size();
lines.reserve(n_lines);
for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
for (const CoolingLine &line : adj.lines)
lines.emplace_back(&line);
std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } );
}
// Second generate the adjusted G-code.
std::string new_gcode;
new_gcode.reserve(gcode.size() * 2);
int fan_speed = -1;
bool bridge_fan_control = false;
int bridge_fan_speed = 0;
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() {
const FullPrintConfig &config = m_gcodegen.config();
#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder)
int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0;
if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) {
int max_fan_speed = EXTRUDER_CONFIG(max_fan_speed);
float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time));
float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time));
if (EXTRUDER_CONFIG(cooling)) {
if (layer_time < slowdown_below_layer_time) {
// Layer time very short. Enable the fan to a full throttle.
fan_speed_new = max_fan_speed;
} else if (layer_time < fan_below_layer_time) {
// Layer time quite short. Enable the fan proportionally according to the current layer time.
assert(layer_time >= slowdown_below_layer_time);
double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time);
fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5);
}
}
bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed);
#undef EXTRUDER_CONFIG
bridge_fan_control = bridge_fan_speed > fan_speed_new;
} else {
bridge_fan_control = false;
bridge_fan_speed = 0;
fan_speed_new = 0;
}
if (fan_speed_new != fan_speed) {
fan_speed = fan_speed_new;
new_gcode += m_gcodegen.writer().set_fan(fan_speed);
}
};
const char *pos = gcode.c_str();
int current_feedrate = 0;
const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
change_extruder_set_fan();
for (const CoolingLine *line : lines) {
const char *line_start = gcode.c_str() + line->line_start;
const char *line_end = gcode.c_str() + line->line_end;
if (line_start > pos)
new_gcode.append(pos, line_start - pos);
if (line->type & CoolingLine::TYPE_SET_TOOL) {
unsigned int new_extruder = (unsigned int)atoi(line_start + toolchange_prefix.size());
if (new_extruder != m_current_extruder) {
m_current_extruder = new_extruder;
change_extruder_set_fan();
}
new_gcode.append(line_start, line_end - line_start);
} else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) {
if (bridge_fan_control)
new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true);
} else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) {
if (bridge_fan_control)
new_gcode += m_gcodegen.writer().set_fan(fan_speed, true);
} else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
// Just remove this comment.
} else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {
// Find the start of a comment, or roll to the end of line.
const char *end = line_start;
for (; end < line_end && *end != ';'; ++ end);
// Find the 'F' word.
const char *fpos = strstr(line_start + 2, " F") + 2;
int new_feedrate = current_feedrate;
bool modify = false;
assert(fpos != nullptr);
if (line->slowdown) {
modify = true;
new_feedrate = int(floor(60. * line->feedrate + 0.5));
} else {
new_feedrate = atoi(fpos);
if (new_feedrate != current_feedrate) {
// Append the line without the comment.
new_gcode.append(line_start, end - line_start);
current_feedrate = new_feedrate;
} else if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) {
// Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment.
end = line_end;
} else {
// Remove the feedrate from the G0/G1 line.
modify = true;
}
}
if (modify) {
if (new_feedrate != current_feedrate) {
// Replace the feedrate.
new_gcode.append(line_start, fpos - line_start);
current_feedrate = new_feedrate;
char buf[64];
sprintf(buf, "%d", int(current_feedrate));
new_gcode += buf;
} else {
// Remove the feedrate word.
const char *f = fpos;
// Roll the pointer before the 'F' word.
for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f);
// Append up to the F word, without the trailing whitespace.
new_gcode.append(line_start, f - line_start + 1);
}
// Skip the non-whitespaces of the F parameter up the comment or end of line.
for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos);
// Append the rest of the line without the comment.
if (fpos < end)
new_gcode.append(fpos, end - fpos);
// There should never be an empty G1 statement emited by the filter. Such lines should be removed completely.
assert(new_gcode.size() < 4 || new_gcode.substr(new_gcode.size() - 4) != "G1 \n");
}
// Process the rest of the line.
if (end < line_end) {
if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) {
// Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
std::string comment(end, line_end);
boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER)
boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
if (line->type & CoolingLine::TYPE_WIPE)
boost::replace_all(comment, ";_WIPE", "");
new_gcode += comment;
} else {
// Just attach the rest of the source line.
new_gcode.append(end, line_end - end);
}
}
} else {
new_gcode.append(line_start, line_end - line_start);
}
pos = line_end;
}
const char *gcode_end = gcode.c_str() + gcode.size();
if (pos < gcode_end)
new_gcode.append(pos, gcode_end - pos);
return new_gcode;
}
} // namespace Slic3r

View file

@ -0,0 +1,53 @@
#ifndef slic3r_CoolingBuffer_hpp_
#define slic3r_CoolingBuffer_hpp_
#include "libslic3r.h"
#include <map>
#include <string>
namespace Slic3r {
class GCode;
class Layer;
class PerExtruderAdjustments;
// A standalone G-code filter, to control cooling of the print.
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
// and the print is modified to stretch over a minimum layer time.
//
// The simple it sounds, the actual implementation is significantly more complex.
// Namely, for a multi-extruder print, each material may require a different cooling logic.
// For example, some materials may not like to print too slowly, while with some materials
// we may slow down significantly.
//
class CoolingBuffer {
public:
CoolingBuffer(GCode &gcodegen);
void reset();
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
std::string process_layer(const std::string &gcode, size_t layer_id);
GCode* gcodegen() { return &m_gcodegen; }
private:
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const;
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
// Returns the adjusted G-code.
std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
GCode& m_gcodegen;
std::string m_gcode;
// Internal data.
// X,Y,Z,E,F
std::vector<char> m_axis;
std::vector<float> m_current_pos;
unsigned int m_current_extruder;
// Old logic: proportional.
bool m_cooling_logic_proportional = false;
};
}
#endif

View file

@ -0,0 +1,60 @@
#include "PostProcessor.hpp"
#if 1
//#ifdef WIN32
namespace Slic3r {
//FIXME Ignore until we include boost::process
void run_post_process_scripts(const std::string &path, const PrintConfig &config)
{
}
} // namespace Slic3r
#else
#include <boost/process/system.hpp>
namespace Slic3r {
void run_post_process_scripts(const std::string &path, const PrintConfig &config)
{
if (config.post_process.values.empty())
return;
config.setenv_();
for (std::string script: config.post_process.values) {
// Ignore empty post processing script lines.
boost::trim(script);
if (script.empty())
continue;
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
if (! boost::filesystem::exists(boost::filesystem::path(path)))
throw std::runtime_exception(std::string("The configured post-processing script does not exist: ") + path);
#ifndef WIN32
file_status fs = boost::filesystem::status(path);
//FIXME test if executible by the effective UID / GID.
// throw std::runtime_exception(std::string("The configured post-processing script is not executable: check permissions. ") + path));
#endif
int result = 0;
#ifdef WIN32
if (boost::iends_with(file, ".gcode")) {
// The current process may be slic3r.exe or slic3r-console.exe.
// Find the path of the process:
wchar_t wpath_exe[_MAX_PATH + 1];
::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
boost::filesystem::path path_exe(wpath_exe);
// Replace it with the current perl interpreter.
result = boost::process::system((path_exe.parent_path() / "perl5.24.0.exe").string(), script, output_file);
} else
#else
result = boost::process::system(script, output_file);
#endif
if (result < 0)
BOOST_LOG_TRIVIAL(error) << "Script " << script << " on file " << path << " failed. Negative error code returned.";
}
}
} // namespace Slic3r
#endif

View file

@ -0,0 +1,15 @@
#ifndef slic3r_GCode_PostProcessor_hpp_
#define slic3r_GCode_PostProcessor_hpp_
#include <string>
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
namespace Slic3r {
extern void run_post_process_scripts(const std::string &path, const PrintConfig &config);
} // namespace Slic3r
#endif /* slic3r_GCode_PostProcessor_hpp_ */

View file

@ -0,0 +1,621 @@
#include <memory.h>
#include <string.h>
#include <float.h>
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "PressureEqualizer.hpp"
namespace Slic3r {
PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig *config) :
m_config(config)
{
reset();
}
PressureEqualizer::~PressureEqualizer()
{
}
void PressureEqualizer::reset()
{
circular_buffer_pos = 0;
circular_buffer_size = 100;
circular_buffer_items = 0;
circular_buffer.assign(circular_buffer_size, GCodeLine());
// Preallocate some data, so that output_buffer.data() will return an empty string.
output_buffer.assign(32, 0);
output_buffer_length = 0;
m_current_extruder = 0;
// Zero the position of the XYZE axes + the current feed
memset(m_current_pos, 0, sizeof(float) * 5);
m_current_extrusion_role = erNone;
// Expect the first command to fill the nozzle (deretract).
m_retracted = true;
// Calculate filamet crossections for the multiple extruders.
m_filament_crossections.clear();
for (size_t i = 0; i < m_config->filament_diameter.values.size(); ++ i) {
double r = m_config->filament_diameter.values[i];
double a = 0.25f*M_PI*r*r;
m_filament_crossections.push_back(float(a));
}
m_max_segment_length = 20.f;
// Volumetric rate of a 0.45mm x 0.2mm extrusion at 60mm/s XY movement: 0.45*0.2*60*60=5.4*60 = 324 mm^3/min
// Volumetric rate of a 0.45mm x 0.2mm extrusion at 20mm/s XY movement: 0.45*0.2*20*60=1.8*60 = 108 mm^3/min
// Slope of the volumetric rate, changing from 20mm/s to 60mm/s over 2 seconds: (5.4-1.8)*60*60/2=60*60*1.8 = 6480 mm^3/min^2 = 1.8 mm^3/s^2
m_max_volumetric_extrusion_rate_slope_positive = (m_config == NULL) ? 6480.f :
m_config->max_volumetric_extrusion_rate_slope_positive.value * 60.f * 60.f;
m_max_volumetric_extrusion_rate_slope_negative = (m_config == NULL) ? 6480.f :
m_config->max_volumetric_extrusion_rate_slope_negative.value * 60.f * 60.f;
for (size_t i = 0; i < numExtrusionRoles; ++ i) {
m_max_volumetric_extrusion_rate_slopes[i].negative = m_max_volumetric_extrusion_rate_slope_negative;
m_max_volumetric_extrusion_rate_slopes[i].positive = m_max_volumetric_extrusion_rate_slope_positive;
}
// Don't regulate the pressure in infill.
m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].negative = 0;
m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].positive = 0;
// Don't regulate the pressure in gap fill.
m_max_volumetric_extrusion_rate_slopes[erGapFill].negative = 0;
m_max_volumetric_extrusion_rate_slopes[erGapFill].positive = 0;
m_stat.reset();
line_idx = 0;
}
const char* PressureEqualizer::process(const char *szGCode, bool flush)
{
// Reset length of the output_buffer.
output_buffer_length = 0;
if (szGCode != 0) {
const char *p = szGCode;
while (*p != 0) {
// Find end of the line.
const char *endl = p;
// Slic3r always generates end of lines in a Unix style.
for (; *endl != 0 && *endl != '\n'; ++ endl) ;
if (circular_buffer_items == circular_buffer_size)
// Buffer is full. Push out the oldest line.
output_gcode_line(circular_buffer[circular_buffer_pos]);
else
++ circular_buffer_items;
// Process a G-code line, store it into the provided GCodeLine object.
size_t idx_tail = circular_buffer_pos;
circular_buffer_pos = circular_buffer_idx_next(circular_buffer_pos);
if (! process_line(p, endl - p, circular_buffer[idx_tail])) {
// The line has to be forgotten. It contains comment marks, which shall be
// filtered out of the target g-code.
circular_buffer_pos = idx_tail;
-- circular_buffer_items;
}
p = endl;
if (*p == '\n')
++ p;
}
}
if (flush) {
// Flush the remaining valid lines of the circular buffer.
for (size_t idx = circular_buffer_idx_head(); circular_buffer_items > 0; -- circular_buffer_items) {
output_gcode_line(circular_buffer[idx]);
if (++ idx == circular_buffer_size)
idx = 0;
}
// Reset the index pointer.
assert(circular_buffer_items == 0);
circular_buffer_pos = 0;
#if 1
printf("Statistics: \n");
printf("Minimum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_min);
printf("Maximum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_max);
if (m_stat.extrusion_length > 0)
m_stat.volumetric_extrusion_rate_avg /= m_stat.extrusion_length;
printf("Average volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_avg);
m_stat.reset();
#endif
}
return output_buffer.data();
}
// Is a white space?
static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; }
// Is it an end of line? Consider a comment to be an end of line as well.
static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; };
// Is it a white space or end of line?
static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); };
// Eat whitespaces.
static void eatws(const char *&line)
{
while (is_ws(*line))
++ line;
}
// Parse an int starting at the current position of a line.
// If succeeded, the line pointer is advanced.
static inline int parse_int(const char *&line)
{
char *endptr = NULL;
long result = strtol(line, &endptr, 10);
if (endptr == NULL || !is_ws_or_eol(*endptr))
throw std::runtime_error("PressureEqualizer: Error parsing an int");
line = endptr;
return int(result);
};
// Parse an int starting at the current position of a line.
// If succeeded, the line pointer is advanced.
static inline float parse_float(const char *&line)
{
char *endptr = NULL;
float result = strtof(line, &endptr);
if (endptr == NULL || !is_ws_or_eol(*endptr))
throw std::runtime_error("PressureEqualizer: Error parsing a float");
line = endptr;
return result;
};
#define EXTRUSION_ROLE_TAG ";_EXTRUSION_ROLE:"
bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLine &buf)
{
if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) {
line += strlen(EXTRUSION_ROLE_TAG);
int role = atoi(line);
m_current_extrusion_role = ExtrusionRole(role);
++ line_idx;
return false;
}
// Set the type, copy the line to the buffer.
buf.type = GCODELINETYPE_OTHER;
buf.modified = false;
if (buf.raw.size() < len + 1)
buf.raw.assign(line, line + len + 1);
else
memcpy(buf.raw.data(), line, len);
buf.raw[len] = 0;
buf.raw_length = len;
memcpy(buf.pos_start, m_current_pos, sizeof(float)*5);
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
memset(buf.pos_provided, 0, 5);
buf.volumetric_extrusion_rate = 0.f;
buf.volumetric_extrusion_rate_start = 0.f;
buf.volumetric_extrusion_rate_end = 0.f;
buf.max_volumetric_extrusion_rate_slope_positive = 0.f;
buf.max_volumetric_extrusion_rate_slope_negative = 0.f;
buf.extrusion_role = m_current_extrusion_role;
// Parse the G-code line, store the result into the buf.
switch (toupper(*line ++)) {
case 'G': {
int gcode = parse_int(line);
eatws(line);
switch (gcode) {
case 0:
case 1:
{
// G0, G1: A FFF 3D printer does not make a difference between the two.
float new_pos[5];
memcpy(new_pos, m_current_pos, sizeof(float)*5);
bool changed[5] = { false, false, false, false, false };
while (!is_eol(*line)) {
char axis = toupper(*line++);
int i = -1;
switch (axis) {
case 'X':
case 'Y':
case 'Z':
i = axis - 'X';
break;
case 'E':
i = 3;
break;
case 'F':
i = 4;
break;
default:
assert(false);
}
if (i == -1)
throw std::runtime_error(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
buf.pos_provided[i] = true;
new_pos[i] = parse_float(line);
if (i == 3 && m_config->use_relative_e_distances.value)
new_pos[i] += m_current_pos[i];
changed[i] = new_pos[i] != m_current_pos[i];
eatws(line);
}
if (changed[3]) {
// Extrusion, retract or unretract.
float diff = new_pos[3] - m_current_pos[3];
if (diff < 0) {
buf.type = GCODELINETYPE_RETRACT;
m_retracted = true;
} else if (! changed[0] && ! changed[1] && ! changed[2]) {
// assert(m_retracted);
buf.type = GCODELINETYPE_UNRETRACT;
m_retracted = false;
} else {
assert(changed[0] || changed[1]);
// Moving in XY plane.
buf.type = GCODELINETYPE_EXTRUDE;
// Calculate the volumetric extrusion rate.
float diff[4];
for (size_t i = 0; i < 4; ++ i)
diff[i] = new_pos[i] - m_current_pos[i];
// volumetric extrusion rate = A_filament * F_xyz * L_e / L_xyz [mm^3/min]
float len2 = diff[0]*diff[0]+diff[1]*diff[1]+diff[2]*diff[2];
float rate = m_filament_crossections[m_current_extruder] * new_pos[4] * sqrt((diff[3]*diff[3])/len2);
buf.volumetric_extrusion_rate = rate;
buf.volumetric_extrusion_rate_start = rate;
buf.volumetric_extrusion_rate_end = rate;
m_stat.update(rate, sqrt(len2));
if (rate < 40.f) {
printf("Extremely low flow rate: %f. Line %d, Length: %f, extrusion: %f Old position: (%f, %f, %f), new position: (%f, %f, %f)\n",
rate,
int(line_idx),
sqrt(len2), sqrt((diff[3]*diff[3])/len2),
m_current_pos[0], m_current_pos[1], m_current_pos[2],
new_pos[0], new_pos[1], new_pos[2]);
}
}
} else if (changed[0] || changed[1] || changed[2]) {
// Moving without extrusion.
buf.type = GCODELINETYPE_MOVE;
}
memcpy(m_current_pos, new_pos, sizeof(float) * 5);
break;
}
case 92:
{
// G92 : Set Position
// Set a logical coordinate position to a new value without actually moving the machine motors.
// Which axes to set?
bool set = false;
while (!is_eol(*line)) {
char axis = toupper(*line++);
switch (axis) {
case 'X':
case 'Y':
case 'Z':
m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
set = true;
break;
case 'E':
m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
set = true;
break;
default:
throw std::runtime_error(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
}
eatws(line);
}
assert(set);
break;
}
case 10:
case 22:
// Firmware retract.
buf.type = GCODELINETYPE_RETRACT;
m_retracted = true;
break;
case 11:
case 23:
// Firmware unretract.
buf.type = GCODELINETYPE_UNRETRACT;
m_retracted = false;
break;
default:
// Ignore the rest.
break;
}
break;
}
case 'M': {
int mcode = parse_int(line);
eatws(line);
switch (mcode) {
default:
// Ignore the rest of the M-codes.
break;
}
break;
}
case 'T':
{
// Activate an extruder head.
int new_extruder = parse_int(line);
if (new_extruder != m_current_extruder) {
m_current_extruder = new_extruder;
m_retracted = true;
buf.type = GCODELINETYPE_TOOL_CHANGE;
} else {
buf.type = GCODELINETYPE_NOOP;
}
break;
}
}
buf.extruder_id = m_current_extruder;
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
adjust_volumetric_rate();
++ line_idx;
return true;
}
void PressureEqualizer::output_gcode_line(GCodeLine &line)
{
if (! line.modified) {
push_to_output(line.raw.data(), line.raw_length, true);
return;
}
// The line was modified.
// Find the comment.
const char *comment = line.raw.data();
while (*comment != ';' && *comment != 0) ++comment;
if (*comment != ';')
comment = NULL;
// Emit the line with lowered extrusion rates.
float l2 = line.dist_xyz2();
float l = sqrt(l2);
size_t nSegments = size_t(ceil(l / m_max_segment_length));
if (nSegments == 1) {
// Just update this segment.
push_line_to_output(line, line.feedrate() * line.volumetric_correction_avg(), comment);
} else {
bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end;
// Update the initial and final feed rate values.
line.pos_start[4] = line.volumetric_extrusion_rate_start * line.pos_end[4] / line.volumetric_extrusion_rate;
line.pos_end [4] = line.volumetric_extrusion_rate_end * line.pos_end[4] / line.volumetric_extrusion_rate;
float feed_avg = 0.5f * (line.pos_start[4] + line.pos_end[4]);
// Limiting volumetric extrusion rate slope for this segment.
float max_volumetric_extrusion_rate_slope = accelerating ?
line.max_volumetric_extrusion_rate_slope_positive : line.max_volumetric_extrusion_rate_slope_negative;
// Total time for the segment, corrected for the possibly lowered volumetric feed rate,
// if accelerating / decelerating over the complete segment.
float t_total = line.dist_xyz() / feed_avg;
// Time of the acceleration / deceleration part of the segment, if accelerating / decelerating
// with the maximum volumetric extrusion rate slope.
float t_acc = 0.5f * (line.volumetric_extrusion_rate_start + line.volumetric_extrusion_rate_end) / max_volumetric_extrusion_rate_slope;
float l_acc = l;
float l_steady = 0.f;
if (t_acc < t_total) {
// One may achieve higher print speeds if part of the segment is not speed limited.
float l_acc = t_acc * feed_avg;
float l_steady = l - l_acc;
if (l_steady < 0.5f * m_max_segment_length) {
l_acc = l;
l_steady = 0.f;
} else
nSegments = size_t(ceil(l_acc / m_max_segment_length));
}
float pos_start[5];
float pos_end [5];
float pos_end2 [4];
memcpy(pos_start, line.pos_start, sizeof(float)*5);
memcpy(pos_end , line.pos_end , sizeof(float)*5);
if (l_steady > 0.f) {
// There will be a steady feed segment emitted.
if (accelerating) {
// Prepare the final steady feed rate segment.
memcpy(pos_end2, pos_end, sizeof(float)*4);
float t = l_acc / l;
for (int i = 0; i < 4; ++ i) {
pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t;
line.pos_provided[i] = true;
}
} else {
// Emit the steady feed rate segment.
float t = l_steady / l;
for (int i = 0; i < 4; ++ i) {
line.pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t;
line.pos_provided[i] = true;
}
push_line_to_output(line, pos_start[4], comment);
comment = NULL;
memcpy(line.pos_start, line.pos_end, sizeof(float)*5);
memcpy(pos_start, line.pos_end, sizeof(float)*5);
}
}
// Split the segment into pieces.
for (size_t i = 1; i < nSegments; ++ i) {
float t = float(i) / float(nSegments);
for (size_t j = 0; j < 4; ++ j) {
line.pos_end[j] = pos_start[j] + (pos_end[j] - pos_start[j]) * t;
line.pos_provided[j] = true;
}
// Interpolate the feed rate at the center of the segment.
push_line_to_output(line, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment);
comment = NULL;
memcpy(line.pos_start, line.pos_end, sizeof(float)*5);
}
if (l_steady > 0.f && accelerating) {
for (int i = 0; i < 4; ++ i) {
line.pos_end[i] = pos_end2[i];
line.pos_provided[i] = true;
}
push_line_to_output(line, pos_end[4], comment);
}
}
}
void PressureEqualizer::adjust_volumetric_rate()
{
if (circular_buffer_items < 2)
return;
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
const size_t idx_head = circular_buffer_idx_head();
const size_t idx_tail = circular_buffer_idx_prev(circular_buffer_idx_tail());
size_t idx = idx_tail;
if (idx == idx_head || ! circular_buffer[idx].extruding())
// Nothing to do, the last move is not extruding.
return;
float feedrate_per_extrusion_role[numExtrusionRoles];
for (size_t i = 0; i < numExtrusionRoles; ++ i)
feedrate_per_extrusion_role[i] = FLT_MAX;
feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_start;
bool modified = true;
while (modified && idx != idx_head) {
size_t idx_prev = circular_buffer_idx_prev(idx);
for (; ! circular_buffer[idx_prev].extruding() && idx_prev != idx_head; idx_prev = circular_buffer_idx_prev(idx_prev)) ;
if (! circular_buffer[idx_prev].extruding())
break;
// Volumetric extrusion rate at the start of the succeding segment.
float rate_succ = circular_buffer[idx].volumetric_extrusion_rate_start;
// What is the gradient of the extrusion rate between idx_prev and idx?
idx = idx_prev;
GCodeLine &line = circular_buffer[idx];
for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) {
float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative;
if (rate_slope == 0)
// The negative rate is unlimited.
continue;
float rate_end = feedrate_per_extrusion_role[iRole];
if (iRole == line.extrusion_role && rate_succ < rate_end)
// Limit by the succeeding volumetric flow rate.
rate_end = rate_succ;
if (line.volumetric_extrusion_rate_end > rate_end) {
line.volumetric_extrusion_rate_end = rate_end;
line.modified = true;
} else if (iRole == line.extrusion_role) {
rate_end = line.volumetric_extrusion_rate_end;
} else if (rate_end == FLT_MAX) {
// The rate for ExtrusionRole iRole is unlimited.
continue;
} else {
// Use the original, 'floating' extrusion rate as a starting point for the limiter.
}
// modified = false;
float rate_start = rate_end + rate_slope * line.time_corrected();
if (rate_start < line.volumetric_extrusion_rate_start) {
// Limit the volumetric extrusion rate at the start of this segment due to a segment
// of ExtrusionType iRole, which will be extruded in the future.
line.volumetric_extrusion_rate_start = rate_start;
line.max_volumetric_extrusion_rate_slope_negative = rate_slope;
line.modified = true;
// modified = true;
}
feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start;
}
}
// Go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
for (size_t i = 0; i < numExtrusionRoles; ++ i)
feedrate_per_extrusion_role[i] = FLT_MAX;
feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_end;
assert(circular_buffer[idx].extruding());
while (idx != idx_tail) {
size_t idx_next = circular_buffer_idx_next(idx);
for (; ! circular_buffer[idx_next].extruding() && idx_next != idx_tail; idx_next = circular_buffer_idx_next(idx_next)) ;
if (! circular_buffer[idx_next].extruding())
break;
float rate_prec = circular_buffer[idx].volumetric_extrusion_rate_end;
// What is the gradient of the extrusion rate between idx_prev and idx?
idx = idx_next;
GCodeLine &line = circular_buffer[idx];
for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) {
float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive;
if (rate_slope == 0)
// The positive rate is unlimited.
continue;
float rate_start = feedrate_per_extrusion_role[iRole];
if (iRole == line.extrusion_role && rate_prec < rate_start)
rate_start = rate_prec;
if (line.volumetric_extrusion_rate_start > rate_start) {
line.volumetric_extrusion_rate_start = rate_start;
line.modified = true;
} else if (iRole == line.extrusion_role) {
rate_start = line.volumetric_extrusion_rate_start;
} else if (rate_start == FLT_MAX) {
// The rate for ExtrusionRole iRole is unlimited.
continue;
} else {
// Use the original, 'floating' extrusion rate as a starting point for the limiter.
}
float rate_end = (rate_slope == 0) ? FLT_MAX : rate_start + rate_slope * line.time_corrected();
if (rate_end < line.volumetric_extrusion_rate_end) {
// Limit the volumetric extrusion rate at the start of this segment due to a segment
// of ExtrusionType iRole, which was extruded before.
line.volumetric_extrusion_rate_end = rate_end;
line.max_volumetric_extrusion_rate_slope_positive = rate_slope;
line.modified = true;
}
feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end;
}
}
}
void PressureEqualizer::push_axis_to_output(const char axis, const float value, bool add_eol)
{
char buf[2048];
int len = sprintf(buf,
(axis == 'E') ? " %c%.3f" : " %c%.5f",
axis, value);
push_to_output(buf, len, add_eol);
}
void PressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol)
{
// New length of the output buffer content.
size_t len_new = output_buffer_length + len + 1;
if (add_eol)
++ len_new;
// Resize the output buffer to a power of 2 higher than the required memory.
if (output_buffer.size() < len_new) {
size_t v = len_new;
// Compute the next highest power of 2 of 32-bit v
// http://graphics.stanford.edu/~seander/bithacks.html
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
output_buffer.resize(v);
}
// Copy the text to the output.
if (len != 0) {
memcpy(output_buffer.data() + output_buffer_length, text, len);
output_buffer_length += len;
}
if (add_eol)
output_buffer[output_buffer_length ++] = '\n';
output_buffer[output_buffer_length] = 0;
}
void PressureEqualizer::push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment)
{
push_to_output("G1", 2, false);
for (char i = 0; i < 3; ++ i)
if (line.pos_provided[i])
push_axis_to_output('X'+i, line.pos_end[i]);
push_axis_to_output('E', m_config->use_relative_e_distances.value ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3]);
// if (line.pos_provided[4] || fabs(line.feedrate() - new_feedrate) > 1e-5)
push_axis_to_output('F', new_feedrate);
// output comment and EOL
push_to_output(comment, (comment == NULL) ? 0 : strlen(comment), true);
}
} // namespace Slic3r

View file

@ -0,0 +1,212 @@
#ifndef slic3r_GCode_PressureEqualizer_hpp_
#define slic3r_GCode_PressureEqualizer_hpp_
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "../ExtrusionEntity.hpp"
namespace Slic3r {
// Processes a G-code. Finds changes in the volumetric extrusion speed and adjusts the transitions
// between these paths to limit fast changes in the volumetric extrusion speed.
class PressureEqualizer
{
public:
PressureEqualizer(const Slic3r::GCodeConfig *config);
~PressureEqualizer();
void reset();
// Process a next batch of G-code lines. Flush the internal buffers if asked for.
const char* process(const char *szGCode, bool flush);
size_t get_output_buffer_length() const { return output_buffer_length; }
private:
struct Statistics
{
void reset() {
volumetric_extrusion_rate_min = std::numeric_limits<float>::max();
volumetric_extrusion_rate_max = 0.f;
volumetric_extrusion_rate_avg = 0.f;
extrusion_length = 0.f;
}
void update(float volumetric_extrusion_rate, float length) {
volumetric_extrusion_rate_min = std::min(volumetric_extrusion_rate_min, volumetric_extrusion_rate);
volumetric_extrusion_rate_max = std::max(volumetric_extrusion_rate_max, volumetric_extrusion_rate);
volumetric_extrusion_rate_avg += volumetric_extrusion_rate * length;
extrusion_length += length;
}
float volumetric_extrusion_rate_min;
float volumetric_extrusion_rate_max;
float volumetric_extrusion_rate_avg;
float extrusion_length;
};
struct Statistics m_stat;
// Keeps the reference, does not own the config.
const Slic3r::GCodeConfig *m_config;
// Private configuration values
// How fast could the volumetric extrusion rate increase / decrase? mm^3/sec^2
struct ExtrusionRateSlope {
float positive;
float negative;
};
enum { numExtrusionRoles = erSupportMaterialInterface + 1 };
ExtrusionRateSlope m_max_volumetric_extrusion_rate_slopes[numExtrusionRoles];
float m_max_volumetric_extrusion_rate_slope_positive;
float m_max_volumetric_extrusion_rate_slope_negative;
// Maximum segment length to split a long segment, if the initial and the final flow rate differ.
float m_max_segment_length;
// Configuration extracted from config.
// Area of the crossestion of each filament. Necessary to calculate the volumetric flow rate.
std::vector<float> m_filament_crossections;
// Internal data.
// X,Y,Z,E,F
float m_current_pos[5];
size_t m_current_extruder;
ExtrusionRole m_current_extrusion_role;
bool m_retracted;
enum GCodeLineType
{
GCODELINETYPE_INVALID,
GCODELINETYPE_NOOP,
GCODELINETYPE_OTHER,
GCODELINETYPE_RETRACT,
GCODELINETYPE_UNRETRACT,
GCODELINETYPE_TOOL_CHANGE,
GCODELINETYPE_MOVE,
GCODELINETYPE_EXTRUDE,
};
struct GCodeLine
{
GCodeLine() :
type(GCODELINETYPE_INVALID),
raw_length(0),
modified(false),
extruder_id(0),
volumetric_extrusion_rate(0.f),
volumetric_extrusion_rate_start(0.f),
volumetric_extrusion_rate_end(0.f)
{}
bool moving_xy() const { return fabs(pos_end[0] - pos_start[0]) > 0.f || fabs(pos_end[1] - pos_start[1]) > 0.f; }
bool moving_z () const { return fabs(pos_end[2] - pos_start[2]) > 0.f; }
bool extruding() const { return moving_xy() && pos_end[3] > pos_start[3]; }
bool retracting() const { return pos_end[3] < pos_start[3]; }
bool deretracting() const { return ! moving_xy() && pos_end[3] > pos_start[3]; }
float dist_xy2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]); }
float dist_xyz2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]) + (pos_end[2] - pos_start[2]) * (pos_end[2] - pos_start[2]); }
float dist_xy() const { return sqrt(dist_xy2()); }
float dist_xyz() const { return sqrt(dist_xyz2()); }
float dist_e() const { return fabs(pos_end[3] - pos_start[3]); }
float feedrate() const { return pos_end[4]; }
float time() const { return dist_xyz() / feedrate(); }
float time_inv() const { return feedrate() / dist_xyz(); }
float volumetric_correction_avg() const {
float avg_correction = 0.5f * (volumetric_extrusion_rate_start + volumetric_extrusion_rate_end) / volumetric_extrusion_rate;
assert(avg_correction > 0.f);
assert(avg_correction <= 1.00000001f);
return avg_correction;
}
float time_corrected() const { return time() * volumetric_correction_avg(); }
GCodeLineType type;
// We try to keep the string buffer once it has been allocated, so it will not be reallocated over and over.
std::vector<char> raw;
size_t raw_length;
// If modified, the raw text has to be adapted by the new extrusion rate,
// or maybe the line needs to be split into multiple lines.
bool modified;
// float timeStart;
// float timeEnd;
// X,Y,Z,E,F. Storing the state of the currently active extruder only.
float pos_start[5];
float pos_end[5];
// Was the axis found on the G-code line? X,Y,Z,F
bool pos_provided[5];
// Index of the active extruder.
size_t extruder_id;
// Extrusion role of this segment.
ExtrusionRole extrusion_role;
// Current volumetric extrusion rate.
float volumetric_extrusion_rate;
// Volumetric extrusion rate at the start of this segment.
float volumetric_extrusion_rate_start;
// Volumetric extrusion rate at the end of this segment.
float volumetric_extrusion_rate_end;
// Volumetric extrusion rate slope limiting this segment.
// If set to zero, the slope is unlimited.
float max_volumetric_extrusion_rate_slope_positive;
float max_volumetric_extrusion_rate_slope_negative;
};
// Circular buffer of GCode lines. The circular buffer size will be limited to circular_buffer_size.
std::vector<GCodeLine> circular_buffer;
// Current position of the circular buffer (index, where to write the next line to, the line has to be pushed out before it is overwritten).
size_t circular_buffer_pos;
// Circular buffer size, configuration value.
size_t circular_buffer_size;
// Number of valid lines in the circular buffer. Lower or equal to circular_buffer_size.
size_t circular_buffer_items;
// Output buffer will only grow. It will not be reallocated over and over.
std::vector<char> output_buffer;
size_t output_buffer_length;
// For debugging purposes. Index of the G-code line processed.
size_t line_idx;
bool process_line(const char *line, const size_t len, GCodeLine &buf);
void output_gcode_line(GCodeLine &buf);
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
// Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
void adjust_volumetric_rate();
// Push the text to the end of the output_buffer.
void push_to_output(const char *text, const size_t len, bool add_eol = true);
// Push an axis assignment to the end of the output buffer.
void push_axis_to_output(const char axis, const float value, bool add_eol = false);
// Push a G-code line to the output,
void push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment);
size_t circular_buffer_idx_head() const {
size_t idx = circular_buffer_pos + circular_buffer_size - circular_buffer_items;
if (idx >= circular_buffer_size)
idx -= circular_buffer_size;
return idx;
}
size_t circular_buffer_idx_tail() const { return circular_buffer_pos; }
size_t circular_buffer_idx_prev(size_t idx) const {
idx += circular_buffer_size - 1;
if (idx >= circular_buffer_size)
idx -= circular_buffer_size;
return idx;
}
size_t circular_buffer_idx_next(size_t idx) const {
if (++ idx >= circular_buffer_size)
idx -= circular_buffer_size;
return idx;
}
};
} // namespace Slic3r
#endif /* slic3r_GCode_PressureEqualizer_hpp_ */

View file

@ -0,0 +1,456 @@
#include "Analyzer.hpp"
#include "PreviewData.hpp"
#include <float.h>
#include <wx/intl.h>
#include <I18N.hpp>
#include <boost/format.hpp>
//! macro used to mark string used at localization,
#define L(s) (s)
namespace Slic3r {
const GCodePreviewData::Color GCodePreviewData::Color::Dummy(0.0f, 0.0f, 0.0f, 0.0f);
GCodePreviewData::Color::Color()
{
rgba[0] = 1.0f;
rgba[1] = 1.0f;
rgba[2] = 1.0f;
rgba[3] = 1.0f;
}
GCodePreviewData::Color::Color(float r, float g, float b, float a)
{
rgba[0] = r;
rgba[1] = g;
rgba[2] = b;
rgba[3] = a;
}
std::vector<unsigned char> GCodePreviewData::Color::as_bytes() const
{
std::vector<unsigned char> ret;
for (unsigned int i = 0; i < 4; ++i)
{
ret.push_back((unsigned char)(255.0f * rgba[i]));
}
return ret;
}
GCodePreviewData::Extrusion::Layer::Layer(float z, const ExtrusionPaths& paths)
: z(z)
, paths(paths)
{
}
GCodePreviewData::Travel::Polyline::Polyline(EType type, EDirection direction, float feedrate, unsigned int extruder_id, const Polyline3& polyline)
: type(type)
, direction(direction)
, feedrate(feedrate)
, extruder_id(extruder_id)
, polyline(polyline)
{
}
const GCodePreviewData::Color GCodePreviewData::Range::Default_Colors[Colors_Count] =
{
Color(0.043f, 0.173f, 0.478f, 1.0f),
Color(0.075f, 0.349f, 0.522f, 1.0f),
Color(0.110f, 0.533f, 0.569f, 1.0f),
Color(0.016f, 0.839f, 0.059f, 1.0f),
Color(0.667f, 0.949f, 0.000f, 1.0f),
Color(0.988f, 0.975f, 0.012f, 1.0f),
Color(0.961f, 0.808f, 0.039f, 1.0f),
Color(0.890f, 0.533f, 0.125f, 1.0f),
Color(0.820f, 0.408f, 0.188f, 1.0f),
Color(0.761f, 0.322f, 0.235f, 1.0f)
};
GCodePreviewData::Range::Range()
{
reset();
}
void GCodePreviewData::Range::reset()
{
min = FLT_MAX;
max = -FLT_MAX;
}
bool GCodePreviewData::Range::empty() const
{
return min == max;
}
void GCodePreviewData::Range::update_from(float value)
{
min = std::min(min, value);
max = std::max(max, value);
}
void GCodePreviewData::Range::update_from(const Range& other)
{
min = std::min(min, other.min);
max = std::max(max, other.max);
}
void GCodePreviewData::Range::set_from(const Range& other)
{
min = other.min;
max = other.max;
}
float GCodePreviewData::Range::step_size() const
{
return (max - min) / (float)(Colors_Count - 1);
}
GCodePreviewData::Color GCodePreviewData::Range::get_color_at(float value) const
{
if (empty())
return Color::Dummy;
float global_t = (value - min) / step_size();
unsigned int low = (unsigned int)global_t;
unsigned int high = clamp((unsigned int)0, Colors_Count - 1, low + 1);
Color color_low = colors[low];
Color color_high = colors[high];
float local_t = global_t - (float)low;
// interpolate in RGB space
Color ret;
for (unsigned int i = 0; i < 4; ++i)
{
ret.rgba[i] = lerp(color_low.rgba[i], color_high.rgba[i], local_t);
}
return ret;
}
GCodePreviewData::LegendItem::LegendItem(const std::string& text, const GCodePreviewData::Color& color)
: text(text)
, color(color)
{
}
const GCodePreviewData::Color GCodePreviewData::Extrusion::Default_Extrusion_Role_Colors[Num_Extrusion_Roles] =
{
Color(0.0f, 0.0f, 0.0f, 1.0f), // erNone
Color(1.0f, 0.0f, 0.0f, 1.0f), // erPerimeter
Color(0.0f, 1.0f, 0.0f, 1.0f), // erExternalPerimeter
Color(0.0f, 0.0f, 1.0f, 1.0f), // erOverhangPerimeter
Color(1.0f, 1.0f, 0.0f, 1.0f), // erInternalInfill
Color(1.0f, 0.0f, 1.0f, 1.0f), // erSolidInfill
Color(0.0f, 1.0f, 1.0f, 1.0f), // erTopSolidInfill
Color(0.5f, 0.5f, 0.5f, 1.0f), // erBridgeInfill
Color(1.0f, 1.0f, 1.0f, 1.0f), // erGapFill
Color(0.5f, 0.0f, 0.0f, 1.0f), // erSkirt
Color(0.0f, 0.5f, 0.0f, 1.0f), // erSupportMaterial
Color(0.0f, 0.0f, 0.5f, 1.0f), // erSupportMaterialInterface
Color(0.7f, 0.89f, 0.67f, 1.0f), // erWipeTower
Color(1.0f, 1.0f, 0.0f, 1.0f), // erCustom
Color(0.0f, 0.0f, 0.0f, 1.0f) // erMixed
};
// todo: merge with Slic3r::ExtrusionRole2String() from GCode.cpp
const std::string GCodePreviewData::Extrusion::Default_Extrusion_Role_Names[Num_Extrusion_Roles]
{
L("None"),
L("Perimeter"),
L("External perimeter"),
L("Overhang perimeter"),
L("Internal infill"),
L("Solid infill"),
L("Top solid infill"),
L("Bridge infill"),
L("Gap fill"),
L("Skirt"),
L("Support material"),
L("Support material interface"),
L("Wipe tower"),
L("Custom"),
L("Mixed")
};
const GCodePreviewData::Extrusion::EViewType GCodePreviewData::Extrusion::Default_View_Type = GCodePreviewData::Extrusion::FeatureType;
void GCodePreviewData::Extrusion::set_default()
{
view_type = Default_View_Type;
::memcpy((void*)role_colors, (const void*)Default_Extrusion_Role_Colors, Num_Extrusion_Roles * sizeof(Color));
for (unsigned int i = 0; i < Num_Extrusion_Roles; ++i)
{
role_names[i] = Default_Extrusion_Role_Names[i];
}
role_flags = 0;
for (unsigned int i = 0; i < Num_Extrusion_Roles; ++i)
{
role_flags |= 1 << i;
}
}
bool GCodePreviewData::Extrusion::is_role_flag_set(ExtrusionRole role) const
{
return is_role_flag_set(role_flags, role);
}
bool GCodePreviewData::Extrusion::is_role_flag_set(unsigned int flags, ExtrusionRole role)
{
return GCodeAnalyzer::is_valid_extrusion_role(role) && (flags & (1 << (role - erPerimeter))) != 0;
}
const float GCodePreviewData::Travel::Default_Width = 0.075f;
const float GCodePreviewData::Travel::Default_Height = 0.075f;
const GCodePreviewData::Color GCodePreviewData::Travel::Default_Type_Colors[Num_Types] =
{
Color(0.0f, 0.0f, 0.75f, 1.0f), // Move
Color(0.0f, 0.75f, 0.0f, 1.0f), // Extrude
Color(0.75f, 0.0f, 0.0f, 1.0f), // Retract
};
void GCodePreviewData::Travel::set_default()
{
width = Default_Width;
height = Default_Height;
::memcpy((void*)type_colors, (const void*)Default_Type_Colors, Num_Types * sizeof(Color));
is_visible = false;
}
const GCodePreviewData::Color GCodePreviewData::Retraction::Default_Color = GCodePreviewData::Color(1.0f, 1.0f, 1.0f, 1.0f);
GCodePreviewData::Retraction::Position::Position(const Vec3crd& position, float width, float height)
: position(position)
, width(width)
, height(height)
{
}
void GCodePreviewData::Retraction::set_default()
{
color = Default_Color;
is_visible = false;
}
void GCodePreviewData::Shell::set_default()
{
is_visible = false;
}
GCodePreviewData::GCodePreviewData()
{
set_default();
}
void GCodePreviewData::set_default()
{
::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.volumetric_rate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
extrusion.set_default();
travel.set_default();
retraction.set_default();
unretraction.set_default();
shell.set_default();
}
void GCodePreviewData::reset()
{
ranges.width.reset();
ranges.height.reset();
ranges.feedrate.reset();
ranges.volumetric_rate.reset();
extrusion.layers.clear();
travel.polylines.clear();
retraction.positions.clear();
unretraction.positions.clear();
}
bool GCodePreviewData::empty() const
{
return extrusion.layers.empty() && travel.polylines.empty() && retraction.positions.empty() && unretraction.positions.empty();
}
GCodePreviewData::Color GCodePreviewData::get_extrusion_role_color(ExtrusionRole role) const
{
return extrusion.role_colors[role];
}
GCodePreviewData::Color GCodePreviewData::get_height_color(float height) const
{
return ranges.height.get_color_at(height);
}
GCodePreviewData::Color GCodePreviewData::get_width_color(float width) const
{
return ranges.width.get_color_at(width);
}
GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) const
{
return ranges.feedrate.get_color_at(feedrate);
}
GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const
{
return ranges.volumetric_rate.get_color_at(rate);
}
void GCodePreviewData::set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha)
{
for (unsigned int i = 0; i < Extrusion::Num_Extrusion_Roles; ++i)
{
if (role_name == extrusion.role_names[i])
{
extrusion.role_colors[i] = Color(red, green, blue, alpha);
break;
}
}
}
void GCodePreviewData::set_extrusion_paths_colors(const std::vector<std::string>& colors)
{
unsigned int size = (unsigned int)colors.size();
if (size % 2 != 0)
return;
for (unsigned int i = 0; i < size; i += 2)
{
const std::string& color_str = colors[i + 1];
if (color_str.size() == 6)
{
bool valid = true;
for (int c = 0; c < 6; ++c)
{
if (::isxdigit(color_str[c]) == 0)
{
valid = false;
break;
}
}
if (valid)
{
unsigned int color;
std::stringstream ss;
ss << std::hex << color_str;
ss >> color;
float den = 1.0f / 255.0f;
float r = (float)((color & 0xFF0000) >> 16) * den;
float g = (float)((color & 0x00FF00) >> 8) * den;
float b = (float)(color & 0x0000FF) * den;
this->set_extrusion_role_color(colors[i], r, g, b, 1.0f);
}
}
}
}
std::string GCodePreviewData::get_legend_title() const
{
switch (extrusion.view_type)
{
case Extrusion::FeatureType:
return L("Feature type");
case Extrusion::Height:
return L("Height (mm)");
case Extrusion::Width:
return L("Width (mm)");
case Extrusion::Feedrate:
return L("Speed (mm/s)");
case Extrusion::VolumetricRate:
return L("Volumetric flow rate (mm3/s)");
case Extrusion::Tool:
return L("Tool");
}
return "";
}
GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::vector<float>& tool_colors) const
{
struct Helper
{
static void FillListFromRange(LegendItemsList& list, const Range& range, unsigned int decimals, float scale_factor)
{
list.reserve(Range::Colors_Count);
float step = range.step_size();
for (int i = Range::Colors_Count - 1; i >= 0; --i)
{
char buf[1024];
sprintf(buf, "%.*f", decimals, scale_factor * (range.min + (float)i * step));
list.emplace_back(buf, range.colors[i]);
}
}
};
LegendItemsList items;
switch (extrusion.view_type)
{
case Extrusion::FeatureType:
{
ExtrusionRole first_valid = erPerimeter;
ExtrusionRole last_valid = erCustom;
items.reserve(last_valid - first_valid + 1);
for (unsigned int i = (unsigned int)first_valid; i <= (unsigned int)last_valid; ++i)
{
items.emplace_back(Slic3r::I18N::translate(extrusion.role_names[i]), extrusion.role_colors[i]);
}
break;
}
case Extrusion::Height:
{
Helper::FillListFromRange(items, ranges.height, 3, 1.0f);
break;
}
case Extrusion::Width:
{
Helper::FillListFromRange(items, ranges.width, 3, 1.0f);
break;
}
case Extrusion::Feedrate:
{
Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f);
break;
}
case Extrusion::VolumetricRate:
{
Helper::FillListFromRange(items, ranges.volumetric_rate, 3, 1.0f);
break;
}
case Extrusion::Tool:
{
unsigned int tools_colors_count = tool_colors.size() / 4;
items.reserve(tools_colors_count);
for (unsigned int i = 0; i < tools_colors_count; ++i)
{
GCodePreviewData::Color color;
::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float));
items.emplace_back((boost::format(Slic3r::I18N::translate(L("Extruder %d"))) % (i + 1)).str(), color);
}
break;
}
}
return items;
}
} // namespace Slic3r

View file

@ -0,0 +1,208 @@
#ifndef slic3r_GCode_PreviewData_hpp_
#define slic3r_GCode_PreviewData_hpp_
#include "../libslic3r.h"
#include "../ExtrusionEntity.hpp"
#include "Point.hpp"
namespace Slic3r {
class GCodePreviewData
{
public:
struct Color
{
float rgba[4];
Color();
Color(float r, float g, float b, float a);
std::vector<unsigned char> as_bytes() const;
static const Color Dummy;
};
struct Range
{
static const unsigned int Colors_Count = 10;
static const Color Default_Colors[Colors_Count];
Color colors[Colors_Count];
float min;
float max;
Range();
void reset();
bool empty() const;
void update_from(float value);
void update_from(const Range& other);
void set_from(const Range& other);
float step_size() const;
Color get_color_at(float value) const;
};
struct Ranges
{
Range height;
Range width;
Range feedrate;
Range volumetric_rate;
};
struct LegendItem
{
std::string text;
Color color;
LegendItem(const std::string& text, const Color& color);
};
typedef std::vector<LegendItem> LegendItemsList;
struct Extrusion
{
enum EViewType : unsigned char
{
FeatureType,
Height,
Width,
Feedrate,
VolumetricRate,
Tool,
Num_View_Types
};
static const unsigned int Num_Extrusion_Roles = (unsigned int)erMixed + 1;
static const Color Default_Extrusion_Role_Colors[Num_Extrusion_Roles];
static const std::string Default_Extrusion_Role_Names[Num_Extrusion_Roles];
static const EViewType Default_View_Type;
struct Layer
{
float z;
ExtrusionPaths paths;
Layer(float z, const ExtrusionPaths& paths);
};
typedef std::vector<Layer> LayersList;
EViewType view_type;
Color role_colors[Num_Extrusion_Roles];
std::string role_names[Num_Extrusion_Roles];
LayersList layers;
unsigned int role_flags;
void set_default();
bool is_role_flag_set(ExtrusionRole role) const;
static bool is_role_flag_set(unsigned int flags, ExtrusionRole role);
};
struct Travel
{
enum EType : unsigned char
{
Move,
Extrude,
Retract,
Num_Types
};
static const float Default_Width;
static const float Default_Height;
static const Color Default_Type_Colors[Num_Types];
struct Polyline
{
enum EDirection
{
Vertical,
Generic,
Num_Directions
};
EType type;
EDirection direction;
float feedrate;
unsigned int extruder_id;
Polyline3 polyline;
Polyline(EType type, EDirection direction, float feedrate, unsigned int extruder_id, const Polyline3& polyline);
};
typedef std::vector<Polyline> PolylinesList;
PolylinesList polylines;
float width;
float height;
Color type_colors[Num_Types];
bool is_visible;
void set_default();
};
struct Retraction
{
static const Color Default_Color;
struct Position
{
Vec3crd position;
float width;
float height;
Position(const Vec3crd& position, float width, float height);
};
typedef std::vector<Position> PositionsList;
PositionsList positions;
Color color;
bool is_visible;
void set_default();
};
struct Shell
{
bool is_visible;
void set_default();
};
Extrusion extrusion;
Travel travel;
Retraction retraction;
Retraction unretraction;
Shell shell;
Ranges ranges;
GCodePreviewData();
void set_default();
void reset();
bool empty() const;
Color get_extrusion_role_color(ExtrusionRole role) const;
Color get_height_color(float height) const;
Color get_width_color(float width) const;
Color get_feedrate_color(float feedrate) const;
Color get_volumetric_rate_color(float rate) const;
void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha);
void set_extrusion_paths_colors(const std::vector<std::string>& colors);
std::string get_legend_title() const;
LegendItemsList get_legend_items(const std::vector<float>& tool_colors) const;
};
GCodePreviewData::Color operator + (const GCodePreviewData::Color& c1, const GCodePreviewData::Color& c2);
GCodePreviewData::Color operator * (float f, const GCodePreviewData::Color& color);
} // namespace Slic3r
#endif /* slic3r_GCode_PreviewData_hpp_ */

View file

@ -0,0 +1,186 @@
// Calculate extents of the extrusions assigned to Print / PrintObject.
// The extents are used for assessing collisions of the print with the priming towers,
// to decide whether to pause the print after the priming towers are extruded
// to let the operator remove them from the print bed.
#include "../BoundingBox.hpp"
#include "../ExtrusionEntity.hpp"
#include "../ExtrusionEntityCollection.hpp"
#include "../Print.hpp"
#include "PrintExtents.hpp"
#include "WipeTower.hpp"
namespace Slic3r {
static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, const coord_t radius)
{
BoundingBox bbox;
if (! polyline.points.empty())
bbox.merge(polyline.points.front());
for (const Point &pt : polyline.points) {
bbox.min(0) = std::min(bbox.min(0), pt(0) - radius);
bbox.min(1) = std::min(bbox.min(1), pt(1) - radius);
bbox.max(0) = std::max(bbox.max(0), pt(0) + radius);
bbox.max(1) = std::max(bbox.max(1), pt(1) + radius);
}
return bbox;
}
static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path)
{
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
bboxf.max = unscale(bbox.max);
bboxf.defined = true;
}
return bboxf;
}
static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusion_loop)
{
BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_loop.paths)
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
bboxf.max = unscale(bbox.max);
bboxf.defined = true;
}
return bboxf;
}
static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &extrusion_multi_path)
{
BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths)
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
bboxf.max = unscale(bbox.max);
bboxf.defined = true;
}
return bboxf;
}
static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_entity);
static inline BoundingBoxf extrusionentity_extents(const ExtrusionEntityCollection &extrusion_entity_collection)
{
BoundingBoxf bbox;
for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities)
bbox.merge(extrusionentity_extents(extrusion_entity));
return bbox;
}
static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_entity)
{
if (extrusion_entity == nullptr)
return BoundingBoxf();
auto *extrusion_path = dynamic_cast<const ExtrusionPath*>(extrusion_entity);
if (extrusion_path != nullptr)
return extrusionentity_extents(*extrusion_path);
auto *extrusion_loop = dynamic_cast<const ExtrusionLoop*>(extrusion_entity);
if (extrusion_loop != nullptr)
return extrusionentity_extents(*extrusion_loop);
auto *extrusion_multi_path = dynamic_cast<const ExtrusionMultiPath*>(extrusion_entity);
if (extrusion_multi_path != nullptr)
return extrusionentity_extents(*extrusion_multi_path);
auto *extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
if (extrusion_entity_collection != nullptr)
return extrusionentity_extents(*extrusion_entity_collection);
throw std::runtime_error("Unexpected extrusion_entity type in extrusionentity_extents()");
return BoundingBoxf();
}
BoundingBoxf get_print_extrusions_extents(const Print &print)
{
BoundingBoxf bbox(extrusionentity_extents(print.brim()));
bbox.merge(extrusionentity_extents(print.skirt()));
return bbox;
}
BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z)
{
BoundingBoxf bbox;
for (const Layer *layer : print_object.layers()) {
if (layer->print_z > max_print_z)
break;
BoundingBoxf bbox_this;
for (const LayerRegion *layerm : layer->regions()) {
bbox_this.merge(extrusionentity_extents(layerm->perimeters));
for (const ExtrusionEntity *ee : layerm->fills.entities)
// fill represents infill extrusions of a single island.
bbox_this.merge(extrusionentity_extents(*dynamic_cast<const ExtrusionEntityCollection*>(ee)));
}
const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer);
if (support_layer)
for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
bbox_this.merge(extrusionentity_extents(extrusion_entity));
for (const Point &offset : print_object.copies()) {
BoundingBoxf bbox_translated(bbox_this);
bbox_translated.translate(unscale(offset));
bbox.merge(bbox_translated);
}
}
return bbox;
}
// Returns a bounding box of a projection of the wipe tower for the layers <= max_print_z.
// The projection does not contain the priming regions.
BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z)
{
// Wipe tower extrusions are saved as if the tower was at the origin with no rotation
// We need to get position and angle of the wipe tower to transform them to actual position.
Transform2d trafo =
Eigen::Translation2d(print.config().wipe_tower_x.value, print.config().wipe_tower_y.value) *
Eigen::Rotation2Dd(print.config().wipe_tower_rotation_angle.value);
BoundingBoxf bbox;
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z)
break;
for (const WipeTower::ToolChangeResult &tcr : tool_changes) {
for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
const WipeTower::Extrusion &e = tcr.extrusions[i];
if (e.width > 0) {
Vec2d delta = 0.5 * Vec2d(e.width, e.width);
Vec2d p1 = trafo * Vec2d((&e - 1)->pos.x, (&e - 1)->pos.y);
Vec2d p2 = trafo * Vec2d(e.pos.x, e.pos.y);
bbox.merge(p1.cwiseMin(p2) - delta);
bbox.merge(p1.cwiseMax(p2) + delta);
}
}
}
}
return bbox;
}
// Returns a bounding box of the wipe tower priming extrusions.
BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print)
{
BoundingBoxf bbox;
if (print.wipe_tower_data().priming != nullptr) {
const WipeTower::ToolChangeResult &tcr = *print.wipe_tower_data().priming;
for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
const WipeTower::Extrusion &e = tcr.extrusions[i];
if (e.width > 0) {
Vec2d p1((&e - 1)->pos.x, (&e - 1)->pos.y);
Vec2d p2(e.pos.x, e.pos.y);
bbox.merge(p1);
coordf_t radius = 0.5 * e.width;
bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius);
bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius);
bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius);
bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius);
}
}
}
return bbox;
}
}

View file

@ -0,0 +1,30 @@
// Measure extents of the planned extrusions.
// To be used for collision reporting.
#ifndef slic3r_PrintExtents_hpp_
#define slic3r_PrintExtents_hpp_
#include "libslic3r.h"
namespace Slic3r {
class Print;
class PrintObject;
class BoundingBoxf;
// Returns a bounding box of a projection of the brim and skirt.
BoundingBoxf get_print_extrusions_extents(const Print &print);
// Returns a bounding box of a projection of the object extrusions at z <= max_print_z.
BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z);
// Returns a bounding box of a projection of the wipe tower for the layers <= max_print_z.
// The projection does not contain the priming regions.
BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z);
// Returns a bounding box of the wipe tower priming extrusions.
BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print);
};
#endif /* slic3r_PrintExtents_hpp_ */

View file

@ -0,0 +1,87 @@
#include "SpiralVase.hpp"
#include "GCode.hpp"
#include <sstream>
namespace Slic3r {
std::string SpiralVase::process_layer(const std::string &gcode)
{
/* This post-processor relies on several assumptions:
- all layers are processed through it, including those that are not supposed
to be transformed, in order to update the reader with the XY positions
- each call to this method includes a full layer, with a single Z move
at the beginning
- each layer is composed by suitable geometry (i.e. a single complete loop)
- loops were not clipped before calling this method */
// If we're not going to modify G-code, just feed it to the reader
// in order to update positions.
if (!this->enable) {
this->_reader.parse_buffer(gcode);
return gcode;
}
// Get total XY length for this layer by summing all extrusion moves.
float total_layer_length = 0;
float layer_height = 0;
float z;
bool set_z = false;
{
//FIXME Performance warning: This copies the GCodeConfig of the reader.
GCodeReader r = this->_reader; // clone
r.parse_buffer(gcode, [&total_layer_length, &layer_height, &z, &set_z]
(GCodeReader &reader, const GCodeReader::GCodeLine &line) {
if (line.cmd_is("G1")) {
if (line.extruding(reader)) {
total_layer_length += line.dist_XY(reader);
} else if (line.has(Z)) {
layer_height += line.dist_Z(reader);
if (!set_z) {
z = line.new_Z(reader);
set_z = true;
}
}
}
});
}
// Remove layer height from initial Z.
z -= layer_height;
std::string new_gcode;
this->_reader.parse_buffer(gcode, [&new_gcode, &z, &layer_height, &total_layer_length]
(GCodeReader &reader, GCodeReader::GCodeLine line) {
if (line.cmd_is("G1")) {
if (line.has_z()) {
// If this is the initial Z move of the layer, replace it with a
// (redundant) move to the last Z of previous layer.
line.set(reader, Z, z);
new_gcode += line.raw() + '\n';
return;
} else {
float dist_XY = line.dist_XY(reader);
if (dist_XY > 0) {
// horizontal move
if (line.extruding(reader)) {
z += dist_XY * layer_height / total_layer_length;
line.set(reader, Z, z);
new_gcode += line.raw() + '\n';
}
return;
/* Skip travel moves: the move to first perimeter point will
cause a visible seam when loops are not aligned in XY; by skipping
it we blend the first loop move in the XY plane (although the smoothness
of such blend depend on how long the first segment is; maybe we should
enforce some minimum length?). */
}
}
}
new_gcode += line.raw() + '\n';
});
return new_gcode;
}
}

View file

@ -0,0 +1,28 @@
#ifndef slic3r_SpiralVase_hpp_
#define slic3r_SpiralVase_hpp_
#include "libslic3r.h"
#include "GCodeReader.hpp"
namespace Slic3r {
class SpiralVase {
public:
bool enable;
SpiralVase(const PrintConfig &config)
: enable(false), _config(&config)
{
this->_reader.z() = this->_config->z_offset;
this->_reader.apply_config(*this->_config);
};
std::string process_layer(const std::string &gcode);
private:
const PrintConfig* _config;
GCodeReader _reader;
};
}
#endif

View file

@ -0,0 +1,631 @@
#include "Print.hpp"
#include "ToolOrdering.hpp"
// #define SLIC3R_DEBUG
// Make assert active if SLIC3R_DEBUG
#ifdef SLIC3R_DEBUG
#define DEBUG
#define _DEBUG
#undef NDEBUG
#endif
#include <cassert>
#include <limits>
namespace Slic3r {
// Returns true in case that extruder a comes before b (b does not have to be present). False otherwise.
bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const
{
if (a==b)
return false;
for (auto extruder : extruders) {
if (extruder == a)
return true;
if (extruder == b)
return false;
}
return false;
}
// For the use case when each object is printed separately
// (print.config().complete_objects is true).
ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material)
{
if (object.layers().empty())
return;
// Initialize the print layers for just a single object.
{
std::vector<coordf_t> zs;
zs.reserve(zs.size() + object.layers().size() + object.support_layers().size());
for (auto layer : object.layers())
zs.emplace_back(layer->print_z);
for (auto layer : object.support_layers())
zs.emplace_back(layer->print_z);
this->initialize_layers(zs);
}
// Collect extruders reuqired to print the layers.
this->collect_extruders(object);
// Reorder the extruders to minimize tool switches.
this->reorder_extruders(first_extruder);
this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height);
this->collect_extruder_statistics(prime_multi_material);
}
// For the use case when all objects are printed at once.
// (print.config().complete_objects is false).
ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool prime_multi_material)
{
m_print_config_ptr = &print.config();
PrintObjectPtrs objects = print.get_printable_objects();
// Initialize the print layers for all objects and all layers.
coordf_t object_bottom_z = 0.;
{
std::vector<coordf_t> zs;
for (auto object : objects) {
zs.reserve(zs.size() + object->layers().size() + object->support_layers().size());
for (auto layer : object->layers())
zs.emplace_back(layer->print_z);
for (auto layer : object->support_layers())
zs.emplace_back(layer->print_z);
if (! object->layers().empty())
object_bottom_z = object->layers().front()->print_z - object->layers().front()->height;
}
this->initialize_layers(zs);
}
// Collect extruders reuqired to print the layers.
for (auto object : objects)
this->collect_extruders(*object);
// Reorder the extruders to minimize tool switches.
this->reorder_extruders(first_extruder);
this->fill_wipe_tower_partitions(print.config(), object_bottom_z);
this->collect_extruder_statistics(prime_multi_material);
}
LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z)
{
auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), LayerTools(print_z - EPSILON));
assert(it_layer_tools != m_layer_tools.end());
coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z);
for (++ it_layer_tools; it_layer_tools != m_layer_tools.end(); ++it_layer_tools) {
coordf_t d = std::abs(it_layer_tools->print_z - print_z);
if (d >= dist_min)
break;
dist_min = d;
}
-- it_layer_tools;
assert(dist_min < EPSILON);
return *it_layer_tools;
}
void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs)
{
sort_remove_duplicates(zs);
// Merge numerically very close Z values.
for (size_t i = 0; i < zs.size();) {
// Find the last layer with roughly the same print_z.
size_t j = i + 1;
coordf_t zmax = zs[i] + EPSILON;
for (; j < zs.size() && zs[j] <= zmax; ++ j) ;
// Assign an average print_z to the set of layers with nearly equal print_z.
m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1]), m_print_config_ptr));
i = j;
}
}
// Collect extruders reuqired to print layers.
void ToolOrdering::collect_extruders(const PrintObject &object)
{
// Collect the support extruders.
for (auto support_layer : object.support_layers()) {
LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z);
ExtrusionRole role = support_layer->support_fills.role();
bool has_support = role == erMixed || role == erSupportMaterial;
bool has_interface = role == erMixed || role == erSupportMaterialInterface;
unsigned int extruder_support = object.config().support_material_extruder.value;
unsigned int extruder_interface = object.config().support_material_interface_extruder.value;
if (has_support)
layer_tools.extruders.push_back(extruder_support);
if (has_interface)
layer_tools.extruders.push_back(extruder_interface);
if (has_support || has_interface)
layer_tools.has_support = true;
}
// Collect the object extruders.
for (auto layer : object.layers()) {
LayerTools &layer_tools = this->tools_for_layer(layer->print_z);
// What extruders are required to print this object layer?
for (size_t region_id = 0; region_id < object.print()->regions().size(); ++ region_id) {
const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr;
if (layerm == nullptr)
continue;
const PrintRegion &region = *object.print()->regions()[region_id];
if (! layerm->perimeters.entities.empty()) {
bool something_nonoverriddable = true;
if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors)
something_nonoverriddable = false;
for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities
if (!layer_tools.wiping_extrusions().is_overriddable(dynamic_cast<const ExtrusionEntityCollection&>(*eec), *m_print_config_ptr, object, region)) {
something_nonoverriddable = true;
break;
}
}
if (something_nonoverriddable)
layer_tools.extruders.push_back(region.config().perimeter_extruder.value);
layer_tools.has_object = true;
}
bool has_infill = false;
bool has_solid_infill = false;
bool something_nonoverriddable = false;
for (const ExtrusionEntity *ee : layerm->fills.entities) {
// fill represents infill extrusions of a single island.
const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role();
if (is_solid_infill(role))
has_solid_infill = true;
else if (role != erNone)
has_infill = true;
if (m_print_config_ptr) {
if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable(*fill, *m_print_config_ptr, object, region))
something_nonoverriddable = true;
}
}
if (something_nonoverriddable || !m_print_config_ptr)
{
if (has_solid_infill)
layer_tools.extruders.push_back(region.config().solid_infill_extruder);
if (has_infill)
layer_tools.extruders.push_back(region.config().infill_extruder);
}
if (has_solid_infill || has_infill)
layer_tools.has_object = true;
}
}
for (auto& layer : m_layer_tools) {
// Sort and remove duplicates
sort_remove_duplicates(layer.extruders);
// make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector)
if (layer.extruders.empty() && layer.has_object)
layer.extruders.push_back(0); // 0="dontcare" extruder - it will be taken care of in reorder_extruders
}
}
// Reorder extruders to minimize layer changes.
void ToolOrdering::reorder_extruders(unsigned int last_extruder_id)
{
if (m_layer_tools.empty())
return;
if (last_extruder_id == (unsigned int)-1) {
// The initial print extruder has not been decided yet.
// Initialize the last_extruder_id with the first non-zero extruder id used for the print.
last_extruder_id = 0;
for (size_t i = 0; i < m_layer_tools.size() && last_extruder_id == 0; ++ i) {
const LayerTools &lt = m_layer_tools[i];
for (unsigned int extruder_id : lt.extruders)
if (extruder_id > 0) {
last_extruder_id = extruder_id;
break;
}
}
if (last_extruder_id == 0)
// Nothing to extrude.
return;
} else
// 1 based index
++ last_extruder_id;
for (LayerTools &lt : m_layer_tools) {
if (lt.extruders.empty())
continue;
if (lt.extruders.size() == 1 && lt.extruders.front() == 0)
lt.extruders.front() = last_extruder_id;
else {
if (lt.extruders.front() == 0)
// Pop the "don't care" extruder, the "don't care" region will be merged with the next one.
lt.extruders.erase(lt.extruders.begin());
// Reorder the extruders to start with the last one.
for (size_t i = 1; i < lt.extruders.size(); ++ i)
if (lt.extruders[i] == last_extruder_id) {
// Move the last extruder to the front.
memmove(lt.extruders.data() + 1, lt.extruders.data(), i * sizeof(unsigned int));
lt.extruders.front() = last_extruder_id;
break;
}
}
last_extruder_id = lt.extruders.back();
}
// Reindex the extruders, so they are zero based, not 1 based.
for (LayerTools &lt : m_layer_tools)
for (unsigned int &extruder_id : lt.extruders) {
assert(extruder_id > 0);
-- extruder_id;
}
}
void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z)
{
if (m_layer_tools.empty())
return;
// Count the minimum number of tool changes per layer.
size_t last_extruder = size_t(-1);
for (LayerTools &lt : m_layer_tools) {
lt.wipe_tower_partitions = lt.extruders.size();
if (! lt.extruders.empty()) {
if (last_extruder == size_t(-1) || last_extruder == lt.extruders.front())
// The first extruder on this layer is equal to the current one, no need to do an initial tool change.
-- lt.wipe_tower_partitions;
last_extruder = lt.extruders.back();
}
}
// Propagate the wipe tower partitions down to support the upper partitions by the lower partitions.
for (int i = int(m_layer_tools.size()) - 2; i >= 0; -- i)
m_layer_tools[i].wipe_tower_partitions = std::max(m_layer_tools[i + 1].wipe_tower_partitions, m_layer_tools[i].wipe_tower_partitions);
//FIXME this is a hack to get the ball rolling.
for (LayerTools &lt : m_layer_tools)
lt.has_wipe_tower = (lt.has_object && lt.wipe_tower_partitions > 0) || lt.print_z < object_bottom_z + EPSILON;
// Test for a raft, insert additional wipe tower layer to fill in the raft separation gap.
double max_layer_height = std::numeric_limits<double>::max();
for (size_t i = 0; i < config.nozzle_diameter.values.size(); ++ i) {
double mlh = config.max_layer_height.values[i];
if (mlh == 0.)
mlh = 0.75 * config.nozzle_diameter.values[i];
max_layer_height = std::min(max_layer_height, mlh);
}
for (size_t i = 0; i + 1 < m_layer_tools.size(); ++ i) {
const LayerTools &lt = m_layer_tools[i];
const LayerTools &lt_next = m_layer_tools[i + 1];
if (lt.print_z < object_bottom_z + EPSILON && lt_next.print_z >= object_bottom_z + EPSILON) {
// lt is the last raft layer. Find the 1st object layer.
size_t j = i + 1;
for (; j < m_layer_tools.size() && ! m_layer_tools[j].has_wipe_tower; ++ j);
if (j < m_layer_tools.size()) {
const LayerTools &lt_object = m_layer_tools[j];
coordf_t gap = lt_object.print_z - lt.print_z;
assert(gap > 0.f);
if (gap > max_layer_height + EPSILON) {
// Insert one additional wipe tower layer between lh.print_z and lt_object.print_z.
LayerTools lt_new(0.5f * (lt.print_z + lt_object.print_z));
// Find the 1st layer above lt_new.
for (j = i + 1; j < m_layer_tools.size() && m_layer_tools[j].print_z < lt_new.print_z - EPSILON; ++ j);
if (std::abs(m_layer_tools[j].print_z - lt_new.print_z) < EPSILON) {
m_layer_tools[j].has_wipe_tower = true;
} else {
LayerTools &lt_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new);
LayerTools &lt_prev = m_layer_tools[j - 1];
LayerTools &lt_next = m_layer_tools[j + 1];
assert(! lt_prev.extruders.empty() && ! lt_next.extruders.empty());
assert(lt_prev.extruders.back() == lt_next.extruders.front());
lt_extra.has_wipe_tower = true;
lt_extra.extruders.push_back(lt_next.extruders.front());
lt_extra.wipe_tower_partitions = lt_next.wipe_tower_partitions;
}
}
}
break;
}
}
// Calculate the wipe_tower_layer_height values.
coordf_t wipe_tower_print_z_last = 0.;
for (LayerTools &lt : m_layer_tools)
if (lt.has_wipe_tower) {
lt.wipe_tower_layer_height = lt.print_z - wipe_tower_print_z_last;
wipe_tower_print_z_last = lt.print_z;
}
}
void ToolOrdering::collect_extruder_statistics(bool prime_multi_material)
{
m_first_printing_extruder = (unsigned int)-1;
for (const auto &lt : m_layer_tools)
if (! lt.extruders.empty()) {
m_first_printing_extruder = lt.extruders.front();
break;
}
m_last_printing_extruder = (unsigned int)-1;
for (auto lt_it = m_layer_tools.rbegin(); lt_it != m_layer_tools.rend(); ++ lt_it)
if (! lt_it->extruders.empty()) {
m_last_printing_extruder = lt_it->extruders.back();
break;
}
m_all_printing_extruders.clear();
for (const auto &lt : m_layer_tools) {
append(m_all_printing_extruders, lt.extruders);
sort_remove_duplicates(m_all_printing_extruders);
}
if (prime_multi_material && ! m_all_printing_extruders.empty()) {
// Reorder m_all_printing_extruders in the sequence they will be primed, the last one will be m_first_printing_extruder.
// Then set m_first_printing_extruder to the 1st extruder primed.
m_all_printing_extruders.erase(
std::remove_if(m_all_printing_extruders.begin(), m_all_printing_extruders.end(),
[ this ](const unsigned int eid) { return eid == m_first_printing_extruder; }),
m_all_printing_extruders.end());
m_all_printing_extruders.emplace_back(m_first_printing_extruder);
m_first_printing_extruder = m_all_printing_extruders.front();
}
}
// This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual)
void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies)
{
something_overridden = true;
auto entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector<int>()))).first; // (add and) return iterator
auto& copies_vector = entity_map_it->second;
if (copies_vector.size() < num_of_copies)
copies_vector.resize(num_of_copies, -1);
if (copies_vector[copy_id] != -1)
std::cout << "ERROR: Entity extruder overriden multiple times!!!\n"; // A debugging message - this must never happen.
copies_vector[copy_id] = extruder;
}
// Finds first non-soluble extruder on the layer
int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const
{
const LayerTools& lt = *m_layer_tools;
for (auto extruders_it = lt.extruders.begin(); extruders_it != lt.extruders.end(); ++extruders_it)
if (!print_config.filament_soluble.get_at(*extruders_it))
return (*extruders_it);
return (-1);
}
// Finds last non-soluble extruder on the layer
int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const
{
const LayerTools& lt = *m_layer_tools;
for (auto extruders_it = lt.extruders.rbegin(); extruders_it != lt.extruders.rend(); ++extruders_it)
if (!print_config.filament_soluble.get_at(*extruders_it))
return (*extruders_it);
return (-1);
}
// Decides whether this entity could be overridden
bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const
{
if (print_config.filament_soluble.get_at(Print::get_extruder(eec, region)))
return false;
if (object.config().wipe_into_objects)
return true;
if (!region.config().wipe_into_infill || eec.role() != erInternalInfill)
return false;
return true;
}
// Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange
// and returns volume that is left to be wiped on the wipe tower.
float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe)
{
const LayerTools& lt = *m_layer_tools;
const float min_infill_volume = 0.f; // ignore infill with smaller volume than this
if (print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder))
return volume_to_wipe; // Soluble filament cannot be wiped in a random infill, neither the filament after it
// we will sort objects so that dedicated for wiping are at the beginning:
PrintObjectPtrs object_list = print.get_printable_objects();
std::sort(object_list.begin(), object_list.end(), [](const PrintObject* a, const PrintObject* b) { return a->config().wipe_into_objects; });
// We will now iterate through
// - first the dedicated objects to mark perimeters or infills (depending on infill_first)
// - second through the dedicated ones again to mark infills or perimeters (depending on infill_first)
// - then all the others to mark infills (in case that !infill_first, we must also check that the perimeter is finished already
// this is controlled by the following variable:
bool perimeters_done = false;
for (int i=0 ; i<(int)object_list.size() + (perimeters_done ? 0 : 1); ++i) {
if (!perimeters_done && (i==(int)object_list.size() || !object_list[i]->config().wipe_into_objects)) { // we passed the last dedicated object in list
perimeters_done = true;
i=-1; // let's go from the start again
continue;
}
const auto& object = object_list[i];
// Finds this layer:
auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
if (this_layer_it == object->layers().end())
continue;
const Layer* this_layer = *this_layer_it;
unsigned int num_of_copies = object->copies().size();
for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
for (size_t region_id = 0; region_id < object->print()->regions().size(); ++ region_id) {
const auto& region = *object->print()->regions()[region_id];
if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
continue;
if ((!print.config().infill_first ? perimeters_done : !perimeters_done) || (!object->config().wipe_into_objects && region.config().wipe_into_infill)) {
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region))
continue;
// What extruder would this normally be printed with?
unsigned int correct_extruder = Print::get_extruder(*fill, region);
if (volume_to_wipe<=0)
continue;
if (!object->config().wipe_into_objects && !print.config().infill_first && region.config().wipe_into_infill)
// In this case we must check that the original extruder is used on this layer before the one we are overridding
// (and the perimeters will be finished before the infill is printed):
if (!lt.is_extruder_order(region.config().perimeter_extruder - 1, new_extruder))
continue;
if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) { // this infill will be used to wipe this extruder
set_extruder_override(fill, copy, new_extruder, num_of_copies);
volume_to_wipe -= fill->total_volume();
}
}
}
// Now the same for perimeters - see comments above for explanation:
if (object->config().wipe_into_objects && (print.config().infill_first ? perimeters_done : !perimeters_done))
{
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) {
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region))
continue;
if (volume_to_wipe<=0)
continue;
if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) {
set_extruder_override(fill, copy, new_extruder, num_of_copies);
volume_to_wipe -= fill->total_volume();
}
}
}
}
}
}
return std::max(0.f, volume_to_wipe);
}
// Called after all toolchanges on a layer were mark_infill_overridden. There might still be overridable entities,
// that were not actually overridden. If they are part of a dedicated object, printing them with the extruder
// they were initially assigned to might mean violating the perimeter-infill order. We will therefore go through
// them again and make sure we override it.
void WipingExtrusions::ensure_perimeters_infills_order(const Print& print)
{
const LayerTools& lt = *m_layer_tools;
unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config());
unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config());
PrintObjectPtrs printable_objects = print.get_printable_objects();
for (const PrintObject* object : printable_objects) {
// Finds this layer:
auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [&lt](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)<EPSILON; });
if (this_layer_it == object->layers().end())
continue;
const Layer* this_layer = *this_layer_it;
unsigned int num_of_copies = object->copies().size();
for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves
for (size_t region_id = 0; region_id < object->print()->regions().size(); ++ region_id) {
const auto& region = *object->print()->regions()[region_id];
if (!region.config().wipe_into_infill && !object->config().wipe_into_objects)
continue;
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region)
|| is_entity_overridden(fill, copy) )
continue;
// This infill could have been overridden but was not - unless we do something, it could be
// printed before its perimeter, or not be printed at all (in case its original extruder has
// not been added to LayerTools
// Either way, we will now force-override it with something suitable:
if (print.config().infill_first
|| object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely
|| lt.is_extruder_order(region.config().perimeter_extruder - 1, last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints
|| std::find(lt.extruders.begin(), lt.extruders.end(), region.config().infill_extruder - 1) == lt.extruders.end()) // we have to force override - this could violate infill_first (FIXME)
)
set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies);
else {
// In this case we can (and should) leave it to be printed normally.
// Force overriding would mean it gets printed before its perimeter.
}
}
// Now the same for perimeters - see comments above for explanation:
for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { // iterate through all perimeter Collections
auto* fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
if (!is_overriddable(*fill, print.config(), *object, region)
|| is_entity_overridden(fill, copy) )
continue;
set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies);
}
}
}
}
}
// Following function is called from process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity.
// It first makes sure the pointer is valid (creates the vector if it does not exist) and contains a record for each copy
// It also modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known,
// so -1 was used as "print as usual".
// The resulting vector has to keep track of which extrusions are the ones that were overridden and which were not. In the extruder is used as overridden,
// its number is saved as it is (zero-based index). Usual extrusions are saved as -number-1 (unfortunately there is no negative zero).
const std::vector<int>* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, int num_of_copies)
{
auto entity_map_it = entity_map.find(entity);
if (entity_map_it == entity_map.end())
entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector<int>()))).first;
// Now the entity_map_it should be valid, let's make sure the vector is long enough:
entity_map_it->second.resize(num_of_copies, -1);
// Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information):
std::replace(entity_map_it->second.begin(), entity_map_it->second.end(), -1, -correct_extruder_id-1);
return &(entity_map_it->second);
}
} // namespace Slic3r

View file

@ -0,0 +1,162 @@
// Ordering of the tools to minimize tool switches.
#ifndef slic3r_ToolOrdering_hpp_
#define slic3r_ToolOrdering_hpp_
#include "libslic3r.h"
namespace Slic3r {
class Print;
class PrintObject;
class LayerTools;
// Object of this class holds information about whether an extrusion is printed immediately
// after a toolchange (as part of infill/perimeter wiping) or not. One extrusion can be a part
// of several copies - this has to be taken into account.
class WipingExtrusions
{
public:
bool is_anything_overridden() const { // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case
return something_overridden;
}
// This is called from GCode::process_layer - see implementation for further comments:
const std::vector<int>* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, int num_of_copies);
// This function goes through all infill entities, decides which ones will be used for wiping and
// marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
float mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe);
void ensure_perimeters_infills_order(const Print& print);
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
private:
int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
// This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual)
void set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies);
// Returns true in case that entity is not printed with its usual extruder for a given copy:
bool is_entity_overridden(const ExtrusionEntity* entity, int copy_id) const {
return (entity_map.find(entity) == entity_map.end() ? false : entity_map.at(entity).at(copy_id) != -1);
}
std::map<const ExtrusionEntity*, std::vector<int>> entity_map; // to keep track of who prints what
bool something_overridden = false;
const LayerTools* m_layer_tools; // so we know which LayerTools object this belongs to
};
class LayerTools
{
public:
LayerTools(const coordf_t z, const PrintConfig* print_config_ptr = nullptr) :
print_z(z),
has_object(false),
has_support(false),
has_wipe_tower(false),
wipe_tower_partitions(0),
wipe_tower_layer_height(0.) {}
// Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other.
// In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports).
bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; }
bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; }
bool is_extruder_order(unsigned int a, unsigned int b) const;
coordf_t print_z;
bool has_object;
bool has_support;
// Zero based extruder IDs, ordered to minimize tool switches.
std::vector<unsigned int> extruders;
// Will there be anything extruded on this layer for the wipe tower?
// Due to the support layers possibly interleaving the object layers,
// wipe tower will be disabled for some support only layers.
bool has_wipe_tower;
// Number of wipe tower partitions to support the required number of tool switches
// and to support the wipe tower partitions above this one.
size_t wipe_tower_partitions;
coordf_t wipe_tower_layer_height;
WipingExtrusions& wiping_extrusions() {
m_wiping_extrusions.set_layer_tools_ptr(this);
return m_wiping_extrusions;
}
private:
// This object holds list of extrusion that will be used for extruder wiping
WipingExtrusions m_wiping_extrusions;
};
class ToolOrdering
{
public:
ToolOrdering() {}
// For the use case when each object is printed separately
// (print.config.complete_objects is true).
ToolOrdering(const PrintObject &object, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false);
// For the use case when all objects are printed at once.
// (print.config.complete_objects is false).
ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false);
void clear() { m_layer_tools.clear(); }
// Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed.
unsigned int first_extruder() const { return m_first_printing_extruder; }
// Get the first extruder printing the layer_tools, returns -1 if there is no layer printed.
unsigned int last_extruder() const { return m_last_printing_extruder; }
// For a multi-material print, the printing extruders are ordered in the order they shall be primed.
const std::vector<unsigned int>& all_extruders() const { return m_all_printing_extruders; }
// Find LayerTools with the closest print_z.
LayerTools& tools_for_layer(coordf_t print_z);
const LayerTools& tools_for_layer(coordf_t print_z) const
{ return *const_cast<const LayerTools*>(&const_cast<const ToolOrdering*>(this)->tools_for_layer(print_z)); }
const LayerTools& front() const { return m_layer_tools.front(); }
const LayerTools& back() const { return m_layer_tools.back(); }
std::vector<LayerTools>::const_iterator begin() const { return m_layer_tools.begin(); }
std::vector<LayerTools>::const_iterator end() const { return m_layer_tools.end(); }
bool empty() const { return m_layer_tools.empty(); }
std::vector<LayerTools>& layer_tools() { return m_layer_tools; }
bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().wipe_tower_partitions > 0; }
private:
void initialize_layers(std::vector<coordf_t> &zs);
void collect_extruders(const PrintObject &object);
void reorder_extruders(unsigned int last_extruder_id);
void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z);
void collect_extruder_statistics(bool prime_multi_material);
std::vector<LayerTools> m_layer_tools;
// First printing extruder, including the multi-material priming sequence.
unsigned int m_first_printing_extruder = (unsigned int)-1;
// Final printing extruder.
unsigned int m_last_printing_extruder = (unsigned int)-1;
// All extruders, which extrude some material over m_layer_tools.
std::vector<unsigned int> m_all_printing_extruders;
const PrintConfig* m_print_config_ptr = nullptr;
};
} // namespace SLic3r
#endif /* slic3r_ToolOrdering_hpp_ */

View file

@ -0,0 +1,167 @@
#ifndef slic3r_WipeTower_hpp_
#define slic3r_WipeTower_hpp_
#include <utility>
#include <string>
#include <vector>
namespace Slic3r
{
// A pure virtual WipeTower definition.
class WipeTower
{
public:
// Internal point class, to make the wipe tower independent from other slic3r modules.
// This is important for Prusa Research as we want to build the wipe tower post-processor independently from slic3r.
struct xy
{
xy(float x = 0.f, float y = 0.f) : x(x), y(y) {}
xy(const xy& pos,float xp,float yp) : x(pos.x+xp), y(pos.y+yp) {}
xy operator+(const xy &rhs) const { xy out(*this); out.x += rhs.x; out.y += rhs.y; return out; }
xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; }
xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; }
xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; }
bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; }
bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; }
// Rotate the point around center of the wipe tower about given angle (in degrees)
xy rotate(float width, float depth, float angle) const {
xy out(0,0);
float temp_x = x - width / 2.f;
float temp_y = y - depth / 2.f;
angle *= float(M_PI/180.);
out.x += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f;
out.y += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f;
return out;
}
// Rotate the point around origin about given angle in degrees
void rotate(float angle) {
float temp_x = x * cos(angle) - y * sin(angle);
y = x * sin(angle) + y * cos(angle);
x = temp_x;
}
void translate(const xy& vect) {
x += vect.x;
y += vect.y;
}
float x;
float y;
};
WipeTower() {}
virtual ~WipeTower() {}
// Return the wipe tower position.
virtual const xy& position() const = 0;
// Return the wipe tower width.
virtual float width() const = 0;
// The wipe tower is finished, there should be no more tool changes or wipe tower prints.
virtual bool finished() const = 0;
// Switch to a next layer.
virtual void set_layer(
// Print height of this layer.
float print_z,
// Layer height, used to calculate extrusion the rate.
float layer_height,
// Maximum number of tool changes on this layer or the layers below.
size_t max_tool_changes,
// Is this the first layer of the print? In that case print the brim first.
bool is_first_layer,
// Is this the last layer of the wipe tower?
bool is_last_layer) = 0;
enum Purpose {
PURPOSE_MOVE_TO_TOWER,
PURPOSE_EXTRUDE,
PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE,
};
// Extrusion path of the wipe tower, for 3D preview of the generated tool paths.
struct Extrusion
{
Extrusion(const xy &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {}
// End position of this extrusion.
xy pos;
// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
// This is left zero if it is a travel move.
float width;
// Current extruder index.
unsigned int tool;
};
struct ToolChangeResult
{
// Print heigh of this tool change.
float print_z;
float layer_height;
// G-code section to be directly included into the output G-code.
std::string gcode;
// For path preview.
std::vector<Extrusion> extrusions;
// Initial position, at which the wipe tower starts its action.
// At this position the extruder is loaded and there is no Z-hop applied.
xy start_pos;
// Last point, at which the normal G-code generator of Slic3r shall continue.
// At this position the extruder is loaded and there is no Z-hop applied.
xy end_pos;
// Time elapsed over this tool change.
// This is useful not only for the print time estimation, but also for the control of layer cooling.
float elapsed_time;
// Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later)
bool priming;
// Sum the total length of the extrusion.
float total_extrusion_length_in_plane() {
float e_length = 0.f;
for (size_t i = 1; i < this->extrusions.size(); ++ i) {
const Extrusion &e = this->extrusions[i];
if (e.width > 0) {
xy v = e.pos - (&e - 1)->pos;
e_length += sqrt(v.x*v.x+v.y*v.y);
}
}
return e_length;
}
};
// Returns gcode to prime the nozzles at the front edge of the print bed.
virtual ToolChangeResult prime(
// print_z of the first layer.
float first_layer_height,
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
const std::vector<unsigned int> &tools,
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
bool last_wipe_inside_wipe_tower) = 0;
// Returns gcode for toolchange and the end position.
// if new_tool == -1, just unload the current filament over the wipe tower.
virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer) = 0;
// Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag.
// Call this method only if layer_finished() is false.
virtual ToolChangeResult finish_layer() = 0;
// Is the current layer finished? A layer is finished if either the wipe tower is finished, or
// the wipe tower has been completely covered by the tool change extrusions,
// or the rest of the tower has been filled by a sparse infill with the finish_layer() method.
virtual bool layer_finished() const = 0;
// Returns used filament length per extruder:
virtual std::vector<float> get_used_filament() const = 0;
// Returns total number of toolchanges:
virtual int get_number_of_toolchanges() const = 0;
};
}; // namespace Slic3r
#endif /* slic3r_WipeTower_hpp_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,374 @@
#ifndef WipeTowerPrusaMM_hpp_
#define WipeTowerPrusaMM_hpp_
#include <cmath>
#include <string>
#include <sstream>
#include <utility>
#include <algorithm>
#include "WipeTower.hpp"
namespace Slic3r
{
namespace PrusaMultiMaterial {
class Writer;
};
class WipeTowerPrusaMM : public WipeTower
{
public:
enum material_type
{
INVALID = -1,
PLA = 0, // E:210C B:55C
ABS = 1, // E:255C B:100C
PET = 2, // E:240C B:90C
HIPS = 3, // E:220C B:100C
FLEX = 4, // E:245C B:80C
SCAFF = 5, // E:215C B:55C
EDGE = 6, // E:240C B:80C
NGEN = 7, // E:230C B:80C
PVA = 8 // E:210C B:80C
};
// Parse material name into material_type.
static material_type parse_material(const char *name);
// x -- x coordinates of wipe tower in mm ( left bottom corner )
// y -- y coordinates of wipe tower in mm ( left bottom corner )
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
// wipe_area -- space available for one toolchange in mm
WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction,
float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging,
const std::vector<std::vector<float>>& wiping_matrix, unsigned int initial_tool) :
m_wipe_tower_pos(x, y),
m_wipe_tower_width(width),
m_wipe_tower_rotation_angle(rotation_angle),
m_y_shift(0.f),
m_z_pos(0.f),
m_is_first_layer(false),
m_cooling_tube_retraction(cooling_tube_retraction),
m_cooling_tube_length(cooling_tube_length),
m_parking_pos_retraction(parking_pos_retraction),
m_extra_loading_move(extra_loading_move),
m_bridging(bridging),
m_current_tool(initial_tool),
wipe_volumes(wiping_matrix)
{}
virtual ~WipeTowerPrusaMM() {}
// Set the extruder properties.
void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start,
float unloading_speed, float unloading_speed_start, float delay, int cooling_moves,
float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter)
{
//while (m_filpar.size() < idx+1) // makes sure the required element is in the vector
m_filpar.push_back(FilamentParameters());
m_filpar[idx].material = material;
m_filpar[idx].temperature = temp;
m_filpar[idx].first_layer_temperature = first_layer_temp;
m_filpar[idx].loading_speed = loading_speed;
m_filpar[idx].loading_speed_start = loading_speed_start;
m_filpar[idx].unloading_speed = unloading_speed;
m_filpar[idx].unloading_speed_start = unloading_speed_start;
m_filpar[idx].delay = delay;
m_filpar[idx].cooling_moves = cooling_moves;
m_filpar[idx].cooling_initial_speed = cooling_initial_speed;
m_filpar[idx].cooling_final_speed = cooling_final_speed;
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
std::stringstream stream{ramming_parameters};
float speed = 0.f;
stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator;
m_filpar[idx].ramming_line_width_multiplicator /= 100;
m_filpar[idx].ramming_step_multiplicator /= 100;
while (stream >> speed)
m_filpar[idx].ramming_speed.push_back(speed);
m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later
}
// Appends into internal structure m_plan containing info about the future wipe tower
// to be used before building begins. The entries must be added ordered in z.
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f);
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
void generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
float get_depth() const { return m_wipe_tower_depth; }
// Switch to a next layer.
virtual void set_layer(
// Print height of this layer.
float print_z,
// Layer height, used to calculate extrusion the rate.
float layer_height,
// Maximum number of tool changes on this layer or the layers below.
size_t max_tool_changes,
// Is this the first layer of the print? In that case print the brim first.
bool is_first_layer,
// Is this the last layer of the waste tower?
bool is_last_layer)
{
m_z_pos = print_z;
m_layer_height = layer_height;
m_is_first_layer = is_first_layer;
m_print_brim = is_first_layer;
m_depth_traversed = 0.f;
m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL;
if (is_first_layer) {
this->m_num_layer_changes = 0;
this->m_num_tool_changes = 0;
}
else
++ m_num_layer_changes;
// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
m_extrusion_flow = extrusion_flow(layer_height);
// Advance m_layer_info iterator, making sure we got it right
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
++m_layer_info;
}
// Return the wipe tower position.
virtual const xy& position() const { return m_wipe_tower_pos; }
// Return the wipe tower width.
virtual float width() const { return m_wipe_tower_width; }
// The wipe tower is finished, there should be no more tool changes or wipe tower prints.
virtual bool finished() const { return m_max_color_changes == 0; }
// Returns gcode to prime the nozzles at the front edge of the print bed.
virtual ToolChangeResult prime(
// print_z of the first layer.
float first_layer_height,
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
const std::vector<unsigned int> &tools,
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
bool last_wipe_inside_wipe_tower);
// Returns gcode for a toolchange and a final print head position.
// On the first layer, extrude a brim around the future wipe tower first.
virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer);
// Fill the unfilled space with a sparse infill.
// Call this method only if layer_finished() is false.
virtual ToolChangeResult finish_layer();
// Is the current layer finished?
virtual bool layer_finished() const {
return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed);
}
virtual std::vector<float> get_used_filament() const override { return m_used_filament_length; }
virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; }
private:
WipeTowerPrusaMM();
enum wipe_shape // A fill-in direction
{
SHAPE_NORMAL = 1,
SHAPE_REVERSED = -1
};
const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet
const float Filament_Area = M_PI * 1.75f * 1.75f / 4.f; // filament area in mm^2
const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust
const float WT_EPSILON = 1e-3f;
xy m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
float m_wipe_tower_width; // Width of the wipe tower.
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
float m_internal_rotation = 0.f;
float m_y_shift = 0.f; // y shift passed to writer
float m_z_pos = 0.f; // Current Z position.
float m_layer_height = 0.f; // Current layer height.
size_t m_max_color_changes = 0; // Maximum number of color changes per layer.
bool m_is_first_layer = false;// Is this the 1st layer of the print? If so, print the brim around the waste tower.
int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary)
// G-code generator parameters.
float m_cooling_tube_retraction = 0.f;
float m_cooling_tube_length = 0.f;
float m_parking_pos_retraction = 0.f;
float m_extra_loading_move = 0.f;
float m_bridging = 0.f;
bool m_adhesion = true;
float m_perimeter_width = 0.4 * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
float m_extrusion_flow = 0.038; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
struct FilamentParameters {
material_type material = PLA;
int temperature = 0;
int first_layer_temperature = 0;
float loading_speed = 0.f;
float loading_speed_start = 0.f;
float unloading_speed = 0.f;
float unloading_speed_start = 0.f;
float delay = 0.f ;
int cooling_moves = 0;
float cooling_initial_speed = 0.f;
float cooling_final_speed = 0.f;
float ramming_line_width_multiplicator = 0.f;
float ramming_step_multiplicator = 0.f;
std::vector<float> ramming_speed;
float nozzle_diameter;
};
// Extruder specific parameters.
std::vector<FilamentParameters> m_filpar;
// State of the wipe tower generator.
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.
///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes.
bool m_print_brim = true;
// A fill-in direction (positive Y, negative Y) alternates with each layer.
wipe_shape m_current_shape = SHAPE_NORMAL;
unsigned int m_current_tool = 0;
const std::vector<std::vector<float>> wipe_volumes;
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
bool m_left_to_right = true;
float m_extra_spacing = 1.f;
// Calculates extrusion flow needed to produce required line width for given layer height
float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
{
if ( layer_height < 0 )
return m_extrusion_flow;
return layer_height * ( m_perimeter_width - layer_height * (1-M_PI/4.f)) / Filament_Area;
}
// Calculates length of extrusion line to extrude given volume
float volume_to_length(float volume, float line_width, float layer_height) const {
return std::max(0., volume / (layer_height * (line_width - layer_height * (1. - M_PI / 4.))));
}
// Calculates depth for all layers and propagates them downwards
void plan_tower();
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
void make_wipe_tower_square();
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
void save_on_last_wipe();
struct box_coordinates
{
box_coordinates(float left, float bottom, float width, float height) :
ld(left , bottom ),
lu(left , bottom + height),
rd(left + width, bottom ),
ru(left + width, bottom + height) {}
box_coordinates(const xy &pos, float width, float height) : box_coordinates(pos.x, pos.y, width, height) {}
void translate(const xy &shift) {
ld += shift; lu += shift;
rd += shift; ru += shift;
}
void translate(const float dx, const float dy) { translate(xy(dx, dy)); }
void expand(const float offset) {
ld += xy(- offset, - offset);
lu += xy(- offset, offset);
rd += xy( offset, - offset);
ru += xy( offset, offset);
}
void expand(const float offset_x, const float offset_y) {
ld += xy(- offset_x, - offset_y);
lu += xy(- offset_x, offset_y);
rd += xy( offset_x, - offset_y);
ru += xy( offset_x, offset_y);
}
xy ld; // left down
xy lu; // left upper
xy rd; // right lower
xy ru; // right upper
};
// to store information about tool changes for a given layer
struct WipeTowerInfo{
struct ToolChange {
unsigned int old_tool;
unsigned int new_tool;
float required_depth;
float ramming_depth;
float first_wipe_line;
float wipe_volume;
ToolChange(unsigned int old, unsigned int newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f)
: old_tool{old}, new_tool{newtool}, required_depth{depth}, ramming_depth{ramming_depth}, first_wipe_line{fwl}, wipe_volume{wv} {}
};
float z; // z position of the layer
float height; // layer height
float depth; // depth of the layer based on all layers above
float extra_spacing;
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
std::vector<ToolChange> tool_changes;
WipeTowerInfo(float z_par, float layer_height_par)
: z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {}
};
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end();
// Stores information about used filament length per extruder:
std::vector<float> m_used_filament_length;
// Returns gcode for wipe tower brim
// sideOnly -- set to false -- experimental, draw brim on sides of wipe tower
// offset -- set to 0 -- experimental, offset to replace brim in front / rear of wipe tower
ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f);
void toolchange_Unload(
PrusaMultiMaterial::Writer &writer,
const box_coordinates &cleaning_box,
const material_type current_material,
const int new_temperature);
void toolchange_Change(
PrusaMultiMaterial::Writer &writer,
const unsigned int new_tool,
material_type new_material);
void toolchange_Load(
PrusaMultiMaterial::Writer &writer,
const box_coordinates &cleaning_box);
void toolchange_Wipe(
PrusaMultiMaterial::Writer &writer,
const box_coordinates &cleaning_box,
float wipe_volume);
};
}; // namespace Slic3r
#endif /* WipeTowerPrusaMM_hpp_ */

View file

@ -0,0 +1,199 @@
#include "GCodeReader.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <fstream>
#include <iostream>
#include <Shiny/Shiny.h>
namespace Slic3r {
void GCodeReader::apply_config(const GCodeConfig &config)
{
m_config = config;
m_extrusion_axis = m_config.get_extrusion_axis()[0];
}
void GCodeReader::apply_config(const DynamicPrintConfig &config)
{
m_config.apply(config, true);
m_extrusion_axis = m_config.get_extrusion_axis()[0];
}
const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command)
{
PROFILE_FUNC();
// command and args
const char *c = ptr;
{
PROFILE_BLOCK(command_and_args);
// Skip the whitespaces.
command.first = skip_whitespaces(c);
// Skip the command.
c = command.second = skip_word(command.first);
// Up to the end of line or comment.
while (! is_end_of_gcode_line(*c)) {
// Skip whitespaces.
c = skip_whitespaces(c);
if (is_end_of_gcode_line(*c))
break;
// Check the name of the axis.
Axis axis = NUM_AXES;
switch (*c) {
case 'X': axis = X; break;
case 'Y': axis = Y; break;
case 'Z': axis = Z; break;
case 'F': axis = F; break;
default:
if (*c == m_extrusion_axis)
axis = E;
break;
}
if (axis != NUM_AXES) {
// Try to parse the numeric value.
char *pend = nullptr;
double v = strtod(++ c, &pend);
if (pend != nullptr && is_end_of_word(*pend)) {
// The axis value has been parsed correctly.
gline.m_axis[int(axis)] = float(v);
gline.m_mask |= 1 << int(axis);
c = pend;
} else
// Skip the rest of the word.
c = skip_word(c);
} else
// Skip the rest of the word.
c = skip_word(c);
}
}
if (gline.has(E) && m_config.use_relative_e_distances)
m_position[E] = 0;
// Skip the rest of the line.
for (; ! is_end_of_line(*c); ++ c);
// Copy the raw string including the comment, without the trailing newlines.
if (c > ptr) {
PROFILE_BLOCK(copy_raw_string);
gline.m_raw.assign(ptr, c);
}
// Skip the trailing newlines.
if (*c == '\r')
++ c;
if (*c == '\n')
++ c;
if (m_verbose)
std::cout << gline.m_raw << std::endl;
return c;
}
void GCodeReader::update_coordinates(GCodeLine &gline, std::pair<const char*, const char*> &command)
{
PROFILE_FUNC();
if (*command.first == 'G') {
int cmd_len = int(command.second - command.first);
if ((cmd_len == 2 && (command.first[1] == '0' || command.first[1] == '1')) ||
(cmd_len == 3 && command.first[1] == '9' && command.first[2] == '2')) {
for (size_t i = 0; i < NUM_AXES; ++ i)
if (gline.has(Axis(i)))
m_position[i] = gline.value(Axis(i));
}
}
}
void GCodeReader::parse_file(const std::string &file, callback_t callback)
{
std::ifstream f(file);
std::string line;
while (std::getline(f, line))
this->parse_line(line, callback);
}
bool GCodeReader::GCodeLine::has(char axis) const
{
const char *c = m_raw.c_str();
// Skip the whitespaces.
c = skip_whitespaces(c);
// Skip the command.
c = skip_word(c);
// Up to the end of line or comment.
while (! is_end_of_gcode_line(*c)) {
// Skip whitespaces.
c = skip_whitespaces(c);
if (is_end_of_gcode_line(*c))
break;
// Check the name of the axis.
if (*c == axis)
return true;
// Skip the rest of the word.
c = skip_word(c);
}
return false;
}
bool GCodeReader::GCodeLine::has_value(char axis, float &value) const
{
const char *c = m_raw.c_str();
// Skip the whitespaces.
c = skip_whitespaces(c);
// Skip the command.
c = skip_word(c);
// Up to the end of line or comment.
while (! is_end_of_gcode_line(*c)) {
// Skip whitespaces.
c = skip_whitespaces(c);
if (is_end_of_gcode_line(*c))
break;
// Check the name of the axis.
if (*c == axis) {
// Try to parse the numeric value.
char *pend = nullptr;
double v = strtod(++ c, &pend);
if (pend != nullptr && is_end_of_word(*pend)) {
// The axis value has been parsed correctly.
value = float(v);
return true;
}
}
// Skip the rest of the word.
c = skip_word(c);
}
return false;
}
void GCodeReader::GCodeLine::set(const GCodeReader &reader, const Axis axis, const float new_value, const int decimal_digits)
{
std::ostringstream ss;
ss << std::fixed << std::setprecision(decimal_digits) << new_value;
char match[3] = " X";
if (int(axis) < 3)
match[1] += int(axis);
else if (axis == F)
match[1] = 'F';
else {
assert(axis == E);
match[1] = reader.extrusion_axis();
}
if (this->has(axis)) {
size_t pos = m_raw.find(match)+2;
size_t end = m_raw.find(' ', pos+1);
m_raw = m_raw.replace(pos, end-pos, ss.str());
} else {
size_t pos = m_raw.find(' ');
if (pos == std::string::npos)
m_raw += std::string(match) + ss.str();
else
m_raw = m_raw.replace(pos, 0, std::string(match) + ss.str());
}
m_axis[axis] = new_value;
m_mask |= 1 << int(axis);
}
}

View file

@ -0,0 +1,140 @@
#ifndef slic3r_GCodeReader_hpp_
#define slic3r_GCodeReader_hpp_
#include "libslic3r.h"
#include <cmath>
#include <cstdlib>
#include <functional>
#include <string>
#include "PrintConfig.hpp"
namespace Slic3r {
class GCodeReader {
public:
class GCodeLine {
public:
GCodeLine() { reset(); }
void reset() { m_mask = 0; memset(m_axis, 0, sizeof(m_axis)); m_raw.clear(); }
const std::string& raw() const { return m_raw; }
const std::string cmd() const {
const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str());
return std::string(cmd, GCodeReader::skip_word(cmd));
}
const std::string comment() const
{ size_t pos = m_raw.find(';'); return (pos == std::string::npos) ? "" : m_raw.substr(pos + 1); }
bool has(Axis axis) const { return (m_mask & (1 << int(axis))) != 0; }
float value(Axis axis) const { return m_axis[axis]; }
bool has(char axis) const;
bool has_value(char axis, float &value) const;
float new_Z(const GCodeReader &reader) const { return this->has(Z) ? this->z() : reader.z(); }
float new_E(const GCodeReader &reader) const { return this->has(E) ? this->e() : reader.e(); }
float new_F(const GCodeReader &reader) const { return this->has(F) ? this->f() : reader.f(); }
float dist_X(const GCodeReader &reader) const { return this->has(X) ? (this->x() - reader.x()) : 0; }
float dist_Y(const GCodeReader &reader) const { return this->has(Y) ? (this->y() - reader.y()) : 0; }
float dist_Z(const GCodeReader &reader) const { return this->has(Z) ? (this->z() - reader.z()) : 0; }
float dist_E(const GCodeReader &reader) const { return this->has(E) ? (this->e() - reader.e()) : 0; }
float dist_XY(const GCodeReader &reader) const {
float x = this->has(X) ? (this->x() - reader.x()) : 0;
float y = this->has(Y) ? (this->y() - reader.y()) : 0;
return sqrt(x*x + y*y);
}
bool cmd_is(const char *cmd_test) const {
const char *cmd = GCodeReader::skip_whitespaces(m_raw.c_str());
int len = strlen(cmd_test);
return strncmp(cmd, cmd_test, len) == 0 && GCodeReader::is_end_of_word(cmd[len]);
}
bool extruding(const GCodeReader &reader) const { return this->cmd_is("G1") && this->dist_E(reader) > 0; }
bool retracting(const GCodeReader &reader) const { return this->cmd_is("G1") && this->dist_E(reader) < 0; }
bool travel() const { return this->cmd_is("G1") && ! this->has(E); }
void set(const GCodeReader &reader, const Axis axis, const float new_value, const int decimal_digits = 3);
bool has_x() const { return this->has(X); }
bool has_y() const { return this->has(Y); }
bool has_z() const { return this->has(Z); }
bool has_e() const { return this->has(E); }
bool has_f() const { return this->has(F); }
float x() const { return m_axis[X]; }
float y() const { return m_axis[Y]; }
float z() const { return m_axis[Z]; }
float e() const { return m_axis[E]; }
float f() const { return m_axis[F]; }
private:
std::string m_raw;
float m_axis[NUM_AXES];
uint32_t m_mask;
friend class GCodeReader;
};
typedef std::function<void(GCodeReader&, const GCodeLine&)> callback_t;
GCodeReader() : m_verbose(false), m_extrusion_axis('E') { memset(m_position, 0, sizeof(m_position)); }
void apply_config(const GCodeConfig &config);
void apply_config(const DynamicPrintConfig &config);
template<typename Callback>
void parse_buffer(const std::string &buffer, Callback callback)
{
const char *ptr = buffer.c_str();
GCodeLine gline;
while (*ptr != 0) {
gline.reset();
ptr = this->parse_line(ptr, gline, callback);
}
}
void parse_buffer(const std::string &buffer)
{ this->parse_buffer(buffer, [](GCodeReader&, const GCodeReader::GCodeLine&){}); }
template<typename Callback>
const char* parse_line(const char *ptr, GCodeLine &gline, Callback &callback)
{
std::pair<const char*, const char*> cmd;
const char *end = parse_line_internal(ptr, gline, cmd);
callback(*this, gline);
update_coordinates(gline, cmd);
return end;
}
template<typename Callback>
void parse_line(const std::string &line, Callback callback)
{ GCodeLine gline; this->parse_line(line.c_str(), gline, callback); }
void parse_file(const std::string &file, callback_t callback);
float& x() { return m_position[X]; }
float x() const { return m_position[X]; }
float& y() { return m_position[Y]; }
float y() const { return m_position[Y]; }
float& z() { return m_position[Z]; }
float z() const { return m_position[Z]; }
float& e() { return m_position[E]; }
float e() const { return m_position[E]; }
float& f() { return m_position[F]; }
float f() const { return m_position[F]; }
char extrusion_axis() const { return m_extrusion_axis; }
private:
const char* parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command);
void update_coordinates(GCodeLine &gline, std::pair<const char*, const char*> &command);
static bool is_whitespace(char c) { return c == ' ' || c == '\t'; }
static bool is_end_of_line(char c) { return c == '\r' || c == '\n' || c == 0; }
static bool is_end_of_gcode_line(char c) { return c == ';' || is_end_of_line(c); }
static bool is_end_of_word(char c) { return is_whitespace(c) || is_end_of_gcode_line(c); }
static const char* skip_whitespaces(const char *c) { for (; is_whitespace(*c); ++ c); return c; }
static const char* skip_word(const char *c) { for (; ! is_end_of_word(*c); ++ c); return c; }
GCodeConfig m_config;
char m_extrusion_axis;
float m_position[NUM_AXES];
bool m_verbose;
};
} /* namespace Slic3r */
#endif /* slic3r_GCodeReader_hpp_ */

View file

@ -0,0 +1,580 @@
#include "GCodeSender.hpp"
#include <iostream>
#include <istream>
#include <string>
#include <thread>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/lexical_cast.hpp>
#if defined(__APPLE__) || defined(__OpenBSD__)
#include <termios.h>
#endif
#ifdef __APPLE__
#include <sys/ioctl.h>
#include <IOKit/serial/ioss.h>
#endif
#ifdef __linux__
#include <sys/ioctl.h>
#include <fcntl.h>
#include "/usr/include/asm-generic/ioctls.h"
/* The following definitions are kindly borrowed from:
/usr/include/asm-generic/termbits.h
Unfortunately we cannot just include that one because
it would redefine the "struct termios" already defined
the <termios.h> already included by Boost.ASIO. */
#define K_NCCS 19
struct termios2 {
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_line;
cc_t c_cc[K_NCCS];
speed_t c_ispeed;
speed_t c_ospeed;
};
#define BOTHER CBAUDEX
#endif
//#define DEBUG_SERIAL
#ifdef DEBUG_SERIAL
#include <cstdlib>
#include <fstream>
std::fstream fs;
#endif
#define KEEP_SENT 20
namespace Slic3r {
GCodeSender::GCodeSender()
: io(), serial(io), can_send(false), sent(0), open(false), error(false),
connected(false), queue_paused(false)
{
#ifdef DEBUG_SERIAL
std::srand(std::time(nullptr));
#endif
}
GCodeSender::~GCodeSender()
{
this->disconnect();
}
bool
GCodeSender::connect(std::string devname, unsigned int baud_rate)
{
this->disconnect();
this->set_error_status(false);
try {
this->serial.open(devname);
this->serial.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::odd));
this->serial.set_option(boost::asio::serial_port_base::character_size(boost::asio::serial_port_base::character_size(8)));
this->serial.set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::none));
this->serial.set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
this->set_baud_rate(baud_rate);
this->serial.close();
this->serial.open(devname);
this->serial.set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
// set baud rate again because set_option overwrote it
this->set_baud_rate(baud_rate);
this->open = true;
this->reset();
} catch (boost::system::system_error &) {
this->set_error_status(true);
return false;
}
// a reset firmware expect line numbers to start again from 1
this->sent = 0;
this->last_sent.clear();
/* Initialize debugger */
#ifdef DEBUG_SERIAL
fs.open("serial.txt", std::fstream::out | std::fstream::trunc);
#endif
// this gives some work to the io_service before it is started
// (post() runs the supplied function in its thread)
this->io.post(boost::bind(&GCodeSender::do_read, this));
// start reading in the background thread
boost::thread t(boost::bind(&boost::asio::io_service::run, &this->io));
this->background_thread.swap(t);
// always send a M105 to check for connection because firmware might be silent on connect
//FIXME Vojtech: This is being sent too early, leading to line number synchronization issues,
// from which the GCodeSender never recovers.
// this->send("M105", true);
return true;
}
void
GCodeSender::set_baud_rate(unsigned int baud_rate)
{
try {
// This does not support speeds > 115200
this->serial.set_option(boost::asio::serial_port_base::baud_rate(baud_rate));
} catch (boost::system::system_error &) {
boost::asio::serial_port::native_handle_type handle = this->serial.native_handle();
#if __APPLE__
termios ios;
::tcgetattr(handle, &ios);
::cfsetspeed(&ios, baud_rate);
speed_t newSpeed = baud_rate;
ioctl(handle, IOSSIOSPEED, &newSpeed);
::tcsetattr(handle, TCSANOW, &ios);
#elif __linux
termios2 ios;
if (ioctl(handle, TCGETS2, &ios))
printf("Error in TCGETS2: %s\n", strerror(errno));
ios.c_ispeed = ios.c_ospeed = baud_rate;
ios.c_cflag &= ~CBAUD;
ios.c_cflag |= BOTHER | CLOCAL | CREAD;
ios.c_cc[VMIN] = 1; // Minimum of characters to read, prevents eof errors when 0 bytes are read
ios.c_cc[VTIME] = 1;
if (ioctl(handle, TCSETS2, &ios))
printf("Error in TCSETS2: %s\n", strerror(errno));
#elif __OpenBSD__
struct termios ios;
::tcgetattr(handle, &ios);
::cfsetspeed(&ios, baud_rate);
if (::tcsetattr(handle, TCSAFLUSH, &ios) != 0)
printf("Failed to set baud rate: %s\n", strerror(errno));
#else
//throw invalid_argument ("OS does not currently support custom bauds");
#endif
}
}
void
GCodeSender::disconnect()
{
if (!this->open) return;
this->open = false;
this->connected = false;
this->io.post(boost::bind(&GCodeSender::do_close, this));
this->background_thread.join();
this->io.reset();
/*
if (this->error_status()) {
throw(boost::system::system_error(boost::system::error_code(),
"Error while closing the device"));
}
*/
#ifdef DEBUG_SERIAL
fs << "DISCONNECTED" << std::endl << std::flush;
fs.close();
#endif
}
bool
GCodeSender::is_connected() const
{
return this->connected;
}
bool
GCodeSender::wait_connected(unsigned int timeout) const
{
using namespace boost::posix_time;
ptime t0 = second_clock::local_time() + seconds(timeout);
while (!this->connected) {
if (second_clock::local_time() > t0) return false;
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
}
return true;
}
size_t
GCodeSender::queue_size() const
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
return this->queue.size();
}
void
GCodeSender::pause_queue()
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->queue_paused = true;
}
void
GCodeSender::resume_queue()
{
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->queue_paused = false;
}
this->send();
}
void
GCodeSender::purge_queue(bool priority)
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
if (priority) {
// clear priority queue
std::list<std::string> empty;
std::swap(this->priqueue, empty);
} else {
// clear queue
std::queue<std::string> empty;
std::swap(this->queue, empty);
this->queue_paused = false;
}
}
// purge log and return its contents
std::vector<std::string>
GCodeSender::purge_log()
{
boost::lock_guard<boost::mutex> l(this->log_mutex);
std::vector<std::string> retval;
retval.reserve(this->log.size());
while (!this->log.empty()) {
retval.push_back(this->log.front());
this->log.pop();
}
return retval;
}
std::string
GCodeSender::getT() const
{
boost::lock_guard<boost::mutex> l(this->log_mutex);
return this->T;
}
std::string
GCodeSender::getB() const
{
boost::lock_guard<boost::mutex> l(this->log_mutex);
return this->B;
}
void
GCodeSender::do_close()
{
this->set_error_status(false);
boost::system::error_code ec;
this->serial.cancel(ec);
if (ec) this->set_error_status(true);
this->serial.close(ec);
if (ec) this->set_error_status(true);
}
void
GCodeSender::set_error_status(bool e)
{
boost::lock_guard<boost::mutex> l(this->error_mutex);
this->error = e;
}
bool
GCodeSender::error_status() const
{
boost::lock_guard<boost::mutex> l(this->error_mutex);
return this->error;
}
void
GCodeSender::do_read()
{
// read one line
boost::asio::async_read_until(
this->serial,
this->read_buffer,
'\n',
boost::bind(
&GCodeSender::on_read,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
void
GCodeSender::on_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
this->set_error_status(false);
if (error) {
#ifdef __APPLE__
if (error.value() == 45) {
// OS X bug: http://osdir.com/ml/lib.boost.asio.user/2008-08/msg00004.html
this->do_read();
return;
}
#endif
// printf("ERROR: [%d] %s\n", error.value(), error.message().c_str());
// error can be true even because the serial port was closed.
// In this case it is not a real error, so ignore.
if (this->open) {
this->do_close();
this->set_error_status(true);
}
return;
}
std::istream is(&this->read_buffer);
std::string line;
std::getline(is, line);
if (!line.empty()) {
#ifdef DEBUG_SERIAL
fs << "<< " << line << std::endl << std::flush;
#endif
// note that line might contain \r at its end
// parse incoming line
if (!this->connected
&& (boost::starts_with(line, "start")
|| boost::starts_with(line, "Grbl ")
|| boost::starts_with(line, "ok")
|| boost::contains(line, "T:"))) {
this->connected = true;
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->can_send = true;
}
this->send();
} else if (boost::starts_with(line, "ok")) {
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
this->can_send = true;
}
this->send();
} else if (boost::istarts_with(line, "resend") // Marlin uses "Resend: "
|| boost::istarts_with(line, "rs")) {
// extract the first number from line
boost::algorithm::trim_left_if(line, !boost::algorithm::is_digit());
size_t toresend = boost::lexical_cast<size_t>(line.substr(0, line.find_first_not_of("0123456789")));
#ifdef DEBUG_SERIAL
fs << "!! line num out of sync: toresend = " << toresend << ", sent = " << sent << ", last_sent.size = " << last_sent.size() << std::endl;
#endif
if (toresend > this->sent - this->last_sent.size() && toresend <= this->sent) {
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
const auto lines_to_resend = this->sent - toresend + 1;
#ifdef DEBUG_SERIAL
fs << "!! resending " << lines_to_resend << " lines" << std::endl;
#endif
// move the unsent lines to priqueue
this->priqueue.insert(
this->priqueue.begin(), // insert at the beginning
this->last_sent.begin() + this->last_sent.size() - lines_to_resend,
this->last_sent.end()
);
// we can empty last_sent because it's not useful anymore
this->last_sent.clear();
// start resending with the requested line number
this->sent = toresend - 1;
this->can_send = true;
}
this->send();
} else {
printf("Cannot resend " PRINTF_ZU " (oldest we have is " PRINTF_ZU ")\n", toresend, this->sent - this->last_sent.size());
}
} else if (boost::starts_with(line, "wait")) {
// ignore
} else {
// push any other line into the log
boost::lock_guard<boost::mutex> l(this->log_mutex);
this->log.push(line);
}
// parse temperature info
{
size_t pos = line.find("T:");
if (pos != std::string::npos && line.size() > pos + 2) {
// we got temperature info
boost::lock_guard<boost::mutex> l(this->log_mutex);
this->T = line.substr(pos+2, line.find_first_not_of("0123456789.", pos+2) - (pos+2));
pos = line.find("B:");
if (pos != std::string::npos && line.size() > pos + 2) {
// we got bed temperature info
this->B = line.substr(pos+2, line.find_first_not_of("0123456789.", pos+2) - (pos+2));
}
}
}
}
this->do_read();
}
void
GCodeSender::send(const std::vector<std::string> &lines, bool priority)
{
// append lines to queue
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
for (std::vector<std::string>::const_iterator line = lines.begin(); line != lines.end(); ++line) {
if (priority) {
this->priqueue.push_back(*line);
} else {
this->queue.push(*line);
}
}
}
this->send();
}
void
GCodeSender::send(const std::string &line, bool priority)
{
// append line to queue
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
if (priority) {
this->priqueue.push_back(line);
} else {
this->queue.push(line);
}
}
this->send();
}
void
GCodeSender::send()
{
this->io.post(boost::bind(&GCodeSender::do_send, this));
}
void
GCodeSender::do_send()
{
boost::lock_guard<boost::mutex> l(this->queue_mutex);
// printer is not connected or we're still waiting for the previous ack
if (!this->can_send) return;
std::string line;
while (!this->priqueue.empty() || (!this->queue.empty() && !this->queue_paused)) {
if (!this->priqueue.empty()) {
line = this->priqueue.front();
this->priqueue.pop_front();
} else {
line = this->queue.front();
this->queue.pop();
}
// strip comments
size_t comment_pos = line.find_first_of(';');
if (comment_pos != std::string::npos)
line.erase(comment_pos, std::string::npos);
boost::algorithm::trim(line);
// if line is not empty, send it
if (!line.empty()) break;
// if line is empty, process next item in queue
}
if (line.empty()) return;
// compute full line
++ this->sent;
#ifndef DEBUG_SERIAL
const auto line_num = this->sent;
#else
// In DEBUG_SERIAL mode, test line re-synchronization by sending bad line number 1/4 of the time
const auto line_num = std::rand() < RAND_MAX/4 ? 0 : this->sent;
#endif
std::string full_line = "N" + boost::lexical_cast<std::string>(line_num) + " " + line;
// calculate checksum
int cs = 0;
for (std::string::const_iterator it = full_line.begin(); it != full_line.end(); ++it)
cs = cs ^ *it;
// write line to device
full_line += "*";
full_line += boost::lexical_cast<std::string>(cs);
full_line += "\n";
#ifdef DEBUG_SERIAL
fs << ">> " << full_line << std::flush;
#endif
this->last_sent.push_back(line);
this->can_send = false;
while (this->last_sent.size() > KEEP_SENT) {
this->last_sent.pop_front();
}
// we can't supply boost::asio::buffer(full_line) to async_write() because full_line is on the
// stack and the buffer would lose its underlying storage causing memory corruption
std::ostream os(&this->write_buffer);
os << full_line;
boost::asio::async_write(this->serial, this->write_buffer, boost::bind(&GCodeSender::on_write, this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void
GCodeSender::on_write(const boost::system::error_code& error,
size_t bytes_transferred)
{
this->set_error_status(false);
if (error) {
if (this->open) {
this->do_close();
this->set_error_status(true);
}
return;
}
this->do_send();
}
void
GCodeSender::set_DTR(bool on)
{
#if defined(_WIN32) && !defined(__SYMBIAN32__)
boost::asio::serial_port_service::native_handle_type handle = this->serial.native_handle();
if (on)
EscapeCommFunction(handle, SETDTR);
else
EscapeCommFunction(handle, CLRDTR);
#else
int fd = this->serial.native_handle();
int status;
ioctl(fd, TIOCMGET, &status);
if (on)
status |= TIOCM_DTR;
else
status &= ~TIOCM_DTR;
ioctl(fd, TIOCMSET, &status);
#endif
}
void
GCodeSender::reset()
{
set_DTR(false);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
set_DTR(true);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
set_DTR(false);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
} // namespace Slic3r

View file

@ -0,0 +1,73 @@
#ifndef slic3r_GCodeSender_hpp_
#define slic3r_GCodeSender_hpp_
#include "libslic3r.h"
#include <queue>
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
namespace Slic3r {
namespace asio = boost::asio;
class GCodeSender : private boost::noncopyable {
public:
GCodeSender();
~GCodeSender();
bool connect(std::string devname, unsigned int baud_rate);
void send(const std::vector<std::string> &lines, bool priority = false);
void send(const std::string &s, bool priority = false);
void disconnect();
bool error_status() const;
bool is_connected() const;
bool wait_connected(unsigned int timeout = 3) const;
size_t queue_size() const;
void pause_queue();
void resume_queue();
void purge_queue(bool priority = false);
std::vector<std::string> purge_log();
std::string getT() const;
std::string getB() const;
void set_DTR(bool on);
void reset();
private:
asio::io_service io;
asio::serial_port serial;
boost::thread background_thread;
boost::asio::streambuf read_buffer, write_buffer;
bool open; // whether the serial socket is connected
bool connected; // whether the printer is online
bool error;
mutable boost::mutex error_mutex;
// this mutex guards queue, priqueue, can_send, queue_paused, sent, last_sent
mutable boost::mutex queue_mutex;
std::queue<std::string> queue;
std::list<std::string> priqueue;
bool can_send;
bool queue_paused;
size_t sent;
std::deque<std::string> last_sent;
// this mutex guards log, T, B
mutable boost::mutex log_mutex;
std::queue<std::string> log;
std::string T, B;
void set_baud_rate(unsigned int baud_rate);
void set_error_status(bool e);
void do_send();
void on_write(const boost::system::error_code& error, size_t bytes_transferred);
void do_close();
void do_read();
void on_read(const boost::system::error_code& error, size_t bytes_transferred);
void send();
};
} // namespace Slic3r
#endif /* slic3r_GCodeSender_hpp_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,436 @@
#ifndef slic3r_GCodeTimeEstimator_hpp_
#define slic3r_GCodeTimeEstimator_hpp_
#include "libslic3r.h"
#include "PrintConfig.hpp"
#include "GCodeReader.hpp"
#define ENABLE_MOVE_STATS 0
namespace Slic3r {
//
// Some of the algorithms used by class GCodeTimeEstimator were inpired by
// Cura Engine's class TimeEstimateCalculator
// https://github.com/Ultimaker/CuraEngine/blob/master/src/timeEstimate.h
//
class GCodeTimeEstimator
{
public:
static const std::string Normal_First_M73_Output_Placeholder_Tag;
static const std::string Silent_First_M73_Output_Placeholder_Tag;
enum EMode : unsigned char
{
Normal,
Silent
};
enum EUnits : unsigned char
{
Millimeters,
Inches
};
enum EAxis : unsigned char
{
X,
Y,
Z,
E,
Num_Axis
};
enum EPositioningType : unsigned char
{
Absolute,
Relative
};
private:
struct Axis
{
float position; // mm
float max_feedrate; // mm/s
float max_acceleration; // mm/s^2
float max_jerk; // mm/s
};
struct Feedrates
{
float feedrate; // mm/s
float axis_feedrate[Num_Axis]; // mm/s
float abs_axis_feedrate[Num_Axis]; // mm/s
float safe_feedrate; // mm/s
void reset();
};
struct State
{
GCodeFlavor dialect;
EUnits units;
EPositioningType global_positioning_type;
EPositioningType e_local_positioning_type;
Axis axis[Num_Axis];
float feedrate; // mm/s
float acceleration; // mm/s^2
// hard limit for the acceleration, to which the firmware will clamp.
float max_acceleration; // mm/s^2
float retract_acceleration; // mm/s^2
float additional_time; // s
float minimum_feedrate; // mm/s
float minimum_travel_feedrate; // mm/s
float extrude_factor_override_percentage;
// Additional load / unload times for a filament exchange sequence.
std::vector<float> filament_load_times;
std::vector<float> filament_unload_times;
unsigned int g1_line_id;
// extruder_id is currently used to correctly calculate filament load / unload times
// into the total print time. This is currently only really used by the MK3 MMU2:
// Extruder id (-1) means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit.
static const unsigned int extruder_id_unloaded = (unsigned int)-1;
unsigned int extruder_id;
};
public:
struct Block
{
#if ENABLE_MOVE_STATS
enum EMoveType : unsigned char
{
Noop,
Retract,
Unretract,
Tool_change,
Move,
Extrude,
Num_Types
};
#endif // ENABLE_MOVE_STATS
struct FeedrateProfile
{
float entry; // mm/s
float cruise; // mm/s
float exit; // mm/s
};
struct Trapezoid
{
float distance; // mm
float accelerate_until; // mm
float decelerate_after; // mm
FeedrateProfile feedrate;
float acceleration_time(float acceleration) const;
float cruise_time() const;
float deceleration_time(float acceleration) const;
float cruise_distance() const;
// This function gives the time needed to accelerate from an initial speed to reach a final distance.
static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration);
// This function gives the final speed while accelerating at the given constant acceleration from the given initial speed along the given distance.
static float speed_from_distance(float initial_feedrate, float distance, float acceleration);
};
struct Flags
{
bool recalculate;
bool nominal_length;
};
#if ENABLE_MOVE_STATS
EMoveType move_type;
#endif // ENABLE_MOVE_STATS
Flags flags;
float delta_pos[Num_Axis]; // mm
float acceleration; // mm/s^2
float max_entry_speed; // mm/s
float safe_feedrate; // mm/s
FeedrateProfile feedrate;
Trapezoid trapezoid;
float elapsed_time;
Block();
// Returns the length of the move covered by this block, in mm
float move_length() const;
// Returns true if this block is a retract/unretract move only
float is_extruder_only_move() const;
// Returns true if this block is a move with no extrusion
float is_travel_move() const;
// Returns the time spent accelerating toward cruise speed, in seconds
float acceleration_time() const;
// Returns the time spent at cruise speed, in seconds
float cruise_time() const;
// Returns the time spent decelerating from cruise speed, in seconds
float deceleration_time() const;
// Returns the distance covered at cruise speed, in mm
float cruise_distance() const;
// Calculates this block's trapezoid
void calculate_trapezoid();
// Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the
// acceleration within the allotted distance.
static float max_allowable_speed(float acceleration, float target_velocity, float distance);
// Calculates the distance (not time) it takes to accelerate from initial_rate to target_rate using the given acceleration:
static float estimate_acceleration_distance(float initial_rate, float target_rate, float acceleration);
// This function gives you the point at which you must start braking (at the rate of -acceleration) if
// you started at speed initial_rate and accelerated until this point and want to end at the final_rate after
// a total travel of distance. This can be used to compute the intersection point between acceleration and
// deceleration in the cases where the trapezoid has no plateau (i.e. never reaches maximum speed)
static float intersection_distance(float initial_rate, float final_rate, float acceleration, float distance);
};
typedef std::vector<Block> BlocksList;
#if ENABLE_MOVE_STATS
struct MoveStats
{
unsigned int count;
float time;
MoveStats();
};
typedef std::map<Block::EMoveType, MoveStats> MovesStatsMap;
#endif // ENABLE_MOVE_STATS
typedef std::map<unsigned int, unsigned int> G1LineIdToBlockIdMap;
private:
EMode _mode;
GCodeReader _parser;
State _state;
Feedrates _curr;
Feedrates _prev;
BlocksList _blocks;
// Map between g1 line id and blocks id, used to speed up export of remaining times
G1LineIdToBlockIdMap _g1_line_ids;
// Index of the last block already st_synchronized
int _last_st_synchronized_block_id;
float _time; // s
#if ENABLE_MOVE_STATS
MovesStatsMap _moves_stats;
#endif // ENABLE_MOVE_STATS
public:
explicit GCodeTimeEstimator(EMode mode);
// Adds the given gcode line
void add_gcode_line(const std::string& gcode_line);
void add_gcode_block(const char *ptr);
void add_gcode_block(const std::string &str) { this->add_gcode_block(str.c_str()); }
// Calculates the time estimate from the gcode lines added using add_gcode_line() or add_gcode_block()
// start_from_beginning:
// if set to true all blocks will be used to calculate the time estimate,
// if set to false only the blocks not yet processed will be used and the calculated time will be added to the current calculated time
void calculate_time(bool start_from_beginning);
// Calculates the time estimate from the given gcode in string format
void calculate_time_from_text(const std::string& gcode);
// Calculates the time estimate from the gcode contained in the file with the given filename
void calculate_time_from_file(const std::string& file);
// Calculates the time estimate from the gcode contained in given list of gcode lines
void calculate_time_from_lines(const std::vector<std::string>& gcode_lines);
// Process the gcode contained in the file with the given filename,
// placing in it new lines (M73) containing the remaining time, at the given interval in seconds
// and saving the result back in the same file
// This time estimator should have been already used to calculate the time estimate for the gcode
// contained in the given file before to call this method
bool post_process_remaining_times(const std::string& filename, float interval_sec);
// Set current position on the given axis with the given value
void set_axis_position(EAxis axis, float position);
void set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec);
void set_axis_max_acceleration(EAxis axis, float acceleration);
void set_axis_max_jerk(EAxis axis, float jerk);
// Returns current position on the given axis
float get_axis_position(EAxis axis) const;
float get_axis_max_feedrate(EAxis axis) const;
float get_axis_max_acceleration(EAxis axis) const;
float get_axis_max_jerk(EAxis axis) const;
void set_feedrate(float feedrate_mm_sec);
float get_feedrate() const;
void set_acceleration(float acceleration_mm_sec2);
float get_acceleration() const;
// Maximum acceleration for the machine. The firmware simulator will clamp the M204 Sxxx to this maximum.
void set_max_acceleration(float acceleration_mm_sec2);
float get_max_acceleration() const;
void set_retract_acceleration(float acceleration_mm_sec2);
float get_retract_acceleration() const;
void set_minimum_feedrate(float feedrate_mm_sec);
float get_minimum_feedrate() const;
void set_minimum_travel_feedrate(float feedrate_mm_sec);
float get_minimum_travel_feedrate() const;
void set_filament_load_times(const std::vector<double> &filament_load_times);
void set_filament_unload_times(const std::vector<double> &filament_unload_times);
float get_filament_load_time(unsigned int id_extruder);
float get_filament_unload_time(unsigned int id_extruder);
void set_extrude_factor_override_percentage(float percentage);
float get_extrude_factor_override_percentage() const;
void set_dialect(GCodeFlavor dialect);
GCodeFlavor get_dialect() const;
void set_units(EUnits units);
EUnits get_units() const;
void set_global_positioning_type(EPositioningType type);
EPositioningType get_global_positioning_type() const;
void set_e_local_positioning_type(EPositioningType type);
EPositioningType get_e_local_positioning_type() const;
int get_g1_line_id() const;
void increment_g1_line_id();
void reset_g1_line_id();
void set_extruder_id(unsigned int id);
unsigned int get_extruder_id() const;
void reset_extruder_id();
void add_additional_time(float timeSec);
void set_additional_time(float timeSec);
float get_additional_time() const;
void set_default();
// Call this method before to start adding lines using add_gcode_line() when reusing an instance of GCodeTimeEstimator
void reset();
// Returns the estimated time, in seconds
float get_time() const;
// Returns the estimated time, in format DDd HHh MMm SSs
std::string get_time_dhms() const;
// Returns the estimated time, in minutes (integer)
std::string get_time_minutes() const;
private:
void _reset();
void _reset_time();
void _reset_blocks();
// Calculates the time estimate
void _calculate_time();
// Processes the given gcode line
void _process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line);
// Move
void _processG1(const GCodeReader::GCodeLine& line);
// Dwell
void _processG4(const GCodeReader::GCodeLine& line);
// Set Units to Inches
void _processG20(const GCodeReader::GCodeLine& line);
// Set Units to Millimeters
void _processG21(const GCodeReader::GCodeLine& line);
// Move to Origin (Home)
void _processG28(const GCodeReader::GCodeLine& line);
// Set to Absolute Positioning
void _processG90(const GCodeReader::GCodeLine& line);
// Set to Relative Positioning
void _processG91(const GCodeReader::GCodeLine& line);
// Set Position
void _processG92(const GCodeReader::GCodeLine& line);
// Sleep or Conditional stop
void _processM1(const GCodeReader::GCodeLine& line);
// Set extruder to absolute mode
void _processM82(const GCodeReader::GCodeLine& line);
// Set extruder to relative mode
void _processM83(const GCodeReader::GCodeLine& line);
// Set Extruder Temperature and Wait
void _processM109(const GCodeReader::GCodeLine& line);
// Set max printing acceleration
void _processM201(const GCodeReader::GCodeLine& line);
// Set maximum feedrate
void _processM203(const GCodeReader::GCodeLine& line);
// Set default acceleration
void _processM204(const GCodeReader::GCodeLine& line);
// Advanced settings
void _processM205(const GCodeReader::GCodeLine& line);
// Set extrude factor override percentage
void _processM221(const GCodeReader::GCodeLine& line);
// Set allowable instantaneous speed change
void _processM566(const GCodeReader::GCodeLine& line);
// Unload the current filament into the MK3 MMU2 unit at the end of print.
void _processM702(const GCodeReader::GCodeLine& line);
// Processes T line (Select Tool)
void _processT(const GCodeReader::GCodeLine& line);
// Simulates firmware st_synchronize() call
void _simulate_st_synchronize();
void _forward_pass();
void _reverse_pass();
void _planner_forward_pass_kernel(Block& prev, Block& curr);
void _planner_reverse_pass_kernel(Block& curr, Block& next);
void _recalculate_trapezoids();
// Returns the given time is seconds in format DDd HHh MMm SSs
static std::string _get_time_dhms(float time_in_secs);
// Returns the given, in minutes (integer)
static std::string _get_time_minutes(float time_in_secs);
#if ENABLE_MOVE_STATS
void _log_moves_stats() const;
#endif // ENABLE_MOVE_STATS
};
} /* namespace Slic3r */
#endif /* slic3r_GCodeTimeEstimator_hpp_ */

View file

@ -0,0 +1,509 @@
#include "GCodeWriter.hpp"
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <assert.h>
#define FLAVOR_IS(val) this->config.gcode_flavor == val
#define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val
#define COMMENT(comment) if (this->config.gcode_comments && !comment.empty()) gcode << " ; " << comment;
#define PRECISION(val, precision) std::fixed << std::setprecision(precision) << val
#define XYZF_NUM(val) PRECISION(val, 3)
#define E_NUM(val) PRECISION(val, 5)
namespace Slic3r {
void GCodeWriter::apply_print_config(const PrintConfig &print_config)
{
this->config.apply(print_config, true);
m_extrusion_axis = this->config.get_extrusion_axis();
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
m_max_acceleration = (print_config.gcode_flavor.value == gcfMarlin) ?
print_config.machine_max_acceleration_extruding.values.front() : 0;
}
void GCodeWriter::set_extruders(const std::vector<unsigned int> &extruder_ids)
{
m_extruders.clear();
m_extruders.reserve(extruder_ids.size());
for (unsigned int extruder_id : extruder_ids)
m_extruders.emplace_back(Extruder(extruder_id, &this->config));
/* we enable support for multiple extruder if any extruder greater than 0 is used
(even if prints only uses that one) since we need to output Tx commands
first extruder has index 0 */
this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0;
}
std::string GCodeWriter::preamble()
{
std::ostringstream gcode;
if (FLAVOR_IS_NOT(gcfMakerWare)) {
gcode << "G21 ; set units to millimeters\n";
gcode << "G90 ; use absolute coordinates\n";
}
if (FLAVOR_IS(gcfRepRap) || FLAVOR_IS(gcfMarlin) || FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepetier) || FLAVOR_IS(gcfSmoothie)) {
if (this->config.use_relative_e_distances) {
gcode << "M83 ; use relative distances for extrusion\n";
} else {
gcode << "M82 ; use absolute distances for extrusion\n";
}
gcode << this->reset_e(true);
}
return gcode.str();
}
std::string GCodeWriter::postamble() const
{
std::ostringstream gcode;
if (FLAVOR_IS(gcfMachinekit))
gcode << "M2 ; end of program\n";
return gcode.str();
}
std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) const
{
if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)))
return "";
std::string code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
code = "M109";
comment = "set temperature and wait for it to be reached";
} else {
code = "M104";
comment = "set temperature";
}
std::ostringstream gcode;
gcode << code << " ";
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
gcode << "P";
} else {
gcode << "S";
}
gcode << temperature;
if (tool != -1 &&
( (this->multiple_extruders && ! m_single_extruder_multi_material) ||
FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) ) {
gcode << " T" << tool;
}
gcode << " ; " << comment << "\n";
if (FLAVOR_IS(gcfTeacup) && wait)
gcode << "M116 ; wait for temperature to be reached\n";
return gcode.str();
}
std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait)
{
if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached))
return std::string();
m_last_bed_temperature = temperature;
m_last_bed_temperature_reached = wait;
std::string code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
code = "M109";
} else {
code = "M190";
}
comment = "set bed temperature and wait for it to be reached";
} else {
code = "M140";
comment = "set bed temperature";
}
std::ostringstream gcode;
gcode << code << " ";
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
gcode << "P";
} else {
gcode << "S";
}
gcode << temperature << " ; " << comment << "\n";
if (FLAVOR_IS(gcfTeacup) && wait)
gcode << "M116 ; wait for bed temperature to be reached\n";
return gcode.str();
}
std::string GCodeWriter::set_fan(unsigned int speed, bool dont_save)
{
std::ostringstream gcode;
if (m_last_fan_speed != speed || dont_save) {
if (!dont_save) m_last_fan_speed = speed;
if (speed == 0) {
if (FLAVOR_IS(gcfTeacup)) {
gcode << "M106 S0";
} else if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
gcode << "M127";
} else {
gcode << "M107";
}
if (this->config.gcode_comments) gcode << " ; disable fan";
gcode << "\n";
} else {
if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
gcode << "M126";
} else {
gcode << "M106 ";
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
gcode << "P";
} else {
gcode << "S";
}
gcode << (255.0 * speed / 100.0);
}
if (this->config.gcode_comments) gcode << " ; enable fan";
gcode << "\n";
}
}
return gcode.str();
}
std::string GCodeWriter::set_acceleration(unsigned int acceleration)
{
// Clamp the acceleration to the allowed maximum.
if (m_max_acceleration > 0 && acceleration > m_max_acceleration)
acceleration = m_max_acceleration;
if (acceleration == 0 || acceleration == m_last_acceleration)
return std::string();
m_last_acceleration = acceleration;
std::ostringstream gcode;
if (FLAVOR_IS(gcfRepetier)) {
// M201: Set max printing acceleration
gcode << "M201 X" << acceleration << " Y" << acceleration;
if (this->config.gcode_comments) gcode << " ; adjust acceleration";
gcode << "\n";
// M202: Set max travel acceleration
gcode << "M202 X" << acceleration << " Y" << acceleration;
} else {
// M204: Set default acceleration
gcode << "M204 S" << acceleration;
}
if (this->config.gcode_comments) gcode << " ; adjust acceleration";
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::reset_e(bool force)
{
if (FLAVOR_IS(gcfMach3)
|| FLAVOR_IS(gcfMakerWare)
|| FLAVOR_IS(gcfSailfish))
return "";
if (m_extruder != nullptr) {
if (m_extruder->E() == 0. && ! force)
return "";
m_extruder->reset_E();
}
if (! m_extrusion_axis.empty() && ! this->config.use_relative_e_distances) {
std::ostringstream gcode;
gcode << "G92 " << m_extrusion_axis << "0";
if (this->config.gcode_comments) gcode << " ; reset extrusion distance";
gcode << "\n";
return gcode.str();
} else {
return "";
}
}
std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) const
{
if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish))
return "";
unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5);
if (!allow_100) percent = std::min(percent, (unsigned int)99);
std::ostringstream gcode;
gcode << "M73 P" << percent;
if (this->config.gcode_comments) gcode << " ; update progress";
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::toolchange_prefix() const
{
return FLAVOR_IS(gcfMakerWare) ? "M135 T" :
FLAVOR_IS(gcfSailfish) ? "M108 T" : "T";
}
std::string GCodeWriter::toolchange(unsigned int extruder_id)
{
// set the new extruder
auto it_extruder = std::lower_bound(m_extruders.begin(), m_extruders.end(), Extruder::key(extruder_id));
assert(it_extruder != m_extruders.end());
m_extruder = const_cast<Extruder*>(&*it_extruder);
// return the toolchange command
// if we are running a single-extruder setup, just set the extruder and return nothing
std::ostringstream gcode;
if (this->multiple_extruders) {
gcode << this->toolchange_prefix() << extruder_id;
if (this->config.gcode_comments)
gcode << " ; change extruder";
gcode << "\n";
gcode << this->reset_e(true);
}
return gcode.str();
}
std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const
{
assert(F > 0.);
assert(F < 100000.);
std::ostringstream gcode;
gcode << "G1 F" << F;
COMMENT(comment);
gcode << cooling_marker;
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
{
m_pos(0) = point(0);
m_pos(1) = point(1);
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " F" << XYZF_NUM(this->config.travel_speed.value * 60.0);
COMMENT(comment);
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment)
{
/* If target Z is lower than current Z but higher than nominal Z we
don't perform the Z move but we only move in the XY plane and
adjust the nominal Z by reducing the lift amount that will be
used for unlift. */
if (!this->will_move_z(point(2))) {
double nominal_z = m_pos(2) - m_lifted;
m_lifted = m_lifted - (point(2) - nominal_z);
return this->travel_to_xy(to_2d(point));
}
/* In all the other cases, we perform an actual XYZ move and cancel
the lift. */
m_lifted = 0;
m_pos = point;
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " Z" << XYZF_NUM(point(2))
<< " F" << XYZF_NUM(this->config.travel_speed.value * 60.0);
COMMENT(comment);
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
{
/* If target Z is lower than current Z but higher than nominal Z
we don't perform the move but we only adjust the nominal Z by
reducing the lift amount that will be used for unlift. */
if (!this->will_move_z(z)) {
double nominal_z = m_pos(2) - m_lifted;
m_lifted = m_lifted - (z - nominal_z);
return "";
}
/* In all the other cases, we perform an actual Z move and cancel
the lift. */
m_lifted = 0;
return this->_travel_to_z(z, comment);
}
std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
{
m_pos(2) = z;
std::ostringstream gcode;
gcode << "G1 Z" << XYZF_NUM(z)
<< " F" << XYZF_NUM(this->config.travel_speed.value * 60.0);
COMMENT(comment);
gcode << "\n";
return gcode.str();
}
bool GCodeWriter::will_move_z(double z) const
{
/* If target Z is lower than current Z but higher than nominal Z
we don't perform an actual Z move. */
if (m_lifted > 0) {
double nominal_z = m_pos(2) - m_lifted;
if (z >= nominal_z && z <= m_pos(2))
return false;
}
return true;
}
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment)
{
m_pos(0) = point(0);
m_pos(1) = point(1);
m_extruder->extrude(dE);
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " " << m_extrusion_axis << E_NUM(m_extruder->E());
COMMENT(comment);
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment)
{
m_pos = point;
m_lifted = 0;
m_extruder->extrude(dE);
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " Z" << XYZF_NUM(point(2))
<< " " << m_extrusion_axis << E_NUM(m_extruder->E());
COMMENT(comment);
gcode << "\n";
return gcode.str();
}
std::string GCodeWriter::retract(bool before_wipe)
{
double factor = before_wipe ? m_extruder->retract_before_wipe() : 1.;
assert(factor >= 0. && factor <= 1. + EPSILON);
return this->_retract(
factor * m_extruder->retract_length(),
factor * m_extruder->retract_restart_extra(),
"retract"
);
}
std::string GCodeWriter::retract_for_toolchange(bool before_wipe)
{
double factor = before_wipe ? m_extruder->retract_before_wipe() : 1.;
assert(factor >= 0. && factor <= 1. + EPSILON);
return this->_retract(
factor * m_extruder->retract_length_toolchange(),
factor * m_extruder->retract_restart_extra_toolchange(),
"retract for toolchange"
);
}
std::string GCodeWriter::_retract(double length, double restart_extra, const std::string &comment)
{
std::ostringstream gcode;
/* If firmware retraction is enabled, we use a fake value of 1
since we ignore the actual configured retract_length which
might be 0, in which case the retraction logic gets skipped. */
if (this->config.use_firmware_retraction) length = 1;
// If we use volumetric E values we turn lengths into volumes */
if (this->config.use_volumetric_e) {
double d = m_extruder->filament_diameter();
double area = d * d * PI/4;
length = length * area;
restart_extra = restart_extra * area;
}
double dE = m_extruder->retract(length, restart_extra);
if (dE != 0) {
if (this->config.use_firmware_retraction) {
if (FLAVOR_IS(gcfMachinekit))
gcode << "G22 ; retract\n";
else
gcode << "G10 ; retract\n";
} else {
gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E())
<< " F" << float(m_extruder->retract_speed() * 60.);
COMMENT(comment);
gcode << "\n";
}
}
if (FLAVOR_IS(gcfMakerWare))
gcode << "M103 ; extruder off\n";
return gcode.str();
}
std::string GCodeWriter::unretract()
{
std::ostringstream gcode;
if (FLAVOR_IS(gcfMakerWare))
gcode << "M101 ; extruder on\n";
double dE = m_extruder->unretract();
if (dE != 0) {
if (this->config.use_firmware_retraction) {
if (FLAVOR_IS(gcfMachinekit))
gcode << "G23 ; unretract\n";
else
gcode << "G11 ; unretract\n";
gcode << this->reset_e();
} else {
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E())
<< " F" << float(m_extruder->deretract_speed() * 60.);
if (this->config.gcode_comments) gcode << " ; unretract";
gcode << "\n";
}
}
return gcode.str();
}
/* If this method is called more than once before calling unlift(),
it will not perform subsequent lifts, even if Z was raised manually
(i.e. with travel_to_z()) and thus _lifted was reduced. */
std::string GCodeWriter::lift()
{
// check whether the above/below conditions are met
double target_lift = 0;
{
double above = this->config.retract_lift_above.get_at(m_extruder->id());
double below = this->config.retract_lift_below.get_at(m_extruder->id());
if (m_pos(2) >= above && (below == 0 || m_pos(2) <= below))
target_lift = this->config.retract_lift.get_at(m_extruder->id());
}
if (m_lifted == 0 && target_lift > 0) {
m_lifted = target_lift;
return this->_travel_to_z(m_pos(2) + target_lift, "lift Z");
}
return "";
}
std::string GCodeWriter::unlift()
{
std::string gcode;
if (m_lifted > 0) {
gcode += this->_travel_to_z(m_pos(2) - m_lifted, "restore layer Z");
m_lifted = 0;
}
return gcode;
}
}

View file

@ -0,0 +1,92 @@
#ifndef slic3r_GCodeWriter_hpp_
#define slic3r_GCodeWriter_hpp_
#include "libslic3r.h"
#include <string>
#include "Extruder.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "GCode/CoolingBuffer.hpp"
namespace Slic3r {
class GCodeWriter {
public:
GCodeConfig config;
bool multiple_extruders;
GCodeWriter() :
multiple_extruders(false), m_extrusion_axis("E"), m_extruder(nullptr),
m_single_extruder_multi_material(false),
m_last_acceleration(0), m_max_acceleration(0), m_last_fan_speed(0),
m_last_bed_temperature(0), m_last_bed_temperature_reached(true),
m_lifted(0)
{}
Extruder* extruder() { return m_extruder; }
const Extruder* extruder() const { return m_extruder; }
std::string extrusion_axis() const { return m_extrusion_axis; }
void apply_print_config(const PrintConfig &print_config);
// Extruders are expected to be sorted in an increasing order.
void set_extruders(const std::vector<unsigned int> &extruder_ids);
const std::vector<Extruder>& extruders() const { return m_extruders; }
std::vector<unsigned int> extruder_ids() const {
std::vector<unsigned int> out;
out.reserve(m_extruders.size());
for (const Extruder &e : m_extruders)
out.push_back(e.id());
return out;
}
std::string preamble();
std::string postamble() const;
std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1) const;
std::string set_bed_temperature(unsigned int temperature, bool wait = false);
std::string set_fan(unsigned int speed, bool dont_save = false);
std::string set_acceleration(unsigned int acceleration);
std::string reset_e(bool force = false);
std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const;
// return false if this extruder was already selected
bool need_toolchange(unsigned int extruder_id) const
{ return m_extruder == nullptr || m_extruder->id() != extruder_id; }
std::string set_extruder(unsigned int extruder_id)
{ return this->need_toolchange(extruder_id) ? this->toolchange(extruder_id) : ""; }
// Prefix of the toolchange G-code line, to be used by the CoolingBuffer to separate sections of the G-code
// printed with the same extruder.
std::string toolchange_prefix() const;
std::string toolchange(unsigned int extruder_id);
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const;
std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string());
std::string travel_to_z(double z, const std::string &comment = std::string());
bool will_move_z(double z) const;
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string());
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
std::string retract(bool before_wipe = false);
std::string retract_for_toolchange(bool before_wipe = false);
std::string unretract();
std::string lift();
std::string unlift();
Vec3d get_position() const { return m_pos; }
private:
std::vector<Extruder> m_extruders;
std::string m_extrusion_axis;
bool m_single_extruder_multi_material;
Extruder* m_extruder;
unsigned int m_last_acceleration;
// Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware.
// If set to zero, the limit is not in action.
unsigned int m_max_acceleration;
unsigned int m_last_fan_speed;
unsigned int m_last_bed_temperature;
bool m_last_bed_temperature_reached;
double m_lifted;
Vec3d m_pos = Vec3d::Zero();
std::string _travel_to_z(double z, const std::string &comment);
std::string _retract(double length, double restart_extra, const std::string &comment);
};
} /* namespace Slic3r */
#endif /* slic3r_GCodeWriter_hpp_ */

1169
src/libslic3r/Geometry.cpp Normal file

File diff suppressed because it is too large Load diff

162
src/libslic3r/Geometry.hpp Normal file
View file

@ -0,0 +1,162 @@
#ifndef slic3r_Geometry_hpp_
#define slic3r_Geometry_hpp_
#include "libslic3r.h"
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
#include "boost/polygon/voronoi.hpp"
using boost::polygon::voronoi_builder;
using boost::polygon::voronoi_diagram;
namespace Slic3r { namespace Geometry {
// Generic result of an orientation predicate.
enum Orientation
{
ORIENTATION_CCW = 1,
ORIENTATION_CW = -1,
ORIENTATION_COLINEAR = 0
};
// Return orientation of the three points (clockwise, counter-clockwise, colinear)
// The predicate is exact for the coord_t type, using 64bit signed integers for the temporaries.
// which means, the coord_t types must not have some of the topmost bits utilized.
// As the points are limited to 30 bits + signum,
// the temporaries u, v, w are limited to 61 bits + signum,
// and d is limited to 63 bits + signum and we are good.
static inline Orientation orient(const Point &a, const Point &b, const Point &c)
{
// BOOST_STATIC_ASSERT(sizeof(coord_t) * 2 == sizeof(int64_t));
int64_t u = int64_t(b(0)) * int64_t(c(1)) - int64_t(b(1)) * int64_t(c(0));
int64_t v = int64_t(a(0)) * int64_t(c(1)) - int64_t(a(1)) * int64_t(c(0));
int64_t w = int64_t(a(0)) * int64_t(b(1)) - int64_t(a(1)) * int64_t(b(0));
int64_t d = u - v + w;
return (d > 0) ? ORIENTATION_CCW : ((d == 0) ? ORIENTATION_COLINEAR : ORIENTATION_CW);
}
// Return orientation of the polygon by checking orientation of the left bottom corner of the polygon
// using exact arithmetics. The input polygon must not contain duplicate points
// (or at least the left bottom corner point must not have duplicates).
static inline bool is_ccw(const Polygon &poly)
{
// The polygon shall be at least a triangle.
assert(poly.points.size() >= 3);
if (poly.points.size() < 3)
return true;
// 1) Find the lowest lexicographical point.
unsigned int imin = 0;
for (unsigned int i = 1; i < poly.points.size(); ++ i) {
const Point &pmin = poly.points[imin];
const Point &p = poly.points[i];
if (p(0) < pmin(0) || (p(0) == pmin(0) && p(1) < pmin(1)))
imin = i;
}
// 2) Detect the orientation of the corner imin.
size_t iPrev = ((imin == 0) ? poly.points.size() : imin) - 1;
size_t iNext = ((imin + 1 == poly.points.size()) ? 0 : imin + 1);
Orientation o = orient(poly.points[iPrev], poly.points[imin], poly.points[iNext]);
// The lowest bottom point must not be collinear if the polygon does not contain duplicate points
// or overlapping segments.
assert(o != ORIENTATION_COLINEAR);
return o == ORIENTATION_CCW;
}
inline bool ray_ray_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res)
{
double denom = v1(0) * v2(1) - v2(0) * v1(1);
if (std::abs(denom) < EPSILON)
return false;
double t = (v2(0) * (p1(1) - p2(1)) - v2(1) * (p1(0) - p2(0))) / denom;
res(0) = p1(0) + t * v1(0);
res(1) = p1(1) + t * v1(1);
return true;
}
inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res)
{
double denom = v1(0) * v2(1) - v2(0) * v1(1);
if (std::abs(denom) < EPSILON)
// Lines are collinear.
return false;
double s12_x = p1(0) - p2(0);
double s12_y = p1(1) - p2(1);
double s_numer = v1(0) * s12_y - v1(1) * s12_x;
bool denom_is_positive = false;
if (denom < 0.) {
denom_is_positive = true;
denom = - denom;
s_numer = - s_numer;
}
if (s_numer < 0.)
// Intersection outside of the 1st segment.
return false;
double t_numer = v2(0) * s12_y - v2(1) * s12_x;
if (! denom_is_positive)
t_numer = - t_numer;
if (t_numer < 0. || s_numer > denom || t_numer > denom)
// Intersection outside of the 1st or 2nd segment.
return false;
// Intersection inside both of the segments.
double t = t_numer / denom;
res(0) = p1(0) + t * v1(0);
res(1) = p1(1) + t * v1(1);
return true;
}
Pointf3s convex_hull(Pointf3s points);
Polygon convex_hull(Points points);
Polygon convex_hull(const Polygons &polygons);
void chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near);
void chained_path(const Points &points, std::vector<Points::size_type> &retval);
template<class T> void chained_path_items(Points &points, T &items, T &retval);
bool directions_parallel(double angle1, double angle2, double max_diff = 0);
template<class T> bool contains(const std::vector<T> &vector, const Point &point);
double rad2deg(double angle);
double rad2deg_dir(double angle);
template<typename T> T deg2rad(T angle) { return T(PI) * angle / T(180.0); }
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval);
double linint(double value, double oldmin, double oldmax, double newmin, double newmax);
bool arrange(
// input
size_t num_parts, const Vec2d &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box,
// output
Pointfs &positions);
class MedialAxis {
public:
Lines lines;
const ExPolygon* expolygon;
double max_width;
double min_width;
MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL)
: expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {};
void build(ThickPolylines* polylines);
void build(Polylines* polylines);
private:
class VD : public voronoi_diagram<double> {
public:
typedef double coord_type;
typedef boost::polygon::point_data<coordinate_type> point_type;
typedef boost::polygon::segment_data<coordinate_type> segment_type;
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
};
VD vd;
std::set<const VD::edge_type*> edges, valid_edges;
std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness;
void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline);
bool validate_edge(const VD::edge_type* edge);
const Line& retrieve_segment(const VD::cell_type* cell) const;
const Point& retrieve_endpoint(const VD::cell_type* cell) const;
};
} }
#endif

18
src/libslic3r/I18N.hpp Normal file
View file

@ -0,0 +1,18 @@
#ifndef slic3r_I18N_hpp_
#define slic3r_I18N_hpp_
#include <string>
namespace Slic3r {
namespace I18N {
typedef std::string (*translate_fn_type)(const char*);
extern translate_fn_type translate_fn;
inline void set_translate_callback(translate_fn_type fn) { translate_fn = fn; }
inline std::string translate(const std::string &s) { return (translate_fn == nullptr) ? s : (*translate_fn)(s.c_str()); }
inline std::string translate(const char *ptr) { return (translate_fn == nullptr) ? std::string(ptr) : (*translate_fn)(ptr); }
} // namespace I18N
} // namespace Slic3r
#endif /* slic3r_I18N_hpp_ */

290
src/libslic3r/Int128.hpp Normal file
View file

@ -0,0 +1,290 @@
// This is an excerpt of from the Clipper library by Angus Johnson, see the license below,
// implementing a 64 x 64 -> 128bit multiply, and 128bit addition, subtraction and compare
// operations, to be used with exact geometric predicates.
// The code has been extended by Vojtech Bubnik to use 128 bit intrinsic types
// and/or 64x64->128 intrinsic functions where possible.
/*******************************************************************************
* *
* Author : Angus Johnson *
* Version : 6.2.9 *
* Date : 16 February 2015 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2015 *
* *
* License: *
* Use, modification & distribution is subject to Boost Software License Ver 1. *
* http://www.boost.org/LICENSE_1_0.txt *
* *
* Attributions: *
* The code in this library is an extension of Bala Vatti's clipping algorithm: *
* "A generic solution to polygon clipping" *
* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
* http://portal.acm.org/citation.cfm?id=129906 *
* *
* Computer graphics and geometric modeling: implementation and algorithms *
* By Max K. Agoston *
* Springer; 1 edition (January 4, 2005) *
* http://books.google.com/books?q=vatti+clipping+agoston *
* *
* See also: *
* "Polygon Offsetting by Computing Winding Numbers" *
* Paper no. DETC2005-85513 pp. 565-575 *
* ASME 2005 International Design Engineering Technical Conferences *
* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
* September 24-28, 2005 , Long Beach, California, USA *
* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
* *
*******************************************************************************/
// #define SLIC3R_DEBUG
// Make assert active if SLIC3R_DEBUG
#ifdef SLIC3R_DEBUG
#undef NDEBUG
#define DEBUG
#define _DEBUG
#undef assert
#endif
#include <cassert>
#if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__)
#define HAS_INTRINSIC_128_TYPE
#endif
//------------------------------------------------------------------------------
// Int128 class (enables safe math on signed 64bit integers)
// eg Int128 val1((int64_t)9223372036854775807); //ie 2^63 -1
// Int128 val2((int64_t)9223372036854775807);
// Int128 val3 = val1 * val2;
//------------------------------------------------------------------------------
class Int128
{
#ifdef HAS_INTRINSIC_128_TYPE
/******************************************** Using the intrinsic 128bit x 128bit multiply ************************************************/
public:
__int128 value;
Int128(int64_t lo = 0) : value(lo) {}
Int128(const Int128 &v) : value(v.value) {}
Int128& operator=(const int64_t &rhs) { value = rhs; return *this; }
uint64_t lo() const { return uint64_t(value); }
int64_t hi() const { return int64_t(value >> 64); }
int sign() const { return (value > 0) - (value < 0); }
bool operator==(const Int128 &rhs) const { return value == rhs.value; }
bool operator!=(const Int128 &rhs) const { return value != rhs.value; }
bool operator> (const Int128 &rhs) const { return value > rhs.value; }
bool operator< (const Int128 &rhs) const { return value < rhs.value; }
bool operator>=(const Int128 &rhs) const { return value >= rhs.value; }
bool operator<=(const Int128 &rhs) const { return value <= rhs.value; }
Int128& operator+=(const Int128 &rhs) { value += rhs.value; return *this; }
Int128 operator+ (const Int128 &rhs) const { return Int128(value + rhs.value); }
Int128& operator-=(const Int128 &rhs) { value -= rhs.value; return *this; }
Int128 operator -(const Int128 &rhs) const { return Int128(value - rhs.value); }
Int128 operator -() const { return Int128(- value); }
operator double() const { return double(value); }
static inline Int128 multiply(int64_t lhs, int64_t rhs) { return Int128(__int128(lhs) * __int128(rhs)); }
// Evaluate signum of a 2x2 determinant.
static int sign_determinant_2x2(int64_t a11, int64_t a12, int64_t a21, int64_t a22)
{
__int128 det = __int128(a11) * __int128(a22) - __int128(a12) * __int128(a21);
return (det > 0) - (det < 0);
}
// Compare two rational numbers.
static int compare_rationals(int64_t p1, int64_t q1, int64_t p2, int64_t q2)
{
int invert = ((q1 < 0) == (q2 < 0)) ? 1 : -1;
__int128 det = __int128(p1) * __int128(q2) - __int128(p2) * __int128(q1);
return ((det > 0) - (det < 0)) * invert;
}
#else /* HAS_INTRINSIC_128_TYPE */
/******************************************** Splitting the 128bit number into two 64bit words *********************************************/
Int128(int64_t lo = 0) : m_lo((uint64_t)lo), m_hi((lo < 0) ? -1 : 0) {}
Int128(const Int128 &val) : m_lo(val.m_lo), m_hi(val.m_hi) {}
Int128(const int64_t& hi, const uint64_t& lo) : m_lo(lo), m_hi(hi) {}
Int128& operator = (const int64_t &val)
{
m_lo = (uint64_t)val;
m_hi = (val < 0) ? -1 : 0;
return *this;
}
uint64_t lo() const { return m_lo; }
int64_t hi() const { return m_hi; }
int sign() const { return (m_hi == 0) ? (m_lo > 0) : (m_hi > 0) - (m_hi < 0); }
bool operator == (const Int128 &val) const { return m_hi == val.m_hi && m_lo == val.m_lo; }
bool operator != (const Int128 &val) const { return ! (*this == val); }
bool operator > (const Int128 &val) const { return (m_hi == val.m_hi) ? m_lo > val.m_lo : m_hi > val.m_hi; }
bool operator < (const Int128 &val) const { return (m_hi == val.m_hi) ? m_lo < val.m_lo : m_hi < val.m_hi; }
bool operator >= (const Int128 &val) const { return ! (*this < val); }
bool operator <= (const Int128 &val) const { return ! (*this > val); }
Int128& operator += (const Int128 &rhs)
{
m_hi += rhs.m_hi;
m_lo += rhs.m_lo;
if (m_lo < rhs.m_lo) m_hi++;
return *this;
}
Int128 operator + (const Int128 &rhs) const
{
Int128 result(*this);
result+= rhs;
return result;
}
Int128& operator -= (const Int128 &rhs)
{
*this += -rhs;
return *this;
}
Int128 operator - (const Int128 &rhs) const
{
Int128 result(*this);
result -= rhs;
return result;
}
Int128 operator-() const { return (m_lo == 0) ? Int128(-m_hi, 0) : Int128(~m_hi, ~m_lo + 1); }
operator double() const
{
const double shift64 = 18446744073709551616.0; //2^64
return (m_hi < 0) ?
((m_lo == 0) ?
(double)m_hi * shift64 :
-(double)(~m_lo + ~m_hi * shift64)) :
(double)(m_lo + m_hi * shift64);
}
static inline Int128 multiply(int64_t lhs, int64_t rhs)
{
#if defined(_MSC_VER) && defined(_WIN64)
// On Visual Studio 64bit, use the _mul128() intrinsic function.
Int128 result;
result.m_lo = (uint64_t)_mul128(lhs, rhs, &result.m_hi);
return result;
#else
// This branch should only be executed in case there is neither __int16 type nor _mul128 intrinsic
// function available. This is mostly on 32bit operating systems.
// Use a pure C implementation of _mul128().
int negate = (lhs < 0) != (rhs < 0);
if (lhs < 0)
lhs = -lhs;
uint64_t int1Hi = uint64_t(lhs) >> 32;
uint64_t int1Lo = uint64_t(lhs & 0xFFFFFFFF);
if (rhs < 0)
rhs = -rhs;
uint64_t int2Hi = uint64_t(rhs) >> 32;
uint64_t int2Lo = uint64_t(rhs & 0xFFFFFFFF);
//because the high (sign) bits in both int1Hi & int2Hi have been zeroed,
//there's no risk of 64 bit overflow in the following assignment
//(ie: $7FFFFFFF*$FFFFFFFF + $7FFFFFFF*$FFFFFFFF < 64bits)
uint64_t a = int1Hi * int2Hi;
uint64_t b = int1Lo * int2Lo;
//Result = A shl 64 + C shl 32 + B ...
uint64_t c = int1Hi * int2Lo + int1Lo * int2Hi;
Int128 tmp;
tmp.m_hi = int64_t(a + (c >> 32));
tmp.m_lo = int64_t(c << 32);
tmp.m_lo += int64_t(b);
if (tmp.m_lo < b)
++ tmp.m_hi;
if (negate)
tmp = - tmp;
return tmp;
#endif
}
// Evaluate signum of a 2x2 determinant.
static int sign_determinant_2x2(int64_t a11, int64_t a12, int64_t a21, int64_t a22)
{
return (Int128::multiply(a11, a22) - Int128::multiply(a12, a21)).sign();
}
// Compare two rational numbers.
static int compare_rationals(int64_t p1, int64_t q1, int64_t p2, int64_t q2)
{
int invert = ((q1 < 0) == (q2 < 0)) ? 1 : -1;
Int128 det = Int128::multiply(p1, q2) - Int128::multiply(p2, q1);
return det.sign() * invert;
}
private:
uint64_t m_lo;
int64_t m_hi;
#endif /* HAS_INTRINSIC_128_TYPE */
/******************************************** Common methods ************************************************/
public:
// Evaluate signum of a 2x2 determinant, use a numeric filter to avoid 128 bit multiply if possible.
static int sign_determinant_2x2_filtered(int64_t a11, int64_t a12, int64_t a21, int64_t a22)
{
// First try to calculate the determinant over the upper 31 bits.
// Round p1, p2, q1, q2 to 31 bits.
int64_t a11s = (a11 + (1 << 31)) >> 32;
int64_t a12s = (a12 + (1 << 31)) >> 32;
int64_t a21s = (a21 + (1 << 31)) >> 32;
int64_t a22s = (a22 + (1 << 31)) >> 32;
// Result fits 63 bits, it is an approximate of the determinant divided by 2^64.
int64_t det = a11s * a22s - a12s * a21s;
// Maximum absolute of the remainder of the exact determinant, divided by 2^64.
int64_t err = ((std::abs(a11s) + std::abs(a12s) + std::abs(a21s) + std::abs(a22s)) << 1) + 1;
assert(std::abs(det) <= err || ((det > 0) ? 1 : -1) == sign_determinant_2x2(a11, a12, a21, a22));
return (std::abs(det) > err) ?
((det > 0) ? 1 : -1) :
sign_determinant_2x2(a11, a12, a21, a22);
}
// Compare two rational numbers, use a numeric filter to avoid 128 bit multiply if possible.
static int compare_rationals_filtered(int64_t p1, int64_t q1, int64_t p2, int64_t q2)
{
// First try to calculate the determinant over the upper 31 bits.
// Round p1, p2, q1, q2 to 31 bits.
int invert = ((q1 < 0) == (q2 < 0)) ? 1 : -1;
int64_t q1s = (q1 + (1 << 31)) >> 32;
int64_t q2s = (q2 + (1 << 31)) >> 32;
if (q1s != 0 && q2s != 0) {
int64_t p1s = (p1 + (1 << 31)) >> 32;
int64_t p2s = (p2 + (1 << 31)) >> 32;
// Result fits 63 bits, it is an approximate of the determinant divided by 2^64.
int64_t det = p1s * q2s - p2s * q1s;
// Maximum absolute of the remainder of the exact determinant, divided by 2^64.
int64_t err = ((std::abs(p1s) + std::abs(q1s) + std::abs(p2s) + std::abs(q2s)) << 1) + 1;
assert(std::abs(det) <= err || ((det > 0) ? 1 : -1) * invert == compare_rationals(p1, q1, p2, q2));
if (std::abs(det) > err)
return ((det > 0) ? 1 : -1) * invert;
}
return sign_determinant_2x2(p1, q1, p2, q2) * invert;
}
};

215
src/libslic3r/Layer.cpp Normal file
View file

@ -0,0 +1,215 @@
#include "Layer.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "Print.hpp"
#include "Fill/Fill.hpp"
#include "SVG.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
Layer::~Layer()
{
this->lower_layer = this->upper_layer = nullptr;
for (LayerRegion *region : m_regions)
delete region;
m_regions.clear();
}
LayerRegion* Layer::add_region(PrintRegion* print_region)
{
m_regions.emplace_back(new LayerRegion(this, print_region));
return m_regions.back();
}
// merge all regions' slices to get islands
void Layer::make_slices()
{
ExPolygons slices;
if (m_regions.size() == 1) {
// optimization: if we only have one region, take its slices
slices = m_regions.front()->slices;
} else {
Polygons slices_p;
FOREACH_LAYERREGION(this, layerm) {
polygons_append(slices_p, to_polygons((*layerm)->slices));
}
slices = union_ex(slices_p);
}
this->slices.expolygons.clear();
this->slices.expolygons.reserve(slices.size());
// prepare ordering points
Points ordering_points;
ordering_points.reserve(slices.size());
for (const ExPolygon &ex : slices)
ordering_points.push_back(ex.contour.first_point());
// sort slices
std::vector<Points::size_type> order;
Slic3r::Geometry::chained_path(ordering_points, order);
// populate slices vector
for (size_t i : order)
this->slices.expolygons.push_back(STDMOVE(slices[i]));
}
void Layer::merge_slices()
{
if (m_regions.size() == 1) {
// Optimization, also more robust. Don't merge classified pieces of layerm->slices,
// but use the non-split islands of a layer. For a single region print, these shall be equal.
m_regions.front()->slices.set(this->slices.expolygons, stInternal);
} else {
FOREACH_LAYERREGION(this, layerm) {
// without safety offset, artifacts are generated (GH #2494)
(*layerm)->slices.set(union_ex(to_polygons(STDMOVE((*layerm)->slices.surfaces)), true), stInternal);
}
}
}
// Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters.
// The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region.
// The resulting fill surface is split back among the originating regions.
void Layer::make_perimeters()
{
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id();
// keep track of regions whose perimeters we have already generated
std::set<size_t> done;
FOREACH_LAYERREGION(this, layerm) {
size_t region_id = layerm - m_regions.begin();
if (done.find(region_id) != done.end()) continue;
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
done.insert(region_id);
const PrintRegionConfig &config = (*layerm)->region()->config();
// find compatible regions
LayerRegionPtrs layerms;
layerms.push_back(*layerm);
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it) {
LayerRegion* other_layerm = *it;
const PrintRegionConfig &other_config = other_layerm->region()->config();
if (config.perimeter_extruder == other_config.perimeter_extruder
&& config.perimeters == other_config.perimeters
&& config.perimeter_speed == other_config.perimeter_speed
&& config.external_perimeter_speed == other_config.external_perimeter_speed
&& config.gap_fill_speed == other_config.gap_fill_speed
&& config.overhangs == other_config.overhangs
&& config.serialize("perimeter_extrusion_width").compare(other_config.serialize("perimeter_extrusion_width")) == 0
&& config.thin_walls == other_config.thin_walls
&& config.external_perimeters_first == other_config.external_perimeters_first) {
layerms.push_back(other_layerm);
done.insert(it - m_regions.begin());
}
}
if (layerms.size() == 1) { // optimization
(*layerm)->fill_surfaces.surfaces.clear();
(*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces);
(*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces);
} else {
SurfaceCollection new_slices;
{
// group slices (surfaces) according to number of extra perimeters
std::map<unsigned short,Surfaces> slices; // extra_perimeters => [ surface, surface... ]
for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) {
for (Surfaces::iterator s = (*l)->slices.surfaces.begin(); s != (*l)->slices.surfaces.end(); ++s) {
slices[s->extra_perimeters].push_back(*s);
}
}
// merge the surfaces assigned to each group
for (std::map<unsigned short,Surfaces>::const_iterator it = slices.begin(); it != slices.end(); ++it)
new_slices.append(union_ex(it->second, true), it->second.front());
}
// make perimeters
SurfaceCollection fill_surfaces;
(*layerm)->make_perimeters(new_slices, &fill_surfaces);
// assign fill_surfaces to each layer
if (!fill_surfaces.surfaces.empty()) {
for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) {
// Separate the fill surfaces.
ExPolygons expp = intersection_ex(to_polygons(fill_surfaces), (*l)->slices);
(*l)->fill_expolygons = expp;
(*l)->fill_surfaces.set(STDMOVE(expp), fill_surfaces.surfaces.front());
}
}
}
}
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done";
}
void Layer::make_fills()
{
#ifdef SLIC3R_DEBUG
printf("Making fills for layer " PRINTF_ZU "\n", this->id());
#endif
for (LayerRegion *layerm : m_regions) {
layerm->fills.clear();
make_fill(*layerm, layerm->fills);
#ifndef NDEBUG
for (size_t i = 0; i < layerm.fills.entities.size(); ++ i)
assert(dynamic_cast<ExtrusionEntityCollection*>(layerm->fills.entities[i]) != NULL);
#endif
}
}
void Layer::export_region_slices_to_svg(const char *path) const
{
BoundingBox bbox;
for (const auto *region : m_regions)
for (const auto &surface : region->slices.surfaces)
bbox.merge(get_extents(surface.expolygon));
Point legend_size = export_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
SVG svg(path, bbox);
const float transparency = 0.5f;
for (const auto *region : m_regions)
for (const auto &surface : region->slices.surfaces)
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
export_surface_type_legend_to_svg(svg, legend_pos);
svg.Close();
}
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
void Layer::export_region_slices_to_svg_debug(const char *name) const
{
static size_t idx = 0;
this->export_region_slices_to_svg(debug_out_path("Layer-slices-%s-%d.svg", name, idx ++).c_str());
}
void Layer::export_region_fill_surfaces_to_svg(const char *path) const
{
BoundingBox bbox;
for (const auto *region : m_regions)
for (const auto &surface : region->slices.surfaces)
bbox.merge(get_extents(surface.expolygon));
Point legend_size = export_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
SVG svg(path, bbox);
const float transparency = 0.5f;
for (const auto *region : m_regions)
for (const auto &surface : region->slices.surfaces)
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
export_surface_type_legend_to_svg(svg, legend_pos);
svg.Close();
}
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
void Layer::export_region_fill_surfaces_to_svg_debug(const char *name) const
{
static size_t idx = 0;
this->export_region_fill_surfaces_to_svg(debug_out_path("Layer-fill_surfaces-%s-%d.svg", name, idx ++).c_str());
}
}

179
src/libslic3r/Layer.hpp Normal file
View file

@ -0,0 +1,179 @@
#ifndef slic3r_Layer_hpp_
#define slic3r_Layer_hpp_
#include "libslic3r.h"
#include "Flow.hpp"
#include "SurfaceCollection.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "ExPolygonCollection.hpp"
#include "PolylineCollection.hpp"
namespace Slic3r {
class Layer;
class PrintRegion;
class PrintObject;
class LayerRegion
{
public:
Layer* layer() { return m_layer; }
const Layer* layer() const { return m_layer; }
PrintRegion* region() { return m_region; }
const PrintRegion* region() const { return m_region; }
// collection of surfaces generated by slicing the original geometry
// divided by type top/bottom/internal
SurfaceCollection slices;
// collection of extrusion paths/loops filling gaps
// These fills are generated by the perimeter generator.
// They are not printed on their own, but they are copied to this->fills during infill generation.
ExtrusionEntityCollection thin_fills;
// Unspecified fill polygons, used for overhang detection ("ensure vertical wall thickness feature")
// and for re-starting of infills.
ExPolygons fill_expolygons;
// collection of surfaces for infill generation
SurfaceCollection fill_surfaces;
// Collection of perimeter surfaces. This is a cached result of diff(slices, fill_surfaces).
// While not necessary, the memory consumption is meager and it speeds up calculation.
// The perimeter_surfaces keep the IDs of the slices (top/bottom/)
SurfaceCollection perimeter_surfaces;
// collection of expolygons representing the bridged areas (thus not
// needing support material)
Polygons bridged;
// collection of polylines representing the unsupported bridge edges
PolylineCollection unsupported_bridge_edges;
// ordered collection of extrusion paths/loops to build all perimeters
// (this collection contains only ExtrusionEntityCollection objects)
ExtrusionEntityCollection perimeters;
// ordered collection of extrusion paths to fill surfaces
// (this collection contains only ExtrusionEntityCollection objects)
ExtrusionEntityCollection fills;
Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
void slices_to_fill_surfaces_clipped();
void prepare_fill_surfaces();
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces);
void process_external_surfaces(const Layer* lower_layer);
double infill_area_threshold() const;
void export_region_slices_to_svg(const char *path) const;
void export_region_fill_surfaces_to_svg(const char *path) const;
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
void export_region_slices_to_svg_debug(const char *name) const;
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
// Is there any valid extrusion assigned to this LayerRegion?
bool has_extrusions() const { return ! this->perimeters.entities.empty() || ! this->fills.entities.empty(); }
protected:
friend class Layer;
LayerRegion(Layer *layer, PrintRegion *region) : m_layer(layer), m_region(region) {}
~LayerRegion() {}
private:
Layer *m_layer;
PrintRegion *m_region;
};
typedef std::vector<LayerRegion*> LayerRegionPtrs;
class Layer
{
public:
size_t id() const { return m_id; }
void set_id(size_t id) { m_id = id; }
PrintObject* object() { return m_object; }
const PrintObject* object() const { return m_object; }
Layer *upper_layer;
Layer *lower_layer;
bool slicing_errors;
coordf_t slice_z; // Z used for slicing in unscaled coordinates
coordf_t print_z; // Z used for printing in unscaled coordinates
coordf_t height; // layer height in unscaled coordinates
// collection of expolygons generated by slicing the original geometry;
// also known as 'islands' (all regions and surface types are merged here)
// The slices are chained by the shortest traverse distance and this traversal
// order will be recovered by the G-code generator.
ExPolygonCollection slices;
size_t region_count() const { return m_regions.size(); }
const LayerRegion* get_region(int idx) const { return m_regions.at(idx); }
LayerRegion* get_region(int idx) { return m_regions[idx]; }
LayerRegion* add_region(PrintRegion* print_region);
const LayerRegionPtrs& regions() const { return m_regions; }
void make_slices();
void merge_slices();
template <class T> bool any_internal_region_slice_contains(const T &item) const {
for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_internal_contains(item)) return true;
return false;
}
template <class T> bool any_bottom_region_slice_contains(const T &item) const {
for (const LayerRegion *layerm : m_regions) if (layerm->slices.any_bottom_contains(item)) return true;
return false;
}
void make_perimeters();
void make_fills();
void export_region_slices_to_svg(const char *path) const;
void export_region_fill_surfaces_to_svg(const char *path) const;
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
void export_region_slices_to_svg_debug(const char *name) const;
void export_region_fill_surfaces_to_svg_debug(const char *name) const;
// Is there any valid extrusion assigned to this LayerRegion?
virtual bool has_extrusions() const { for (auto layerm : m_regions) if (layerm->has_extrusions()) return true; return false; }
protected:
friend class PrintObject;
Layer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
upper_layer(nullptr), lower_layer(nullptr), slicing_errors(false),
slice_z(slice_z), print_z(print_z), height(height),
m_id(id), m_object(object) {}
virtual ~Layer();
private:
// sequential number of layer, 0-based
size_t m_id;
PrintObject *m_object;
LayerRegionPtrs m_regions;
};
class SupportLayer : public Layer
{
public:
// Polygons covered by the supports: base, interface and contact areas.
ExPolygonCollection support_islands;
// Extrusion paths for the support base and for the support interface and contacts.
ExtrusionEntityCollection support_fills;
// Is there any valid extrusion assigned to this LayerRegion?
virtual bool has_extrusions() const { return ! support_fills.empty(); }
protected:
friend class PrintObject;
// The constructor has been made public to be able to insert additional support layers for the skirt or a wipe tower
// between the raft and the object first layer.
SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z) :
Layer(id, object, height, print_z, slice_z) {}
virtual ~SupportLayer() {}
};
}
#endif

View file

@ -0,0 +1,443 @@
#include "Layer.hpp"
#include "BridgeDetector.hpp"
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "PerimeterGenerator.hpp"
#include "Print.hpp"
#include "Surface.hpp"
#include "BoundingBox.hpp"
#include "SVG.hpp"
#include <string>
#include <map>
#include <boost/log/trivial.hpp>
namespace Slic3r {
Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const
{
return m_region->flow(
role,
m_layer->height,
bridge,
m_layer->id() == 0,
width,
*m_layer->object()
);
}
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
void LayerRegion::slices_to_fill_surfaces_clipped()
{
// Note: this method should be idempotent, but fill_surfaces gets modified
// in place. However we're now only using its boundaries (which are invariant)
// so we're safe. This guarantees idempotence of prepare_infill() also in case
// that combine_infill() turns some fill_surface into VOID surfaces.
// Polygons fill_boundaries = to_polygons(STDMOVE(this->fill_surfaces));
Polygons fill_boundaries = to_polygons(this->fill_expolygons);
// Collect polygons per surface type.
std::vector<Polygons> polygons_by_surface;
polygons_by_surface.assign(size_t(stCount), Polygons());
for (Surface &surface : this->slices.surfaces)
polygons_append(polygons_by_surface[(size_t)surface.surface_type], surface.expolygon);
// Trim surfaces by the fill_boundaries.
this->fill_surfaces.surfaces.clear();
for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) {
const Polygons &polygons = polygons_by_surface[surface_type];
if (! polygons.empty())
this->fill_surfaces.append(intersection_ex(polygons, fill_boundaries), SurfaceType(surface_type));
}
}
void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces)
{
this->perimeters.clear();
this->thin_fills.clear();
PerimeterGenerator g(
// input:
&slices,
this->layer()->height,
this->flow(frPerimeter),
&this->region()->config(),
&this->layer()->object()->config(),
&this->layer()->object()->print()->config(),
// output:
&this->perimeters,
&this->thin_fills,
fill_surfaces
);
if (this->layer()->lower_layer != NULL)
// Cummulative sum of polygons over all the regions.
g.lower_slices = &this->layer()->lower_layer->slices;
g.layer_id = this->layer()->id();
g.ext_perimeter_flow = this->flow(frExternalPerimeter);
g.overhang_flow = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object());
g.solid_infill_flow = this->flow(frSolidInfill);
g.process();
}
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
void LayerRegion::process_external_surfaces(const Layer* lower_layer)
{
const Surfaces &surfaces = this->fill_surfaces.surfaces;
const double margin = scale_(EXTERNAL_INFILL_MARGIN);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset
// for better anchoring.
// Bottom surfaces, grown.
Surfaces bottom;
// Bridge surfaces, initialy not grown.
Surfaces bridges;
// Top surfaces, grown.
Surfaces top;
// Internal surfaces, not grown.
Surfaces internal;
// Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed.
//FIXME if non zero infill, then fill_boundaries could be cheaply initialized from layerm->fill_expolygons.
Polygons fill_boundaries;
// Collect top surfaces and internal surfaces.
// Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill.
// This loop destroys the surfaces (aliasing this->fill_surfaces.surfaces) by moving into top/internal/fill_boundaries!
{
// bottom_polygons are used to trim inflated top surfaces.
fill_boundaries.reserve(number_polygons(surfaces));
bool has_infill = this->region()->config().fill_density.value > 0.;
for (const Surface &surface : this->fill_surfaces.surfaces) {
if (surface.surface_type == stTop) {
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
// This gives the priority to bottom surfaces.
surfaces_append(top, offset_ex(surface.expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == NULL)) {
// Grown by 3mm.
surfaces_append(bottom, offset_ex(surface.expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottomBridge) {
if (! surface.empty())
bridges.push_back(surface);
}
bool internal_surface = surface.surface_type != stTop && ! surface.is_bottom();
if (has_infill || surface.surface_type != stInternal) {
if (internal_surface)
// Make a copy as the following line uses the move semantics.
internal.push_back(surface);
polygons_append(fill_boundaries, STDMOVE(surface.expolygon));
} else if (internal_surface)
internal.push_back(STDMOVE(surface));
}
}
#if 0
{
static int iRun = 0;
bridges.export_to_svg(debug_out_path("bridges-before-grouping-%d.svg", iRun ++), true);
}
#endif
if (bridges.empty())
{
fill_boundaries = union_(fill_boundaries, true);
} else
{
// 1) Calculate the inflated bridge regions, each constrained to its island.
ExPolygons fill_boundaries_ex = union_ex(fill_boundaries, true);
std::vector<Polygons> bridges_grown;
std::vector<BoundingBox> bridge_bboxes;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
static int iRun = 0;
SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex));
svg.draw(fill_boundaries_ex);
svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05));
svg.Close();
}
// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
{
// Bridge expolygons, grown, to be tested for intersection with other bridge regions.
std::vector<BoundingBox> fill_boundaries_ex_bboxes = get_extents_vector(fill_boundaries_ex);
bridges_grown.reserve(bridges.size());
bridge_bboxes.reserve(bridges.size());
for (size_t i = 0; i < bridges.size(); ++ i) {
// Find the island of this bridge.
const Point pt = bridges[i].expolygon.contour.points.front();
int idx_island = -1;
for (int j = 0; j < int(fill_boundaries_ex.size()); ++ j)
if (fill_boundaries_ex_bboxes[j].contains(pt) &&
fill_boundaries_ex[j].contains(pt)) {
idx_island = j;
break;
}
// Grown by 3mm.
Polygons polys = offset(to_polygons(bridges[i].expolygon), float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS);
if (idx_island == -1) {
printf("Bridge did not fall into the source region!\r\n");
} else {
// Found an island, to which this bridge region belongs. Trim it,
polys = intersection(polys, to_polygons(fill_boundaries_ex[idx_island]));
}
bridge_bboxes.push_back(get_extents(polys));
bridges_grown.push_back(STDMOVE(polys));
}
}
// 2) Group the bridge surfaces by overlaps.
std::vector<size_t> bridge_group(bridges.size(), (size_t)-1);
size_t n_groups = 0;
for (size_t i = 0; i < bridges.size(); ++ i) {
// A grup id for this bridge.
size_t group_id = (bridge_group[i] == -1) ? (n_groups ++) : bridge_group[i];
bridge_group[i] = group_id;
// For all possibly overlaping bridges:
for (size_t j = i + 1; j < bridges.size(); ++ j) {
if (! bridge_bboxes[i].overlap(bridge_bboxes[j]))
continue;
if (intersection(bridges_grown[i], bridges_grown[j], false).empty())
continue;
// The two bridge regions intersect. Give them the same group id.
if (bridge_group[j] != -1) {
// The j'th bridge has been merged with some other bridge before.
size_t group_id_new = bridge_group[j];
for (size_t k = 0; k < j; ++ k)
if (bridge_group[k] == group_id)
bridge_group[k] = group_id_new;
group_id = group_id_new;
}
bridge_group[j] = group_id;
}
}
// 3) Merge the groups with the same group id, detect bridges.
{
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z << ", bridge groups: " << n_groups;
for (size_t group_id = 0; group_id < n_groups; ++ group_id) {
size_t n_bridges_merged = 0;
size_t idx_last = (size_t)-1;
for (size_t i = 0; i < bridges.size(); ++ i) {
if (bridge_group[i] == group_id) {
++ n_bridges_merged;
idx_last = i;
}
}
if (n_bridges_merged == 0)
// This group has no regions assigned as these were moved into another group.
continue;
// Collect the initial ungrown regions and the grown polygons.
ExPolygons initial;
Polygons grown;
for (size_t i = 0; i < bridges.size(); ++ i) {
if (bridge_group[i] != group_id)
continue;
initial.push_back(STDMOVE(bridges[i].expolygon));
polygons_append(grown, bridges_grown[i]);
}
// detect bridge direction before merging grown surfaces otherwise adjacent bridges
// would get merged into a single one while they need different directions
// also, supply the original expolygon instead of the grown one, because in case
// of very thin (but still working) anchors, the grown expolygon would go beyond them
BridgeDetector bd(
initial,
lower_layer->slices,
this->flow(frInfill, true).scaled_width()
);
#ifdef SLIC3R_DEBUG
printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id());
#endif
if (bd.detect_angle(Geometry::deg2rad(this->region()->config().bridge_angle.value))) {
bridges[idx_last].bridge_angle = bd.angle;
if (this->layer()->object()->config().support_material) {
polygons_append(this->bridged, bd.coverage());
this->unsupported_bridge_edges.append(bd.unsupported_edges());
}
}
// without safety offset, artifacts are generated (GH #2494)
surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]);
}
fill_boundaries = STDMOVE(to_polygons(fill_boundaries_ex));
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
}
#if 0
{
static int iRun = 0;
bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun ++), true);
}
#endif
}
Surfaces new_surfaces;
{
// Merge top and bottom in a single collection.
surfaces_append(top, STDMOVE(bottom));
// Intersect the grown surfaces with the actual fill boundaries.
Polygons bottom_polygons = to_polygons(bottom);
for (size_t i = 0; i < top.size(); ++ i) {
Surface &s1 = top[i];
if (s1.empty())
continue;
Polygons polys;
polygons_append(polys, STDMOVE(s1));
for (size_t j = i + 1; j < top.size(); ++ j) {
Surface &s2 = top[j];
if (! s2.empty() && surfaces_could_merge(s1, s2)) {
polygons_append(polys, STDMOVE(s2));
s2.clear();
}
}
if (s1.surface_type == stTop)
// Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces.
polys = diff(polys, bottom_polygons);
surfaces_append(
new_surfaces,
// Don't use a safety offset as fill_boundaries were already united using the safety offset.
STDMOVE(intersection_ex(polys, fill_boundaries, false)),
s1);
}
}
// Subtract the new top surfaces from the other non-top surfaces and re-add them.
Polygons new_polygons = to_polygons(new_surfaces);
for (size_t i = 0; i < internal.size(); ++ i) {
Surface &s1 = internal[i];
if (s1.empty())
continue;
Polygons polys;
polygons_append(polys, STDMOVE(s1));
for (size_t j = i + 1; j < internal.size(); ++ j) {
Surface &s2 = internal[j];
if (! s2.empty() && surfaces_could_merge(s1, s2)) {
polygons_append(polys, STDMOVE(s2));
s2.clear();
}
}
ExPolygons new_expolys = diff_ex(polys, new_polygons);
polygons_append(new_polygons, to_polygons(new_expolys));
surfaces_append(new_surfaces, STDMOVE(new_expolys), s1);
}
this->fill_surfaces.surfaces = STDMOVE(new_surfaces);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
void LayerRegion::prepare_fill_surfaces()
{
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_slices_to_svg_debug("2_prepare_fill_surfaces-initial");
export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
/* Note: in order to make the psPrepareInfill step idempotent, we should never
alter fill_surfaces boundaries on which our idempotency relies since that's
the only meaningful information returned by psPerimeters. */
// if no solid layers are requested, turn top/bottom surfaces to internal
if (this->region()->config().top_solid_layers == 0) {
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface)
if (surface->surface_type == stTop)
surface->surface_type = (this->layer()->object()->config().infill_only_where_needed) ?
stInternalVoid : stInternal;
}
if (this->region()->config().bottom_solid_layers == 0) {
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) {
if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge)
surface->surface_type = stInternal;
}
}
// turn too small internal regions into solid regions according to the user setting
if (this->region()->config().fill_density.value > 0) {
// scaling an area requires two calls!
double min_area = scale_(scale_(this->region()->config().solid_infill_below_area.value));
for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) {
if (surface->surface_type == stInternal && surface->area() <= min_area)
surface->surface_type = stInternalSolid;
}
}
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_slices_to_svg_debug("2_prepare_fill_surfaces-final");
export_region_fill_surfaces_to_svg_debug("2_prepare_fill_surfaces-final");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
double LayerRegion::infill_area_threshold() const
{
double ss = this->flow(frSolidInfill).scaled_spacing();
return ss*ss;
}
void LayerRegion::export_region_slices_to_svg(const char *path) const
{
BoundingBox bbox;
for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface)
bbox.merge(get_extents(surface->expolygon));
Point legend_size = export_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
SVG svg(path, bbox);
const float transparency = 0.5f;
for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++surface)
svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency);
for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface)
svg.draw(surface->expolygon.lines(), surface_type_to_color_name(surface->surface_type));
export_surface_type_legend_to_svg(svg, legend_pos);
svg.Close();
}
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
void LayerRegion::export_region_slices_to_svg_debug(const char *name) const
{
static std::map<std::string, size_t> idx_map;
size_t &idx = idx_map[name];
this->export_region_slices_to_svg(debug_out_path("LayerRegion-slices-%s-%d.svg", name, idx ++).c_str());
}
void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const
{
BoundingBox bbox;
for (Surfaces::const_iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface)
bbox.merge(get_extents(surface->expolygon));
Point legend_size = export_surface_type_legend_to_svg_box_size();
Point legend_pos(bbox.min(0), bbox.max(1));
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
SVG svg(path, bbox);
const float transparency = 0.5f;
for (const Surface &surface : this->fill_surfaces.surfaces) {
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
svg.draw_outline(surface.expolygon, "black", "blue", scale_(0.05));
}
export_surface_type_legend_to_svg(svg, legend_pos);
svg.Close();
}
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
void LayerRegion::export_region_fill_surfaces_to_svg_debug(const char *name) const
{
static std::map<std::string, size_t> idx_map;
size_t &idx = idx_map[name];
this->export_region_fill_surfaces_to_svg(debug_out_path("LayerRegion-fill_surfaces-%s-%d.svg", name, idx ++).c_str());
}
}

119
src/libslic3r/Line.cpp Normal file
View file

@ -0,0 +1,119 @@
#include "Geometry.hpp"
#include "Line.hpp"
#include "Polyline.hpp"
#include <algorithm>
#include <cmath>
#include <sstream>
namespace Slic3r {
Linef3 transform(const Linef3& line, const Transform3d& t)
{
typedef Eigen::Matrix<double, 3, 2> LineInMatrixForm;
LineInMatrixForm world_line;
::memcpy((void*)world_line.col(0).data(), (const void*)line.a.data(), 3 * sizeof(double));
::memcpy((void*)world_line.col(1).data(), (const void*)line.b.data(), 3 * sizeof(double));
LineInMatrixForm local_line = t * world_line.colwise().homogeneous();
return Linef3(Vec3d(local_line(0, 0), local_line(1, 0), local_line(2, 0)), Vec3d(local_line(0, 1), local_line(1, 1), local_line(2, 1)));
}
bool Line::intersection_infinite(const Line &other, Point* point) const
{
Vec2d a1 = this->a.cast<double>();
Vec2d a2 = other.a.cast<double>();
Vec2d v12 = (other.a - this->a).cast<double>();
Vec2d v1 = (this->b - this->a).cast<double>();
Vec2d v2 = (other.b - other.a).cast<double>();
double denom = cross2(v1, v2);
if (std::fabs(denom) < EPSILON)
return false;
double t1 = cross2(v12, v2) / denom;
*point = (a1 + t1 * v1).cast<coord_t>();
return true;
}
/* distance to the closest point of line */
double Line::distance_to(const Point &point) const
{
const Line &line = *this;
const Vec2d v = (line.b - line.a).cast<double>();
const Vec2d va = (point - line.a).cast<double>();
const double l2 = v.squaredNorm(); // avoid a sqrt
if (l2 == 0.0)
// line.a == line.b case
return va.norm();
// Consider the line extending the segment, parameterized as line.a + t (line.b - line.a).
// We find projection of this point onto the line.
// It falls where t = [(this-line.a) . (line.b-line.a)] / |line.b-line.a|^2
const double t = va.dot(v) / l2;
if (t < 0.0) return va.norm(); // beyond the 'a' end of the segment
else if (t > 1.0) return (point - line.b).cast<double>().norm(); // beyond the 'b' end of the segment
return (t * v - va).norm();
}
double Line::perp_distance_to(const Point &point) const
{
const Line &line = *this;
const Vec2d v = (line.b - line.a).cast<double>();
const Vec2d va = (point - line.a).cast<double>();
if (line.a == line.b)
return va.norm();
return std::abs(cross2(v, va)) / v.norm();
}
double Line::orientation() const
{
double angle = this->atan2_();
if (angle < 0) angle = 2*PI + angle;
return angle;
}
double Line::direction() const
{
double atan2 = this->atan2_();
return (fabs(atan2 - PI) < EPSILON) ? 0
: (atan2 < 0) ? (atan2 + PI)
: atan2;
}
bool Line::parallel_to(double angle) const
{
return Slic3r::Geometry::directions_parallel(this->direction(), angle);
}
bool Line::intersection(const Line &l2, Point *intersection) const
{
const Line &l1 = *this;
const Vec2d v1 = (l1.b - l1.a).cast<double>();
const Vec2d v2 = (l2.b - l2.a).cast<double>();
const Vec2d v12 = (l1.a - l2.a).cast<double>();
double denom = cross2(v1, v2);
double nume_a = cross2(v2, v12);
double nume_b = cross2(v1, v12);
if (fabs(denom) < EPSILON)
#if 0
// Lines are collinear. Return true if they are coincident (overlappign).
return ! (fabs(nume_a) < EPSILON && fabs(nume_b) < EPSILON);
#else
return false;
#endif
double t1 = nume_a / denom;
double t2 = nume_b / denom;
if (t1 >= 0 && t1 <= 1.0f && t2 >= 0 && t2 <= 1.0f) {
// Get the intersection point.
(*intersection) = (l1.a.cast<double>() + t1 * v1).cast<coord_t>();
return true;
}
return false; // not intersecting
}
Vec3d Linef3::intersect_plane(double z) const
{
auto v = (this->b - this->a).cast<double>();
double t = (z - this->a(2)) / v(2);
return Vec3d(this->a(0) + v(0) * t, this->a(1) + v(1) * t, z);
}
}

119
src/libslic3r/Line.hpp Normal file
View file

@ -0,0 +1,119 @@
#ifndef slic3r_Line_hpp_
#define slic3r_Line_hpp_
#include "libslic3r.h"
#include "Point.hpp"
namespace Slic3r {
class Line;
class Line3;
class Linef3;
class Polyline;
class ThickLine;
typedef std::vector<Line> Lines;
typedef std::vector<Line3> Lines3;
typedef std::vector<ThickLine> ThickLines;
Linef3 transform(const Linef3& line, const Transform3d& t);
class Line
{
public:
Line() {}
Line(const Point& _a, const Point& _b) : a(_a), b(_b) {}
explicit operator Lines() const { Lines lines; lines.emplace_back(*this); return lines; }
void scale(double factor) { this->a *= factor; this->b *= factor; }
void translate(double x, double y) { Vector v(x, y); this->a += v; this->b += v; }
void rotate(double angle, const Point &center) { this->a.rotate(angle, center); this->b.rotate(angle, center); }
void reverse() { std::swap(this->a, this->b); }
double length() const { return (b - a).cast<double>().norm(); }
Point midpoint() const { return (this->a + this->b) / 2; }
bool intersection_infinite(const Line &other, Point* point) const;
bool operator==(const Line &rhs) const { return this->a == rhs.a && this->b == rhs.b; }
double distance_to(const Point &point) const;
double perp_distance_to(const Point &point) const;
bool parallel_to(double angle) const;
bool parallel_to(const Line &line) const { return this->parallel_to(line.direction()); }
double atan2_() const { return atan2(this->b(1) - this->a(1), this->b(0) - this->a(0)); }
double orientation() const;
double direction() const;
Vector vector() const { return this->b - this->a; }
Vector normal() const { return Vector((this->b(1) - this->a(1)), -(this->b(0) - this->a(0))); }
bool intersection(const Line& line, Point* intersection) const;
double ccw(const Point& point) const { return point.ccw(*this); }
Point a;
Point b;
};
class ThickLine : public Line
{
public:
ThickLine() : a_width(0), b_width(0) {}
ThickLine(const Point& a, const Point& b) : Line(a, b), a_width(0), b_width(0) {}
ThickLine(const Point& a, const Point& b, double wa, double wb) : Line(a, b), a_width(wa), b_width(wb) {}
double a_width, b_width;
};
class Line3
{
public:
Line3() : a(Vec3crd::Zero()), b(Vec3crd::Zero()) {}
Line3(const Vec3crd& _a, const Vec3crd& _b) : a(_a), b(_b) {}
double length() const { return (this->a - this->b).cast<double>().norm(); }
Vec3crd vector() const { return this->b - this->a; }
Vec3crd a;
Vec3crd b;
};
class Linef
{
public:
Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {}
Linef(const Vec2d& _a, const Vec2d& _b) : a(_a), b(_b) {}
Vec2d a;
Vec2d b;
};
class Linef3
{
public:
Linef3() : a(Vec3d::Zero()), b(Vec3d::Zero()) {}
Linef3(const Vec3d& _a, const Vec3d& _b) : a(_a), b(_b) {}
Vec3d intersect_plane(double z) const;
void scale(double factor) { this->a *= factor; this->b *= factor; }
Vec3d vector() const { return this->b - this->a; }
Vec3d unit_vector() const { return (length() == 0.0) ? Vec3d::Zero() : vector().normalized(); }
double length() const { return vector().norm(); }
Vec3d a;
Vec3d b;
};
} // namespace Slic3r
// start Boost
#include <boost/polygon/polygon.hpp>
namespace boost { namespace polygon {
template <>
struct geometry_concept<Slic3r::Line> { typedef segment_concept type; };
template <>
struct segment_traits<Slic3r::Line> {
typedef coord_t coordinate_type;
typedef Slic3r::Point point_type;
static inline point_type get(const Slic3r::Line& line, direction_1d dir) {
return dir.to_int() ? line.b : line.a;
}
};
} }
// end Boost
#endif

1125
src/libslic3r/Model.cpp Normal file

File diff suppressed because it is too large Load diff

372
src/libslic3r/Model.hpp Normal file
View file

@ -0,0 +1,372 @@
#ifndef slic3r_Model_hpp_
#define slic3r_Model_hpp_
#include "libslic3r.h"
#include "PrintConfig.hpp"
#include "Layer.hpp"
#include "Point.hpp"
#include "TriangleMesh.hpp"
#include "Slicing.hpp"
#include <map>
#include <string>
#include <utility>
#include <vector>
namespace Slic3r {
class Model;
class ModelInstance;
class ModelMaterial;
class ModelObject;
class ModelVolume;
class PresetBundle;
typedef std::string t_model_material_id;
typedef std::string t_model_material_attribute;
typedef std::map<t_model_material_attribute,std::string> t_model_material_attributes;
typedef std::map<t_model_material_id,ModelMaterial*> ModelMaterialMap;
typedef std::vector<ModelObject*> ModelObjectPtrs;
typedef std::vector<ModelVolume*> ModelVolumePtrs;
typedef std::vector<ModelInstance*> ModelInstancePtrs;
// Material, which may be shared across multiple ModelObjects of a single Model.
class ModelMaterial
{
friend class Model;
public:
// Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose.
t_model_material_attributes attributes;
// Dynamic configuration storage for the object specific configuration values, overriding the global configuration.
DynamicPrintConfig config;
Model* get_model() const { return m_model; }
void apply(const t_model_material_attributes &attributes)
{ this->attributes.insert(attributes.begin(), attributes.end()); }
private:
// Parent, owning this material.
Model *m_model;
ModelMaterial(Model *model) : m_model(model) {}
ModelMaterial(Model *model, const ModelMaterial &other) : attributes(other.attributes), config(other.config), m_model(model) {}
};
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
// different rotation and different uniform scaling.
class ModelObject
{
friend class Model;
public:
std::string name;
std::string input_file;
// Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling.
// Instances are owned by this ModelObject.
ModelInstancePtrs instances;
// Printable and modifier volumes, each with its material ID and a set of override parameters.
// ModelVolumes are owned by this ModelObject.
ModelVolumePtrs volumes;
// Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings.
DynamicPrintConfig config;
// Variation of a layer thickness for spans of Z coordinates.
t_layer_height_ranges layer_height_ranges;
// Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers.
// The pairs of <z, layer_height> are packed into a 1D array to simplify handling by the Perl XS.
std::vector<coordf_t> layer_height_profile;
// layer_height_profile is initialized when the layer editing mode is entered.
// Only if the user really modified the layer height, layer_height_profile_valid is set
// and used subsequently by the PrintObject.
bool layer_height_profile_valid;
/* This vector accumulates the total translation applied to the object by the
center_around_origin() method. Callers might want to apply the same translation
to new volumes before adding them to this object in order to preserve alignment
when user expects that. */
Vec3d origin_translation;
Model* get_model() const { return m_model; };
ModelVolume* add_volume(const TriangleMesh &mesh);
ModelVolume* add_volume(TriangleMesh &&mesh);
ModelVolume* add_volume(const ModelVolume &volume);
void delete_volume(size_t idx);
void clear_volumes();
ModelInstance* add_instance();
ModelInstance* add_instance(const ModelInstance &instance);
void delete_instance(size_t idx);
void delete_last_instance();
void clear_instances();
// Returns the bounding box of the transformed instances.
// This bounding box is approximate and not snug.
// This bounding box is being cached.
const BoundingBoxf3& bounding_box() const;
void invalidate_bounding_box() { m_bounding_box_valid = false; }
// A mesh containing all transformed instances of this object.
TriangleMesh mesh() const;
// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
// Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D platter.
TriangleMesh raw_mesh() const;
// A transformed snug bounding box around the non-modifier object volumes, without the translation applied.
// This bounding box is only used for the actual slicing.
BoundingBoxf3 raw_bounding_box() const;
// A snug bounding box around the transformed non-modifier object volumes.
BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const;
void center_around_origin();
void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); }
void translate(coordf_t x, coordf_t y, coordf_t z);
void scale(const Vec3d &versor);
void rotate(float angle, const Axis &axis);
void rotate(float angle, const Vec3d& axis);
void mirror(const Axis &axis);
size_t materials_count() const;
size_t facets_count() const;
bool needed_repair() const;
void cut(coordf_t z, Model* model) const;
void split(ModelObjectPtrs* new_objects);
// Called by Print::validate() from the UI thread.
void check_instances_print_volume_state(const BoundingBoxf3& print_volume);
// Print object statistics to console.
void print_info() const;
private:
ModelObject(Model *model) : layer_height_profile_valid(false), m_model(model), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false) {}
ModelObject(Model *model, const ModelObject &other, bool copy_volumes = true);
ModelObject& operator= (ModelObject other);
void swap(ModelObject &other);
~ModelObject();
// Parent object, owning this ModelObject.
Model *m_model;
// Bounding box, cached.
mutable BoundingBoxf3 m_bounding_box;
mutable bool m_bounding_box_valid;
};
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
// ModelVolume instances are owned by a ModelObject.
class ModelVolume
{
friend class ModelObject;
// The convex hull of this model's mesh.
TriangleMesh m_convex_hull;
public:
std::string name;
// The triangular model.
TriangleMesh mesh;
// Configuration parameters specific to an object model geometry or a modifier volume,
// overriding the global Slic3r settings and the ModelObject settings.
DynamicPrintConfig config;
enum Type {
MODEL_TYPE_INVALID = -1,
MODEL_PART = 0,
PARAMETER_MODIFIER,
SUPPORT_ENFORCER,
SUPPORT_BLOCKER,
};
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; };
Type type() const { return m_type; }
void set_type(const Type t) { m_type = t; }
bool is_model_part() const { return m_type == MODEL_PART; }
bool is_modifier() const { return m_type == PARAMETER_MODIFIER; }
bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; }
bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; }
t_model_material_id material_id() const { return this->_material_id; }
void material_id(t_model_material_id material_id);
ModelMaterial* material() const;
void set_material(t_model_material_id material_id, const ModelMaterial &material);
// Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object.
size_t split(unsigned int max_extruders);
ModelMaterial* assign_unique_material();
void calculate_convex_hull();
const TriangleMesh& get_convex_hull() const;
// Helpers for loading / storing into AMF / 3MF files.
static Type type_from_string(const std::string &s);
static std::string type_to_string(const Type t);
private:
// Parent object owning this ModelVolume.
ModelObject* object;
// Is it an object to be printed, or a modifier volume?
Type m_type;
t_model_material_id _material_id;
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object)
{
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {}
ModelVolume(ModelObject *object, const ModelVolume &other) :
name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object)
{
this->material_id(other.material_id());
}
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object)
{
this->material_id(other.material_id());
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
}
};
// A single instance of a ModelObject.
// Knows the affine transformation of an object.
class ModelInstance
{
public:
enum EPrintVolumeState : unsigned char
{
PVS_Inside,
PVS_Partly_Outside,
PVS_Fully_Outside,
Num_BedStates
};
friend class ModelObject;
#if ENABLE_MODELINSTANCE_3D_OFFSET
private:
Vec3d m_offset; // in unscaled coordinates
public:
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
double rotation; // Rotation around the Z axis, in radians around mesh center point
double scaling_factor;
#if !ENABLE_MODELINSTANCE_3D_OFFSET
Vec2d offset; // in unscaled coordinates
#endif // !ENABLE_MODELINSTANCE_3D_OFFSET
// flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state())
EPrintVolumeState print_volume_state;
ModelObject* get_object() const { return this->object; }
#if ENABLE_MODELINSTANCE_3D_OFFSET
const Vec3d& get_offset() const { return m_offset; }
double get_offset(Axis axis) const { return m_offset(axis); }
void set_offset(const Vec3d& offset) { m_offset = offset; }
void set_offset(Axis axis, double offset) { m_offset(axis) = offset; }
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
// To be called on an external mesh
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
// Calculate a bounding box of a transformed mesh. To be called on an external mesh.
BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate = false) const;
// Transform an external bounding box.
BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const;
// Transform an external vector.
Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const;
// To be called on an external polygon. It does not translate the polygon, only rotates and scales.
void transform_polygon(Polygon* polygon) const;
Transform3d world_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false) const;
bool is_printable() const { return print_volume_state == PVS_Inside; }
private:
// Parent object, owning this instance.
ModelObject* object;
#if ENABLE_MODELINSTANCE_3D_OFFSET
ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), m_offset(Vec3d::Zero()), object(object), print_volume_state(PVS_Inside) {}
ModelInstance(ModelObject *object, const ModelInstance &other) :
rotation(other.rotation), scaling_factor(other.scaling_factor), m_offset(other.m_offset), object(object), print_volume_state(PVS_Inside) {}
#else
ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), offset(Vec2d::Zero()), object(object), print_volume_state(PVS_Inside) {}
ModelInstance(ModelObject *object, const ModelInstance &other) :
rotation(other.rotation), scaling_factor(other.scaling_factor), offset(other.offset), object(object), print_volume_state(PVS_Inside) {}
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
};
// The print bed content.
// Description of a triangular model with multiple materials, multiple instances with various affine transformations
// and with multiple modifier meshes.
// A model groups multiple objects, each object having possibly multiple instances,
// all objects may share mutliple materials.
class Model
{
static unsigned int s_auto_extruder_id;
public:
// Materials are owned by a model and referenced by objects through t_model_material_id.
// Single material may be shared by multiple models.
ModelMaterialMap materials;
// Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation).
ModelObjectPtrs objects;
Model() {}
Model(const Model &other);
Model& operator= (Model other);
void swap(Model &other);
~Model() { this->clear_objects(); this->clear_materials(); }
static Model read_from_file(const std::string &input_file, bool add_default_instances = true);
static Model read_from_archive(const std::string &input_file, PresetBundle* bundle, bool add_default_instances = true);
ModelObject* add_object();
ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh);
ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh);
ModelObject* add_object(const ModelObject &other, bool copy_volumes = true);
void delete_object(size_t idx);
void delete_object(ModelObject* object);
void clear_objects();
ModelMaterial* add_material(t_model_material_id material_id);
ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other);
ModelMaterial* get_material(t_model_material_id material_id) {
ModelMaterialMap::iterator i = this->materials.find(material_id);
return (i == this->materials.end()) ? nullptr : i->second;
}
void delete_material(t_model_material_id material_id);
void clear_materials();
bool add_default_instances();
// Returns approximate axis aligned bounding box of this model
BoundingBoxf3 bounding_box() const;
void center_instances_around_point(const Vec2d &point);
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
TriangleMesh mesh() const;
bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL);
// Croaks if the duplicated objects do not fit the print bed.
void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
bool looks_like_multipart_object() const;
void convert_multipart_object(unsigned int max_extruders);
// Ensures that the min z of the model is not negative
void adjust_min_z();
void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); }
static unsigned int get_auto_extruder_id(unsigned int max_extruders);
static std::string get_auto_extruder_id_as_string(unsigned int max_extruders);
static void reset_auto_extruder_id();
};
}
#endif

View file

@ -0,0 +1,825 @@
#ifndef MODELARRANGE_HPP
#define MODELARRANGE_HPP
#include "Model.hpp"
#include "SVG.hpp"
#include <libnest2d.h>
#include <numeric>
#include <ClipperUtils.hpp>
#include <boost/geometry/index/rtree.hpp>
namespace Slic3r {
namespace arr {
using namespace libnest2d;
std::string toString(const Model& model, bool holes = true) {
std::stringstream ss;
ss << "{\n";
for(auto objptr : model.objects) {
if(!objptr) continue;
auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) {
if(!objinst) continue;
Slic3r::TriangleMesh tmpmesh = rmesh;
tmpmesh.scale(objinst->scaling_factor);
objinst->transform_mesh(&tmpmesh);
ExPolygons expolys = tmpmesh.horizontal_projection();
for(auto& expoly_complex : expolys) {
auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
if(tmp.empty()) continue;
auto expoly = tmp.front();
expoly.contour.make_clockwise();
for(auto& h : expoly.holes) h.make_counter_clockwise();
ss << "\t{\n";
ss << "\t\t{\n";
for(auto v : expoly.contour.points) ss << "\t\t\t{"
<< v(0) << ", "
<< v(1) << "},\n";
{
auto v = expoly.contour.points.front();
ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n";
}
ss << "\t\t},\n";
// Holes:
ss << "\t\t{\n";
if(holes) for(auto h : expoly.holes) {
ss << "\t\t\t{\n";
for(auto v : h.points) ss << "\t\t\t\t{"
<< v(0) << ", "
<< v(1) << "},\n";
{
auto v = h.points.front();
ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n";
}
ss << "\t\t\t},\n";
}
ss << "\t\t},\n";
ss << "\t},\n";
}
}
}
ss << "}\n";
return ss.str();
}
void toSVG(SVG& svg, const Model& model) {
for(auto objptr : model.objects) {
if(!objptr) continue;
auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) {
if(!objinst) continue;
Slic3r::TriangleMesh tmpmesh = rmesh;
tmpmesh.scale(objinst->scaling_factor);
objinst->transform_mesh(&tmpmesh);
ExPolygons expolys = tmpmesh.horizontal_projection();
svg.draw(expolys);
}
}
}
namespace bgi = boost::geometry::index;
using SpatElement = std::pair<Box, unsigned>;
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
template<class TBin>
using TPacker = typename placers::_NofitPolyPlacer<PolygonImpl, TBin>;
const double BIG_ITEM_TRESHOLD = 0.02;
Box boundingBox(const Box& pilebb, const Box& ibb ) {
auto& pminc = pilebb.minCorner();
auto& pmaxc = pilebb.maxCorner();
auto& iminc = ibb.minCorner();
auto& imaxc = ibb.maxCorner();
PointImpl minc, maxc;
setX(minc, std::min(getX(pminc), getX(iminc)));
setY(minc, std::min(getY(pminc), getY(iminc)));
setX(maxc, std::max(getX(pmaxc), getX(imaxc)));
setY(maxc, std::max(getY(pmaxc), getY(imaxc)));
return Box(minc, maxc);
}
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
objfunc(const PointImpl& bincenter,
const shapelike::Shapes<PolygonImpl>& merged_pile,
const Box& pilebb,
const ItemGroup& items,
const Item &item,
double bin_area,
double norm, // A norming factor for physical dimensions
// a spatial index to quickly get neighbors of the candidate item
const SpatIndex& spatindex,
const SpatIndex& smalls_spatindex,
const ItemGroup& remaining
)
{
using Coord = TCoord<PointImpl>;
static const double ROUNDNESS_RATIO = 0.5;
static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO;
// We will treat big items (compared to the print bed) differently
auto isBig = [bin_area](double a) {
return a/bin_area > BIG_ITEM_TRESHOLD ;
};
// Candidate item bounding box
auto ibb = sl::boundingBox(item.transformedShape());
// Calculate the full bounding box of the pile with the candidate item
auto fullbb = boundingBox(pilebb, ibb);
// The bounding box of the big items (they will accumulate in the center
// of the pile
Box bigbb;
if(spatindex.empty()) bigbb = fullbb;
else {
auto boostbb = spatindex.bounds();
boost::geometry::convert(boostbb, bigbb);
}
// Will hold the resulting score
double score = 0;
if(isBig(item.area()) || spatindex.empty()) {
// This branch is for the bigger items..
auto minc = ibb.minCorner(); // bottom left corner
auto maxc = ibb.maxCorner(); // top right corner
// top left and bottom right corners
auto top_left = PointImpl{getX(minc), getY(maxc)};
auto bottom_right = PointImpl{getX(maxc), getY(minc)};
// Now the distance of the gravity center will be calculated to the
// five anchor points and the smallest will be chosen.
std::array<double, 5> dists;
auto cc = fullbb.center(); // The gravity center
dists[0] = pl::distance(minc, cc);
dists[1] = pl::distance(maxc, cc);
dists[2] = pl::distance(ibb.center(), cc);
dists[3] = pl::distance(top_left, cc);
dists[4] = pl::distance(bottom_right, cc);
// The smalles distance from the arranged pile center:
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
dist = 0.8*dist + 0.2*bindist;
// Density is the pack density: how big is the arranged pile
double density = 0;
if(remaining.empty()) {
auto mp = merged_pile;
mp.emplace_back(item.transformedShape());
auto chull = sl::convexHull(mp);
placers::EdgeCache<PolygonImpl> ec(chull);
double circ = ec.circumference() / norm;
double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm;
score = 0.5*circ + 0.5*bcirc;
} else {
// Prepare a variable for the alignment score.
// This will indicate: how well is the candidate item aligned with
// its neighbors. We will check the alignment with all neighbors and
// return the score for the best alignment. So it is enough for the
// candidate to be aligned with only one item.
auto alignment_score = 1.0;
density = std::sqrt((fullbb.width() / norm )*
(fullbb.height() / norm));
auto querybb = item.boundingBox();
// Query the spatial index for the neighbors
std::vector<SpatElement> result;
result.reserve(spatindex.size());
if(isBig(item.area())) {
spatindex.query(bgi::intersects(querybb),
std::back_inserter(result));
} else {
smalls_spatindex.query(bgi::intersects(querybb),
std::back_inserter(result));
}
for(auto& e : result) { // now get the score for the best alignment
auto idx = e.second;
Item& p = items[idx];
auto parea = p.area();
if(std::abs(1.0 - parea/item.area()) < 1e-6) {
auto bb = boundingBox(p.boundingBox(), ibb);
auto bbarea = bb.area();
auto ascore = 1.0 - (item.area() + parea)/bbarea;
if(ascore < alignment_score) alignment_score = ascore;
}
}
// The final mix of the score is the balance between the distance
// from the full pile center, the pack density and the
// alignment with the neighbors
if(result.empty())
score = 0.5 * dist + 0.5 * density;
else
score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score;
}
} else {
// Here there are the small items that should be placed around the
// already processed bigger items.
// No need to play around with the anchor points, the center will be
// just fine for small items
score = pl::distance(ibb.center(), bigbb.center()) / norm;
}
return std::make_tuple(score, fullbb);
}
template<class PConf>
void fillConfig(PConf& pcfg) {
// Align the arranged pile into the center of the bin
pcfg.alignment = PConf::Alignment::CENTER;
// Start placing the items from the center of the print bed
pcfg.starting_point = PConf::Alignment::CENTER;
// TODO cannot use rotations until multiple objects of same geometry can
// handle different rotations
// arranger.useMinimumBoundigBoxRotation();
pcfg.rotations = { 0.0 };
// The accuracy of optimization.
// Goes from 0.0 to 1.0 and scales performance as well
pcfg.accuracy = 0.65f;
pcfg.parallel = true;
}
template<class TBin>
class AutoArranger {};
template<class TBin>
class _ArrBase {
protected:
using Placer = TPacker<TBin>;
using Selector = FirstFitSelection;
using Packer = Nester<Placer, Selector>;
using PConfig = typename Packer::PlacementConfig;
using Distance = TCoord<PointImpl>;
using Pile = sl::Shapes<PolygonImpl>;
Packer pck_;
PConfig pconf_; // Placement configuration
double bin_area_;
SpatIndex rtree_;
SpatIndex smallsrtree_;
double norm_;
Pile merged_pile_;
Box pilebb_;
ItemGroup remaining_;
ItemGroup items_;
public:
_ArrBase(const TBin& bin, Distance dist,
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond):
pck_(bin, dist), bin_area_(sl::area(bin)),
norm_(std::sqrt(sl::area(bin)))
{
fillConfig(pconf_);
pconf_.before_packing =
[this](const Pile& merged_pile, // merged pile
const ItemGroup& items, // packed items
const ItemGroup& remaining) // future items to be packed
{
items_ = items;
merged_pile_ = merged_pile;
remaining_ = remaining;
pilebb_ = sl::boundingBox(merged_pile);
rtree_.clear();
smallsrtree_.clear();
// We will treat big items (compared to the print bed) differently
auto isBig = [this](double a) {
return a/bin_area_ > BIG_ITEM_TRESHOLD ;
};
for(unsigned idx = 0; idx < items.size(); ++idx) {
Item& itm = items[idx];
if(isBig(itm.area())) rtree_.insert({itm.boundingBox(), idx});
smallsrtree_.insert({itm.boundingBox(), idx});
}
};
pck_.progressIndicator(progressind);
pck_.stopCondition(stopcond);
}
template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
rtree_.clear();
return pck_.executeIndexed(std::forward<Args>(args)...);
}
};
template<>
class AutoArranger<Box>: public _ArrBase<Box> {
public:
AutoArranger(const Box& bin, Distance dist,
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond):
_ArrBase<Box>(bin, dist, progressind, stopcond)
{
pconf_.object_function = [this, bin] (const Item &item) {
auto result = objfunc(bin.center(),
merged_pile_,
pilebb_,
items_,
item,
bin_area_,
norm_,
rtree_,
smallsrtree_,
remaining_);
double score = std::get<0>(result);
auto& fullbb = std::get<1>(result);
double miss = Placer::overfit(fullbb, bin);
miss = miss > 0? miss : 0;
score += miss*miss;
return score;
};
pck_.configure(pconf_);
}
};
using lnCircle = libnest2d::_Circle<libnest2d::PointImpl>;
template<>
class AutoArranger<lnCircle>: public _ArrBase<lnCircle> {
public:
AutoArranger(const lnCircle& bin, Distance dist,
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond):
_ArrBase<lnCircle>(bin, dist, progressind, stopcond) {
pconf_.object_function = [this, &bin] (const Item &item) {
auto result = objfunc(bin.center(),
merged_pile_,
pilebb_,
items_,
item,
bin_area_,
norm_,
rtree_,
smallsrtree_,
remaining_);
double score = std::get<0>(result);
auto isBig = [this](const Item& itm) {
return itm.area()/bin_area_ > BIG_ITEM_TRESHOLD ;
};
if(isBig(item)) {
auto mp = merged_pile_;
mp.push_back(item.transformedShape());
auto chull = sl::convexHull(mp);
double miss = Placer::overfit(chull, bin);
if(miss < 0) miss = 0;
score += miss*miss;
}
return score;
};
pck_.configure(pconf_);
}
};
template<>
class AutoArranger<PolygonImpl>: public _ArrBase<PolygonImpl> {
public:
AutoArranger(const PolygonImpl& bin, Distance dist,
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond):
_ArrBase<PolygonImpl>(bin, dist, progressind, stopcond)
{
pconf_.object_function = [this, &bin] (const Item &item) {
auto binbb = sl::boundingBox(bin);
auto result = objfunc(binbb.center(),
merged_pile_,
pilebb_,
items_,
item,
bin_area_,
norm_,
rtree_,
smallsrtree_,
remaining_);
double score = std::get<0>(result);
return score;
};
pck_.configure(pconf_);
}
};
template<> // Specialization with no bin
class AutoArranger<bool>: public _ArrBase<Box> {
public:
AutoArranger(Distance dist, std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcond):
_ArrBase<Box>(Box(0, 0), dist, progressind, stopcond)
{
this->pconf_.object_function = [this] (const Item &item) {
auto result = objfunc({0, 0},
merged_pile_,
pilebb_,
items_,
item,
0,
norm_,
rtree_,
smallsrtree_,
remaining_);
return std::get<0>(result);
};
this->pck_.configure(pconf_);
}
};
// A container which stores a pointer to the 3D object and its projected
// 2D shape from top view.
using ShapeData2D =
std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
ShapeData2D ret;
auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0,
[](size_t s, ModelObject* o){
return s + o->instances.size();
});
ret.reserve(s);
for(auto objptr : model.objects) {
if(objptr) {
auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) {
if(objinst) {
Slic3r::TriangleMesh tmpmesh = rmesh;
ClipperLib::PolygonImpl pn;
tmpmesh.scale(objinst->scaling_factor);
// TODO export the exact 2D projection
auto p = tmpmesh.convex_hull();
p.make_clockwise();
p.append(p.first_point());
pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
// Efficient conversion to item.
Item item(std::move(pn));
// Invalid geometries would throw exceptions when arranging
if(item.vertexCount() > 3) {
item.rotation(objinst->rotation);
item.translation( {
#if ENABLE_MODELINSTANCE_3D_OFFSET
ClipperLib::cInt(objinst->get_offset(X) / SCALING_FACTOR),
ClipperLib::cInt(objinst->get_offset(Y) / SCALING_FACTOR)
#else
ClipperLib::cInt(objinst->offset(0)/SCALING_FACTOR),
ClipperLib::cInt(objinst->offset(1)/SCALING_FACTOR)
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
});
ret.emplace_back(objinst, item);
}
}
}
}
}
return ret;
}
class Circle {
Point center_;
double radius_;
public:
inline Circle(): center_(0, 0), radius_(std::nan("")) {}
inline Circle(const Point& c, double r): center_(c), radius_(r) {}
inline double radius() const { return radius_; }
inline const Point& center() const { return center_; }
inline operator bool() { return !std::isnan(radius_); }
inline operator lnCircle() {
return lnCircle({center_(0), center_(1)}, radius_);
}
};
enum class BedShapeType {
BOX,
CIRCLE,
IRREGULAR,
WHO_KNOWS
};
struct BedShapeHint {
BedShapeType type;
/*union*/ struct { // I know but who cares...
Circle circ;
BoundingBox box;
Polyline polygon;
} shape;
};
BedShapeHint bedShape(const Polyline& bed) {
BedShapeHint ret;
auto x = [](const Point& p) { return p(0); };
auto y = [](const Point& p) { return p(1); };
auto width = [x](const BoundingBox& box) {
return x(box.max) - x(box.min);
};
auto height = [y](const BoundingBox& box) {
return y(box.max) - y(box.min);
};
auto area = [&width, &height](const BoundingBox& box) {
double w = width(box);
double h = height(box);
return w*h;
};
auto poly_area = [](Polyline p) {
Polygon pp; pp.points.reserve(p.points.size() + 1);
pp.points = std::move(p.points);
pp.points.emplace_back(pp.points.front());
return std::abs(pp.area());
};
auto distance_to = [x, y](const Point& p1, const Point& p2) {
double dx = x(p2) - x(p1);
double dy = y(p2) - y(p1);
return std::sqrt(dx*dx + dy*dy);
};
auto bb = bed.bounding_box();
auto isCircle = [bb, distance_to](const Polyline& polygon) {
auto center = bb.center();
std::vector<double> vertex_distances;
double avg_dist = 0;
for (auto pt: polygon.points)
{
double distance = distance_to(center, pt);
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
Circle ret(center, avg_dist);
for (auto el: vertex_distances)
{
if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
ret = Circle();
break;
}
return ret;
};
auto parea = poly_area(bed);
if( (1.0 - parea/area(bb)) < 1e-3 ) {
ret.type = BedShapeType::BOX;
ret.shape.box = bb;
}
else if(auto c = isCircle(bed)) {
ret.type = BedShapeType::CIRCLE;
ret.shape.circ = c;
} else {
ret.type = BedShapeType::IRREGULAR;
ret.shape.polygon = bed;
}
// Determine the bed shape by hand
return ret;
}
void applyResult(
IndexedPackGroup::value_type& group,
Coord batch_offset,
ShapeData2D& shapemap)
{
for(auto& r : group) {
auto idx = r.first; // get the original item index
Item& item = r.second; // get the item itself
// Get the model instance from the shapemap using the index
ModelInstance *inst_ptr = shapemap[idx].first;
// Get the tranformation data from the item object and scale it
// appropriately
auto off = item.translation();
Radians rot = item.rotation();
#if ENABLE_MODELINSTANCE_3D_OFFSET
Vec3d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR, 0.0);
#else
Vec2d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR);
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
// write the tranformation data into the model instance
inst_ptr->rotation = rot;
#if ENABLE_MODELINSTANCE_3D_OFFSET
inst_ptr->set_offset(foff);
#else
inst_ptr->offset = foff;
#endif // ENABLE_MODELINSTANCE_3D_OFFSET
}
}
/**
* \brief Arranges the model objects on the screen.
*
* The arrangement considers multiple bins (aka. print beds) for placing all
* the items provided in the model argument. If the items don't fit on one
* print bed, the remaining will be placed onto newly created print beds.
* The first_bin_only parameter, if set to true, disables this behaviour and
* makes sure that only one print bed is filled and the remaining items will be
* untouched. When set to false, the items which could not fit onto the
* print bed will be placed next to the print bed so the user should see a
* pile of items on the print bed and some other piles outside the print
* area that can be dragged later onto the print bed as a group.
*
* \param model The model object with the 3D content.
* \param dist The minimum distance which is allowed for any pair of items
* on the print bed in any direction.
* \param bb The bounding box of the print bed. It corresponds to the 'bin'
* for bin packing.
* \param first_bin_only This parameter controls whether to place the
* remaining items which do not fit onto the print area next to the print
* bed or leave them untouched (let the user arrange them by hand or remove
* them).
* \param progressind Progress indicator callback called when an object gets
* packed. The unsigned argument is the number of items remaining to pack.
* \param stopcondition A predicate returning true if abort is needed.
*/
bool arrange(Model &model, coordf_t min_obj_distance,
const Slic3r::Polyline& bed,
BedShapeHint bedhint,
bool first_bin_only,
std::function<void(unsigned)> progressind,
std::function<bool(void)> stopcondition)
{
using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
bool ret = true;
// Get the 2D projected shapes with their 3D model instance pointers
auto shapemap = arr::projectModelFromTop(model);
// Copy the references for the shapes only as the arranger expects a
// sequence of objects convertible to Item or ClipperPolygon
std::vector<std::reference_wrapper<Item>> shapes;
shapes.reserve(shapemap.size());
std::for_each(shapemap.begin(), shapemap.end(),
[&shapes] (ShapeData2D::value_type& it)
{
shapes.push_back(std::ref(it.second));
});
IndexedPackGroup result;
if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed);
BoundingBox bbb(bed);
auto& cfn = stopcondition;
auto binbb = Box({
static_cast<libnest2d::Coord>(bbb.min(0)),
static_cast<libnest2d::Coord>(bbb.min(1))
},
{
static_cast<libnest2d::Coord>(bbb.max(0)),
static_cast<libnest2d::Coord>(bbb.max(1))
});
switch(bedhint.type) {
case BedShapeType::BOX: {
// Create the arranger for the box shaped bed
AutoArranger<Box> arrange(binbb, min_obj_distance, progressind, cfn);
// Arrange and return the items with their respective indices within the
// input sequence.
result = arrange(shapes.begin(), shapes.end());
break;
}
case BedShapeType::CIRCLE: {
auto c = bedhint.shape.circ;
auto cc = lnCircle(c);
AutoArranger<lnCircle> arrange(cc, min_obj_distance, progressind, cfn);
result = arrange(shapes.begin(), shapes.end());
break;
}
case BedShapeType::IRREGULAR:
case BedShapeType::WHO_KNOWS: {
using P = libnest2d::PolygonImpl;
auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
P irrbed = sl::create<PolygonImpl>(std::move(ctour));
AutoArranger<P> arrange(irrbed, min_obj_distance, progressind, cfn);
// Arrange and return the items with their respective indices within the
// input sequence.
result = arrange(shapes.begin(), shapes.end());
break;
}
};
if(result.empty() || stopcondition()) return false;
if(first_bin_only) {
applyResult(result.front(), 0, shapemap);
} else {
const auto STRIDE_PADDING = 1.2;
Coord stride = static_cast<Coord>(STRIDE_PADDING*
binbb.width()*SCALING_FACTOR);
Coord batch_offset = 0;
for(auto& group : result) {
applyResult(group, batch_offset, shapemap);
// Only the first pack group can be placed onto the print bed. The
// other objects which could not fit will be placed next to the
// print bed
batch_offset += stride;
}
}
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
return ret && result.size() == 1;
}
}
}
#endif // MODELARRANGE_HPP

View file

@ -0,0 +1,362 @@
#include "BoundingBox.hpp"
#include "MotionPlanner.hpp"
#include "MutablePriorityQueue.hpp"
#include "Utils.hpp"
#include <limits> // for numeric_limits
#include <assert.h>
#include "boost/polygon/voronoi.hpp"
using boost::polygon::voronoi_builder;
using boost::polygon::voronoi_diagram;
namespace Slic3r {
MotionPlanner::MotionPlanner(const ExPolygons &islands) : m_initialized(false)
{
ExPolygons expp;
for (const ExPolygon &island : islands) {
island.simplify(SCALED_EPSILON, &expp);
for (ExPolygon &island : expp)
m_islands.emplace_back(MotionPlannerEnv(island));
expp.clear();
}
}
void MotionPlanner::initialize()
{
// prevent initialization of empty BoundingBox
if (m_initialized || m_islands.empty())
return;
// loop through islands in order to create inner expolygons and collect their contours.
Polygons outer_holes;
for (MotionPlannerEnv &island : m_islands) {
// Generate the internal env boundaries by shrinking the island
// we'll use these inner rings for motion planning (endpoints of the Voronoi-based
// graph, visibility check) in order to avoid moving too close to the boundaries.
island.m_env = ExPolygonCollection(offset_ex(island.m_island, -MP_INNER_MARGIN));
// Island contours are holes of our external environment.
outer_holes.push_back(island.m_island.contour);
}
// Generate a box contour around everyting.
Polygons contour = offset(get_extents(outer_holes).polygon(), +MP_OUTER_MARGIN*2);
assert(contour.size() == 1);
// make expolygon for outer environment
ExPolygons outer = diff_ex(contour, outer_holes);
assert(outer.size() == 1);
// If some of the islands are nested, then the 0th contour is the outer contour due to the order of conversion
// from Clipper data structure into the Slic3r expolygons inside diff_ex().
m_outer = MotionPlannerEnv(outer.front());
m_outer.m_env = ExPolygonCollection(diff_ex(contour, offset(outer_holes, +MP_OUTER_MARGIN)));
m_graphs.resize(m_islands.size() + 1);
m_initialized = true;
}
Polyline MotionPlanner::shortest_path(const Point &from, const Point &to)
{
// If we have an empty configuration space, return a straight move.
if (m_islands.empty())
return Polyline(from, to);
// Are both points in the same island?
int island_idx_from = -1;
int island_idx_to = -1;
int island_idx = -1;
for (MotionPlannerEnv &island : m_islands) {
int idx = &island - m_islands.data();
if (island.island_contains(from))
island_idx_from = idx;
if (island.island_contains(to))
island_idx_to = idx;
if (island_idx_from == idx && island_idx_to == idx) {
// Since both points are in the same island, is a direct move possible?
// If so, we avoid generating the visibility environment.
if (island.m_island.contains(Line(from, to)))
return Polyline(from, to);
// Both points are inside a single island, but the straight line crosses the island boundary.
island_idx = idx;
break;
}
}
// lazy generation of configuration space.
this->initialize();
// Get environment. If the from / to points do not share an island, then they cross an open space,
// therefore island_idx == -1 and env will be set to the environment of the empty space.
const MotionPlannerEnv &env = this->get_env(island_idx);
if (env.m_env.expolygons.empty()) {
// if this environment is empty (probably because it's too small), perform straight move
// and avoid running the algorithms on empty dataset
return Polyline(from, to);
}
// Now check whether points are inside the environment.
Point inner_from = from;
Point inner_to = to;
if (island_idx == -1) {
// The end points do not share the same island. In that case some of the travel
// will be likely performed inside the empty space.
// TODO: instead of using the nearest_env_point() logic, we should
// create a temporary graph where we connect 'from' and 'to' to the
// nodes which don't require more than one crossing, and let Dijkstra
// figure out the entire path - this should also replace the call to
// find_node() below
if (island_idx_from != -1)
// The start point is inside some island. Find the closest point at the empty space to start from.
inner_from = env.nearest_env_point(from, to);
if (island_idx_to != -1)
// The start point is inside some island. Find the closest point at the empty space to start from.
inner_to = env.nearest_env_point(to, inner_from);
}
// Perform a path search either in the open space, or in a common island of from/to.
const MotionPlannerGraph &graph = this->init_graph(island_idx);
// If no path exists without crossing perimeters, returns a straight segment.
Polyline polyline = graph.shortest_path(inner_from, inner_to);
polyline.points.insert(polyline.points.begin(), from);
polyline.points.emplace_back(to);
{
// grow our environment slightly in order for simplify_by_visibility()
// to work best by considering moves on boundaries valid as well
ExPolygonCollection grown_env(offset_ex(env.m_env.expolygons, float(+SCALED_EPSILON)));
if (island_idx == -1) {
/* If 'from' or 'to' are not inside our env, they were connected using the
nearest_env_point() search which maybe produce ugly paths since it does not
include the endpoint in the Dijkstra search; the simplify_by_visibility()
call below will not work in many cases where the endpoint is not contained in
grown_env (whose contour was arbitrarily constructed with MP_OUTER_MARGIN,
which may not be enough for, say, including a skirt point). So we prune
the extra points manually. */
if (! grown_env.contains(from)) {
// delete second point while the line connecting first to third crosses the
// boundaries as many times as the current first to second
while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), grown_env).size() == 1)
polyline.points.erase(polyline.points.begin() + 1);
}
if (! grown_env.contains(to))
while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), grown_env).size() == 1)
polyline.points.erase(polyline.points.end() - 2);
}
// Perform some quick simplification (simplify_by_visibility() would make this
// unnecessary, but this is much faster)
polyline.simplify(MP_INNER_MARGIN/10);
// remove unnecessary vertices
// Note: this is computationally intensive and does not look very necessary
// now that we prune the endpoints with the logic above,
// so we comment it for now until a good test case arises
//polyline.simplify_by_visibility(grown_env);
/*
SVG svg("shortest_path.svg");
svg.draw(grown_env.expolygons);
svg.arrows = false;
for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) {
Point a = graph->nodes[it - graph->adjacency_list.begin()];
for (std::vector<MotionPlannerGraph::Neighbor>::const_iterator n = it->begin(); n != it->end(); ++n) {
Point b = graph->nodes[n->target];
svg.draw(Line(a, b));
}
}
svg.arrows = true;
svg.draw(from);
svg.draw(inner_from, "red");
svg.draw(to);
svg.draw(inner_to, "red");
svg.draw(polyline, "red");
svg.Close();
*/
}
return polyline;
}
const MotionPlannerGraph& MotionPlanner::init_graph(int island_idx)
{
// 0th graph is the graph for m_outer. Other graphs are 1 indexed.
MotionPlannerGraph *graph = m_graphs[island_idx + 1].get();
if (graph == nullptr) {
// If this graph doesn't exist, initialize it.
m_graphs[island_idx + 1] = make_unique<MotionPlannerGraph>();
graph = m_graphs[island_idx + 1].get();
/* We don't add polygon boundaries as graph edges, because we'd need to connect
them to the Voronoi-generated edges by recognizing coinciding nodes. */
typedef voronoi_diagram<double> VD;
VD vd;
// Mapping between Voronoi vertices and graph nodes.
std::map<const VD::vertex_type*, size_t> vd_vertices;
// get boundaries as lines
const MotionPlannerEnv &env = this->get_env(island_idx);
Lines lines = env.m_env.lines();
boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd);
// traverse the Voronoi diagram and generate graph nodes and edges
for (const VD::edge_type &edge : vd.edges()) {
if (edge.is_infinite())
continue;
const VD::vertex_type* v0 = edge.vertex0();
const VD::vertex_type* v1 = edge.vertex1();
Point p0(v0->x(), v0->y());
Point p1(v1->x(), v1->y());
// Insert only Voronoi edges fully contained in the island.
//FIXME This test has a terrible O(n^2) time complexity.
if (env.island_contains_b(p0) && env.island_contains_b(p1)) {
// Find v0 in the graph, allocate a new node if v0 does not exist in the graph yet.
auto i_v0 = vd_vertices.find(v0);
size_t v0_idx;
if (i_v0 == vd_vertices.end())
vd_vertices[v0] = v0_idx = graph->add_node(p0);
else
v0_idx = i_v0->second;
// Find v1 in the graph, allocate a new node if v0 does not exist in the graph yet.
auto i_v1 = vd_vertices.find(v1);
size_t v1_idx;
if (i_v1 == vd_vertices.end())
vd_vertices[v1] = v1_idx = graph->add_node(p1);
else
v1_idx = i_v1->second;
// Euclidean distance is used as weight for the graph edge
graph->add_edge(v0_idx, v1_idx, (p1 - p0).cast<double>().norm());
}
}
}
return *graph;
}
// Find a middle point on the path from start_point to end_point with the shortest path.
static inline size_t nearest_waypoint_index(const Point &start_point, const Points &middle_points, const Point &end_point)
{
size_t idx = size_t(-1);
double dmin = std::numeric_limits<double>::infinity();
for (const Point &p : middle_points) {
double d = (p - start_point).cast<double>().norm() + (end_point - p).cast<double>().norm();
if (d < dmin) {
idx = &p - middle_points.data();
dmin = d;
if (dmin < EPSILON)
break;
}
}
return idx;
}
Point MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) const
{
/* In order to ensure that the move between 'from' and the initial env point does
not violate any of the configuration space boundaries, we limit our search to
the points that satisfy this condition. */
/* Assume that this method is never called when 'env' contains 'from';
so 'from' is either inside a hole or outside all contours */
// get the points of the hole containing 'from', if any
Points pp;
for (const ExPolygon &ex : m_env.expolygons) {
for (const Polygon &hole : ex.holes)
if (hole.contains(from))
pp = hole;
if (! pp.empty())
break;
}
// If 'from' is not inside a hole, it's outside of all contours, so take all contours' points.
if (pp.empty())
for (const ExPolygon &ex : m_env.expolygons)
append(pp, ex.contour.points);
// Find the candidate result and check that it doesn't cross too many boundaries.
while (pp.size() > 1) {
// find the point in pp that is closest to both 'from' and 'to'
size_t result = nearest_waypoint_index(from, pp, to);
// as we assume 'from' is outside env, any node will require at least one crossing
if (intersection_ln(Line(from, pp[result]), m_island).size() > 1) {
// discard result
pp.erase(pp.begin() + result);
} else
return pp[result];
}
// if we're here, return last point if any (better than nothing)
// if we have no points at all, then we have an empty environment and we
// make this method behave as a no-op (we shouldn't get here by the way)
return pp.empty() ? from : pp.front();
}
// Add a new directed edge to the adjacency graph.
void MotionPlannerGraph::add_edge(size_t from, size_t to, double weight)
{
// Extend adjacency list until this start node.
if (m_adjacency_list.size() < from + 1) {
// Reserve in powers of two to avoid repeated reallocation.
m_adjacency_list.reserve(std::max<size_t>(8, next_highest_power_of_2(from + 1)));
// Allocate new empty adjacency vectors.
m_adjacency_list.resize(from + 1);
}
m_adjacency_list[from].emplace_back(Neighbor(node_t(to), weight));
}
// Dijkstra's shortest path in a weighted graph from node_start to node_end.
// The returned path contains the end points.
// If no path exists from node_start to node_end, a straight segment is returned.
Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) const
{
// This prevents a crash in case for some reason we got here with an empty adjacency list.
if (this->empty())
return Polyline();
// Dijkstra algorithm, previous node of the current node 'u' in the shortest path towards node_start.
std::vector<node_t> previous(m_adjacency_list.size(), -1);
std::vector<weight_t> distance(m_adjacency_list.size(), std::numeric_limits<weight_t>::infinity());
std::vector<size_t> map_node_to_queue_id(m_adjacency_list.size(), size_t(-1));
distance[node_start] = 0.;
auto queue = make_mutable_priority_queue<node_t>(
[&map_node_to_queue_id](const node_t node, size_t idx) { map_node_to_queue_id[node] = idx; },
[&distance](const node_t node1, const node_t node2) { return distance[node1] < distance[node2]; });
queue.reserve(m_adjacency_list.size());
for (size_t i = 0; i < m_adjacency_list.size(); ++ i)
queue.push(node_t(i));
while (! queue.empty()) {
// Get the next node with the lowest distance to node_start.
node_t u = node_t(queue.top());
queue.pop();
map_node_to_queue_id[u] = size_t(-1);
// Stop searching if we reached our destination.
if (u == node_end)
break;
// Visit each edge starting at node u.
for (const Neighbor& neighbor : m_adjacency_list[u])
if (map_node_to_queue_id[neighbor.target] != size_t(-1)) {
weight_t alt = distance[u] + neighbor.weight;
// If total distance through u is shorter than the previous
// distance (if any) between node_start and neighbor.target, replace it.
if (alt < distance[neighbor.target]) {
distance[neighbor.target] = alt;
previous[neighbor.target] = u;
queue.update(map_node_to_queue_id[neighbor.target]);
}
}
}
// In case the end point was not reached, previous[node_end] contains -1
// and a straight line from node_start to node_end is returned.
Polyline polyline;
polyline.points.reserve(m_adjacency_list.size());
for (node_t vertex = node_t(node_end); vertex != -1; vertex = previous[vertex])
polyline.points.emplace_back(m_nodes[vertex]);
polyline.points.emplace_back(m_nodes[node_start]);
polyline.reverse();
return polyline;
}
}

Some files were not shown because too many files have changed in this diff Show more