Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/supermerill/SuperSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/libslic3r')
-rw-r--r--src/libslic3r/BoundingBox.cpp283
-rw-r--r--src/libslic3r/BoundingBox.hpp163
-rw-r--r--src/libslic3r/BridgeDetector.cpp317
-rw-r--r--src/libslic3r/BridgeDetector.hpp69
-rw-r--r--src/libslic3r/CMakeLists.txt173
-rw-r--r--src/libslic3r/ClipperUtils.cpp798
-rw-r--r--src/libslic3r/ClipperUtils.hpp231
-rw-r--r--src/libslic3r/Config.cpp602
-rw-r--r--src/libslic3r/Config.hpp1264
-rw-r--r--src/libslic3r/EdgeGrid.cpp1453
-rw-r--r--src/libslic3r/EdgeGrid.hpp118
-rw-r--r--src/libslic3r/ExPolygon.cpp555
-rw-r--r--src/libslic3r/ExPolygon.hpp406
-rw-r--r--src/libslic3r/ExPolygonCollection.cpp135
-rw-r--r--src/libslic3r/ExPolygonCollection.hpp41
-rw-r--r--src/libslic3r/Extruder.cpp138
-rw-r--r--src/libslic3r/Extruder.hpp81
-rw-r--r--src/libslic3r/ExtrusionEntity.cpp321
-rw-r--r--src/libslic3r/ExtrusionEntity.hpp321
-rw-r--r--src/libslic3r/ExtrusionEntityCollection.cpp220
-rw-r--r--src/libslic3r/ExtrusionEntityCollection.hpp108
-rw-r--r--src/libslic3r/ExtrusionSimulator.cpp1030
-rw-r--r--src/libslic3r/ExtrusionSimulator.hpp59
-rw-r--r--src/libslic3r/FileParserError.hpp52
-rw-r--r--src/libslic3r/Fill/Fill.cpp271
-rw-r--r--src/libslic3r/Fill/Fill.hpp36
-rw-r--r--src/libslic3r/Fill/Fill3DHoneycomb.cpp204
-rw-r--r--src/libslic3r/Fill/Fill3DHoneycomb.hpp32
-rw-r--r--src/libslic3r/Fill/FillBase.cpp133
-rw-r--r--src/libslic3r/Fill/FillBase.hpp133
-rw-r--r--src/libslic3r/Fill/FillConcentric.cpp64
-rw-r--r--src/libslic3r/Fill/FillConcentric.hpp27
-rw-r--r--src/libslic3r/Fill/FillGyroid.cpp196
-rw-r--r--src/libslic3r/Fill/FillGyroid.hpp30
-rw-r--r--src/libslic3r/Fill/FillHoneycomb.cpp125
-rw-r--r--src/libslic3r/Fill/FillHoneycomb.hpp57
-rw-r--r--src/libslic3r/Fill/FillPlanePath.cpp203
-rw-r--r--src/libslic3r/Fill/FillPlanePath.hpp69
-rw-r--r--src/libslic3r/Fill/FillRectilinear.cpp130
-rw-r--r--src/libslic3r/Fill/FillRectilinear.hpp79
-rw-r--r--src/libslic3r/Fill/FillRectilinear2.cpp1475
-rw-r--r--src/libslic3r/Fill/FillRectilinear2.hpp74
-rw-r--r--src/libslic3r/Fill/FillRectilinear3.cpp1642
-rw-r--r--src/libslic3r/Fill/FillRectilinear3.hpp83
-rw-r--r--src/libslic3r/Flow.cpp148
-rw-r--r--src/libslic3r/Flow.hpp67
-rw-r--r--src/libslic3r/Format/3mf.cpp2022
-rw-r--r--src/libslic3r/Format/3mf.hpp19
-rw-r--r--src/libslic3r/Format/AMF.cpp901
-rw-r--r--src/libslic3r/Format/AMF.hpp19
-rw-r--r--src/libslic3r/Format/OBJ.cpp118
-rw-r--r--src/libslic3r/Format/OBJ.hpp14
-rw-r--r--src/libslic3r/Format/PRUS.cpp399
-rw-r--r--src/libslic3r/Format/PRUS.hpp14
-rw-r--r--src/libslic3r/Format/STL.cpp58
-rw-r--r--src/libslic3r/Format/STL.hpp17
-rw-r--r--src/libslic3r/Format/objparser.cpp540
-rw-r--r--src/libslic3r/Format/objparser.hpp109
-rw-r--r--src/libslic3r/GCode.cpp2730
-rw-r--r--src/libslic3r/GCode.hpp365
-rw-r--r--src/libslic3r/GCode/Analyzer.cpp842
-rw-r--r--src/libslic3r/GCode/Analyzer.hpp233
-rw-r--r--src/libslic3r/GCode/CoolingBuffer.cpp749
-rw-r--r--src/libslic3r/GCode/CoolingBuffer.hpp53
-rw-r--r--src/libslic3r/GCode/PostProcessor.cpp60
-rw-r--r--src/libslic3r/GCode/PostProcessor.hpp15
-rw-r--r--src/libslic3r/GCode/PressureEqualizer.cpp621
-rw-r--r--src/libslic3r/GCode/PressureEqualizer.hpp212
-rw-r--r--src/libslic3r/GCode/PreviewData.cpp456
-rw-r--r--src/libslic3r/GCode/PreviewData.hpp208
-rw-r--r--src/libslic3r/GCode/PrintExtents.cpp186
-rw-r--r--src/libslic3r/GCode/PrintExtents.hpp30
-rw-r--r--src/libslic3r/GCode/SpiralVase.cpp87
-rw-r--r--src/libslic3r/GCode/SpiralVase.hpp28
-rw-r--r--src/libslic3r/GCode/ToolOrdering.cpp631
-rw-r--r--src/libslic3r/GCode/ToolOrdering.hpp162
-rw-r--r--src/libslic3r/GCode/WipeTower.hpp167
-rw-r--r--src/libslic3r/GCode/WipeTowerPrusaMM.cpp1258
-rw-r--r--src/libslic3r/GCode/WipeTowerPrusaMM.hpp374
-rw-r--r--src/libslic3r/GCodeReader.cpp199
-rw-r--r--src/libslic3r/GCodeReader.hpp140
-rw-r--r--src/libslic3r/GCodeSender.cpp580
-rw-r--r--src/libslic3r/GCodeSender.hpp73
-rw-r--r--src/libslic3r/GCodeTimeEstimator.cpp1505
-rw-r--r--src/libslic3r/GCodeTimeEstimator.hpp436
-rw-r--r--src/libslic3r/GCodeWriter.cpp509
-rw-r--r--src/libslic3r/GCodeWriter.hpp92
-rw-r--r--src/libslic3r/Geometry.cpp1169
-rw-r--r--src/libslic3r/Geometry.hpp162
-rw-r--r--src/libslic3r/I18N.hpp18
-rw-r--r--src/libslic3r/Int128.hpp290
-rw-r--r--src/libslic3r/Layer.cpp215
-rw-r--r--src/libslic3r/Layer.hpp179
-rw-r--r--src/libslic3r/LayerRegion.cpp443
-rw-r--r--src/libslic3r/Line.cpp119
-rw-r--r--src/libslic3r/Line.hpp119
-rw-r--r--src/libslic3r/Model.cpp1125
-rw-r--r--src/libslic3r/Model.hpp372
-rw-r--r--src/libslic3r/ModelArrange.hpp825
-rw-r--r--src/libslic3r/MotionPlanner.cpp362
-rw-r--r--src/libslic3r/MotionPlanner.hpp91
-rw-r--r--src/libslic3r/MultiPoint.cpp281
-rw-r--r--src/libslic3r/MultiPoint.hpp110
-rw-r--r--src/libslic3r/MutablePriorityQueue.hpp147
-rw-r--r--src/libslic3r/PerimeterGenerator.cpp480
-rw-r--r--src/libslic3r/PerimeterGenerator.hpp92
-rw-r--r--src/libslic3r/PlaceholderParser.cpp1216
-rw-r--r--src/libslic3r/PlaceholderParser.hpp50
-rw-r--r--src/libslic3r/Point.cpp210
-rw-r--r--src/libslic3r/Point.hpp265
-rw-r--r--src/libslic3r/Polygon.cpp459
-rw-r--r--src/libslic3r/Polygon.hpp268
-rw-r--r--src/libslic3r/Polyline.cpp256
-rw-r--r--src/libslic3r/Polyline.hpp157
-rw-r--r--src/libslic3r/PolylineCollection.cpp92
-rw-r--r--src/libslic3r/PolylineCollection.hpp47
-rw-r--r--src/libslic3r/Print.cpp1370
-rw-r--r--src/libslic3r/Print.hpp535
-rw-r--r--src/libslic3r/PrintConfig.cpp2604
-rw-r--r--src/libslic3r/PrintConfig.hpp973
-rw-r--r--src/libslic3r/PrintExport.hpp383
-rw-r--r--src/libslic3r/PrintObject.cpp2263
-rw-r--r--src/libslic3r/PrintRegion.cpp64
-rw-r--r--src/libslic3r/Rasterizer/Rasterizer.cpp214
-rw-r--r--src/libslic3r/Rasterizer/Rasterizer.hpp86
-rw-r--r--src/libslic3r/SLABasePool.cpp531
-rw-r--r--src/libslic3r/SLABasePool.hpp32
-rw-r--r--src/libslic3r/SVG.cpp374
-rw-r--r--src/libslic3r/SVG.hpp128
-rw-r--r--src/libslic3r/Slicing.cpp670
-rw-r--r--src/libslic3r/Slicing.hpp164
-rw-r--r--src/libslic3r/SlicingAdaptive.cpp140
-rw-r--r--src/libslic3r/SlicingAdaptive.hpp36
-rw-r--r--src/libslic3r/SupportMaterial.cpp3224
-rw-r--r--src/libslic3r/SupportMaterial.hpp256
-rw-r--r--src/libslic3r/Surface.cpp147
-rw-r--r--src/libslic3r/Surface.hpp289
-rw-r--r--src/libslic3r/SurfaceCollection.cpp191
-rw-r--r--src/libslic3r/SurfaceCollection.hpp70
-rw-r--r--src/libslic3r/Technologies.hpp12
-rw-r--r--src/libslic3r/TriangleMesh.cpp1888
-rw-r--r--src/libslic3r/TriangleMesh.hpp199
-rw-r--r--src/libslic3r/Utils.hpp100
-rw-r--r--src/libslic3r/libslic3r.h182
-rw-r--r--src/libslic3r/utils.cpp345
145 files changed, 58536 insertions, 0 deletions
diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp
new file mode 100644
index 000000000..d3cca7ff2
--- /dev/null
+++ b/src/libslic3r/BoundingBox.cpp
@@ -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);
+}
+
+}
diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp
new file mode 100644
index 000000000..b3a1c2f5c
--- /dev/null
+++ b/src/libslic3r/BoundingBox.hpp
@@ -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
diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp
new file mode 100644
index 000000000..ccc3505ce
--- /dev/null
+++ b/src/libslic3r/BridgeDetector.cpp
@@ -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;
+}
+
+}
diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp
new file mode 100644
index 000000000..5c55276be
--- /dev/null
+++ b/src/libslic3r/BridgeDetector.hpp
@@ -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
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
new file mode 100644
index 000000000..6955dd1e7
--- /dev/null
+++ b/src/libslic3r/CMakeLists.txt
@@ -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()
diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp
new file mode 100644
index 000000000..f00e908ce
--- /dev/null
+++ b/src/libslic3r/ClipperUtils.cpp
@@ -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;
+}
+
+} \ No newline at end of file
diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp
new file mode 100644
index 000000000..b065cfe07
--- /dev/null
+++ b/src/libslic3r/ClipperUtils.hpp
@@ -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 \ No newline at end of file
diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp
new file mode 100644
index 000000000..e7442d773
--- /dev/null
+++ b/src/libslic3r/Config.cpp
@@ -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;
+}
+
+}
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
new file mode 100644
index 000000000..e3cd14335
--- /dev/null
+++ b/src/libslic3r/Config.hpp
@@ -0,0 +1,1264 @@
+#ifndef slic3r_Config_hpp_
+#define slic3r_Config_hpp_
+
+#include <assert.h>
+#include <map>
+#include <climits>
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+#include "libslic3r.h"
+#include "Point.hpp"
+
+#include <boost/property_tree/ptree.hpp>
+
+namespace Slic3r {
+
+// Name of the configuration option.
+typedef std::string t_config_option_key;
+typedef std::vector<std::string> t_config_option_keys;
+
+extern std::string escape_string_cstyle(const std::string &str);
+extern std::string escape_strings_cstyle(const std::vector<std::string> &strs);
+extern bool unescape_string_cstyle(const std::string &str, std::string &out);
+extern bool unescape_strings_cstyle(const std::string &str, std::vector<std::string> &out);
+
+
+// Type of a configuration value.
+enum ConfigOptionType {
+ coVectorType = 0x4000,
+ coNone = 0,
+ // single float
+ coFloat = 1,
+ // vector of floats
+ coFloats = coFloat + coVectorType,
+ // single int
+ coInt = 2,
+ // vector of ints
+ coInts = coInt + coVectorType,
+ // single string
+ coString = 3,
+ // vector of strings
+ coStrings = coString + coVectorType,
+ // percent value. Currently only used for infill.
+ coPercent = 4,
+ // percents value. Currently used for retract before wipe only.
+ coPercents = coPercent + coVectorType,
+ // a fraction or an absolute value
+ coFloatOrPercent = 5,
+ // single 2d point (Point2f). Currently not used.
+ coPoint = 6,
+ // vector of 2d points (Point2f). Currently used for the definition of the print bed and for the extruder offsets.
+ coPoints = coPoint + coVectorType,
+ // single boolean value
+ coBool = 7,
+ // vector of boolean values
+ coBools = coBool + coVectorType,
+ // a generic enum
+ coEnum = 8,
+};
+
+// A generic value of a configuration option.
+class ConfigOption {
+public:
+ virtual ~ConfigOption() {}
+
+ virtual ConfigOptionType type() const = 0;
+ virtual std::string serialize() const = 0;
+ virtual bool deserialize(const std::string &str, bool append = false) = 0;
+ virtual ConfigOption* clone() const = 0;
+ // Set a value from a ConfigOption. The two options should be compatible.
+ virtual void set(const ConfigOption *option) = 0;
+ virtual int getInt() const { throw std::runtime_error("Calling ConfigOption::getInt on a non-int ConfigOption"); return 0; }
+ virtual double getFloat() const { throw std::runtime_error("Calling ConfigOption::getFloat on a non-float ConfigOption"); return 0; }
+ virtual bool getBool() const { throw std::runtime_error("Calling ConfigOption::getBool on a non-boolean ConfigOption"); return 0; }
+ virtual void setInt(int /* val */) { throw std::runtime_error("Calling ConfigOption::setInt on a non-int ConfigOption"); }
+ virtual bool operator==(const ConfigOption &rhs) const = 0;
+ bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); }
+ bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; }
+ bool is_vector() const { return ! this->is_scalar(); }
+};
+
+typedef ConfigOption* ConfigOptionPtr;
+typedef const ConfigOption* ConfigOptionConstPtr;
+
+// Value of a single valued option (bool, int, float, string, point, enum)
+template <class T>
+class ConfigOptionSingle : public ConfigOption {
+public:
+ T value;
+ explicit ConfigOptionSingle(T value) : value(value) {}
+ operator T() const { return this->value; }
+
+ void set(const ConfigOption *rhs) override
+ {
+ if (rhs->type() != this->type())
+ throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type");
+ assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
+ this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
+ }
+
+ bool operator==(const ConfigOption &rhs) const override
+ {
+ if (rhs.type() != this->type())
+ throw std::runtime_error("ConfigOptionSingle: Comparing incompatible types");
+ assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
+ return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
+ }
+
+ bool operator==(const T &rhs) const { return this->value == rhs; }
+ bool operator!=(const T &rhs) const { return this->value != rhs; }
+};
+
+// Value of a vector valued option (bools, ints, floats, strings, points)
+class ConfigOptionVectorBase : public ConfigOption {
+public:
+ // Currently used only to initialize the PlaceholderParser.
+ virtual std::vector<std::string> vserialize() const = 0;
+ // Set from a vector of ConfigOptions.
+ // If the rhs ConfigOption is scalar, then its value is used,
+ // otherwise for each of rhs, the first value of a vector is used.
+ // This function is useful to collect values for multiple extrder / filament settings.
+ virtual void set(const std::vector<const ConfigOption*> &rhs) = 0;
+ // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions.
+ // This function is useful to split values from multiple extrder / filament settings into separate configurations.
+ virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0;
+
+ virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0;
+
+ // Get size of this vector.
+ virtual size_t size() const = 0;
+ // Is this vector empty?
+ virtual bool empty() const = 0;
+
+protected:
+ // Used to verify type compatibility when assigning to / from a scalar ConfigOption.
+ ConfigOptionType scalar_type() const { return static_cast<ConfigOptionType>(this->type() - coVectorType); }
+};
+
+// Value of a vector valued option (bools, ints, floats, strings, points), template
+template <class T>
+class ConfigOptionVector : public ConfigOptionVectorBase
+{
+public:
+ ConfigOptionVector() {}
+ explicit ConfigOptionVector(size_t n, const T &value) : values(n, value) {}
+ explicit ConfigOptionVector(std::initializer_list<T> il) : values(std::move(il)) {}
+ explicit ConfigOptionVector(const std::vector<T> &values) : values(values) {}
+ explicit ConfigOptionVector(std::vector<T> &&values) : values(std::move(values)) {}
+ std::vector<T> values;
+
+ void set(const ConfigOption *rhs) override
+ {
+ if (rhs->type() != this->type())
+ throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type");
+ assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
+ this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
+ }
+
+ // Set from a vector of ConfigOptions.
+ // If the rhs ConfigOption is scalar, then its value is used,
+ // otherwise for each of rhs, the first value of a vector is used.
+ // This function is useful to collect values for multiple extrder / filament settings.
+ void set(const std::vector<const ConfigOption*> &rhs) override
+ {
+ this->values.clear();
+ this->values.reserve(rhs.size());
+ for (const ConfigOption *opt : rhs) {
+ if (opt->type() == this->type()) {
+ auto other = static_cast<const ConfigOptionVector<T>*>(opt);
+ if (other->values.empty())
+ throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector");
+ this->values.emplace_back(other->values.front());
+ } else if (opt->type() == this->scalar_type())
+ this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value);
+ else
+ throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type");
+ }
+ }
+
+ // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions.
+ // This function is useful to split values from multiple extrder / filament settings into separate configurations.
+ void set_at(const ConfigOption *rhs, size_t i, size_t j) override
+ {
+ // It is expected that the vector value has at least one value, which is the default, if not overwritten.
+ assert(! this->values.empty());
+ if (this->values.size() <= i) {
+ // Resize this vector, fill in the new vector fields with the copy of the first field.
+ T v = this->values.front();
+ this->values.resize(i + 1, v);
+ }
+ if (rhs->type() == this->type()) {
+ // Assign the first value of the rhs vector.
+ auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
+ if (other->values.empty())
+ throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector");
+ this->values[i] = other->get_at(j);
+ } else if (rhs->type() == this->scalar_type())
+ this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
+ else
+ throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type");
+ }
+
+ T& get_at(size_t i)
+ {
+ assert(! this->values.empty());
+ return (i < this->values.size()) ? this->values[i] : this->values.front();
+ }
+
+ const T& get_at(size_t i) const { return const_cast<ConfigOptionVector<T>*>(this)->get_at(i); }
+
+ // Resize this vector by duplicating the last value.
+ // If the current vector is empty, the default value is used instead.
+ void resize(size_t n, const ConfigOption *opt_default = nullptr) override
+ {
+ assert(opt_default == nullptr || opt_default->is_vector());
+// assert(opt_default == nullptr || dynamic_cast<ConfigOptionVector<T>>(opt_default));
+ assert(! this->values.empty() || opt_default != nullptr);
+ if (n == 0)
+ this->values.clear();
+ else if (n < this->values.size())
+ this->values.erase(this->values.begin() + n, this->values.end());
+ else if (n > this->values.size()) {
+ if (this->values.empty()) {
+ if (opt_default == nullptr)
+ throw std::runtime_error("ConfigOptionVector::resize(): No default value provided.");
+ if (opt_default->type() != this->type())
+ throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type.");
+ this->values.resize(n, static_cast<const ConfigOptionVector<T>*>(opt_default)->values.front());
+ } else {
+ // Resize by duplicating the last value.
+ this->values.resize(n, this->values.back());
+ }
+ }
+ }
+
+ size_t size() const override { return this->values.size(); }
+ bool empty() const override { return this->values.empty(); }
+
+ bool operator==(const ConfigOption &rhs) const override
+ {
+ if (rhs.type() != this->type())
+ throw std::runtime_error("ConfigOptionVector: Comparing incompatible types");
+ assert(dynamic_cast<const ConfigOptionVector<T>*>(&rhs));
+ return this->values == static_cast<const ConfigOptionVector<T>*>(&rhs)->values;
+ }
+
+ bool operator==(const std::vector<T> &rhs) const { return this->values == rhs; }
+ bool operator!=(const std::vector<T> &rhs) const { return this->values != rhs; }
+};
+
+class ConfigOptionFloat : public ConfigOptionSingle<double>
+{
+public:
+ ConfigOptionFloat() : ConfigOptionSingle<double>(0) {}
+ explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {}
+
+ static ConfigOptionType static_type() { return coFloat; }
+ ConfigOptionType type() const override { return static_type(); }
+ double getFloat() const override { return this->value; }
+ ConfigOption* clone() const override { return new ConfigOptionFloat(*this); }
+ bool operator==(const ConfigOptionFloat &rhs) const { return this->value == rhs.value; }
+
+ std::string serialize() const override
+ {
+ std::ostringstream ss;
+ ss << this->value;
+ return ss.str();
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ UNUSED(append);
+ std::istringstream iss(str);
+ iss >> this->value;
+ return !iss.fail();
+ }
+
+ ConfigOptionFloat& operator=(const ConfigOption *opt)
+ {
+ this->set(opt);
+ return *this;
+ }
+};
+
+class ConfigOptionFloats : public ConfigOptionVector<double>
+{
+public:
+ ConfigOptionFloats() : ConfigOptionVector<double>() {}
+ explicit ConfigOptionFloats(size_t n, double value) : ConfigOptionVector<double>(n, value) {}
+ explicit ConfigOptionFloats(std::initializer_list<double> il) : ConfigOptionVector<double>(std::move(il)) {}
+ explicit ConfigOptionFloats(const std::vector<double> &vec) : ConfigOptionVector<double>(vec) {}
+ explicit ConfigOptionFloats(std::vector<double> &&vec) : ConfigOptionVector<double>(std::move(vec)) {}
+
+ static ConfigOptionType static_type() { return coFloats; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionFloats(*this); }
+ bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; }
+
+ std::string serialize() const override
+ {
+ std::ostringstream ss;
+ for (std::vector<double>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
+ if (it - this->values.begin() != 0) ss << ",";
+ ss << *it;
+ }
+ return ss.str();
+ }
+
+ std::vector<std::string> vserialize() const override
+ {
+ std::vector<std::string> vv;
+ vv.reserve(this->values.size());
+ for (std::vector<double>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
+ std::ostringstream ss;
+ ss << *it;
+ vv.push_back(ss.str());
+ }
+ return vv;
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ if (! append)
+ this->values.clear();
+ std::istringstream is(str);
+ std::string item_str;
+ while (std::getline(is, item_str, ',')) {
+ std::istringstream iss(item_str);
+ double value;
+ iss >> value;
+ this->values.push_back(value);
+ }
+ return true;
+ }
+
+ ConfigOptionFloats& operator=(const ConfigOption *opt)
+ {
+ this->set(opt);
+ return *this;
+ }
+};
+
+class ConfigOptionInt : public ConfigOptionSingle<int>
+{
+public:
+ ConfigOptionInt() : ConfigOptionSingle<int>(0) {}
+ explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {}
+ explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {}
+
+ static ConfigOptionType static_type() { return coInt; }
+ ConfigOptionType type() const override { return static_type(); }
+ int getInt() const override { return this->value; }
+ void setInt(int val) { this->value = val; }
+ ConfigOption* clone() const override { return new ConfigOptionInt(*this); }
+ bool operator==(const ConfigOptionInt &rhs) const { return this->value == rhs.value; }
+
+ std::string serialize() const override
+ {
+ std::ostringstream ss;
+ ss << this->value;
+ return ss.str();
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ UNUSED(append);
+ std::istringstream iss(str);
+ iss >> this->value;
+ return !iss.fail();
+ }
+
+ ConfigOptionInt& operator=(const ConfigOption *opt)
+ {
+ this->set(opt);
+ return *this;
+ }
+};
+
+class ConfigOptionInts : public ConfigOptionVector<int>
+{
+public:
+ ConfigOptionInts() : ConfigOptionVector<int>() {}
+ explicit ConfigOptionInts(size_t n, int value) : ConfigOptionVector<int>(n, value) {}
+ explicit ConfigOptionInts(std::initializer_list<int> il) : ConfigOptionVector<int>(std::move(il)) {}
+
+ static ConfigOptionType static_type() { return coInts; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionInts(*this); }
+ ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; }
+
+ std::string serialize() const override {
+ std::ostringstream ss;
+ for (std::vector<int>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
+ if (it - this->values.begin() != 0) ss << ",";
+ ss << *it;
+ }
+ return ss.str();
+ }
+
+ std::vector<std::string> vserialize() const override
+ {
+ std::vector<std::string> vv;
+ vv.reserve(this->values.size());
+ for (std::vector<int>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
+ std::ostringstream ss;
+ ss << *it;
+ vv.push_back(ss.str());
+ }
+ return vv;
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ if (! append)
+ this->values.clear();
+ std::istringstream is(str);
+ std::string item_str;
+ while (std::getline(is, item_str, ',')) {
+ std::istringstream iss(item_str);
+ int value;
+ iss >> value;
+ this->values.push_back(value);
+ }
+ return true;
+ }
+};
+
+class ConfigOptionString : public ConfigOptionSingle<std::string>
+{
+public:
+ ConfigOptionString() : ConfigOptionSingle<std::string>("") {}
+ explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(value) {}
+
+ static ConfigOptionType static_type() { return coString; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionString(*this); }
+ ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionString &rhs) const { return this->value == rhs.value; }
+
+ std::string serialize() const override
+ {
+ return escape_string_cstyle(this->value);
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ UNUSED(append);
+ return unescape_string_cstyle(str, this->value);
+ }
+};
+
+// semicolon-separated strings
+class ConfigOptionStrings : public ConfigOptionVector<std::string>
+{
+public:
+ ConfigOptionStrings() : ConfigOptionVector<std::string>() {}
+ explicit ConfigOptionStrings(size_t n, const std::string &value) : ConfigOptionVector<std::string>(n, value) {}
+ explicit ConfigOptionStrings(const std::vector<std::string> &values) : ConfigOptionVector<std::string>(values) {}
+ explicit ConfigOptionStrings(std::vector<std::string> &&values) : ConfigOptionVector<std::string>(std::move(values)) {}
+ explicit ConfigOptionStrings(std::initializer_list<std::string> il) : ConfigOptionVector<std::string>(std::move(il)) {}
+
+ static ConfigOptionType static_type() { return coStrings; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionStrings(*this); }
+ ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; }
+
+ std::string serialize() const override
+ {
+ return escape_strings_cstyle(this->values);
+ }
+
+ std::vector<std::string> vserialize() const override
+ {
+ return this->values;
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ if (! append)
+ this->values.clear();
+ return unescape_strings_cstyle(str, this->values);
+ }
+};
+
+class ConfigOptionPercent : public ConfigOptionFloat
+{
+public:
+ ConfigOptionPercent() : ConfigOptionFloat(0) {}
+ explicit ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {}
+
+ static ConfigOptionType static_type() { return coPercent; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionPercent(*this); }
+ ConfigOptionPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionPercent &rhs) const { return this->value == rhs.value; }
+ double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100; }
+
+ std::string serialize() const override
+ {
+ std::ostringstream ss;
+ ss << this->value;
+ std::string s(ss.str());
+ s += "%";
+ return s;
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ UNUSED(append);
+ // don't try to parse the trailing % since it's optional
+ std::istringstream iss(str);
+ iss >> this->value;
+ return !iss.fail();
+ }
+};
+
+class ConfigOptionPercents : public ConfigOptionFloats
+{
+public:
+ ConfigOptionPercents() : ConfigOptionFloats() {}
+ explicit ConfigOptionPercents(size_t n, double value) : ConfigOptionFloats(n, value) {}
+ explicit ConfigOptionPercents(std::initializer_list<double> il) : ConfigOptionFloats(std::move(il)) {}
+
+ static ConfigOptionType static_type() { return coPercents; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionPercents(*this); }
+ ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; }
+
+ std::string serialize() const override
+ {
+ std::ostringstream ss;
+ for (const auto &v : this->values) {
+ if (&v != &this->values.front()) ss << ",";
+ ss << v << "%";
+ }
+ std::string str = ss.str();
+ return str;
+ }
+
+ std::vector<std::string> vserialize() const override
+ {
+ std::vector<std::string> vv;
+ vv.reserve(this->values.size());
+ for (const auto v : this->values) {
+ std::ostringstream ss;
+ ss << v;
+ std::string sout = ss.str() + "%";
+ vv.push_back(sout);
+ }
+ return vv;
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ if (! append)
+ this->values.clear();
+ std::istringstream is(str);
+ std::string item_str;
+ while (std::getline(is, item_str, ',')) {
+ std::istringstream iss(item_str);
+ double value;
+ // don't try to parse the trailing % since it's optional
+ iss >> value;
+ this->values.push_back(value);
+ }
+ return true;
+ }
+};
+
+class ConfigOptionFloatOrPercent : public ConfigOptionPercent
+{
+public:
+ bool percent;
+ ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {}
+ explicit ConfigOptionFloatOrPercent(double _value, bool _percent) : ConfigOptionPercent(_value), percent(_percent) {}
+
+ static ConfigOptionType static_type() { return coFloatOrPercent; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionFloatOrPercent(*this); }
+ ConfigOptionFloatOrPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOption &rhs) const override
+ {
+ if (rhs.type() != this->type())
+ throw std::runtime_error("ConfigOptionFloatOrPercent: Comparing incompatible types");
+ assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(&rhs));
+ return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&rhs);
+ }
+ bool operator==(const ConfigOptionFloatOrPercent &rhs) const
+ { return this->value == rhs.value && this->percent == rhs.percent; }
+ double get_abs_value(double ratio_over) const
+ { return this->percent ? (ratio_over * this->value / 100) : this->value; }
+
+ void set(const ConfigOption *rhs) override {
+ if (rhs->type() != this->type())
+ throw std::runtime_error("ConfigOptionFloatOrPercent: Assigning an incompatible type");
+ assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs));
+ *this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs);
+ }
+
+ std::string serialize() const override
+ {
+ std::ostringstream ss;
+ ss << this->value;
+ std::string s(ss.str());
+ if (this->percent) s += "%";
+ return s;
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ UNUSED(append);
+ this->percent = str.find_first_of("%") != std::string::npos;
+ std::istringstream iss(str);
+ iss >> this->value;
+ return !iss.fail();
+ }
+};
+
+class ConfigOptionPoint : public ConfigOptionSingle<Vec2d>
+{
+public:
+ ConfigOptionPoint() : ConfigOptionSingle<Vec2d>(Vec2d(0,0)) {}
+ explicit ConfigOptionPoint(const Vec2d &value) : ConfigOptionSingle<Vec2d>(value) {}
+
+ static ConfigOptionType static_type() { return coPoint; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionPoint(*this); }
+ ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionPoint &rhs) const { return this->value == rhs.value; }
+
+ std::string serialize() const override
+ {
+ std::ostringstream ss;
+ ss << this->value(0);
+ ss << ",";
+ ss << this->value(1);
+ return ss.str();
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ UNUSED(append);
+ char dummy;
+ return sscanf(str.data(), " %lf , %lf %c", &this->value(0), &this->value(1), &dummy) == 2 ||
+ sscanf(str.data(), " %lf x %lf %c", &this->value(0), &this->value(1), &dummy) == 2;
+ }
+};
+
+class ConfigOptionPoints : public ConfigOptionVector<Vec2d>
+{
+public:
+ ConfigOptionPoints() : ConfigOptionVector<Vec2d>() {}
+ explicit ConfigOptionPoints(size_t n, const Vec2d &value) : ConfigOptionVector<Vec2d>(n, value) {}
+ explicit ConfigOptionPoints(std::initializer_list<Vec2d> il) : ConfigOptionVector<Vec2d>(std::move(il)) {}
+ explicit ConfigOptionPoints(const std::vector<Vec2d> &values) : ConfigOptionVector<Vec2d>(values) {}
+
+ static ConfigOptionType static_type() { return coPoints; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionPoints(*this); }
+ ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; }
+
+ std::string serialize() const override
+ {
+ std::ostringstream ss;
+ for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
+ if (it - this->values.begin() != 0) ss << ",";
+ ss << (*it)(0);
+ ss << "x";
+ ss << (*it)(1);
+ }
+ return ss.str();
+ }
+
+ std::vector<std::string> vserialize() const override
+ {
+ std::vector<std::string> vv;
+ for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
+ std::ostringstream ss;
+ ss << *it;
+ vv.push_back(ss.str());
+ }
+ return vv;
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ if (! append)
+ this->values.clear();
+ std::istringstream is(str);
+ std::string point_str;
+ while (std::getline(is, point_str, ',')) {
+ Vec2d point(Vec2d::Zero());
+ std::istringstream iss(point_str);
+ std::string coord_str;
+ if (std::getline(iss, coord_str, 'x')) {
+ std::istringstream(coord_str) >> point(0);
+ if (std::getline(iss, coord_str, 'x')) {
+ std::istringstream(coord_str) >> point(1);
+ }
+ }
+ this->values.push_back(point);
+ }
+ return true;
+ }
+};
+
+class ConfigOptionBool : public ConfigOptionSingle<bool>
+{
+public:
+ ConfigOptionBool() : ConfigOptionSingle<bool>(false) {}
+ explicit ConfigOptionBool(bool _value) : ConfigOptionSingle<bool>(_value) {}
+
+ static ConfigOptionType static_type() { return coBool; }
+ ConfigOptionType type() const override { return static_type(); }
+ bool getBool() const override { return this->value; }
+ ConfigOption* clone() const override { return new ConfigOptionBool(*this); }
+ ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionBool &rhs) const { return this->value == rhs.value; }
+
+ std::string serialize() const override
+ {
+ return std::string(this->value ? "1" : "0");
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ UNUSED(append);
+ this->value = (str.compare("1") == 0);
+ return true;
+ }
+};
+
+class ConfigOptionBools : public ConfigOptionVector<unsigned char>
+{
+public:
+ ConfigOptionBools() : ConfigOptionVector<unsigned char>() {}
+ explicit ConfigOptionBools(size_t n, bool value) : ConfigOptionVector<unsigned char>(n, (unsigned char)value) {}
+ explicit ConfigOptionBools(std::initializer_list<bool> il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); }
+
+ static ConfigOptionType static_type() { return coBools; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionBools(*this); }
+ ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; }
+
+ bool& get_at(size_t i) {
+ assert(! this->values.empty());
+ return *reinterpret_cast<bool*>(&((i < this->values.size()) ? this->values[i] : this->values.front()));
+ }
+
+ //FIXME this smells, the parent class has the method declared returning (unsigned char&).
+ bool get_at(size_t i) const { return bool((i < this->values.size()) ? this->values[i] : this->values.front()); }
+
+ std::string serialize() const override
+ {
+ std::ostringstream ss;
+ for (std::vector<unsigned char>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
+ if (it - this->values.begin() != 0) ss << ",";
+ ss << (*it ? "1" : "0");
+ }
+ return ss.str();
+ }
+
+ std::vector<std::string> vserialize() const override
+ {
+ std::vector<std::string> vv;
+ for (std::vector<unsigned char>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
+ std::ostringstream ss;
+ ss << (*it ? "1" : "0");
+ vv.push_back(ss.str());
+ }
+ return vv;
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ if (! append)
+ this->values.clear();
+ std::istringstream is(str);
+ std::string item_str;
+ while (std::getline(is, item_str, ',')) {
+ this->values.push_back(item_str.compare("1") == 0);
+ }
+ return true;
+ }
+};
+
+// Map from an enum integer value to an enum name.
+typedef std::vector<std::string> t_config_enum_names;
+// Map from an enum name to an enum integer value.
+typedef std::map<std::string,int> t_config_enum_values;
+
+template <class T>
+class ConfigOptionEnum : public ConfigOptionSingle<T>
+{
+public:
+ // by default, use the first value (0) of the T enum type
+ ConfigOptionEnum() : ConfigOptionSingle<T>(static_cast<T>(0)) {}
+ explicit ConfigOptionEnum(T _value) : ConfigOptionSingle<T>(_value) {}
+
+ static ConfigOptionType static_type() { return coEnum; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionEnum<T>(*this); }
+ ConfigOptionEnum<T>& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionEnum<T> &rhs) const { return this->value == rhs.value; }
+
+ std::string serialize() const override
+ {
+ const t_config_enum_names& names = ConfigOptionEnum<T>::get_enum_names();
+ assert(static_cast<int>(this->value) < int(names.size()));
+ return names[static_cast<int>(this->value)];
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ UNUSED(append);
+ return from_string(str, this->value);
+ }
+
+ static bool has(T value)
+ {
+ for (const std::pair<std::string, int> &kvp : ConfigOptionEnum<T>::get_enum_values())
+ if (kvp.second == value)
+ return true;
+ return false;
+ }
+
+ // Map from an enum name to an enum integer value.
+ static const t_config_enum_names& get_enum_names()
+ {
+ static t_config_enum_names names;
+ if (names.empty()) {
+ // Initialize the map.
+ const t_config_enum_values &enum_keys_map = ConfigOptionEnum<T>::get_enum_values();
+ int cnt = 0;
+ for (const std::pair<std::string, int> &kvp : enum_keys_map)
+ cnt = std::max(cnt, kvp.second);
+ cnt += 1;
+ names.assign(cnt, "");
+ for (const std::pair<std::string, int> &kvp : enum_keys_map)
+ names[kvp.second] = kvp.first;
+ }
+ return names;
+ }
+ // Map from an enum name to an enum integer value.
+ static const t_config_enum_values& get_enum_values();
+
+ static bool from_string(const std::string &str, T &value)
+ {
+ const t_config_enum_values &enum_keys_map = ConfigOptionEnum<T>::get_enum_values();
+ auto it = enum_keys_map.find(str);
+ if (it == enum_keys_map.end())
+ return false;
+ value = static_cast<T>(it->second);
+ return true;
+ }
+};
+
+// Generic enum configuration value.
+// We use this one in DynamicConfig objects when creating a config value object for ConfigOptionType == coEnum.
+// In the StaticConfig, it is better to use the specialized ConfigOptionEnum<T> containers.
+class ConfigOptionEnumGeneric : public ConfigOptionInt
+{
+public:
+ ConfigOptionEnumGeneric(const t_config_enum_values* keys_map = nullptr) : keys_map(keys_map) {}
+
+ const t_config_enum_values* keys_map;
+
+ static ConfigOptionType static_type() { return coEnum; }
+ ConfigOptionType type() const override { return static_type(); }
+ ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); }
+ ConfigOptionEnumGeneric& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+ bool operator==(const ConfigOptionEnumGeneric &rhs) const { return this->value == rhs.value; }
+
+ std::string serialize() const override
+ {
+ for (const auto &kvp : *this->keys_map)
+ if (kvp.second == this->value)
+ return kvp.first;
+ return std::string();
+ }
+
+ bool deserialize(const std::string &str, bool append = false) override
+ {
+ UNUSED(append);
+ auto it = this->keys_map->find(str);
+ if (it == this->keys_map->end())
+ return false;
+ this->value = it->second;
+ return true;
+ }
+};
+
+// Definition of a configuration value for the purpose of GUI presentation, editing, value mapping and config file handling.
+class ConfigOptionDef
+{
+public:
+ // What type? bool, int, string etc.
+ ConfigOptionType type = coNone;
+ // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor.
+ const ConfigOption *default_value = nullptr;
+
+ // Usually empty.
+ // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection,
+ // "select_open" - to open a selection dialog (currently only a serial port selection).
+ std::string gui_type;
+ // Usually empty. Otherwise "serialized" or "show_value"
+ // The flags may be combined.
+ // "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon.
+ // "show_value" - even if enum_values / enum_labels are set, still display the value, not the enum label.
+ std::string gui_flags;
+ // Label of the GUI input field.
+ // In case the GUI input fields are grouped in some views, the label defines a short label of a grouped value,
+ // while full_label contains a label of a stand-alone field.
+ // The full label is shown, when adding an override parameter for an object or a modified object.
+ std::string label;
+ std::string full_label;
+ // Category of a configuration field, from the GUI perspective.
+ // One of: "Layers and Perimeters", "Infill", "Support material", "Speed", "Extruders", "Advanced", "Extrusion Width"
+ std::string category;
+ // A tooltip text shown in the GUI.
+ std::string tooltip;
+ // Text right from the input field, usually a unit of measurement.
+ std::string sidetext;
+ // Format of this parameter on a command line.
+ std::string cli;
+ // Set for type == coFloatOrPercent.
+ // It provides a link to a configuration value, of which this option provides a ratio.
+ // For example,
+ // For example external_perimeter_speed may be defined as a fraction of perimeter_speed.
+ t_config_option_key ratio_over;
+ // True for multiline strings.
+ bool multiline = false;
+ // For text input: If true, the GUI text box spans the complete page width.
+ bool full_width = false;
+ // Not editable. Currently only used for the display of the number of threads.
+ bool readonly = false;
+ // Height of a multiline GUI text box.
+ int height = -1;
+ // Optional width of an input field.
+ int width = -1;
+ // <min, max> limit of a numeric input.
+ // If not set, the <min, max> is set to <INT_MIN, INT_MAX>
+ // By setting min=0, only nonnegative input is allowed.
+ int min = INT_MIN;
+ int max = INT_MAX;
+ // Legacy names for this configuration option.
+ // Used when parsing legacy configuration file.
+ std::vector<t_config_option_key> aliases;
+ // Sometimes a single value may well define multiple values in a "beginner" mode.
+ // Currently used for aliasing "solid_layers" to "top_solid_layers", "bottom_solid_layers".
+ std::vector<t_config_option_key> shortcut;
+ // Definition of values / labels for a combo box.
+ // Mostly used for enums (when type == coEnum), but may be used for ints resp. floats, if gui_type is set to "i_enum_open" resp. "f_enum_open".
+ std::vector<std::string> enum_values;
+ std::vector<std::string> enum_labels;
+ // For enums (when type == coEnum). Maps enum_values to enums.
+ // Initialized by ConfigOptionEnum<xxx>::get_enum_values()
+ const t_config_enum_values *enum_keys_map = nullptr;
+
+ bool has_enum_value(const std::string &value) const {
+ for (const std::string &v : enum_values)
+ if (v == value)
+ return true;
+ return false;
+ }
+};
+
+// Map from a config option name to its definition.
+// The definition does not carry an actual value of the config option, only its constant default value.
+// t_config_option_key is std::string
+typedef std::map<t_config_option_key,ConfigOptionDef> t_optiondef_map;
+
+// Definition of configuration values for the purpose of GUI presentation, editing, value mapping and config file handling.
+// The configuration definition is static: It does not carry the actual configuration values,
+// but it carries the defaults of the configuration values.
+class ConfigDef
+{
+public:
+ t_optiondef_map options;
+ ~ConfigDef() { for (auto &opt : this->options) delete opt.second.default_value; }
+ ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) {
+ ConfigOptionDef* opt = &this->options[opt_key];
+ opt->type = type;
+ return opt;
+ }
+ bool has(const t_config_option_key &opt_key) const { return this->options.count(opt_key) > 0; }
+ const ConfigOptionDef* get(const t_config_option_key &opt_key) const {
+ t_optiondef_map::iterator it = const_cast<ConfigDef*>(this)->options.find(opt_key);
+ return (it == this->options.end()) ? nullptr : &it->second;
+ }
+};
+
+// An abstract configuration store.
+class ConfigBase
+{
+public:
+ // Definition of configuration values for the purpose of GUI presentation, editing, value mapping and config file handling.
+ // The configuration definition is static: It does not carry the actual configuration values,
+ // but it carries the defaults of the configuration values.
+
+ ConfigBase() {}
+ virtual ~ConfigBase() {}
+
+ // Virtual overridables:
+public:
+ // Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
+ virtual const ConfigDef* def() const = 0;
+ // Find ando/or create a ConfigOption instance for a given name.
+ virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0;
+ // Collect names of all configuration values maintained by this configuration store.
+ virtual t_config_option_keys keys() const = 0;
+protected:
+ // Verify whether the opt_key has not been obsoleted or renamed.
+ // 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().
+ // handle_legacy() is called internally by set_deserialize().
+ virtual void handle_legacy(t_config_option_key &opt_key, std::string &value) const {}
+
+public:
+ // Non-virtual methods:
+ bool has(const t_config_option_key &opt_key) const { return this->option(opt_key) != nullptr; }
+ const ConfigOption* option(const t_config_option_key &opt_key) const
+ { return const_cast<ConfigBase*>(this)->option(opt_key, false); }
+ ConfigOption* option(const t_config_option_key &opt_key, bool create = false)
+ { return this->optptr(opt_key, create); }
+ template<typename TYPE>
+ TYPE* option(const t_config_option_key &opt_key, bool create = false)
+ {
+ ConfigOption *opt = this->optptr(opt_key, create);
+// assert(opt == nullptr || opt->type() == TYPE::static_type());
+ return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast<TYPE*>(opt);
+ }
+ template<typename TYPE>
+ const TYPE* option(const t_config_option_key &opt_key) const
+ { return const_cast<ConfigBase*>(this)->option<TYPE>(opt_key, false); }
+ // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase.
+ // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(),
+ // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
+ void apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->apply_only(other, other.keys(), ignore_nonexistent); }
+ // Apply explicitely enumerated keys of other ConfigBase defined by this->def() to this ConfigBase.
+ // An UnknownOptionException is thrown in case some option keys are not defined by this->def(),
+ // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set.
+ void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false);
+ bool equals(const ConfigBase &other) const { return this->diff(other).empty(); }
+ t_config_option_keys diff(const ConfigBase &other) const;
+ // Use deep_diff to correct return of changed options,
+ // considering individual options for each extruder
+ t_config_option_keys deep_diff(const ConfigBase &other) const;
+ t_config_option_keys equal(const ConfigBase &other) const;
+ std::string serialize(const t_config_option_key &opt_key) const;
+ // Set a configuration value from a string, it will call an overridable handle_legacy()
+ // to resolve renamed and removed configuration keys.
+ bool set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false);
+
+ double get_abs_value(const t_config_option_key &opt_key) const;
+ double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const;
+ void setenv_();
+ void load(const std::string &file);
+ void load_from_ini(const std::string &file);
+ void load_from_gcode_file(const std::string &file);
+ void load_from_gcode_string(const char* str);
+ void load(const boost::property_tree::ptree &tree);
+ void save(const std::string &file) const;
+
+private:
+ // Set a configuration value from a string.
+ bool set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &str, bool append);
+};
+
+// Configuration store with dynamic number of configuration values.
+// In Slic3r, the dynamic config is mostly used at the user interface layer.
+class DynamicConfig : public virtual ConfigBase
+{
+public:
+ DynamicConfig() {}
+ DynamicConfig(const DynamicConfig& other) { *this = other; }
+ DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); }
+ virtual ~DynamicConfig() { clear(); }
+
+ // Copy a content of one DynamicConfig to another DynamicConfig.
+ // If rhs.def() is not null, then it has to be equal to this->def().
+ DynamicConfig& operator=(const DynamicConfig &rhs)
+ {
+ assert(this->def() == nullptr || this->def() == rhs.def());
+ this->clear();
+ for (const auto &kvp : rhs.options)
+ this->options[kvp.first] = kvp.second->clone();
+ return *this;
+ }
+
+ // Move a content of one DynamicConfig to another DynamicConfig.
+ // If rhs.def() is not null, then it has to be equal to this->def().
+ DynamicConfig& operator=(DynamicConfig &&rhs)
+ {
+ assert(this->def() == nullptr || this->def() == rhs.def());
+ this->clear();
+ this->options = std::move(rhs.options);
+ rhs.options.clear();
+ return *this;
+ }
+
+ // Add a content of one DynamicConfig to another DynamicConfig.
+ // If rhs.def() is not null, then it has to be equal to this->def().
+ DynamicConfig& operator+=(const DynamicConfig &rhs)
+ {
+ assert(this->def() == nullptr || this->def() == rhs.def());
+ for (const auto &kvp : rhs.options) {
+ auto it = this->options.find(kvp.first);
+ if (it == this->options.end())
+ this->options[kvp.first] = kvp.second->clone();
+ else {
+ assert(it->second->type() == kvp.second->type());
+ if (it->second->type() == kvp.second->type())
+ *it->second = *kvp.second;
+ else {
+ delete it->second;
+ it->second = kvp.second->clone();
+ }
+ }
+ }
+ return *this;
+ }
+
+ // Move a content of one DynamicConfig to another DynamicConfig.
+ // If rhs.def() is not null, then it has to be equal to this->def().
+ DynamicConfig& operator+=(DynamicConfig &&rhs)
+ {
+ assert(this->def() == nullptr || this->def() == rhs.def());
+ for (const auto &kvp : rhs.options) {
+ auto it = this->options.find(kvp.first);
+ if (it == this->options.end()) {
+ this->options[kvp.first] = kvp.second;
+ } else {
+ assert(it->second->type() == kvp.second->type());
+ delete it->second;
+ it->second = kvp.second;
+ }
+ }
+ rhs.options.clear();
+ return *this;
+ }
+
+ bool operator==(const DynamicConfig &rhs) const;
+ bool operator!=(const DynamicConfig &rhs) const { return ! (*this == rhs); }
+
+ void swap(DynamicConfig &other)
+ {
+ std::swap(this->options, other.options);
+ }
+
+ void clear()
+ {
+ for (auto &opt : this->options)
+ delete opt.second;
+ this->options.clear();
+ }
+
+ bool erase(const t_config_option_key &opt_key)
+ {
+ auto it = this->options.find(opt_key);
+ if (it == this->options.end())
+ return false;
+ delete it->second;
+ this->options.erase(it);
+ return true;
+ }
+
+ // Allow DynamicConfig to be instantiated on ints own without a definition.
+ // If the definition is not defined, the method requiring the definition will throw NoDefinitionException.
+ const ConfigDef* def() const override { return nullptr; };
+ template<class T> T* opt(const t_config_option_key &opt_key, bool create = false)
+ { return dynamic_cast<T*>(this->option(opt_key, create)); }
+ template<class T> const T* opt(const t_config_option_key &opt_key) const
+ { return dynamic_cast<const T*>(this->option(opt_key)); }
+ // Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name.
+ ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override;
+ // Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store.
+ t_config_option_keys keys() const override;
+
+ // Set a value for an opt_key. Returns true if the value did not exist yet.
+ // This DynamicConfig will take ownership of opt.
+ // Be careful, as this method does not test the existence of opt_key in this->def().
+ bool set_key_value(const std::string &opt_key, ConfigOption *opt)
+ {
+ auto it = this->options.find(opt_key);
+ if (it == this->options.end()) {
+ this->options[opt_key] = opt;
+ return true;
+ } else {
+ delete it->second;
+ it->second = opt;
+ return false;
+ }
+ }
+
+ std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option<ConfigOptionString>(opt_key, create)->value; }
+ const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); }
+ std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); }
+ const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key, idx); }
+
+ double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; }
+ const double opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; }
+ double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); }
+ const double opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); }
+
+ int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; }
+ const int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; }
+ int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); }
+ const int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); }
+
+ bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; }
+ bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
+
+private:
+ typedef std::map<t_config_option_key,ConfigOption*> t_options_map;
+ t_options_map options;
+};
+
+/// Configuration store with a static definition of configuration values.
+/// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
+/// because the configuration values could be accessed directly.
+class StaticConfig : public virtual ConfigBase
+{
+public:
+ StaticConfig() {}
+ /// Gets list of config option names for each config option of this->def, which has a static counter-part defined by the derived object
+ /// and which could be resolved by this->optptr(key) call.
+ t_config_option_keys keys() const;
+
+protected:
+ /// Set all statically defined config options to their defaults defined by this->def().
+ void set_defaults();
+};
+
+/// Specialization of std::exception to indicate that an unknown config option has been encountered.
+class UnknownOptionException : public std::runtime_error {
+public:
+ UnknownOptionException() :
+ std::runtime_error("Unknown option exception") {}
+ UnknownOptionException(const std::string &opt_key) :
+ std::runtime_error(std::string("Unknown option exception: ") + opt_key) {}
+};
+
+/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
+class NoDefinitionException : public std::runtime_error
+{
+public:
+ NoDefinitionException() :
+ std::runtime_error("No definition exception") {}
+ NoDefinitionException(const std::string &opt_key) :
+ std::runtime_error(std::string("No definition exception: ") + opt_key) {}
+};
+
+}
+
+#endif
diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp
new file mode 100644
index 000000000..2b2893c80
--- /dev/null
+++ b/src/libslic3r/EdgeGrid.cpp
@@ -0,0 +1,1453 @@
+#include <algorithm>
+#include <vector>
+#include <float.h>
+#include <unordered_map>
+
+#if 0
+// #ifdef SLIC3R_GUI
+#include <wx/image.h>
+#endif /* SLIC3R_GUI */
+
+#include "libslic3r.h"
+#include "EdgeGrid.hpp"
+
+#if 0
+// Enable debugging and assert in this file.
+#define DEBUG
+#define _DEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+
+namespace Slic3r {
+
+EdgeGrid::Grid::Grid() :
+ m_rows(0), m_cols(0)
+{
+}
+
+EdgeGrid::Grid::~Grid()
+{
+ m_contours.clear();
+ m_cell_data.clear();
+ m_cells.clear();
+}
+
+void EdgeGrid::Grid::create(const Polygons &polygons, coord_t resolution)
+{
+ // Count the contours.
+ size_t ncontours = 0;
+ for (size_t j = 0; j < polygons.size(); ++ j)
+ if (! polygons[j].points.empty())
+ ++ ncontours;
+
+ // Collect the contours.
+ m_contours.assign(ncontours, NULL);
+ ncontours = 0;
+ for (size_t j = 0; j < polygons.size(); ++ j)
+ if (! polygons[j].points.empty())
+ m_contours[ncontours++] = &polygons[j].points;
+
+ create_from_m_contours(resolution);
+}
+
+void EdgeGrid::Grid::create(const ExPolygon &expoly, coord_t resolution)
+{
+ // Count the contours.
+ size_t ncontours = 0;
+ if (! expoly.contour.points.empty())
+ ++ ncontours;
+ for (size_t j = 0; j < expoly.holes.size(); ++ j)
+ if (! expoly.holes[j].points.empty())
+ ++ ncontours;
+
+ // Collect the contours.
+ m_contours.assign(ncontours, NULL);
+ ncontours = 0;
+ if (! expoly.contour.points.empty())
+ m_contours[ncontours++] = &expoly.contour.points;
+ for (size_t j = 0; j < expoly.holes.size(); ++ j)
+ if (! expoly.holes[j].points.empty())
+ m_contours[ncontours++] = &expoly.holes[j].points;
+
+ create_from_m_contours(resolution);
+}
+
+void EdgeGrid::Grid::create(const ExPolygons &expolygons, coord_t resolution)
+{
+ // Count the contours.
+ size_t ncontours = 0;
+ for (size_t i = 0; i < expolygons.size(); ++ i) {
+ const ExPolygon &expoly = expolygons[i];
+ if (! expoly.contour.points.empty())
+ ++ ncontours;
+ for (size_t j = 0; j < expoly.holes.size(); ++ j)
+ if (! expoly.holes[j].points.empty())
+ ++ ncontours;
+ }
+
+ // Collect the contours.
+ m_contours.assign(ncontours, NULL);
+ ncontours = 0;
+ for (size_t i = 0; i < expolygons.size(); ++ i) {
+ const ExPolygon &expoly = expolygons[i];
+ if (! expoly.contour.points.empty())
+ m_contours[ncontours++] = &expoly.contour.points;
+ for (size_t j = 0; j < expoly.holes.size(); ++ j)
+ if (! expoly.holes[j].points.empty())
+ m_contours[ncontours++] = &expoly.holes[j].points;
+ }
+
+ create_from_m_contours(resolution);
+}
+
+void EdgeGrid::Grid::create(const ExPolygonCollection &expolygons, coord_t resolution)
+{
+ create(expolygons.expolygons, resolution);
+}
+
+// m_contours has been initialized. Now fill in the edge grid.
+void EdgeGrid::Grid::create_from_m_contours(coord_t resolution)
+{
+ // 1) Measure the bounding box.
+ for (size_t i = 0; i < m_contours.size(); ++ i) {
+ const Slic3r::Points &pts = *m_contours[i];
+ for (size_t j = 0; j < pts.size(); ++ j)
+ m_bbox.merge(pts[j]);
+ }
+ coord_t eps = 16;
+ m_bbox.min(0) -= eps;
+ m_bbox.min(1) -= eps;
+ m_bbox.max(0) += eps;
+ m_bbox.max(1) += eps;
+
+ // 2) Initialize the edge grid.
+ m_resolution = resolution;
+ m_cols = (m_bbox.max(0) - m_bbox.min(0) + m_resolution - 1) / m_resolution;
+ m_rows = (m_bbox.max(1) - m_bbox.min(1) + m_resolution - 1) / m_resolution;
+ m_cells.assign(m_rows * m_cols, Cell());
+
+ // 3) First round of contour rasterization, count the edges per grid cell.
+ for (size_t i = 0; i < m_contours.size(); ++ i) {
+ const Slic3r::Points &pts = *m_contours[i];
+ for (size_t j = 0; j < pts.size(); ++ j) {
+ // End points of the line segment.
+ Slic3r::Point p1(pts[j]);
+ Slic3r::Point p2 = pts[(j + 1 == pts.size()) ? 0 : j + 1];
+ p1(0) -= m_bbox.min(0);
+ p1(1) -= m_bbox.min(1);
+ p2(0) -= m_bbox.min(0);
+ p2(1) -= m_bbox.min(1);
+ // Get the cells of the end points.
+ coord_t ix = p1(0) / m_resolution;
+ coord_t iy = p1(1) / m_resolution;
+ coord_t ixb = p2(0) / m_resolution;
+ coord_t iyb = p2(1) / m_resolution;
+ assert(ix >= 0 && ix < m_cols);
+ assert(iy >= 0 && iy < m_rows);
+ assert(ixb >= 0 && ixb < m_cols);
+ assert(iyb >= 0 && iyb < m_rows);
+ // Account for the end points.
+ ++ m_cells[iy*m_cols+ix].end;
+ if (ix == ixb && iy == iyb)
+ // Both ends fall into the same cell.
+ continue;
+ // Raster the centeral part of the line.
+ coord_t dx = std::abs(p2(0) - p1(0));
+ coord_t dy = std::abs(p2(1) - p1(1));
+ if (p1(0) < p2(0)) {
+ int64_t ex = int64_t((ix + 1)*m_resolution - p1(0)) * int64_t(dy);
+ if (p1(1) < p2(1)) {
+ // x positive, y positive
+ int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx);
+ do {
+ assert(ix <= ixb && iy <= iyb);
+ if (ex < ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix += 1;
+ }
+ else if (ex == ey) {
+ ex = int64_t(dy) * m_resolution;
+ ey = int64_t(dx) * m_resolution;
+ ix += 1;
+ iy += 1;
+ }
+ else {
+ assert(ex > ey);
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy += 1;
+ }
+ ++m_cells[iy*m_cols + ix].end;
+ } while (ix != ixb || iy != iyb);
+ }
+ else {
+ // x positive, y non positive
+ int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx);
+ do {
+ assert(ix <= ixb && iy >= iyb);
+ if (ex <= ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix += 1;
+ }
+ else {
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy -= 1;
+ }
+ ++m_cells[iy*m_cols + ix].end;
+ } while (ix != ixb || iy != iyb);
+ }
+ }
+ else {
+ int64_t ex = int64_t(p1(0) - ix*m_resolution) * int64_t(dy);
+ if (p1(1) < p2(1)) {
+ // x non positive, y positive
+ int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx);
+ do {
+ assert(ix >= ixb && iy <= iyb);
+ if (ex < ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix -= 1;
+ }
+ else {
+ assert(ex >= ey);
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy += 1;
+ }
+ ++m_cells[iy*m_cols + ix].end;
+ } while (ix != ixb || iy != iyb);
+ }
+ else {
+ // x non positive, y non positive
+ int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx);
+ do {
+ assert(ix >= ixb && iy >= iyb);
+ if (ex < ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix -= 1;
+ }
+ else if (ex == ey) {
+ // The lower edge of a grid cell belongs to the cell.
+ // Handle the case where the ray may cross the lower left corner of a cell in a general case,
+ // or a left or lower edge in a degenerate case (horizontal or vertical line).
+ if (dx > 0) {
+ ex = int64_t(dy) * m_resolution;
+ ix -= 1;
+ }
+ if (dy > 0) {
+ ey = int64_t(dx) * m_resolution;
+ iy -= 1;
+ }
+ }
+ else {
+ assert(ex > ey);
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy -= 1;
+ }
+ ++m_cells[iy*m_cols + ix].end;
+ } while (ix != ixb || iy != iyb);
+ }
+ }
+ }
+ }
+
+ // 4) Prefix sum the numbers of hits per cells to get an index into m_cell_data.
+ size_t cnt = m_cells.front().end;
+ for (size_t i = 1; i < m_cells.size(); ++ i) {
+ m_cells[i].begin = cnt;
+ cnt += m_cells[i].end;
+ m_cells[i].end = cnt;
+ }
+
+ // 5) Allocate the cell data.
+ m_cell_data.assign(cnt, std::pair<size_t, size_t>(size_t(-1), size_t(-1)));
+
+ // 6) Finally fill in m_cell_data by rasterizing the lines once again.
+ for (size_t i = 0; i < m_cells.size(); ++i)
+ m_cells[i].end = m_cells[i].begin;
+ for (size_t i = 0; i < m_contours.size(); ++i) {
+ const Slic3r::Points &pts = *m_contours[i];
+ for (size_t j = 0; j < pts.size(); ++j) {
+ // End points of the line segment.
+ Slic3r::Point p1(pts[j]);
+ Slic3r::Point p2 = pts[(j + 1 == pts.size()) ? 0 : j + 1];
+ p1(0) -= m_bbox.min(0);
+ p1(1) -= m_bbox.min(1);
+ p2(0) -= m_bbox.min(0);
+ p2(1) -= m_bbox.min(1);
+ // Get the cells of the end points.
+ coord_t ix = p1(0) / m_resolution;
+ coord_t iy = p1(1) / m_resolution;
+ coord_t ixb = p2(0) / m_resolution;
+ coord_t iyb = p2(1) / m_resolution;
+ assert(ix >= 0 && ix < m_cols);
+ assert(iy >= 0 && iy < m_rows);
+ assert(ixb >= 0 && ixb < m_cols);
+ assert(iyb >= 0 && iyb < m_rows);
+ // Account for the end points.
+ m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair<size_t, size_t>(i, j);
+ if (ix == ixb && iy == iyb)
+ // Both ends fall into the same cell.
+ continue;
+ // Raster the centeral part of the line.
+ coord_t dx = std::abs(p2(0) - p1(0));
+ coord_t dy = std::abs(p2(1) - p1(1));
+ if (p1(0) < p2(0)) {
+ int64_t ex = int64_t((ix + 1)*m_resolution - p1(0)) * int64_t(dy);
+ if (p1(1) < p2(1)) {
+ // x positive, y positive
+ int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx);
+ do {
+ assert(ix <= ixb && iy <= iyb);
+ if (ex < ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix += 1;
+ }
+ else if (ex == ey) {
+ ex = int64_t(dy) * m_resolution;
+ ey = int64_t(dx) * m_resolution;
+ ix += 1;
+ iy += 1;
+ }
+ else {
+ assert(ex > ey);
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy += 1;
+ }
+ m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair<size_t, size_t>(i, j);
+ } while (ix != ixb || iy != iyb);
+ }
+ else {
+ // x positive, y non positive
+ int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx);
+ do {
+ assert(ix <= ixb && iy >= iyb);
+ if (ex <= ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix += 1;
+ }
+ else {
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy -= 1;
+ }
+ m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair<size_t, size_t>(i, j);
+ } while (ix != ixb || iy != iyb);
+ }
+ }
+ else {
+ int64_t ex = int64_t(p1(0) - ix*m_resolution) * int64_t(dy);
+ if (p1(1) < p2(1)) {
+ // x non positive, y positive
+ int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx);
+ do {
+ assert(ix >= ixb && iy <= iyb);
+ if (ex < ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix -= 1;
+ }
+ else {
+ assert(ex >= ey);
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy += 1;
+ }
+ m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair<size_t, size_t>(i, j);
+ } while (ix != ixb || iy != iyb);
+ }
+ else {
+ // x non positive, y non positive
+ int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx);
+ do {
+ assert(ix >= ixb && iy >= iyb);
+ if (ex < ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix -= 1;
+ }
+ else if (ex == ey) {
+ // The lower edge of a grid cell belongs to the cell.
+ // Handle the case where the ray may cross the lower left corner of a cell in a general case,
+ // or a left or lower edge in a degenerate case (horizontal or vertical line).
+ if (dx > 0) {
+ ex = int64_t(dy) * m_resolution;
+ ix -= 1;
+ }
+ if (dy > 0) {
+ ey = int64_t(dx) * m_resolution;
+ iy -= 1;
+ }
+ }
+ else {
+ assert(ex > ey);
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy -= 1;
+ }
+ m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair<size_t, size_t>(i, j);
+ } while (ix != ixb || iy != iyb);
+ }
+ }
+ }
+ }
+}
+
+#if 0
+// Divide, round to a grid coordinate.
+// Divide x/y, round down. y is expected to be positive.
+static inline coord_t div_floor(coord_t x, coord_t y)
+{
+ assert(y > 0);
+ return ((x < 0) ? (x - y + 1) : x) / y;
+}
+
+// Walk the polyline, test whether any lines of this polyline does not intersect
+// any line stored into the grid.
+bool EdgeGrid::Grid::intersect(const MultiPoint &polyline, bool closed)
+{
+ size_t n = polyline.points.size();
+ if (closed)
+ ++ n;
+ for (size_t i = 0; i < n; ++ i) {
+ size_t j = i + 1;
+ if (j == polyline.points.size())
+ j = 0;
+ Point p1src = polyline.points[i];
+ Point p2src = polyline.points[j];
+ Point p1 = p1src;
+ Point p2 = p2src;
+ // Discretize the line segment p1, p2.
+ p1(0) -= m_bbox.min(0);
+ p1(1) -= m_bbox.min(1);
+ p2(0) -= m_bbox.min(0);
+ p2(1) -= m_bbox.min(1);
+ // Get the cells of the end points.
+ coord_t ix = div_floor(p1(0), m_resolution);
+ coord_t iy = div_floor(p1(1), m_resolution);
+ coord_t ixb = div_floor(p2(0), m_resolution);
+ coord_t iyb = div_floor(p2(1), m_resolution);
+// assert(ix >= 0 && ix < m_cols);
+// assert(iy >= 0 && iy < m_rows);
+// assert(ixb >= 0 && ixb < m_cols);
+// assert(iyb >= 0 && iyb < m_rows);
+ // Account for the end points.
+ if (line_cell_intersect(p1src, p2src, m_cells[iy*m_cols + ix]))
+ return true;
+ if (ix == ixb && iy == iyb)
+ // Both ends fall into the same cell.
+ continue;
+ // Raster the centeral part of the line.
+ coord_t dx = std::abs(p2(0) - p1(0));
+ coord_t dy = std::abs(p2(1) - p1(1));
+ if (p1(0) < p2(0)) {
+ int64_t ex = int64_t((ix + 1)*m_resolution - p1(0)) * int64_t(dy);
+ if (p1(1) < p2(1)) {
+ int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx);
+ do {
+ assert(ix <= ixb && iy <= iyb);
+ if (ex < ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix += 1;
+ }
+ else if (ex == ey) {
+ ex = int64_t(dy) * m_resolution;
+ ey = int64_t(dx) * m_resolution;
+ ix += 1;
+ iy += 1;
+ }
+ else {
+ assert(ex > ey);
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy += 1;
+ }
+ if (line_cell_intersect(p1src, p2src, m_cells[iy*m_cols + ix]))
+ return true;
+ } while (ix != ixb || iy != iyb);
+ }
+ else {
+ int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx);
+ do {
+ assert(ix <= ixb && iy >= iyb);
+ if (ex <= ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix += 1;
+ }
+ else {
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy -= 1;
+ }
+ if (line_cell_intersect(p1src, p2src, m_cells[iy*m_cols + ix]))
+ return true;
+ } while (ix != ixb || iy != iyb);
+ }
+ }
+ else {
+ int64_t ex = int64_t(p1(0) - ix*m_resolution) * int64_t(dy);
+ if (p1(1) < p2(1)) {
+ int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx);
+ do {
+ assert(ix >= ixb && iy <= iyb);
+ if (ex < ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix -= 1;
+ }
+ else {
+ assert(ex >= ey);
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy += 1;
+ }
+ if (line_cell_intersect(p1src, p2src, m_cells[iy*m_cols + ix]))
+ return true;
+ } while (ix != ixb || iy != iyb);
+ }
+ else {
+ int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx);
+ do {
+ assert(ix >= ixb && iy >= iyb);
+ if (ex < ey) {
+ ey -= ex;
+ ex = int64_t(dy) * m_resolution;
+ ix -= 1;
+ }
+ else if (ex == ey) {
+ if (dx > 0) {
+ ex = int64_t(dy) * m_resolution;
+ ix -= 1;
+ }
+ if (dy > 0) {
+ ey = int64_t(dx) * m_resolution;
+ iy -= 1;
+ }
+ }
+ else {
+ assert(ex > ey);
+ ex -= ey;
+ ey = int64_t(dx) * m_resolution;
+ iy -= 1;
+ }
+ if (line_cell_intersect(p1src, p2src, m_cells[iy*m_cols + ix]))
+ return true;
+ } while (ix != ixb || iy != iyb);
+ }
+ }
+ }
+ return false;
+}
+
+bool EdgeGrid::Grid::line_cell_intersect(const Point &p1a, const Point &p2a, const Cell &cell)
+{
+ BoundingBox bbox(p1a, p1a);
+ bbox.merge(p2a);
+ int64_t va_x = p2a(0) - p1a(0);
+ int64_t va_y = p2a(1) - p1a(1);
+ for (size_t i = cell.begin; i != cell.end; ++ i) {
+ const std::pair<size_t, size_t> &cell_data = m_cell_data[i];
+ // Contour indexed by the ith line of this cell.
+ const Slic3r::Points &contour = *m_contours[cell_data.first];
+ // Point indices in contour indexed by the ith line of this cell.
+ size_t idx1 = cell_data.second;
+ size_t idx2 = idx1 + 1;
+ if (idx2 == contour.size())
+ idx2 = 0;
+ // The points of the ith line of this cell and its bounding box.
+ const Point &p1b = contour[idx1];
+ const Point &p2b = contour[idx2];
+ BoundingBox bbox2(p1b, p1b);
+ bbox2.merge(p2b);
+ // Do the bounding boxes intersect?
+ if (! bbox.overlap(bbox2))
+ continue;
+ // Now intersect the two line segments using exact arithmetics.
+ int64_t w1_x = p1b(0) - p1a(0);
+ int64_t w1_y = p1b(1) - p1a(1);
+ int64_t w2_x = p2b(0) - p1a(0);
+ int64_t w2_y = p2b(1) - p1a(1);
+ int64_t side1 = va_x * w1_y - va_y * w1_x;
+ int64_t side2 = va_x * w2_y - va_y * w2_x;
+ if (side1 == side2 && side1 != 0)
+ // The line segments don't intersect.
+ continue;
+ w1_x = p1a(0) - p1b(0);
+ w1_y = p1a(1) - p1b(1);
+ w2_x = p2a(0) - p1b(0);
+ w2_y = p2a(1) - p1b(1);
+ int64_t vb_x = p2b(0) - p1b(0);
+ int64_t vb_y = p2b(1) - p1b(1);
+ side1 = vb_x * w1_y - vb_y * w1_x;
+ side2 = vb_x * w2_y - vb_y * w2_x;
+ if (side1 == side2 && side1 != 0)
+ // The line segments don't intersect.
+ continue;
+ // The line segments intersect.
+ return true;
+ }
+ // The line segment (p1a, p2a) does not intersect any of the line segments inside this cell.
+ return false;
+}
+
+// Test, whether a point is inside a contour.
+bool EdgeGrid::Grid::inside(const Point &pt_src)
+{
+ Point p = pt_src;
+ p(0) -= m_bbox.min(0);
+ p(1) -= m_bbox.min(1);
+ // Get the cell of the point.
+ if (p(0) < 0 || p(1) < 0)
+ return false;
+ coord_t ix = p(0) / m_resolution;
+ coord_t iy = p(1) / m_resolution;
+ if (ix >= this->m_cols || iy >= this->m_rows)
+ return false;
+
+ size_t i_closest = (size_t)-1;
+ bool inside = false;
+
+ {
+ // Hit in the first cell?
+ const Cell &cell = m_cells[iy * m_cols + ix];
+ for (size_t i = cell.begin; i != cell.end; ++ i) {
+ const std::pair<size_t, size_t> &cell_data = m_cell_data[i];
+ // Contour indexed by the ith line of this cell.
+ const Slic3r::Points &contour = *m_contours[cell_data.first];
+ // Point indices in contour indexed by the ith line of this cell.
+ size_t idx1 = cell_data.second;
+ size_t idx2 = idx1 + 1;
+ if (idx2 == contour.size())
+ idx2 = 0;
+ const Point &p1 = contour[idx1];
+ const Point &p2 = contour[idx2];
+ if (p1(1) < p2(1)) {
+ if (p(1) < p1(1) || p(1) > p2(1))
+ continue;
+ //FIXME finish this!
+ int64_t vx = 0;// pt_src
+ //FIXME finish this!
+ int64_t det = 0;
+ } else if (p1(1) != p2(1)) {
+ assert(p1(1) > p2(1));
+ if (p(1) < p2(1) || p(1) > p1(1))
+ continue;
+ } else {
+ assert(p1(1) == p2(1));
+ if (p1(1) == p(1)) {
+ if (p(0) >= p1(0) && p(0) <= p2(0))
+ // On the segment.
+ return true;
+ // Before or after the segment.
+ size_t idx0 = idx1 - 1;
+ size_t idx2 = idx1 + 1;
+ if (idx0 == (size_t)-1)
+ idx0 = contour.size() - 1;
+ if (idx2 == contour.size())
+ idx2 = 0;
+ }
+ }
+ }
+ }
+
+ //FIXME This code follows only a single direction. Better to follow the direction closest to the bounding box.
+}
+#endif
+
+template<const int INCX, const int INCY>
+struct PropagateDanielssonSingleStep {
+ PropagateDanielssonSingleStep(float *aL, unsigned char *asigns, size_t astride, coord_t aresolution) :
+ L(aL), signs(asigns), stride(astride), resolution(aresolution) {}
+ inline void operator()(int r, int c, int addr_delta) {
+ size_t addr = r * stride + c;
+ if ((signs[addr] & 2) == 0) {
+ float *v = &L[addr << 1];
+ float l = v[0] * v[0] + v[1] * v[1];
+ float *v2s = v + (addr_delta << 1);
+ float v2[2] = {
+ v2s[0] + INCX * resolution,
+ v2s[1] + INCY * resolution
+ };
+ float l2 = v2[0] * v2[0] + v2[1] * v2[1];
+ if (l2 < l) {
+ v[0] = v2[0];
+ v[1] = v2[1];
+ }
+ }
+ }
+ float *L;
+ unsigned char *signs;
+ size_t stride;
+ coord_t resolution;
+};
+
+struct PropagateDanielssonSingleVStep3 {
+ PropagateDanielssonSingleVStep3(float *aL, unsigned char *asigns, size_t astride, coord_t aresolution) :
+ L(aL), signs(asigns), stride(astride), resolution(aresolution) {}
+ inline void operator()(int r, int c, int addr_delta, bool has_l, bool has_r) {
+ size_t addr = r * stride + c;
+ if ((signs[addr] & 2) == 0) {
+ float *v = &L[addr<<1];
+ float l = v[0]*v[0]+v[1]*v[1];
+ float *v2s = v+(addr_delta<<1);
+ float v2[2] = {
+ v2s[0],
+ v2s[1] + resolution
+ };
+ float l2 = v2[0]*v2[0]+v2[1]*v2[1];
+ if (l2 < l) {
+ v[0] = v2[0];
+ v[1] = v2[1];
+ }
+ if (has_l) {
+ float *v2sl = v2s - 1;
+ v2[0] = v2sl[0] + resolution;
+ v2[1] = v2sl[1] + resolution;
+ l2 = v2[0]*v2[0]+v2[1]*v2[1];
+ if (l2 < l) {
+ v[0] = v2[0];
+ v[1] = v2[1];
+ }
+ }
+ if (has_r) {
+ float *v2sr = v2s + 1;
+ v2[0] = v2sr[0] + resolution;
+ v2[1] = v2sr[1] + resolution;
+ l2 = v2[0]*v2[0]+v2[1]*v2[1];
+ if (l2 < l) {
+ v[0] = v2[0];
+ v[1] = v2[1];
+ }
+ }
+ }
+ }
+ float *L;
+ unsigned char *signs;
+ size_t stride;
+ coord_t resolution;
+};
+
+void EdgeGrid::Grid::calculate_sdf()
+{
+ // 1) Initialize a signum and an unsigned vector to a zero iso surface.
+ size_t nrows = m_rows + 1;
+ size_t ncols = m_cols + 1;
+ // Unsigned vectors towards the closest point on the surface.
+ std::vector<float> L(nrows * ncols * 2, FLT_MAX);
+ // Bit 0 set - negative.
+ // Bit 1 set - original value, the distance value shall not be changed by the Danielsson propagation.
+ // Bit 2 set - signum not propagated yet.
+ std::vector<unsigned char> signs(nrows * ncols, 4);
+ // SDF will be initially filled with unsigned DF.
+// m_signed_distance_field.assign(nrows * ncols, FLT_MAX);
+ float search_radius = float(m_resolution<<1);
+ m_signed_distance_field.assign(nrows * ncols, search_radius);
+ // For each cell:
+ for (size_t r = 0; r < m_rows; ++ r) {
+ for (size_t c = 0; c < m_cols; ++ c) {
+ const Cell &cell = m_cells[r * m_cols + c];
+ // For each segment in the cell:
+ for (size_t i = cell.begin; i != cell.end; ++ i) {
+ const Slic3r::Points &pts = *m_contours[m_cell_data[i].first];
+ size_t ipt = m_cell_data[i].second;
+ // End points of the line segment.
+ const Slic3r::Point &p1 = pts[ipt];
+ const Slic3r::Point &p2 = pts[(ipt + 1 == pts.size()) ? 0 : ipt + 1];
+ // Segment vector
+ const Slic3r::Point v_seg = p2 - p1;
+ // l2 of v_seg
+ const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1));
+ // For each corner of this cell and its 1 ring neighbours:
+ for (int corner_y = -1; corner_y < 3; ++ corner_y) {
+ coord_t corner_r = r + corner_y;
+ if (corner_r < 0 || corner_r >= nrows)
+ continue;
+ for (int corner_x = -1; corner_x < 3; ++ corner_x) {
+ coord_t corner_c = c + corner_x;
+ if (corner_c < 0 || corner_c >= ncols)
+ continue;
+ float &d_min = m_signed_distance_field[corner_r * ncols + corner_c];
+ Slic3r::Point pt(m_bbox.min(0) + corner_c * m_resolution, m_bbox.min(1) + corner_r * m_resolution);
+ Slic3r::Point v_pt = pt - p1;
+ // dot(p2-p1, pt-p1)
+ int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1));
+ if (t_pt < 0) {
+ // Closest to p1.
+ double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1)));
+ if (dabs < d_min) {
+ // Previous point.
+ const Slic3r::Point &p0 = pts[(ipt == 0) ? (pts.size() - 1) : ipt - 1];
+ Slic3r::Point v_seg_prev = p1 - p0;
+ int64_t t2_pt = int64_t(v_seg_prev(0)) * int64_t(v_pt(0)) + int64_t(v_seg_prev(1)) * int64_t(v_pt(1));
+ if (t2_pt > 0) {
+ // Inside the wedge between the previous and the next segment.
+ // Set the signum depending on whether the vertex is convex or reflex.
+ int64_t det = int64_t(v_seg_prev(0)) * int64_t(v_seg(1)) - int64_t(v_seg_prev(1)) * int64_t(v_seg(0));
+ assert(det != 0);
+ d_min = dabs;
+ // Fill in an unsigned vector towards the zero iso surface.
+ float *l = &L[(corner_r * ncols + corner_c) << 1];
+ l[0] = std::abs(v_pt(0));
+ l[1] = std::abs(v_pt(1));
+ #ifdef _DEBUG
+ double dabs2 = sqrt(l[0]*l[0]+l[1]*l[1]);
+ assert(std::abs(dabs-dabs2) < 1e-4 * std::max(dabs, dabs2));
+ #endif /* _DEBUG */
+ signs[corner_r * ncols + corner_c] = ((det < 0) ? 1 : 0) | 2;
+ }
+ }
+ }
+ else if (t_pt > l2_seg) {
+ // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the same cell.
+ continue;
+ } else {
+ // Closest to the segment.
+ assert(t_pt >= 0 && t_pt <= l2_seg);
+ int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1));
+ double d = double(d_seg) / sqrt(double(l2_seg));
+ double dabs = std::abs(d);
+ if (dabs < d_min) {
+ d_min = dabs;
+ // Fill in an unsigned vector towards the zero iso surface.
+ float *l = &L[(corner_r * ncols + corner_c) << 1];
+ float linv = float(d_seg) / float(l2_seg);
+ l[0] = std::abs(float(v_seg(1)) * linv);
+ l[1] = std::abs(float(v_seg(0)) * linv);
+ #ifdef _DEBUG
+ double dabs2 = sqrt(l[0]*l[0]+l[1]*l[1]);
+ assert(std::abs(dabs-dabs2) <= 1e-4 * std::max(dabs, dabs2));
+ #endif /* _DEBUG */
+ signs[corner_r * ncols + corner_c] = ((d_seg < 0) ? 1 : 0) | 2;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+#if 0
+ static int iRun = 0;
+ ++ iRun;
+//#ifdef SLIC3R_GUI
+ {
+ wxImage img(ncols, nrows);
+ unsigned char *data = img.GetData();
+ memset(data, 0, ncols * nrows * 3);
+ for (coord_t r = 0; r < nrows; ++r) {
+ for (coord_t c = 0; c < ncols; ++c) {
+ unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3;
+ float d = m_signed_distance_field[r * ncols + c];
+ if (d != search_radius) {
+ float s = 255 * d / search_radius;
+ int is = std::max(0, std::min(255, int(floor(s + 0.5f))));
+ pxl[0] = 255;
+ pxl[1] = 255 - is;
+ pxl[2] = 255 - is;
+ }
+ else {
+ pxl[0] = 0;
+ pxl[1] = 255;
+ pxl[2] = 0;
+ }
+ }
+ }
+ img.SaveFile(debug_out_path("unsigned_df-%d.png", iRun), wxBITMAP_TYPE_PNG);
+ }
+ {
+ wxImage img(ncols, nrows);
+ unsigned char *data = img.GetData();
+ memset(data, 0, ncols * nrows * 3);
+ for (coord_t r = 0; r < nrows; ++r) {
+ for (coord_t c = 0; c < ncols; ++c) {
+ unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3;
+ float d = m_signed_distance_field[r * ncols + c];
+ if (d != search_radius) {
+ float s = 255 * d / search_radius;
+ int is = std::max(0, std::min(255, int(floor(s + 0.5f))));
+ if ((signs[r * ncols + c] & 1) == 0) {
+ // Positive
+ pxl[0] = 255;
+ pxl[1] = 255 - is;
+ pxl[2] = 255 - is;
+ }
+ else {
+ // Negative
+ pxl[0] = 255 - is;
+ pxl[1] = 255 - is;
+ pxl[2] = 255;
+ }
+ }
+ else {
+ pxl[0] = 0;
+ pxl[1] = 255;
+ pxl[2] = 0;
+ }
+ }
+ }
+ img.SaveFile(debug_out_path("signed_df-%d.png", iRun), wxBITMAP_TYPE_PNG);
+ }
+#endif /* SLIC3R_GUI */
+
+ // 2) Propagate the signum.
+ #define PROPAGATE_SIGNUM_SINGLE_STEP(DELTA) do { \
+ size_t addr = r * ncols + c; \
+ unsigned char &cur_val = signs[addr]; \
+ if (cur_val & 4) { \
+ unsigned char old_val = signs[addr + (DELTA)]; \
+ if ((old_val & 4) == 0) \
+ cur_val = old_val & 1; \
+ } \
+ } while (0);
+ // Top to bottom propagation.
+ for (size_t r = 0; r < nrows; ++ r) {
+ if (r > 0)
+ for (size_t c = 0; c < ncols; ++ c)
+ PROPAGATE_SIGNUM_SINGLE_STEP(- int(ncols));
+ for (size_t c = 1; c < ncols; ++ c)
+ PROPAGATE_SIGNUM_SINGLE_STEP(- 1);
+ for (int c = int(ncols) - 2; c >= 0; -- c)
+ PROPAGATE_SIGNUM_SINGLE_STEP(+ 1);
+ }
+ // Bottom to top propagation.
+ for (int r = int(nrows) - 2; r >= 0; -- r) {
+ for (size_t c = 0; c < ncols; ++ c)
+ PROPAGATE_SIGNUM_SINGLE_STEP(+ ncols);
+ for (size_t c = 1; c < ncols; ++ c)
+ PROPAGATE_SIGNUM_SINGLE_STEP(- 1);
+ for (int c = int(ncols) - 2; c >= 0; -- c)
+ PROPAGATE_SIGNUM_SINGLE_STEP(+ 1);
+ }
+ #undef PROPAGATE_SIGNUM_SINGLE_STEP
+
+ // 3) Propagate the distance by the Danielsson chamfer metric.
+ // Top to bottom propagation.
+ PropagateDanielssonSingleStep<1, 0> danielsson_hstep(L.data(), signs.data(), ncols, m_resolution);
+ PropagateDanielssonSingleStep<0, 1> danielsson_vstep(L.data(), signs.data(), ncols, m_resolution);
+ PropagateDanielssonSingleVStep3 danielsson_vstep3(L.data(), signs.data(), ncols, m_resolution);
+ // Top to bottom propagation.
+ for (size_t r = 0; r < nrows; ++ r) {
+ if (r > 0)
+ for (size_t c = 0; c < ncols; ++ c)
+ danielsson_vstep(r, c, -int(ncols));
+// PROPAGATE_DANIELSSON_SINGLE_VSTEP3(-int(ncols), c != 0, c + 1 != ncols);
+ for (size_t c = 1; c < ncols; ++ c)
+ danielsson_hstep(r, c, -1);
+ for (int c = int(ncols) - 2; c >= 0; -- c)
+ danielsson_hstep(r, c, +1);
+ }
+ // Bottom to top propagation.
+ for (int r = int(nrows) - 2; r >= 0; -- r) {
+ for (size_t c = 0; c < ncols; ++ c)
+ danielsson_vstep(r, c, +ncols);
+// PROPAGATE_DANIELSSON_SINGLE_VSTEP3(+int(ncols), c != 0, c + 1 != ncols);
+ for (size_t c = 1; c < ncols; ++ c)
+ danielsson_hstep(r, c, -1);
+ for (int c = int(ncols) - 2; c >= 0; -- c)
+ danielsson_hstep(r, c, +1);
+ }
+
+ // Update signed distance field from absolte vectors to the iso-surface.
+ for (size_t r = 0; r < nrows; ++ r) {
+ for (size_t c = 0; c < ncols; ++ c) {
+ size_t addr = r * ncols + c;
+ float *v = &L[addr<<1];
+ float d = sqrt(v[0]*v[0]+v[1]*v[1]);
+ if (signs[addr] & 1)
+ d = -d;
+ m_signed_distance_field[addr] = d;
+ }
+ }
+
+#if 0
+//#ifdef SLIC3R_GUI
+ {
+ wxImage img(ncols, nrows);
+ unsigned char *data = img.GetData();
+ memset(data, 0, ncols * nrows * 3);
+ float search_radius = float(m_resolution * 5);
+ for (coord_t r = 0; r < nrows; ++r) {
+ for (coord_t c = 0; c < ncols; ++c) {
+ unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3;
+ unsigned char sign = signs[r * ncols + c];
+ switch (sign) {
+ case 0:
+ // Positive, outside of a narrow band.
+ pxl[0] = 0;
+ pxl[1] = 0;
+ pxl[2] = 255;
+ break;
+ case 1:
+ // Negative, outside of a narrow band.
+ pxl[0] = 255;
+ pxl[1] = 0;
+ pxl[2] = 0;
+ break;
+ case 2:
+ // Positive, outside of a narrow band.
+ pxl[0] = 100;
+ pxl[1] = 100;
+ pxl[2] = 255;
+ break;
+ case 3:
+ // Negative, outside of a narrow band.
+ pxl[0] = 255;
+ pxl[1] = 100;
+ pxl[2] = 100;
+ break;
+ case 4:
+ // This shall not happen. Undefined signum.
+ pxl[0] = 0;
+ pxl[1] = 255;
+ pxl[2] = 0;
+ break;
+ default:
+ // This shall not happen. Invalid signum value.
+ pxl[0] = 255;
+ pxl[1] = 255;
+ pxl[2] = 255;
+ break;
+ }
+ }
+ }
+ img.SaveFile(debug_out_path("signed_df-signs-%d.png", iRun), wxBITMAP_TYPE_PNG);
+ }
+#endif /* SLIC3R_GUI */
+
+#if 0
+//#ifdef SLIC3R_GUI
+ {
+ wxImage img(ncols, nrows);
+ unsigned char *data = img.GetData();
+ memset(data, 0, ncols * nrows * 3);
+ float search_radius = float(m_resolution * 5);
+ for (coord_t r = 0; r < nrows; ++r) {
+ for (coord_t c = 0; c < ncols; ++c) {
+ unsigned char *pxl = data + (((nrows - r - 1) * ncols) + c) * 3;
+ float d = m_signed_distance_field[r * ncols + c];
+ float s = 255.f * fabs(d) / search_radius;
+ int is = std::max(0, std::min(255, int(floor(s + 0.5f))));
+ if (d < 0.f) {
+ pxl[0] = 255;
+ pxl[1] = 255 - is;
+ pxl[2] = 255 - is;
+ }
+ else {
+ pxl[0] = 255 - is;
+ pxl[1] = 255 - is;
+ pxl[2] = 255;
+ }
+ }
+ }
+ img.SaveFile(debug_out_path("signed_df2-%d.png", iRun), wxBITMAP_TYPE_PNG);
+ }
+#endif /* SLIC3R_GUI */
+}
+
+float EdgeGrid::Grid::signed_distance_bilinear(const Point &pt) const
+{
+ coord_t x = pt(0) - m_bbox.min(0);
+ coord_t y = pt(1) - m_bbox.min(1);
+ coord_t w = m_resolution * m_cols;
+ coord_t h = m_resolution * m_rows;
+ bool clamped = false;
+ coord_t xcl = x;
+ coord_t ycl = y;
+ if (x < 0) {
+ xcl = 0;
+ clamped = true;
+ } else if (x >= w) {
+ xcl = w - 1;
+ clamped = true;
+ }
+ if (y < 0) {
+ ycl = 0;
+ clamped = true;
+ } else if (y >= h) {
+ ycl = h - 1;
+ clamped = true;
+ }
+
+ coord_t cell_c = coord_t(floor(xcl / m_resolution));
+ coord_t cell_r = coord_t(floor(ycl / m_resolution));
+ float tx = float(xcl - cell_c * m_resolution) / float(m_resolution);
+ assert(tx >= -1e-5 && tx < 1.f + 1e-5);
+ float ty = float(ycl - cell_r * m_resolution) / float(m_resolution);
+ assert(ty >= -1e-5 && ty < 1.f + 1e-5);
+ size_t addr = cell_r * (m_cols + 1) + cell_c;
+ float f00 = m_signed_distance_field[addr];
+ float f01 = m_signed_distance_field[addr+1];
+ addr += m_cols + 1;
+ float f10 = m_signed_distance_field[addr];
+ float f11 = m_signed_distance_field[addr+1];
+ float f0 = (1.f - tx) * f00 + tx * f01;
+ float f1 = (1.f - tx) * f10 + tx * f11;
+ float f = (1.f - ty) * f0 + ty * f1;
+
+ if (clamped) {
+ if (f > 0) {
+ if (x < 0)
+ f += -x;
+ else if (x >= w)
+ f += x - w + 1;
+ if (y < 0)
+ f += -y;
+ else if (y >= h)
+ f += y - h + 1;
+ } else {
+ if (x < 0)
+ f -= -x;
+ else if (x >= w)
+ f -= x - w + 1;
+ if (y < 0)
+ f -= -y;
+ else if (y >= h)
+ f -= y - h + 1;
+ }
+ }
+
+ return f;
+}
+
+bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment) const {
+ BoundingBox bbox;
+ bbox.min = bbox.max = Point(pt(0) - m_bbox.min(0), pt(1) - m_bbox.min(1));
+ bbox.defined = true;
+ // Upper boundary, round to grid and test validity.
+ bbox.max(0) += search_radius;
+ bbox.max(1) += search_radius;
+ if (bbox.max(0) < 0 || bbox.max(1) < 0)
+ return false;
+ bbox.max(0) /= m_resolution;
+ bbox.max(1) /= m_resolution;
+ if (bbox.max(0) >= m_cols)
+ bbox.max(0) = m_cols - 1;
+ if (bbox.max(1) >= m_rows)
+ bbox.max(1) = m_rows - 1;
+ // Lower boundary, round to grid and test validity.
+ bbox.min(0) -= search_radius;
+ bbox.min(1) -= search_radius;
+ if (bbox.min(0) < 0)
+ bbox.min(0) = 0;
+ if (bbox.min(1) < 0)
+ bbox.min(1) = 0;
+ bbox.min(0) /= m_resolution;
+ bbox.min(1) /= m_resolution;
+ // Is the interval empty?
+ if (bbox.min(0) > bbox.max(0) ||
+ bbox.min(1) > bbox.max(1))
+ return false;
+ // Traverse all cells in the bounding box.
+ float d_min = search_radius;
+ // Signum of the distance field at pt.
+ int sign_min = 0;
+ bool on_segment = false;
+ for (int r = bbox.min(1); r <= bbox.max(1); ++ r) {
+ for (int c = bbox.min(0); c <= bbox.max(0); ++ c) {
+ const Cell &cell = m_cells[r * m_cols + c];
+ for (size_t i = cell.begin; i < cell.end; ++ i) {
+ const Slic3r::Points &pts = *m_contours[m_cell_data[i].first];
+ size_t ipt = m_cell_data[i].second;
+ // End points of the line segment.
+ const Slic3r::Point &p1 = pts[ipt];
+ const Slic3r::Point &p2 = pts[(ipt + 1 == pts.size()) ? 0 : ipt + 1];
+ Slic3r::Point v_seg = p2 - p1;
+ Slic3r::Point v_pt = pt - p1;
+ // dot(p2-p1, pt-p1)
+ int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1));
+ // l2 of seg
+ int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1));
+ if (t_pt < 0) {
+ // Closest to p1.
+ double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1)));
+ if (dabs < d_min) {
+ // Previous point.
+ const Slic3r::Point &p0 = pts[(ipt == 0) ? (pts.size() - 1) : ipt - 1];
+ Slic3r::Point v_seg_prev = p1 - p0;
+ int64_t t2_pt = int64_t(v_seg_prev(0)) * int64_t(v_pt(0)) + int64_t(v_seg_prev(1)) * int64_t(v_pt(1));
+ if (t2_pt > 0) {
+ // Inside the wedge between the previous and the next segment.
+ d_min = dabs;
+ // Set the signum depending on whether the vertex is convex or reflex.
+ int64_t det = int64_t(v_seg_prev(0)) * int64_t(v_seg(1)) - int64_t(v_seg_prev(1)) * int64_t(v_seg(0));
+ assert(det != 0);
+ sign_min = (det > 0) ? 1 : -1;
+ on_segment = false;
+ }
+ }
+ }
+ else if (t_pt > l2_seg) {
+ // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the same cell.
+ continue;
+ } else {
+ // Closest to the segment.
+ assert(t_pt >= 0 && t_pt <= l2_seg);
+ int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1));
+ double d = double(d_seg) / sqrt(double(l2_seg));
+ double dabs = std::abs(d);
+ if (dabs < d_min) {
+ d_min = dabs;
+ sign_min = (d_seg < 0) ? -1 : ((d_seg == 0) ? 0 : 1);
+ on_segment = true;
+ }
+ }
+ }
+ }
+ }
+ if (d_min >= search_radius)
+ return false;
+ result_min_dist = d_min * sign_min;
+ if (pon_segment != NULL)
+ *pon_segment = on_segment;
+ return true;
+}
+
+bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coordf_t &result_min_dist) const
+{
+ if (signed_distance_edges(pt, search_radius, result_min_dist))
+ return true;
+ if (m_signed_distance_field.empty())
+ return false;
+ result_min_dist = signed_distance_bilinear(pt);
+ return true;
+}
+
+Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) const
+{
+ assert(std::abs(2 * offset) < m_resolution);
+
+ typedef std::unordered_multimap<Point, int, PointHash> EndPointMapType;
+ // 0) Prepare a binary grid.
+ size_t cell_rows = m_rows + 2;
+ size_t cell_cols = m_cols + 2;
+ std::vector<char> cell_inside(cell_rows * cell_cols, false);
+ for (int r = 0; r < int(cell_rows); ++ r)
+ for (int c = 0; c < int(cell_cols); ++ c)
+ cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1);
+ // Fill in empty cells, which have a left / right neighbor filled.
+ // Fill in empty cells, which have the top / bottom neighbor filled.
+ if (fill_holes) {
+ std::vector<char> cell_inside2(cell_inside);
+ for (int r = 1; r + 1 < int(cell_rows); ++ r) {
+ for (int c = 1; c + 1 < int(cell_cols); ++ c) {
+ int addr = r * cell_cols + c;
+ if ((cell_inside2[addr - 1] && cell_inside2[addr + 1]) ||
+ (cell_inside2[addr - cell_cols] && cell_inside2[addr + cell_cols]))
+ cell_inside[addr] = true;
+ }
+ }
+ }
+
+ // 1) Collect the lines.
+ std::vector<Line> lines;
+ EndPointMapType start_point_to_line_idx;
+ for (int r = 0; r <= int(m_rows); ++ r) {
+ for (int c = 0; c <= int(m_cols); ++ c) {
+ int addr = (r + 1) * cell_cols + c + 1;
+ bool left = cell_inside[addr - 1];
+ bool top = cell_inside[addr - cell_cols];
+ bool current = cell_inside[addr];
+ if (left != current) {
+ lines.push_back(
+ left ?
+ Line(Point(c, r+1), Point(c, r )) :
+ Line(Point(c, r ), Point(c, r+1)));
+ start_point_to_line_idx.insert(std::pair<Point, int>(lines.back().a, int(lines.size()) - 1));
+ }
+ if (top != current) {
+ lines.push_back(
+ top ?
+ Line(Point(c , r), Point(c+1, r)) :
+ Line(Point(c+1, r), Point(c , r)));
+ start_point_to_line_idx.insert(std::pair<Point, int>(lines.back().a, int(lines.size()) - 1));
+ }
+ }
+ }
+
+ // 2) Chain the lines.
+ std::vector<char> line_processed(lines.size(), false);
+ Polygons out;
+ for (int i_candidate = 0; i_candidate < int(lines.size()); ++ i_candidate) {
+ if (line_processed[i_candidate])
+ continue;
+ Polygon poly;
+ line_processed[i_candidate] = true;
+ poly.points.push_back(lines[i_candidate].b);
+ int i_line_current = i_candidate;
+ for (;;) {
+ std::pair<EndPointMapType::iterator,EndPointMapType::iterator> line_range =
+ start_point_to_line_idx.equal_range(lines[i_line_current].b);
+ // The interval has to be non empty, there shall be at least one line continuing the current one.
+ assert(line_range.first != line_range.second);
+ int i_next = -1;
+ for (EndPointMapType::iterator it = line_range.first; it != line_range.second; ++ it) {
+ if (it->second == i_candidate) {
+ // closing the loop.
+ goto end_of_poly;
+ }
+ if (line_processed[it->second])
+ continue;
+ if (i_next == -1) {
+ i_next = it->second;
+ } else {
+ // This is a corner, where two lines meet exactly. Pick the line, which encloses a smallest angle with
+ // the current edge.
+ const Line &line_current = lines[i_line_current];
+ const Line &line_next = lines[it->second];
+ const Vector v1 = line_current.vector();
+ const Vector v2 = line_next.vector();
+ int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v2(0)) * int64_t(v1(1));
+ if (cross > 0) {
+ // This has to be a convex right angle. There is no better next line.
+ i_next = it->second;
+ break;
+ }
+ }
+ }
+ line_processed[i_next] = true;
+ i_line_current = i_next;
+ poly.points.push_back(lines[i_line_current].b);
+ }
+ end_of_poly:
+ out.push_back(std::move(poly));
+ }
+
+ // 3) Scale the polygons back into world, shrink slightly and remove collinear points.
+ for (size_t i = 0; i < out.size(); ++ i) {
+ Polygon &poly = out[i];
+ for (size_t j = 0; j < poly.points.size(); ++ j) {
+ Point &p = poly.points[j];
+ p(0) *= m_resolution;
+ p(1) *= m_resolution;
+ p(0) += m_bbox.min(0);
+ p(1) += m_bbox.min(1);
+ }
+ // Shrink the contour slightly, so if the same contour gets discretized and simplified again, one will get the same result.
+ // Remove collineaer points.
+ Points pts;
+ pts.reserve(poly.points.size());
+ for (size_t j = 0; j < poly.points.size(); ++ j) {
+ size_t j0 = (j == 0) ? poly.points.size() - 1 : j - 1;
+ size_t j2 = (j + 1 == poly.points.size()) ? 0 : j + 1;
+ Point v = poly.points[j2] - poly.points[j0];
+ if (v(0) != 0 && v(1) != 0) {
+ // This is a corner point. Copy it to the output contour.
+ Point p = poly.points[j];
+ p(1) += (v(0) < 0) ? - offset : offset;
+ p(0) += (v(1) > 0) ? - offset : offset;
+ pts.push_back(p);
+ }
+ }
+ poly.points = std::move(pts);
+ }
+ return out;
+}
+
+#if 0
+void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path)
+{
+ unsigned int w = (bbox.max(0) - bbox.min(0) + resolution - 1) / resolution;
+ unsigned int h = (bbox.max(1) - bbox.min(1) + resolution - 1) / resolution;
+ wxImage img(w, h);
+ unsigned char *data = img.GetData();
+ memset(data, 0, w * h * 3);
+
+ static int iRun = 0;
+ ++iRun;
+
+ const coord_t search_radius = grid.resolution() * 2;
+ const coord_t display_blend_radius = grid.resolution() * 2;
+ for (coord_t r = 0; r < h; ++r) {
+ for (coord_t c = 0; c < w; ++ c) {
+ unsigned char *pxl = data + (((h - r - 1) * w) + c) * 3;
+ Point pt(c * resolution + bbox.min(0), r * resolution + bbox.min(1));
+ coordf_t min_dist;
+ bool on_segment = true;
+ #if 0
+ if (grid.signed_distance_edges(pt, search_radius, min_dist, &on_segment)) {
+ #else
+ if (grid.signed_distance(pt, search_radius, min_dist)) {
+ #endif
+ float s = 255 * std::abs(min_dist) / float(display_blend_radius);
+ int is = std::max(0, std::min(255, int(floor(s + 0.5f))));
+ if (min_dist < 0) {
+ if (on_segment) {
+ pxl[0] = 255;
+ pxl[1] = 255 - is;
+ pxl[2] = 255 - is;
+ } else {
+ pxl[0] = 255;
+ pxl[1] = 0;
+ pxl[2] = 255 - is;
+ }
+ }
+ else {
+ if (on_segment) {
+ pxl[0] = 255 - is;
+ pxl[1] = 255 - is;
+ pxl[2] = 255;
+ } else {
+ pxl[0] = 255 - is;
+ pxl[1] = 0;
+ pxl[2] = 255;
+ }
+ }
+ } else {
+ pxl[0] = 0;
+ pxl[1] = 255;
+ pxl[2] = 0;
+ }
+
+ float gridx = float(pt(0) - grid.bbox().min(0)) / float(grid.resolution());
+ float gridy = float(pt(1) - grid.bbox().min(1)) / float(grid.resolution());
+ if (gridx >= -0.4f && gridy >= -0.4f && gridx <= grid.cols() + 0.4f && gridy <= grid.rows() + 0.4f) {
+ int ix = int(floor(gridx + 0.5f));
+ int iy = int(floor(gridy + 0.5f));
+ float dx = gridx - float(ix);
+ float dy = gridy - float(iy);
+ float d = sqrt(dx*dx + dy*dy) * float(grid.resolution()) / float(resolution);
+ if (d < 1.f) {
+ // Less than 1 pixel from the grid point.
+ float t = 0.5f + 0.5f * d;
+ pxl[0] = (unsigned char)(t * pxl[0]);
+ pxl[1] = (unsigned char)(t * pxl[1]);
+ pxl[2] = (unsigned char)(t * pxl[2]);
+ }
+ }
+
+ float dgrid = fabs(min_dist) / float(grid.resolution());
+ float igrid = floor(dgrid + 0.5f);
+ dgrid = std::abs(dgrid - igrid) * float(grid.resolution()) / float(resolution);
+ if (dgrid < 1.f) {
+ // Less than 1 pixel from the grid point.
+ float t = 0.5f + 0.5f * dgrid;
+ pxl[0] = (unsigned char)(t * pxl[0]);
+ pxl[1] = (unsigned char)(t * pxl[1]);
+ pxl[2] = (unsigned char)(t * pxl[2]);
+ if (igrid > 0.f) {
+ // Other than zero iso contour.
+ int g = pxl[1] + 255.f * (1.f - t);
+ pxl[1] = std::min(g, 255);
+ }
+ }
+ }
+ }
+
+ img.SaveFile(path, wxBITMAP_TYPE_PNG);
+}
+#endif /* SLIC3R_GUI */
+
+} // namespace Slic3r
diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp
new file mode 100644
index 000000000..ab1aa4ed0
--- /dev/null
+++ b/src/libslic3r/EdgeGrid.hpp
@@ -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_ */
diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp
new file mode 100644
index 000000000..00bb4ffe4
--- /dev/null
+++ b/src/libslic3r/ExPolygon.cpp
@@ -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
diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp
new file mode 100644
index 000000000..4833ee49e
--- /dev/null
+++ b/src/libslic3r/ExPolygon.hpp
@@ -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
diff --git a/src/libslic3r/ExPolygonCollection.cpp b/src/libslic3r/ExPolygonCollection.cpp
new file mode 100644
index 000000000..6933544b6
--- /dev/null
+++ b/src/libslic3r/ExPolygonCollection.cpp
@@ -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);
+}
+
+}
diff --git a/src/libslic3r/ExPolygonCollection.hpp b/src/libslic3r/ExPolygonCollection.hpp
new file mode 100644
index 000000000..4c181cd6a
--- /dev/null
+++ b/src/libslic3r/ExPolygonCollection.hpp
@@ -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
diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp
new file mode 100644
index 000000000..74d900825
--- /dev/null
+++ b/src/libslic3r/Extruder.cpp
@@ -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);
+}
+
+}
diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp
new file mode 100644
index 000000000..df92bf84b
--- /dev/null
+++ b/src/libslic3r/Extruder.hpp
@@ -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
diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp
new file mode 100644
index 000000000..92f0d3669
--- /dev/null
+++ b/src/libslic3r/ExtrusionEntity.cpp
@@ -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;
+}
+
+}
diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp
new file mode 100644
index 000000000..cce3020f8
--- /dev/null
+++ b/src/libslic3r/ExtrusionEntity.hpp
@@ -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
diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp
new file mode 100644
index 000000000..7a086bcbf
--- /dev/null
+++ b/src/libslic3r/ExtrusionEntityCollection.cpp
@@ -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;
+}
+
+}
diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp
new file mode 100644
index 000000000..230c04160
--- /dev/null
+++ b/src/libslic3r/ExtrusionEntityCollection.hpp
@@ -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
diff --git a/src/libslic3r/ExtrusionSimulator.cpp b/src/libslic3r/ExtrusionSimulator.cpp
new file mode 100644
index 000000000..fcb2fe825
--- /dev/null
+++ b/src/libslic3r/ExtrusionSimulator.cpp
@@ -0,0 +1,1030 @@
+// Optimize the extrusion simulator to the bones.
+//#pragma GCC optimize ("O3")
+//#undef SLIC3R_DEBUG
+//#define NDEBUG
+
+#include <cmath>
+#include <cassert>
+
+#include <boost/geometry.hpp>
+#include <boost/geometry/geometries/box.hpp>
+#include <boost/geometry/geometries/point.hpp>
+#include <boost/geometry/geometries/point_xy.hpp>
+
+#include <boost/multi_array.hpp>
+
+#include "libslic3r.h"
+#include "ExtrusionSimulator.hpp"
+
+#ifndef M_PI
+#define M_PI 3.1415926535897932384626433832795
+#endif
+
+namespace Slic3r {
+
+// Replacement for a template alias.
+// Shorthand for the point_xy.
+template<typename T>
+struct V2
+{
+ typedef boost::geometry::model::d2::point_xy<T> Type;
+};
+
+// Replacement for a template alias.
+// Shorthand for the point with a cartesian coordinate system.
+template<typename T>
+struct V3
+{
+ typedef boost::geometry::model::point<T, 3, boost::geometry::cs::cartesian> Type;
+};
+
+// Replacement for a template alias.
+// Shorthand for the point with a cartesian coordinate system.
+template<typename T>
+struct V4
+{
+ typedef boost::geometry::model::point<T, 4, boost::geometry::cs::cartesian> Type;
+};
+
+typedef V2<int >::Type V2i;
+typedef V2<float >::Type V2f;
+typedef V2<double>::Type V2d;
+
+// Used for an RGB color.
+typedef V3<unsigned char>::Type V3uc;
+// Used for an RGBA color.
+typedef V4<unsigned char>::Type V4uc;
+
+typedef boost::geometry::model::box<V2i> B2i;
+typedef boost::geometry::model::box<V2f> B2f;
+typedef boost::geometry::model::box<V2d> B2d;
+
+typedef boost::multi_array<unsigned char, 2> A2uc;
+typedef boost::multi_array<int , 2> A2i;
+typedef boost::multi_array<float , 2> A2f;
+typedef boost::multi_array<double , 2> A2d;
+
+template<typename T>
+inline void operator+=(
+ boost::geometry::model::d2::point_xy<T> &v1,
+ const boost::geometry::model::d2::point_xy<T> &v2)
+{
+ boost::geometry::add_point(v1, v2);
+}
+
+template<typename T>
+inline void operator-=(
+ boost::geometry::model::d2::point_xy<T> &v1,
+ const boost::geometry::model::d2::point_xy<T> &v2)
+{
+ boost::geometry::subtract_point(v1, v2);
+}
+
+template<typename T>
+inline void operator*=(boost::geometry::model::d2::point_xy<T> &v, const T c)
+{
+ boost::geometry::multiply_value(v, c);
+}
+
+template<typename T>
+inline void operator/=(boost::geometry::model::d2::point_xy<T> &v, const T c)
+{
+ boost::geometry::divide_value(v, c);
+}
+
+template<typename T>
+inline typename boost::geometry::model::d2::point_xy<T> operator+(
+ const boost::geometry::model::d2::point_xy<T> &v1,
+ const boost::geometry::model::d2::point_xy<T> &v2)
+{
+ boost::geometry::model::d2::point_xy<T> out(v1);
+ out += v2;
+ return out;
+}
+
+template<typename T>
+inline boost::geometry::model::d2::point_xy<T> operator-(
+ const boost::geometry::model::d2::point_xy<T> &v1,
+ const boost::geometry::model::d2::point_xy<T> &v2)
+{
+ boost::geometry::model::d2::point_xy<T> out(v1);
+ out -= v2;
+ return out;
+}
+
+template<typename T>
+inline boost::geometry::model::d2::point_xy<T> operator*(
+ const boost::geometry::model::d2::point_xy<T> &v, const T c)
+{
+ boost::geometry::model::d2::point_xy<T> out(v);
+ out *= c;
+ return out;
+}
+
+template<typename T>
+inline typename boost::geometry::model::d2::point_xy<T> operator*(
+ const T c, const boost::geometry::model::d2::point_xy<T> &v)
+{
+ boost::geometry::model::d2::point_xy<T> out(v);
+ out *= c;
+ return out;
+}
+
+template<typename T>
+inline typename boost::geometry::model::d2::point_xy<T> operator/(
+ const boost::geometry::model::d2::point_xy<T> &v, const T c)
+{
+ boost::geometry::model::d2::point_xy<T> out(v);
+ out /= c;
+ return out;
+}
+
+template<typename T>
+inline T dot(
+ const boost::geometry::model::d2::point_xy<T> &v1,
+ const boost::geometry::model::d2::point_xy<T> &v2)
+{
+ return boost::geometry::dot_product(v1, v2);
+}
+
+template<typename T>
+inline T dot(const boost::geometry::model::d2::point_xy<T> &v)
+{
+ return boost::geometry::dot_product(v, v);
+}
+
+template <typename T>
+inline T cross(
+ const boost::geometry::model::d2::point_xy<T> &v1,
+ const boost::geometry::model::d2::point_xy<T> &v2)
+{
+ return v1.x() * v2.y() - v2.x() * v1.y();
+}
+
+// Euclidian measure
+template<typename T>
+inline T l2(const boost::geometry::model::d2::point_xy<T> &v)
+{
+ return std::sqrt(dot(v));
+}
+
+// Euclidian measure
+template<typename T>
+inline T mag(const boost::geometry::model::d2::point_xy<T> &v)
+{
+ return l2(v);
+}
+
+template<typename T>
+inline T dist2_to_line(
+ const boost::geometry::model::d2::point_xy<T> &p0,
+ const boost::geometry::model::d2::point_xy<T> &p1,
+ const boost::geometry::model::d2::point_xy<T> &px)
+{
+ boost::geometry::model::d2::point_xy<T> v = p1 - p0;
+ boost::geometry::model::d2::point_xy<T> vx = px - p0;
+ T l = dot(v);
+ T t = dot(v, vx);
+ if (l != T(0) && t > T(0.)) {
+ t /= l;
+ vx = px - ((t > T(1.)) ? p1 : (p0 + t * v));
+ }
+ return dot(vx);
+}
+
+// Intersect a circle with a line segment.
+// Returns number of intersection points.
+template<typename T>
+int line_circle_intersection(
+ const boost::geometry::model::d2::point_xy<T> &p0,
+ const boost::geometry::model::d2::point_xy<T> &p1,
+ const boost::geometry::model::d2::point_xy<T> &center,
+ const T radius,
+ boost::geometry::model::d2::point_xy<T> intersection[2])
+{
+ typedef typename V2<T>::Type V2T;
+ V2T v = p1 - p0;
+ V2T vc = p0 - center;
+ T a = dot(v);
+ T b = T(2.) * dot(vc, v);
+ T c = dot(vc) - radius * radius;
+ T d = b * b - T(4.) * a * c;
+
+ if (d < T(0))
+ // The circle misses the ray.
+ return 0;
+
+ int n = 0;
+ if (d == T(0)) {
+ // The circle touches the ray at a single tangent point.
+ T t = - b / (T(2.) * a);
+ if (t >= T(0.) && t <= T(1.))
+ intersection[n ++] = p0 + t * v;
+ } else {
+ // The circle intersects the ray in two points.
+ d = sqrt(d);
+ T t = (- b - d) / (T(2.) * a);
+ if (t >= T(0.) && t <= T(1.))
+ intersection[n ++] = p0 + t * v;
+ t = (- b + d) / (T(2.) * a);
+ if (t >= T(0.) && t <= T(1.))
+ intersection[n ++] = p0 + t * v;
+ }
+ return n;
+}
+
+// Sutherland–Hodgman clipping of a rectangle against an AABB.
+// Expects the first 4 points of rect to be filled at the beginning.
+// The clipping may produce up to 8 points.
+// Returns the number of resulting points.
+template<typename T>
+int clip_rect_by_AABB(
+ boost::geometry::model::d2::point_xy<T> rect[8],
+ const boost::geometry::model::box<boost::geometry::model::d2::point_xy<T> > &aabb)
+{
+ typedef typename V2<T>::Type V2T;
+ V2T result[8];
+ int nin = 4;
+ int nout = 0;
+ V2T *in = rect;
+ V2T *out = result;
+ // Clip left
+ {
+ const V2T *S = in + nin - 1;
+ T left = aabb.min_corner().x();
+ for (int i = 0; i < nin; ++i) {
+ const V2T &E = in[i];
+ if (E.x() == left) {
+ out[nout++] = E;
+ }
+ else if (E.x() > left) {
+ // E is inside the AABB.
+ if (S->x() < left) {
+ // S is outside the AABB. Calculate an intersection point.
+ T t = (left - S->x()) / (E.x() - S->x());
+ out[nout++] = V2T(left, S->y() + t * (E.y() - S->y()));
+ }
+ out[nout++] = E;
+ }
+ else if (S->x() > left) {
+ // S is inside the AABB, E is outside the AABB.
+ T t = (left - S->x()) / (E.x() - S->x());
+ out[nout++] = V2T(left, S->y() + t * (E.y() - S->y()));
+ }
+ S = &E;
+ }
+ assert(nout <= 8);
+ }
+ // Clip bottom
+ {
+ std::swap(in, out);
+ nin = nout;
+ nout = 0;
+ const V2T *S = in + nin - 1;
+ T bottom = aabb.min_corner().y();
+ for (int i = 0; i < nin; ++i) {
+ const V2T &E = in[i];
+ if (E.y() == bottom) {
+ out[nout++] = E;
+ }
+ else if (E.y() > bottom) {
+ // E is inside the AABB.
+ if (S->y() < bottom) {
+ // S is outside the AABB. Calculate an intersection point.
+ T t = (bottom - S->y()) / (E.y() - S->y());
+ out[nout++] = V2T(S->x() + t * (E.x() - S->x()), bottom);
+ }
+ out[nout++] = E;
+ }
+ else if (S->y() > bottom) {
+ // S is inside the AABB, E is outside the AABB.
+ T t = (bottom - S->y()) / (E.y() - S->y());
+ out[nout++] = V2T(S->x() + t * (E.x() - S->x()), bottom);
+ }
+ S = &E;
+ }
+ assert(nout <= 8);
+ }
+ // Clip right
+ {
+ std::swap(in, out);
+ nin = nout;
+ nout = 0;
+ const V2T *S = in + nin - 1;
+ T right = aabb.max_corner().x();
+ for (int i = 0; i < nin; ++i) {
+ const V2T &E = in[i];
+ if (E.x() == right) {
+ out[nout++] = E;
+ }
+ else if (E.x() < right) {
+ // E is inside the AABB.
+ if (S->x() > right) {
+ // S is outside the AABB. Calculate an intersection point.
+ T t = (right - S->x()) / (E.x() - S->x());
+ out[nout++] = V2T(right, S->y() + t * (E.y() - S->y()));
+ }
+ out[nout++] = E;
+ }
+ else if (S->x() < right) {
+ // S is inside the AABB, E is outside the AABB.
+ T t = (right - S->x()) / (E.x() - S->x());
+ out[nout++] = V2T(right, S->y() + t * (E.y() - S->y()));
+ }
+ S = &E;
+ }
+ assert(nout <= 8);
+ }
+ // Clip top
+ {
+ std::swap(in, out);
+ nin = nout;
+ nout = 0;
+ const V2T *S = in + nin - 1;
+ T top = aabb.max_corner().y();
+ for (int i = 0; i < nin; ++i) {
+ const V2T &E = in[i];
+ if (E.y() == top) {
+ out[nout++] = E;
+ }
+ else if (E.y() < top) {
+ // E is inside the AABB.
+ if (S->y() > top) {
+ // S is outside the AABB. Calculate an intersection point.
+ T t = (top - S->y()) / (E.y() - S->y());
+ out[nout++] = V2T(S->x() + t * (E.x() - S->x()), top);
+ }
+ out[nout++] = E;
+ }
+ else if (S->y() < top) {
+ // S is inside the AABB, E is outside the AABB.
+ T t = (top - S->y()) / (E.y() - S->y());
+ out[nout++] = V2T(S->x() + t * (E.x() - S->x()), top);
+ }
+ S = &E;
+ }
+ assert(nout <= 8);
+ }
+
+ assert(nout <= 8);
+ return nout;
+}
+
+// Calculate area of the circle x AABB intersection.
+// The calculation is approximate in a way, that the circular segment
+// intersecting the cell is approximated by its chord (a linear segment).
+template<typename T>
+int clip_circle_by_AABB(
+ const boost::geometry::model::d2::point_xy<T> &center,
+ const T radius,
+ const boost::geometry::model::box<boost::geometry::model::d2::point_xy<T> > &aabb,
+ boost::geometry::model::d2::point_xy<T> result[8],
+ bool result_arc[8])
+{
+ typedef typename V2<T>::Type V2T;
+
+ V2T rect[4] = {
+ aabb.min_corner(),
+ V2T(aabb.max_corner().x(), aabb.min_corner().y()),
+ aabb.max_corner(),
+ V2T(aabb.min_corner().x(), aabb.max_corner().y())
+ };
+
+ int bits_corners = 0;
+ T r2 = sqr(radius);
+ for (int i = 0; i < 4; ++ i, bits_corners <<= 1)
+ bits_corners |= dot(rect[i] - center) >= r2;
+ bits_corners >>= 1;
+
+ if (bits_corners == 0) {
+ // all inside
+ memcpy(result, rect, sizeof(rect));
+ memset(result_arc, true, 4);
+ return 4;
+ }
+
+ if (bits_corners == 0x0f)
+ // all outside
+ return 0;
+
+ // Some corners are outside, some are inside. Trim the rectangle.
+ int n = 0;
+ for (int i = 0; i < 4; ++ i) {
+ bool inside = (bits_corners & 0x08) == 0;
+ bits_corners <<= 1;
+ V2T chordal_points[2];
+ int n_chordal_points = line_circle_intersection(rect[i], rect[(i + 1)%4], center, radius, chordal_points);
+ if (n_chordal_points == 2) {
+ result_arc[n] = true;
+ result[n ++] = chordal_points[0];
+ result_arc[n] = true;
+ result[n ++] = chordal_points[1];
+ } else {
+ if (inside) {
+ result_arc[n] = false;
+ result[n ++] = rect[i];
+ }
+ if (n_chordal_points == 1) {
+ result_arc[n] = false;
+ result[n ++] = chordal_points[0];
+ }
+ }
+ }
+ return n;
+}
+/*
+// Calculate area of the circle x AABB intersection.
+// The calculation is approximate in a way, that the circular segment
+// intersecting the cell is approximated by its chord (a linear segment).
+template<typename T>
+T circle_AABB_intersection_area(
+ const boost::geometry::model::d2::point_xy<T> &center,
+ const T radius,
+ const boost::geometry::model::box<boost::geometry::model::d2::point_xy<T> > &aabb)
+{
+ typedef typename V2<T>::Type V2T;
+ typedef typename boost::geometry::model::box<V2T> B2T;
+ T radius2 = radius * radius;
+
+ bool intersectionLeft = sqr(aabb.min_corner().x() - center.x()) < radius2;
+ bool intersectionRight = sqr(aabb.max_corner().x() - center.x()) < radius2;
+ bool intersectionBottom = sqr(aabb.min_corner().y() - center.y()) < radius2;
+ bool intersectionTop = sqr(aabb.max_corner().y() - center.y()) < radius2;
+
+ if (! (intersectionLeft || intersectionRight || intersectionTop || intersectionBottom))
+ // No intersection between the aabb and the center.
+ return boost::geometry::point_in_box<V2T, B2T>()::apply(center, aabb) ? 1.f : 0.f;
+
+
+
+ V2T rect[4] = {
+ aabb.min_corner(),
+ V2T(aabb.max_corner().x(), aabb.min_corner().y()),
+ aabb.max_corner(),
+ V2T(aabb.min_corner().x(), aabb.max_corner().y())
+ };
+
+ int bits_corners = 0;
+ T r2 = sqr(radius);
+ for (int i = 0; i < 4; ++ i, bits_corners <<= 1)
+ bits_corners |= dot(rect[i] - center) >= r2;
+ bits_corners >>= 1;
+
+ if (bits_corners == 0) {
+ // all inside
+ memcpy(result, rect, sizeof(rect));
+ memset(result_arc, true, 4);
+ return 4;
+ }
+
+ if (bits_corners == 0x0f)
+ // all outside
+ return 0;
+
+ // Some corners are outside, some are inside. Trim the rectangle.
+ int n = 0;
+ for (int i = 0; i < 4; ++ i) {
+ bool inside = (bits_corners & 0x08) == 0;
+ bits_corners <<= 1;
+ V2T chordal_points[2];
+ int n_chordal_points = line_circle_intersection(rect[i], rect[(i + 1)%4], center, radius, chordal_points);
+ if (n_chordal_points == 2) {
+ result_arc[n] = true;
+ result[n ++] = chordal_points[0];
+ result_arc[n] = true;
+ result[n ++] = chordal_points[1];
+ } else {
+ if (inside) {
+ result_arc[n] = false;
+ result[n ++] = rect[i];
+ }
+ if (n_chordal_points == 1) {
+ result_arc[n] = false;
+ result[n ++] = chordal_points[0];
+ }
+ }
+ }
+ return n;
+}
+*/
+
+template<typename T>
+inline T polyArea(const boost::geometry::model::d2::point_xy<T> *poly, int n)
+{
+ T area = T(0);
+ for (int i = 1; i + 1 < n; ++i)
+ area += cross(poly[i] - poly[0], poly[i + 1] - poly[0]);
+ return T(0.5) * area;
+}
+
+template<typename T>
+boost::geometry::model::d2::point_xy<T> polyCentroid(const boost::geometry::model::d2::point_xy<T> *poly, int n)
+{
+ boost::geometry::model::d2::point_xy<T> centroid(T(0), T(0));
+ for (int i = 0; i < n; ++i)
+ centroid += poly[i];
+ return (n == 0) ? centroid : (centroid / float(n));
+}
+
+void gcode_paint_layer(
+ const std::vector<V2f> &polyline,
+ float width,
+ float thickness,
+ A2f &acc)
+{
+ int nc = acc.shape()[1];
+ int nr = acc.shape()[0];
+// printf("gcode_paint_layer %d,%d\n", nc, nr);
+ for (size_t iLine = 1; iLine != polyline.size(); ++iLine) {
+ const V2f &p1 = polyline[iLine - 1];
+ const V2f &p2 = polyline[iLine];
+ // printf("p1, p2: %f,%f %f,%f\n", p1.x(), p1.y(), p2.x(), p2.y());
+ const V2f dir = p2 - p1;
+ V2f vperp(- dir.y(), dir.x());
+ vperp = vperp * 0.5f * width / l2(vperp);
+ // Rectangle of the extrusion.
+ V2f rect[4] = { p1 + vperp, p1 - vperp, p2 - vperp, p2 + vperp };
+ // Bounding box of the extrusion.
+ B2f bboxLine(rect[0], rect[0]);
+ boost::geometry::expand(bboxLine, rect[1]);
+ boost::geometry::expand(bboxLine, rect[2]);
+ boost::geometry::expand(bboxLine, rect[3]);
+ B2i bboxLinei(
+ V2i(clamp(0, nc-1, int(floor(bboxLine.min_corner().x()))),
+ clamp(0, nr-1, int(floor(bboxLine.min_corner().y())))),
+ V2i(clamp(0, nc-1, int(ceil (bboxLine.max_corner().x()))),
+ clamp(0, nr-1, int(ceil (bboxLine.max_corner().y())))));
+ // printf("bboxLinei %d,%d %d,%d\n", bboxLinei.min_corner().x(), bboxLinei.min_corner().y(), bboxLinei.max_corner().x(), bboxLinei.max_corner().y());
+#ifdef _DEBUG
+ float area = polyArea(rect, 4);
+ assert(area > 0.f);
+#endif /* _DEBUG */
+ for (int j = bboxLinei.min_corner().y(); j + 1 < bboxLinei.max_corner().y(); ++ j) {
+ for (int i = bboxLinei.min_corner().x(); i + 1 < bboxLinei.max_corner().x(); ++i) {
+ V2f rect2[8];
+ memcpy(rect2, rect, sizeof(rect));
+ int n = clip_rect_by_AABB(rect2, B2f(V2f(float(i), float(j)), V2f(float(i + 1), float(j + 1))));
+ float area = polyArea(rect2, n);
+ assert(area >= 0.f && area <= 1.000001f);
+ acc[j][i] += area * thickness;
+ }
+ }
+ }
+}
+
+void gcode_paint_bitmap(
+ const std::vector<V2f> &polyline,
+ float width,
+ A2uc &bitmap,
+ float scale)
+{
+ int nc = bitmap.shape()[1];
+ int nr = bitmap.shape()[0];
+ float r2 = width * width * 0.25f;
+// printf("gcode_paint_layer %d,%d\n", nc, nr);
+ for (size_t iLine = 1; iLine != polyline.size(); ++iLine) {
+ const V2f &p1 = polyline[iLine - 1];
+ const V2f &p2 = polyline[iLine];
+ // printf("p1, p2: %f,%f %f,%f\n", p1.x(), p1.y(), p2.x(), p2.y());
+ V2f dir = p2 - p1;
+ dir = dir * 0.5f * width / l2(dir);
+ V2f vperp(- dir.y(), dir.x());
+ // Rectangle of the extrusion.
+ V2f rect[4] = { (p1 + vperp - dir) * scale, (p1 - vperp - dir) * scale, (p2 - vperp + dir) * scale, (p2 + vperp + dir) * scale };
+ // Bounding box of the extrusion.
+ B2f bboxLine(rect[0], rect[0]);
+ boost::geometry::expand(bboxLine, rect[1]);
+ boost::geometry::expand(bboxLine, rect[2]);
+ boost::geometry::expand(bboxLine, rect[3]);
+ B2i bboxLinei(
+ V2i(clamp(0, nc-1, int(floor(bboxLine.min_corner().x()))),
+ clamp(0, nr-1, int(floor(bboxLine.min_corner().y())))),
+ V2i(clamp(0, nc-1, int(ceil (bboxLine.max_corner().x()))),
+ clamp(0, nr-1, int(ceil (bboxLine.max_corner().y())))));
+ // printf("bboxLinei %d,%d %d,%d\n", bboxLinei.min_corner().x(), bboxLinei.min_corner().y(), bboxLinei.max_corner().x(), bboxLinei.max_corner().y());
+ for (int j = bboxLinei.min_corner().y(); j + 1 < bboxLinei.max_corner().y(); ++ j) {
+ for (int i = bboxLinei.min_corner().x(); i + 1 < bboxLinei.max_corner().x(); ++i) {
+ float d2 = dist2_to_line(p1, p2, V2f(float(i) + 0.5f, float(j) + 0.5f) / scale);
+ if (d2 < r2)
+ bitmap[j][i] = 1;
+ }
+ }
+ }
+}
+
+struct Cell
+{
+ // Cell index in the grid.
+ V2i idx;
+ // Total volume of the material stored in this cell.
+ float volume;
+ // Area covered inside this cell, <0,1>.
+ float area;
+ // Fraction of the area covered by the print head. <0,1>
+ float fraction_covered;
+ // Height of the covered part in excess to the expected layer height.
+ float excess_height;
+
+ bool operator<(const Cell &c2) const {
+ return this->excess_height < c2.excess_height;
+ }
+};
+
+struct ExtrusionPoint {
+ V2f center;
+ float radius;
+ float height;
+};
+
+typedef std::vector<ExtrusionPoint> ExtrusionPoints;
+
+void gcode_spread_points(
+ A2f &acc,
+ const A2f &mask,
+ const ExtrusionPoints &points,
+ ExtrusionSimulationType simulationType)
+{
+ int nc = acc.shape()[1];
+ int nr = acc.shape()[0];
+
+ // Maximum radius of the spreading points, to allocate a large enough cell array.
+ float rmax = 0.f;
+ for (ExtrusionPoints::const_iterator it = points.begin(); it != points.end(); ++ it)
+ rmax = std::max(rmax, it->radius);
+ size_t n_rows_max = size_t(ceil(rmax * 2.f + 2.f));
+ size_t n_cells_max = sqr(n_rows_max);
+ std::vector<std::pair<float, float> > spans;
+ std::vector<Cell> cells(n_cells_max, Cell());
+ std::vector<float> areas_sum(n_cells_max, 0.f);
+
+ for (ExtrusionPoints::const_iterator it = points.begin(); it != points.end(); ++ it) {
+ const V2f &center = it->center;
+ const float radius = it->radius;
+ const float radius2 = radius * radius;
+ const float height_target = it->height;
+ B2f bbox(center - V2f(radius, radius), center + V2f(radius, radius));
+ B2i bboxi(
+ V2i(clamp(0, nc-1, int(floor(bbox.min_corner().x()))),
+ clamp(0, nr-1, int(floor(bbox.min_corner().y())))),
+ V2i(clamp(0, nc-1, int(ceil (bbox.max_corner().x()))),
+ clamp(0, nr-1, int(ceil (bbox.max_corner().y())))));
+ /*
+ // Fill in the spans, at which the circle intersects the rows.
+ int row_first = bboxi.min_corner().y();
+ int row_last = bboxi.max_corner().y();
+ for (; row_first <= row_last; ++ row_first) {
+ float y = float(j) - center.y();
+ float discr = radius2 - sqr(y);
+ if (discr > 0) {
+ // Circle intersects the row j at 2 points.
+ float d = sqrt(discr);
+ spans.push_back(std.pair<float, float>(center.x() - d, center.x() + d)));
+ break;
+ }
+ }
+ for (int j = row_first + 1; j <= row_last; ++ j) {
+ float y = float(j) - center.y();
+ float discr = radius2 - sqr(y);
+ if (discr > 0) {
+ // Circle intersects the row j at 2 points.
+ float d = sqrt(discr);
+ spans.push_back(std.pair<float, float>(center.x() - d, center.x() + d)));
+ } else {
+ row_last = j - 1;
+ break;
+ }
+ }
+ */
+ float area_total = 0;
+ float volume_total = 0;
+ float volume_excess = 0;
+ float volume_deficit = 0;
+ size_t n_cells = 0;
+ float area_circle_total = 0;
+#if 0
+ // The intermediate lines.
+ for (int j = row_first; j < row_last; ++ j) {
+ const std::pair<float, float> &span1 = spans[j];
+ const std::pair<float, float> &span2 = spans[j+1];
+ float l1 = span1.first;
+ float l2 = span2.first;
+ float r1 = span1.second;
+ float r2 = span2.second;
+ if (l2 < l1)
+ std::swap(l1, l2);
+ if (r1 > r2)
+ std::swap(r1, r2);
+ int il1 = int(floor(l1));
+ int il2 = int(ceil(l2));
+ int ir1 = int(floor(r1));
+ int ir2 = int(floor(r2));
+ assert(il2 <= ir1);
+ for (int i = il1; i < il2; ++ i) {
+ Cell &cell = cells[n_cells ++];
+ cell.idx.x(i);
+ cell.idx.y(j);
+ cell.area = area;
+ }
+ for (int i = il2; i < ir1; ++ i) {
+ Cell &cell = cells[n_cells ++];
+ cell.idx.x(i);
+ cell.idx.y(j);
+ cell.area = 1.f;
+ }
+ for (int i = ir1; i < ir2; ++ i) {
+ Cell &cell = cells[n_cells ++];
+ cell.idx.x(i);
+ cell.idx.y(j);
+ cell.area = area;
+ }
+ }
+#else
+ for (int j = bboxi.min_corner().y(); j < bboxi.max_corner().y(); ++ j) {
+ for (int i = bboxi.min_corner().x(); i < bboxi.max_corner().x(); ++i) {
+ B2f bb(V2f(float(i), float(j)), V2f(float(i + 1), float(j + 1)));
+ V2f poly[8];
+ bool poly_arc[8];
+ int n = clip_circle_by_AABB(center, radius, bb, poly, poly_arc);
+ float area = polyArea(poly, n);
+ assert(area >= 0.f && area <= 1.000001f);
+ if (area == 0.f)
+ continue;
+ Cell &cell = cells[n_cells ++];
+ cell.idx.x(i);
+ cell.idx.y(j);
+ cell.volume = acc[j][i];
+ cell.area = mask[j][i];
+ assert(cell.area >= 0.f && cell.area <= 1.000001f);
+ area_circle_total += area;
+ if (cell.area < area)
+ cell.area = area;
+ cell.fraction_covered = clamp(0.f, 1.f, (cell.area > 0) ? (area / cell.area) : 0);
+ if (cell.fraction_covered == 0) {
+ -- n_cells;
+ continue;
+ }
+ float cell_height = cell.volume / cell.area;
+ cell.excess_height = cell_height - height_target;
+ if (cell.excess_height > 0.f)
+ volume_excess += cell.excess_height * cell.area * cell.fraction_covered;
+ else
+ volume_deficit -= cell.excess_height * cell.area * cell.fraction_covered;
+ volume_total += cell.volume * cell.fraction_covered;
+ area_total += cell.area * cell.fraction_covered;
+ }
+ }
+#endif
+ float area_circle_total2 = float(M_PI) * sqr(radius);
+ float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2;
+// printf("area_circle_total: %f, %f, %f\n", area_circle_total, area_circle_total2, area_err);
+ float volume_full = float(M_PI) * sqr(radius) * height_target;
+// if (true) {
+// printf("volume_total: %f, volume_full: %f, fill factor: %f\n", volume_total, volume_full, 100.f - 100.f * volume_total / volume_full);
+// printf("volume_full: %f, volume_excess+deficit: %f, volume_excess: %f, volume_deficit: %f\n", volume_full, volume_excess+volume_deficit, volume_excess, volume_deficit);
+ if (simulationType == ExtrusionSimulationSpreadFull || volume_total <= volume_full) {
+ // The volume under the circle is spreaded fully.
+ float height_avg = volume_total / area_total;
+ for (size_t i = 0; i < n_cells; ++ i) {
+ const Cell &cell = cells[i];
+ acc[cell.idx.y()][cell.idx.x()] = (1.f - cell.fraction_covered) * cell.volume + cell.fraction_covered * cell.area * height_avg;
+ }
+ } else if (simulationType == ExtrusionSimulationSpreadExcess) {
+ // The volume under the circle does not fit.
+ // 1) Fill the underfilled cells and remove them from the list.
+ float volume_borrowed_total = 0.;
+ for (size_t i = 0; i < n_cells;) {
+ Cell &cell = cells[i];
+ if (cell.excess_height <= 0) {
+ // Fill in the part of the cell below the circle.
+ float volume_borrowed = - cell.excess_height * cell.area * cell.fraction_covered;
+ assert(volume_borrowed >= 0.f);
+ acc[cell.idx.y()][cell.idx.x()] = cell.volume + volume_borrowed;
+ volume_borrowed_total += volume_borrowed;
+ cell = cells[-- n_cells];
+ } else
+ ++ i;
+ }
+ // 2) Sort the remaining cells by their excess height.
+ std::sort(cells.begin(), cells.begin() + n_cells);
+ // 3) Prefix sum the areas per excess height.
+ // The excess height is discrete with the number of excess cells.
+ areas_sum[n_cells-1] = cells[n_cells-1].area * cells[n_cells-1].fraction_covered;
+ for (int i = n_cells - 2; i >= 0; -- i) {
+ const Cell &cell = cells[i];
+ areas_sum[i] = areas_sum[i + 1] + cell.area * cell.fraction_covered;
+ }
+ // 4) Find the excess height, where the volume_excess is over the volume_borrowed_total.
+ float volume_current = 0.f;
+ float excess_height_prev = 0.f;
+ size_t i_top = n_cells;
+ for (size_t i = 0; i < n_cells; ++ i) {
+ const Cell &cell = cells[i];
+ volume_current += (cell.excess_height - excess_height_prev) * areas_sum[i];
+ excess_height_prev = cell.excess_height;
+ if (volume_current > volume_borrowed_total) {
+ i_top = i;
+ break;
+ }
+ }
+ // 5) Remove material from the cells with deficit.
+ // First remove all the excess material from the cells, where the deficit is low.
+ for (size_t i = 0; i < i_top; ++ i) {
+ const Cell &cell = cells[i];
+ float volume_removed = cell.excess_height * cell.area * cell.fraction_covered;
+ acc[cell.idx.y()][cell.idx.x()] = cell.volume - volume_removed;
+ volume_borrowed_total -= volume_removed;
+ }
+ // Second remove some excess material from the cells, where the deficit is high.
+ if (i_top < n_cells) {
+ float height_diff = volume_borrowed_total / areas_sum[i_top];
+ for (size_t i = i_top; i < n_cells; ++ i) {
+ const Cell &cell = cells[i];
+ acc[cell.idx.y()][cell.idx.x()] = cell.volume - height_diff * cell.area * cell.fraction_covered;
+ }
+ }
+ }
+ }
+}
+
+inline std::vector<V3uc> CreatePowerColorGradient24bit()
+{
+ int i;
+ int iColor = 0;
+ std::vector<V3uc> out(6 * 255 + 1, V3uc(0, 0, 0));
+ for (i = 0; i < 256; ++i)
+ out[iColor++] = V3uc(0, 0, i);
+ for (i = 1; i < 256; ++i)
+ out[iColor++] = V3uc(0, i, 255);
+ for (i = 1; i < 256; ++i)
+ out[iColor++] = V3uc(0, 255, 256 - i);
+ for (i = 1; i < 256; ++i)
+ out[iColor++] = V3uc(i, 255, 0);
+ for (i = 1; i < 256; ++i)
+ out[iColor++] = V3uc(255, 256 - i, 0);
+ for (i = 1; i < 256; ++i)
+ out[iColor++] = V3uc(255, 0, i);
+ return out;
+}
+
+class ExtrusionSimulatorImpl {
+public:
+ std::vector<unsigned char> image_data;
+ A2f accumulator;
+ A2uc bitmap;
+ unsigned int bitmap_oversampled;
+ ExtrusionPoints extrusion_points;
+ // RGB gradient to color map the fullness of an accumulator bucket into the output image.
+ std::vector<boost::geometry::model::point<unsigned char, 3, boost::geometry::cs::cartesian> > color_gradient;
+};
+
+ExtrusionSimulator::ExtrusionSimulator() :
+ pimpl(new ExtrusionSimulatorImpl)
+{
+ pimpl->color_gradient = CreatePowerColorGradient24bit();
+ pimpl->bitmap_oversampled = 4;
+}
+
+ExtrusionSimulator::~ExtrusionSimulator()
+{
+ delete pimpl;
+ pimpl = NULL;
+}
+
+void ExtrusionSimulator::set_image_size(const Point &image_size)
+{
+ // printf("ExtrusionSimulator::set_image_size()\n");
+ if (this->image_size.x() == image_size.x() &&
+ this->image_size.y() == image_size.y())
+ return;
+
+ // printf("Setting image size: %d, %d\n", image_size.x, image_size.y);
+ this->image_size = image_size;
+ // Allocate the image data in an RGBA format.
+ // printf("Allocating image data, size %d\n", image_size.x * image_size.y * 4);
+ pimpl->image_data.assign(image_size.x() * image_size.y() * 4, 0);
+ // printf("Allocating image data, allocated\n");
+
+ //FIXME fill the image with red vertical lines.
+ for (size_t r = 0; r < image_size.y(); ++ r) {
+ for (size_t c = 0; c < image_size.x(); c += 2) {
+ // Color red
+ pimpl->image_data[r * image_size.x() * 4 + c * 4] = 255;
+ // Opacity full
+ pimpl->image_data[r * image_size.x() * 4 + c * 4 + 3] = 255;
+ }
+ }
+ // printf("Allocating image data, set\n");
+}
+
+void ExtrusionSimulator::set_viewport(const BoundingBox &viewport)
+{
+ // printf("ExtrusionSimulator::set_viewport(%d, %d, %d, %d)\n", viewport.min.x, viewport.min.y, viewport.max.x, viewport.max.y);
+ if (this->viewport != viewport) {
+ this->viewport = viewport;
+ Point sz = viewport.size();
+ pimpl->accumulator.resize(boost::extents[sz.y()][sz.x()]);
+ pimpl->bitmap.resize(boost::extents[sz.y()*pimpl->bitmap_oversampled][sz.x()*pimpl->bitmap_oversampled]);
+ // printf("Accumulator size: %d, %d\n", sz.y, sz.x);
+ }
+}
+
+void ExtrusionSimulator::set_bounding_box(const BoundingBox &bbox)
+{
+ this->bbox = bbox;
+}
+
+const void* ExtrusionSimulator::image_ptr() const
+{
+ return (pimpl->image_data.empty()) ? NULL : (void*)&pimpl->image_data.front();
+}
+
+void ExtrusionSimulator::reset_accumulator()
+{
+ // printf("ExtrusionSimulator::reset_accumulator()\n");
+ Point sz = viewport.size();
+ // printf("Reset accumulator, Accumulator size: %d, %d\n", sz.y, sz.x);
+ memset(&pimpl->accumulator[0][0], 0, sizeof(float) * sz.x() * sz.y());
+ memset(&pimpl->bitmap[0][0], 0, sz.x() * sz.y() * pimpl->bitmap_oversampled * pimpl->bitmap_oversampled);
+ pimpl->extrusion_points.clear();
+ // printf("Reset accumulator, done.\n");
+}
+
+void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const Point &shift, ExtrusionSimulationType simulationType)
+{
+ // printf("Extruding a path. Nr points: %d, width: %f, height: %f\r\n", path.polyline.points.size(), path.width, path.height);
+ // Convert the path to V2f points, shift and scale them to the viewport.
+ std::vector<V2f> polyline;
+ polyline.reserve(path.polyline.points.size());
+ float scalex = float(viewport.size().x()) / float(bbox.size().x());
+ float scaley = float(viewport.size().y()) / float(bbox.size().y());
+ float w = scale_(path.width) * scalex;
+ float h = scale_(path.height) * scalex;
+ w = scale_(path.mm3_per_mm / path.height) * scalex;
+ // printf("scalex: %f, scaley: %f\n", scalex, scaley);
+ // printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y);
+ for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) {
+ // printf("point %d,%d\n", it->x+shift.x(), it->y+shift.y);
+ ExtrusionPoint ept;
+ ept.center = V2f(float((*it)(0)+shift.x()-bbox.min.x()) * scalex, float((*it)(1)+shift.y()-bbox.min.y()) * scaley);
+ ept.radius = w/2.f;
+ ept.height = 0.5f;
+ polyline.push_back(ept.center);
+ pimpl->extrusion_points.push_back(ept);
+ }
+ // Extrude the polyline into an accumulator.
+ // printf("width scaled: %f, height scaled: %f\n", w, h);
+ gcode_paint_layer(polyline, w, 0.5f, pimpl->accumulator);
+
+ if (simulationType > ExtrusionSimulationDontSpread)
+ gcode_paint_bitmap(polyline, w, pimpl->bitmap, pimpl->bitmap_oversampled);
+ // double path.mm3_per_mm; // mm^3 of plastic per mm of linear head motion
+ // float path.width;
+ // float path.height;
+}
+
+void ExtrusionSimulator::evaluate_accumulator(ExtrusionSimulationType simulationType)
+{
+ // printf("ExtrusionSimulator::evaluate_accumulator()\n");
+ Point sz = viewport.size();
+
+ if (simulationType > ExtrusionSimulationDontSpread) {
+ // Average the cells of a bitmap into a lower resolution floating point mask.
+ A2f mask(boost::extents[sz.y()][sz.x()]);
+ for (int r = 0; r < sz.y(); ++r) {
+ for (int c = 0; c < sz.x(); ++c) {
+ float p = 0;
+ for (int j = 0; j < pimpl->bitmap_oversampled; ++ j) {
+ for (int i = 0; i < pimpl->bitmap_oversampled; ++ i) {
+ if (pimpl->bitmap[r * pimpl->bitmap_oversampled + j][c * pimpl->bitmap_oversampled + i])
+ p += 1.f;
+ }
+ }
+ p /= float(pimpl->bitmap_oversampled * pimpl->bitmap_oversampled * 2);
+ mask[r][c] = p;
+ }
+ }
+
+ // Spread the excess of the material.
+ gcode_spread_points(pimpl->accumulator, mask, pimpl->extrusion_points, simulationType);
+ }
+
+ // Color map the accumulator.
+ for (int r = 0; r < sz.y(); ++r) {
+ unsigned char *ptr = &pimpl->image_data[(image_size.x() * (viewport.min.y() + r) + viewport.min.x()) * 4];
+ for (int c = 0; c < sz.x(); ++c) {
+ #if 1
+ float p = pimpl->accumulator[r][c];
+ #else
+ float p = mask[r][c];
+ #endif
+ int idx = int(floor(p * float(pimpl->color_gradient.size()) + 0.5f));
+ V3uc clr = pimpl->color_gradient[clamp(0, int(pimpl->color_gradient.size()-1), idx)];
+ *ptr ++ = clr.get<0>();
+ *ptr ++ = clr.get<1>();
+ *ptr ++ = clr.get<2>();
+ *ptr ++ = (idx == 0) ? 0 : 255;
+ }
+ }
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/ExtrusionSimulator.hpp b/src/libslic3r/ExtrusionSimulator.hpp
new file mode 100644
index 000000000..040406766
--- /dev/null
+++ b/src/libslic3r/ExtrusionSimulator.hpp
@@ -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_ */
diff --git a/src/libslic3r/FileParserError.hpp b/src/libslic3r/FileParserError.hpp
new file mode 100644
index 000000000..3f560fa4f
--- /dev/null
+++ b/src/libslic3r/FileParserError.hpp
@@ -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_
diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp
new file mode 100644
index 000000000..f1436c931
--- /dev/null
+++ b/src/libslic3r/Fill/Fill.cpp
@@ -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
diff --git a/src/libslic3r/Fill/Fill.hpp b/src/libslic3r/Fill/Fill.hpp
new file mode 100644
index 000000000..c04305c04
--- /dev/null
+++ b/src/libslic3r/Fill/Fill.hpp
@@ -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_
diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp
new file mode 100644
index 000000000..6a37e4369
--- /dev/null
+++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp
@@ -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
diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.hpp b/src/libslic3r/Fill/Fill3DHoneycomb.hpp
new file mode 100644
index 000000000..52b792251
--- /dev/null
+++ b/src/libslic3r/Fill/Fill3DHoneycomb.hpp
@@ -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_
diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp
new file mode 100644
index 000000000..7a99e84f7
--- /dev/null
+++ b/src/libslic3r/Fill/FillBase.cpp
@@ -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
diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp
new file mode 100644
index 000000000..b67d14339
--- /dev/null
+++ b/src/libslic3r/Fill/FillBase.hpp
@@ -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_
diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp
new file mode 100644
index 000000000..8a3a7ea89
--- /dev/null
+++ b/src/libslic3r/Fill/FillConcentric.cpp
@@ -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
diff --git a/src/libslic3r/Fill/FillConcentric.hpp b/src/libslic3r/Fill/FillConcentric.hpp
new file mode 100644
index 000000000..1286858ea
--- /dev/null
+++ b/src/libslic3r/Fill/FillConcentric.hpp
@@ -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_
diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp
new file mode 100644
index 000000000..d6bf03ce6
--- /dev/null
+++ b/src/libslic3r/Fill/FillGyroid.cpp
@@ -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
diff --git a/src/libslic3r/Fill/FillGyroid.hpp b/src/libslic3r/Fill/FillGyroid.hpp
new file mode 100644
index 000000000..17924b5ab
--- /dev/null
+++ b/src/libslic3r/Fill/FillGyroid.hpp
@@ -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_
diff --git a/src/libslic3r/Fill/FillHoneycomb.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp
new file mode 100644
index 000000000..cbfe926f2
--- /dev/null
+++ b/src/libslic3r/Fill/FillHoneycomb.cpp
@@ -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
diff --git a/src/libslic3r/Fill/FillHoneycomb.hpp b/src/libslic3r/Fill/FillHoneycomb.hpp
new file mode 100644
index 000000000..4717602d4
--- /dev/null
+++ b/src/libslic3r/Fill/FillHoneycomb.hpp
@@ -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_
diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp
new file mode 100644
index 000000000..615cc6efe
--- /dev/null
+++ b/src/libslic3r/Fill/FillPlanePath.cpp
@@ -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
diff --git a/src/libslic3r/Fill/FillPlanePath.hpp b/src/libslic3r/Fill/FillPlanePath.hpp
new file mode 100644
index 000000000..49fb2b387
--- /dev/null
+++ b/src/libslic3r/Fill/FillPlanePath.hpp
@@ -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_
diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp
new file mode 100644
index 000000000..205eb1b66
--- /dev/null
+++ b/src/libslic3r/Fill/FillRectilinear.cpp
@@ -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
diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp
new file mode 100644
index 000000000..1be85f755
--- /dev/null
+++ b/src/libslic3r/Fill/FillRectilinear.hpp
@@ -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_
diff --git a/src/libslic3r/Fill/FillRectilinear2.cpp b/src/libslic3r/Fill/FillRectilinear2.cpp
new file mode 100644
index 000000000..65440d0ef
--- /dev/null
+++ b/src/libslic3r/Fill/FillRectilinear2.cpp
@@ -0,0 +1,1475 @@
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include <boost/static_assert.hpp>
+
+#include "../ClipperUtils.hpp"
+#include "../ExPolygon.hpp"
+#include "../Geometry.hpp"
+#include "../Surface.hpp"
+
+#include "FillRectilinear2.hpp"
+
+// #define SLIC3R_DEBUG
+
+// Make assert active if SLIC3R_DEBUG
+#ifdef SLIC3R_DEBUG
+ #undef NDEBUG
+ #include "SVG.hpp"
+#endif
+
+#include <cassert>
+
+// We want our version of assert.
+#include "../libslic3r.h"
+
+namespace Slic3r {
+
+// Having a segment of a closed polygon, calculate its Euclidian length.
+// The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop,
+// therefore the point p1 lies on poly.points[seg1-1], poly.points[seg1] etc.
+static inline coordf_t segment_length(const Polygon &poly, size_t seg1, const Point &p1, size_t seg2, const Point &p2)
+{
+#ifdef SLIC3R_DEBUG
+ // Verify that p1 lies on seg1. This is difficult to verify precisely,
+ // but at least verify, that p1 lies in the bounding box of seg1.
+ for (size_t i = 0; i < 2; ++ i) {
+ size_t seg = (i == 0) ? seg1 : seg2;
+ Point px = (i == 0) ? p1 : p2;
+ Point pa = poly.points[((seg == 0) ? poly.points.size() : seg) - 1];
+ Point pb = poly.points[seg];
+ if (pa(0) > pb(0))
+ std::swap(pa(0), pb(0));
+ if (pa(1) > pb(1))
+ std::swap(pa(1), pb(1));
+ assert(px(0) >= pa(0) && px(0) <= pb(0));
+ assert(px(1) >= pa(1) && px(1) <= pb(1));
+ }
+#endif /* SLIC3R_DEBUG */
+ const Point *pPrev = &p1;
+ const Point *pThis = NULL;
+ coordf_t len = 0;
+ if (seg1 <= seg2) {
+ for (size_t i = seg1; i < seg2; ++ i, pPrev = pThis)
+ len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm();
+ } else {
+ for (size_t i = seg1; i < poly.points.size(); ++ i, pPrev = pThis)
+ len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm();
+ for (size_t i = 0; i < seg2; ++ i, pPrev = pThis)
+ len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm();
+ }
+ len += (*pPrev - p2).cast<double>().norm();
+ return len;
+}
+
+// Append a segment of a closed polygon to a polyline.
+// The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop.
+// Only insert intermediate points between seg1 and seg2.
+static inline void polygon_segment_append(Points &out, const Polygon &polygon, size_t seg1, size_t seg2)
+{
+ if (seg1 == seg2) {
+ // Nothing to append from this segment.
+ } else if (seg1 < seg2) {
+ // Do not append a point pointed to by seg2.
+ out.insert(out.end(), polygon.points.begin() + seg1, polygon.points.begin() + seg2);
+ } else {
+ out.reserve(out.size() + seg2 + polygon.points.size() - seg1);
+ out.insert(out.end(), polygon.points.begin() + seg1, polygon.points.end());
+ // Do not append a point pointed to by seg2.
+ out.insert(out.end(), polygon.points.begin(), polygon.points.begin() + seg2);
+ }
+}
+
+// Append a segment of a closed polygon to a polyline.
+// The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop,
+// but this time the segment is traversed backward.
+// Only insert intermediate points between seg1 and seg2.
+static inline void polygon_segment_append_reversed(Points &out, const Polygon &polygon, size_t seg1, size_t seg2)
+{
+ if (seg1 >= seg2) {
+ out.reserve(seg1 - seg2);
+ for (size_t i = seg1; i > seg2; -- i)
+ out.push_back(polygon.points[i - 1]);
+ } else {
+ // it could be, that seg1 == seg2. In that case, append the complete loop.
+ out.reserve(out.size() + seg2 + polygon.points.size() - seg1);
+ for (size_t i = seg1; i > 0; -- i)
+ out.push_back(polygon.points[i - 1]);
+ for (size_t i = polygon.points.size(); i > seg2; -- i)
+ out.push_back(polygon.points[i - 1]);
+ }
+}
+
+// Intersection point of a vertical line with a polygon segment.
+class SegmentIntersection
+{
+public:
+ SegmentIntersection() :
+ iContour(0),
+ iSegment(0),
+ pos_p(0),
+ pos_q(1),
+ type(UNKNOWN),
+ consumed_vertical_up(false),
+ consumed_perimeter_right(false)
+ {}
+
+ // Index of a contour in ExPolygonWithOffset, with which this vertical line intersects.
+ size_t iContour;
+ // Index of a segment in iContour, with which this vertical line intersects.
+ size_t iSegment;
+ // y position of the intersection, ratinal number.
+ int64_t pos_p;
+ uint32_t pos_q;
+
+ coord_t pos() const {
+ // Division rounds both positive and negative down to zero.
+ // Add half of q for an arithmetic rounding effect.
+ int64_t p = pos_p;
+ if (p < 0)
+ p -= int64_t(pos_q>>1);
+ else
+ p += int64_t(pos_q>>1);
+ return coord_t(p / int64_t(pos_q));
+ }
+
+ // Kind of intersection. With the original contour, or with the inner offestted contour?
+ // A vertical segment will be at least intersected by OUTER_LOW, OUTER_HIGH,
+ // but it could be intersected with OUTER_LOW, INNER_LOW, INNER_HIGH, OUTER_HIGH,
+ // and there may be more than one pair of INNER_LOW, INNER_HIGH between OUTER_LOW, OUTER_HIGH.
+ enum SegmentIntersectionType {
+ OUTER_LOW = 0,
+ OUTER_HIGH = 1,
+ INNER_LOW = 2,
+ INNER_HIGH = 3,
+ UNKNOWN = -1
+ };
+ SegmentIntersectionType type;
+
+ // Was this segment along the y axis consumed?
+ // Up means up along the vertical segment.
+ bool consumed_vertical_up;
+ // Was a segment of the inner perimeter contour consumed?
+ // Right means right from the vertical segment.
+ bool consumed_perimeter_right;
+
+ // For the INNER_LOW type, this point may be connected to another INNER_LOW point following a perimeter contour.
+ // For the INNER_HIGH type, this point may be connected to another INNER_HIGH point following a perimeter contour.
+ // If INNER_LOW is connected to INNER_HIGH or vice versa,
+ // one has to make sure the vertical infill line does not overlap with the connecting perimeter line.
+ bool is_inner() const { return type == INNER_LOW || type == INNER_HIGH; }
+ bool is_outer() const { return type == OUTER_LOW || type == OUTER_HIGH; }
+ bool is_low () const { return type == INNER_LOW || type == OUTER_LOW; }
+ bool is_high () const { return type == INNER_HIGH || type == OUTER_HIGH; }
+
+ // Compare two y intersection points given by rational numbers.
+ // Note that the rational number is given as pos_p/pos_q, where pos_p is int64 and pos_q is uint32.
+ // This function calculates pos_p * other.pos_q < other.pos_p * pos_q as a 48bit number.
+ // We don't use 128bit intrinsic data types as these are usually not supported by 32bit compilers and
+ // we don't need the full 128bit precision anyway.
+ bool operator<(const SegmentIntersection &other) const
+ {
+ assert(pos_q > 0);
+ assert(other.pos_q > 0);
+ if (pos_p == 0 || other.pos_p == 0) {
+ // Because the denominators are positive and one of the nominators is zero,
+ // following simple statement holds.
+ return pos_p < other.pos_p;
+ } else {
+ // None of the nominators is zero.
+ int sign1 = (pos_p > 0) ? 1 : -1;
+ int sign2 = (other.pos_p > 0) ? 1 : -1;
+ int signs = sign1 * sign2;
+ assert(signs == 1 || signs == -1);
+ if (signs < 0) {
+ // The nominators have different signs.
+ return sign1 < 0;
+ } else {
+ // The nominators have the same sign.
+ // Absolute values
+ uint64_t p1, p2;
+ if (sign1 > 0) {
+ p1 = uint64_t(pos_p);
+ p2 = uint64_t(other.pos_p);
+ } else {
+ p1 = uint64_t(- pos_p);
+ p2 = uint64_t(- other.pos_p);
+ };
+ // Multiply low and high 32bit words of p1 by other_pos.q
+ // 32bit x 32bit => 64bit
+ // l_hi and l_lo overlap by 32 bits.
+ uint64_t l_hi = (p1 >> 32) * uint64_t(other.pos_q);
+ uint64_t l_lo = (p1 & 0xffffffffll) * uint64_t(other.pos_q);
+ l_hi += (l_lo >> 32);
+ uint64_t r_hi = (p2 >> 32) * uint64_t(pos_q);
+ uint64_t r_lo = (p2 & 0xffffffffll) * uint64_t(pos_q);
+ r_hi += (r_lo >> 32);
+ // Compare the high 64 bits.
+ if (l_hi == r_hi) {
+ // Compare the low 32 bits.
+ l_lo &= 0xffffffffll;
+ r_lo &= 0xffffffffll;
+ return (sign1 < 0) ? (l_lo > r_lo) : (l_lo < r_lo);
+ }
+ return (sign1 < 0) ? (l_hi > r_hi) : (l_hi < r_hi);
+ }
+ }
+ }
+
+ bool operator==(const SegmentIntersection &other) const
+ {
+ assert(pos_q > 0);
+ assert(other.pos_q > 0);
+ if (pos_p == 0 || other.pos_p == 0) {
+ // Because the denominators are positive and one of the nominators is zero,
+ // following simple statement holds.
+ return pos_p == other.pos_p;
+ }
+
+ // None of the nominators is zero, none of the denominators is zero.
+ bool positive = pos_p > 0;
+ if (positive != (other.pos_p > 0))
+ return false;
+ // The nominators have the same sign.
+ // Absolute values
+ uint64_t p1 = positive ? uint64_t(pos_p) : uint64_t(- pos_p);
+ uint64_t p2 = positive ? uint64_t(other.pos_p) : uint64_t(- other.pos_p);
+ // Multiply low and high 32bit words of p1 by other_pos.q
+ // 32bit x 32bit => 64bit
+ // l_hi and l_lo overlap by 32 bits.
+ uint64_t l_lo = (p1 & 0xffffffffll) * uint64_t(other.pos_q);
+ uint64_t r_lo = (p2 & 0xffffffffll) * uint64_t(pos_q);
+ if (l_lo != r_lo)
+ return false;
+ uint64_t l_hi = (p1 >> 32) * uint64_t(other.pos_q);
+ uint64_t r_hi = (p2 >> 32) * uint64_t(pos_q);
+ return l_hi + (l_lo >> 32) == r_hi + (r_lo >> 32);
+ }
+};
+
+// A vertical line with intersection points with polygons.
+class SegmentedIntersectionLine
+{
+public:
+ // Index of this vertical intersection line.
+ size_t idx;
+ // x position of this vertical intersection line.
+ coord_t pos;
+ // List of intersection points with polygons, sorted increasingly by the y axis.
+ std::vector<SegmentIntersection> intersections;
+};
+
+// A container maintaining an expolygon with its inner offsetted polygon.
+// The purpose of the inner offsetted polygon is to provide segments to connect the infill lines.
+struct ExPolygonWithOffset
+{
+public:
+ ExPolygonWithOffset(
+ const ExPolygon &expolygon,
+ float angle,
+ coord_t aoffset1,
+ coord_t aoffset2)
+ {
+ // Copy and rotate the source polygons.
+ polygons_src = expolygon;
+ polygons_src.contour.rotate(angle);
+ for (Polygons::iterator it = polygons_src.holes.begin(); it != polygons_src.holes.end(); ++ it)
+ it->rotate(angle);
+
+ double mitterLimit = 3.;
+ // for the infill pattern, don't cut the corners.
+ // default miterLimt = 3
+ //double mitterLimit = 10.;
+ assert(aoffset1 < 0);
+ assert(aoffset2 < 0);
+ assert(aoffset2 < aoffset1);
+ bool sticks_removed = remove_sticks(polygons_src);
+// if (sticks_removed) printf("Sticks removed!\n");
+ polygons_outer = offset(polygons_src, aoffset1,
+ ClipperLib::jtMiter,
+ mitterLimit);
+ polygons_inner = offset(polygons_outer, aoffset2 - aoffset1,
+ ClipperLib::jtMiter,
+ mitterLimit);
+ // Filter out contours with zero area or small area, contours with 2 points only.
+ const double min_area_threshold = 0.01 * aoffset2 * aoffset2;
+ remove_small(polygons_outer, min_area_threshold);
+ remove_small(polygons_inner, min_area_threshold);
+ remove_sticks(polygons_outer);
+ remove_sticks(polygons_inner);
+ n_contours_outer = polygons_outer.size();
+ n_contours_inner = polygons_inner.size();
+ n_contours = n_contours_outer + n_contours_inner;
+ polygons_ccw.assign(n_contours, false);
+ for (size_t i = 0; i < n_contours; ++ i) {
+ contour(i).remove_duplicate_points();
+ assert(! contour(i).has_duplicate_points());
+ polygons_ccw[i] = Slic3r::Geometry::is_ccw(contour(i));
+ }
+ }
+
+ // Any contour with offset1
+ bool is_contour_outer(size_t idx) const { return idx < n_contours_outer; }
+ // Any contour with offset2
+ bool is_contour_inner(size_t idx) const { return idx >= n_contours_outer; }
+
+ const Polygon& contour(size_t idx) const
+ { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; }
+
+ Polygon& contour(size_t idx)
+ { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; }
+
+ bool is_contour_ccw(size_t idx) const { return polygons_ccw[idx]; }
+
+ BoundingBox bounding_box_src() const
+ { return get_extents(polygons_src); }
+ BoundingBox bounding_box_outer() const
+ { return get_extents(polygons_outer); }
+ BoundingBox bounding_box_inner() const
+ { return get_extents(polygons_inner); }
+
+#ifdef SLIC3R_DEBUG
+ void export_to_svg(Slic3r::SVG &svg) {
+ svg.draw_outline(polygons_src, "black");
+ svg.draw_outline(polygons_outer, "green");
+ svg.draw_outline(polygons_inner, "brown");
+ }
+#endif /* SLIC3R_DEBUG */
+
+ ExPolygon polygons_src;
+ Polygons polygons_outer;
+ Polygons polygons_inner;
+
+ size_t n_contours_outer;
+ size_t n_contours_inner;
+ size_t n_contours;
+
+protected:
+ // For each polygon of polygons_inner, remember its orientation.
+ std::vector<unsigned char> polygons_ccw;
+};
+
+static inline int distance_of_segmens(const Polygon &poly, size_t seg1, size_t seg2, bool forward)
+{
+ int d = int(seg2) - int(seg1);
+ if (! forward)
+ d = - d;
+ if (d < 0)
+ d += int(poly.points.size());
+ return d;
+}
+
+// For a vertical line, an inner contour and an intersection point,
+// find an intersection point on the previous resp. next vertical line.
+// The intersection point is connected with the prev resp. next intersection point with iInnerContour.
+// Return -1 if there is no such point on the previous resp. next vertical line.
+static inline int intersection_on_prev_next_vertical_line(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ bool dir_is_next)
+{
+ size_t iVerticalLineOther = iVerticalLine;
+ if (dir_is_next) {
+ if (++ iVerticalLineOther == segs.size())
+ // No successive vertical line.
+ return -1;
+ } else if (iVerticalLineOther -- == 0) {
+ // No preceding vertical line.
+ return -1;
+ }
+
+ const SegmentedIntersectionLine &il = segs[iVerticalLine];
+ const SegmentIntersection &itsct = il.intersections[iIntersection];
+ const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther];
+ const Polygon &poly = poly_with_offset.contour(iInnerContour);
+// const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour);
+ const bool forward = itsct.is_low() == dir_is_next;
+ // Resulting index of an intersection point on il2.
+ int out = -1;
+ // Find an intersection point on iVerticalLineOther, intersecting iInnerContour
+ // at the same orientation as iIntersection, and being closest to iIntersection
+ // in the number of contour segments, when following the direction of the contour.
+ int dmin = std::numeric_limits<int>::max();
+ for (size_t i = 0; i < il2.intersections.size(); ++ i) {
+ const SegmentIntersection &itsct2 = il2.intersections[i];
+ if (itsct.iContour == itsct2.iContour && itsct.type == itsct2.type) {
+ /*
+ if (itsct.is_low()) {
+ assert(itsct.type == SegmentIntersection::INNER_LOW);
+ assert(iIntersection > 0);
+ assert(il.intersections[iIntersection-1].type == SegmentIntersection::OUTER_LOW);
+ assert(i > 0);
+ if (il2.intersections[i-1].is_inner())
+ // Take only the lowest inner intersection point.
+ continue;
+ assert(il2.intersections[i-1].type == SegmentIntersection::OUTER_LOW);
+ } else {
+ assert(itsct.type == SegmentIntersection::INNER_HIGH);
+ assert(iIntersection+1 < il.intersections.size());
+ assert(il.intersections[iIntersection+1].type == SegmentIntersection::OUTER_HIGH);
+ assert(i+1 < il2.intersections.size());
+ if (il2.intersections[i+1].is_inner())
+ // Take only the highest inner intersection point.
+ continue;
+ assert(il2.intersections[i+1].type == SegmentIntersection::OUTER_HIGH);
+ }
+ */
+ // The intersection points lie on the same contour and have the same orientation.
+ // Find the intersection point with a shortest path in the direction of the contour.
+ int d = distance_of_segmens(poly, itsct.iSegment, itsct2.iSegment, forward);
+ if (d < dmin) {
+ out = i;
+ dmin = d;
+ }
+ }
+ }
+ //FIXME this routine is not asymptotic optimal, it will be slow if there are many intersection points along the line.
+ return out;
+}
+
+static inline int intersection_on_prev_vertical_line(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection)
+{
+ return intersection_on_prev_next_vertical_line(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, false);
+}
+
+static inline int intersection_on_next_vertical_line(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection)
+{
+ return intersection_on_prev_next_vertical_line(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, true);
+}
+
+enum IntersectionTypeOtherVLine {
+ // There is no connection point on the other vertical line.
+ INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED = -1,
+ // Connection point on the other vertical segment was found
+ // and it could be followed.
+ INTERSECTION_TYPE_OTHER_VLINE_OK = 0,
+ // The connection segment connects to a middle of a vertical segment.
+ // Cannot follow.
+ INTERSECTION_TYPE_OTHER_VLINE_INNER,
+ // Cannot extend the contor to this intersection point as either the connection segment
+ // or the succeeding vertical segment were already consumed.
+ INTERSECTION_TYPE_OTHER_VLINE_CONSUMED,
+ // Not the first intersection along the contor. This intersection point
+ // has been preceded by an intersection point along the vertical line.
+ INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST,
+};
+
+// Find an intersection on a previous line, but return -1, if the connecting segment of a perimeter was already extruded.
+static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical_line(
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iIntersection,
+ size_t iIntersectionOther,
+ bool dir_is_next)
+{
+ // This routine will propose a connecting line even if the connecting perimeter segment intersects
+ // iVertical line multiple times before reaching iIntersectionOther.
+ if (iIntersectionOther == -1)
+ return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED;
+ assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0));
+ const SegmentedIntersectionLine &il_this = segs[iVerticalLine];
+ const SegmentIntersection &itsct_this = il_this.intersections[iIntersection];
+ const SegmentedIntersectionLine &il_other = segs[dir_is_next ? (iVerticalLine+1) : (iVerticalLine-1)];
+ const SegmentIntersection &itsct_other = il_other.intersections[iIntersectionOther];
+ assert(itsct_other.is_inner());
+ assert(iIntersectionOther > 0);
+ assert(iIntersectionOther + 1 < il_other.intersections.size());
+ // Is iIntersectionOther at the boundary of a vertical segment?
+ const SegmentIntersection &itsct_other2 = il_other.intersections[itsct_other.is_low() ? iIntersectionOther - 1 : iIntersectionOther + 1];
+ if (itsct_other2.is_inner())
+ // Cannot follow a perimeter segment into the middle of another vertical segment.
+ // Only perimeter segments connecting to the end of a vertical segment are followed.
+ return INTERSECTION_TYPE_OTHER_VLINE_INNER;
+ assert(itsct_other.is_low() == itsct_other2.is_low());
+ if (dir_is_next ? itsct_this.consumed_perimeter_right : itsct_other.consumed_perimeter_right)
+ // This perimeter segment was already consumed.
+ return INTERSECTION_TYPE_OTHER_VLINE_CONSUMED;
+ if (itsct_other.is_low() ? itsct_other.consumed_vertical_up : il_other.intersections[iIntersectionOther-1].consumed_vertical_up)
+ // This vertical segment was already consumed.
+ return INTERSECTION_TYPE_OTHER_VLINE_CONSUMED;
+ return INTERSECTION_TYPE_OTHER_VLINE_OK;
+}
+
+static inline IntersectionTypeOtherVLine intersection_type_on_prev_vertical_line(
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iIntersection,
+ size_t iIntersectionPrev)
+{
+ return intersection_type_on_prev_next_vertical_line(segs, iVerticalLine, iIntersection, iIntersectionPrev, false);
+}
+
+static inline IntersectionTypeOtherVLine intersection_type_on_next_vertical_line(
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iIntersection,
+ size_t iIntersectionNext)
+{
+ return intersection_type_on_prev_next_vertical_line(segs, iVerticalLine, iIntersection, iIntersectionNext, true);
+}
+
+// Measure an Euclidian length of a perimeter segment when going from iIntersection to iIntersection2.
+static inline coordf_t measure_perimeter_prev_next_segment_length(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2,
+ bool dir_is_next)
+{
+ size_t iVerticalLineOther = iVerticalLine;
+ if (dir_is_next) {
+ if (++ iVerticalLineOther == segs.size())
+ // No successive vertical line.
+ return coordf_t(-1);
+ } else if (iVerticalLineOther -- == 0) {
+ // No preceding vertical line.
+ return coordf_t(-1);
+ }
+
+ const SegmentedIntersectionLine &il = segs[iVerticalLine];
+ const SegmentIntersection &itsct = il.intersections[iIntersection];
+ const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther];
+ const SegmentIntersection &itsct2 = il2.intersections[iIntersection2];
+ const Polygon &poly = poly_with_offset.contour(iInnerContour);
+// const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour);
+ assert(itsct.type == itsct2.type);
+ assert(itsct.iContour == itsct2.iContour);
+ assert(itsct.is_inner());
+ const bool forward = itsct.is_low() == dir_is_next;
+
+ Point p1(il.pos, itsct.pos());
+ Point p2(il2.pos, itsct2.pos());
+ return forward ?
+ segment_length(poly, itsct .iSegment, p1, itsct2.iSegment, p2) :
+ segment_length(poly, itsct2.iSegment, p2, itsct .iSegment, p1);
+}
+
+static inline coordf_t measure_perimeter_prev_segment_length(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2)
+{
+ return measure_perimeter_prev_next_segment_length(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, iIntersection2, false);
+}
+
+static inline coordf_t measure_perimeter_next_segment_length(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2)
+{
+ return measure_perimeter_prev_next_segment_length(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, iIntersection2, true);
+}
+
+// Append the points of a perimeter segment when going from iIntersection to iIntersection2.
+// The first point (the point of iIntersection) will not be inserted,
+// the last point will be inserted.
+static inline void emit_perimeter_prev_next_segment(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2,
+ Polyline &out,
+ bool dir_is_next)
+{
+ size_t iVerticalLineOther = iVerticalLine;
+ if (dir_is_next) {
+ ++ iVerticalLineOther;
+ assert(iVerticalLineOther < segs.size());
+ } else {
+ assert(iVerticalLineOther > 0);
+ -- iVerticalLineOther;
+ }
+
+ const SegmentedIntersectionLine &il = segs[iVerticalLine];
+ const SegmentIntersection &itsct = il.intersections[iIntersection];
+ const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther];
+ const SegmentIntersection &itsct2 = il2.intersections[iIntersection2];
+ const Polygon &poly = poly_with_offset.contour(iInnerContour);
+// const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour);
+ assert(itsct.type == itsct2.type);
+ assert(itsct.iContour == itsct2.iContour);
+ assert(itsct.is_inner());
+ const bool forward = itsct.is_low() == dir_is_next;
+ // Do not append the first point.
+ // out.points.push_back(Point(il.pos, itsct.pos));
+ if (forward)
+ polygon_segment_append(out.points, poly, itsct.iSegment, itsct2.iSegment);
+ else
+ polygon_segment_append_reversed(out.points, poly, itsct.iSegment, itsct2.iSegment);
+ // Append the last point.
+ out.points.push_back(Point(il2.pos, itsct2.pos()));
+}
+
+static inline coordf_t measure_perimeter_segment_on_vertical_line_length(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2,
+ bool forward)
+{
+ const SegmentedIntersectionLine &il = segs[iVerticalLine];
+ const SegmentIntersection &itsct = il.intersections[iIntersection];
+ const SegmentIntersection &itsct2 = il.intersections[iIntersection2];
+ const Polygon &poly = poly_with_offset.contour(iInnerContour);
+ assert(itsct.is_inner());
+ assert(itsct2.is_inner());
+ assert(itsct.type != itsct2.type);
+ assert(itsct.iContour == iInnerContour);
+ assert(itsct.iContour == itsct2.iContour);
+ Point p1(il.pos, itsct.pos());
+ Point p2(il.pos, itsct2.pos());
+ return forward ?
+ segment_length(poly, itsct .iSegment, p1, itsct2.iSegment, p2) :
+ segment_length(poly, itsct2.iSegment, p2, itsct .iSegment, p1);
+}
+
+// Append the points of a perimeter segment when going from iIntersection to iIntersection2.
+// The first point (the point of iIntersection) will not be inserted,
+// the last point will be inserted.
+static inline void emit_perimeter_segment_on_vertical_line(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2,
+ Polyline &out,
+ bool forward)
+{
+ const SegmentedIntersectionLine &il = segs[iVerticalLine];
+ const SegmentIntersection &itsct = il.intersections[iIntersection];
+ const SegmentIntersection &itsct2 = il.intersections[iIntersection2];
+ const Polygon &poly = poly_with_offset.contour(iInnerContour);
+ assert(itsct.is_inner());
+ assert(itsct2.is_inner());
+ assert(itsct.type != itsct2.type);
+ assert(itsct.iContour == iInnerContour);
+ assert(itsct.iContour == itsct2.iContour);
+ // Do not append the first point.
+ // out.points.push_back(Point(il.pos, itsct.pos));
+ if (forward)
+ polygon_segment_append(out.points, poly, itsct.iSegment, itsct2.iSegment);
+ else
+ polygon_segment_append_reversed(out.points, poly, itsct.iSegment, itsct2.iSegment);
+ // Append the last point.
+ out.points.push_back(Point(il.pos, itsct2.pos()));
+}
+
+//TBD: For precise infill, measure the area of a slab spanned by an infill line.
+/*
+static inline float measure_outer_contour_slab(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t i_vline,
+ size_t iIntersection)
+{
+ const SegmentedIntersectionLine &il = segs[i_vline];
+ const SegmentIntersection &itsct = il.intersections[i_vline];
+ const SegmentIntersection &itsct2 = il.intersections[iIntersection2];
+ const Polygon &poly = poly_with_offset.contour((itsct.iContour);
+ assert(itsct.is_outer());
+ assert(itsct2.is_outer());
+ assert(itsct.type != itsct2.type);
+ assert(itsct.iContour == itsct2.iContour);
+ if (! itsct.is_outer() || ! itsct2.is_outer() || itsct.type == itsct2.type || itsct.iContour != itsct2.iContour)
+ // Error, return zero area.
+ return 0.f;
+
+ // Find possible connection points on the previous / next vertical line.
+ int iPrev = intersection_on_prev_vertical_line(poly_with_offset, segs, i_vline, itsct.iContour, i_intersection);
+ int iNext = intersection_on_next_vertical_line(poly_with_offset, segs, i_vline, itsct.iContour, i_intersection);
+ // Find possible connection points on the same vertical line.
+ int iAbove = iBelow = -1;
+ // Does the perimeter intersect the current vertical line above intrsctn?
+ for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i)
+ if (seg.intersections[i].iContour == itsct.iContour)
+ { iAbove = i; break; }
+ // Does the perimeter intersect the current vertical line below intrsctn?
+ for (int i = int(i_intersection) - 1; i > 0; -- i)
+ if (seg.intersections[i].iContour == itsct.iContour)
+ { iBelow = i; break; }
+
+ if (iSegAbove != -1 && seg.intersections[iAbove].type == SegmentIntersection::OUTER_HIGH) {
+ // Invalidate iPrev resp. iNext, if the perimeter crosses the current vertical line earlier than iPrev resp. iNext.
+ // The perimeter contour orientation.
+ const Polygon &poly = poly_with_offset.contour(itsct.iContour);
+ {
+ int d_horiz = (iPrev == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, segs[i_vline-1].intersections[iPrev].iSegment, itsct.iSegment, true);
+ int d_down = (iBelow == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, iSegBelow, itsct.iSegment, true);
+ int d_up = (iAbove == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, iSegAbove, itsct.iSegment, true);
+ if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up))
+ // The vertical crossing comes eralier than the prev crossing.
+ // Disable the perimeter going back.
+ intrsctn_type_prev = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST;
+ if (d_up > std::min(d_horiz, d_down))
+ // The horizontal crossing comes earlier than the vertical crossing.
+ vert_seg_dir_valid_mask &= ~DIR_BACKWARD;
+ }
+ {
+ int d_horiz = (iNext == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, itsct.iSegment, segs[i_vline+1].intersections[iNext].iSegment, true);
+ int d_down = (iSegBelow == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, itsct.iSegment, iSegBelow, true);
+ int d_up = (iSegAbove == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, itsct.iSegment, iSegAbove, true);
+ if (d_up > std::min(d_horiz, d_down))
+ // The horizontal crossing comes earlier than the vertical crossing.
+ vert_seg_dir_valid_mask &= ~DIR_FORWARD;
+ }
+ }
+}
+*/
+
+enum DirectionMask
+{
+ DIR_FORWARD = 1,
+ DIR_BACKWARD = 2
+};
+
+bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillParams &params, float angleBase, float pattern_shift, Polylines &polylines_out)
+{
+ // At the end, only the new polylines will be rotated back.
+ size_t n_polylines_out_initial = polylines_out.size();
+
+ // Shrink the input polygon a bit first to not push the infill lines out of the perimeters.
+// const float INFILL_OVERLAP_OVER_SPACING = 0.3f;
+ const float INFILL_OVERLAP_OVER_SPACING = 0.45f;
+ assert(INFILL_OVERLAP_OVER_SPACING > 0 && INFILL_OVERLAP_OVER_SPACING < 0.5f);
+
+ // Rotate polygons so that we can work with vertical lines here
+ std::pair<float, Point> rotate_vector = this->_infill_direction(surface);
+ rotate_vector.first += angleBase;
+
+ assert(params.density > 0.0001f && params.density <= 1.f);
+ coord_t line_spacing = coord_t(scale_(this->spacing) / params.density);
+
+ // On the polygons of poly_with_offset, the infill lines will be connected.
+ ExPolygonWithOffset poly_with_offset(
+ surface->expolygon,
+ - rotate_vector.first,
+ scale_(this->overlap - (0.5 - INFILL_OVERLAP_OVER_SPACING) * this->spacing),
+ scale_(this->overlap - 0.5 * this->spacing));
+ if (poly_with_offset.n_contours_inner == 0) {
+ // Not a single infill line fits.
+ //FIXME maybe one shall trigger the gap fill here?
+ return true;
+ }
+
+ BoundingBox bounding_box = poly_with_offset.bounding_box_src();
+
+ // define flow spacing according to requested density
+ if (params.full_infill() && !params.dont_adjust) {
+ line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), line_spacing);
+ this->spacing = unscale<double>(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.
+ Point refpt = rotate_vector.second.rotated(- rotate_vector.first);
+ // _align_to_grid will not work correctly with positive pattern_shift.
+ coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing;
+ refpt(0) -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled);
+ bounding_box.merge(_align_to_grid(
+ bounding_box.min,
+ Point(line_spacing, line_spacing),
+ refpt));
+ }
+
+ // Intersect a set of euqally spaced vertical lines wiht expolygon.
+ // n_vlines = ceil(bbox_width / line_spacing)
+ size_t n_vlines = (bounding_box.max(0) - bounding_box.min(0) + line_spacing - 1) / line_spacing;
+ coord_t x0 = bounding_box.min(0);
+ if (params.full_infill())
+ x0 += (line_spacing + SCALED_EPSILON) / 2;
+
+#ifdef SLIC3R_DEBUG
+ static int iRun = 0;
+ BoundingBox bbox_svg = poly_with_offset.bounding_box_outer();
+ ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-%d.svg", iRun), bbox_svg); // , scale_(1.));
+ poly_with_offset.export_to_svg(svg);
+ {
+ ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-initial-%d.svg", iRun), bbox_svg); // , scale_(1.));
+ poly_with_offset.export_to_svg(svg);
+ }
+ iRun ++;
+#endif /* SLIC3R_DEBUG */
+
+ // For each contour
+ // Allocate storage for the segments.
+ std::vector<SegmentedIntersectionLine> segs(n_vlines, SegmentedIntersectionLine());
+ for (size_t i = 0; i < n_vlines; ++ i) {
+ segs[i].idx = i;
+ segs[i].pos = x0 + i * line_spacing;
+ }
+ for (size_t iContour = 0; iContour < poly_with_offset.n_contours; ++ iContour) {
+ const Points &contour = poly_with_offset.contour(iContour).points;
+ if (contour.size() < 2)
+ continue;
+ // For each segment
+ for (size_t iSegment = 0; iSegment < contour.size(); ++ iSegment) {
+ size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1;
+ const Point &p1 = contour[iPrev];
+ const Point &p2 = contour[iSegment];
+ // Which of the equally spaced vertical lines is intersected by this segment?
+ coord_t l = p1(0);
+ coord_t r = p2(0);
+ if (l > r)
+ std::swap(l, r);
+ // il, ir are the left / right indices of vertical lines intersecting a segment
+ int il = (l - x0) / line_spacing;
+ while (il * line_spacing + x0 < l)
+ ++ il;
+ il = std::max(int(0), il);
+ int ir = (r - x0 + line_spacing) / line_spacing;
+ while (ir * line_spacing + x0 > r)
+ -- ir;
+ ir = std::min(int(segs.size()) - 1, ir);
+ if (il > ir)
+ // No vertical line intersects this segment.
+ continue;
+ assert(il >= 0 && il < segs.size());
+ assert(ir >= 0 && ir < segs.size());
+ for (int i = il; i <= ir; ++ i) {
+ coord_t this_x = segs[i].pos;
+ assert(this_x == i * line_spacing + x0);
+ SegmentIntersection is;
+ is.iContour = iContour;
+ is.iSegment = iSegment;
+ assert(l <= this_x);
+ assert(r >= this_x);
+ // Calculate the intersection position in y axis. x is known.
+ if (p1(0) == this_x) {
+ if (p2(0) == this_x) {
+ // Ignore strictly vertical segments.
+ continue;
+ }
+ is.pos_p = p1(1);
+ is.pos_q = 1;
+ } else if (p2(0) == this_x) {
+ is.pos_p = p2(1);
+ is.pos_q = 1;
+ } else {
+ // First calculate the intersection parameter 't' as a rational number with non negative denominator.
+ if (p2(0) > p1(0)) {
+ is.pos_p = this_x - p1(0);
+ is.pos_q = p2(0) - p1(0);
+ } else {
+ is.pos_p = p1(0) - this_x;
+ is.pos_q = p1(0) - p2(0);
+ }
+ assert(is.pos_p >= 0 && is.pos_p <= is.pos_q);
+ // Make an intersection point from the 't'.
+ is.pos_p *= int64_t(p2(1) - p1(1));
+ is.pos_p += p1(1) * int64_t(is.pos_q);
+ }
+ // +-1 to take rounding into account.
+ assert(is.pos() + 1 >= std::min(p1(1), p2(1)));
+ assert(is.pos() <= std::max(p1(1), p2(1)) + 1);
+ segs[i].intersections.push_back(is);
+ }
+ }
+ }
+
+ // Sort the intersections along their segments, specify the intersection types.
+ for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) {
+ SegmentedIntersectionLine &sil = segs[i_seg];
+ // Sort the intersection points using exact rational arithmetic.
+ std::sort(sil.intersections.begin(), sil.intersections.end());
+ // Assign the intersection types, remove duplicate or overlapping intersection points.
+ // When a loop vertex touches a vertical line, intersection point is generated for both segments.
+ // If such two segments are oriented equally, then one of them is removed.
+ // Otherwise the vertex is tangential to the vertical line and both segments are removed.
+ // The same rule applies, if the loop is pinched into a single point and this point touches the vertical line:
+ // The loop has a zero vertical size at the vertical line, therefore the intersection point is removed.
+ size_t j = 0;
+ for (size_t i = 0; i < sil.intersections.size(); ++ i) {
+ // What is the orientation of the segment at the intersection point?
+ size_t iContour = sil.intersections[i].iContour;
+ const Points &contour = poly_with_offset.contour(iContour).points;
+ size_t iSegment = sil.intersections[i].iSegment;
+ size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1;
+ coord_t dir = contour[iSegment](0) - contour[iPrev](0);
+ bool low = dir > 0;
+ sil.intersections[i].type = poly_with_offset.is_contour_outer(iContour) ?
+ (low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) :
+ (low ? SegmentIntersection::INNER_LOW : SegmentIntersection::INNER_HIGH);
+ if (j > 0 && sil.intersections[i].iContour == sil.intersections[j-1].iContour) {
+ // Two successive intersection points on a vertical line with the same contour. This may be a special case.
+ if (sil.intersections[i].pos() == sil.intersections[j-1].pos()) {
+ // Two successive segments meet exactly at the vertical line.
+ #ifdef SLIC3R_DEBUG
+ // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint.
+ size_t iSegment2 = sil.intersections[j-1].iSegment;
+ size_t iPrev2 = ((iSegment2 == 0) ? contour.size() : iSegment2) - 1;
+ assert(iSegment == iPrev2 || iSegment2 == iPrev);
+ #endif /* SLIC3R_DEBUG */
+ if (sil.intersections[i].type == sil.intersections[j-1].type) {
+ // Two successive segments of the same direction (both to the right or both to the left)
+ // meet exactly at the vertical line.
+ // Remove the second intersection point.
+ } else {
+ // This is a loop returning to the same point.
+ // It may as well be a vertex of a loop touching this vertical line.
+ // Remove both the lines.
+ -- j;
+ }
+ } else if (sil.intersections[i].type == sil.intersections[j-1].type) {
+ // Two non successive segments of the same direction (both to the right or both to the left)
+ // meet exactly at the vertical line. That means there is a Z shaped path, where the center segment
+ // of the Z shaped path is aligned with this vertical line.
+ // Remove one of the intersection points while maximizing the vertical segment length.
+ if (low) {
+ // Remove the second intersection point, keep the first intersection point.
+ } else {
+ // Remove the first intersection point, keep the second intersection point.
+ sil.intersections[j-1] = sil.intersections[i];
+ }
+ } else {
+ // Vertical line intersects a contour segment at a general position (not at one of its end points).
+ // or the contour just touches this vertical line with a vertical segment or a sequence of vertical segments.
+ // Keep both intersection points.
+ if (j < i)
+ sil.intersections[j] = sil.intersections[i];
+ ++ j;
+ }
+ } else {
+ // Vertical line intersects a contour segment at a general position (not at one of its end points).
+ if (j < i)
+ sil.intersections[j] = sil.intersections[i];
+ ++ j;
+ }
+ }
+ // Shrink the list of intersections, if any of the intersection was removed during the classification.
+ if (j < sil.intersections.size())
+ sil.intersections.erase(sil.intersections.begin() + j, sil.intersections.end());
+ }
+
+ // Verify the segments. If something is wrong, give up.
+#define ASSERT_OR_RETURN(CONDITION) do { assert(CONDITION); if (! (CONDITION)) return false; } while (0)
+ for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) {
+ SegmentedIntersectionLine &sil = segs[i_seg];
+ // The intersection points have to be even.
+ ASSERT_OR_RETURN((sil.intersections.size() & 1) == 0);
+ for (size_t i = 0; i < sil.intersections.size();) {
+ // An intersection segment crossing the bigger contour may cross the inner offsetted contour even number of times.
+ ASSERT_OR_RETURN(sil.intersections[i].type == SegmentIntersection::OUTER_LOW);
+ size_t j = i + 1;
+ ASSERT_OR_RETURN(j < sil.intersections.size());
+ ASSERT_OR_RETURN(sil.intersections[j].type == SegmentIntersection::INNER_LOW || sil.intersections[j].type == SegmentIntersection::OUTER_HIGH);
+ for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++ j) ;
+ ASSERT_OR_RETURN(j < sil.intersections.size());
+ ASSERT_OR_RETURN((j & 1) == 1);
+ ASSERT_OR_RETURN(sil.intersections[j].type == SegmentIntersection::OUTER_HIGH);
+ ASSERT_OR_RETURN(i + 1 == j || sil.intersections[j - 1].type == SegmentIntersection::INNER_HIGH);
+ i = j + 1;
+ }
+ }
+#undef ASSERT_OR_RETURN
+
+#ifdef SLIC3R_DEBUG
+ // Paint the segments and finalize the SVG file.
+ for (size_t i_seg = 0; i_seg < segs.size(); ++ i_seg) {
+ SegmentedIntersectionLine &sil = segs[i_seg];
+ for (size_t i = 0; i < sil.intersections.size();) {
+ size_t j = i + 1;
+ for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++ j) ;
+ if (i + 1 == j) {
+ svg.draw(Line(Point(sil.pos, sil.intersections[i].pos()), Point(sil.pos, sil.intersections[j].pos())), "blue");
+ } else {
+ svg.draw(Line(Point(sil.pos, sil.intersections[i].pos()), Point(sil.pos, sil.intersections[i+1].pos())), "green");
+ svg.draw(Line(Point(sil.pos, sil.intersections[i+1].pos()), Point(sil.pos, sil.intersections[j-1].pos())), (j - i + 1 > 4) ? "yellow" : "magenta");
+ svg.draw(Line(Point(sil.pos, sil.intersections[j-1].pos()), Point(sil.pos, sil.intersections[j].pos())), "green");
+ }
+ i = j + 1;
+ }
+ }
+ svg.Close();
+#endif /* SLIC3R_DEBUG */
+
+ // For each outer only chords, measure their maximum distance to the bow of the outer contour.
+ // Mark an outer only chord as consumed, if the distance is low.
+ for (size_t i_vline = 0; i_vline < segs.size(); ++ i_vline) {
+ SegmentedIntersectionLine &seg = segs[i_vline];
+ for (size_t i_intersection = 0; i_intersection + 1 < seg.intersections.size(); ++ i_intersection) {
+ if (seg.intersections[i_intersection].type == SegmentIntersection::OUTER_LOW &&
+ seg.intersections[i_intersection+1].type == SegmentIntersection::OUTER_HIGH) {
+ bool consumed = false;
+// if (params.full_infill()) {
+// measure_outer_contour_slab(poly_with_offset, segs, i_vline, i_ntersection);
+// } else
+ consumed = true;
+ seg.intersections[i_intersection].consumed_vertical_up = consumed;
+ }
+ }
+ }
+
+ // Now construct a graph.
+ // Find the first point.
+ // Naively one would expect to achieve best results by chaining the paths by the shortest distance,
+ // but that procedure does not create the longest continuous paths.
+ // A simple "sweep left to right" procedure achieves better results.
+ size_t i_vline = 0;
+ size_t i_intersection = size_t(-1);
+ // Follow the line, connect the lines into a graph.
+ // Until no new line could be added to the output path:
+ Point pointLast;
+ Polyline *polyline_current = NULL;
+ if (! polylines_out.empty())
+ pointLast = polylines_out.back().points.back();
+ for (;;) {
+ if (i_intersection == size_t(-1)) {
+ // The path has been interrupted. Find a next starting point, closest to the previous extruder position.
+ coordf_t dist2min = std::numeric_limits<coordf_t>().max();
+ for (size_t i_vline2 = 0; i_vline2 < segs.size(); ++ i_vline2) {
+ const SegmentedIntersectionLine &seg = segs[i_vline2];
+ if (! seg.intersections.empty()) {
+ assert(seg.intersections.size() > 1);
+ // Even number of intersections with the loops.
+ assert((seg.intersections.size() & 1) == 0);
+ assert(seg.intersections.front().type == SegmentIntersection::OUTER_LOW);
+ for (size_t i = 0; i < seg.intersections.size(); ++ i) {
+ const SegmentIntersection &intrsctn = seg.intersections[i];
+ if (intrsctn.is_outer()) {
+ assert(intrsctn.is_low() || i > 0);
+ bool consumed = intrsctn.is_low() ?
+ intrsctn.consumed_vertical_up :
+ seg.intersections[i-1].consumed_vertical_up;
+ if (! consumed) {
+ coordf_t dist2 = sqr(coordf_t(pointLast(0) - seg.pos)) + sqr(coordf_t(pointLast(1) - intrsctn.pos()));
+ if (dist2 < dist2min) {
+ dist2min = dist2;
+ i_vline = i_vline2;
+ i_intersection = i;
+ //FIXME We are taking the first left point always. Verify, that the caller chains the paths
+ // by a shortest distance, while reversing the paths if needed.
+ //if (polylines_out.empty())
+ // Initial state, take the first line, which is the first from the left.
+ goto found;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (i_intersection == size_t(-1))
+ // We are finished.
+ break;
+ found:
+ // Start a new path.
+ polylines_out.push_back(Polyline());
+ polyline_current = &polylines_out.back();
+ // Emit the first point of a path.
+ pointLast = Point(segs[i_vline].pos, segs[i_vline].intersections[i_intersection].pos());
+ polyline_current->points.push_back(pointLast);
+ }
+
+ // From the initial point (i_vline, i_intersection), follow a path.
+ SegmentedIntersectionLine &seg = segs[i_vline];
+ SegmentIntersection *intrsctn = &seg.intersections[i_intersection];
+ bool going_up = intrsctn->is_low();
+ bool try_connect = false;
+ if (going_up) {
+ assert(! intrsctn->consumed_vertical_up);
+ assert(i_intersection + 1 < seg.intersections.size());
+ // Step back to the beginning of the vertical segment to mark it as consumed.
+ if (intrsctn->is_inner()) {
+ assert(i_intersection > 0);
+ -- intrsctn;
+ -- i_intersection;
+ }
+ // Consume the complete vertical segment up to the outer contour.
+ do {
+ intrsctn->consumed_vertical_up = true;
+ ++ intrsctn;
+ ++ i_intersection;
+ assert(i_intersection < seg.intersections.size());
+ } while (intrsctn->type != SegmentIntersection::OUTER_HIGH);
+ if ((intrsctn - 1)->is_inner()) {
+ // Step back.
+ -- intrsctn;
+ -- i_intersection;
+ assert(intrsctn->type == SegmentIntersection::INNER_HIGH);
+ try_connect = true;
+ }
+ } else {
+ // Going down.
+ assert(intrsctn->is_high());
+ assert(i_intersection > 0);
+ assert(! (intrsctn - 1)->consumed_vertical_up);
+ // Consume the complete vertical segment up to the outer contour.
+ if (intrsctn->is_inner())
+ intrsctn->consumed_vertical_up = true;
+ do {
+ assert(i_intersection > 0);
+ -- intrsctn;
+ -- i_intersection;
+ intrsctn->consumed_vertical_up = true;
+ } while (intrsctn->type != SegmentIntersection::OUTER_LOW);
+ if ((intrsctn + 1)->is_inner()) {
+ // Step back.
+ ++ intrsctn;
+ ++ i_intersection;
+ assert(intrsctn->type == SegmentIntersection::INNER_LOW);
+ try_connect = true;
+ }
+ }
+ if (try_connect) {
+ // Decide, whether to finish the segment, or whether to follow the perimeter.
+
+ // 1) Find possible connection points on the previous / next vertical line.
+ int iPrev = intersection_on_prev_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection);
+ int iNext = intersection_on_next_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection);
+ IntersectionTypeOtherVLine intrsctn_type_prev = intersection_type_on_prev_vertical_line(segs, i_vline, i_intersection, iPrev);
+ IntersectionTypeOtherVLine intrsctn_type_next = intersection_type_on_next_vertical_line(segs, i_vline, i_intersection, iNext);
+
+ // 2) Find possible connection points on the same vertical line.
+ int iAbove = -1;
+ int iBelow = -1;
+ int iSegAbove = -1;
+ int iSegBelow = -1;
+ {
+ SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ?
+ SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW;
+ // Does the perimeter intersect the current vertical line above intrsctn?
+ for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i)
+// if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) {
+ if (seg.intersections[i].iContour == intrsctn->iContour) {
+ iAbove = i;
+ iSegAbove = seg.intersections[i].iSegment;
+ break;
+ }
+ // Does the perimeter intersect the current vertical line below intrsctn?
+ for (size_t i = i_intersection - 1; i > 0; -- i)
+// if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) {
+ if (seg.intersections[i].iContour == intrsctn->iContour) {
+ iBelow = i;
+ iSegBelow = seg.intersections[i].iSegment;
+ break;
+ }
+ }
+
+ // 3) Sort the intersection points, clear iPrev / iNext / iSegBelow / iSegAbove,
+ // if it is preceded by any other intersection point along the contour.
+ unsigned int vert_seg_dir_valid_mask =
+ (going_up ?
+ (iSegAbove != -1 && seg.intersections[iAbove].type == SegmentIntersection::INNER_LOW) :
+ (iSegBelow != -1 && seg.intersections[iBelow].type == SegmentIntersection::INNER_HIGH)) ?
+ (DIR_FORWARD | DIR_BACKWARD) :
+ 0;
+ {
+ // Invalidate iPrev resp. iNext, if the perimeter crosses the current vertical line earlier than iPrev resp. iNext.
+ // The perimeter contour orientation.
+ const bool forward = intrsctn->is_low(); // == poly_with_offset.is_contour_ccw(intrsctn->iContour);
+ const Polygon &poly = poly_with_offset.contour(intrsctn->iContour);
+ {
+ int d_horiz = (iPrev == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, segs[i_vline-1].intersections[iPrev].iSegment, intrsctn->iSegment, forward);
+ int d_down = (iSegBelow == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, iSegBelow, intrsctn->iSegment, forward);
+ int d_up = (iSegAbove == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, iSegAbove, intrsctn->iSegment, forward);
+ if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up))
+ // The vertical crossing comes eralier than the prev crossing.
+ // Disable the perimeter going back.
+ intrsctn_type_prev = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST;
+ if (going_up ? (d_up > std::min(d_horiz, d_down)) : (d_down > std::min(d_horiz, d_up)))
+ // The horizontal crossing comes earlier than the vertical crossing.
+ vert_seg_dir_valid_mask &= ~(forward ? DIR_BACKWARD : DIR_FORWARD);
+ }
+ {
+ int d_horiz = (iNext == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, intrsctn->iSegment, segs[i_vline+1].intersections[iNext].iSegment, forward);
+ int d_down = (iSegBelow == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, intrsctn->iSegment, iSegBelow, forward);
+ int d_up = (iSegAbove == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, intrsctn->iSegment, iSegAbove, forward);
+ if (intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up))
+ // The vertical crossing comes eralier than the prev crossing.
+ // Disable the perimeter going forward.
+ intrsctn_type_next = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST;
+ if (going_up ? (d_up > std::min(d_horiz, d_down)) : (d_down > std::min(d_horiz, d_up)))
+ // The horizontal crossing comes earlier than the vertical crossing.
+ vert_seg_dir_valid_mask &= ~(forward ? DIR_FORWARD : DIR_BACKWARD);
+ }
+ }
+
+ // 4) Try to connect to a previous or next vertical line, making a zig-zag pattern.
+ if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK || intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK) {
+ coordf_t distPrev = (intrsctn_type_prev != INTERSECTION_TYPE_OTHER_VLINE_OK) ? std::numeric_limits<coord_t>::max() :
+ measure_perimeter_prev_segment_length(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iPrev);
+ coordf_t distNext = (intrsctn_type_next != INTERSECTION_TYPE_OTHER_VLINE_OK) ? std::numeric_limits<coord_t>::max() :
+ measure_perimeter_next_segment_length(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext);
+ // Take the shorter path.
+ //FIXME this may not be always the best strategy to take the shortest connection line now.
+ bool take_next = (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK) ?
+ (distNext < distPrev) :
+ intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK;
+ assert(intrsctn->is_inner());
+ bool skip = params.dont_connect || (link_max_length > 0 && (take_next ? distNext : distPrev) > link_max_length);
+ if (skip) {
+ // Just skip the connecting contour and start a new path.
+ goto dont_connect;
+ polyline_current->points.push_back(Point(seg.pos, intrsctn->pos()));
+ polylines_out.push_back(Polyline());
+ polyline_current = &polylines_out.back();
+ const SegmentedIntersectionLine &il2 = segs[take_next ? (i_vline + 1) : (i_vline - 1)];
+ polyline_current->points.push_back(Point(il2.pos, il2.intersections[take_next ? iNext : iPrev].pos()));
+ } else {
+ polyline_current->points.push_back(Point(seg.pos, intrsctn->pos()));
+ emit_perimeter_prev_next_segment(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, take_next ? iNext : iPrev, *polyline_current, take_next);
+ }
+ // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed.
+ if (iPrev != -1)
+ segs[i_vline-1].intersections[iPrev].consumed_perimeter_right = true;
+ if (iNext != -1)
+ intrsctn->consumed_perimeter_right = true;
+ //FIXME consume the left / right connecting segments at the other end of this line? Currently it is not critical because a perimeter segment is not followed if the vertical segment at the other side has already been consumed.
+ // Advance to the neighbor line.
+ if (take_next) {
+ ++ i_vline;
+ i_intersection = iNext;
+ } else {
+ -- i_vline;
+ i_intersection = iPrev;
+ }
+ continue;
+ }
+
+ // 5) Try to connect to a previous or next point on the same vertical line.
+ if (vert_seg_dir_valid_mask) {
+ bool valid = true;
+ // Verify, that there is no intersection with the inner contour up to the end of the contour segment.
+ // Verify, that the successive segment has not been consumed yet.
+ if (going_up) {
+ if (seg.intersections[iAbove].consumed_vertical_up) {
+ valid = false;
+ } else {
+ for (int i = (int)i_intersection + 1; i < iAbove && valid; ++i)
+ if (seg.intersections[i].is_inner())
+ valid = false;
+ }
+ } else {
+ if (seg.intersections[iBelow-1].consumed_vertical_up) {
+ valid = false;
+ } else {
+ for (int i = iBelow + 1; i < (int)i_intersection && valid; ++i)
+ if (seg.intersections[i].is_inner())
+ valid = false;
+ }
+ }
+ if (valid) {
+ const Polygon &poly = poly_with_offset.contour(intrsctn->iContour);
+ int iNext = going_up ? iAbove : iBelow;
+ int iSegNext = going_up ? iSegAbove : iSegBelow;
+ bool dir_forward = (vert_seg_dir_valid_mask == (DIR_FORWARD | DIR_BACKWARD)) ?
+ // Take the shorter length between the current and the next intersection point.
+ (distance_of_segmens(poly, intrsctn->iSegment, iSegNext, true) <
+ distance_of_segmens(poly, intrsctn->iSegment, iSegNext, false)) :
+ (vert_seg_dir_valid_mask == DIR_FORWARD);
+ // Skip this perimeter line?
+ bool skip = params.dont_connect;
+ if (! skip && link_max_length > 0) {
+ coordf_t link_length = measure_perimeter_segment_on_vertical_line_length(
+ poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, dir_forward);
+ skip = link_length > link_max_length;
+ }
+ polyline_current->points.push_back(Point(seg.pos, intrsctn->pos()));
+ if (skip) {
+ // Just skip the connecting contour and start a new path.
+ polylines_out.push_back(Polyline());
+ polyline_current = &polylines_out.back();
+ polyline_current->points.push_back(Point(seg.pos, seg.intersections[iNext].pos()));
+ } else {
+ // Consume the connecting contour and the next segment.
+ emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, *polyline_current, dir_forward);
+ }
+ // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed.
+ // If there are any outer intersection points skipped (bypassed) by the contour,
+ // mark them as processed.
+ if (going_up) {
+ for (int i = (int)i_intersection; i < iAbove; ++ i)
+ seg.intersections[i].consumed_vertical_up = true;
+ } else {
+ for (int i = iBelow; i < (int)i_intersection; ++ i)
+ seg.intersections[i].consumed_vertical_up = true;
+ }
+// seg.intersections[going_up ? i_intersection : i_intersection - 1].consumed_vertical_up = true;
+ intrsctn->consumed_perimeter_right = true;
+ i_intersection = iNext;
+ if (going_up)
+ ++ intrsctn;
+ else
+ -- intrsctn;
+ intrsctn->consumed_perimeter_right = true;
+ continue;
+ }
+ }
+ dont_connect:
+ // No way to continue the current polyline. Take the rest of the line up to the outer contour.
+ // This will finish the polyline, starting another polyline at a new point.
+ if (going_up)
+ ++ intrsctn;
+ else
+ -- intrsctn;
+ }
+
+ // Finish the current vertical line,
+ // reset the current vertical line to pick a new starting point in the next round.
+ assert(intrsctn->is_outer());
+ assert(intrsctn->is_high() == going_up);
+ pointLast = Point(seg.pos, intrsctn->pos());
+ polyline_current->points.push_back(pointLast);
+ // Handle duplicate points and zero length segments.
+ polyline_current->remove_duplicate_points();
+ assert(! polyline_current->has_duplicate_points());
+ // Handle nearly zero length edges.
+ if (polyline_current->points.size() <= 1 ||
+ (polyline_current->points.size() == 2 &&
+ std::abs(polyline_current->points.front()(0) - polyline_current->points.back()(0)) < SCALED_EPSILON &&
+ std::abs(polyline_current->points.front()(1) - polyline_current->points.back()(1)) < SCALED_EPSILON))
+ polylines_out.pop_back();
+ intrsctn = NULL;
+ i_intersection = -1;
+ polyline_current = NULL;
+ }
+
+#ifdef SLIC3R_DEBUG
+ {
+ {
+ ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-final-%03d.svg", iRun), bbox_svg); // , scale_(1.));
+ poly_with_offset.export_to_svg(svg);
+ for (size_t i = n_polylines_out_initial; i < polylines_out.size(); ++ i)
+ svg.draw(polylines_out[i].lines(), "black");
+ }
+ // Paint a picture per polyline. This makes it easier to discover the order of the polylines and their overlap.
+ for (size_t i_polyline = n_polylines_out_initial; i_polyline < polylines_out.size(); ++ i_polyline) {
+ ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-final-%03d-%03d.svg", iRun, i_polyline), bbox_svg); // , scale_(1.));
+ svg.draw(polylines_out[i_polyline].lines(), "black");
+ }
+ }
+#endif /* SLIC3R_DEBUG */
+
+ // paths must be rotated back
+ for (Polylines::iterator it = polylines_out.begin() + n_polylines_out_initial; it != polylines_out.end(); ++ it) {
+ // No need to translate, the absolute position is irrelevant.
+ // it->translate(- rotate_vector.second(0), - rotate_vector.second(1));
+ assert(! it->has_duplicate_points());
+ it->rotate(rotate_vector.first);
+ //FIXME rather simplify the paths to avoid very short edges?
+ //assert(! it->has_duplicate_points());
+ it->remove_duplicate_points();
+ }
+
+#ifdef SLIC3R_DEBUG
+ // Verify, that there are no duplicate points in the sequence.
+ for (Polyline &polyline : polylines_out)
+ assert(! polyline.has_duplicate_points());
+#endif /* SLIC3R_DEBUG */
+
+ return true;
+}
+
+Polylines FillRectilinear2::fill_surface(const Surface *surface, const FillParams &params)
+{
+ Polylines polylines_out;
+ if (! fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out)) {
+ printf("FillRectilinear2::fill_surface() failed to fill a region.\n");
+ }
+ return polylines_out;
+}
+
+Polylines FillGrid2::fill_surface(const Surface *surface, const FillParams &params)
+{
+ // Each linear fill covers half of the target coverage.
+ FillParams params2 = params;
+ params2.density *= 0.5f;
+ Polylines polylines_out;
+ if (! fill_surface_by_lines(surface, params2, 0.f, 0.f, polylines_out) ||
+ ! fill_surface_by_lines(surface, params2, float(M_PI / 2.), 0.f, polylines_out)) {
+ printf("FillGrid2::fill_surface() failed to fill a region.\n");
+ }
+ return polylines_out;
+}
+
+Polylines FillTriangles::fill_surface(const Surface *surface, const FillParams &params)
+{
+ // Each linear fill covers 1/3 of the target coverage.
+ FillParams params2 = params;
+ params2.density *= 0.333333333f;
+ FillParams params3 = params2;
+ params3.dont_connect = true;
+ Polylines polylines_out;
+ if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) ||
+ ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) ||
+ ! fill_surface_by_lines(surface, params3, float(2. * M_PI / 3.), 0., polylines_out)) {
+ printf("FillTriangles::fill_surface() failed to fill a region.\n");
+ }
+ return polylines_out;
+}
+
+Polylines FillStars::fill_surface(const Surface *surface, const FillParams &params)
+{
+ // Each linear fill covers 1/3 of the target coverage.
+ FillParams params2 = params;
+ params2.density *= 0.333333333f;
+ FillParams params3 = params2;
+ params3.dont_connect = true;
+ Polylines polylines_out;
+ if (! fill_surface_by_lines(surface, params2, 0.f, 0., polylines_out) ||
+ ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), 0., polylines_out) ||
+ ! fill_surface_by_lines(surface, params3, float(2. * M_PI / 3.), 0.5 * this->spacing / params2.density, polylines_out)) {
+ printf("FillStars::fill_surface() failed to fill a region.\n");
+ }
+ return polylines_out;
+}
+
+Polylines FillCubic::fill_surface(const Surface *surface, const FillParams &params)
+{
+ // Each linear fill covers 1/3 of the target coverage.
+ FillParams params2 = params;
+ params2.density *= 0.333333333f;
+ FillParams params3 = params2;
+ params3.dont_connect = true;
+ Polylines polylines_out;
+ coordf_t dx = sqrt(0.5) * z;
+ if (! fill_surface_by_lines(surface, params2, 0.f, dx, polylines_out) ||
+ ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), - dx, polylines_out) ||
+ // Rotated by PI*2/3 + PI to achieve reverse sloping wall.
+ ! fill_surface_by_lines(surface, params3, float(M_PI * 2. / 3.), dx, polylines_out)) {
+ printf("FillCubic::fill_surface() failed to fill a region.\n");
+ }
+ return polylines_out;
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/Fill/FillRectilinear2.hpp b/src/libslic3r/Fill/FillRectilinear2.hpp
new file mode 100644
index 000000000..4459919b0
--- /dev/null
+++ b/src/libslic3r/Fill/FillRectilinear2.hpp
@@ -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_
diff --git a/src/libslic3r/Fill/FillRectilinear3.cpp b/src/libslic3r/Fill/FillRectilinear3.cpp
new file mode 100644
index 000000000..8fc129eac
--- /dev/null
+++ b/src/libslic3r/Fill/FillRectilinear3.cpp
@@ -0,0 +1,1642 @@
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include <boost/static_assert.hpp>
+
+#include "../ClipperUtils.hpp"
+#include "../ExPolygon.hpp"
+#include "../Geometry.hpp"
+#include "../Surface.hpp"
+#include "../Int128.hpp"
+
+#include "FillRectilinear3.hpp"
+
+ #define SLIC3R_DEBUG
+
+// Make assert active if SLIC3R_DEBUG
+#ifdef SLIC3R_DEBUG
+ #undef NDEBUG
+ #define DEBUG
+ #define _DEBUG
+ #include "SVG.hpp"
+#endif
+
+#include <cassert>
+
+namespace Slic3r {
+
+namespace FillRectilinear3_Internal {
+
+// A container maintaining the source expolygon with its inner offsetted polygon.
+// The source expolygon is offsetted twice:
+// 1) A tiny offset is used to get a contour, to which the open hatching lines will be extended.
+// 2) A larger offset is used to get a contor, along which the individual hatching lines will be connected.
+struct ExPolygonWithOffset
+{
+public:
+ ExPolygonWithOffset(
+ const ExPolygon &expolygon,
+ float aoffset1,
+ float aoffset2)
+ {
+ // Copy and rotate the source polygons.
+ polygons_src = expolygon;
+
+ double mitterLimit = 3.;
+ // for the infill pattern, don't cut the corners.
+ // default miterLimt = 3
+ //double mitterLimit = 10.;
+ assert(aoffset1 < 0);
+ assert(aoffset2 < 0);
+ assert(aoffset2 < aoffset1);
+// bool sticks_removed = remove_sticks(polygons_src);
+// if (sticks_removed) printf("Sticks removed!\n");
+ polygons_outer = offset(polygons_src, aoffset1,
+ ClipperLib::jtMiter,
+ mitterLimit);
+ polygons_inner = offset(polygons_outer, aoffset2 - aoffset1,
+ ClipperLib::jtMiter,
+ mitterLimit);
+ // Filter out contours with zero area or small area, contours with 2 points only.
+ const double min_area_threshold = 0.01 * aoffset2 * aoffset2;
+ remove_small(polygons_outer, min_area_threshold);
+ remove_small(polygons_inner, min_area_threshold);
+ remove_sticks(polygons_outer);
+ remove_sticks(polygons_inner);
+ n_contours_outer = polygons_outer.size();
+ n_contours_inner = polygons_inner.size();
+ n_contours = n_contours_outer + n_contours_inner;
+ polygons_ccw.assign(n_contours, false);
+ for (size_t i = 0; i < n_contours; ++ i) {
+ contour(i).remove_duplicate_points();
+ assert(! contour(i).has_duplicate_points());
+ polygons_ccw[i] = Slic3r::Geometry::is_ccw(contour(i));
+ }
+ }
+
+ // Any contour with offset1
+ bool is_contour_outer(size_t idx) const { return idx < n_contours_outer; }
+ // Any contour with offset2
+ bool is_contour_inner(size_t idx) const { return idx >= n_contours_outer; }
+
+ const Polygon& contour(size_t idx) const
+ { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; }
+
+ Polygon& contour(size_t idx)
+ { return is_contour_outer(idx) ? polygons_outer[idx] : polygons_inner[idx - n_contours_outer]; }
+
+ bool is_contour_ccw(size_t idx) const { return polygons_ccw[idx] != 0; }
+
+ BoundingBox bounding_box_src() const
+ { return get_extents(polygons_src); }
+ BoundingBox bounding_box_outer() const
+ { return get_extents(polygons_outer); }
+ BoundingBox bounding_box_inner() const
+ { return get_extents(polygons_inner); }
+
+#ifdef SLIC3R_DEBUG
+ void export_to_svg(Slic3r::SVG &svg) const {
+ svg.draw_outline(polygons_src, "black");
+ svg.draw_outline(polygons_outer, "green");
+ svg.draw_outline(polygons_inner, "brown");
+ }
+#endif /* SLIC3R_DEBUG */
+
+ ExPolygon polygons_src;
+ Polygons polygons_outer;
+ Polygons polygons_inner;
+
+ size_t n_contours_outer;
+ size_t n_contours_inner;
+ size_t n_contours;
+
+protected:
+ // For each polygon of polygons_inner, remember its orientation.
+ std::vector<unsigned char> polygons_ccw;
+};
+
+class SegmentedIntersectionLine;
+
+// Intersection point of a vertical line with a polygon segment.
+class SegmentIntersection
+{
+public:
+ SegmentIntersection() :
+ line(nullptr),
+ expoly_with_offset(nullptr),
+ iContour(0),
+ iSegment(0),
+ type(UNKNOWN),
+ consumed_vertical_up(false),
+ consumed_perimeter_right(false)
+ {}
+
+ // Parent object owning this intersection point.
+ const SegmentedIntersectionLine *line;
+ // Container with the source expolygon and its shrank copies, to be intersected by the line.
+ const ExPolygonWithOffset *expoly_with_offset;
+
+ // Index of a contour in ExPolygonWithOffset, with which this vertical line intersects.
+ size_t iContour;
+ // Index of a segment in iContour, with which this vertical line intersects.
+ size_t iSegment;
+
+ // Kind of intersection. With the original contour, or with the inner offestted contour?
+ // A vertical segment will be at least intersected by OUTER_LOW, OUTER_HIGH,
+ // but it could be intersected with OUTER_LOW, INNER_LOW, INNER_HIGH, OUTER_HIGH,
+ // and there may be more than one pair of INNER_LOW, INNER_HIGH between OUTER_LOW, OUTER_HIGH.
+ enum SegmentIntersectionType {
+ OUTER_LOW = 0,
+ OUTER_HIGH = 1,
+ INNER_LOW = 2,
+ INNER_HIGH = 3,
+ UNKNOWN = -1
+ };
+ SegmentIntersectionType type;
+
+ // For the INNER_LOW type, this point may be connected to another INNER_LOW point following a perimeter contour.
+ // For the INNER_HIGH type, this point may be connected to another INNER_HIGH point following a perimeter contour.
+ // If INNER_LOW is connected to INNER_HIGH or vice versa,
+ // one has to make sure the vertical infill line does not overlap with the connecting perimeter line.
+ bool is_inner() const { return type == INNER_LOW || type == INNER_HIGH; }
+ bool is_outer() const { return type == OUTER_LOW || type == OUTER_HIGH; }
+ bool is_low () const { return type == INNER_LOW || type == OUTER_LOW; }
+ bool is_high () const { return type == INNER_HIGH || type == OUTER_HIGH; }
+
+ // Calculate a position of this intersection point. The position does not need to be necessary exact.
+ Point pos() const;
+
+ // Returns 0, if this and other segments intersect at the hatching line.
+ // Returns -1, if this intersection is below the other intersection on the hatching line.
+ // Returns +1 otherwise.
+ int ordering_along_line(const SegmentIntersection &other) const;
+
+ // Compare two y intersection points given by rational numbers.
+ bool operator< (const SegmentIntersection &other) const;
+ // { return this->ordering_along_line(other) == -1; }
+ bool operator==(const SegmentIntersection &other) const { return this->ordering_along_line(other) == 0; }
+
+ //FIXME legacy code, suporting the old graph traversal algorithm. Please remove.
+ // Was this segment along the y axis consumed?
+ // Up means up along the vertical segment.
+ bool consumed_vertical_up;
+ // Was a segment of the inner perimeter contour consumed?
+ // Right means right from the vertical segment.
+ bool consumed_perimeter_right;
+};
+
+// A single hathing line intersecting the ExPolygonWithOffset.
+class SegmentedIntersectionLine
+{
+public:
+ // Index of this vertical intersection line.
+ size_t idx;
+ // Position of the line along the X axis of the oriented bounding box.
+// coord_t x;
+ // Position of this vertical intersection line, rotated to the world coordinate system.
+ Point pos;
+ // Direction of this vertical intersection line, rotated to the world coordinate system. The direction is not normalized to maintain a sufficient accuracy!
+ Vector dir;
+ // List of intersection points with polygons, sorted increasingly by the y axis.
+ // The SegmentIntersection keeps a pointer to this object to access the start and direction of this line.
+ std::vector<SegmentIntersection> intersections;
+};
+
+// Return an intersection point of the parent SegmentedIntersectionLine with the segment of a parent ExPolygonWithOffset.
+// The intersected segment of the ExPolygonWithOffset is addressed with (iContour, iSegment).
+// When calling this method, the SegmentedIntersectionLine must not be parallel with the segment.
+Point SegmentIntersection::pos() const
+{
+ // Get the two rays to be intersected.
+ const Polygon &poly = this->expoly_with_offset->contour(this->iContour);
+ // 30 bits + 1 signum bit.
+ const Point &seg_start = poly.points[(this->iSegment == 0) ? poly.points.size() - 1 : this->iSegment - 1];
+ const Point &seg_end = poly.points[this->iSegment];
+ // Point, vector of the segment.
+ const Vec2d p1(seg_start.cast<coordf_t>());
+ const Vec2d v1((seg_end - seg_start).cast<coordf_t>());
+ // Point, vector of this hatching line.
+ const Vec2d p2(line->pos.cast<coordf_t>());
+ const Vec2d v2(line->dir.cast<coordf_t>());
+ // Intersect the two rays.
+ double denom = v1(0) * v2(1) - v2(0) * v1(1);
+ Point out;
+ if (denom == 0.) {
+ // Lines are collinear. As the pos() method is not supposed to be called on collinear vectors,
+ // the source vectors are not quite collinear. Return the center of the contour segment.
+ out = seg_start + seg_end;
+ out(0) >>= 1;
+ out(1) >>= 1;
+ } else {
+ // Find the intersection point.
+ double t = (v2(0) * (p1(1) - p2(1)) - v2(1) * (p1(0) - p2(0))) / denom;
+ if (t < 0.)
+ out = seg_start;
+ else if (t > 1.)
+ out = seg_end;
+ else {
+ out(0) = coord_t(floor(p1(0) + t * v1(0) + 0.5));
+ out(1) = coord_t(floor(p1(1) + t * v1(1) + 0.5));
+ }
+ }
+ return out;
+}
+
+static inline int signum(int64_t v) { return (v > 0) - (v < 0); }
+
+// Returns 0, if this and other segments intersect at the hatching line.
+// Returns -1, if this intersection is below the other intersection on the hatching line.
+// Returns +1 otherwise.
+int SegmentIntersection::ordering_along_line(const SegmentIntersection &other) const
+{
+ assert(this->line == other.line);
+ assert(this->expoly_with_offset == other.expoly_with_offset);
+
+ if (this->iContour == other.iContour && this->iSegment == other.iSegment)
+ return true;
+
+ // Segment of this
+ const Polygon &poly_a = this->expoly_with_offset->contour(this->iContour);
+ // 30 bits + 1 signum bit.
+ const Point &seg_start_a = poly_a.points[(this->iSegment == 0) ? poly_a.points.size() - 1 : this->iSegment - 1];
+ const Point &seg_end_a = poly_a.points[this->iSegment];
+
+ // Segment of other
+ const Polygon &poly_b = this->expoly_with_offset->contour(other.iContour);
+ // 30 bits + 1 signum bit.
+ const Point &seg_start_b = poly_b.points[(other.iSegment == 0) ? poly_b.points.size() - 1 : other.iSegment - 1];
+ const Point &seg_end_b = poly_b.points[other.iSegment];
+
+ if (this->iContour == other.iContour) {
+ if ((this->iSegment + 1) % poly_a.points.size() == other.iSegment) {
+ // other.iSegment succeeds this->iSegment
+ assert(seg_end_a == seg_start_b);
+ // Avoid calling the 128bit x 128bit multiplication below if this->line intersects the common point.
+ if (cross2(Vec2i64(this->line->dir.cast<int64_t>()), (seg_end_b - this->line->pos).cast<int64_t>()) == 0)
+ return 0;
+ } else if ((other.iSegment + 1) % poly_a.points.size() == this->iSegment) {
+ // this->iSegment succeeds other.iSegment
+ assert(seg_start_a == seg_end_b);
+ // Avoid calling the 128bit x 128bit multiplication below if this->line intersects the common point.
+ if (cross2(Vec2i64(this->line->dir.cast<int64_t>()), (seg_start_a - this->line->pos).cast<int64_t>()) == 0)
+ return 0;
+ } else {
+ // General case.
+ }
+ }
+
+ // First test, whether both points of one segment are completely in one half-plane of the other line.
+ const Vec2i64 vec_b = (seg_end_b - seg_start_b).cast<int64_t>();
+ int side_start = signum(cross2(vec_b, (seg_start_a - seg_start_b).cast<int64_t>()));
+ int side_end = signum(cross2(vec_b, (seg_end_a - seg_start_b).cast<int64_t>()));
+ int side = side_start * side_end;
+ if (side > 0)
+ // This segment is completely inside one half-plane of the other line, therefore the ordering is trivial.
+ return signum(cross2(vec_b, this->line->dir.cast<int64_t>())) * side_start;
+
+ const Vec2i64 vec_a = (seg_end_a - seg_start_a).cast<int64_t>();
+ int side_start2 = signum(cross2(vec_a, (seg_start_b - seg_start_a).cast<int64_t>()));
+ int side_end2 = signum(cross2(vec_a, (seg_end_b - seg_start_a).cast<int64_t>()));
+ int side2 = side_start2 * side_end2;
+ //if (side == 0 && side2 == 0)
+ // The segments share one of their end points.
+ if (side2 > 0)
+ // This segment is completely inside one half-plane of the other line, therefore the ordering is trivial.
+ return signum(cross2(this->line->dir.cast<int64_t>(), vec_a)) * side_start2;
+
+ // The two segments intersect and they are not sucessive segments of the same contour.
+ // Ordering of the points depends on the position of the segment intersection (left / right from this->line),
+ // therefore a simple test over the input segment end points is not sufficient.
+
+ // Find the parameters of intersection of the two segmetns with this->line.
+ int64_t denom1 = cross2(this->line->dir.cast<int64_t>(), vec_a);
+ int64_t denom2 = cross2(this->line->dir.cast<int64_t>(), vec_b);
+ Vec2i64 vx_a = (seg_start_a - this->line->pos).cast<int64_t>();
+ Vec2i64 vx_b = (seg_start_b - this->line->pos).cast<int64_t>();
+ int64_t t1_times_denom1 = vx_a(0) * vec_a(1) - vx_a(1) * vec_a(0);
+ int64_t t2_times_denom2 = vx_b(0) * vec_b(1) - vx_b(1) * vec_b(0);
+ assert(denom1 != 0);
+ assert(denom2 != 0);
+ return Int128::compare_rationals_filtered(t1_times_denom1, denom1, t2_times_denom2, denom2);
+}
+
+// Compare two y intersection points given by rational numbers.
+bool SegmentIntersection::operator<(const SegmentIntersection &other) const
+{
+#ifdef _DEBUG
+ Point p1 = this->pos();
+ Point p2 = other.pos();
+ int64_t d = this->line->dir.cast<int64_t>().dot((p2 - p1).cast<int64_t>());
+#endif /* _DEBUG */
+ int ordering = this->ordering_along_line(other);
+#ifdef _DEBUG
+ if (ordering == -1)
+ assert(d >= - int64_t(SCALED_EPSILON));
+ else if (ordering == 1)
+ assert(d <= int64_t(SCALED_EPSILON));
+#endif /* _DEBUG */
+ return ordering == -1;
+}
+
+// When doing a rectilinear / grid / triangle / stars / cubic infill,
+// the following class holds the hatching lines of each of the hatching directions.
+class InfillHatchingSingleDirection
+{
+public:
+ // Hatching angle, CCW from the X axis.
+ double angle;
+ // Starting point of the 1st hatching line.
+ Point start_point;
+ // Direction vector, its size is not normalized to maintain a sufficient accuracy!
+ Vector direction;
+ // Spacing of the hatching lines, perpendicular to the direction vector.
+ coord_t line_spacing;
+ // Infill segments oriented at angle.
+ std::vector<SegmentedIntersectionLine> segs;
+};
+
+// For the rectilinear, grid, triangles, stars and cubic pattern fill one InfillHatchingSingleDirection structure
+// for each infill direction. The segments stored in InfillHatchingSingleDirection will then form a graph of candidate
+// paths to be extruded.
+static bool prepare_infill_hatching_segments(
+ // Input geometry to be hatch, containing two concentric contours for each input contour.
+ const ExPolygonWithOffset &poly_with_offset,
+ // fill density, dont_adjust
+ const FillParams &params,
+ // angle, pattern_shift, spacing
+ FillRectilinear3::FillDirParams &fill_dir_params,
+ // Reference point of the pattern, to which the infill lines will be alligned, and the base angle.
+ const std::pair<float, Point> &rotate_vector,
+ // Resulting straight segments of the infill graph.
+ InfillHatchingSingleDirection &out)
+{
+ out.angle = rotate_vector.first + fill_dir_params.angle;
+ out.direction = Point(coord_t(scale_(1000)), coord_t(0));
+ // Hatch along the Y axis of the rotated coordinate system.
+ out.direction.rotate(out.angle + 0.5 * M_PI);
+ out.segs.clear();
+
+ assert(params.density > 0.0001f && params.density <= 1.f);
+ coord_t line_spacing = coord_t(scale_(fill_dir_params.spacing) / params.density);
+
+ // Bounding box around the source contour, aligned with out.angle.
+ BoundingBox bounding_box = get_extents_rotated(poly_with_offset.polygons_src.contour, - out.angle);
+
+ // Define the flow spacing according to requested density.
+ if (params.full_infill() && ! params.dont_adjust) {
+ // Full infill, adjust the line spacing to fit an integer number of lines.
+ out.line_spacing = Fill::_adjust_solid_spacing(bounding_box.size()(0), line_spacing);
+ // Report back the adjusted line spacing.
+ fill_dir_params.spacing = unscale<double>(line_spacing);
+ } else {
+ // Extend bounding box so that our pattern will be aligned with the other layers.
+ // Transform the reference point to the rotated coordinate system.
+ Point refpt = rotate_vector.second.rotated(- out.angle);
+ // _align_to_grid will not work correctly with positive pattern_shift.
+ coord_t pattern_shift_scaled = coord_t(scale_(fill_dir_params.pattern_shift)) % line_spacing;
+ refpt(0) -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled);
+ bounding_box.merge(Fill::_align_to_grid(
+ bounding_box.min,
+ Point(line_spacing, line_spacing),
+ refpt));
+ }
+
+ // Intersect a set of euqally spaced vertical lines wiht expolygon.
+ // n_vlines = ceil(bbox_width / line_spacing)
+ size_t n_vlines = (bounding_box.max(0) - bounding_box.min(0) + line_spacing - 1) / line_spacing;
+ coord_t x0 = bounding_box.min(0);
+ if (params.full_infill())
+ x0 += coord_t((line_spacing + SCALED_EPSILON) / 2);
+
+ out.line_spacing = line_spacing;
+ out.start_point = Point(x0, bounding_box.min(1));
+ out.start_point.rotate(out.angle);
+
+#ifdef SLIC3R_DEBUG
+ static int iRun = 0;
+ BoundingBox bbox_svg = poly_with_offset.bounding_box_outer();
+ ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-%d.svg", iRun), bbox_svg); // , scale_(1.));
+ poly_with_offset.export_to_svg(svg);
+ {
+ ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-initial-%d.svg", iRun), bbox_svg); // , scale_(1.));
+ poly_with_offset.export_to_svg(svg);
+ }
+ iRun ++;
+#endif /* SLIC3R_DEBUG */
+
+ // For each contour
+ // Allocate storage for the segments.
+ out.segs.assign(n_vlines, SegmentedIntersectionLine());
+ double cos_a = cos(out.angle);
+ double sin_a = sin(out.angle);
+ for (size_t i = 0; i < n_vlines; ++ i) {
+ auto &seg = out.segs[i];
+ seg.idx = i;
+ // seg(0) = x0 + coord_t(i) * line_spacing;
+ coord_t x = x0 + coord_t(i) * line_spacing;
+ seg.pos(0) = coord_t(floor(cos_a * x - sin_a * bounding_box.min(1) + 0.5));
+ seg.pos(1) = coord_t(floor(cos_a * bounding_box.min(1) + sin_a * x + 0.5));
+ seg.dir = out.direction;
+ }
+
+ for (size_t iContour = 0; iContour < poly_with_offset.n_contours; ++ iContour) {
+ const Points &contour = poly_with_offset.contour(iContour).points;
+ if (contour.size() < 2)
+ continue;
+ // For each segment
+ for (size_t iSegment = 0; iSegment < contour.size(); ++ iSegment) {
+ size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1;
+ const Point *pl = &contour[iPrev];
+ const Point *pr = &contour[iSegment];
+ // Orient the segment to the direction vector.
+ const Point v = *pr - *pl;
+ int orientation = Int128::sign_determinant_2x2_filtered(v(0), v(1), out.direction(0), out.direction(1));
+ if (orientation == 0)
+ // Ignore strictly vertical segments.
+ continue;
+ if (orientation < 0)
+ // Always orient the input segment consistently towards the hatching direction.
+ std::swap(pl, pr);
+ // Which of the equally spaced vertical lines is intersected by this segment?
+ coord_t l = (coord_t)floor(cos_a * (*pl)(0) + sin_a * (*pl)(1) - SCALED_EPSILON);
+ coord_t r = (coord_t)ceil (cos_a * (*pr)(0) + sin_a * (*pr)(1) + SCALED_EPSILON);
+ assert(l < r - SCALED_EPSILON);
+ // il, ir are the left / right indices of vertical lines intersecting a segment
+ int il = std::max<int>(0, (l - x0 + line_spacing) / line_spacing);
+ int ir = std::min<int>(int(out.segs.size()) - 1, (r - x0) / line_spacing);
+ // The previous tests were done with floating point arithmetics over an epsilon-extended interval.
+ // Now do the same tests with exact arithmetics over the exact interval.
+ while (il <= ir && int128::orient(out.segs[il].pos, out.segs[il].pos + out.direction, *pl) < 0)
+ ++ il;
+ while (il <= ir && int128::orient(out.segs[ir].pos, out.segs[ir].pos + out.direction, *pr) > 0)
+ -- ir;
+ // Here it is ensured, that
+ // 1) out.seg is not parallel to (pl, pr)
+ // 2) all lines from il to ir intersect <pl, pr>.
+ assert(il >= 0 && ir < int(out.segs.size()));
+ for (int i = il; i <= ir; ++ i) {
+ // assert(out.segs[i](0) == i * line_spacing + x0);
+ // assert(l <= out.segs[i](0));
+ // assert(r >= out.segs[i](0));
+ SegmentIntersection is;
+ is.line = &out.segs[i];
+ is.expoly_with_offset = &poly_with_offset;
+ is.iContour = iContour;
+ is.iSegment = iSegment;
+ // Test whether the calculated intersection point falls into the bounding box of the input segment.
+ // +-1 to take rounding into account.
+ assert(int128::orient(out.segs[i].pos, out.segs[i].pos + out.direction, *pl) >= 0);
+ assert(int128::orient(out.segs[i].pos, out.segs[i].pos + out.direction, *pr) <= 0);
+ assert(is.pos()(0) + 1 >= std::min((*pl)(0), (*pr)(0)));
+ assert(is.pos()(1) + 1 >= std::min((*pl)(1), (*pr)(1)));
+ assert(is.pos()(0) <= std::max((*pl)(0), (*pr)(0)) + 1);
+ assert(is.pos()(1) <= std::max((*pl)(1), (*pr)(1)) + 1);
+ out.segs[i].intersections.push_back(is);
+ }
+ }
+ }
+
+ // Sort the intersections along their segments, specify the intersection types.
+ for (size_t i_seg = 0; i_seg < out.segs.size(); ++ i_seg) {
+ SegmentedIntersectionLine &sil = out.segs[i_seg];
+ // Sort the intersection points using exact rational arithmetic.
+ std::sort(sil.intersections.begin(), sil.intersections.end());
+#ifdef _DEBUG
+ // Verify that the intersections are sorted along the haching direction.
+ for (size_t i = 1; i < sil.intersections.size(); ++ i) {
+ Point p1 = sil.intersections[i - 1].pos();
+ Point p2 = sil.intersections[i].pos();
+ int64_t d = sil.dir.cast<int64_t>().dot((p2 - p1).cast<int64_t>());
+ assert(d >= - int64_t(SCALED_EPSILON));
+ }
+#endif /* _DEBUG */
+ // Assign the intersection types, remove duplicate or overlapping intersection points.
+ // When a loop vertex touches a vertical line, intersection point is generated for both segments.
+ // If such two segments are oriented equally, then one of them is removed.
+ // Otherwise the vertex is tangential to the vertical line and both segments are removed.
+ // The same rule applies, if the loop is pinched into a single point and this point touches the vertical line:
+ // The loop has a zero vertical size at the vertical line, therefore the intersection point is removed.
+ size_t j = 0;
+ for (size_t i = 0; i < sil.intersections.size(); ++ i) {
+ // What is the orientation of the segment at the intersection point?
+ size_t iContour = sil.intersections[i].iContour;
+ const Points &contour = poly_with_offset.contour(iContour).points;
+ size_t iSegment = sil.intersections[i].iSegment;
+ size_t iPrev = ((iSegment == 0) ? contour.size() : iSegment) - 1;
+ int dir = int128::cross(contour[iSegment] - contour[iPrev], sil.dir);
+ bool low = dir > 0;
+ sil.intersections[i].type = poly_with_offset.is_contour_outer(iContour) ?
+ (low ? SegmentIntersection::OUTER_LOW : SegmentIntersection::OUTER_HIGH) :
+ (low ? SegmentIntersection::INNER_LOW : SegmentIntersection::INNER_HIGH);
+ if (j > 0 && sil.intersections[i].iContour == sil.intersections[j-1].iContour) {
+ // Two successive intersection points on a vertical line with the same contour. This may be a special case.
+ if (sil.intersections[i] == sil.intersections[j-1]) {
+ // Two successive segments meet exactly at the vertical line.
+ #ifdef SLIC3R_DEBUG
+ // Verify that the segments of sil.intersections[i] and sil.intersections[j-1] are adjoint.
+ size_t iSegment2 = sil.intersections[j-1].iSegment;
+ size_t iPrev2 = ((iSegment2 == 0) ? contour.size() : iSegment2) - 1;
+ assert(iSegment == iPrev2 || iSegment2 == iPrev);
+ #endif /* SLIC3R_DEBUG */
+ if (sil.intersections[i].type == sil.intersections[j-1].type) {
+ // Two successive segments of the same direction (both to the right or both to the left)
+ // meet exactly at the vertical line.
+ // Remove the second intersection point.
+ } else {
+ // This is a loop returning to the same point.
+ // It may as well be a vertex of a loop touching this vertical line.
+ // Remove both the lines.
+ -- j;
+ }
+ } else if (sil.intersections[i].type == sil.intersections[j-1].type) {
+ // Two non successive segments of the same direction (both to the right or both to the left)
+ // meet exactly at the vertical line. That means there is a Z shaped path, where the center segment
+ // of the Z shaped path is aligned with this vertical line.
+ // Remove one of the intersection points while maximizing the vertical segment length.
+ if (low) {
+ // Remove the second intersection point, keep the first intersection point.
+ } else {
+ // Remove the first intersection point, keep the second intersection point.
+ sil.intersections[j-1] = sil.intersections[i];
+ }
+ } else {
+ // Vertical line intersects a contour segment at a general position (not at one of its end points).
+ // or the contour just touches this vertical line with a vertical segment or a sequence of vertical segments.
+ // Keep both intersection points.
+ if (j < i)
+ sil.intersections[j] = sil.intersections[i];
+ ++ j;
+ }
+ } else {
+ // Vertical line intersects a contour segment at a general position (not at one of its end points).
+ if (j < i)
+ sil.intersections[j] = sil.intersections[i];
+ ++ j;
+ }
+ }
+ // Shrink the list of intersections, if any of the intersection was removed during the classification.
+ if (j < sil.intersections.size())
+ sil.intersections.erase(sil.intersections.begin() + j, sil.intersections.end());
+ }
+
+ // Verify the segments. If something is wrong, give up.
+#define ASSERT_OR_RETURN(CONDITION) do { assert(CONDITION); if (! (CONDITION)) return false; } while (0)
+#ifdef _MSC_VER
+ #pragma warning(push)
+ #pragma warning(disable: 4127)
+#endif
+ for (size_t i_seg = 0; i_seg < out.segs.size(); ++ i_seg) {
+ SegmentedIntersectionLine &sil = out.segs[i_seg];
+ // The intersection points have to be even.
+ ASSERT_OR_RETURN((sil.intersections.size() & 1) == 0);
+ for (size_t i = 0; i < sil.intersections.size();) {
+ // An intersection segment crossing the bigger contour may cross the inner offsetted contour even number of times.
+ ASSERT_OR_RETURN(sil.intersections[i].type == SegmentIntersection::OUTER_LOW);
+ size_t j = i + 1;
+ ASSERT_OR_RETURN(j < sil.intersections.size());
+ ASSERT_OR_RETURN(sil.intersections[j].type == SegmentIntersection::INNER_LOW || sil.intersections[j].type == SegmentIntersection::OUTER_HIGH);
+ for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++ j) ;
+ ASSERT_OR_RETURN(j < sil.intersections.size());
+ ASSERT_OR_RETURN((j & 1) == 1);
+ ASSERT_OR_RETURN(sil.intersections[j].type == SegmentIntersection::OUTER_HIGH);
+ ASSERT_OR_RETURN(i + 1 == j || sil.intersections[j - 1].type == SegmentIntersection::INNER_HIGH);
+ i = j + 1;
+ }
+ }
+#undef ASSERT_OR_RETURN
+#ifdef _MSC_VER
+ #pragma warning(push)
+#endif /* _MSC_VER */
+
+#ifdef SLIC3R_DEBUG
+ // Paint the segments and finalize the SVG file.
+ for (size_t i_seg = 0; i_seg < out.segs.size(); ++ i_seg) {
+ SegmentedIntersectionLine &sil = out.segs[i_seg];
+ for (size_t i = 0; i < sil.intersections.size();) {
+ size_t j = i + 1;
+ for (; j < sil.intersections.size() && sil.intersections[j].is_inner(); ++ j) ;
+ if (i + 1 == j) {
+ svg.draw(Line(sil.intersections[i ].pos(), sil.intersections[j ].pos()), "blue");
+ } else {
+ svg.draw(Line(sil.intersections[i ].pos(), sil.intersections[i+1].pos()), "green");
+ svg.draw(Line(sil.intersections[i+1].pos(), sil.intersections[j-1].pos()), (j - i + 1 > 4) ? "yellow" : "magenta");
+ svg.draw(Line(sil.intersections[j-1].pos(), sil.intersections[j ].pos()), "green");
+ }
+ i = j + 1;
+ }
+ }
+ svg.Close();
+#endif /* SLIC3R_DEBUG */
+
+
+ return true;
+}
+
+
+
+
+
+
+
+
+/****************************************************************** Legacy code, to be replaced by a graph algorithm ******************************************************************/
+
+
+// Having a segment of a closed polygon, calculate its Euclidian length.
+// The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop,
+// therefore the point p1 lies on poly.points[seg1-1], poly.points[seg1] etc.
+static inline coordf_t segment_length(const Polygon &poly, size_t seg1, const Point &p1, size_t seg2, const Point &p2)
+{
+#ifdef SLIC3R_DEBUG
+ // Verify that p1 lies on seg1. This is difficult to verify precisely,
+ // but at least verify, that p1 lies in the bounding box of seg1.
+ for (size_t i = 0; i < 2; ++ i) {
+ size_t seg = (i == 0) ? seg1 : seg2;
+ Point px = (i == 0) ? p1 : p2;
+ Point pa = poly.points[((seg == 0) ? poly.points.size() : seg) - 1];
+ Point pb = poly.points[seg];
+ if (pa(0) > pb(0))
+ std::swap(pa(0), pb(0));
+ if (pa(1) > pb(1))
+ std::swap(pa(1), pb(1));
+ assert(px(0) >= pa(0) && px(0) <= pb(0));
+ assert(px(1) >= pa(1) && px(1) <= pb(1));
+ }
+#endif /* SLIC3R_DEBUG */
+ const Point *pPrev = &p1;
+ const Point *pThis = NULL;
+ coordf_t len = 0;
+ if (seg1 <= seg2) {
+ for (size_t i = seg1; i < seg2; ++ i, pPrev = pThis)
+ len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm();
+ } else {
+ for (size_t i = seg1; i < poly.points.size(); ++ i, pPrev = pThis)
+ len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm();
+ for (size_t i = 0; i < seg2; ++ i, pPrev = pThis)
+ len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm();
+ }
+ len += (*pPrev - p2).cast<double>().norm();
+ return len;
+}
+
+// Append a segment of a closed polygon to a polyline.
+// The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop.
+// Only insert intermediate points between seg1 and seg2.
+static inline void polygon_segment_append(Points &out, const Polygon &polygon, size_t seg1, size_t seg2)
+{
+ if (seg1 == seg2) {
+ // Nothing to append from this segment.
+ } else if (seg1 < seg2) {
+ // Do not append a point pointed to by seg2.
+ out.insert(out.end(), polygon.points.begin() + seg1, polygon.points.begin() + seg2);
+ } else {
+ out.reserve(out.size() + seg2 + polygon.points.size() - seg1);
+ out.insert(out.end(), polygon.points.begin() + seg1, polygon.points.end());
+ // Do not append a point pointed to by seg2.
+ out.insert(out.end(), polygon.points.begin(), polygon.points.begin() + seg2);
+ }
+}
+
+// Append a segment of a closed polygon to a polyline.
+// The segment indices seg1 and seg2 signify an end point of an edge in the forward direction of the loop,
+// but this time the segment is traversed backward.
+// Only insert intermediate points between seg1 and seg2.
+static inline void polygon_segment_append_reversed(Points &out, const Polygon &polygon, size_t seg1, size_t seg2)
+{
+ if (seg1 >= seg2) {
+ out.reserve(seg1 - seg2);
+ for (size_t i = seg1; i > seg2; -- i)
+ out.push_back(polygon.points[i - 1]);
+ } else {
+ // it could be, that seg1 == seg2. In that case, append the complete loop.
+ out.reserve(out.size() + seg2 + polygon.points.size() - seg1);
+ for (size_t i = seg1; i > 0; -- i)
+ out.push_back(polygon.points[i - 1]);
+ for (size_t i = polygon.points.size(); i > seg2; -- i)
+ out.push_back(polygon.points[i - 1]);
+ }
+}
+
+static inline int distance_of_segmens(const Polygon &poly, size_t seg1, size_t seg2, bool forward)
+{
+ int d = int(seg2) - int(seg1);
+ if (! forward)
+ d = - d;
+ if (d < 0)
+ d += int(poly.points.size());
+ return d;
+}
+
+// For a vertical line, an inner contour and an intersection point,
+// find an intersection point on the previous resp. next vertical line.
+// The intersection point is connected with the prev resp. next intersection point with iInnerContour.
+// Return -1 if there is no such point on the previous resp. next vertical line.
+static inline int intersection_on_prev_next_vertical_line(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ bool dir_is_next)
+{
+ size_t iVerticalLineOther = iVerticalLine;
+ if (dir_is_next) {
+ if (++ iVerticalLineOther == segs.size())
+ // No successive vertical line.
+ return -1;
+ } else if (iVerticalLineOther -- == 0) {
+ // No preceding vertical line.
+ return -1;
+ }
+
+ const SegmentedIntersectionLine &il = segs[iVerticalLine];
+ const SegmentIntersection &itsct = il.intersections[iIntersection];
+ const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther];
+ const Polygon &poly = poly_with_offset.contour(iInnerContour);
+// const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour);
+ const bool forward = itsct.is_low() == dir_is_next;
+ // Resulting index of an intersection point on il2.
+ int out = -1;
+ // Find an intersection point on iVerticalLineOther, intersecting iInnerContour
+ // at the same orientation as iIntersection, and being closest to iIntersection
+ // in the number of contour segments, when following the direction of the contour.
+ int dmin = std::numeric_limits<int>::max();
+ for (size_t i = 0; i < il2.intersections.size(); ++ i) {
+ const SegmentIntersection &itsct2 = il2.intersections[i];
+ if (itsct.iContour == itsct2.iContour && itsct.type == itsct2.type) {
+ /*
+ if (itsct.is_low()) {
+ assert(itsct.type == SegmentIntersection::INNER_LOW);
+ assert(iIntersection > 0);
+ assert(il.intersections[iIntersection-1].type == SegmentIntersection::OUTER_LOW);
+ assert(i > 0);
+ if (il2.intersections[i-1].is_inner())
+ // Take only the lowest inner intersection point.
+ continue;
+ assert(il2.intersections[i-1].type == SegmentIntersection::OUTER_LOW);
+ } else {
+ assert(itsct.type == SegmentIntersection::INNER_HIGH);
+ assert(iIntersection+1 < il.intersections.size());
+ assert(il.intersections[iIntersection+1].type == SegmentIntersection::OUTER_HIGH);
+ assert(i+1 < il2.intersections.size());
+ if (il2.intersections[i+1].is_inner())
+ // Take only the highest inner intersection point.
+ continue;
+ assert(il2.intersections[i+1].type == SegmentIntersection::OUTER_HIGH);
+ }
+ */
+ // The intersection points lie on the same contour and have the same orientation.
+ // Find the intersection point with a shortest path in the direction of the contour.
+ int d = distance_of_segmens(poly, itsct.iSegment, itsct2.iSegment, forward);
+ if (d < dmin) {
+ out = i;
+ dmin = d;
+ }
+ }
+ }
+ //FIXME this routine is not asymptotic optimal, it will be slow if there are many intersection points along the line.
+ return out;
+}
+
+static inline int intersection_on_prev_vertical_line(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection)
+{
+ return intersection_on_prev_next_vertical_line(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, false);
+}
+
+static inline int intersection_on_next_vertical_line(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection)
+{
+ return intersection_on_prev_next_vertical_line(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, true);
+}
+
+enum IntersectionTypeOtherVLine {
+ // There is no connection point on the other vertical line.
+ INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED = -1,
+ // Connection point on the other vertical segment was found
+ // and it could be followed.
+ INTERSECTION_TYPE_OTHER_VLINE_OK = 0,
+ // The connection segment connects to a middle of a vertical segment.
+ // Cannot follow.
+ INTERSECTION_TYPE_OTHER_VLINE_INNER,
+ // Cannot extend the contor to this intersection point as either the connection segment
+ // or the succeeding vertical segment were already consumed.
+ INTERSECTION_TYPE_OTHER_VLINE_CONSUMED,
+ // Not the first intersection along the contor. This intersection point
+ // has been preceded by an intersection point along the vertical line.
+ INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST,
+};
+
+// Find an intersection on a previous line, but return -1, if the connecting segment of a perimeter was already extruded.
+static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical_line(
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iIntersection,
+ size_t iIntersectionOther,
+ bool dir_is_next)
+{
+ // This routine will propose a connecting line even if the connecting perimeter segment intersects
+ // iVertical line multiple times before reaching iIntersectionOther.
+ if (iIntersectionOther == -1)
+ return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED;
+ assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0));
+ const SegmentedIntersectionLine &il_this = segs[iVerticalLine];
+ const SegmentIntersection &itsct_this = il_this.intersections[iIntersection];
+ const SegmentedIntersectionLine &il_other = segs[dir_is_next ? (iVerticalLine+1) : (iVerticalLine-1)];
+ const SegmentIntersection &itsct_other = il_other.intersections[iIntersectionOther];
+ assert(itsct_other.is_inner());
+ assert(iIntersectionOther > 0);
+ assert(iIntersectionOther + 1 < il_other.intersections.size());
+ // Is iIntersectionOther at the boundary of a vertical segment?
+ const SegmentIntersection &itsct_other2 = il_other.intersections[itsct_other.is_low() ? iIntersectionOther - 1 : iIntersectionOther + 1];
+ if (itsct_other2.is_inner())
+ // Cannot follow a perimeter segment into the middle of another vertical segment.
+ // Only perimeter segments connecting to the end of a vertical segment are followed.
+ return INTERSECTION_TYPE_OTHER_VLINE_INNER;
+ assert(itsct_other.is_low() == itsct_other2.is_low());
+ if (dir_is_next ? itsct_this.consumed_perimeter_right : itsct_other.consumed_perimeter_right)
+ // This perimeter segment was already consumed.
+ return INTERSECTION_TYPE_OTHER_VLINE_CONSUMED;
+ if (itsct_other.is_low() ? itsct_other.consumed_vertical_up : il_other.intersections[iIntersectionOther-1].consumed_vertical_up)
+ // This vertical segment was already consumed.
+ return INTERSECTION_TYPE_OTHER_VLINE_CONSUMED;
+ return INTERSECTION_TYPE_OTHER_VLINE_OK;
+}
+
+static inline IntersectionTypeOtherVLine intersection_type_on_prev_vertical_line(
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iIntersection,
+ size_t iIntersectionPrev)
+{
+ return intersection_type_on_prev_next_vertical_line(segs, iVerticalLine, iIntersection, iIntersectionPrev, false);
+}
+
+static inline IntersectionTypeOtherVLine intersection_type_on_next_vertical_line(
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iIntersection,
+ size_t iIntersectionNext)
+{
+ return intersection_type_on_prev_next_vertical_line(segs, iVerticalLine, iIntersection, iIntersectionNext, true);
+}
+
+// Measure an Euclidian length of a perimeter segment when going from iIntersection to iIntersection2.
+static inline coordf_t measure_perimeter_prev_next_segment_length(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2,
+ bool dir_is_next)
+{
+ size_t iVerticalLineOther = iVerticalLine;
+ if (dir_is_next) {
+ if (++ iVerticalLineOther == segs.size())
+ // No successive vertical line.
+ return coordf_t(-1);
+ } else if (iVerticalLineOther -- == 0) {
+ // No preceding vertical line.
+ return coordf_t(-1);
+ }
+
+ const SegmentedIntersectionLine &il = segs[iVerticalLine];
+ const SegmentIntersection &itsct = il.intersections[iIntersection];
+ const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther];
+ const SegmentIntersection &itsct2 = il2.intersections[iIntersection2];
+ const Polygon &poly = poly_with_offset.contour(iInnerContour);
+// const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour);
+ assert(itsct.type == itsct2.type);
+ assert(itsct.iContour == itsct2.iContour);
+ assert(itsct.is_inner());
+ const bool forward = itsct.is_low() == dir_is_next;
+
+ Point p1 = itsct.pos();
+ Point p2 = itsct2.pos();
+ return forward ?
+ segment_length(poly, itsct .iSegment, p1, itsct2.iSegment, p2) :
+ segment_length(poly, itsct2.iSegment, p2, itsct .iSegment, p1);
+}
+
+static inline coordf_t measure_perimeter_prev_segment_length(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2)
+{
+ return measure_perimeter_prev_next_segment_length(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, iIntersection2, false);
+}
+
+static inline coordf_t measure_perimeter_next_segment_length(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2)
+{
+ return measure_perimeter_prev_next_segment_length(poly_with_offset, segs, iVerticalLine, iInnerContour, iIntersection, iIntersection2, true);
+}
+
+// Append the points of a perimeter segment when going from iIntersection to iIntersection2.
+// The first point (the point of iIntersection) will not be inserted,
+// the last point will be inserted.
+static inline void emit_perimeter_prev_next_segment(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2,
+ Polyline &out,
+ bool dir_is_next)
+{
+ size_t iVerticalLineOther = iVerticalLine;
+ if (dir_is_next) {
+ ++ iVerticalLineOther;
+ assert(iVerticalLineOther < segs.size());
+ } else {
+ assert(iVerticalLineOther > 0);
+ -- iVerticalLineOther;
+ }
+
+ const SegmentedIntersectionLine &il = segs[iVerticalLine];
+ const SegmentIntersection &itsct = il.intersections[iIntersection];
+ const SegmentedIntersectionLine &il2 = segs[iVerticalLineOther];
+ const SegmentIntersection &itsct2 = il2.intersections[iIntersection2];
+ const Polygon &poly = poly_with_offset.contour(iInnerContour);
+// const bool ccw = poly_with_offset.is_contour_ccw(iInnerContour);
+ assert(itsct.type == itsct2.type);
+ assert(itsct.iContour == itsct2.iContour);
+ assert(itsct.is_inner());
+ const bool forward = itsct.is_low() == dir_is_next;
+ // Do not append the first point.
+ // out.points.push_back(Point(il.pos, itsct.pos));
+ if (forward)
+ polygon_segment_append(out.points, poly, itsct.iSegment, itsct2.iSegment);
+ else
+ polygon_segment_append_reversed(out.points, poly, itsct.iSegment, itsct2.iSegment);
+ // Append the last point.
+ out.points.push_back(itsct2.pos());
+}
+
+static inline coordf_t measure_perimeter_segment_on_vertical_line_length(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2,
+ bool forward)
+{
+ const SegmentedIntersectionLine &il = segs[iVerticalLine];
+ const SegmentIntersection &itsct = il.intersections[iIntersection];
+ const SegmentIntersection &itsct2 = il.intersections[iIntersection2];
+ const Polygon &poly = poly_with_offset.contour(iInnerContour);
+ assert(itsct.is_inner());
+ assert(itsct2.is_inner());
+ assert(itsct.type != itsct2.type);
+ assert(itsct.iContour == iInnerContour);
+ assert(itsct.iContour == itsct2.iContour);
+ return forward ?
+ segment_length(poly, itsct .iSegment, itsct.pos(), itsct2.iSegment, itsct2.pos()) :
+ segment_length(poly, itsct2.iSegment, itsct2.pos(), itsct .iSegment, itsct.pos());
+}
+
+// Append the points of a perimeter segment when going from iIntersection to iIntersection2.
+// The first point (the point of iIntersection) will not be inserted,
+// the last point will be inserted.
+static inline void emit_perimeter_segment_on_vertical_line(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t iVerticalLine,
+ size_t iInnerContour,
+ size_t iIntersection,
+ size_t iIntersection2,
+ Polyline &out,
+ bool forward)
+{
+ const SegmentedIntersectionLine &il = segs[iVerticalLine];
+ const SegmentIntersection &itsct = il.intersections[iIntersection];
+ const SegmentIntersection &itsct2 = il.intersections[iIntersection2];
+ const Polygon &poly = poly_with_offset.contour(iInnerContour);
+ assert(itsct.is_inner());
+ assert(itsct2.is_inner());
+ assert(itsct.type != itsct2.type);
+ assert(itsct.iContour == iInnerContour);
+ assert(itsct.iContour == itsct2.iContour);
+ // Do not append the first point.
+ // out.points.push_back(Point(il.pos, itsct.pos));
+ if (forward)
+ polygon_segment_append(out.points, poly, itsct.iSegment, itsct2.iSegment);
+ else
+ polygon_segment_append_reversed(out.points, poly, itsct.iSegment, itsct2.iSegment);
+ // Append the last point.
+ out.points.push_back(itsct2.pos());
+}
+
+//TBD: For precise infill, measure the area of a slab spanned by an infill line.
+/*
+static inline float measure_outer_contour_slab(
+ const ExPolygonWithOffset &poly_with_offset,
+ const std::vector<SegmentedIntersectionLine> &segs,
+ size_t i_vline,
+ size_t iIntersection)
+{
+ const SegmentedIntersectionLine &il = segs[i_vline];
+ const SegmentIntersection &itsct = il.intersections[i_vline];
+ const SegmentIntersection &itsct2 = il.intersections[iIntersection2];
+ const Polygon &poly = poly_with_offset.contour((itsct.iContour);
+ assert(itsct.is_outer());
+ assert(itsct2.is_outer());
+ assert(itsct.type != itsct2.type);
+ assert(itsct.iContour == itsct2.iContour);
+ if (! itsct.is_outer() || ! itsct2.is_outer() || itsct.type == itsct2.type || itsct.iContour != itsct2.iContour)
+ // Error, return zero area.
+ return 0.f;
+
+ // Find possible connection points on the previous / next vertical line.
+ int iPrev = intersection_on_prev_vertical_line(poly_with_offset, segs, i_vline, itsct.iContour, i_intersection);
+ int iNext = intersection_on_next_vertical_line(poly_with_offset, segs, i_vline, itsct.iContour, i_intersection);
+ // Find possible connection points on the same vertical line.
+ int iAbove = iBelow = -1;
+ // Does the perimeter intersect the current vertical line above intrsctn?
+ for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i)
+ if (seg.intersections[i].iContour == itsct.iContour)
+ { iAbove = i; break; }
+ // Does the perimeter intersect the current vertical line below intrsctn?
+ for (int i = int(i_intersection) - 1; i > 0; -- i)
+ if (seg.intersections[i].iContour == itsct.iContour)
+ { iBelow = i; break; }
+
+ if (iSegAbove != -1 && seg.intersections[iAbove].type == SegmentIntersection::OUTER_HIGH) {
+ // Invalidate iPrev resp. iNext, if the perimeter crosses the current vertical line earlier than iPrev resp. iNext.
+ // The perimeter contour orientation.
+ const Polygon &poly = poly_with_offset.contour(itsct.iContour);
+ {
+ int d_horiz = (iPrev == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, segs[i_vline-1].intersections[iPrev].iSegment, itsct.iSegment, true);
+ int d_down = (iBelow == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, iSegBelow, itsct.iSegment, true);
+ int d_up = (iAbove == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, iSegAbove, itsct.iSegment, true);
+ if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up))
+ // The vertical crossing comes eralier than the prev crossing.
+ // Disable the perimeter going back.
+ intrsctn_type_prev = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST;
+ if (d_up > std::min(d_horiz, d_down))
+ // The horizontal crossing comes earlier than the vertical crossing.
+ vert_seg_dir_valid_mask &= ~DIR_BACKWARD;
+ }
+ {
+ int d_horiz = (iNext == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, itsct.iSegment, segs[i_vline+1].intersections[iNext].iSegment, true);
+ int d_down = (iSegBelow == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, itsct.iSegment, iSegBelow, true);
+ int d_up = (iSegAbove == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, itsct.iSegment, iSegAbove, true);
+ if (d_up > std::min(d_horiz, d_down))
+ // The horizontal crossing comes earlier than the vertical crossing.
+ vert_seg_dir_valid_mask &= ~DIR_FORWARD;
+ }
+ }
+}
+*/
+
+enum DirectionMask
+{
+ DIR_FORWARD = 1,
+ DIR_BACKWARD = 2
+};
+
+// For the rectilinear, grid, triangles, stars and cubic pattern fill one InfillHatchingSingleDirection structure
+// for each infill direction. The segments stored in InfillHatchingSingleDirection will then form a graph of candidate
+// paths to be extruded.
+static bool fill_hatching_segments_legacy(
+ // Input geometry to be hatch, containing two concentric contours for each input contour.
+ const ExPolygonWithOffset &poly_with_offset,
+ // fill density, dont_adjust
+ const FillParams &params,
+ const coord_t link_max_length,
+ // Resulting straight segments of the infill graph.
+ InfillHatchingSingleDirection &hatching,
+ Polylines &polylines_out)
+{
+ // At the end, only the new polylines will be rotated back.
+ size_t n_polylines_out_initial = polylines_out.size();
+
+ std::vector<SegmentedIntersectionLine> &segs = hatching.segs;
+
+ // For each outer only chords, measure their maximum distance to the bow of the outer contour.
+ // Mark an outer only chord as consumed, if the distance is low.
+ for (size_t i_vline = 0; i_vline < segs.size(); ++ i_vline) {
+ SegmentedIntersectionLine &seg = segs[i_vline];
+ for (size_t i_intersection = 0; i_intersection + 1 < seg.intersections.size(); ++ i_intersection) {
+ if (seg.intersections[i_intersection].type == SegmentIntersection::OUTER_LOW &&
+ seg.intersections[i_intersection+1].type == SegmentIntersection::OUTER_HIGH) {
+ bool consumed = false;
+// if (params.full_infill()) {
+// measure_outer_contour_slab(poly_with_offset, segs, i_vline, i_ntersection);
+// } else
+ consumed = true;
+ seg.intersections[i_intersection].consumed_vertical_up = consumed;
+ }
+ }
+ }
+
+ // Now construct a graph.
+ // Find the first point.
+ // Naively one would expect to achieve best results by chaining the paths by the shortest distance,
+ // but that procedure does not create the longest continuous paths.
+ // A simple "sweep left to right" procedure achieves better results.
+ size_t i_vline = 0;
+ size_t i_intersection = size_t(-1);
+ // Follow the line, connect the lines into a graph.
+ // Until no new line could be added to the output path:
+ Point pointLast;
+ Polyline *polyline_current = NULL;
+ if (! polylines_out.empty())
+ pointLast = polylines_out.back().points.back();
+ for (;;) {
+ if (i_intersection == size_t(-1)) {
+ // The path has been interrupted. Find a next starting point, closest to the previous extruder position.
+ coordf_t dist2min = std::numeric_limits<coordf_t>().max();
+ for (size_t i_vline2 = 0; i_vline2 < segs.size(); ++ i_vline2) {
+ const SegmentedIntersectionLine &seg = segs[i_vline2];
+ if (! seg.intersections.empty()) {
+ assert(seg.intersections.size() > 1);
+ // Even number of intersections with the loops.
+ assert((seg.intersections.size() & 1) == 0);
+ assert(seg.intersections.front().type == SegmentIntersection::OUTER_LOW);
+ for (size_t i = 0; i < seg.intersections.size(); ++ i) {
+ const SegmentIntersection &intrsctn = seg.intersections[i];
+ if (intrsctn.is_outer()) {
+ assert(intrsctn.is_low() || i > 0);
+ bool consumed = intrsctn.is_low() ?
+ intrsctn.consumed_vertical_up :
+ seg.intersections[i-1].consumed_vertical_up;
+ if (! consumed) {
+ coordf_t dist2 = (intrsctn.pos() - pointLast).cast<double>().norm();
+ if (dist2 < dist2min) {
+ dist2min = dist2;
+ i_vline = i_vline2;
+ i_intersection = i;
+ //FIXME We are taking the first left point always. Verify, that the caller chains the paths
+ // by a shortest distance, while reversing the paths if needed.
+ //if (polylines_out.empty())
+ // Initial state, take the first line, which is the first from the left.
+ goto found;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (i_intersection == size_t(-1))
+ // We are finished.
+ break;
+ found:
+ // Start a new path.
+ polylines_out.push_back(Polyline());
+ polyline_current = &polylines_out.back();
+ // Emit the first point of a path.
+ pointLast = segs[i_vline].intersections[i_intersection].pos();
+ polyline_current->points.push_back(pointLast);
+ }
+
+ // From the initial point (i_vline, i_intersection), follow a path.
+ SegmentedIntersectionLine &seg = segs[i_vline];
+ SegmentIntersection *intrsctn = &seg.intersections[i_intersection];
+ bool going_up = intrsctn->is_low();
+ bool try_connect = false;
+ if (going_up) {
+ assert(! intrsctn->consumed_vertical_up);
+ assert(i_intersection + 1 < seg.intersections.size());
+ // Step back to the beginning of the vertical segment to mark it as consumed.
+ if (intrsctn->is_inner()) {
+ assert(i_intersection > 0);
+ -- intrsctn;
+ -- i_intersection;
+ }
+ // Consume the complete vertical segment up to the outer contour.
+ do {
+ intrsctn->consumed_vertical_up = true;
+ ++ intrsctn;
+ ++ i_intersection;
+ assert(i_intersection < seg.intersections.size());
+ } while (intrsctn->type != SegmentIntersection::OUTER_HIGH);
+ if ((intrsctn - 1)->is_inner()) {
+ // Step back.
+ -- intrsctn;
+ -- i_intersection;
+ assert(intrsctn->type == SegmentIntersection::INNER_HIGH);
+ try_connect = true;
+ }
+ } else {
+ // Going down.
+ assert(intrsctn->is_high());
+ assert(i_intersection > 0);
+ assert(! (intrsctn - 1)->consumed_vertical_up);
+ // Consume the complete vertical segment up to the outer contour.
+ if (intrsctn->is_inner())
+ intrsctn->consumed_vertical_up = true;
+ do {
+ assert(i_intersection > 0);
+ -- intrsctn;
+ -- i_intersection;
+ intrsctn->consumed_vertical_up = true;
+ } while (intrsctn->type != SegmentIntersection::OUTER_LOW);
+ if ((intrsctn + 1)->is_inner()) {
+ // Step back.
+ ++ intrsctn;
+ ++ i_intersection;
+ assert(intrsctn->type == SegmentIntersection::INNER_LOW);
+ try_connect = true;
+ }
+ }
+ if (try_connect) {
+ // Decide, whether to finish the segment, or whether to follow the perimeter.
+
+ // 1) Find possible connection points on the previous / next vertical line.
+ int iPrev = intersection_on_prev_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection);
+ int iNext = intersection_on_next_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection);
+ IntersectionTypeOtherVLine intrsctn_type_prev = intersection_type_on_prev_vertical_line(segs, i_vline, i_intersection, iPrev);
+ IntersectionTypeOtherVLine intrsctn_type_next = intersection_type_on_next_vertical_line(segs, i_vline, i_intersection, iNext);
+
+ // 2) Find possible connection points on the same vertical line.
+ int iAbove = -1;
+ int iBelow = -1;
+ int iSegAbove = -1;
+ int iSegBelow = -1;
+ {
+ SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ?
+ SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW;
+ // Does the perimeter intersect the current vertical line above intrsctn?
+ for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i)
+// if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) {
+ if (seg.intersections[i].iContour == intrsctn->iContour) {
+ iAbove = i;
+ iSegAbove = seg.intersections[i].iSegment;
+ break;
+ }
+ // Does the perimeter intersect the current vertical line below intrsctn?
+ for (size_t i = i_intersection - 1; i > 0; -- i)
+// if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) {
+ if (seg.intersections[i].iContour == intrsctn->iContour) {
+ iBelow = i;
+ iSegBelow = seg.intersections[i].iSegment;
+ break;
+ }
+ }
+
+ // 3) Sort the intersection points, clear iPrev / iNext / iSegBelow / iSegAbove,
+ // if it is preceded by any other intersection point along the contour.
+ unsigned int vert_seg_dir_valid_mask =
+ (going_up ?
+ (iSegAbove != -1 && seg.intersections[iAbove].type == SegmentIntersection::INNER_LOW) :
+ (iSegBelow != -1 && seg.intersections[iBelow].type == SegmentIntersection::INNER_HIGH)) ?
+ (DIR_FORWARD | DIR_BACKWARD) :
+ 0;
+ {
+ // Invalidate iPrev resp. iNext, if the perimeter crosses the current vertical line earlier than iPrev resp. iNext.
+ // The perimeter contour orientation.
+ const bool forward = intrsctn->is_low(); // == poly_with_offset.is_contour_ccw(intrsctn->iContour);
+ const Polygon &poly = poly_with_offset.contour(intrsctn->iContour);
+ {
+ int d_horiz = (iPrev == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, segs[i_vline-1].intersections[iPrev].iSegment, intrsctn->iSegment, forward);
+ int d_down = (iSegBelow == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, iSegBelow, intrsctn->iSegment, forward);
+ int d_up = (iSegAbove == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, iSegAbove, intrsctn->iSegment, forward);
+ if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up))
+ // The vertical crossing comes eralier than the prev crossing.
+ // Disable the perimeter going back.
+ intrsctn_type_prev = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST;
+ if (going_up ? (d_up > std::min(d_horiz, d_down)) : (d_down > std::min(d_horiz, d_up)))
+ // The horizontal crossing comes earlier than the vertical crossing.
+ vert_seg_dir_valid_mask &= ~(forward ? DIR_BACKWARD : DIR_FORWARD);
+ }
+ {
+ int d_horiz = (iNext == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, intrsctn->iSegment, segs[i_vline+1].intersections[iNext].iSegment, forward);
+ int d_down = (iSegBelow == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, intrsctn->iSegment, iSegBelow, forward);
+ int d_up = (iSegAbove == -1) ? std::numeric_limits<int>::max() :
+ distance_of_segmens(poly, intrsctn->iSegment, iSegAbove, forward);
+ if (intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK && d_horiz > std::min(d_down, d_up))
+ // The vertical crossing comes eralier than the prev crossing.
+ // Disable the perimeter going forward.
+ intrsctn_type_next = INTERSECTION_TYPE_OTHER_VLINE_NOT_FIRST;
+ if (going_up ? (d_up > std::min(d_horiz, d_down)) : (d_down > std::min(d_horiz, d_up)))
+ // The horizontal crossing comes earlier than the vertical crossing.
+ vert_seg_dir_valid_mask &= ~(forward ? DIR_FORWARD : DIR_BACKWARD);
+ }
+ }
+
+ // 4) Try to connect to a previous or next vertical line, making a zig-zag pattern.
+ if (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK || intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK) {
+ coordf_t distPrev = (intrsctn_type_prev != INTERSECTION_TYPE_OTHER_VLINE_OK) ? std::numeric_limits<coord_t>::max() :
+ measure_perimeter_prev_segment_length(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iPrev);
+ coordf_t distNext = (intrsctn_type_next != INTERSECTION_TYPE_OTHER_VLINE_OK) ? std::numeric_limits<coord_t>::max() :
+ measure_perimeter_next_segment_length(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext);
+ // Take the shorter path.
+ //FIXME this may not be always the best strategy to take the shortest connection line now.
+ bool take_next = (intrsctn_type_prev == INTERSECTION_TYPE_OTHER_VLINE_OK && intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK) ?
+ (distNext < distPrev) :
+ intrsctn_type_next == INTERSECTION_TYPE_OTHER_VLINE_OK;
+ assert(intrsctn->is_inner());
+ bool skip = params.dont_connect || (link_max_length > 0 && (take_next ? distNext : distPrev) > link_max_length);
+ if (skip) {
+ // Just skip the connecting contour and start a new path.
+ goto dont_connect;
+ polyline_current->points.push_back(intrsctn->pos());
+ polylines_out.push_back(Polyline());
+ polyline_current = &polylines_out.back();
+ const SegmentedIntersectionLine &il2 = segs[take_next ? (i_vline + 1) : (i_vline - 1)];
+ polyline_current->points.push_back(il2.intersections[take_next ? iNext : iPrev].pos());
+ } else {
+ polyline_current->points.push_back(intrsctn->pos());
+ emit_perimeter_prev_next_segment(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, take_next ? iNext : iPrev, *polyline_current, take_next);
+ }
+ // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed.
+ if (iPrev != -1)
+ segs[i_vline-1].intersections[iPrev].consumed_perimeter_right = true;
+ if (iNext != -1)
+ intrsctn->consumed_perimeter_right = true;
+ //FIXME consume the left / right connecting segments at the other end of this line? Currently it is not critical because a perimeter segment is not followed if the vertical segment at the other side has already been consumed.
+ // Advance to the neighbor line.
+ if (take_next) {
+ ++ i_vline;
+ i_intersection = iNext;
+ } else {
+ -- i_vline;
+ i_intersection = iPrev;
+ }
+ continue;
+ }
+
+ // 5) Try to connect to a previous or next point on the same vertical line.
+ if (vert_seg_dir_valid_mask) {
+ bool valid = true;
+ // Verify, that there is no intersection with the inner contour up to the end of the contour segment.
+ // Verify, that the successive segment has not been consumed yet.
+ if (going_up) {
+ if (seg.intersections[iAbove].consumed_vertical_up) {
+ valid = false;
+ } else {
+ for (int i = (int)i_intersection + 1; i < iAbove && valid; ++i)
+ if (seg.intersections[i].is_inner())
+ valid = false;
+ }
+ } else {
+ if (seg.intersections[iBelow-1].consumed_vertical_up) {
+ valid = false;
+ } else {
+ for (int i = iBelow + 1; i < (int)i_intersection && valid; ++i)
+ if (seg.intersections[i].is_inner())
+ valid = false;
+ }
+ }
+ if (valid) {
+ const Polygon &poly = poly_with_offset.contour(intrsctn->iContour);
+ int iNext = going_up ? iAbove : iBelow;
+ int iSegNext = going_up ? iSegAbove : iSegBelow;
+ bool dir_forward = (vert_seg_dir_valid_mask == (DIR_FORWARD | DIR_BACKWARD)) ?
+ // Take the shorter length between the current and the next intersection point.
+ (distance_of_segmens(poly, intrsctn->iSegment, iSegNext, true) <
+ distance_of_segmens(poly, intrsctn->iSegment, iSegNext, false)) :
+ (vert_seg_dir_valid_mask == DIR_FORWARD);
+ // Skip this perimeter line?
+ bool skip = params.dont_connect;
+ if (! skip && link_max_length > 0) {
+ coordf_t link_length = measure_perimeter_segment_on_vertical_line_length(
+ poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, dir_forward);
+ skip = link_length > link_max_length;
+ }
+ polyline_current->points.push_back(intrsctn->pos());
+ if (skip) {
+ // Just skip the connecting contour and start a new path.
+ polylines_out.push_back(Polyline());
+ polyline_current = &polylines_out.back();
+ polyline_current->points.push_back(seg.intersections[iNext].pos());
+ } else {
+ // Consume the connecting contour and the next segment.
+ emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, intrsctn->iContour, i_intersection, iNext, *polyline_current, dir_forward);
+ }
+ // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed.
+ // If there are any outer intersection points skipped (bypassed) by the contour,
+ // mark them as processed.
+ if (going_up) {
+ for (int i = (int)i_intersection; i < iAbove; ++ i)
+ seg.intersections[i].consumed_vertical_up = true;
+ } else {
+ for (int i = iBelow; i < (int)i_intersection; ++ i)
+ seg.intersections[i].consumed_vertical_up = true;
+ }
+// seg.intersections[going_up ? i_intersection : i_intersection - 1].consumed_vertical_up = true;
+ intrsctn->consumed_perimeter_right = true;
+ i_intersection = iNext;
+ if (going_up)
+ ++ intrsctn;
+ else
+ -- intrsctn;
+ intrsctn->consumed_perimeter_right = true;
+ continue;
+ }
+ }
+ dont_connect:
+ // No way to continue the current polyline. Take the rest of the line up to the outer contour.
+ // This will finish the polyline, starting another polyline at a new point.
+ if (going_up)
+ ++ intrsctn;
+ else
+ -- intrsctn;
+ }
+
+ // Finish the current vertical line,
+ // reset the current vertical line to pick a new starting point in the next round.
+ assert(intrsctn->is_outer());
+ assert(intrsctn->is_high() == going_up);
+ pointLast = intrsctn->pos();
+ polyline_current->points.push_back(pointLast);
+ // Handle duplicate points and zero length segments.
+ polyline_current->remove_duplicate_points();
+ assert(! polyline_current->has_duplicate_points());
+ // Handle nearly zero length edges.
+ if (polyline_current->points.size() <= 1 ||
+ (polyline_current->points.size() == 2 &&
+ std::abs(polyline_current->points.front()(0) - polyline_current->points.back()(0)) < SCALED_EPSILON &&
+ std::abs(polyline_current->points.front()(1) - polyline_current->points.back()(1)) < SCALED_EPSILON))
+ polylines_out.pop_back();
+ intrsctn = NULL;
+ i_intersection = -1;
+ polyline_current = NULL;
+ }
+
+#ifdef SLIC3R_DEBUG
+ {
+ static int iRun = 0;
+ BoundingBox bbox_svg = poly_with_offset.bounding_box_outer();
+ {
+ ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-final-%03d.svg", iRun), bbox_svg); // , scale_(1.));
+ poly_with_offset.export_to_svg(svg);
+ for (size_t i = n_polylines_out_initial; i < polylines_out.size(); ++ i)
+ svg.draw(polylines_out[i].lines(), "black");
+ }
+ // Paint a picture per polyline. This makes it easier to discover the order of the polylines and their overlap.
+ for (size_t i_polyline = n_polylines_out_initial; i_polyline < polylines_out.size(); ++ i_polyline) {
+ ::Slic3r::SVG svg(debug_out_path("FillRectilinear2-final-%03d-%03d.svg", iRun, i_polyline), bbox_svg); // , scale_(1.));
+ svg.draw(polylines_out[i_polyline].lines(), "black");
+ }
+ }
+#endif /* SLIC3R_DEBUG */
+
+ // paths must be rotated back
+ for (Polylines::iterator it = polylines_out.begin() + n_polylines_out_initial; it != polylines_out.end(); ++ it) {
+ // No need to translate, the absolute position is irrelevant.
+ // it->translate(- rotate_vector.second(0), - rotate_vector.second(1));
+ assert(! it->has_duplicate_points());
+ //it->rotate(rotate_vector.first);
+ //FIXME rather simplify the paths to avoid very short edges?
+ //assert(! it->has_duplicate_points());
+ it->remove_duplicate_points();
+ }
+
+#ifdef SLIC3R_DEBUG
+ // Verify, that there are no duplicate points in the sequence.
+ for (Polyline &polyline : polylines_out)
+ assert(! polyline.has_duplicate_points());
+#endif /* SLIC3R_DEBUG */
+
+ return true;
+}
+
+}; // namespace FillRectilinear3_Internal
+
+bool FillRectilinear3::fill_surface_by_lines(const Surface *surface, const FillParams &params, std::vector<FillDirParams> &fill_dir_params, Polylines &polylines_out)
+{
+ assert(params.density > 0.0001f && params.density <= 1.f);
+
+ const float INFILL_OVERLAP_OVER_SPACING = 0.45f;
+ assert(INFILL_OVERLAP_OVER_SPACING > 0 && INFILL_OVERLAP_OVER_SPACING < 0.5f);
+
+ // On the polygons of poly_with_offset, the infill lines will be connected.
+ FillRectilinear3_Internal::ExPolygonWithOffset poly_with_offset(
+ surface->expolygon,
+ float(scale_(- (0.5 - INFILL_OVERLAP_OVER_SPACING) * this->spacing)),
+ float(scale_(- 0.5 * this->spacing)));
+ if (poly_with_offset.n_contours_inner == 0) {
+ // Not a single infill line fits.
+ //FIXME maybe one shall trigger the gap fill here?
+ return true;
+ }
+
+ // Rotate polygons so that we can work with vertical lines here
+ std::pair<float, Point> rotate_vector = this->_infill_direction(surface);
+ std::vector<FillRectilinear3_Internal::InfillHatchingSingleDirection> hatching(fill_dir_params.size(), FillRectilinear3_Internal::InfillHatchingSingleDirection());
+ for (size_t i = 0; i < hatching.size(); ++ i)
+ if (! FillRectilinear3_Internal::prepare_infill_hatching_segments(poly_with_offset, params, fill_dir_params[i], rotate_vector, hatching[i]))
+ return false;
+
+ for (size_t i = 0; i < hatching.size(); ++ i)
+ if (! FillRectilinear3_Internal::fill_hatching_segments_legacy(
+ poly_with_offset,
+ params,
+ this->link_max_length,
+ hatching[i],
+ polylines_out))
+ return false;
+
+ return true;
+}
+
+Polylines FillRectilinear3::fill_surface(const Surface *surface, const FillParams &params)
+{
+ Polylines polylines_out;
+ std::vector<FillDirParams> fill_dir_params;
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, 0.f));
+ if (! fill_surface_by_lines(surface, params, fill_dir_params, polylines_out))
+ printf("FillRectilinear3::fill_surface() failed to fill a region.\n");
+ if (params.full_infill() && ! params.dont_adjust)
+ // Return back the adjusted spacing.
+ this->spacing = fill_dir_params.front().spacing;
+ return polylines_out;
+}
+
+Polylines FillGrid3::fill_surface(const Surface *surface, const FillParams &params)
+{
+ // Each linear fill covers half of the target coverage.
+ FillParams params2 = params;
+ params2.density *= 0.5f;
+ Polylines polylines_out;
+ std::vector<FillDirParams> fill_dir_params;
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, 0.f));
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, float(M_PI / 2.)));
+ if (! fill_surface_by_lines(surface, params2, fill_dir_params, polylines_out))
+ printf("FillGrid3::fill_surface() failed to fill a region.\n");
+ return polylines_out;
+}
+
+Polylines FillTriangles3::fill_surface(const Surface *surface, const FillParams &params)
+{
+ // Each linear fill covers 1/3 of the target coverage.
+ FillParams params2 = params;
+ params2.density *= 0.333333333f;
+ Polylines polylines_out;
+ std::vector<FillDirParams> fill_dir_params;
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, 0.));
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, M_PI / 3.));
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, 2. * M_PI / 3.));
+ if (! fill_surface_by_lines(surface, params2, fill_dir_params, polylines_out))
+ printf("FillTriangles3::fill_surface() failed to fill a region.\n");
+ return polylines_out;
+}
+
+Polylines FillStars3::fill_surface(const Surface *surface, const FillParams &params)
+{
+ // Each linear fill covers 1/3 of the target coverage.
+ FillParams params2 = params;
+ params2.density *= 0.333333333f;
+ Polylines polylines_out;
+ std::vector<FillDirParams> fill_dir_params;
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, 0.));
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, M_PI / 3.));
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, 2. * M_PI / 3., 0.5 * this->spacing / params2.density));
+ if (! fill_surface_by_lines(surface, params2, fill_dir_params, polylines_out))
+ printf("FillStars3::fill_surface() failed to fill a region.\n");
+ return polylines_out;
+}
+
+Polylines FillCubic3::fill_surface(const Surface *surface, const FillParams &params)
+{
+ // Each linear fill covers 1/3 of the target coverage.
+ FillParams params2 = params;
+ params2.density *= 0.333333333f;
+ Polylines polylines_out;
+ std::vector<FillDirParams> fill_dir_params;
+ coordf_t dx = sqrt(0.5) * z;
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, 0., dx));
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, M_PI / 3., -dx));
+ fill_dir_params.emplace_back(FillDirParams(this->spacing, 2. * M_PI / 3., dx));
+ if (! fill_surface_by_lines(surface, params2, fill_dir_params, polylines_out))
+ printf("FillCubic3::fill_surface() failed to fill a region.\n");
+ return polylines_out;
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/Fill/FillRectilinear3.hpp b/src/libslic3r/Fill/FillRectilinear3.hpp
new file mode 100644
index 000000000..2023a25b7
--- /dev/null
+++ b/src/libslic3r/Fill/FillRectilinear3.hpp
@@ -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_
diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp
new file mode 100644
index 000000000..e71b935db
--- /dev/null
+++ b/src/libslic3r/Flow.cpp
@@ -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);
+}
+
+}
diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp
new file mode 100644
index 000000000..516490b35
--- /dev/null
+++ b/src/libslic3r/Flow.hpp
@@ -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
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
new file mode 100644
index 000000000..43c99f19f
--- /dev/null
+++ b/src/libslic3r/Format/3mf.cpp
@@ -0,0 +1,2022 @@
+#include "../libslic3r.h"
+#include "../Model.hpp"
+#include "../Utils.hpp"
+#include "../GCode.hpp"
+#include "../slic3r/GUI/PresetBundle.hpp"
+
+#include "3mf.hpp"
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <boost/nowide/fstream.hpp>
+
+#include <expat.h>
+#include <Eigen/Dense>
+#include <miniz/miniz_zip.h>
+
+// VERSION NUMBERS
+// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them.
+// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files.
+const unsigned int VERSION_3MF = 1;
+const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file
+
+const std::string MODEL_FOLDER = "3D/";
+const std::string MODEL_EXTENSION = ".model";
+const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA
+const std::string CONTENT_TYPES_FILE = "[Content_Types].xml";
+const std::string RELATIONSHIPS_FILE = "_rels/.rels";
+const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config";
+const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config";
+const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
+
+const char* MODEL_TAG = "model";
+const char* RESOURCES_TAG = "resources";
+const char* OBJECT_TAG = "object";
+const char* MESH_TAG = "mesh";
+const char* VERTICES_TAG = "vertices";
+const char* VERTEX_TAG = "vertex";
+const char* TRIANGLES_TAG = "triangles";
+const char* TRIANGLE_TAG = "triangle";
+const char* COMPONENTS_TAG = "components";
+const char* COMPONENT_TAG = "component";
+const char* BUILD_TAG = "build";
+const char* ITEM_TAG = "item";
+const char* METADATA_TAG = "metadata";
+
+const char* CONFIG_TAG = "config";
+const char* VOLUME_TAG = "volume";
+
+const char* UNIT_ATTR = "unit";
+const char* NAME_ATTR = "name";
+const char* TYPE_ATTR = "type";
+const char* ID_ATTR = "id";
+const char* X_ATTR = "x";
+const char* Y_ATTR = "y";
+const char* Z_ATTR = "z";
+const char* V1_ATTR = "v1";
+const char* V2_ATTR = "v2";
+const char* V3_ATTR = "v3";
+const char* OBJECTID_ATTR = "objectid";
+const char* TRANSFORM_ATTR = "transform";
+
+const char* KEY_ATTR = "key";
+const char* VALUE_ATTR = "value";
+const char* FIRST_TRIANGLE_ID_ATTR = "firstid";
+const char* LAST_TRIANGLE_ID_ATTR = "lastid";
+
+const char* OBJECT_TYPE = "object";
+const char* VOLUME_TYPE = "volume";
+
+const char* NAME_KEY = "name";
+const char* MODIFIER_KEY = "modifier";
+const char* VOLUME_TYPE_KEY = "volume_type";
+
+const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
+const char* VALID_OBJECT_TYPES[] =
+{
+ "model"
+};
+
+const unsigned int INVALID_OBJECT_TYPES_COUNT = 4;
+const char* INVALID_OBJECT_TYPES[] =
+{
+ "solidsupport",
+ "support",
+ "surface",
+ "other"
+};
+
+const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key)
+{
+ if ((attributes == nullptr) || (attributes_size == 0) || (attributes_size % 2 != 0) || (attribute_key == nullptr))
+ return nullptr;
+
+ for (unsigned int a = 0; a < attributes_size; a += 2)
+ {
+ if (::strcmp(attributes[a], attribute_key) == 0)
+ return attributes[a + 1];
+ }
+
+ return nullptr;
+}
+
+std::string get_attribute_value_string(const char** attributes, unsigned int attributes_size, const char* attribute_key)
+{
+ const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key);
+ return (text != nullptr) ? text : "";
+}
+
+float get_attribute_value_float(const char** attributes, unsigned int attributes_size, const char* attribute_key)
+{
+ const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key);
+ return (text != nullptr) ? (float)::atof(text) : 0.0f;
+}
+
+int get_attribute_value_int(const char** attributes, unsigned int attributes_size, const char* attribute_key)
+{
+ const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key);
+ return (text != nullptr) ? ::atoi(text) : 0;
+}
+
+Slic3r::Transform3d get_transform_from_string(const std::string& mat_str)
+{
+ if (mat_str.empty())
+ // empty string means default identity matrix
+ return Slic3r::Transform3d::Identity();
+
+ std::vector<std::string> mat_elements_str;
+ boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on);
+
+ unsigned int size = (unsigned int)mat_elements_str.size();
+ if (size != 12)
+ // invalid data, return identity matrix
+ return Slic3r::Transform3d::Identity();
+
+ Slic3r::Transform3d ret = Slic3r::Transform3d::Identity();
+ unsigned int i = 0;
+ // matrices are stored into 3mf files as 4x3
+ // we need to transpose them
+ for (unsigned int c = 0; c < 4; ++c)
+ {
+ for (unsigned int r = 0; r < 3; ++r)
+ {
+ ret(r, c) = ::atof(mat_elements_str[i++].c_str());
+ }
+ }
+ return ret;
+}
+
+float get_unit_factor(const std::string& unit)
+{
+ const char* text = unit.c_str();
+
+ if (::strcmp(text, "micron") == 0)
+ return 0.001f;
+ else if (::strcmp(text, "centimeter") == 0)
+ return 10.0f;
+ else if (::strcmp(text, "inch") == 0)
+ return 25.4f;
+ else if (::strcmp(text, "foot") == 0)
+ return 304.8f;
+ else if (::strcmp(text, "meter") == 0)
+ return 1000.0f;
+ else
+ // default "millimeters" (see specification)
+ return 1.0f;
+}
+
+bool is_valid_object_type(const std::string& type)
+{
+ // if the type is empty defaults to "model" (see specification)
+ if (type.empty())
+ return true;
+
+ for (unsigned int i = 0; i < VALID_OBJECT_TYPES_COUNT; ++i)
+ {
+ if (::strcmp(type.c_str(), VALID_OBJECT_TYPES[i]) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+namespace Slic3r {
+
+ // Base class with error messages management
+ class _3MF_Base
+ {
+ std::vector<std::string> m_errors;
+
+ protected:
+ void add_error(const std::string& error) { m_errors.push_back(error); }
+ void clear_errors() { m_errors.clear(); }
+
+ public:
+ void log_errors()
+ {
+ for (const std::string& error : m_errors)
+ {
+ printf("%s\n", error.c_str());
+ }
+ }
+ };
+
+ class _3MF_Importer : public _3MF_Base
+ {
+ struct Component
+ {
+ int object_id;
+ Transform3d transform;
+
+ explicit Component(int object_id)
+ : object_id(object_id)
+ , transform(Transform3d::Identity())
+ {
+ }
+
+ Component(int object_id, const Transform3d& transform)
+ : object_id(object_id)
+ , transform(transform)
+ {
+ }
+ };
+
+ typedef std::vector<Component> ComponentsList;
+
+ struct Geometry
+ {
+ std::vector<float> vertices;
+ std::vector<unsigned int> triangles;
+
+ bool empty()
+ {
+ return vertices.empty() || triangles.empty();
+ }
+
+ void reset()
+ {
+ vertices.clear();
+ triangles.clear();
+ }
+ };
+
+ struct CurrentObject
+ {
+ int id;
+ Geometry geometry;
+ ModelObject* object;
+ ComponentsList components;
+
+ CurrentObject()
+ {
+ reset();
+ }
+
+ void reset()
+ {
+ id = -1;
+ geometry.reset();
+ object = nullptr;
+ components.clear();
+ }
+ };
+
+ struct CurrentConfig
+ {
+ int object_id;
+ int volume_id;
+ };
+
+ struct Instance
+ {
+ ModelInstance* instance;
+ Transform3d transform;
+
+ Instance(ModelInstance* instance, const Transform3d& transform)
+ : instance(instance)
+ , transform(transform)
+ {
+ }
+ };
+
+ struct Metadata
+ {
+ std::string key;
+ std::string value;
+
+ Metadata(const std::string& key, const std::string& value)
+ : key(key)
+ , value(value)
+ {
+ }
+ };
+
+ typedef std::vector<Metadata> MetadataList;
+
+ struct ObjectMetadata
+ {
+ struct VolumeMetadata
+ {
+ unsigned int first_triangle_id;
+ unsigned int last_triangle_id;
+ MetadataList metadata;
+
+ VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id)
+ : first_triangle_id(first_triangle_id)
+ , last_triangle_id(last_triangle_id)
+ {
+ }
+ };
+
+ typedef std::vector<VolumeMetadata> VolumeMetadataList;
+
+ MetadataList metadata;
+ VolumeMetadataList volumes;
+ };
+
+ typedef std::map<int, ModelObject*> IdToModelObjectMap;
+ typedef std::map<int, ComponentsList> IdToAliasesMap;
+ typedef std::vector<Instance> InstancesList;
+ typedef std::map<int, ObjectMetadata> IdToMetadataMap;
+ typedef std::map<int, Geometry> IdToGeometryMap;
+ typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
+
+ // Version of the 3mf file
+ unsigned int m_version;
+
+ XML_Parser m_xml_parser;
+ Model* m_model;
+ float m_unit_factor;
+ CurrentObject m_curr_object;
+ IdToModelObjectMap m_objects;
+ IdToAliasesMap m_objects_aliases;
+ InstancesList m_instances;
+ IdToGeometryMap m_geometries;
+ CurrentConfig m_curr_config;
+ IdToMetadataMap m_objects_metadata;
+ IdToLayerHeightsProfileMap m_layer_heights_profiles;
+ std::string m_curr_metadata_name;
+ std::string m_curr_characters;
+
+ public:
+ _3MF_Importer();
+ ~_3MF_Importer();
+
+ bool load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle);
+
+ private:
+ void _destroy_xml_parser();
+ void _stop_xml_parser();
+
+ bool _load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle);
+ bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
+ void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
+ void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename);
+ bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
+
+ // handlers to parse the .model file
+ void _handle_start_model_xml_element(const char* name, const char** attributes);
+ void _handle_end_model_xml_element(const char* name);
+ void _handle_model_xml_characters(const XML_Char* s, int len);
+
+ // handlers to parse the MODEL_CONFIG_FILE file
+ void _handle_start_config_xml_element(const char* name, const char** attributes);
+ void _handle_end_config_xml_element(const char* name);
+
+ bool _handle_start_model(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_model();
+
+ bool _handle_start_resources(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_resources();
+
+ bool _handle_start_object(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_object();
+
+ bool _handle_start_mesh(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_mesh();
+
+ bool _handle_start_vertices(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_vertices();
+
+ bool _handle_start_vertex(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_vertex();
+
+ bool _handle_start_triangles(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_triangles();
+
+ bool _handle_start_triangle(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_triangle();
+
+ bool _handle_start_components(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_components();
+
+ bool _handle_start_component(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_component();
+
+ bool _handle_start_build(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_build();
+
+ bool _handle_start_item(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_item();
+
+ bool _handle_start_metadata(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_metadata();
+
+ bool _create_object_instance(int object_id, const Transform3d& transform, unsigned int recur_counter);
+
+ void _apply_transform(ModelInstance& instance, const Transform3d& transform);
+
+ bool _handle_start_config(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_config();
+
+ bool _handle_start_config_object(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_config_object();
+
+ bool _handle_start_config_volume(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_config_volume();
+
+ bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_config_metadata();
+
+ bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes);
+
+ // callbacks to parse the .model file
+ static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
+ static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name);
+ static void XMLCALL _handle_model_xml_characters(void* userData, const XML_Char* s, int len);
+
+ // callbacks to parse the MODEL_CONFIG_FILE file
+ static void XMLCALL _handle_start_config_xml_element(void* userData, const char* name, const char** attributes);
+ static void XMLCALL _handle_end_config_xml_element(void* userData, const char* name);
+ };
+
+ _3MF_Importer::_3MF_Importer()
+ : m_version(0)
+ , m_xml_parser(nullptr)
+ , m_model(nullptr)
+ , m_unit_factor(1.0f)
+ , m_curr_metadata_name("")
+ , m_curr_characters("")
+ {
+ }
+
+ _3MF_Importer::~_3MF_Importer()
+ {
+ _destroy_xml_parser();
+ }
+
+ bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle)
+ {
+ m_version = 0;
+ m_model = &model;
+ m_unit_factor = 1.0f;
+ m_curr_object.reset();
+ m_objects.clear();
+ m_objects_aliases.clear();
+ m_instances.clear();
+ m_geometries.clear();
+ m_curr_config.object_id = -1;
+ m_curr_config.volume_id = -1;
+ m_objects_metadata.clear();
+ m_layer_heights_profiles.clear();
+ m_curr_metadata_name.clear();
+ m_curr_characters.clear();
+ clear_errors();
+
+ return _load_model_from_file(filename, model, bundle);
+ }
+
+ void _3MF_Importer::_destroy_xml_parser()
+ {
+ if (m_xml_parser != nullptr)
+ {
+ XML_ParserFree(m_xml_parser);
+ m_xml_parser = nullptr;
+ }
+ }
+
+ void _3MF_Importer::_stop_xml_parser()
+ {
+ if (m_xml_parser != nullptr)
+ XML_StopParser(m_xml_parser, false);
+ }
+
+ bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle)
+ {
+ mz_zip_archive archive;
+ mz_zip_zero_struct(&archive);
+
+ mz_bool res = mz_zip_reader_init_file(&archive, filename.c_str(), 0);
+ if (res == 0)
+ {
+ add_error("Unable to open the file");
+ 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 .model 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))
+ {
+ std::string name(stat.m_filename);
+ std::replace(name.begin(), name.end(), '\\', '/');
+
+ if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION))
+ {
+ // valid model name -> extract model
+ if (!_extract_model_from_archive(archive, stat))
+ {
+ mz_zip_reader_end(&archive);
+ add_error("Archive does not contain a valid model");
+ return false;
+ }
+ }
+ }
+ }
+
+ // 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))
+ {
+ std::string name(stat.m_filename);
+ std::replace(name.begin(), name.end(), '\\', '/');
+
+ if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE))
+ {
+ // extract slic3r lazer heights profile file
+ _extract_layer_heights_profile_config_from_archive(archive, stat);
+ }
+ else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
+ {
+ // extract slic3r print config file
+ _extract_print_config_from_archive(archive, stat, bundle, filename);
+ }
+ else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE))
+ {
+ // extract slic3r model config file
+ if (!_extract_model_config_from_archive(archive, stat, model))
+ {
+ mz_zip_reader_end(&archive);
+ add_error("Archive does not contain a valid model config");
+ return false;
+ }
+ }
+ }
+ }
+
+ mz_zip_reader_end(&archive);
+
+ for (const IdToModelObjectMap::value_type& object : m_objects)
+ {
+ ObjectMetadata::VolumeMetadataList volumes;
+ ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
+
+ IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first);
+ if (obj_geometry == m_geometries.end())
+ {
+ add_error("Unable to find object geometry");
+ return false;
+ }
+
+ IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.first);
+ if (obj_layer_heights_profile != m_layer_heights_profiles.end())
+ {
+ object.second->layer_height_profile = obj_layer_heights_profile->second;
+ object.second->layer_height_profile_valid = true;
+ }
+
+ IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
+ if (obj_metadata != m_objects_metadata.end())
+ {
+ // config data has been found, this model was saved using slic3r pe
+
+ // apply object's name and config data
+ for (const Metadata& metadata : obj_metadata->second.metadata)
+ {
+ if (metadata.key == "name")
+ object.second->name = metadata.value;
+ else
+ object.second->config.set_deserialize(metadata.key, metadata.value);
+ }
+
+ // select object's detected volumes
+ volumes_ptr = &obj_metadata->second.volumes;
+ }
+ else
+ {
+ // config data not found, this model was not saved using slic3r pe
+
+ // add the entire geometry as the single volume to generate
+ volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() / 3 - 1);
+
+ // select as volumes
+ volumes_ptr = &volumes;
+ }
+
+ if (!_generate_volumes(*object.second, obj_geometry->second, *volumes_ptr))
+ return false;
+ }
+
+ // fixes the min z of the model if negative
+ model.adjust_min_z();
+
+ return true;
+ }
+
+ bool _3MF_Importer::_extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
+ {
+ if (stat.m_uncomp_size == 0)
+ {
+ add_error("Found invalid size");
+ return false;
+ }
+
+ _destroy_xml_parser();
+
+ m_xml_parser = XML_ParserCreate(nullptr);
+ if (m_xml_parser == nullptr)
+ {
+ add_error("Unable to create parser");
+ return false;
+ }
+
+ XML_SetUserData(m_xml_parser, (void*)this);
+ XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_model_xml_element, _3MF_Importer::_handle_end_model_xml_element);
+ XML_SetCharacterDataHandler(m_xml_parser, _3MF_Importer::_handle_model_xml_characters);
+
+ void* parser_buffer = XML_GetBuffer(m_xml_parser, (int)stat.m_uncomp_size);
+ if (parser_buffer == nullptr)
+ {
+ add_error("Unable to create buffer");
+ 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)
+ {
+ add_error("Error while reading model data to buffer");
+ return false;
+ }
+
+ if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1))
+ {
+ char error_buf[1024];
+ ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), XML_GetCurrentLineNumber(m_xml_parser));
+ add_error(error_buf);
+ return false;
+ }
+
+ return true;
+ }
+
+ void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename)
+ {
+ if (stat.m_uncomp_size > 0)
+ {
+ std::string buffer((size_t)stat.m_uncomp_size, 0);
+ mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
+ if (res == 0)
+ {
+ add_error("Error while reading config data to buffer");
+ return;
+ }
+ bundle.load_config_string(buffer.data(), archive_filename.c_str());
+ }
+ }
+
+ void _3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
+ {
+ if (stat.m_uncomp_size > 0)
+ {
+ std::string buffer((size_t)stat.m_uncomp_size, 0);
+ mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
+ if (res == 0)
+ {
+ add_error("Error while reading layer heights profile data to buffer");
+ return;
+ }
+
+ if (buffer.back() == '\n')
+ buffer.pop_back();
+
+ std::vector<std::string> objects;
+ boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
+
+ for (const std::string& object : objects)
+ {
+ std::vector<std::string> object_data;
+ boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
+ if (object_data.size() != 2)
+ {
+ add_error("Error while reading object data");
+ continue;
+ }
+
+ std::vector<std::string> object_data_id;
+ boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
+ if (object_data_id.size() != 2)
+ {
+ add_error("Error while reading object id");
+ continue;
+ }
+
+ int object_id = std::atoi(object_data_id[1].c_str());
+ if (object_id == 0)
+ {
+ add_error("Found invalid object id");
+ continue;
+ }
+
+ IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id);
+ if (object_item != m_layer_heights_profiles.end())
+ {
+ add_error("Found duplicated layer heights profile");
+ continue;
+ }
+
+ std::vector<std::string> object_data_profile;
+ boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off);
+ if ((object_data_profile.size() <= 4) || (object_data_profile.size() % 2 != 0))
+ {
+ add_error("Found invalid layer heights profile");
+ continue;
+ }
+
+ std::vector<coordf_t> profile;
+ profile.reserve(object_data_profile.size());
+
+ for (const std::string& value : object_data_profile)
+ {
+ profile.push_back((coordf_t)std::atof(value.c_str()));
+ }
+
+ m_layer_heights_profiles.insert(IdToLayerHeightsProfileMap::value_type(object_id, profile));
+ }
+ }
+ }
+
+ bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
+ {
+ if (stat.m_uncomp_size == 0)
+ {
+ add_error("Found invalid size");
+ return false;
+ }
+
+ _destroy_xml_parser();
+
+ m_xml_parser = XML_ParserCreate(nullptr);
+ if (m_xml_parser == nullptr)
+ {
+ add_error("Unable to create parser");
+ return false;
+ }
+
+ XML_SetUserData(m_xml_parser, (void*)this);
+ XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_config_xml_element, _3MF_Importer::_handle_end_config_xml_element);
+
+ void* parser_buffer = XML_GetBuffer(m_xml_parser, (int)stat.m_uncomp_size);
+ if (parser_buffer == nullptr)
+ {
+ add_error("Unable to create buffer");
+ 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)
+ {
+ add_error("Error while reading config data to buffer");
+ return false;
+ }
+
+ if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1))
+ {
+ char error_buf[1024];
+ ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), XML_GetCurrentLineNumber(m_xml_parser));
+ add_error(error_buf);
+ return false;
+ }
+
+ return true;
+ }
+
+ void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes)
+ {
+ if (m_xml_parser == nullptr)
+ return;
+
+ bool res = true;
+ unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser);
+
+ if (::strcmp(MODEL_TAG, name) == 0)
+ res = _handle_start_model(attributes, num_attributes);
+ else if (::strcmp(RESOURCES_TAG, name) == 0)
+ res = _handle_start_resources(attributes, num_attributes);
+ else if (::strcmp(OBJECT_TAG, name) == 0)
+ res = _handle_start_object(attributes, num_attributes);
+ else if (::strcmp(MESH_TAG, name) == 0)
+ res = _handle_start_mesh(attributes, num_attributes);
+ else if (::strcmp(VERTICES_TAG, name) == 0)
+ res = _handle_start_vertices(attributes, num_attributes);
+ else if (::strcmp(VERTEX_TAG, name) == 0)
+ res = _handle_start_vertex(attributes, num_attributes);
+ else if (::strcmp(TRIANGLES_TAG, name) == 0)
+ res = _handle_start_triangles(attributes, num_attributes);
+ else if (::strcmp(TRIANGLE_TAG, name) == 0)
+ res = _handle_start_triangle(attributes, num_attributes);
+ else if (::strcmp(COMPONENTS_TAG, name) == 0)
+ res = _handle_start_components(attributes, num_attributes);
+ else if (::strcmp(COMPONENT_TAG, name) == 0)
+ res = _handle_start_component(attributes, num_attributes);
+ else if (::strcmp(BUILD_TAG, name) == 0)
+ res = _handle_start_build(attributes, num_attributes);
+ else if (::strcmp(ITEM_TAG, name) == 0)
+ res = _handle_start_item(attributes, num_attributes);
+ else if (::strcmp(METADATA_TAG, name) == 0)
+ res = _handle_start_metadata(attributes, num_attributes);
+
+ if (!res)
+ _stop_xml_parser();
+ }
+
+ void _3MF_Importer::_handle_end_model_xml_element(const char* name)
+ {
+ if (m_xml_parser == nullptr)
+ return;
+
+ bool res = true;
+
+ if (::strcmp(MODEL_TAG, name) == 0)
+ res = _handle_end_model();
+ else if (::strcmp(RESOURCES_TAG, name) == 0)
+ res = _handle_end_resources();
+ else if (::strcmp(OBJECT_TAG, name) == 0)
+ res = _handle_end_object();
+ else if (::strcmp(MESH_TAG, name) == 0)
+ res = _handle_end_mesh();
+ else if (::strcmp(VERTICES_TAG, name) == 0)
+ res = _handle_end_vertices();
+ else if (::strcmp(VERTEX_TAG, name) == 0)
+ res = _handle_end_vertex();
+ else if (::strcmp(TRIANGLES_TAG, name) == 0)
+ res = _handle_end_triangles();
+ else if (::strcmp(TRIANGLE_TAG, name) == 0)
+ res = _handle_end_triangle();
+ else if (::strcmp(COMPONENTS_TAG, name) == 0)
+ res = _handle_end_components();
+ else if (::strcmp(COMPONENT_TAG, name) == 0)
+ res = _handle_end_component();
+ else if (::strcmp(BUILD_TAG, name) == 0)
+ res = _handle_end_build();
+ else if (::strcmp(ITEM_TAG, name) == 0)
+ res = _handle_end_item();
+ else if (::strcmp(METADATA_TAG, name) == 0)
+ res = _handle_end_metadata();
+
+ if (!res)
+ _stop_xml_parser();
+ }
+
+ void _3MF_Importer::_handle_model_xml_characters(const XML_Char* s, int len)
+ {
+ m_curr_characters.append(s, len);
+ }
+
+ void _3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes)
+ {
+ if (m_xml_parser == nullptr)
+ return;
+
+ bool res = true;
+ unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser);
+
+ if (::strcmp(CONFIG_TAG, name) == 0)
+ res = _handle_start_config(attributes, num_attributes);
+ else if (::strcmp(OBJECT_TAG, name) == 0)
+ res = _handle_start_config_object(attributes, num_attributes);
+ else if (::strcmp(VOLUME_TAG, name) == 0)
+ res = _handle_start_config_volume(attributes, num_attributes);
+ else if (::strcmp(METADATA_TAG, name) == 0)
+ res = _handle_start_config_metadata(attributes, num_attributes);
+
+ if (!res)
+ _stop_xml_parser();
+ }
+
+ void _3MF_Importer::_handle_end_config_xml_element(const char* name)
+ {
+ if (m_xml_parser == nullptr)
+ return;
+
+ bool res = true;
+
+ if (::strcmp(CONFIG_TAG, name) == 0)
+ res = _handle_end_config();
+ else if (::strcmp(OBJECT_TAG, name) == 0)
+ res = _handle_end_config_object();
+ else if (::strcmp(VOLUME_TAG, name) == 0)
+ res = _handle_end_config_volume();
+ else if (::strcmp(METADATA_TAG, name) == 0)
+ res = _handle_end_config_metadata();
+
+ if (!res)
+ _stop_xml_parser();
+ }
+
+ bool _3MF_Importer::_handle_start_model(const char** attributes, unsigned int num_attributes)
+ {
+ m_unit_factor = get_unit_factor(get_attribute_value_string(attributes, num_attributes, UNIT_ATTR));
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_model()
+ {
+ // deletes all non-built or non-instanced objects
+ for (const IdToModelObjectMap::value_type& object : m_objects)
+ {
+ if ((object.second != nullptr) && (object.second->instances.size() == 0))
+ m_model->delete_object(object.second);
+ }
+
+ // applies instances' matrices
+ for (Instance& instance : m_instances)
+ {
+ if (instance.instance != nullptr)
+ {
+ ModelObject* object = instance.instance->get_object();
+ if (object != nullptr)
+ {
+ // apply the transform to the instance
+ _apply_transform(*instance.instance, instance.transform);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_resources(const char** attributes, unsigned int num_attributes)
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_resources()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_object(const char** attributes, unsigned int num_attributes)
+ {
+ // reset current data
+ m_curr_object.reset();
+
+ if (is_valid_object_type(get_attribute_value_string(attributes, num_attributes, TYPE_ATTR)))
+ {
+ // create new object (it may be removed later if no instances are generated from it)
+ m_curr_object.object = m_model->add_object();
+ if (m_curr_object.object == nullptr)
+ {
+ add_error("Unable to create object");
+ return false;
+ }
+
+ // set object data
+ m_curr_object.object->name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR);
+ m_curr_object.id = get_attribute_value_int(attributes, num_attributes, ID_ATTR);
+ }
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_object()
+ {
+ if (m_curr_object.object != nullptr)
+ {
+ if (m_curr_object.geometry.empty())
+ {
+ // no geometry defined
+ // remove the object from the model
+ m_model->delete_object(m_curr_object.object);
+
+ if (m_curr_object.components.empty())
+ {
+ // no components defined -> invalid object, delete it
+ IdToModelObjectMap::iterator object_item = m_objects.find(m_curr_object.id);
+ if (object_item != m_objects.end())
+ m_objects.erase(object_item);
+
+ IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id);
+ if (alias_item != m_objects_aliases.end())
+ m_objects_aliases.erase(alias_item);
+ }
+ else
+ // adds components to aliases
+ m_objects_aliases.insert(IdToAliasesMap::value_type(m_curr_object.id, m_curr_object.components));
+ }
+ else
+ {
+ // geometry defined, store it for later use
+ m_geometries.insert(IdToGeometryMap::value_type(m_curr_object.id, std::move(m_curr_object.geometry)));
+
+ // stores the object for later use
+ if (m_objects.find(m_curr_object.id) == m_objects.end())
+ {
+ m_objects.insert(IdToModelObjectMap::value_type(m_curr_object.id, m_curr_object.object));
+ m_objects_aliases.insert(IdToAliasesMap::value_type(m_curr_object.id, ComponentsList(1, Component(m_curr_object.id)))); // aliases itself
+ }
+ else
+ {
+ add_error("Found object with duplicate id");
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_mesh(const char** attributes, unsigned int num_attributes)
+ {
+ // reset current geometry
+ m_curr_object.geometry.reset();
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_mesh()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_vertices(const char** attributes, unsigned int num_attributes)
+ {
+ // reset current vertices
+ m_curr_object.geometry.vertices.clear();
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_vertices()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_vertex(const char** attributes, unsigned int num_attributes)
+ {
+ // appends the vertex coordinates
+ // missing values are set equal to ZERO
+ m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR));
+ m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR));
+ m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR));
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_vertex()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_triangles(const char** attributes, unsigned int num_attributes)
+ {
+ // reset current triangles
+ m_curr_object.geometry.triangles.clear();
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_triangles()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_triangle(const char** attributes, unsigned int num_attributes)
+ {
+ // we are ignoring the following attributes:
+ // p1
+ // p2
+ // p3
+ // pid
+ // see specifications
+
+ // appends the triangle's vertices indices
+ // missing values are set equal to ZERO
+ m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR));
+ m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR));
+ m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR));
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_triangle()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_components(const char** attributes, unsigned int num_attributes)
+ {
+ // reset current components
+ m_curr_object.components.clear();
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_components()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
+ {
+ int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
+ Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
+
+ IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
+ if (object_item == m_objects.end())
+ {
+ IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id);
+ if (alias_item == m_objects_aliases.end())
+ {
+ add_error("Found component with invalid object id");
+ return false;
+ }
+ }
+
+ m_curr_object.components.emplace_back(object_id, transform);
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_component()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_build(const char** attributes, unsigned int num_attributes)
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_build()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_item(const char** attributes, unsigned int num_attributes)
+ {
+ // we are ignoring the following attributes
+ // thumbnail
+ // partnumber
+ // pid
+ // pindex
+ // see specifications
+
+ int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
+ Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
+
+ return _create_object_instance(object_id, transform, 1);
+ }
+
+ bool _3MF_Importer::_handle_end_item()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes)
+ {
+ m_curr_characters.clear();
+
+ std::string name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR);
+ if (!name.empty())
+ m_curr_metadata_name = name;
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_metadata()
+ {
+ if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION)
+ m_version = (unsigned int)atoi(m_curr_characters.c_str());
+
+ return true;
+ }
+
+ bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, unsigned int recur_counter)
+ {
+ static const unsigned int MAX_RECURSIONS = 10;
+
+ // escape from circular aliasing
+ if (recur_counter > MAX_RECURSIONS)
+ {
+ add_error("Too many recursions");
+ return false;
+ }
+
+ IdToAliasesMap::iterator it = m_objects_aliases.find(object_id);
+ if (it == m_objects_aliases.end())
+ {
+ add_error("Found item with invalid object id");
+ return false;
+ }
+
+ if ((it->second.size() == 1) && (it->second[0].object_id == object_id))
+ {
+ // aliasing to itself
+
+ IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
+ if ((object_item == m_objects.end()) || (object_item->second == nullptr))
+ {
+ add_error("Found invalid object");
+ return false;
+ }
+ else
+ {
+ ModelInstance* instance = object_item->second->add_instance();
+ if (instance == nullptr)
+ {
+ add_error("Unable to add object instance");
+ return false;
+ }
+
+ m_instances.emplace_back(instance, transform);
+ }
+ }
+ else
+ {
+ // recursively process nested components
+ for (const Component& component : it->second)
+ {
+ if (!_create_object_instance(component.object_id, transform * component.transform, recur_counter + 1))
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void _3MF_Importer::_apply_transform(ModelInstance& instance, const Transform3d& transform)
+ {
+ // slic3r ModelInstance cannot be transformed using a matrix
+ // we extract from the given matrix only the values currently used
+
+ // translation
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ Vec3d offset(transform(0, 3), transform(1, 3), transform(2, 3));
+#else
+ double offset_x = transform(0, 3);
+ double offset_y = transform(1, 3);
+ double offset_z = transform(2, 3);
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+
+ // scale
+ double sx = ::sqrt(sqr(transform(0, 0)) + sqr(transform(1, 0)) + sqr(transform(2, 0)));
+ double sy = ::sqrt(sqr(transform(0, 1)) + sqr(transform(1, 1)) + sqr(transform(2, 1)));
+ double sz = ::sqrt(sqr(transform(0, 2)) + sqr(transform(1, 2)) + sqr(transform(2, 2)));
+
+ // invalid scale value, return
+ if ((sx == 0.0) || (sy == 0.0) || (sz == 0.0))
+ return;
+
+ // non-uniform scale value, return
+ if ((std::abs(sx - sy) > 0.00001) || (std::abs(sx - sz) > 0.00001))
+ return;
+
+ double inv_sx = 1.0 / sx;
+ double inv_sy = 1.0 / sy;
+ double inv_sz = 1.0 / sz;
+
+ Eigen::Matrix3d m3x3;
+ m3x3 << transform(0, 0) * inv_sx, transform(0, 1) * inv_sy, transform(0, 2) * inv_sz,
+ transform(1, 0) * inv_sx, transform(1, 1) * inv_sy, transform(1, 2) * inv_sz,
+ transform(2, 0) * inv_sx, transform(2, 1) * inv_sy, transform(2, 2) * inv_sz;
+
+ Eigen::AngleAxisd rotation;
+ rotation.fromRotationMatrix(m3x3);
+
+ // invalid rotation axis, we currently handle only rotations around Z axis
+ if ((rotation.angle() != 0.0) && (rotation.axis() != Vec3d::UnitZ()) && (rotation.axis() != -Vec3d::UnitZ()))
+ return;
+
+ double angle_z = (rotation.axis() == Vec3d::UnitZ()) ? rotation.angle() : -rotation.angle();
+
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ instance.set_offset(offset);
+#else
+ instance.offset(0) = offset_x;
+ instance.offset(1) = offset_y;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ instance.scaling_factor = sx;
+ instance.rotation = angle_z;
+ }
+
+ bool _3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes)
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_config()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_config_object(const char** attributes, unsigned int num_attributes)
+ {
+ int object_id = get_attribute_value_int(attributes, num_attributes, ID_ATTR);
+ IdToMetadataMap::iterator object_item = m_objects_metadata.find(object_id);
+ if (object_item != m_objects_metadata.end())
+ {
+ add_error("Found duplicated object id");
+ return false;
+ }
+
+ m_objects_metadata.insert(IdToMetadataMap::value_type(object_id, ObjectMetadata()));
+ m_curr_config.object_id = object_id;
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_config_object()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_config_volume(const char** attributes, unsigned int num_attributes)
+ {
+ IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id);
+ if (object == m_objects_metadata.end())
+ {
+ add_error("Cannot assign volume to a valid object");
+ return false;
+ }
+
+ m_curr_config.volume_id = object->second.volumes.size();
+
+ unsigned int first_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, FIRST_TRIANGLE_ID_ATTR);
+ unsigned int last_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, LAST_TRIANGLE_ID_ATTR);
+
+ object->second.volumes.emplace_back(first_triangle_id, last_triangle_id);
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_config_volume()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_config_metadata(const char** attributes, unsigned int num_attributes)
+ {
+ IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id);
+ if (object == m_objects_metadata.end())
+ {
+ add_error("Cannot assign metadata to valid object id");
+ return false;
+ }
+
+ std::string type = get_attribute_value_string(attributes, num_attributes, TYPE_ATTR);
+ std::string key = get_attribute_value_string(attributes, num_attributes, KEY_ATTR);
+ std::string value = get_attribute_value_string(attributes, num_attributes, VALUE_ATTR);
+
+ if (type == OBJECT_TYPE)
+ object->second.metadata.emplace_back(key, value);
+ else if (type == VOLUME_TYPE)
+ {
+ if (m_curr_config.volume_id < object->second.volumes.size())
+ object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value);
+ }
+ else
+ {
+ add_error("Found invalid metadata type");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_config_metadata()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes)
+ {
+ if (!object.volumes.empty())
+ {
+ add_error("Found invalid volumes count");
+ return false;
+ }
+
+ unsigned int geo_tri_count = geometry.triangles.size() / 3;
+
+ for (const ObjectMetadata::VolumeMetadata& volume_data : volumes)
+ {
+ if ((geo_tri_count <= volume_data.first_triangle_id) || (geo_tri_count <= volume_data.last_triangle_id) || (volume_data.last_triangle_id < volume_data.first_triangle_id))
+ {
+ add_error("Found invalid triangle id");
+ return false;
+ }
+
+ // splits volume out of imported geometry
+ unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1;
+ ModelVolume* volume = object.add_volume(TriangleMesh());
+ stl_file& stl = volume->mesh.stl;
+ stl.stats.type = inmemory;
+ stl.stats.number_of_facets = (uint32_t)triangles_count;
+ stl.stats.original_num_facets = (int)stl.stats.number_of_facets;
+ stl_allocate(&stl);
+
+ unsigned int src_start_id = volume_data.first_triangle_id * 3;
+
+ for (size_t i = 0; i < triangles_count; ++i)
+ {
+ unsigned int ii = i * 3;
+ stl_facet& facet = stl.facet_start[i];
+ for (unsigned int v = 0; v < 3; ++v)
+ {
+ ::memcpy(facet.vertex[v].data(), (const void*)&geometry.vertices[geometry.triangles[src_start_id + ii + v] * 3], 3 * sizeof(float));
+ }
+ }
+
+ stl_get_size(&stl);
+ volume->mesh.repair();
+ volume->calculate_convex_hull();
+
+ // apply volume's name and config data
+ for (const Metadata& metadata : volume_data.metadata)
+ {
+ if (metadata.key == NAME_KEY)
+ volume->name = metadata.value;
+ else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
+ volume->set_type(ModelVolume::PARAMETER_MODIFIER);
+ else if (metadata.key == VOLUME_TYPE_KEY)
+ volume->set_type(ModelVolume::type_from_string(metadata.value));
+ else
+ volume->config.set_deserialize(metadata.key, metadata.value);
+ }
+ }
+
+ return true;
+ }
+
+ void XMLCALL _3MF_Importer::_handle_start_model_xml_element(void* userData, const char* name, const char** attributes)
+ {
+ _3MF_Importer* importer = (_3MF_Importer*)userData;
+ if (importer != nullptr)
+ importer->_handle_start_model_xml_element(name, attributes);
+ }
+
+ void XMLCALL _3MF_Importer::_handle_end_model_xml_element(void* userData, const char* name)
+ {
+ _3MF_Importer* importer = (_3MF_Importer*)userData;
+ if (importer != nullptr)
+ importer->_handle_end_model_xml_element(name);
+ }
+
+ void XMLCALL _3MF_Importer::_handle_model_xml_characters(void* userData, const XML_Char* s, int len)
+ {
+ _3MF_Importer* importer = (_3MF_Importer*)userData;
+ if (importer != nullptr)
+ importer->_handle_model_xml_characters(s, len);
+ }
+
+ void XMLCALL _3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes)
+ {
+ _3MF_Importer* importer = (_3MF_Importer*)userData;
+ if (importer != nullptr)
+ importer->_handle_start_config_xml_element(name, attributes);
+ }
+
+ void XMLCALL _3MF_Importer::_handle_end_config_xml_element(void* userData, const char* name)
+ {
+ _3MF_Importer* importer = (_3MF_Importer*)userData;
+ if (importer != nullptr)
+ importer->_handle_end_config_xml_element(name);
+ }
+
+ class _3MF_Exporter : public _3MF_Base
+ {
+ struct BuildItem
+ {
+ unsigned int id;
+ Transform3d transform;
+
+ BuildItem(unsigned int id, const Transform3d& transform)
+ : id(id)
+ , transform(transform)
+ {
+ }
+ };
+
+ struct Offsets
+ {
+ unsigned int first_vertex_id;
+ unsigned int first_triangle_id;
+ unsigned int last_triangle_id;
+
+ Offsets(unsigned int first_vertex_id)
+ : first_vertex_id(first_vertex_id)
+ , first_triangle_id(-1)
+ , last_triangle_id(-1)
+ {
+ }
+ };
+
+ typedef std::map<const ModelVolume*, Offsets> VolumeToOffsetsMap;
+
+ struct ObjectData
+ {
+ ModelObject* object;
+ VolumeToOffsetsMap volumes_offsets;
+
+ explicit ObjectData(ModelObject* object)
+ : object(object)
+ {
+ }
+ };
+
+ typedef std::vector<BuildItem> BuildItemsList;
+ typedef std::map<int, ObjectData> IdToObjectDataMap;
+
+ IdToObjectDataMap m_objects_data;
+
+ public:
+ bool save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config);
+
+ private:
+ bool _save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config);
+ bool _add_content_types_file_to_archive(mz_zip_archive& archive);
+ bool _add_relationships_file_to_archive(mz_zip_archive& archive);
+ bool _add_model_file_to_archive(mz_zip_archive& archive, Model& model);
+ bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets);
+ bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets);
+ bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items);
+ bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
+ bool _add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print);
+ bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model);
+ };
+
+ bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config)
+ {
+ clear_errors();
+ return _save_model_to_file(filename, model, print, export_print_config);
+ }
+
+ bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config)
+ {
+ mz_zip_archive archive;
+ mz_zip_zero_struct(&archive);
+
+ m_objects_data.clear();
+
+ mz_bool res = mz_zip_writer_init_file(&archive, filename.c_str(), 0);
+ if (res == 0)
+ {
+ add_error("Unable to open the file");
+ return false;
+ }
+
+ // adds content types file
+ if (!_add_content_types_file_to_archive(archive))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
+ // adds relationships file
+ if (!_add_relationships_file_to_archive(archive))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
+ // adds model file
+ if (!_add_model_file_to_archive(archive, model))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
+ // adds layer height profile file
+ if (!_add_layer_height_profile_file_to_archive(archive, model))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
+ // adds slic3r print config file
+ if (export_print_config)
+ {
+ if (!_add_print_config_file_to_archive(archive, print))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+ }
+
+ // adds slic3r model config file
+ if (!_add_model_config_file_to_archive(archive, model))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
+ if (!mz_zip_writer_finalize_archive(&archive))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ add_error("Unable to finalize the archive");
+ return false;
+ }
+
+ mz_zip_writer_end(&archive);
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_content_types_file_to_archive(mz_zip_archive& archive)
+ {
+ std::stringstream stream;
+ stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ stream << "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n";
+ stream << " <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\" />\n";
+ stream << " <Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\" />\n";
+ stream << "</Types>";
+
+ std::string out = stream.str();
+
+ if (!mz_zip_writer_add_mem(&archive, CONTENT_TYPES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add content types file to archive");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_relationships_file_to_archive(mz_zip_archive& archive)
+ {
+ std::stringstream stream;
+ stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ stream << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n";
+ stream << " <Relationship Target=\"/" << MODEL_FILE << "\" Id=\"rel-1\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\" />\n";
+ stream << "</Relationships>";
+
+ std::string out = stream.str();
+
+ if (!mz_zip_writer_add_mem(&archive, RELATIONSHIPS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add relationships file to archive");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_model_file_to_archive(mz_zip_archive& archive, Model& model)
+ {
+ std::stringstream stream;
+ stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n";
+ stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "</" << METADATA_TAG << ">\n";
+ stream << " <" << RESOURCES_TAG << ">\n";
+
+ BuildItemsList build_items;
+
+ unsigned int object_id = 1;
+ for (ModelObject* obj : model.objects)
+ {
+ if (obj == nullptr)
+ continue;
+
+ unsigned int curr_id = object_id;
+ IdToObjectDataMap::iterator object_it = m_objects_data.insert(IdToObjectDataMap::value_type(curr_id, ObjectData(obj))).first;
+
+ if (!_add_object_to_model_stream(stream, object_id, *obj, build_items, object_it->second.volumes_offsets))
+ {
+ add_error("Unable to add object to archive");
+ return false;
+ }
+ }
+
+ stream << " </" << RESOURCES_TAG << ">\n";
+
+ if (!_add_build_to_model_stream(stream, build_items))
+ {
+ add_error("Unable to add build to archive");
+ return false;
+ }
+
+ stream << "</" << MODEL_TAG << ">\n";
+
+ std::string out = stream.str();
+
+ if (!mz_zip_writer_add_mem(&archive, MODEL_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add model file to archive");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets)
+ {
+ unsigned int id = 0;
+ for (const ModelInstance* instance : object.instances)
+ {
+ if (instance == nullptr)
+ continue;
+
+ unsigned int instance_id = object_id + id;
+ stream << " <" << OBJECT_TAG << " id=\"" << instance_id << "\" type=\"model\">\n";
+
+ if (id == 0)
+ {
+ if (!_add_mesh_to_object_stream(stream, object, volumes_offsets))
+ {
+ add_error("Unable to add mesh to archive");
+ return false;
+ }
+ }
+ else
+ {
+ stream << " <" << COMPONENTS_TAG << ">\n";
+ stream << " <" << COMPONENT_TAG << " objectid=\"" << object_id << "\" />\n";
+ stream << " </" << COMPONENTS_TAG << ">\n";
+ }
+
+ Transform3d t = instance->world_matrix();
+ build_items.emplace_back(instance_id, t);
+
+ stream << " </" << OBJECT_TAG << ">\n";
+
+ ++id;
+ }
+
+ object_id += id;
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets)
+ {
+ stream << " <" << MESH_TAG << ">\n";
+ stream << " <" << VERTICES_TAG << ">\n";
+
+ unsigned int vertices_count = 0;
+ for (ModelVolume* volume : object.volumes)
+ {
+ if (volume == nullptr)
+ continue;
+
+ VolumeToOffsetsMap::iterator volume_it = volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first;
+
+ if (!volume->mesh.repaired)
+ volume->mesh.repair();
+
+ stl_file& stl = volume->mesh.stl;
+ if (stl.v_shared == nullptr)
+ stl_generate_shared_vertices(&stl);
+
+ if (stl.stats.shared_vertices == 0)
+ {
+ add_error("Found invalid mesh");
+ return false;
+ }
+
+ vertices_count += stl.stats.shared_vertices;
+
+ for (int i = 0; i < stl.stats.shared_vertices; ++i)
+ {
+ stream << " <" << VERTEX_TAG << " ";
+ stream << "x=\"" << stl.v_shared[i](0) << "\" ";
+ stream << "y=\"" << stl.v_shared[i](1) << "\" ";
+ stream << "z=\"" << stl.v_shared[i](2) << "\" />\n";
+ }
+ }
+
+ stream << " </" << VERTICES_TAG << ">\n";
+ stream << " <" << TRIANGLES_TAG << ">\n";
+
+ unsigned int triangles_count = 0;
+ for (ModelVolume* volume : object.volumes)
+ {
+ if (volume == nullptr)
+ continue;
+
+ VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume);
+ assert(volume_it != volumes_offsets.end());
+
+ stl_file& stl = volume->mesh.stl;
+
+ // updates triangle offsets
+ volume_it->second.first_triangle_id = triangles_count;
+ triangles_count += stl.stats.number_of_facets;
+ volume_it->second.last_triangle_id = triangles_count - 1;
+
+ for (uint32_t i = 0; i < stl.stats.number_of_facets; ++i)
+ {
+ stream << " <" << TRIANGLE_TAG << " ";
+ for (int j = 0; j < 3; ++j)
+ {
+ stream << "v" << j + 1 << "=\"" << stl.v_indices[i].vertex[j] + volume_it->second.first_vertex_id << "\" ";
+ }
+ stream << "/>\n";
+ }
+ }
+
+ stream << " </" << TRIANGLES_TAG << ">\n";
+ stream << " </" << MESH_TAG << ">\n";
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items)
+ {
+ if (build_items.size() == 0)
+ {
+ add_error("No build item found");
+ return false;
+ }
+
+ stream << " <" << BUILD_TAG << ">\n";
+
+ for (const BuildItem& item : build_items)
+ {
+ stream << " <" << ITEM_TAG << " objectid=\"" << item.id << "\" transform =\"";
+ for (unsigned c = 0; c < 4; ++c)
+ {
+ for (unsigned r = 0; r < 3; ++r)
+ {
+ stream << item.transform(r, c);
+ if ((r != 2) || (c != 3))
+ stream << " ";
+ }
+ }
+ stream << "\" />\n";
+ }
+
+ stream << " </" << BUILD_TAG << ">\n";
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model)
+ {
+ std::string out = "";
+ char buffer[1024];
+
+ unsigned int count = 0;
+ for (const ModelObject* object : model.objects)
+ {
+ ++count;
+ 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))
+ {
+ sprintf(buffer, "object_id=%d|", count);
+ out += buffer;
+
+ // Store the layer height profile as a single semicolon separated list.
+ for (size_t i = 0; i < layer_height_profile.size(); ++i)
+ {
+ sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]);
+ out += buffer;
+ }
+
+ out += "\n";
+ }
+ }
+
+ if (!out.empty())
+ {
+ if (!mz_zip_writer_add_mem(&archive, LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add layer heights profile file to archive");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print)
+ {
+ char buffer[1024];
+ sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str());
+ std::string out = buffer;
+
+ GCode::append_full_config(print, out);
+
+ if (!out.empty())
+ {
+ if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add print config file to archive");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model)
+ {
+ std::stringstream stream;
+ stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ stream << "<" << CONFIG_TAG << ">\n";
+
+ for (const IdToObjectDataMap::value_type& obj_metadata : m_objects_data)
+ {
+ const ModelObject* obj = obj_metadata.second.object;
+ if (obj != nullptr)
+ {
+ stream << " <" << OBJECT_TAG << " id=\"" << obj_metadata.first << "\">\n";
+
+ // stores object's name
+ if (!obj->name.empty())
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << xml_escape(obj->name) << "\"/>\n";
+
+ // stores object's config data
+ for (const std::string& key : obj->config.keys())
+ {
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.serialize(key) << "\"/>\n";
+ }
+
+ for (const ModelVolume* volume : obj_metadata.second.object->volumes)
+ {
+ if (volume != nullptr)
+ {
+ const VolumeToOffsetsMap& offsets = obj_metadata.second.volumes_offsets;
+ VolumeToOffsetsMap::const_iterator it = offsets.find(volume);
+ if (it != offsets.end())
+ {
+ // stores volume's offsets
+ stream << " <" << VOLUME_TAG << " ";
+ stream << FIRST_TRIANGLE_ID_ATTR << "=\"" << it->second.first_triangle_id << "\" ";
+ stream << LAST_TRIANGLE_ID_ATTR << "=\"" << it->second.last_triangle_id << "\">\n";
+
+ // stores volume's name
+ if (!volume->name.empty())
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n";
+
+ // stores volume's modifier field (legacy, to support old slicers)
+ if (volume->is_modifier())
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
+ // stores volume's type (overrides the modifier field above)
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " <<
+ VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n";
+
+ // stores volume's config data
+ for (const std::string& key : volume->config.keys())
+ {
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.serialize(key) << "\"/>\n";
+ }
+
+ stream << " </" << VOLUME_TAG << ">\n";
+ }
+ }
+ }
+
+ stream << " </" << OBJECT_TAG << ">\n";
+ }
+ }
+
+ stream << "</" << CONFIG_TAG << ">\n";
+
+ std::string out = stream.str();
+
+ if (!mz_zip_writer_add_mem(&archive, MODEL_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add model config file to archive");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool load_3mf(const char* path, PresetBundle* bundle, Model* model)
+ {
+ if ((path == nullptr) || (bundle == nullptr) || (model == nullptr))
+ return false;
+
+ _3MF_Importer importer;
+ bool res = importer.load_model_from_file(path, *model, *bundle);
+ importer.log_errors();
+ return res;
+ }
+
+ bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config)
+ {
+ if ((path == nullptr) || (model == nullptr) || (print == nullptr))
+ return false;
+
+ _3MF_Exporter exporter;
+ bool res = exporter.save_model_to_file(path, *model, *print, export_print_config);
+
+ if (!res)
+ exporter.log_errors();
+
+ return res;
+ }
+} // namespace Slic3r
diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp
new file mode 100644
index 000000000..85bc812e3
--- /dev/null
+++ b/src/libslic3r/Format/3mf.hpp
@@ -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_ */
diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp
new file mode 100644
index 000000000..458ce79de
--- /dev/null
+++ b/src/libslic3r/Format/AMF.cpp
@@ -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
diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp
new file mode 100644
index 000000000..4779e9a51
--- /dev/null
+++ b/src/libslic3r/Format/AMF.hpp
@@ -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_ */ \ No newline at end of file
diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp
new file mode 100644
index 000000000..ee5756083
--- /dev/null
+++ b/src/libslic3r/Format/OBJ.cpp
@@ -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
diff --git a/src/libslic3r/Format/OBJ.hpp b/src/libslic3r/Format/OBJ.hpp
new file mode 100644
index 000000000..9a8790bff
--- /dev/null
+++ b/src/libslic3r/Format/OBJ.hpp
@@ -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_ */
diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp
new file mode 100644
index 000000000..45eb56c63
--- /dev/null
+++ b/src/libslic3r/Format/PRUS.cpp
@@ -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 */
diff --git a/src/libslic3r/Format/PRUS.hpp b/src/libslic3r/Format/PRUS.hpp
new file mode 100644
index 000000000..8559a70d6
--- /dev/null
+++ b/src/libslic3r/Format/PRUS.hpp
@@ -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_) */
diff --git a/src/libslic3r/Format/STL.cpp b/src/libslic3r/Format/STL.cpp
new file mode 100644
index 000000000..99e2ff193
--- /dev/null
+++ b/src/libslic3r/Format/STL.cpp
@@ -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
diff --git a/src/libslic3r/Format/STL.hpp b/src/libslic3r/Format/STL.hpp
new file mode 100644
index 000000000..2fd32324c
--- /dev/null
+++ b/src/libslic3r/Format/STL.hpp
@@ -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_ */
diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp
new file mode 100644
index 000000000..88dfae695
--- /dev/null
+++ b/src/libslic3r/Format/objparser.cpp
@@ -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
diff --git a/src/libslic3r/Format/objparser.hpp b/src/libslic3r/Format/objparser.hpp
new file mode 100644
index 000000000..5fc25e297
--- /dev/null
+++ b/src/libslic3r/Format/objparser.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
new file mode 100644
index 000000000..d10705c18
--- /dev/null
+++ b/src/libslic3r/GCode.cpp
@@ -0,0 +1,2730 @@
+#include "GCode.hpp"
+#include "ExtrusionEntity.hpp"
+#include "EdgeGrid.hpp"
+#include "Geometry.hpp"
+#include "GCode/PrintExtents.hpp"
+#include "GCode/WipeTowerPrusaMM.hpp"
+#include "Utils.hpp"
+
+#include <algorithm>
+#include <cstdlib>
+#include <math.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/find.hpp>
+#include <boost/foreach.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/log/trivial.hpp>
+
+#include <boost/nowide/iostream.hpp>
+#include <boost/nowide/cstdio.hpp>
+#include <boost/nowide/cstdlib.hpp>
+
+#include "SVG.hpp"
+
+#include <Shiny/Shiny.h>
+
+#if 0
+// Enable debugging and asserts, even in the release build.
+#define DEBUG
+#define _DEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+
+namespace Slic3r {
+
+// Only add a newline in case the current G-code does not end with a newline.
+static inline void check_add_eol(std::string &gcode)
+{
+ if (! gcode.empty() && gcode.back() != '\n')
+ gcode += '\n';
+}
+
+// Plan a travel move while minimizing the number of perimeter crossings.
+// point is in unscaled coordinates, in the coordinate system of the current active object
+// (set by gcodegen.set_origin()).
+Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point)
+{
+ // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
+ // Otherwise perform the path planning in the coordinate system of the active object.
+ bool use_external = this->use_external_mp || this->use_external_mp_once;
+ Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
+ Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())->
+ shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin);
+ if (use_external)
+ result.translate(- scaled_origin);
+ return result;
+}
+
+std::string OozePrevention::pre_toolchange(GCode &gcodegen)
+{
+ std::string gcode;
+
+ // move to the nearest standby point
+ if (!this->standby_points.empty()) {
+ // get current position in print coordinates
+ Vec3d writer_pos = gcodegen.writer().get_position();
+ Point pos = Point::new_scale(writer_pos(0), writer_pos(1));
+
+ // find standby point
+ Point standby_point;
+ pos.nearest_point(this->standby_points, &standby_point);
+
+ /* We don't call gcodegen.travel_to() because we don't need retraction (it was already
+ triggered by the caller) nor avoid_crossing_perimeters and also because the coordinates
+ of the destination point must not be transformed by origin nor current extruder offset. */
+ gcode += gcodegen.writer().travel_to_xy(unscale(standby_point),
+ "move to standby position");
+ }
+
+ if (gcodegen.config().standby_temperature_delta.value != 0) {
+ // we assume that heating is always slower than cooling, so no need to block
+ gcode += gcodegen.writer().set_temperature
+ (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false);
+ }
+
+ return gcode;
+}
+
+std::string OozePrevention::post_toolchange(GCode &gcodegen)
+{
+ return (gcodegen.config().standby_temperature_delta.value != 0) ?
+ gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true) :
+ std::string();
+}
+
+int
+OozePrevention::_get_temp(GCode &gcodegen)
+{
+ return (gcodegen.layer() != NULL && gcodegen.layer()->id() == 0)
+ ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
+ : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id());
+}
+
+std::string
+Wipe::wipe(GCode &gcodegen, bool toolchange)
+{
+ std::string gcode;
+
+ /* Reduce feedrate a bit; travel speed is often too high to move on existing material.
+ Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */
+ double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
+
+ // get the retraction length
+ double length = toolchange
+ ? gcodegen.writer().extruder()->retract_length_toolchange()
+ : gcodegen.writer().extruder()->retract_length();
+ // Shorten the retraction length by the amount already retracted before wipe.
+ length *= (1. - gcodegen.writer().extruder()->retract_before_wipe());
+
+ if (length > 0) {
+ /* Calculate how long we need to travel in order to consume the required
+ amount of retraction. In other words, how far do we move in XY at wipe_speed
+ for the time needed to consume retract_length at retract_speed? */
+ double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed);
+
+ /* Take the stored wipe path and replace first point with the current actual position
+ (they might be different, for example, in case of loop clipping). */
+ Polyline wipe_path;
+ wipe_path.append(gcodegen.last_pos());
+ wipe_path.append(
+ this->path.points.begin() + 1,
+ this->path.points.end()
+ );
+
+ wipe_path.clip_end(wipe_path.length() - wipe_dist);
+
+ // subdivide the retraction in segments
+ for (const Line &line : wipe_path.lines()) {
+ double segment_length = line.length();
+ /* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
+ due to rounding (TODO: test and/or better math for this) */
+ double dE = length * (segment_length / wipe_dist) * 0.95;
+ //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle.
+ // Is it here for the cooling markers? Or should it be outside of the cycle?
+ gcode += gcodegen.writer().set_speed(wipe_speed*60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
+ gcode += gcodegen.writer().extrude_to_xy(
+ gcodegen.point_to_gcode(line.b),
+ -dE,
+ "wipe and retract"
+ );
+ }
+
+ // prevent wiping again on same path
+ this->reset_path();
+ }
+
+ return gcode;
+}
+
+static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const WipeTower::xy &wipe_tower_pt)
+{
+ return Point(scale_(wipe_tower_pt.x - gcodegen.origin()(0)), scale_(wipe_tower_pt.y - gcodegen.origin()(1)));
+}
+
+std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const
+{
+ std::string gcode;
+
+ // Toolchangeresult.gcode assumes the wipe tower corner is at the origin
+ // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
+ float alpha = m_wipe_tower_rotation/180.f * M_PI;
+ WipeTower::xy start_pos = tcr.start_pos;
+ WipeTower::xy end_pos = tcr.end_pos;
+ start_pos.rotate(alpha);
+ start_pos.translate(m_wipe_tower_pos);
+ end_pos.rotate(alpha);
+ end_pos.translate(m_wipe_tower_pos);
+ std::string tcr_rotated_gcode = rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha);
+
+
+ // Disable linear advance for the wipe tower operations.
+ gcode += "M900 K0\n";
+ // Move over the wipe tower.
+ // Retract for a tool change, using the toolchange retract value and setting the priming extra length.
+ gcode += gcodegen.retract(true);
+ gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
+ gcode += gcodegen.travel_to(
+ wipe_tower_point_to_object_point(gcodegen, start_pos),
+ erMixed,
+ "Travel to a Wipe Tower");
+ gcode += gcodegen.unretract();
+
+ // Let the tool change be executed by the wipe tower class.
+ // Inform the G-code writer about the changes done behind its back.
+ gcode += tcr_rotated_gcode;
+ // Let the m_writer know the current extruder_id, but ignore the generated G-code.
+ if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))
+ gcodegen.writer().toolchange(new_extruder_id);
+ // Always append the filament start G-code even if the extruder did not switch,
+ // because the wipe tower resets the linear advance and we want it to be re-enabled.
+ const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id);
+ if (! start_filament_gcode.empty()) {
+ // Process the start_filament_gcode for the active filament only.
+ gcodegen.placeholder_parser().set("current_extruder", new_extruder_id);
+ gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id);
+ check_add_eol(gcode);
+ }
+ // A phony move to the end position at the wipe tower.
+ gcodegen.writer().travel_to_xy(Vec2d(end_pos.x, end_pos.y));
+ gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
+
+ // Prepare a future wipe.
+ gcodegen.m_wipe.path.points.clear();
+ if (new_extruder_id >= 0) {
+ // Start the wipe at the current position.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos));
+ // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
+ WipeTower::xy((std::abs(m_left - end_pos.x) < std::abs(m_right - end_pos.x)) ? m_right : m_left,
+ end_pos.y)));
+ }
+
+ // Let the planner know we are traveling between objects.
+ gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
+ return gcode;
+}
+
+// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
+// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
+std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const
+{
+ std::istringstream gcode_str(gcode_original);
+ std::string gcode_out;
+ std::string line;
+ WipeTower::xy pos = start_pos;
+ WipeTower::xy transformed_pos;
+ WipeTower::xy old_pos(-1000.1f, -1000.1f);
+
+ while (gcode_str) {
+ std::getline(gcode_str, line); // we read the gcode line by line
+ if (line.find("G1 ") == 0) {
+ std::ostringstream line_out;
+ std::istringstream line_str(line);
+ line_str >> std::noskipws; // don't skip whitespace
+ char ch = 0;
+ while (line_str >> ch) {
+ if (ch == 'X')
+ line_str >> pos.x;
+ else
+ if (ch == 'Y')
+ line_str >> pos.y;
+ else
+ line_out << ch;
+ }
+
+ transformed_pos = pos;
+ transformed_pos.rotate(angle);
+ transformed_pos.translate(translation);
+
+ if (transformed_pos != old_pos) {
+ line = line_out.str();
+ char buf[2048] = "G1";
+ if (transformed_pos.x != old_pos.x)
+ sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x);
+ if (transformed_pos.y != old_pos.y)
+ sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y);
+
+ line.replace(line.find("G1 "), 3, buf);
+ old_pos = transformed_pos;
+ }
+ }
+ gcode_out += line + "\n";
+ }
+ return gcode_out;
+}
+
+
+std::string WipeTowerIntegration::prime(GCode &gcodegen)
+{
+ assert(m_layer_idx == 0);
+ std::string gcode;
+
+ if (&m_priming != nullptr && ! m_priming.extrusions.empty()) {
+ // Disable linear advance for the wipe tower operations.
+ gcode += "M900 K0\n";
+ // Let the tool change be executed by the wipe tower class.
+ // Inform the G-code writer about the changes done behind its back.
+ gcode += m_priming.gcode;
+ // Let the m_writer know the current extruder_id, but ignore the generated G-code.
+ unsigned int current_extruder_id = m_priming.extrusions.back().tool;
+ gcodegen.writer().toolchange(current_extruder_id);
+ gcodegen.placeholder_parser().set("current_extruder", current_extruder_id);
+ // A phony move to the end position at the wipe tower.
+ gcodegen.writer().travel_to_xy(Vec2d(m_priming.end_pos.x, m_priming.end_pos.y));
+ gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos));
+ // Prepare a future wipe.
+ gcodegen.m_wipe.path.points.clear();
+ // Start the wipe at the current position.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos));
+ // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
+ gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
+ WipeTower::xy((std::abs(m_left - m_priming.end_pos.x) < std::abs(m_right - m_priming.end_pos.x)) ? m_right : m_left,
+ m_priming.end_pos.y)));
+ }
+ return gcode;
+}
+
+std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer)
+{
+ std::string gcode;
+ assert(m_layer_idx >= 0 && m_layer_idx <= m_tool_changes.size());
+ if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
+ if (m_layer_idx < m_tool_changes.size()) {
+ assert(m_tool_change_idx < m_tool_changes[m_layer_idx].size());
+ gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id);
+ }
+ m_brim_done = true;
+ }
+ return gcode;
+}
+
+// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
+std::string WipeTowerIntegration::finalize(GCode &gcodegen)
+{
+ std::string gcode;
+ if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON)
+ gcode += gcodegen.change_layer(m_final_purge.print_z);
+ gcode += append_tcr(gcodegen, m_final_purge, -1);
+ return gcode;
+}
+
+#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id())
+
+// Collect pairs of object_layer + support_layer sorted by print_z.
+// object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON.
+std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObject &object)
+{
+ std::vector<GCode::LayerToPrint> layers_to_print;
+ layers_to_print.reserve(object.layers().size() + object.support_layers().size());
+
+ // Pair the object layers with the support layers by z.
+ size_t idx_object_layer = 0;
+ size_t idx_support_layer = 0;
+ while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) {
+ LayerToPrint layer_to_print;
+ layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr;
+ layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer ++] : nullptr;
+ if (layer_to_print.object_layer && layer_to_print.support_layer) {
+ if (layer_to_print.object_layer->print_z < layer_to_print.support_layer->print_z - EPSILON) {
+ layer_to_print.support_layer = nullptr;
+ -- idx_support_layer;
+ } else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) {
+ layer_to_print.object_layer = nullptr;
+ -- idx_object_layer;
+ }
+ }
+ layers_to_print.emplace_back(layer_to_print);
+ }
+
+ return layers_to_print;
+}
+
+// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
+// will be printed for all objects at once.
+// Return a list of <print_z, per object LayerToPrint> items.
+std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collect_layers_to_print(const Print &print)
+{
+ struct OrderingItem {
+ coordf_t print_z;
+ size_t object_idx;
+ size_t layer_idx;
+ };
+
+ PrintObjectPtrs printable_objects = print.get_printable_objects();
+ std::vector<std::vector<LayerToPrint>> per_object(printable_objects.size(), std::vector<LayerToPrint>());
+ std::vector<OrderingItem> ordering;
+ for (size_t i = 0; i < printable_objects.size(); ++i) {
+ per_object[i] = collect_layers_to_print(*printable_objects[i]);
+ OrderingItem ordering_item;
+ ordering_item.object_idx = i;
+ ordering.reserve(ordering.size() + per_object[i].size());
+ const LayerToPrint &front = per_object[i].front();
+ for (const LayerToPrint &ltp : per_object[i]) {
+ ordering_item.print_z = ltp.print_z();
+ ordering_item.layer_idx = &ltp - &front;
+ ordering.emplace_back(ordering_item);
+ }
+ }
+
+ std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; });
+
+ std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print;
+ // Merge numerically very close Z values.
+ for (size_t i = 0; i < ordering.size();) {
+ // Find the last layer with roughly the same print_z.
+ size_t j = i + 1;
+ coordf_t zmax = ordering[i].print_z + EPSILON;
+ for (; j < ordering.size() && ordering[j].print_z <= zmax; ++ j) ;
+ // Merge into layers_to_print.
+ std::pair<coordf_t, std::vector<LayerToPrint>> merged;
+ // Assign an average print_z to the set of layers with nearly equal print_z.
+ merged.first = 0.5 * (ordering[i].print_z + ordering[j-1].print_z);
+ merged.second.assign(printable_objects.size(), LayerToPrint());
+ for (; i < j; ++i) {
+ const OrderingItem &oi = ordering[i];
+ assert(merged.second[oi.object_idx].layer() == nullptr);
+ merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]);
+ }
+ layers_to_print.emplace_back(std::move(merged));
+ }
+
+ return layers_to_print;
+}
+
+void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_data)
+{
+ PROFILE_CLEAR();
+
+ // Does the file exist? If so, we hope that it is still valid.
+ if (print->is_step_done(psGCodeExport) && boost::filesystem::exists(boost::filesystem::path(path)))
+ return;
+
+ print->set_started(psGCodeExport);
+
+ BOOST_LOG_TRIVIAL(info) << "Exporting G-code...";
+
+ // Remove the old g-code if it exists.
+ boost::nowide::remove(path);
+
+ std::string path_tmp(path);
+ path_tmp += ".tmp";
+
+ FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb");
+ if (file == nullptr)
+ throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
+
+ try {
+ m_placeholder_parser_failed_templates.clear();
+ this->_do_export(*print, file, preview_data);
+ fflush(file);
+ if (ferror(file)) {
+ fclose(file);
+ boost::nowide::remove(path_tmp.c_str());
+ throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
+ }
+ } catch (std::exception &ex) {
+ // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
+ // Close and remove the file.
+ fclose(file);
+ boost::nowide::remove(path_tmp.c_str());
+ throw;
+ }
+ fclose(file);
+
+ if (print->config().remaining_times.value) {
+ m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f);
+ if (m_silent_time_estimator_enabled)
+ m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f);
+ }
+
+ if (! m_placeholder_parser_failed_templates.empty()) {
+ // G-code export proceeded, but some of the PlaceholderParser substitutions failed.
+ std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n";
+ for (const std::string &name : m_placeholder_parser_failed_templates)
+ msg += std::string("\t") + name + "\n";
+ msg += "\nPlease inspect the file ";
+ msg += path_tmp + " for error messages enclosed between\n";
+ msg += " !!!!! Failed to process the custom G-code template ...\n";
+ msg += "and\n";
+ msg += " !!!!! End of an error report for the custom G-code template ...\n";
+ throw std::runtime_error(msg);
+ }
+
+ if (rename_file(path_tmp, path) != 0)
+ throw std::runtime_error(
+ std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' +
+ "Is " + path_tmp + " locked?" + '\n');
+
+ BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished";
+ print->set_done(psGCodeExport);
+
+ // Write the profiler measurements to file
+ PROFILE_UPDATE();
+ PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str());
+}
+
+void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
+{
+ PROFILE_FUNC();
+
+ // resets time estimators
+ m_normal_time_estimator.reset();
+ m_normal_time_estimator.set_dialect(print.config().gcode_flavor);
+ m_silent_time_estimator_enabled = (print.config().gcode_flavor == gcfMarlin) && print.config().silent_mode;
+
+ // Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values
+ // and let the user to enter the G-code limits into the start G-code.
+ // If the following block is enabled for other firmwares than the Marlin, then the function
+ // this->print_machine_envelope(file, print);
+ // shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
+ if (print.config().gcode_flavor.value == gcfMarlin) {
+ m_normal_time_estimator.set_max_acceleration(print.config().machine_max_acceleration_extruding.values[0]);
+ m_normal_time_estimator.set_retract_acceleration(print.config().machine_max_acceleration_retracting.values[0]);
+ m_normal_time_estimator.set_minimum_feedrate(print.config().machine_min_extruding_rate.values[0]);
+ m_normal_time_estimator.set_minimum_travel_feedrate(print.config().machine_min_travel_rate.values[0]);
+ m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, print.config().machine_max_acceleration_x.values[0]);
+ m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, print.config().machine_max_acceleration_y.values[0]);
+ m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, print.config().machine_max_acceleration_z.values[0]);
+ m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, print.config().machine_max_acceleration_e.values[0]);
+ m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, print.config().machine_max_feedrate_x.values[0]);
+ m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, print.config().machine_max_feedrate_y.values[0]);
+ m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, print.config().machine_max_feedrate_z.values[0]);
+ m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, print.config().machine_max_feedrate_e.values[0]);
+ m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, print.config().machine_max_jerk_x.values[0]);
+ m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, print.config().machine_max_jerk_y.values[0]);
+ m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, print.config().machine_max_jerk_z.values[0]);
+ m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, print.config().machine_max_jerk_e.values[0]);
+
+ if (m_silent_time_estimator_enabled)
+ {
+ m_silent_time_estimator.reset();
+ m_silent_time_estimator.set_dialect(print.config().gcode_flavor);
+ m_silent_time_estimator.set_max_acceleration(print.config().machine_max_acceleration_extruding.values[1]);
+ m_silent_time_estimator.set_retract_acceleration(print.config().machine_max_acceleration_retracting.values[1]);
+ m_silent_time_estimator.set_minimum_feedrate(print.config().machine_min_extruding_rate.values[1]);
+ m_silent_time_estimator.set_minimum_travel_feedrate(print.config().machine_min_travel_rate.values[1]);
+ m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, print.config().machine_max_acceleration_x.values[1]);
+ m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, print.config().machine_max_acceleration_y.values[1]);
+ m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, print.config().machine_max_acceleration_z.values[1]);
+ m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, print.config().machine_max_acceleration_e.values[1]);
+ m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, print.config().machine_max_feedrate_x.values[1]);
+ m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, print.config().machine_max_feedrate_y.values[1]);
+ m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, print.config().machine_max_feedrate_z.values[1]);
+ m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, print.config().machine_max_feedrate_e.values[1]);
+ m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, print.config().machine_max_jerk_x.values[1]);
+ m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, print.config().machine_max_jerk_y.values[1]);
+ m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, print.config().machine_max_jerk_z.values[1]);
+ m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, print.config().machine_max_jerk_e.values[1]);
+ if (print.config().single_extruder_multi_material) {
+ // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
+ // are considered to be active for the single extruder multi-material printers only.
+ m_silent_time_estimator.set_filament_load_times(print.config().filament_load_time.values);
+ m_silent_time_estimator.set_filament_unload_times(print.config().filament_unload_time.values);
+ }
+ }
+ }
+ // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
+ if (print.config().single_extruder_multi_material) {
+ // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
+ // are considered to be active for the single extruder multi-material printers only.
+ m_normal_time_estimator.set_filament_load_times(print.config().filament_load_time.values);
+ m_normal_time_estimator.set_filament_unload_times(print.config().filament_unload_time.values);
+ }
+
+ // resets analyzer
+ m_analyzer.reset();
+ m_enable_analyzer = preview_data != nullptr;
+
+ // resets analyzer's tracking data
+ m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm;
+ m_last_width = GCodeAnalyzer::Default_Width;
+ m_last_height = GCodeAnalyzer::Default_Height;
+
+ // How many times will be change_layer() called?
+ // change_layer() in turn increments the progress bar status.
+ m_layer_count = 0;
+ PrintObjectPtrs printable_objects = print.get_printable_objects();
+ if (print.config().complete_objects.value) {
+ // Add each of the object's layers separately.
+ for (auto object : printable_objects) {
+ std::vector<coordf_t> zs;
+ zs.reserve(object->layers().size() + object->support_layers().size());
+ for (auto layer : object->layers())
+ zs.push_back(layer->print_z);
+ for (auto layer : object->support_layers())
+ zs.push_back(layer->print_z);
+ std::sort(zs.begin(), zs.end());
+ m_layer_count += (unsigned int)(object->copies().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
+ }
+ } else {
+ // Print all objects with the same print_z together.
+ std::vector<coordf_t> zs;
+ for (auto object : printable_objects) {
+ zs.reserve(zs.size() + object->layers().size() + object->support_layers().size());
+ for (auto layer : object->layers())
+ zs.push_back(layer->print_z);
+ for (auto layer : object->support_layers())
+ zs.push_back(layer->print_z);
+ }
+ std::sort(zs.begin(), zs.end());
+ m_layer_count = (unsigned int)(std::unique(zs.begin(), zs.end()) - zs.begin());
+ }
+ print.throw_if_canceled();
+
+ m_enable_cooling_markers = true;
+ this->apply_print_config(print.config());
+ this->set_extruders(print.extruders());
+
+ // Initialize autospeed.
+ {
+ // get the minimum cross-section used in the print
+ std::vector<double> mm3_per_mm;
+ for (auto object : printable_objects) {
+ for (size_t region_id = 0; region_id < print.regions().size(); ++region_id) {
+ auto region = print.regions()[region_id];
+ for (auto layer : object->layers()) {
+ auto layerm = layer->regions()[region_id];
+ if (region->config().get_abs_value("perimeter_speed" ) == 0 ||
+ region->config().get_abs_value("small_perimeter_speed" ) == 0 ||
+ region->config().get_abs_value("external_perimeter_speed" ) == 0 ||
+ region->config().get_abs_value("bridge_speed" ) == 0)
+ mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm());
+ if (region->config().get_abs_value("infill_speed" ) == 0 ||
+ region->config().get_abs_value("solid_infill_speed" ) == 0 ||
+ region->config().get_abs_value("top_solid_infill_speed" ) == 0 ||
+ region->config().get_abs_value("bridge_speed" ) == 0)
+ mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm());
+ }
+ }
+ if (object->config().get_abs_value("support_material_speed" ) == 0 ||
+ object->config().get_abs_value("support_material_interface_speed" ) == 0)
+ for (auto layer : object->support_layers())
+ mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm());
+ }
+ print.throw_if_canceled();
+ // filter out 0-width segments
+ mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end());
+ if (! mm3_per_mm.empty()) {
+ // In order to honor max_print_speed we need to find a target volumetric
+ // speed that we can use throughout the print. So we define this target
+ // volumetric speed as the volumetric speed produced by printing the
+ // smallest cross-section at the maximum speed: any larger cross-section
+ // will need slower feedrates.
+ m_volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config().max_print_speed.value;
+ // limit such volumetric speed with max_volumetric_speed if set
+ if (print.config().max_volumetric_speed.value > 0)
+ m_volumetric_speed = std::min(m_volumetric_speed, print.config().max_volumetric_speed.value);
+ }
+ }
+ print.throw_if_canceled();
+
+ m_cooling_buffer = make_unique<CoolingBuffer>(*this);
+ if (print.config().spiral_vase.value)
+ m_spiral_vase = make_unique<SpiralVase>(print.config());
+ if (print.config().max_volumetric_extrusion_rate_slope_positive.value > 0 ||
+ print.config().max_volumetric_extrusion_rate_slope_negative.value > 0)
+ m_pressure_equalizer = make_unique<PressureEqualizer>(&print.config());
+ m_enable_extrusion_role_markers = (bool)m_pressure_equalizer;
+
+ // Write information on the generator.
+ _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
+ // Write notes (content of the Print Settings tab -> Notes)
+ {
+ std::list<std::string> lines;
+ boost::split(lines, print.config().notes.value, boost::is_any_of("\n"), boost::token_compress_off);
+ for (auto line : lines) {
+ // Remove the trailing '\r' from the '\r\n' sequence.
+ if (! line.empty() && line.back() == '\r')
+ line.pop_back();
+ _write_format(file, "; %s\n", line.c_str());
+ }
+ if (! lines.empty())
+ _write(file, "\n");
+ }
+ print.throw_if_canceled();
+
+ // Write some terse information on the slicing parameters.
+ const PrintObject *first_object = printable_objects.front();
+ const double layer_height = first_object->config().layer_height.value;
+ const double first_layer_height = first_object->config().first_layer_height.get_abs_value(layer_height);
+ for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) {
+ auto region = print.regions()[region_id];
+ _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
+ _write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width);
+ _write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width);
+ _write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(frSolidInfill, layer_height, false, false, -1., *first_object).width);
+ _write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(frTopSolidInfill, layer_height, false, false, -1., *first_object).width);
+ if (print.has_support_material())
+ _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
+ if (print.config().first_layer_extrusion_width.value > 0)
+ _write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
+ _write_format(file, "\n");
+ }
+ print.throw_if_canceled();
+
+ // adds tags for time estimators
+ if (print.config().remaining_times.value)
+ {
+ _writeln(file, GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag);
+ if (m_silent_time_estimator_enabled)
+ _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag);
+ }
+
+ // Prepare the helper object for replacing placeholders in custom G-code and output filename.
+ m_placeholder_parser = print.placeholder_parser();
+ m_placeholder_parser.update_timestamp();
+
+ // Get optimal tool ordering to minimize tool switches of a multi-exruder print.
+ // For a print by objects, find the 1st printing object.
+ ToolOrdering tool_ordering;
+ unsigned int initial_extruder_id = (unsigned int)-1;
+ unsigned int final_extruder_id = (unsigned int)-1;
+ size_t initial_print_object_id = 0;
+ bool has_wipe_tower = false;
+ if (print.config().complete_objects.value) {
+ // Find the 1st printing object, find its tool ordering and the initial extruder ID.
+ for (; initial_print_object_id < printable_objects.size(); ++initial_print_object_id) {
+ tool_ordering = ToolOrdering(*printable_objects[initial_print_object_id], initial_extruder_id);
+ if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1)
+ break;
+ }
+ } else {
+ // Find tool ordering for all the objects at once, and the initial extruder ID.
+ // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
+ tool_ordering = print.wipe_tower_data().tool_ordering.empty() ?
+ ToolOrdering(print, initial_extruder_id) :
+ print.wipe_tower_data().tool_ordering;
+ has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
+ initial_extruder_id = (has_wipe_tower && ! print.config().single_extruder_multi_material_priming) ?
+ // The priming towers will be skipped.
+ tool_ordering.all_extruders().back() :
+ // Don't skip the priming towers.
+ tool_ordering.first_extruder();
+ }
+ if (initial_extruder_id == (unsigned int)-1) {
+ // Nothing to print!
+ initial_extruder_id = 0;
+ final_extruder_id = 0;
+ } else {
+ final_extruder_id = tool_ordering.last_extruder();
+ assert(final_extruder_id != (unsigned int)-1);
+ }
+ print.throw_if_canceled();
+
+ m_cooling_buffer->set_current_extruder(initial_extruder_id);
+
+ // Emit machine envelope limits for the Marlin firmware.
+ this->print_machine_envelope(file, print);
+
+ // Disable fan.
+ if (! print.config().cooling.get_at(initial_extruder_id) || print.config().disable_fan_first_layers.get_at(initial_extruder_id))
+ _write(file, m_writer.set_fan(0, true));
+
+ // Let the start-up script prime the 1st printing tool.
+ m_placeholder_parser.set("initial_tool", initial_extruder_id);
+ m_placeholder_parser.set("initial_extruder", initial_extruder_id);
+ m_placeholder_parser.set("current_extruder", initial_extruder_id);
+ // Useful for sequential prints.
+ m_placeholder_parser.set("current_object_idx", 0);
+ // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
+ m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
+ m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming);
+ std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
+ // Set bed temperature if the start G-code does not contain any bed temp control G-codes.
+ this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
+ // Set extruder(s) temperature before and after start G-code.
+ this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
+
+ if (m_enable_analyzer)
+ {
+ // adds tag for analyzer
+ char buf[32];
+ sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
+ _writeln(file, buf);
+ }
+
+ // Write the custom start G-code
+ _writeln(file, start_gcode);
+ // Process filament-specific gcode in extruder order.
+ if (print.config().single_extruder_multi_material) {
+ if (has_wipe_tower) {
+ // Wipe tower will control the extruder switching, it will call the start_filament_gcode.
+ } else {
+ // Only initialize the initial extruder.
+ _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id));
+ }
+ } else {
+ for (const std::string &start_gcode : print.config().start_filament_gcode.values)
+ _writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config().start_filament_gcode.values.front())));
+ }
+ this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true);
+ print.throw_if_canceled();
+
+ // Set other general things.
+ _write(file, this->preamble());
+
+ // Initialize a motion planner for object-to-object travel moves.
+ if (print.config().avoid_crossing_perimeters.value) {
+ // Collect outer contours of all objects over all layers.
+ // Discard objects only containing thin walls (offset would fail on an empty polygon).
+ Polygons islands;
+ for (const PrintObject *object : printable_objects)
+ for (const Layer *layer : object->layers())
+ for (const ExPolygon &expoly : layer->slices.expolygons)
+ for (const Point &copy : object->copies()) {
+ islands.emplace_back(expoly.contour);
+ islands.back().translate(- copy);
+ }
+ //FIXME Mege the islands in parallel.
+ m_avoid_crossing_perimeters.init_external_mp(union_ex(islands));
+ print.throw_if_canceled();
+ }
+
+ // Calculate wiping points if needed
+ if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) {
+ Points skirt_points;
+ for (const ExtrusionEntity *ee : print.skirt().entities)
+ for (const ExtrusionPath &path : dynamic_cast<const ExtrusionLoop*>(ee)->paths)
+ append(skirt_points, path.polyline.points);
+ if (! skirt_points.empty()) {
+ Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points);
+ Polygons skirts;
+ for (unsigned int extruder_id : print.extruders()) {
+ const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id);
+ Polygon s(outer_skirt);
+ s.translate(Point::new_scale(- extruder_offset(0), - extruder_offset(1)));
+ skirts.emplace_back(std::move(s));
+ }
+ m_ooze_prevention.enable = true;
+ m_ooze_prevention.standby_points =
+ offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.));
+#if 0
+ require "Slic3r/SVG.pm";
+ Slic3r::SVG::output(
+ "ooze_prevention.svg",
+ red_polygons => \@skirts,
+ polygons => [$outer_skirt],
+ points => $gcodegen->ooze_prevention->standby_points,
+ );
+#endif
+ }
+ print.throw_if_canceled();
+ }
+
+ if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) {
+ // Set initial extruder only after custom start G-code.
+ // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed.
+ _write(file, this->set_extruder(initial_extruder_id));
+ }
+
+ // Do all objects for each layer.
+ if (print.config().complete_objects.value) {
+ // Print objects from the smallest to the tallest to avoid collisions
+ // when moving onto next object starting point.
+ std::vector<PrintObject*> objects(printable_objects);
+ std::sort(objects.begin(), objects.end(), [](const PrintObject* po1, const PrintObject* po2) { return po1->size(2) < po2->size(2); });
+ size_t finished_objects = 0;
+ for (size_t object_id = initial_print_object_id; object_id < objects.size(); ++ object_id) {
+ const PrintObject &object = *objects[object_id];
+ for (const Point &copy : object.copies()) {
+ // Get optimal tool ordering to minimize tool switches of a multi-exruder print.
+ if (object_id != initial_print_object_id || &copy != object.copies().data()) {
+ // Don't initialize for the first object and first copy.
+ tool_ordering = ToolOrdering(object, final_extruder_id);
+ unsigned int new_extruder_id = tool_ordering.first_extruder();
+ if (new_extruder_id == (unsigned int)-1)
+ // Skip this object.
+ continue;
+ initial_extruder_id = new_extruder_id;
+ final_extruder_id = tool_ordering.last_extruder();
+ assert(final_extruder_id != (unsigned int)-1);
+ }
+ print.throw_if_canceled();
+ this->set_origin(unscale(copy));
+ if (finished_objects > 0) {
+ // Move to the origin position for the copy we're going to print.
+ // This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
+ m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
+ m_avoid_crossing_perimeters.use_external_mp_once = true;
+ _write(file, this->retract());
+ _write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object"));
+ m_enable_cooling_markers = true;
+ // Disable motion planner when traveling to first object point.
+ m_avoid_crossing_perimeters.disable_once = true;
+ // Ff we are printing the bottom layer of an object, and we have already finished
+ // another one, set first layer temperatures. This happens before the Z move
+ // is triggered, so machine has more time to reach such temperatures.
+ m_placeholder_parser.set("current_object_idx", int(finished_objects));
+ std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config().between_objects_gcode.value, initial_extruder_id);
+ // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature.
+ this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false);
+ this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false);
+ _writeln(file, between_objects_gcode);
+ }
+ // Reset the cooling buffer internal state (the current position, feed rate, accelerations).
+ m_cooling_buffer->reset();
+ m_cooling_buffer->set_current_extruder(initial_extruder_id);
+ // Pair the object layers with the support layers by z, extrude them.
+ std::vector<LayerToPrint> layers_to_print = collect_layers_to_print(object);
+ for (const LayerToPrint &ltp : layers_to_print) {
+ std::vector<LayerToPrint> lrs;
+ lrs.emplace_back(std::move(ltp));
+ this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), &copy - object.copies().data());
+ print.throw_if_canceled();
+ }
+ if (m_pressure_equalizer)
+ _write(file, m_pressure_equalizer->process("", true));
+ ++ finished_objects;
+ // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
+ // Reset it when starting another object from 1st layer.
+ m_second_layer_things_done = false;
+ }
+ }
+ } else {
+ // Order objects using a nearest neighbor search.
+ std::vector<size_t> object_indices;
+ Points object_reference_points;
+ PrintObjectPtrs printable_objects = print.get_printable_objects();
+ for (PrintObject *object : printable_objects)
+ object_reference_points.push_back(object->copies().front());
+ Slic3r::Geometry::chained_path(object_reference_points, object_indices);
+ // Sort layers by Z.
+ // All extrusion moves with the same top layer height are extruded uninterrupted.
+ std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
+ // Prusa Multi-Material wipe tower.
+ if (has_wipe_tower && ! layers_to_print.empty()) {
+ m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()));
+ _write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
+ if (print.config().single_extruder_multi_material_priming) {
+ _write(file, m_wipe_tower->prime(*this));
+ // Verify, whether the print overaps the priming extrusions.
+ BoundingBoxf bbox_print(get_print_extrusions_extents(print));
+ coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
+ for (const PrintObject *print_object : printable_objects)
+ bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
+ bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
+ BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
+ bbox_prime.offset(0.5f);
+ // Beep for 500ms, tone 800Hz. Yet better, play some Morse.
+ _write(file, this->retract());
+ _write(file, "M300 S800 P500\n");
+ if (bbox_prime.overlap(bbox_print)) {
+ // Wait for the user to remove the priming extrusions, otherwise they would
+ // get covered by the print.
+ _write(file, "M1 Remove priming towers and click button.\n");
+ }
+ else {
+ // Just wait for a bit to let the user check, that the priming succeeded.
+ //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
+ _write(file, "M1 S10\n");
+ }
+ }
+ print.throw_if_canceled();
+ }
+ // Extrude the layers.
+ for (auto &layer : layers_to_print) {
+ const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first);
+ if (m_wipe_tower && layer_tools.has_wipe_tower)
+ m_wipe_tower->next_layer();
+ this->process_layer(file, print, layer.second, layer_tools, size_t(-1));
+ print.throw_if_canceled();
+ }
+ if (m_pressure_equalizer)
+ _write(file, m_pressure_equalizer->process("", true));
+ if (m_wipe_tower)
+ // Purge the extruder, pull out the active filament.
+ _write(file, m_wipe_tower->finalize(*this));
+ }
+
+ // Write end commands to file.
+ _write(file, this->retract());
+ _write(file, m_writer.set_fan(false));
+
+ if (m_enable_analyzer)
+ {
+ // adds tag for analyzer
+ char buf[32];
+ sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom);
+ _writeln(file, buf);
+ }
+
+ // Process filament-specific gcode in extruder order.
+ {
+ DynamicConfig config;
+ config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
+ config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position()(2) - m_config.z_offset.value));
+ if (print.config().single_extruder_multi_material) {
+ // Process the end_filament_gcode for the active filament only.
+ _writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id(), &config));
+ } else {
+ for (const std::string &end_gcode : print.config().end_filament_gcode.values)
+ _writeln(file, this->placeholder_parser_process("end_filament_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front()), &config));
+ }
+ _writeln(file, this->placeholder_parser_process("end_gcode", print.config().end_gcode, m_writer.extruder()->id(), &config));
+ }
+ _write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100%
+ _write(file, m_writer.postamble());
+ print.throw_if_canceled();
+
+ // calculates estimated printing time
+ m_normal_time_estimator.calculate_time(false);
+ if (m_silent_time_estimator_enabled)
+ m_silent_time_estimator.calculate_time(false);
+
+ // Get filament stats.
+ print.m_print_statistics.clear();
+ print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms();
+ print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A";
+ for (const Extruder &extruder : m_writer.extruders()) {
+ double used_filament = extruder.used_filament() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] : 0.f);
+ double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
+ double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
+ double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
+ print.m_print_statistics.filament_stats.insert(std::pair<size_t, float>(extruder.id(), (float)used_filament));
+ _write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001);
+ if (filament_weight > 0.) {
+ print.m_print_statistics.total_weight = print.m_print_statistics.total_weight + filament_weight;
+ _write_format(file, "; filament used = %.1lf\n", filament_weight);
+ if (filament_cost > 0.) {
+ print.m_print_statistics.total_cost = print.m_print_statistics.total_cost + filament_cost;
+ _write_format(file, "; filament cost = %.1lf\n", filament_cost);
+ }
+ }
+ print.m_print_statistics.total_used_filament += used_filament;
+ print.m_print_statistics.total_extruded_volume += extruded_volume;
+ print.m_print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
+ print.m_print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
+ }
+ _write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
+ _write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());
+ if (m_silent_time_estimator_enabled)
+ _write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str());
+
+ // Append full config.
+ _write(file, "\n");
+ {
+ std::string full_config = "";
+ append_full_config(print, full_config);
+ if (!full_config.empty())
+ _write(file, full_config);
+ }
+ print.throw_if_canceled();
+
+ // starts analizer calculations
+ if (preview_data != nullptr)
+ m_analyzer.calc_gcode_preview_data(*preview_data);
+}
+
+std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override)
+{
+ try {
+ return m_placeholder_parser.process(templ, current_extruder_id, config_override);
+ } catch (std::runtime_error &err) {
+ // Collect the names of failed template substitutions for error reporting.
+ m_placeholder_parser_failed_templates.insert(name);
+ // Insert the macro error message into the G-code.
+ return
+ std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" +
+ err.what() +
+ "!!!!! End of an error report for the custom G-code template " + name + "\n\n";
+ }
+}
+
+// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code.
+// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out.
+static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, int &temp_out)
+{
+ temp_out = -1;
+ if (gcode.empty())
+ return false;
+
+ const char *ptr = gcode.data();
+ bool temp_set_by_gcode = false;
+ while (*ptr != 0) {
+ // Skip whitespaces.
+ for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
+ if (*ptr == 'M') {
+ // Line starts with 'M'. It is a machine command.
+ ++ ptr;
+ // Parse the M code value.
+ char *endptr = nullptr;
+ int mcode = int(strtol(ptr, &endptr, 10));
+ if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) {
+ // M104/M109 or M140/M190 found.
+ ptr = endptr;
+ // Let the caller know that the custom G-code sets the temperature.
+ temp_set_by_gcode = true;
+ // Now try to parse the temperature value.
+ // While not at the end of the line:
+ while (strchr(";\r\n\0", *ptr) == nullptr) {
+ // Skip whitespaces.
+ for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
+ if (*ptr == 'S') {
+ // Skip whitespaces.
+ for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr);
+ // Parse an int.
+ endptr = nullptr;
+ long temp_parsed = strtol(ptr, &endptr, 10);
+ if (endptr > ptr) {
+ ptr = endptr;
+ temp_out = temp_parsed;
+ }
+ } else {
+ // Skip this word.
+ for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr);
+ }
+ }
+ }
+ }
+ // Skip the rest of the line.
+ for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr);
+ // Skip the end of line indicators.
+ for (; *ptr == '\r' || *ptr == '\n'; ++ ptr);
+ }
+ return temp_set_by_gcode;
+}
+
+// Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters.
+// Do not process this piece of G-code by the time estimator, it already knows the values through another sources.
+void GCode::print_machine_envelope(FILE *file, Print &print)
+{
+ if (print.config().gcode_flavor.value == gcfMarlin) {
+ fprintf(file, "M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n",
+ int(print.config().machine_max_acceleration_x.values.front() + 0.5),
+ int(print.config().machine_max_acceleration_y.values.front() + 0.5),
+ int(print.config().machine_max_acceleration_z.values.front() + 0.5),
+ int(print.config().machine_max_acceleration_e.values.front() + 0.5));
+ fprintf(file, "M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec\n",
+ int(print.config().machine_max_feedrate_x.values.front() + 0.5),
+ int(print.config().machine_max_feedrate_y.values.front() + 0.5),
+ int(print.config().machine_max_feedrate_z.values.front() + 0.5),
+ int(print.config().machine_max_feedrate_e.values.front() + 0.5));
+ fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n",
+ int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
+ int(print.config().machine_max_acceleration_retracting.values.front() + 0.5),
+ int(print.config().machine_max_acceleration_extruding.values.front() + 0.5));
+ fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n",
+ print.config().machine_max_jerk_x.values.front(),
+ print.config().machine_max_jerk_y.values.front(),
+ print.config().machine_max_jerk_z.values.front(),
+ print.config().machine_max_jerk_e.values.front());
+ fprintf(file, "M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n",
+ int(print.config().machine_min_extruding_rate.values.front() + 0.5),
+ int(print.config().machine_min_travel_rate.values.front() + 0.5));
+ }
+}
+
+// Write 1st layer bed temperatures into the G-code.
+// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
+// M140 - Set Extruder Temperature
+// M190 - Set Extruder Temperature and Wait
+void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
+{
+ // Initial bed temperature based on the first extruder.
+ int temp = print.config().first_layer_bed_temperature.get_at(first_printing_extruder_id);
+ // Is the bed temperature set by the provided custom G-code?
+ int temp_by_gcode = -1;
+ bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode);
+ if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000)
+ temp = temp_by_gcode;
+ // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
+ // the custom start G-code emited these.
+ std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait);
+ if (! temp_set_by_gcode)
+ _write(file, set_temp_gcode);
+}
+
+// Write 1st layer extruder temperatures into the G-code.
+// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
+// M104 - Set Extruder Temperature
+// M109 - Set Extruder Temperature and Wait
+void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
+{
+ // Is the bed temperature set by the provided custom G-code?
+ int temp_by_gcode = -1;
+ if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) {
+ // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code.
+ int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
+ if (temp_by_gcode >= 0 && temp_by_gcode < 1000)
+ temp = temp_by_gcode;
+ m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id);
+ } else {
+ // Custom G-code does not set the extruder temperature. Do it now.
+ if (print.config().single_extruder_multi_material.value) {
+ // Set temperature of the first printing extruder only.
+ int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
+ if (temp > 0)
+ _write(file, m_writer.set_temperature(temp, wait, first_printing_extruder_id));
+ } else {
+ // Set temperatures of all the printing extruders.
+ for (unsigned int tool_id : print.extruders()) {
+ int temp = print.config().first_layer_temperature.get_at(tool_id);
+ if (print.config().ooze_prevention.value)
+ temp += print.config().standby_temperature_delta.value;
+ if (temp > 0)
+ _write(file, m_writer.set_temperature(temp, wait, tool_id));
+ }
+ }
+ }
+}
+
+inline GCode::ObjectByExtruder& object_by_extruder(
+ std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
+ unsigned int extruder_id,
+ size_t object_idx,
+ size_t num_objects)
+{
+ std::vector<GCode::ObjectByExtruder> &objects_by_extruder = by_extruder[extruder_id];
+ if (objects_by_extruder.empty())
+ objects_by_extruder.assign(num_objects, GCode::ObjectByExtruder());
+ return objects_by_extruder[object_idx];
+}
+
+inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
+ std::map<unsigned int, std::vector<GCode::ObjectByExtruder>> &by_extruder,
+ unsigned int extruder_id,
+ size_t object_idx,
+ size_t num_objects,
+ size_t num_islands)
+{
+ std::vector<GCode::ObjectByExtruder::Island> &islands = object_by_extruder(by_extruder, extruder_id, object_idx, num_objects).islands;
+ if (islands.empty())
+ islands.assign(num_islands, GCode::ObjectByExtruder::Island());
+ return islands;
+}
+
+// In sequential mode, process_layer is called once per each object and its copy,
+// therefore layers will contain a single entry and single_object_idx will point to the copy of the object.
+// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
+// For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
+// and performing the extruder specific extrusions together.
+void GCode::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)
+{
+ assert(! layers.empty());
+ assert(! layer_tools.extruders.empty());
+ // Either printing all copies of all objects, or just a single copy of a single object.
+ assert(single_object_idx == size_t(-1) || layers.size() == 1);
+
+ if (layer_tools.extruders.empty())
+ // Nothing to extrude.
+ return;
+
+ // Extract 1st object_layer and support_layer of this set of layers with an equal print_z.
+ const Layer *object_layer = nullptr;
+ const SupportLayer *support_layer = nullptr;
+ for (const LayerToPrint &l : layers) {
+ if (l.object_layer != nullptr && object_layer == nullptr)
+ object_layer = l.object_layer;
+ if (l.support_layer != nullptr && support_layer == nullptr)
+ support_layer = l.support_layer;
+ }
+ const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer;
+ coordf_t print_z = layer.print_z;
+ bool first_layer = layer.id() == 0;
+ unsigned int first_extruder_id = layer_tools.extruders.front();
+
+ // Initialize config with the 1st object to be printed at this layer.
+ m_config.apply(layer.object()->config(), true);
+
+ // Check whether it is possible to apply the spiral vase logic for this layer.
+ // Just a reminder: A spiral vase mode is allowed for a single object, single material print only.
+ if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) {
+ bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= print.config().skirt_height.value && ! print.has_infinite_skirt());
+ if (enable) {
+ for (const LayerRegion *layer_region : layer.regions())
+ if (layer_region->region()->config().bottom_solid_layers.value > layer.id() ||
+ layer_region->perimeters.items_count() > 1 ||
+ layer_region->fills.items_count() > 0) {
+ enable = false;
+ break;
+ }
+ }
+ m_spiral_vase->enable = enable;
+ }
+ // If we're going to apply spiralvase to this layer, disable loop clipping
+ m_enable_loop_clipping = ! m_spiral_vase || ! m_spiral_vase->enable;
+
+ std::string gcode;
+
+ // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
+ if (! print.config().before_layer_gcode.value.empty()) {
+ DynamicConfig config;
+ config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
+ config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
+ gcode += this->placeholder_parser_process("before_layer_gcode",
+ print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config)
+ + "\n";
+ }
+ gcode += this->change_layer(print_z); // this will increase m_layer_index
+ m_layer = &layer;
+ if (! print.config().layer_gcode.value.empty()) {
+ DynamicConfig config;
+ config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
+ config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
+ gcode += this->placeholder_parser_process("layer_gcode",
+ print.config().layer_gcode.value, m_writer.extruder()->id(), &config)
+ + "\n";
+ }
+
+ if (! first_layer && ! m_second_layer_things_done) {
+ // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent
+ // first_layer_temperature vs. temperature settings.
+ for (const Extruder &extruder : m_writer.extruders()) {
+ if (print.config().single_extruder_multi_material.value && extruder.id() != m_writer.extruder()->id())
+ // In single extruder multi material mode, set the temperature for the current extruder only.
+ continue;
+ int temperature = print.config().temperature.get_at(extruder.id());
+ if (temperature > 0 && temperature != print.config().first_layer_temperature.get_at(extruder.id()))
+ gcode += m_writer.set_temperature(temperature, false, extruder.id());
+ }
+ gcode += m_writer.set_bed_temperature(print.config().bed_temperature.get_at(first_extruder_id));
+ // Mark the temperature transition from 1st to 2nd layer to be finished.
+ m_second_layer_things_done = true;
+ }
+
+ // Extrude skirt at the print_z of the raft layers and normal object layers
+ // not at the print_z of the interlaced support material layers.
+ bool extrude_skirt =
+ ! print.skirt().entities.empty() &&
+ // Not enough skirt layers printed yet.
+ (m_skirt_done.size() < print.config().skirt_height.value || print.has_infinite_skirt()) &&
+ // This print_z has not been extruded yet
+ (m_skirt_done.empty() ? 0. : m_skirt_done.back()) < print_z - EPSILON &&
+ // and this layer is the 1st layer, or it is an object layer, or it is a raft layer.
+ (first_layer || object_layer != nullptr || support_layer->id() < m_config.raft_layers.value);
+ std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder;
+ coordf_t skirt_height = 0.;
+ if (extrude_skirt) {
+ // Fill in skirt_loops_per_extruder.
+ skirt_height = print_z - (m_skirt_done.empty() ? 0. : m_skirt_done.back());
+ m_skirt_done.push_back(print_z);
+ if (first_layer) {
+ // Prime the extruders over the skirt lines.
+ std::vector<unsigned int> extruder_ids = m_writer.extruder_ids();
+ // Reorder the extruders, so that the last used extruder is at the front.
+ for (size_t i = 1; i < extruder_ids.size(); ++ i)
+ if (extruder_ids[i] == first_extruder_id) {
+ // Move the last extruder to the front.
+ memmove(extruder_ids.data() + 1, extruder_ids.data(), i * sizeof(unsigned int));
+ extruder_ids.front() = first_extruder_id;
+ break;
+ }
+ size_t n_loops = print.skirt().entities.size();
+ if (n_loops <= extruder_ids.size()) {
+ for (size_t i = 0; i < n_loops; ++i)
+ skirt_loops_per_extruder[extruder_ids[i]] = std::pair<size_t, size_t>(i, i + 1);
+ } else {
+ // Assign skirt loops to the extruders.
+ std::vector<unsigned int> extruder_loops(extruder_ids.size(), 1);
+ n_loops -= extruder_loops.size();
+ while (n_loops > 0) {
+ for (size_t i = 0; i < extruder_ids.size() && n_loops > 0; ++ i, -- n_loops)
+ ++ extruder_loops[i];
+ }
+ for (size_t i = 0; i < extruder_ids.size(); ++ i)
+ skirt_loops_per_extruder[extruder_ids[i]] = std::make_pair<size_t, size_t>(
+ (i == 0) ? 0 : extruder_loops[i - 1],
+ ((i == 0) ? 0 : extruder_loops[i - 1]) + extruder_loops[i]);
+ }
+ } else
+ // Extrude all skirts with the current extruder.
+ skirt_loops_per_extruder[first_extruder_id] = std::pair<size_t, size_t>(0, print.config().skirts.value);
+ }
+
+ // Group extrusions by an extruder, then by an object, an island and a region.
+ std::map<unsigned int, std::vector<ObjectByExtruder>> by_extruder;
+ for (const LayerToPrint &layer_to_print : layers) {
+ if (layer_to_print.support_layer != nullptr) {
+ const SupportLayer &support_layer = *layer_to_print.support_layer;
+ const PrintObject &object = *support_layer.object();
+ if (! support_layer.support_fills.entities.empty()) {
+ ExtrusionRole role = support_layer.support_fills.role();
+ bool has_support = role == erMixed || role == erSupportMaterial;
+ bool has_interface = role == erMixed || role == erSupportMaterialInterface;
+ // Extruder ID of the support base. -1 if "don't care".
+ unsigned int support_extruder = object.config().support_material_extruder.value - 1;
+ // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
+ bool support_dontcare = object.config().support_material_extruder.value == 0;
+ // Extruder ID of the support interface. -1 if "don't care".
+ unsigned int interface_extruder = object.config().support_material_interface_extruder.value - 1;
+ // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
+ bool interface_dontcare = object.config().support_material_interface_extruder.value == 0;
+ if (support_dontcare || interface_dontcare) {
+ // Some support will be printed with "don't care" material, preferably non-soluble.
+ // Is the current extruder assigned a soluble filament?
+ unsigned int dontcare_extruder = first_extruder_id;
+ if (print.config().filament_soluble.get_at(dontcare_extruder)) {
+ // The last extruder printed on the previous layer extrudes soluble filament.
+ // Try to find a non-soluble extruder on the same layer.
+ for (unsigned int extruder_id : layer_tools.extruders)
+ if (! print.config().filament_soluble.get_at(extruder_id)) {
+ dontcare_extruder = extruder_id;
+ break;
+ }
+ }
+ if (support_dontcare)
+ support_extruder = dontcare_extruder;
+ if (interface_dontcare)
+ interface_extruder = dontcare_extruder;
+ }
+ // Both the support and the support interface are printed with the same extruder, therefore
+ // the interface may be interleaved with the support base.
+ bool single_extruder = ! has_support || support_extruder == interface_extruder;
+ // Assign an extruder to the base.
+ ObjectByExtruder &obj = object_by_extruder(by_extruder, has_support ? support_extruder : interface_extruder, &layer_to_print - layers.data(), layers.size());
+ obj.support = &support_layer.support_fills;
+ obj.support_extrusion_role = single_extruder ? erMixed : erSupportMaterial;
+ if (! single_extruder && has_interface) {
+ ObjectByExtruder &obj_interface = object_by_extruder(by_extruder, interface_extruder, &layer_to_print - layers.data(), layers.size());
+ obj_interface.support = &support_layer.support_fills;
+ obj_interface.support_extrusion_role = erSupportMaterialInterface;
+ }
+ }
+ }
+ if (layer_to_print.object_layer != nullptr) {
+ const Layer &layer = *layer_to_print.object_layer;
+ // We now define a strategy for building perimeters and fills. The separation
+ // between regions doesn't matter in terms of printing order, as we follow
+ // another logic instead:
+ // - we group all extrusions by extruder so that we minimize toolchanges
+ // - we start from the last used extruder
+ // - for each extruder, we group extrusions by island
+ // - for each island, we extrude perimeters first, unless user set the infill_first
+ // option
+ // (Still, we have to keep track of regions because we need to apply their config)
+ size_t n_slices = layer.slices.expolygons.size();
+ std::vector<BoundingBox> layer_surface_bboxes;
+ layer_surface_bboxes.reserve(n_slices);
+ for (const ExPolygon &expoly : layer.slices.expolygons)
+ layer_surface_bboxes.push_back(get_extents(expoly.contour));
+ auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) {
+ const BoundingBox &bbox = layer_surface_bboxes[i];
+ return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
+ point(1) >= bbox.min(1) && point(1) < bbox.max(1) &&
+ layer.slices.expolygons[i].contour.contains(point);
+ };
+
+ for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) {
+ const LayerRegion *layerm = layer.regions()[region_id];
+ if (layerm == nullptr)
+ continue;
+ const PrintRegion &region = *print.regions()[region_id];
+
+
+ // Now we must process perimeters and infills and create islands of extrusions in by_region std::map.
+ // It is also necessary to save which extrusions are part of MM wiping and which are not.
+ // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice:
+ for (std::string entity_type("infills") ; entity_type != "done" ; entity_type = entity_type=="infills" ? "perimeters" : "done") {
+
+ const ExtrusionEntitiesPtr& source_entities = entity_type=="infills" ? layerm->fills.entities : layerm->perimeters.entities;
+
+ for (const ExtrusionEntity *ee : source_entities) {
+ // fill represents infill extrusions of a single island.
+ const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
+ if (fill->entities.empty()) // This shouldn't happen but first_point() would fail.
+ continue;
+
+ // This extrusion is part of certain Region, which tells us which extruder should be used for it:
+ int correct_extruder_id = Print::get_extruder(*fill, region);
+ //FIXME what is this?
+ entity_type=="infills" ?
+ std::max<int>(0, (is_solid_infill(fill->entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) - 1) :
+ std::max<int>(region.config().perimeter_extruder.value - 1, 0);
+
+ // Let's recover vector of extruder overrides:
+ const ExtruderPerCopy* entity_overrides = const_cast<LayerTools&>(layer_tools).wiping_extrusions().get_extruder_overrides(fill, correct_extruder_id, layer_to_print.object()->copies().size());
+
+ // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it:
+ for (unsigned int extruder : layer_tools.extruders)
+ {
+ // Init by_extruder item only if we actually use the extruder:
+ if (std::find(entity_overrides->begin(), entity_overrides->end(), extruder) != entity_overrides->end() || // at least one copy is overridden to use this extruder
+ std::find(entity_overrides->begin(), entity_overrides->end(), -extruder-1) != entity_overrides->end() || // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation)
+ (std::find(layer_tools.extruders.begin(), layer_tools.extruders.end(), correct_extruder_id) == layer_tools.extruders.end() && extruder == layer_tools.extruders.back())) // this entity is not overridden, but its extruder is not in layer_tools - we'll print it
+ //by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
+ {
+ std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder(
+ by_extruder,
+ extruder,
+ &layer_to_print - layers.data(),
+ layers.size(), n_slices+1);
+ for (size_t i = 0; i <= n_slices; ++i)
+ if (// fill->first_point does not fit inside any slice
+ i == n_slices ||
+ // fill->first_point fits inside ith slice
+ point_inside_surface(i, fill->first_point())) {
+ if (islands[i].by_region.empty())
+ islands[i].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region());
+ islands[i].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size());
+ break;
+ }
+ }
+ }
+ }
+ }
+ } // for regions
+ }
+ } // for objects
+
+
+
+ // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders.
+ std::vector<std::unique_ptr<EdgeGrid::Grid>> lower_layer_edge_grids(layers.size());
+ for (unsigned int extruder_id : layer_tools.extruders)
+ {
+ gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ?
+ m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) :
+ this->set_extruder(extruder_id);
+
+ // let analyzer tag generator aware of a role type change
+ if (m_enable_analyzer && layer_tools.has_wipe_tower && m_wipe_tower)
+ m_last_analyzer_extrusion_role = erWipeTower;
+
+ if (extrude_skirt) {
+ auto loops_it = skirt_loops_per_extruder.find(extruder_id);
+ if (loops_it != skirt_loops_per_extruder.end()) {
+ const std::pair<size_t, size_t> loops = loops_it->second;
+ this->set_origin(0.,0.);
+ m_avoid_crossing_perimeters.use_external_mp = true;
+ Flow skirt_flow = print.skirt_flow();
+ for (size_t i = loops.first; i < loops.second; ++ i) {
+ // Adjust flow according to this layer's layer height.
+ ExtrusionLoop loop = *dynamic_cast<const ExtrusionLoop*>(print.skirt().entities[i]);
+ Flow layer_skirt_flow(skirt_flow);
+ layer_skirt_flow.height = (float)skirt_height;
+ double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
+ for (ExtrusionPath &path : loop.paths) {
+ path.height = (float)layer.height;
+ path.mm3_per_mm = mm3_per_mm;
+ }
+ gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value);
+ }
+ m_avoid_crossing_perimeters.use_external_mp = false;
+ // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
+ if (first_layer && loops.first == 0)
+ m_avoid_crossing_perimeters.disable_once = true;
+ }
+ }
+
+ // Extrude brim with the extruder of the 1st region.
+ if (! m_brim_done) {
+ this->set_origin(0., 0.);
+ m_avoid_crossing_perimeters.use_external_mp = true;
+ for (const ExtrusionEntity *ee : print.brim().entities)
+ gcode += this->extrude_loop(*dynamic_cast<const ExtrusionLoop*>(ee), "brim", m_config.support_material_speed.value);
+ m_brim_done = true;
+ m_avoid_crossing_perimeters.use_external_mp = false;
+ // Allow a straight travel move to the first object point.
+ m_avoid_crossing_perimeters.disable_once = true;
+ }
+
+
+ auto objects_by_extruder_it = by_extruder.find(extruder_id);
+ if (objects_by_extruder_it == by_extruder.end())
+ continue;
+ // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
+ for (int print_wipe_extrusions=const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden(); print_wipe_extrusions>=0; --print_wipe_extrusions) {
+ if (print_wipe_extrusions == 0)
+ gcode+="; PURGING FINISHED\n";
+
+ for (ObjectByExtruder &object_by_extruder : objects_by_extruder_it->second) {
+ const size_t layer_id = &object_by_extruder - objects_by_extruder_it->second.data();
+ const PrintObject *print_object = layers[layer_id].object();
+ if (print_object == nullptr)
+ // This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z.
+ continue;
+
+ m_config.apply(print_object->config(), true);
+ m_layer = layers[layer_id].layer();
+ if (m_config.avoid_crossing_perimeters)
+ m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true));
+ Points copies;
+ if (single_object_idx == size_t(-1))
+ copies = print_object->copies();
+ else
+ copies.push_back(print_object->copies()[single_object_idx]);
+ // Sort the copies by the closest point starting with the current print position.
+
+ unsigned int copy_id = 0;
+ for (const Point &copy : copies) {
+ // When starting a new object, use the external motion planner for the first travel move.
+ std::pair<const PrintObject*, Point> this_object_copy(print_object, copy);
+ if (m_last_obj_copy != this_object_copy)
+ m_avoid_crossing_perimeters.use_external_mp_once = true;
+ m_last_obj_copy = this_object_copy;
+ this->set_origin(unscale(copy));
+ if (object_by_extruder.support != nullptr && !print_wipe_extrusions) {
+ m_layer = layers[layer_id].support_layer;
+ gcode += this->extrude_support(
+ // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
+ object_by_extruder.support->chained_path_from(m_last_pos, false, object_by_extruder.support_extrusion_role));
+ m_layer = layers[layer_id].layer();
+ }
+ for (ObjectByExtruder::Island &island : object_by_extruder.islands) {
+ const auto& by_region_specific = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden() ? island.by_region_per_copy(copy_id, extruder_id, print_wipe_extrusions) : island.by_region;
+
+ if (print.config().infill_first) {
+ gcode += this->extrude_infill(print, by_region_specific);
+ gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]);
+ } else {
+ gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]);
+ gcode += this->extrude_infill(print,by_region_specific);
+ }
+ }
+ ++copy_id;
+ }
+ }
+ }
+ }
+
+ // Apply spiral vase post-processing if this layer contains suitable geometry
+ // (we must feed all the G-code into the post-processor, including the first
+ // bottom non-spiral layers otherwise it will mess with positions)
+ // we apply spiral vase at this stage because it requires a full layer.
+ // Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only.
+ if (m_spiral_vase)
+ gcode = m_spiral_vase->process_layer(gcode);
+
+ // Apply cooling logic; this may alter speeds.
+ if (m_cooling_buffer)
+ gcode = m_cooling_buffer->process_layer(gcode, layer.id());
+
+ // Apply pressure equalization if enabled;
+ // printf("G-code before filter:\n%s\n", gcode.c_str());
+ if (m_pressure_equalizer)
+ gcode = m_pressure_equalizer->process(gcode.c_str(), false);
+ // printf("G-code after filter:\n%s\n", out.c_str());
+
+ _write(file, gcode);
+}
+
+void GCode::apply_print_config(const PrintConfig &print_config)
+{
+ m_writer.apply_print_config(print_config);
+ m_config.apply(print_config);
+}
+
+void GCode::append_full_config(const Print& print, std::string& str)
+{
+ const StaticPrintConfig *configs[] = { static_cast<const GCodeConfig*>(&print.config()), &print.default_object_config(), &print.default_region_config() };
+ for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++i) {
+ const StaticPrintConfig *cfg = configs[i];
+ for (const std::string &key : cfg->keys())
+ if (key != "compatible_printers")
+ str += "; " + key + " = " + cfg->serialize(key) + "\n";
+ }
+ const DynamicConfig &full_config = print.placeholder_parser().config();
+ for (const char *key : {
+ "print_settings_id", "filament_settings_id", "printer_settings_id",
+ "printer_model", "printer_variant", "default_print_profile", "default_filament_profile",
+ "compatible_printers_condition_cummulative", "inherits_cummulative" }) {
+ const ConfigOption *opt = full_config.option(key);
+ if (opt != nullptr)
+ str += std::string("; ") + key + " = " + opt->serialize() + "\n";
+ }
+}
+
+void GCode::set_extruders(const std::vector<unsigned int> &extruder_ids)
+{
+ m_writer.set_extruders(extruder_ids);
+
+ // enable wipe path generation if any extruder has wipe enabled
+ m_wipe.enable = false;
+ for (auto id : extruder_ids)
+ if (m_config.wipe.get_at(id)) {
+ m_wipe.enable = true;
+ break;
+ }
+}
+
+void GCode::set_origin(const Vec2d &pointf)
+{
+ // if origin increases (goes towards right), last_pos decreases because it goes towards left
+ const Point translate(
+ scale_(m_origin(0) - pointf(0)),
+ scale_(m_origin(1) - pointf(1))
+ );
+ m_last_pos += translate;
+ m_wipe.path.translate(translate);
+ m_origin = pointf;
+}
+
+std::string GCode::preamble()
+{
+ std::string gcode = m_writer.preamble();
+
+ /* Perform a *silent* move to z_offset: we need this to initialize the Z
+ position of our writer object so that any initial lift taking place
+ before the first layer change will raise the extruder from the correct
+ initial Z instead of 0. */
+ m_writer.travel_to_z(m_config.z_offset.value);
+
+ return gcode;
+}
+
+// called by GCode::process_layer()
+std::string GCode::change_layer(coordf_t print_z)
+{
+ std::string gcode;
+ if (m_layer_count > 0)
+ // Increment a progress bar indicator.
+ gcode += m_writer.update_progress(++ m_layer_index, m_layer_count);
+ coordf_t z = print_z + m_config.z_offset.value; // in unscaled coordinates
+ if (EXTRUDER_CONFIG(retract_layer_change) && m_writer.will_move_z(z))
+ gcode += this->retract();
+
+ {
+ std::ostringstream comment;
+ comment << "move to next layer (" << m_layer_index << ")";
+ gcode += m_writer.travel_to_z(z, comment.str());
+ }
+
+ // forget last wiping path as wiping after raising Z is pointless
+ m_wipe.reset_path();
+
+ return gcode;
+}
+
+static inline const char* ExtrusionRole2String(const ExtrusionRole role)
+{
+ switch (role) {
+ case erNone: return "erNone";
+ case erPerimeter: return "erPerimeter";
+ case erExternalPerimeter: return "erExternalPerimeter";
+ case erOverhangPerimeter: return "erOverhangPerimeter";
+ case erInternalInfill: return "erInternalInfill";
+ case erSolidInfill: return "erSolidInfill";
+ case erTopSolidInfill: return "erTopSolidInfill";
+ case erBridgeInfill: return "erBridgeInfill";
+ case erGapFill: return "erGapFill";
+ case erSkirt: return "erSkirt";
+ case erSupportMaterial: return "erSupportMaterial";
+ case erSupportMaterialInterface: return "erSupportMaterialInterface";
+ case erWipeTower: return "erWipeTower";
+ case erMixed: return "erMixed";
+
+ default: return "erInvalid";
+ };
+}
+
+static inline const char* ExtrusionLoopRole2String(const ExtrusionLoopRole role)
+{
+ switch (role) {
+ case elrDefault: return "elrDefault";
+ case elrContourInternalPerimeter: return "elrContourInternalPerimeter";
+ case elrSkirt: return "elrSkirt";
+ default: return "elrInvalid";
+ }
+};
+
+// Return a value in <0, 1> of a cubic B-spline kernel centered around zero.
+// The B-spline is re-scaled so it has value 1 at zero.
+static inline float bspline_kernel(float x)
+{
+ x = std::abs(x);
+ if (x < 1.f) {
+ return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x;
+ }
+ else if (x < 2.f) {
+ x -= 1.f;
+ float x2 = x * x;
+ float x3 = x2 * x;
+ return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3;
+ }
+ else
+ return 0;
+}
+
+static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance)
+{
+ // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve.
+ // Solved by sympy package:
+/*
+from sympy import *
+(x,a,b,c,d,r,z)=symbols('x a b c d r z')
+p = a + b*x + c*x*x + d*x*x*x
+p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d]))
+from sympy.plotting import plot
+plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400)
+*/
+ if (overlap_distance < - nozzle_r) {
+ // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty.
+ return 0.f;
+ } else {
+ float x = overlap_distance / nozzle_r;
+ float x2 = x * x;
+ float x3 = x2 * x;
+ return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3);
+ }
+}
+
+static Points::iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps)
+{
+ assert(polygon.points.size() >= 2);
+ if (polygon.points.size() <= 1)
+ if (polygon.points.size() == 1)
+ return polygon.points.begin();
+
+ Point pt_min;
+ double d_min = std::numeric_limits<double>::max();
+ size_t i_min = size_t(-1);
+
+ for (size_t i = 0; i < polygon.points.size(); ++ i) {
+ size_t j = i + 1;
+ if (j == polygon.points.size())
+ j = 0;
+ const Point &p1 = polygon.points[i];
+ const Point &p2 = polygon.points[j];
+ const Slic3r::Point v_seg = p2 - p1;
+ const Slic3r::Point v_pt = pt - p1;
+ const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1));
+ int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1));
+ if (t_pt < 0) {
+ // Closest to p1.
+ double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1)));
+ if (dabs < d_min) {
+ d_min = dabs;
+ i_min = i;
+ pt_min = p1;
+ }
+ }
+ else if (t_pt > l2_seg) {
+ // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step.
+ continue;
+ } else {
+ // Closest to the segment.
+ assert(t_pt >= 0 && t_pt <= l2_seg);
+ int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1));
+ double d = double(d_seg) / sqrt(double(l2_seg));
+ double dabs = std::abs(d);
+ if (dabs < d_min) {
+ d_min = dabs;
+ i_min = i;
+ // Evaluate the foot point.
+ pt_min = p1;
+ double linv = double(d_seg) / double(l2_seg);
+ pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5));
+ pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5));
+ assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5));
+ }
+ }
+ }
+
+ assert(i_min != size_t(-1));
+ if ((pt_min - polygon.points[i_min]).cast<double>().norm() > eps) {
+ // Insert a new point on the segment i_min, i_min+1.
+ return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min);
+ }
+ return polygon.points.begin() + i_min;
+}
+
+std::vector<float> polygon_parameter_by_length(const Polygon &polygon)
+{
+ // Parametrize the polygon by its length.
+ std::vector<float> lengths(polygon.points.size()+1, 0.);
+ for (size_t i = 1; i < polygon.points.size(); ++ i)
+ lengths[i] = lengths[i-1] + (polygon.points[i] - polygon.points[i-1]).cast<float>().norm();
+ lengths.back() = lengths[lengths.size()-2] + (polygon.points.front() - polygon.points.back()).cast<float>().norm();
+ return lengths;
+}
+
+std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std::vector<float> &lengths, float min_arm_length)
+{
+ assert(polygon.points.size() + 1 == lengths.size());
+ if (min_arm_length > 0.25f * lengths.back())
+ min_arm_length = 0.25f * lengths.back();
+
+ // Find the initial prev / next point span.
+ size_t idx_prev = polygon.points.size();
+ size_t idx_curr = 0;
+ size_t idx_next = 1;
+ while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length)
+ -- idx_prev;
+ while (idx_next < idx_prev && lengths[idx_next] < min_arm_length)
+ ++ idx_next;
+
+ std::vector<float> angles(polygon.points.size(), 0.f);
+ for (; idx_curr < polygon.points.size(); ++ idx_curr) {
+ // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length.
+ if (idx_prev >= idx_curr) {
+ while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length)
+ ++ idx_prev;
+ if (idx_prev == polygon.points.size())
+ idx_prev = 0;
+ }
+ while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length)
+ ++ idx_prev;
+ // Move idx_prev one step back.
+ if (idx_prev == 0)
+ idx_prev = polygon.points.size() - 1;
+ else
+ -- idx_prev;
+ // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length.
+ if (idx_curr <= idx_next) {
+ while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length)
+ ++ idx_next;
+ if (idx_next == polygon.points.size())
+ idx_next = 0;
+ }
+ while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length)
+ ++ idx_next;
+ // Calculate angle between idx_prev, idx_curr, idx_next.
+ const Point &p0 = polygon.points[idx_prev];
+ const Point &p1 = polygon.points[idx_curr];
+ const Point &p2 = polygon.points[idx_next];
+ const Point v1 = p1 - p0;
+ const Point v2 = p2 - p1;
+ int64_t dot = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1));
+ int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0));
+ float angle = float(atan2(double(cross), double(dot)));
+ angles[idx_curr] = angle;
+ }
+
+ return angles;
+}
+
+std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
+{
+ // get a copy; don't modify the orientation of the original loop object otherwise
+ // next copies (if any) would not detect the correct orientation
+
+ if (m_layer->lower_layer != nullptr && lower_layer_edge_grid != nullptr) {
+ if (! *lower_layer_edge_grid) {
+ // Create the distance field for a layer below.
+ const coord_t distance_field_resolution = coord_t(scale_(1.) + 0.5);
+ *lower_layer_edge_grid = make_unique<EdgeGrid::Grid>();
+ (*lower_layer_edge_grid)->create(m_layer->lower_layer->slices, distance_field_resolution);
+ (*lower_layer_edge_grid)->calculate_sdf();
+ #if 0
+ {
+ static int iRun = 0;
+ BoundingBox bbox = (*lower_layer_edge_grid)->bbox();
+ bbox.min(0) -= scale_(5.f);
+ bbox.min(1) -= scale_(5.f);
+ bbox.max(0) += scale_(5.f);
+ bbox.max(1) += scale_(5.f);
+ EdgeGrid::save_png(*(*lower_layer_edge_grid), bbox, scale_(0.1f), debug_out_path("GCode_extrude_loop_edge_grid-%d.png", iRun++));
+ }
+ #endif
+ }
+ }
+
+ // extrude all loops ccw
+ bool was_clockwise = loop.make_counter_clockwise();
+
+ SeamPosition seam_position = m_config.seam_position;
+ if (loop.loop_role() == elrSkirt)
+ seam_position = spNearest;
+
+ // find the point of the loop that is closest to the current extruder position
+ // or randomize if requested
+ Point last_pos = this->last_pos();
+ if (m_config.spiral_vase) {
+ loop.split_at(last_pos, false);
+ } else if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) {
+ Polygon polygon = loop.polygon();
+ const coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
+ const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5);
+
+ // Retrieve the last start position for this object.
+ float last_pos_weight = 1.f;
+ switch (seam_position) {
+ case spAligned:
+ // Seam is aligned to the seam at the preceding layer.
+ if (m_layer != NULL && m_seam_position.count(m_layer->object()) > 0) {
+ last_pos = m_seam_position[m_layer->object()];
+ last_pos_weight = 1.f;
+ }
+ break;
+ case spRear:
+ last_pos = m_layer->object()->bounding_box().center();
+ last_pos(1) += coord_t(3. * m_layer->object()->bounding_box().radius());
+ last_pos_weight = 5.f;
+ break;
+ }
+
+ // Insert a projection of last_pos into the polygon.
+ size_t last_pos_proj_idx;
+ {
+ Points::iterator it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r);
+ last_pos_proj_idx = it - polygon.points.begin();
+ }
+ Point last_pos_proj = polygon.points[last_pos_proj_idx];
+ // Parametrize the polygon by its length.
+ std::vector<float> lengths = polygon_parameter_by_length(polygon);
+
+ // For each polygon point, store a penalty.
+ // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r.
+ std::vector<float> penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r));
+ // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces.
+ const float penaltyConvexVertex = 1.f;
+ const float penaltyFlatSurface = 5.f;
+ const float penaltySeam = 1.3f;
+ const float penaltyOverhangHalf = 10.f;
+ // Penalty for visible seams.
+ for (size_t i = 0; i < polygon.points.size(); ++ i) {
+ float ccwAngle = penalties[i];
+ if (was_clockwise)
+ ccwAngle = - ccwAngle;
+ float penalty = 0;
+// if (ccwAngle <- float(PI/3.))
+ if (ccwAngle <- float(0.6 * PI))
+ // Sharp reflex vertex. We love that, it hides the seam perfectly.
+ penalty = 0.f;
+// else if (ccwAngle > float(PI/3.))
+ else if (ccwAngle > float(0.6 * PI))
+ // Seams on sharp convex vertices are more visible than on reflex vertices.
+ penalty = penaltyConvexVertex;
+ else if (ccwAngle < 0.f) {
+ // Interpolate penalty between maximum and zero.
+ penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
+ } else {
+ assert(ccwAngle >= 0.f);
+ // Interpolate penalty between maximum and the penalty for a convex vertex.
+ penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.));
+ }
+ // Give a negative penalty for points close to the last point or the prefered seam location.
+ //float dist_to_last_pos_proj = last_pos_proj.distance_to(polygon.points[i]);
+ float dist_to_last_pos_proj = (i < last_pos_proj_idx) ?
+ std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) :
+ std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]);
+ float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr
+ penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max);
+ penalties[i] = std::max(0.f, penalty);
+ }
+
+ // Penalty for overhangs.
+ if (lower_layer_edge_grid && (*lower_layer_edge_grid)) {
+ // Use the edge grid distance field structure over the lower layer to calculate overhangs.
+ coord_t nozzle_r = coord_t(floor(scale_(0.5 * nozzle_dmr) + 0.5));
+ coord_t search_r = coord_t(floor(scale_(0.8 * nozzle_dmr) + 0.5));
+ for (size_t i = 0; i < polygon.points.size(); ++ i) {
+ const Point &p = polygon.points[i];
+ coordf_t dist;
+ // Signed distance is positive outside the object, negative inside the object.
+ // The point is considered at an overhang, if it is more than nozzle radius
+ // outside of the lower layer contour.
+ bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist);
+ // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid,
+ // then the signed distnace shall always be known.
+ assert(found);
+ penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist));
+ }
+ }
+
+ // Find a point with a minimum penalty.
+ size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin();
+
+ // if (seam_position == spAligned)
+ // For all (aligned, nearest, rear) seams:
+ {
+ // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx.
+ // In that case use last_pos_proj_idx instead.
+ float penalty_aligned = penalties[last_pos_proj_idx];
+ float penalty_min = penalties[idx_min];
+ float penalty_diff_abs = std::abs(penalty_min - penalty_aligned);
+ float penalty_max = std::max(penalty_min, penalty_aligned);
+ float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max;
+ // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel);
+ if (penalty_diff_rel < 0.05) {
+ // Penalty of the aligned point is very close to the minimum penalty.
+ // Align the seams as accurately as possible.
+ idx_min = last_pos_proj_idx;
+ }
+ m_seam_position[m_layer->object()] = polygon.points[idx_min];
+ }
+
+ // Export the contour into a SVG file.
+ #if 0
+ {
+ static int iRun = 0;
+ SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++));
+ if (m_layer->lower_layer != NULL)
+ svg.draw(m_layer->lower_layer->slices.expolygons);
+ for (size_t i = 0; i < loop.paths.size(); ++ i)
+ svg.draw(loop.paths[i].as_polyline(), "red");
+ Polylines polylines;
+ for (size_t i = 0; i < loop.paths.size(); ++ i)
+ polylines.push_back(loop.paths[i].as_polyline());
+ Slic3r::Polygons polygons;
+ coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter);
+ coord_t delta = scale_(0.5*nozzle_dmr);
+ Slic3r::offset(polylines, &polygons, delta);
+// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue");
+ svg.draw(last_pos, "green", 3);
+ svg.draw(polygon.points[idx_min], "yellow", 3);
+ svg.Close();
+ }
+ #endif
+
+ // Split the loop at the point with a minium penalty.
+ if (!loop.split_at_vertex(polygon.points[idx_min]))
+ // The point is not in the original loop. Insert it.
+ loop.split_at(polygon.points[idx_min], true);
+
+ } else if (seam_position == spRandom) {
+ if (loop.loop_role() == elrContourInternalPerimeter) {
+ // This loop does not contain any other loop. Set a random position.
+ // The other loops will get a seam close to the random point chosen
+ // on the inner most contour.
+ //FIXME This works correctly for inner contours first only.
+ //FIXME Better parametrize the loop by its length.
+ Polygon polygon = loop.polygon();
+ Point centroid = polygon.centroid();
+ last_pos = Point(polygon.bounding_box().max(0), centroid(1));
+ last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid);
+ }
+ // Find the closest point, avoid overhangs.
+ loop.split_at(last_pos, true);
+ }
+
+ // clip the path to avoid the extruder to get exactly on the first point of the loop;
+ // if polyline was shorter than the clipping distance we'd get a null polyline, so
+ // we discard it in that case
+ double clip_length = m_enable_loop_clipping ?
+ scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER :
+ 0;
+
+ // get paths
+ ExtrusionPaths paths;
+ loop.clip_end(clip_length, &paths);
+ if (paths.empty()) return "";
+
+ // apply the small perimeter speed
+ if (is_perimeter(paths.front().role()) && loop.length() <= SMALL_PERIMETER_LENGTH && speed == -1)
+ speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed);
+
+ // extrude along the path
+ std::string gcode;
+ for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
+// description += ExtrusionLoopRole2String(loop.loop_role());
+// description += ExtrusionRole2String(path->role);
+ path->simplify(SCALED_RESOLUTION);
+ gcode += this->_extrude(*path, description, speed);
+ }
+
+ // reset acceleration
+ gcode += m_writer.set_acceleration((unsigned int)(m_config.default_acceleration.value + 0.5));
+
+ if (m_wipe.enable)
+ m_wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path
+
+ // make a little move inwards before leaving loop
+ if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1) {
+ // detect angle between last and first segment
+ // the side depends on the original winding order of the polygon (left for contours, right for holes)
+ Point a = paths.front().polyline.points[1]; // second point
+ Point b = *(paths.back().polyline.points.end()-3); // second to last point
+ if (was_clockwise) {
+ // swap points
+ Point c = a; a = b; b = c;
+ }
+
+ double angle = paths.front().first_point().ccw_angle(a, b) / 3;
+
+ // turn left if contour, turn right if hole
+ if (was_clockwise) angle *= -1;
+
+ // create the destination point along the first segment and rotate it
+ // we make sure we don't exceed the segment length because we don't know
+ // the rotation of the second segment so we might cross the object boundary
+ Vec2d p1 = paths.front().polyline.points.front().cast<double>();
+ Vec2d p2 = paths.front().polyline.points[1].cast<double>();
+ Vec2d v = p2 - p1;
+ double nd = scale_(EXTRUDER_CONFIG(nozzle_diameter));
+ double l2 = v.squaredNorm();
+ // Shift by no more than a nozzle diameter.
+ //FIXME Hiding the seams will not work nicely for very densely discretized contours!
+ Point pt = ((nd * nd >= l2) ? p2 : (p1 + v * (nd / sqrt(l2)))).cast<coord_t>();
+ pt.rotate(angle, paths.front().polyline.points.front());
+ // generate the travel move
+ gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel");
+ }
+
+ return gcode;
+}
+
+std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed)
+{
+ // extrude along the path
+ std::string gcode;
+ for (ExtrusionPath path : multipath.paths) {
+// description += ExtrusionLoopRole2String(loop.loop_role());
+// description += ExtrusionRole2String(path->role);
+ path.simplify(SCALED_RESOLUTION);
+ gcode += this->_extrude(path, description, speed);
+ }
+ if (m_wipe.enable) {
+ m_wipe.path = std::move(multipath.paths.back().polyline); // TODO: don't limit wipe to last path
+ m_wipe.path.reverse();
+ }
+ // reset acceleration
+ gcode += m_writer.set_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5));
+ return gcode;
+}
+
+std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed, std::unique_ptr<EdgeGrid::Grid> *lower_layer_edge_grid)
+{
+ if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(&entity))
+ return this->extrude_path(*path, description, speed);
+ else if (const ExtrusionMultiPath* multipath = dynamic_cast<const ExtrusionMultiPath*>(&entity))
+ return this->extrude_multi_path(*multipath, description, speed);
+ else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(&entity))
+ return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid);
+ else {
+ throw std::invalid_argument("Invalid argument supplied to extrude()");
+ return "";
+ }
+}
+
+std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed)
+{
+// description += ExtrusionRole2String(path.role());
+ path.simplify(SCALED_RESOLUTION);
+ std::string gcode = this->_extrude(path, description, speed);
+ if (m_wipe.enable) {
+ m_wipe.path = std::move(path.polyline);
+ m_wipe.path.reverse();
+ }
+ // reset acceleration
+ gcode += m_writer.set_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5));
+ return gcode;
+}
+
+// Extrude perimeters: Decide where to put seams (hide or align seams).
+std::string GCode::extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid)
+{
+ std::string gcode;
+ for (const ObjectByExtruder::Island::Region &region : by_region) {
+ m_config.apply(print.regions()[&region - &by_region.front()]->config());
+ for (ExtrusionEntity *ee : region.perimeters.entities)
+ gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid);
+ }
+ return gcode;
+}
+
+// Chain the paths hierarchically by a greedy algorithm to minimize a travel distance.
+std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region)
+{
+ std::string gcode;
+ for (const ObjectByExtruder::Island::Region &region : by_region) {
+ m_config.apply(print.regions()[&region - &by_region.front()]->config());
+ ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false);
+ for (ExtrusionEntity *fill : chained.entities) {
+ auto *eec = dynamic_cast<ExtrusionEntityCollection*>(fill);
+ if (eec) {
+ ExtrusionEntityCollection chained2 = eec->chained_path_from(m_last_pos, false);
+ for (ExtrusionEntity *ee : chained2.entities)
+ gcode += this->extrude_entity(*ee, "infill");
+ } else
+ gcode += this->extrude_entity(*fill, "infill");
+ }
+ }
+ return gcode;
+}
+
+std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills)
+{
+ std::string gcode;
+ if (! support_fills.entities.empty()) {
+ const char *support_label = "support material";
+ const char *support_interface_label = "support material interface";
+ const double support_speed = m_config.support_material_speed.value;
+ const double support_interface_speed = m_config.support_material_interface_speed.get_abs_value(support_speed);
+ for (const ExtrusionEntity *ee : support_fills.entities) {
+ ExtrusionRole role = ee->role();
+ assert(role == erSupportMaterial || role == erSupportMaterialInterface);
+ const char *label = (role == erSupportMaterial) ? support_label : support_interface_label;
+ const double speed = (role == erSupportMaterial) ? support_speed : support_interface_speed;
+ const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(ee);
+ if (path)
+ gcode += this->extrude_path(*path, label, speed);
+ else {
+ const ExtrusionMultiPath *multipath = dynamic_cast<const ExtrusionMultiPath*>(ee);
+ assert(multipath != nullptr);
+ if (multipath)
+ gcode += this->extrude_multi_path(*multipath, label, speed);
+ }
+ }
+ }
+ return gcode;
+}
+
+void GCode::_write(FILE* file, const char *what)
+{
+ if (what != nullptr) {
+ // apply analyzer, if enabled
+ const char* gcode = m_enable_analyzer ? m_analyzer.process_gcode(what).c_str() : what;
+
+ // writes string to file
+ fwrite(gcode, 1, ::strlen(gcode), file);
+ // updates time estimator and gcode lines vector
+ m_normal_time_estimator.add_gcode_block(gcode);
+ if (m_silent_time_estimator_enabled)
+ m_silent_time_estimator.add_gcode_block(gcode);
+ }
+}
+
+void GCode::_writeln(FILE* file, const std::string &what)
+{
+ if (! what.empty())
+ _write(file, (what.back() == '\n') ? what : (what + '\n'));
+}
+
+void GCode::_write_format(FILE* file, const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+
+ int buflen;
+ {
+ va_list args2;
+ va_copy(args2, args);
+ buflen =
+ #ifdef _MSC_VER
+ ::_vscprintf(format, args2)
+ #else
+ ::vsnprintf(nullptr, 0, format, args2)
+ #endif
+ + 1;
+ va_end(args2);
+ }
+
+ char buffer[1024];
+ bool buffer_dynamic = buflen > 1024;
+ char *bufptr = buffer_dynamic ? (char*)malloc(buflen) : buffer;
+ int res = ::vsnprintf(bufptr, buflen, format, args);
+ if (res > 0)
+ _write(file, bufptr);
+
+ if (buffer_dynamic)
+ free(bufptr);
+
+ va_end(args);
+}
+
+std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed)
+{
+ std::string gcode;
+
+ // go to first point of extrusion path
+ if (!m_last_pos_defined || m_last_pos != path.first_point()) {
+ gcode += this->travel_to(
+ path.first_point(),
+ path.role(),
+ "move to first " + description + " point"
+ );
+ }
+
+ // compensate retraction
+ gcode += this->unretract();
+
+ // adjust acceleration
+ {
+ double acceleration;
+ if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) {
+ acceleration = m_config.first_layer_acceleration.value;
+ } else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) {
+ acceleration = m_config.perimeter_acceleration.value;
+ } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) {
+ acceleration = m_config.bridge_acceleration.value;
+ } else if (m_config.infill_acceleration.value > 0 && is_infill(path.role())) {
+ acceleration = m_config.infill_acceleration.value;
+ } else {
+ acceleration = m_config.default_acceleration.value;
+ }
+ gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5));
+ }
+
+ // calculate extrusion length per distance unit
+ double e_per_mm = m_writer.extruder()->e_per_mm3() * path.mm3_per_mm;
+ if (m_writer.extrusion_axis().empty()) e_per_mm = 0;
+
+ // set speed
+ if (speed == -1) {
+ if (path.role() == erPerimeter) {
+ speed = m_config.get_abs_value("perimeter_speed");
+ } else if (path.role() == erExternalPerimeter) {
+ speed = m_config.get_abs_value("external_perimeter_speed");
+ } else if (path.role() == erOverhangPerimeter || path.role() == erBridgeInfill) {
+ speed = m_config.get_abs_value("bridge_speed");
+ } else if (path.role() == erInternalInfill) {
+ speed = m_config.get_abs_value("infill_speed");
+ } else if (path.role() == erSolidInfill) {
+ speed = m_config.get_abs_value("solid_infill_speed");
+ } else if (path.role() == erTopSolidInfill) {
+ speed = m_config.get_abs_value("top_solid_infill_speed");
+ } else if (path.role() == erGapFill) {
+ speed = m_config.get_abs_value("gap_fill_speed");
+ } else {
+ throw std::invalid_argument("Invalid speed");
+ }
+ }
+ if (this->on_first_layer())
+ speed = m_config.get_abs_value("first_layer_speed", speed);
+ if (m_volumetric_speed != 0. && speed == 0)
+ speed = m_volumetric_speed / path.mm3_per_mm;
+ if (m_config.max_volumetric_speed.value > 0) {
+ // cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
+ speed = std::min(
+ speed,
+ m_config.max_volumetric_speed.value / path.mm3_per_mm
+ );
+ }
+ if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) {
+ // cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
+ speed = std::min(
+ speed,
+ EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm
+ );
+ }
+ double F = speed * 60; // convert mm/sec to mm/min
+
+ // extrude arc or line
+ if (m_enable_extrusion_role_markers)
+ {
+ if (path.role() != m_last_extrusion_role)
+ {
+ m_last_extrusion_role = path.role();
+ if (m_enable_extrusion_role_markers)
+ {
+ char buf[32];
+ sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(m_last_extrusion_role));
+ gcode += buf;
+ }
+ }
+ }
+
+ // adds analyzer tags and updates analyzer's tracking data
+ if (m_enable_analyzer)
+ {
+ if (path.role() != m_last_analyzer_extrusion_role)
+ {
+ m_last_analyzer_extrusion_role = path.role();
+ char buf[32];
+ sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), int(m_last_analyzer_extrusion_role));
+ gcode += buf;
+ }
+
+ if (m_last_mm3_per_mm != path.mm3_per_mm)
+ {
+ m_last_mm3_per_mm = path.mm3_per_mm;
+
+ char buf[32];
+ sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), m_last_mm3_per_mm);
+ gcode += buf;
+ }
+
+ if (m_last_width != path.width)
+ {
+ m_last_width = path.width;
+
+ char buf[32];
+ sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), m_last_width);
+ gcode += buf;
+ }
+
+ if (m_last_height != path.height)
+ {
+ m_last_height = path.height;
+
+ char buf[32];
+ sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_last_height);
+ gcode += buf;
+ }
+ }
+
+ std::string comment;
+ if (m_enable_cooling_markers) {
+ if (is_bridge(path.role()))
+ gcode += ";_BRIDGE_FAN_START\n";
+ else
+ comment = ";_EXTRUDE_SET_SPEED";
+ if (path.role() == erExternalPerimeter)
+ comment += ";_EXTERNAL_PERIMETER";
+ }
+
+ // F is mm per minute.
+ gcode += m_writer.set_speed(F, "", comment);
+ double path_length = 0.;
+ {
+ std::string comment = m_config.gcode_comments ? description : "";
+ for (const Line &line : path.polyline.lines()) {
+ const double line_length = line.length() * SCALING_FACTOR;
+ path_length += line_length;
+ gcode += m_writer.extrude_to_xy(
+ this->point_to_gcode(line.b),
+ e_per_mm * line_length,
+ comment);
+ }
+ }
+ if (m_enable_cooling_markers)
+ gcode += is_bridge(path.role()) ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n";
+
+ this->set_last_pos(path.last_point());
+ return gcode;
+}
+
+// This method accepts &point in print coordinates.
+std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment)
+{
+ /* Define the travel move as a line between current position and the taget point.
+ This is expressed in print coordinates, so it will need to be translated by
+ this->origin in order to get G-code coordinates. */
+ Polyline travel;
+ travel.append(this->last_pos());
+ travel.append(point);
+
+ // check whether a straight travel move would need retraction
+ bool needs_retraction = this->needs_retraction(travel, role);
+
+ // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
+ // multi-hop travel path inside the configuration space
+ if (needs_retraction
+ && m_config.avoid_crossing_perimeters
+ && ! m_avoid_crossing_perimeters.disable_once) {
+ travel = m_avoid_crossing_perimeters.travel_to(*this, point);
+
+ // check again whether the new travel path still needs a retraction
+ needs_retraction = this->needs_retraction(travel, role);
+ //if (needs_retraction && m_layer_index > 1) exit(0);
+ }
+
+ // Re-allow avoid_crossing_perimeters for the next travel moves
+ m_avoid_crossing_perimeters.disable_once = false;
+ m_avoid_crossing_perimeters.use_external_mp_once = false;
+
+ // generate G-code for the travel move
+ std::string gcode;
+ if (needs_retraction)
+ gcode += this->retract();
+ else
+ // Reset the wipe path when traveling, so one would not wipe along an old path.
+ m_wipe.reset_path();
+
+ // use G1 because we rely on paths being straight (G0 may make round paths)
+ Lines lines = travel.lines();
+ for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line)
+ gcode += m_writer.travel_to_xy(this->point_to_gcode(line->b), comment);
+
+ return gcode;
+}
+
+bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
+{
+ if (travel.length() < scale_(EXTRUDER_CONFIG(retract_before_travel))) {
+ // skip retraction if the move is shorter than the configured threshold
+ return false;
+ }
+
+ if (role == erSupportMaterial) {
+ const SupportLayer* support_layer = dynamic_cast<const SupportLayer*>(m_layer);
+ //FIXME support_layer->support_islands.contains should use some search structure!
+ if (support_layer != NULL && support_layer->support_islands.contains(travel))
+ // skip retraction if this is a travel move inside a support material island
+ //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material
+ // at the end of the extrusion path!
+ return false;
+ }
+
+ if (m_config.only_retract_when_crossing_perimeters && m_layer != nullptr &&
+ m_config.fill_density.value > 0 && m_layer->any_internal_region_slice_contains(travel))
+ // Skip retraction if travel is contained in an internal slice *and*
+ // internal infill is enabled (so that stringing is entirely not visible).
+ //FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first.
+ return false;
+
+ // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
+ return true;
+}
+
+std::string GCode::retract(bool toolchange)
+{
+ std::string gcode;
+
+ if (m_writer.extruder() == nullptr)
+ return gcode;
+
+ // wipe (if it's enabled for this extruder and we have a stored wipe path)
+ if (EXTRUDER_CONFIG(wipe) && m_wipe.has_path()) {
+ gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true);
+ gcode += m_wipe.wipe(*this, toolchange);
+ }
+
+ /* The parent class will decide whether we need to perform an actual retraction
+ (the extruder might be already retracted fully or partially). We call these
+ methods even if we performed wipe, since this will ensure the entire retraction
+ length is honored in case wipe path was too short. */
+ gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract();
+
+ gcode += m_writer.reset_e();
+ if (m_writer.extruder()->retract_length() > 0 || m_config.use_firmware_retraction)
+ gcode += m_writer.lift();
+
+ return gcode;
+}
+
+std::string GCode::set_extruder(unsigned int extruder_id)
+{
+ if (!m_writer.need_toolchange(extruder_id))
+ return "";
+
+ // if we are running a single-extruder setup, just set the extruder and return nothing
+ if (!m_writer.multiple_extruders) {
+ m_placeholder_parser.set("current_extruder", extruder_id);
+ return m_writer.toolchange(extruder_id);
+ }
+
+ // prepend retraction on the current extruder
+ std::string gcode = this->retract(true);
+
+ // Always reset the extrusion path, even if the tool change retract is set to zero.
+ m_wipe.reset_path();
+
+ if (m_writer.extruder() != nullptr) {
+ // Process the custom end_filament_gcode in case of single_extruder_multi_material.
+ unsigned int old_extruder_id = m_writer.extruder()->id();
+ const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id);
+ if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) {
+ gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id);
+ check_add_eol(gcode);
+ }
+ }
+
+ m_placeholder_parser.set("current_extruder", extruder_id);
+
+ if (m_writer.extruder() != nullptr && ! m_config.toolchange_gcode.value.empty()) {
+ // Process the custom toolchange_gcode.
+ DynamicConfig config;
+ config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id()));
+ config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id));
+ gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config);
+ check_add_eol(gcode);
+ }
+
+ // If ooze prevention is enabled, park current extruder in the nearest
+ // standby point and set it to the standby temperature.
+ if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
+ gcode += m_ooze_prevention.pre_toolchange(*this);
+ // Append the toolchange command.
+ gcode += m_writer.toolchange(extruder_id);
+ // Append the filament start G-code for single_extruder_multi_material.
+ const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
+ if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) {
+ // Process the start_filament_gcode for the active filament only.
+ gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id);
+ check_add_eol(gcode);
+ }
+ // Set the new extruder to the operating temperature.
+ if (m_ooze_prevention.enable)
+ gcode += m_ooze_prevention.post_toolchange(*this);
+
+ return gcode;
+}
+
+// convert a model-space scaled point into G-code coordinates
+Vec2d GCode::point_to_gcode(const Point &point) const
+{
+ Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset);
+ return unscale(point) + m_origin - extruder_offset;
+}
+
+// convert a model-space scaled point into G-code coordinates
+Point GCode::gcode_to_point(const Vec2d &point) const
+{
+ Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset);
+ return Point(
+ scale_(point(0) - m_origin(0) + extruder_offset(0)),
+ scale_(point(1) - m_origin(1) + extruder_offset(1)));
+}
+
+// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed
+// during infill/perimeter wiping, or normally (depends on wiping_entities parameter)
+// Returns a reference to member to avoid copying.
+const std::vector<GCode::ObjectByExtruder::Island::Region>& GCode::ObjectByExtruder::Island::by_region_per_copy(unsigned int copy, int extruder, bool wiping_entities)
+{
+ by_region_per_copy_cache.clear();
+
+ for (const auto& reg : by_region) {
+ by_region_per_copy_cache.push_back(ObjectByExtruder::Island::Region()); // creates a region in the newly created Island
+
+ // Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed
+ // References are used so that we don't have to repeat the same code
+ for (int iter = 0; iter < 2; ++iter) {
+ const ExtrusionEntitiesPtr& entities = (iter ? reg.infills.entities : reg.perimeters.entities);
+ ExtrusionEntityCollection& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters);
+ const std::vector<const ExtruderPerCopy*>& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides);
+
+ // Now the most important thing - which extrusion should we print.
+ // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack.
+ int this_extruder_mark = wiping_entities ? extruder : -extruder-1;
+
+ for (unsigned int i=0;i<entities.size();++i)
+ if (overrides[i]->at(copy) == this_extruder_mark) // this copy should be printed with this extruder
+ target_eec.append((*entities[i]));
+ }
+ }
+ return by_region_per_copy_cache;
+}
+
+
+
+// This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter)
+// It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy.
+void GCode::ObjectByExtruder::Island::Region::append(const std::string& type, const ExtrusionEntityCollection* eec, const ExtruderPerCopy* copies_extruder, unsigned int object_copies_num)
+{
+ // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves:
+ ExtrusionEntityCollection* perimeters_or_infills = &infills;
+ std::vector<const ExtruderPerCopy*>* perimeters_or_infills_overrides = &infills_overrides;
+
+ if (type == "perimeters") {
+ perimeters_or_infills = &perimeters;
+ perimeters_or_infills_overrides = &perimeters_overrides;
+ }
+ else
+ if (type != "infills") {
+ throw std::invalid_argument("Unknown parameter!");
+ return;
+ }
+
+
+ // First we append the entities, there are eec->entities.size() of them:
+ perimeters_or_infills->append(eec->entities);
+
+ for (unsigned int i=0;i<eec->entities.size();++i)
+ perimeters_or_infills_overrides->push_back(copies_extruder);
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp
new file mode 100644
index 000000000..45f17a68a
--- /dev/null
+++ b/src/libslic3r/GCode.hpp
@@ -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
diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp
new file mode 100644
index 000000000..51d5b1a06
--- /dev/null
+++ b/src/libslic3r/GCode/Analyzer.cpp
@@ -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
diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp
new file mode 100644
index 000000000..27a49b869
--- /dev/null
+++ b/src/libslic3r/GCode/Analyzer.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp
new file mode 100644
index 000000000..40ccc7b09
--- /dev/null
+++ b/src/libslic3r/GCode/CoolingBuffer.cpp
@@ -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
diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp
new file mode 100644
index 000000000..bf4b082e2
--- /dev/null
+++ b/src/libslic3r/GCode/CoolingBuffer.hpp
@@ -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
diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp
new file mode 100644
index 000000000..c04aeae3c
--- /dev/null
+++ b/src/libslic3r/GCode/PostProcessor.cpp
@@ -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
diff --git a/src/libslic3r/GCode/PostProcessor.hpp b/src/libslic3r/GCode/PostProcessor.hpp
new file mode 100644
index 000000000..ce47374cb
--- /dev/null
+++ b/src/libslic3r/GCode/PostProcessor.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp
new file mode 100644
index 000000000..3b2a58a88
--- /dev/null
+++ b/src/libslic3r/GCode/PressureEqualizer.cpp
@@ -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
diff --git a/src/libslic3r/GCode/PressureEqualizer.hpp b/src/libslic3r/GCode/PressureEqualizer.hpp
new file mode 100644
index 000000000..13cdc9418
--- /dev/null
+++ b/src/libslic3r/GCode/PressureEqualizer.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp
new file mode 100644
index 000000000..9cf9716e0
--- /dev/null
+++ b/src/libslic3r/GCode/PreviewData.cpp
@@ -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
diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp
new file mode 100644
index 000000000..ab74993f5
--- /dev/null
+++ b/src/libslic3r/GCode/PreviewData.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp
new file mode 100644
index 000000000..92a58fdf0
--- /dev/null
+++ b/src/libslic3r/GCode/PrintExtents.cpp
@@ -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;
+}
+
+}
diff --git a/src/libslic3r/GCode/PrintExtents.hpp b/src/libslic3r/GCode/PrintExtents.hpp
new file mode 100644
index 000000000..db507689d
--- /dev/null
+++ b/src/libslic3r/GCode/PrintExtents.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp
new file mode 100644
index 000000000..8e8ae3075
--- /dev/null
+++ b/src/libslic3r/GCode/SpiralVase.cpp
@@ -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;
+}
+
+}
diff --git a/src/libslic3r/GCode/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp
new file mode 100644
index 000000000..60aa668d8
--- /dev/null
+++ b/src/libslic3r/GCode/SpiralVase.hpp
@@ -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
diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp
new file mode 100644
index 000000000..175b69447
--- /dev/null
+++ b/src/libslic3r/GCode/ToolOrdering.cpp
@@ -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
diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp
new file mode 100644
index 000000000..4dcf6516a
--- /dev/null
+++ b/src/libslic3r/GCode/ToolOrdering.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp
new file mode 100644
index 000000000..5cbbc1ca9
--- /dev/null
+++ b/src/libslic3r/GCode/WipeTower.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
new file mode 100644
index 000000000..54bdfdfd6
--- /dev/null
+++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp
@@ -0,0 +1,1258 @@
+/*
+
+TODO LIST
+---------
+
+1. cooling moves - DONE
+2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE
+3. priming extrusions (last wipe must clear the color) - DONE
+4. Peter's wipe tower - layer's are not exactly square
+5. Peter's wipe tower - variable width for higher levels
+6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer)
+7. Peter's wipe tower - enable enhanced first layer adhesion
+
+*/
+
+#include "WipeTowerPrusaMM.hpp"
+
+#include <assert.h>
+#include <math.h>
+#include <iostream>
+#include <vector>
+#include <numeric>
+
+#include "Analyzer.hpp"
+
+#if defined(__linux) || defined(__GNUC__ )
+#include <strings.h>
+#endif /* __linux */
+
+#ifdef _MSC_VER
+#define strcasecmp _stricmp
+#endif
+
+
+namespace Slic3r
+{
+
+namespace PrusaMultiMaterial {
+
+class Writer
+{
+public:
+ Writer(float layer_height, float line_width) :
+ m_current_pos(std::numeric_limits<float>::max(), std::numeric_limits<float>::max()),
+ m_current_z(0.f),
+ m_current_feedrate(0.f),
+ m_layer_height(layer_height),
+ m_extrusion_flow(0.f),
+ m_preview_suppressed(false),
+ m_elapsed_time(0.f),
+ m_default_analyzer_line_width(line_width)
+ {
+ // adds tag for analyzer:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeAnalyzer::Height_Tag.c_str(), m_layer_height); // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming
+ m_gcode += buf;
+ sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower);
+ m_gcode += buf;
+ change_analyzer_line_width(line_width);
+ }
+
+ Writer& change_analyzer_line_width(float line_width) {
+ // adds tag for analyzer:
+ char buf[64];
+ sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width);
+ m_gcode += buf;
+ return *this;
+ }
+
+ Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) {
+ m_wipe_tower_width = width;
+ m_wipe_tower_depth = depth;
+ m_internal_angle = internal_angle;
+ m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle);
+ m_current_pos = pos;
+ return *this;
+ }
+
+ Writer& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; }
+
+ Writer& set_z(float z)
+ { m_current_z = z; return *this; }
+
+ Writer& set_extrusion_flow(float flow)
+ { m_extrusion_flow = flow; return *this; }
+
+ Writer& set_y_shift(float shift) {
+ m_current_pos.y -= shift-m_y_shift;
+ m_y_shift = shift;
+ return (*this);
+ }
+
+ // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various
+ // filament loading and cooling moves from normal extrusion moves. Therefore the writer
+ // is asked to suppres output of some lines, which look like extrusions.
+ Writer& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; }
+ Writer& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; }
+
+ Writer& feedrate(float f)
+ {
+ if (f != m_current_feedrate)
+ m_gcode += "G1" + set_format_F(f) + "\n";
+ return *this;
+ }
+
+ const std::string& gcode() const { return m_gcode; }
+ const std::vector<WipeTower::Extrusion>& extrusions() const { return m_extrusions; }
+ float x() const { return m_current_pos.x; }
+ float y() const { return m_current_pos.y; }
+ const WipeTower::xy& pos() const { return m_current_pos; }
+ const WipeTower::xy start_pos_rotated() const { return m_start_pos; }
+ const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); }
+ float elapsed_time() const { return m_elapsed_time; }
+ float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; }
+
+ // Extrude with an explicitely provided amount of extrusion.
+ Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false)
+ {
+ if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate))
+ // Neither extrusion nor a travel move.
+ return *this;
+
+ float dx = x - m_current_pos.x;
+ float dy = y - m_current_pos.y;
+ double len = sqrt(dx*dx+dy*dy);
+ if (record_length)
+ m_used_filament_length += e;
+
+
+ // Now do the "internal rotation" with respect to the wipe tower center
+ WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are
+ WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go
+
+ if (! m_preview_suppressed && e > 0.f && len > 0.) {
+ // 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 = float(double(e) * /*Filament_Area*/2.40528 / (len * m_layer_height));
+ // Correct for the roundings of a squished extrusion.
+ width += m_layer_height * float(1. - M_PI / 4.);
+ if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos)
+ m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool));
+ m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(rot.x, rot.y), width, m_current_tool));
+ }
+
+ m_gcode += "G1";
+ if (std::abs(rot.x - rotated_current_pos.x) > EPSILON)
+ m_gcode += set_format_X(rot.x);
+
+ if (std::abs(rot.y - rotated_current_pos.y) > EPSILON)
+ m_gcode += set_format_Y(rot.y);
+
+
+ if (e != 0.f)
+ m_gcode += set_format_E(e);
+
+ if (f != 0.f && f != m_current_feedrate)
+ m_gcode += set_format_F(f);
+
+ m_current_pos.x = x;
+ m_current_pos.y = y;
+
+ // Update the elapsed time with a rough estimate.
+ m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f;
+ m_gcode += "\n";
+ return *this;
+ }
+
+ Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false)
+ { return extrude_explicit(dest.x, dest.y, e, f, record_length); }
+
+ // Travel to a new XY position. f=0 means use the current value.
+ Writer& travel(float x, float y, float f = 0.f)
+ { return extrude_explicit(x, y, 0.f, f); }
+
+ Writer& travel(const WipeTower::xy &dest, float f = 0.f)
+ { return extrude_explicit(dest.x, dest.y, 0.f, f); }
+
+ // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow.
+ Writer& extrude(float x, float y, float f = 0.f)
+ {
+ float dx = x - m_current_pos.x;
+ float dy = y - m_current_pos.y;
+ return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true);
+ }
+
+ Writer& extrude(const WipeTower::xy &dest, const float f = 0.f)
+ { return extrude(dest.x, dest.y, f); }
+
+ Writer& rectangle(const WipeTower::xy& ld,float width,float height,const float f = 0.f)
+ {
+ WipeTower::xy corners[4];
+ corners[0] = ld;
+ corners[1] = WipeTower::xy(ld,width,0.f);
+ corners[2] = WipeTower::xy(ld,width,height);
+ corners[3] = WipeTower::xy(ld,0.f,height);
+ int index_of_closest = 0;
+ if (x()-ld.x > ld.x+width-x()) // closer to the right
+ index_of_closest = 1;
+ if (y()-ld.y > ld.y+height-y()) // closer to the top
+ index_of_closest = (index_of_closest==0 ? 3 : 2);
+
+ travel(corners[index_of_closest].x, y()); // travel to the closest corner
+ travel(x(),corners[index_of_closest].y);
+
+ int i = index_of_closest;
+ do {
+ ++i;
+ if (i==4) i=0;
+ extrude(corners[i], f);
+ } while (i != index_of_closest);
+ return (*this);
+ }
+
+ Writer& load(float e, float f = 0.f)
+ {
+ if (e == 0.f && (f == 0.f || f == m_current_feedrate))
+ return *this;
+ m_gcode += "G1";
+ if (e != 0.f)
+ m_gcode += set_format_E(e);
+ if (f != 0.f && f != m_current_feedrate)
+ m_gcode += set_format_F(f);
+ m_gcode += "\n";
+ return *this;
+ }
+
+ // Derectract while moving in the X direction.
+ // If |x| > 0, the feed rate relates to the x distance,
+ // otherwise the feed rate relates to the e distance.
+ Writer& load_move_x(float x, float e, float f = 0.f)
+ { return extrude_explicit(x, m_current_pos.y, e, f); }
+
+ Writer& retract(float e, float f = 0.f)
+ { return load(-e, f); }
+
+// Loads filament while also moving towards given points in x-axis (x feedrate is limited by cutting the distance short if necessary)
+ Writer& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f)
+ {
+ float time = std::abs(loading_dist / loading_speed);
+ float x_speed = std::min(max_x_speed, std::abs(farthest_x - x()) / time);
+ float feedrate = 60.f * std::hypot(x_speed, loading_speed);
+
+ float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_speed * time;
+ return extrude_explicit(end_point, y(), loading_dist, feedrate);
+ }
+
+ // Elevate the extruder head above the current print_z position.
+ Writer& z_hop(float hop, float f = 0.f)
+ {
+ m_gcode += std::string("G1") + set_format_Z(m_current_z + hop);
+ if (f != 0 && f != m_current_feedrate)
+ m_gcode += set_format_F(f);
+ m_gcode += "\n";
+ return *this;
+ }
+
+ // Lower the extruder head back to the current print_z position.
+ Writer& z_hop_reset(float f = 0.f)
+ { return z_hop(0, f); }
+
+ // Move to x1, +y_increment,
+ // extrude quickly amount e to x2 with feed f.
+ Writer& ram(float x1, float x2, float dy, float e0, float e, float f)
+ {
+ extrude_explicit(x1, m_current_pos.y + dy, e0, f, true);
+ extrude_explicit(x2, m_current_pos.y, e, 0.f, true);
+ return *this;
+ }
+
+ // Let the end of the pulled out filament cool down in the cooling tube
+ // by moving up and down and moving the print head left / right
+ // at the current Y position to spread the leaking material.
+ Writer& cool(float x1, float x2, float e1, float e2, float f)
+ {
+ extrude_explicit(x1, m_current_pos.y, e1, f);
+ extrude_explicit(x2, m_current_pos.y, e2);
+ return *this;
+ }
+
+ Writer& set_tool(int tool)
+ {
+ char buf[64];
+ sprintf(buf, "T%d\n", tool);
+ m_gcode += buf;
+ m_current_tool = tool;
+ return *this;
+ }
+
+ // Set extruder temperature, don't wait by default.
+ Writer& set_extruder_temp(int temperature, bool wait = false)
+ {
+ char buf[128];
+ sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature);
+ m_gcode += buf;
+ return *this;
+ };
+
+ // Wait for a period of time (seconds).
+ Writer& wait(float time)
+ {
+ if (time==0)
+ return *this;
+ char buf[128];
+ sprintf(buf, "G4 S%.3f\n", time);
+ m_gcode += buf;
+ return *this;
+ };
+
+ // Set speed factor override percentage.
+ Writer& speed_override(int speed)
+ {
+ char buf[128];
+ sprintf(buf, "M220 S%d\n", speed);
+ m_gcode += buf;
+ return *this;
+ };
+
+ // Set digital trimpot motor
+ Writer& set_extruder_trimpot(int current)
+ {
+ char buf[128];
+ sprintf(buf, "M907 E%d\n", current);
+ m_gcode += buf;
+ return *this;
+ };
+
+ Writer& flush_planner_queue()
+ {
+ m_gcode += "G4 S0\n";
+ return *this;
+ }
+
+ // Reset internal extruder counter.
+ Writer& reset_extruder()
+ {
+ m_gcode += "G92 E0\n";
+ return *this;
+ }
+
+ Writer& comment_with_value(const char *comment, int value)
+ {
+ char strvalue[64];
+ sprintf(strvalue, "%d", value);
+ m_gcode += std::string(";") + comment + strvalue + "\n";
+ return *this;
+ };
+
+
+ Writer& set_fan(unsigned int speed)
+ {
+ if (speed == m_last_fan_speed)
+ return *this;
+
+ if (speed == 0)
+ m_gcode += "M107\n";
+ else
+ {
+ m_gcode += "M106 S";
+ char buf[128];
+ sprintf(buf,"%u\n",(unsigned int)(255.0 * speed / 100.0));
+ m_gcode += buf;
+ }
+ m_last_fan_speed = speed;
+ return *this;
+ }
+
+ Writer& comment_material(WipeTowerPrusaMM::material_type material)
+ {
+ m_gcode += "; material : ";
+ switch (material)
+ {
+ case WipeTowerPrusaMM::PVA:
+ m_gcode += "#8 (PVA)";
+ break;
+ case WipeTowerPrusaMM::SCAFF:
+ m_gcode += "#5 (Scaffold)";
+ break;
+ case WipeTowerPrusaMM::FLEX:
+ m_gcode += "#4 (Flex)";
+ break;
+ default:
+ m_gcode += "DEFAULT (PLA)";
+ break;
+ }
+ m_gcode += "\n";
+ return *this;
+ };
+
+ Writer& append(const char *text) { m_gcode += text; return *this; }
+
+private:
+ WipeTower::xy m_start_pos;
+ WipeTower::xy m_current_pos;
+ float m_current_z;
+ float m_current_feedrate;
+ unsigned int m_current_tool;
+ float m_layer_height;
+ float m_extrusion_flow;
+ bool m_preview_suppressed;
+ std::string m_gcode;
+ std::vector<WipeTower::Extrusion> m_extrusions;
+ float m_elapsed_time;
+ float m_internal_angle = 0.f;
+ float m_y_shift = 0.f;
+ float m_wipe_tower_width = 0.f;
+ float m_wipe_tower_depth = 0.f;
+ float m_last_fan_speed = 0.f;
+ int current_temp = -1;
+ const float m_default_analyzer_line_width;
+ float m_used_filament_length = 0.f;
+
+ std::string set_format_X(float x)
+ {
+ char buf[64];
+ sprintf(buf, " X%.3f", x);
+ m_current_pos.x = x;
+ return buf;
+ }
+
+ std::string set_format_Y(float y) {
+ char buf[64];
+ sprintf(buf, " Y%.3f", y);
+ m_current_pos.y = y;
+ return buf;
+ }
+
+ std::string set_format_Z(float z) {
+ char buf[64];
+ sprintf(buf, " Z%.3f", z);
+ return buf;
+ }
+
+ std::string set_format_E(float e) {
+ char buf[64];
+ sprintf(buf, " E%.4f", e);
+ return buf;
+ }
+
+ std::string set_format_F(float f) {
+ char buf[64];
+ sprintf(buf, " F%d", int(floor(f + 0.5f)));
+ m_current_feedrate = f;
+ return buf;
+ }
+
+ Writer& operator=(const Writer &rhs);
+}; // class Writer
+
+}; // namespace PrusaMultiMaterial
+
+
+
+WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *name)
+{
+ if (strcasecmp(name, "PLA") == 0)
+ return PLA;
+ if (strcasecmp(name, "ABS") == 0)
+ return ABS;
+ if (strcasecmp(name, "PET") == 0)
+ return PET;
+ if (strcasecmp(name, "HIPS") == 0)
+ return HIPS;
+ if (strcasecmp(name, "FLEX") == 0)
+ return FLEX;
+ if (strcasecmp(name, "SCAFF") == 0)
+ return SCAFF;
+ if (strcasecmp(name, "EDGE") == 0)
+ return EDGE;
+ if (strcasecmp(name, "NGEN") == 0)
+ return NGEN;
+ if (strcasecmp(name, "PVA") == 0)
+ return PVA;
+ return INVALID;
+}
+
+
+// Returns gcode to prime the nozzles at the front edge of the print bed.
+WipeTower::ToolChangeResult WipeTowerPrusaMM::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)
+{
+ this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false);
+ this->m_current_tool = tools.front();
+
+ // The Prusa i3 MK2 has a working space of [0, -2.2] to [250, 210].
+ // Due to the XYZ calibration, this working space may shrink slightly from all directions,
+ // therefore the homing position is shifted inside the bed by 0.2 in the firmware to [0.2, -2.0].
+// box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area);
+
+ const float prime_section_width = std::min(240.f / tools.size(), 60.f);
+ box_coordinates cleaning_box(xy(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f);
+
+ PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width);
+ writer.set_extrusion_flow(m_extrusion_flow)
+ .set_z(m_z_pos)
+ .set_initial_tool(m_current_tool)
+ .append(";--------------------\n"
+ "; CP PRIMING START\n")
+ .append(";--------------------\n")
+ .speed_override(100);
+
+ writer.set_initial_position(xy(0.f, 0.f)) // Always move to the starting position
+ .travel(cleaning_box.ld, 7200)
+ .set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming.
+
+ for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) {
+ unsigned int tool = tools[idx_tool];
+ m_left_to_right = true;
+ toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials.
+ toolchange_Load(writer, cleaning_box); // Prime the tool.
+ if (idx_tool + 1 == tools.size()) {
+ // Last tool should not be unloaded, but it should be wiped enough to become of a pure color.
+ toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool]);
+ } else {
+ // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
+ //writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200);
+ toolchange_Wipe(writer, cleaning_box , 20.f);
+ box_coordinates box = cleaning_box;
+ box.translate(0.f, writer.y() - cleaning_box.ld.y + m_perimeter_width);
+ toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature);
+ cleaning_box.translate(prime_section_width, 0.f);
+ writer.travel(cleaning_box.ld, 7200);
+ }
+ ++ m_num_tool_changes;
+ }
+
+ m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear
+ // in the output gcode - we should not remember emitting them (we will output them twice in the worst case)
+
+ // Reset the extruder current to a normal value.
+ writer.set_extruder_trimpot(550)
+ .feedrate(6000)
+ .flush_planner_queue()
+ .reset_extruder()
+ .append("; CP PRIMING END\n"
+ ";------------------\n"
+ "\n\n");
+
+ // so that tool_change() will know to extrude the wipe tower brim:
+ m_print_brim = true;
+
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
+ ToolChangeResult result;
+ result.priming = true;
+ result.print_z = this->m_z_pos;
+ result.layer_height = this->m_layer_height;
+ result.gcode = writer.gcode();
+ result.elapsed_time = writer.elapsed_time();
+ result.extrusions = writer.extrusions();
+ result.start_pos = writer.start_pos_rotated();
+ result.end_pos = writer.pos_rotated();
+ return result;
+}
+
+WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer)
+{
+ if ( m_print_brim )
+ return toolchange_Brim();
+
+ float wipe_area = 0.f;
+ bool last_change_in_layer = false;
+ float wipe_volume = 0.f;
+
+ // Finds this toolchange info
+ if (tool != (unsigned int)(-1))
+ {
+ for (const auto &b : m_layer_info->tool_changes)
+ if ( b.new_tool == tool ) {
+ wipe_volume = b.wipe_volume;
+ if (tool == m_layer_info->tool_changes.back().new_tool)
+ last_change_in_layer = true;
+ wipe_area = b.required_depth * m_layer_info->extra_spacing;
+ break;
+ }
+ }
+ else {
+ // Otherwise we are going to Unload only. And m_layer_info would be invalid.
+ }
+
+ box_coordinates cleaning_box(
+ xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f),
+ m_wipe_tower_width - m_perimeter_width,
+ (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width
+ : m_wipe_tower_depth-m_perimeter_width));
+
+ PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width);
+ writer.set_extrusion_flow(m_extrusion_flow)
+ .set_z(m_z_pos)
+ .set_initial_tool(m_current_tool)
+ .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f))
+ .append(";--------------------\n"
+ "; CP TOOLCHANGE START\n")
+ .comment_with_value(" toolchange #", m_num_tool_changes + 1) // the number is zero-based
+ .comment_material(m_filpar[m_current_tool].material)
+ .append(";--------------------\n")
+ .speed_override(100);
+
+ xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed);
+ writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
+
+ // Increase the extruder driver current to allow fast ramming.
+ writer.set_extruder_trimpot(750);
+
+ // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
+ if (tool != (unsigned int)-1){ // This is not the last change.
+ toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
+ m_is_first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature);
+ toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
+ toolchange_Load(writer, cleaning_box);
+ writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
+ toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area.
+ ++ m_num_tool_changes;
+ } else
+ toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature);
+
+ m_depth_traversed += wipe_area;
+
+ if (last_change_in_layer) {// draw perimeter line
+ writer.set_y_shift(m_y_shift);
+ if (m_peters_wipe_tower)
+ writer.rectangle(WipeTower::xy(0.f, 0.f),m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth);
+ else {
+ writer.rectangle(WipeTower::xy(0.f, 0.f),m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
+ if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle
+ writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y());
+ }
+ }
+ }
+
+ writer.set_extruder_trimpot(550) // Reset the extruder current to a normal value.
+ .feedrate(6000)
+ .flush_planner_queue()
+ .reset_extruder()
+ .append("; CP TOOLCHANGE END\n"
+ ";------------------\n"
+ "\n\n");
+
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
+ ToolChangeResult result;
+ result.priming = false;
+ result.print_z = this->m_z_pos;
+ result.layer_height = this->m_layer_height;
+ result.gcode = writer.gcode();
+ result.elapsed_time = writer.elapsed_time();
+ result.extrusions = writer.extrusions();
+ result.start_pos = writer.start_pos_rotated();
+ result.end_pos = writer.pos_rotated();
+ return result;
+}
+
+WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset)
+{
+ const box_coordinates wipeTower_box(
+ WipeTower::xy(0.f, 0.f),
+ m_wipe_tower_width,
+ m_wipe_tower_depth);
+
+ PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width);
+ writer.set_extrusion_flow(m_extrusion_flow * 1.1f)
+ .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop.
+ .set_initial_tool(m_current_tool)
+ .append(";-------------------------------------\n"
+ "; CP WIPE TOWER FIRST LAYER BRIM START\n");
+
+ xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0);
+ writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
+
+ writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower.
+ 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400);
+
+ // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded.
+ // Extrude 4 rounds of a brim around the future wipe tower.
+ box_coordinates box(wipeTower_box);
+ box.expand(m_perimeter_width);
+ for (size_t i = 0; i < 4; ++ i) {
+ writer.travel (box.ld, 7000)
+ .extrude(box.lu, 2100).extrude(box.ru)
+ .extrude(box.rd ).extrude(box.ld);
+ box.expand(m_perimeter_width);
+ }
+
+ writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner.
+ writer.travel(wipeTower_box.rd) // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower.
+ .travel(wipeTower_box.ld);
+ writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n"
+ ";-----------------------------------\n");
+
+ m_print_brim = false; // Mark the brim as extruded
+
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
+ ToolChangeResult result;
+ result.priming = false;
+ result.print_z = this->m_z_pos;
+ result.layer_height = this->m_layer_height;
+ result.gcode = writer.gcode();
+ result.elapsed_time = writer.elapsed_time();
+ result.extrusions = writer.extrusions();
+ result.start_pos = writer.start_pos_rotated();
+ result.end_pos = writer.pos_rotated();
+ return result;
+}
+
+
+
+// Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool.
+void WipeTowerPrusaMM::toolchange_Unload(
+ PrusaMultiMaterial::Writer &writer,
+ const box_coordinates &cleaning_box,
+ const material_type current_material,
+ const int new_temperature)
+{
+ float xl = cleaning_box.ld.x + 1.f * m_perimeter_width;
+ float xr = cleaning_box.rd.x - 1.f * m_perimeter_width;
+
+ const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness
+ const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm
+
+ writer.append("; CP TOOLCHANGE UNLOAD\n")
+ .change_analyzer_line_width(line_width);
+
+ unsigned i = 0; // iterates through ramming_speed
+ m_left_to_right = true; // current direction of ramming
+ float remaining = xr - xl ; // keeps track of distance to the next turnaround
+ float e_done = 0; // measures E move done from each segment
+
+ writer.travel(xl, cleaning_box.ld.y + m_depth_traversed + y_step/2.f ); // move to starting position
+
+ // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower:
+ if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) {
+
+ // this is y of the center of previous sparse infill border
+ float sparse_beginning_y = 0.f;
+ if (m_current_shape == SHAPE_REVERSED)
+ sparse_beginning_y += ((m_layer_info-1)->depth - (m_layer_info-1)->toolchanges_depth())
+ - ((m_layer_info)->depth-(m_layer_info)->toolchanges_depth()) ;
+ else
+ sparse_beginning_y += (m_layer_info-1)->toolchanges_depth() + m_perimeter_width;
+
+ //debugging:
+ /* float oldx = writer.x();
+ float oldy = writer.y();
+ writer.travel(xr,sparse_beginning_y);
+ writer.extrude(xr+5,writer.y());
+ writer.travel(oldx,oldy);*/
+
+ float sum_of_depths = 0.f;
+ for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange
+ if (tch.old_tool == m_current_tool) {
+ sum_of_depths += tch.ramming_depth;
+ float ramming_end_y = sum_of_depths;
+ ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line
+
+ // debugging:
+ /*float oldx = writer.x();
+ float oldy = writer.y();
+ writer.travel(xl,ramming_end_y);
+ writer.extrude(xl-15,writer.y());
+ writer.travel(oldx,oldy);*/
+
+ if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) ||
+ (m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) )
+ {
+ writer.extrude(xl + tch.first_wipe_line-1.f*m_perimeter_width,writer.y());
+ remaining -= tch.first_wipe_line-1.f*m_perimeter_width;
+ }
+ break;
+ }
+ sum_of_depths += tch.required_depth;
+ }
+ }
+
+ // now the ramming itself:
+ while (i < m_filpar[m_current_tool].ramming_speed.size())
+ {
+ const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height);
+ const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / Filament_Area; // transform volume per sec to E move;
+ const float dist = std::min(x - e_done, remaining); // distance to travel for either the next 0.25s, or to the next turnaround
+ const float actual_time = dist/x * 0.25;
+ writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), std::hypot(dist, e * (dist / x)) / (actual_time / 60.));
+ remaining -= dist;
+
+ if (remaining < WT_EPSILON) { // we reached a turning point
+ writer.travel(writer.x(), writer.y() + y_step, 7200);
+ m_left_to_right = !m_left_to_right;
+ remaining = xr - xl;
+ }
+ e_done += dist; // subtract what was actually done
+ if (e_done > x - WT_EPSILON) { // current segment finished
+ ++i;
+ e_done = 0;
+ }
+ }
+ WipeTower::xy end_of_ramming(writer.x(),writer.y());
+ writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier
+
+ // Retraction:
+ float old_x = writer.x();
+ float turning_point = (!m_left_to_right ? xl : xr );
+ float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming
+ writer.suppress_preview()
+ .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s
+ .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f)
+ .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f)
+ .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f)
+
+ /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed
+ .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed)
+ .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed)
+ .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed)
+ .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/
+ .resume_preview();
+ if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait.
+ // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset)
+ // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off).
+ writer.set_extruder_temp(new_temperature, false);
+ m_old_temperature = new_temperature;
+ }
+
+ // Cooling:
+ const int& number_of_moves = m_filpar[m_current_tool].cooling_moves;
+ if (number_of_moves > 0) {
+ const float& initial_speed = m_filpar[m_current_tool].cooling_initial_speed;
+ const float& final_speed = m_filpar[m_current_tool].cooling_final_speed;
+
+ float speed_inc = (final_speed - initial_speed) / (2.f * number_of_moves - 1.f);
+
+ writer.suppress_preview()
+ .travel(writer.x(), writer.y() + y_step);
+ old_x = writer.x();
+ turning_point = xr-old_x > old_x-xl ? xr : xl;
+ for (int i=0; i<number_of_moves; ++i) {
+ float speed = initial_speed + speed_inc * 2*i;
+ writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed);
+ speed += speed_inc;
+ writer.load_move_x_advanced(old_x, -m_cooling_tube_length, speed);
+ }
+ }
+
+ // let's wait is necessary:
+ writer.wait(m_filpar[m_current_tool].delay);
+ // we should be at the beginning of the cooling tube again - let's move to parking position:
+ writer.retract(-m_cooling_tube_length/2.f+m_parking_pos_retraction-m_cooling_tube_retraction, 2000);
+
+ // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start:
+ // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material
+ writer.travel(end_of_ramming.x, end_of_ramming.y + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f);
+
+ writer.resume_preview()
+ .flush_planner_queue();
+}
+
+// Change the tool, set a speed override for soluble and flex materials.
+void WipeTowerPrusaMM::toolchange_Change(
+ PrusaMultiMaterial::Writer &writer,
+ const unsigned int new_tool,
+ material_type new_material)
+{
+ // Ask the writer about how much of the old filament we consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
+ // Speed override for the material. Go slow for flex and soluble materials.
+ int speed_override;
+ switch (new_material) {
+ case PVA: speed_override = (m_z_pos < 0.80f) ? 60 : 80; break;
+ case SCAFF: speed_override = 35; break;
+ case FLEX: speed_override = 35; break;
+ default: speed_override = 100;
+ }
+ writer.set_tool(new_tool)
+ .speed_override(speed_override)
+ .flush_planner_queue();
+ m_current_tool = new_tool;
+}
+
+
+
+void WipeTowerPrusaMM::toolchange_Load(
+ PrusaMultiMaterial::Writer &writer,
+ const box_coordinates &cleaning_box)
+{
+ float xl = cleaning_box.ld.x + m_perimeter_width * 0.75f;
+ float xr = cleaning_box.rd.x - m_perimeter_width * 0.75f;
+ float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position
+
+ // Load the filament while moving left / right, so the excess material will not create a blob at a single position.
+ float turning_point = ( oldx-xl < xr-oldx ? xr : xl );
+ float edist = m_parking_pos_retraction+m_extra_loading_move;
+
+ writer.append("; CP TOOLCHANGE LOAD\n")
+ .suppress_preview()
+ /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration
+ .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase
+ .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down
+ .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/
+
+ .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start)
+ .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase
+ .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/
+
+ .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate
+ .resume_preview();
+
+ // Reset the extruder current to the normal value.
+ writer.set_extruder_trimpot(550);
+}
+
+
+
+
+// Wipe the newly loaded filament until the end of the assigned wipe area.
+void WipeTowerPrusaMM::toolchange_Wipe(
+ PrusaMultiMaterial::Writer &writer,
+ const box_coordinates &cleaning_box,
+ float wipe_volume)
+{
+ // Increase flow on first layer, slow down print.
+ writer.set_extrusion_flow(m_extrusion_flow * (m_is_first_layer ? 1.18f : 1.f))
+ .append("; CP TOOLCHANGE WIPE\n");
+ float wipe_coeff = m_is_first_layer ? 0.5f : 1.f;
+ const float& xl = cleaning_box.ld.x;
+ const float& xr = cleaning_box.rd.x;
+
+ // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least
+ // the ordered volume, even if it means violating the box. This can later be removed and simply
+ // wipe until the end of the assigned area.
+
+ float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height);
+ float dy = m_extra_spacing*m_perimeter_width;
+ float wipe_speed = 1600.f;
+
+ // if there is less than 2.5*m_perimeter_width to the edge, advance straightaway (there is likely a blob anyway)
+ if ((m_left_to_right ? xr-writer.x() : writer.x()-xl) < 2.5f*m_perimeter_width) {
+ writer.travel((m_left_to_right ? xr-m_perimeter_width : xl+m_perimeter_width),writer.y()+dy);
+ m_left_to_right = !m_left_to_right;
+ }
+
+ // now the wiping itself:
+ for (int i = 0; true; ++i) {
+ if (i!=0) {
+ if (wipe_speed < 1610.f) wipe_speed = 1800.f;
+ else if (wipe_speed < 1810.f) wipe_speed = 2200.f;
+ else if (wipe_speed < 2210.f) wipe_speed = 4200.f;
+ else wipe_speed = std::min(4800.f, wipe_speed + 50.f);
+ }
+
+ float traversed_x = writer.x();
+ if (m_left_to_right)
+ writer.extrude(xr - (i % 4 == 0 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff);
+ else
+ writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff);
+
+ if (writer.y()+EPSILON > cleaning_box.lu.y-0.5f*m_perimeter_width)
+ break; // in case next line would not fit
+
+ traversed_x -= writer.x();
+ x_to_wipe -= fabs(traversed_x);
+ if (x_to_wipe < WT_EPSILON) {
+ writer.travel(m_left_to_right ? xl + 1.5*m_perimeter_width : xr - 1.5*m_perimeter_width, writer.y(), 7200);
+ break;
+ }
+ // stepping to the next line:
+ writer.extrude(writer.x() + (i % 4 == 0 ? -1.f : (i % 4 == 1 ? 1.f : 0.f)) * 1.5*m_perimeter_width, writer.y() + dy);
+ m_left_to_right = !m_left_to_right;
+ }
+
+ // this is neither priming nor not the last toolchange on this layer - we are going back to the model - wipe the nozzle
+ if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) {
+ m_left_to_right = !m_left_to_right;
+ writer.travel(writer.x(), writer.y() - dy)
+ .travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y());
+ }
+
+ writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow.
+}
+
+
+
+
+WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
+{
+ // This should only be called if the layer is not finished yet.
+ // Otherwise the caller would likely travel to the wipe tower in vain.
+ assert(! this->layer_finished());
+
+ PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width);
+ writer.set_extrusion_flow(m_extrusion_flow)
+ .set_z(m_z_pos)
+ .set_initial_tool(m_current_tool)
+ .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f))
+ .append(";--------------------\n"
+ "; CP EMPTY GRID START\n")
+ .comment_with_value(" layer #", m_num_layer_changes + 1);
+
+ // Slow down on the 1st layer.
+ float speed_factor = m_is_first_layer ? 0.5f : 1.f;
+ float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth();
+ box_coordinates fill_box(xy(m_perimeter_width, m_depth_traversed + m_perimeter_width),
+ m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width);
+
+
+ writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel
+ m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
+
+ box_coordinates box = fill_box;
+ for (int i=0;i<2;++i) {
+ if (m_layer_info->toolchanges_depth() < WT_EPSILON) { // there were no toolchanges on this layer
+ if (i==0) box.expand(m_perimeter_width);
+ else box.expand(-m_perimeter_width);
+ }
+ else i=2; // only draw the inner perimeter, outer has been already drawn by tool_change(...)
+ writer.rectangle(box.ld,box.rd.x-box.ld.x,box.ru.y-box.rd.y,2900*speed_factor);
+ }
+
+ // we are in one of the corners, travel to ld along the perimeter:
+ if (writer.x() > fill_box.ld.x+EPSILON) writer.travel(fill_box.ld.x,writer.y());
+ if (writer.y() > fill_box.ld.y+EPSILON) writer.travel(writer.x(),fill_box.ld.y);
+
+ if (m_is_first_layer && m_adhesion) {
+ // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower.
+ box.expand(-m_perimeter_width/2.f);
+ int nsteps = int(floor((box.lu.y - box.ld.y) / (2*m_perimeter_width)));
+ float step = (box.lu.y - box.ld.y) / nsteps;
+ writer.travel(box.ld-xy(m_perimeter_width/2.f,m_perimeter_width/2.f));
+ if (nsteps >= 0)
+ for (int i = 0; i < nsteps; ++i) {
+ writer.extrude(box.ld.x+m_perimeter_width/2.f, writer.y() + 0.5f * step);
+ writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y());
+ writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y() + 0.5f * step);
+ writer.extrude(box.ld.x + m_perimeter_width / 2.f, writer.y());
+ }
+ writer.travel(box.rd.x-m_perimeter_width/2.f,writer.y()); // wipe the nozzle
+ }
+ else { // Extrude a sparse infill to support the material to be printed above.
+ const float dy = (fill_box.lu.y - fill_box.ld.y - m_perimeter_width);
+ const float left = fill_box.lu.x+2*m_perimeter_width;
+ const float right = fill_box.ru.x - 2 * m_perimeter_width;
+ if (dy > m_perimeter_width)
+ {
+ // Extrude an inverse U at the left of the region.
+ writer.travel(fill_box.ld + xy(m_perimeter_width * 2, 0.f))
+ .extrude(fill_box.lu + xy(m_perimeter_width * 2, 0.f), 2900 * speed_factor);
+
+ const int n = 1+(right-left)/(m_bridging);
+ const float dx = (right-left)/n;
+ for (int i=1;i<=n;++i) {
+ float x=left+dx*i;
+ writer.travel(x,writer.y());
+ writer.extrude(x,i%2 ? fill_box.rd.y : fill_box.ru.y);
+ }
+ writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower
+ }
+ else
+ writer.travel(right,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower
+ }
+ writer.append("; CP EMPTY GRID END\n"
+ ";------------------\n\n\n\n\n\n\n");
+
+ m_depth_traversed = m_wipe_tower_depth-m_perimeter_width;
+
+ // Ask our writer about how much material was consumed:
+ m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
+
+ ToolChangeResult result;
+ result.priming = false;
+ result.print_z = this->m_z_pos;
+ result.layer_height = this->m_layer_height;
+ result.gcode = writer.gcode();
+ result.elapsed_time = writer.elapsed_time();
+ result.extrusions = writer.extrusions();
+ result.start_pos = writer.start_pos_rotated();
+ result.end_pos = writer.pos_rotated();
+ return result;
+}
+
+// Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box
+void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume)
+{
+ assert(m_plan.back().z <= z_par + WT_EPSILON ); // refuses to add a layer below the last one
+
+ if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first
+ m_plan.push_back(WipeTowerInfo(z_par, layer_height_par));
+
+ if (brim) { // this toolchange prints brim - we must add it to m_plan, but not to count its depth
+ m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool));
+ return;
+ }
+
+ if (old_tool==new_tool) // new layer without toolchanges - we are done
+ return;
+
+ // this is an actual toolchange - let's calculate depth to reserve on the wipe tower
+ float depth = 0.f;
+ float width = m_wipe_tower_width - 3*m_perimeter_width;
+ float length_to_extrude = volume_to_length(0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f),
+ m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator,
+ layer_height_par);
+ depth = (int(length_to_extrude / width) + 1) * (m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator * m_filpar[old_tool].ramming_step_multiplicator);
+ float ramming_depth = depth;
+ length_to_extrude = width*((length_to_extrude / width)-int(length_to_extrude / width)) - width;
+ float first_wipe_line = -length_to_extrude;
+ length_to_extrude += volume_to_length(wipe_volume, m_perimeter_width, layer_height_par);
+ length_to_extrude = std::max(length_to_extrude,0.f);
+
+ depth += (int(length_to_extrude / width) + 1) * m_perimeter_width;
+ depth *= m_extra_spacing;
+
+ m_plan.back().tool_changes.push_back(WipeTowerInfo::ToolChange(old_tool, new_tool, depth, ramming_depth, first_wipe_line, wipe_volume));
+}
+
+
+
+void WipeTowerPrusaMM::plan_tower()
+{
+ // Calculate m_wipe_tower_depth (maximum depth for all the layers) and propagate depths downwards
+ m_wipe_tower_depth = 0.f;
+ for (auto& layer : m_plan)
+ layer.depth = 0.f;
+
+ for (int layer_index = m_plan.size() - 1; layer_index >= 0; --layer_index)
+ {
+ float this_layer_depth = std::max(m_plan[layer_index].depth, m_plan[layer_index].toolchanges_depth());
+ m_plan[layer_index].depth = this_layer_depth;
+
+ if (this_layer_depth > m_wipe_tower_depth - m_perimeter_width)
+ m_wipe_tower_depth = this_layer_depth + m_perimeter_width;
+
+ for (int i = layer_index - 1; i >= 0 ; i--)
+ {
+ if (m_plan[i].depth - this_layer_depth < 2*m_perimeter_width )
+ m_plan[i].depth = this_layer_depth;
+ }
+ }
+}
+
+void WipeTowerPrusaMM::save_on_last_wipe()
+{
+ for (m_layer_info=m_plan.begin();m_layer_info<m_plan.end();++m_layer_info) {
+ set_layer(m_layer_info->z, m_layer_info->height, 0, m_layer_info->z == m_plan.front().z, m_layer_info->z == m_plan.back().z);
+ if (m_layer_info->tool_changes.size()==0) // we have no way to save anything on an empty layer
+ continue;
+
+ for (const auto &toolchange : m_layer_info->tool_changes)
+ tool_change(toolchange.new_tool, false);
+
+ float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into
+ float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f);
+ float length_to_wipe = volume_to_length(m_layer_info->tool_changes.back().wipe_volume,
+ m_perimeter_width,m_layer_info->height) - m_layer_info->tool_changes.back().first_wipe_line - length_to_save;
+
+ length_to_wipe = std::max(length_to_wipe,0.f);
+ float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ) * m_extra_spacing;
+
+ //depth += (int(length_to_extrude / width) + 1) * m_perimeter_width;
+ m_layer_info->tool_changes.back().required_depth = m_layer_info->tool_changes.back().ramming_depth + depth_to_wipe;
+ }
+}
+
+// Processes vector m_plan and calls respective functions to generate G-code for the wipe tower
+// Resulting ToolChangeResults are appended into vector "result"
+void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result)
+{
+ if (m_plan.empty())
+
+ return;
+
+ m_extra_spacing = 1.f;
+
+ plan_tower();
+ for (int i=0;i<5;++i) {
+ save_on_last_wipe();
+ plan_tower();
+ }
+
+ if (m_peters_wipe_tower)
+ make_wipe_tower_square();
+
+ m_layer_info = m_plan.begin();
+ m_current_tool = (unsigned int)(-2); // we don't know which extruder to start with - we'll set it according to the first toolchange
+ for (auto& used : m_used_filament_length) // reset used filament stats
+ used = 0.f;
+
+ std::vector<WipeTower::ToolChangeResult> layer_result;
+ for (auto layer : m_plan)
+ {
+ set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z);
+ if (m_peters_wipe_tower)
+ m_internal_rotation += 90.f;
+ else
+ m_internal_rotation += 180.f;
+
+ if (!m_peters_wipe_tower && m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
+ m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;
+
+ for (const auto &toolchange : layer.tool_changes) {
+ if (m_current_tool == (unsigned int)(-2))
+ m_current_tool = toolchange.old_tool;
+ layer_result.emplace_back(tool_change(toolchange.new_tool, false));
+ }
+
+ if (! layer_finished()) {
+ auto finish_layer_toolchange = finish_layer();
+ if ( ! layer.tool_changes.empty() ) { // we will merge it to the last toolchange
+ auto& last_toolchange = layer_result.back();
+ if (last_toolchange.end_pos != finish_layer_toolchange.start_pos) {
+ char buf[2048]; // Add a travel move from tc1.end_pos to tc2.start_pos.
+ sprintf(buf, "G1 X%.3f Y%.3f F7200\n", finish_layer_toolchange.start_pos.x, finish_layer_toolchange.start_pos.y);
+ last_toolchange.gcode += buf;
+ }
+ last_toolchange.gcode += finish_layer_toolchange.gcode;
+ last_toolchange.extrusions.insert(last_toolchange.extrusions.end(), finish_layer_toolchange.extrusions.begin(), finish_layer_toolchange.extrusions.end());
+ last_toolchange.end_pos = finish_layer_toolchange.end_pos;
+ }
+ else
+ layer_result.emplace_back(std::move(finish_layer_toolchange));
+ }
+
+ result.emplace_back(std::move(layer_result));
+ m_is_first_layer = false;
+ }
+}
+
+void WipeTowerPrusaMM::make_wipe_tower_square()
+{
+ const float width = m_wipe_tower_width - 3 * m_perimeter_width;
+ const float depth = m_wipe_tower_depth - m_perimeter_width;
+ // area that we actually print into is width*depth
+ float side = sqrt(depth * width);
+
+ m_wipe_tower_width = side + 3 * m_perimeter_width;
+ m_wipe_tower_depth = side + 2 * m_perimeter_width;
+ // For all layers, find how depth changed and update all toolchange depths
+ for (auto &lay : m_plan)
+ {
+ side = sqrt(lay.depth * width);
+ float width_ratio = width / side;
+
+ //lay.extra_spacing = width_ratio;
+ for (auto &tch : lay.tool_changes)
+ tch.required_depth *= width_ratio;
+ }
+
+ plan_tower(); // propagates depth downwards again (width has changed)
+ for (auto& lay : m_plan) // depths set, now the spacing
+ lay.extra_spacing = lay.depth / lay.toolchanges_depth();
+}
+
+}; // namespace Slic3r
diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
new file mode 100644
index 000000000..06625d189
--- /dev/null
+++ b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp
new file mode 100644
index 000000000..51853e9fa
--- /dev/null
+++ b/src/libslic3r/GCodeReader.cpp
@@ -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);
+}
+
+}
diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp
new file mode 100644
index 000000000..84ed89a7c
--- /dev/null
+++ b/src/libslic3r/GCodeReader.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCodeSender.cpp b/src/libslic3r/GCodeSender.cpp
new file mode 100644
index 000000000..0988091ce
--- /dev/null
+++ b/src/libslic3r/GCodeSender.cpp
@@ -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
diff --git a/src/libslic3r/GCodeSender.hpp b/src/libslic3r/GCodeSender.hpp
new file mode 100644
index 000000000..d7663ca55
--- /dev/null
+++ b/src/libslic3r/GCodeSender.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp
new file mode 100644
index 000000000..f97265ee3
--- /dev/null
+++ b/src/libslic3r/GCodeTimeEstimator.cpp
@@ -0,0 +1,1505 @@
+#include "GCodeTimeEstimator.hpp"
+#include "Utils.hpp"
+#include <boost/bind.hpp>
+#include <cmath>
+
+#include <Shiny/Shiny.h>
+
+#include <boost/nowide/fstream.hpp>
+#include <boost/nowide/cstdio.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
+static const float MILLISEC_TO_SEC = 0.001f;
+static const float INCHES_TO_MM = 25.4f;
+
+static const float DEFAULT_FEEDRATE = 1500.0f; // from Prusa Firmware (Marlin_main.cpp)
+static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2
+static const float DEFAULT_RETRACT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2
+static const float DEFAULT_AXIS_MAX_FEEDRATE[] = { 500.0f, 500.0f, 12.0f, 120.0f }; // Prusa Firmware 1_75mm_MK2
+static const float DEFAULT_AXIS_MAX_ACCELERATION[] = { 9000.0f, 9000.0f, 500.0f, 10000.0f }; // Prusa Firmware 1_75mm_MK2
+static const float DEFAULT_AXIS_MAX_JERK[] = { 10.0f, 10.0f, 0.4f, 2.5f }; // from Prusa Firmware (Configuration.h)
+static const float DEFAULT_MINIMUM_FEEDRATE = 0.0f; // from Prusa Firmware (Configuration_adv.h)
+static const float DEFAULT_MINIMUM_TRAVEL_FEEDRATE = 0.0f; // from Prusa Firmware (Configuration_adv.h)
+static const float DEFAULT_EXTRUDE_FACTOR_OVERRIDE_PERCENTAGE = 1.0f; // 100 percent
+
+static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f;
+
+#if ENABLE_MOVE_STATS
+static const std::string MOVE_TYPE_STR[Slic3r::GCodeTimeEstimator::Block::Num_Types] =
+{
+ "Noop",
+ "Retract",
+ "Unretract",
+ "Tool_change",
+ "Move",
+ "Extrude"
+};
+#endif // ENABLE_MOVE_STATS
+
+namespace Slic3r {
+
+ void GCodeTimeEstimator::Feedrates::reset()
+ {
+ feedrate = 0.0f;
+ safe_feedrate = 0.0f;
+ ::memset(axis_feedrate, 0, Num_Axis * sizeof(float));
+ ::memset(abs_axis_feedrate, 0, Num_Axis * sizeof(float));
+ }
+
+ float GCodeTimeEstimator::Block::Trapezoid::acceleration_time(float acceleration) const
+ {
+ return acceleration_time_from_distance(feedrate.entry, accelerate_until, acceleration);
+ }
+
+ float GCodeTimeEstimator::Block::Trapezoid::cruise_time() const
+ {
+ return (feedrate.cruise != 0.0f) ? cruise_distance() / feedrate.cruise : 0.0f;
+ }
+
+ float GCodeTimeEstimator::Block::Trapezoid::deceleration_time(float acceleration) const
+ {
+ return acceleration_time_from_distance(feedrate.cruise, (distance - decelerate_after), -acceleration);
+ }
+
+ float GCodeTimeEstimator::Block::Trapezoid::cruise_distance() const
+ {
+ return decelerate_after - accelerate_until;
+ }
+
+ float GCodeTimeEstimator::Block::Trapezoid::acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration)
+ {
+ return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f;
+ }
+
+ float GCodeTimeEstimator::Block::Trapezoid::speed_from_distance(float initial_feedrate, float distance, float acceleration)
+ {
+ // to avoid invalid negative numbers due to numerical imprecision
+ float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance);
+ return ::sqrt(value);
+ }
+
+ GCodeTimeEstimator::Block::Block()
+ {
+ }
+
+ float GCodeTimeEstimator::Block::move_length() const
+ {
+ float length = ::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
+ return (length > 0.0f) ? length : std::abs(delta_pos[E]);
+ }
+
+ float GCodeTimeEstimator::Block::is_extruder_only_move() const
+ {
+ return (delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f) && (delta_pos[E] != 0.0f);
+ }
+
+ float GCodeTimeEstimator::Block::is_travel_move() const
+ {
+ return delta_pos[E] == 0.0f;
+ }
+
+ float GCodeTimeEstimator::Block::acceleration_time() const
+ {
+ return trapezoid.acceleration_time(acceleration);
+ }
+
+ float GCodeTimeEstimator::Block::cruise_time() const
+ {
+ return trapezoid.cruise_time();
+ }
+
+ float GCodeTimeEstimator::Block::deceleration_time() const
+ {
+ return trapezoid.deceleration_time(acceleration);
+ }
+
+ float GCodeTimeEstimator::Block::cruise_distance() const
+ {
+ return trapezoid.cruise_distance();
+ }
+
+ void GCodeTimeEstimator::Block::calculate_trapezoid()
+ {
+ float distance = move_length();
+
+ trapezoid.distance = distance;
+ trapezoid.feedrate = feedrate;
+
+ float accelerate_distance = estimate_acceleration_distance(feedrate.entry, feedrate.cruise, acceleration);
+ float decelerate_distance = estimate_acceleration_distance(feedrate.cruise, feedrate.exit, -acceleration);
+ float cruise_distance = distance - accelerate_distance - decelerate_distance;
+
+ // Not enough space to reach the nominal feedrate.
+ // This means no cruising, and we'll have to use intersection_distance() to calculate when to abort acceleration
+ // and start braking in order to reach the exit_feedrate exactly at the end of this block.
+ if (cruise_distance < 0.0f)
+ {
+ accelerate_distance = clamp(0.0f, distance, intersection_distance(feedrate.entry, feedrate.exit, acceleration, distance));
+ cruise_distance = 0.0f;
+ trapezoid.feedrate.cruise = Trapezoid::speed_from_distance(feedrate.entry, accelerate_distance, acceleration);
+ }
+
+ trapezoid.accelerate_until = accelerate_distance;
+ trapezoid.decelerate_after = accelerate_distance + cruise_distance;
+ }
+
+ float GCodeTimeEstimator::Block::max_allowable_speed(float acceleration, float target_velocity, float distance)
+ {
+ // to avoid invalid negative numbers due to numerical imprecision
+ float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance);
+ return ::sqrt(value);
+ }
+
+ float GCodeTimeEstimator::Block::estimate_acceleration_distance(float initial_rate, float target_rate, float acceleration)
+ {
+ return (acceleration == 0.0f) ? 0.0f : (sqr(target_rate) - sqr(initial_rate)) / (2.0f * acceleration);
+ }
+
+ float GCodeTimeEstimator::Block::intersection_distance(float initial_rate, float final_rate, float acceleration, float distance)
+ {
+ return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration);
+ }
+
+#if ENABLE_MOVE_STATS
+ GCodeTimeEstimator::MoveStats::MoveStats()
+ : count(0)
+ , time(0.0f)
+ {
+ }
+#endif // ENABLE_MOVE_STATS
+
+ const std::string GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag = "; NORMAL_FIRST_M73_OUTPUT_PLACEHOLDER";
+ const std::string GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag = "; SILENT_FIRST_M73_OUTPUT_PLACEHOLDER";
+
+ GCodeTimeEstimator::GCodeTimeEstimator(EMode mode)
+ : _mode(mode)
+ {
+ reset();
+ set_default();
+ }
+
+ void GCodeTimeEstimator::add_gcode_line(const std::string& gcode_line)
+ {
+ PROFILE_FUNC();
+ _parser.parse_line(gcode_line,
+ [this](GCodeReader &reader, const GCodeReader::GCodeLine &line)
+ { this->_process_gcode_line(reader, line); });
+ }
+
+ void GCodeTimeEstimator::add_gcode_block(const char *ptr)
+ {
+ PROFILE_FUNC();
+ GCodeReader::GCodeLine gline;
+ auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line)
+ { this->_process_gcode_line(reader, line); };
+ for (; *ptr != 0;) {
+ gline.reset();
+ ptr = _parser.parse_line(ptr, gline, action);
+ }
+ }
+
+ void GCodeTimeEstimator::calculate_time(bool start_from_beginning)
+ {
+ PROFILE_FUNC();
+ if (start_from_beginning)
+ {
+ _reset_time();
+ _last_st_synchronized_block_id = -1;
+ }
+ _calculate_time();
+
+#if ENABLE_MOVE_STATS
+ _log_moves_stats();
+#endif // ENABLE_MOVE_STATS
+ }
+
+ void GCodeTimeEstimator::calculate_time_from_text(const std::string& gcode)
+ {
+ reset();
+
+ _parser.parse_buffer(gcode,
+ [this](GCodeReader &reader, const GCodeReader::GCodeLine &line)
+ { this->_process_gcode_line(reader, line); });
+
+ _calculate_time();
+
+#if ENABLE_MOVE_STATS
+ _log_moves_stats();
+#endif // ENABLE_MOVE_STATS
+ }
+
+ void GCodeTimeEstimator::calculate_time_from_file(const std::string& file)
+ {
+ reset();
+
+ _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2));
+ _calculate_time();
+
+#if ENABLE_MOVE_STATS
+ _log_moves_stats();
+#endif // ENABLE_MOVE_STATS
+ }
+
+ void GCodeTimeEstimator::calculate_time_from_lines(const std::vector<std::string>& gcode_lines)
+ {
+ reset();
+
+ auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line)
+ { this->_process_gcode_line(reader, line); };
+ for (const std::string& line : gcode_lines)
+ _parser.parse_line(line, action);
+ _calculate_time();
+
+#if ENABLE_MOVE_STATS
+ _log_moves_stats();
+#endif // ENABLE_MOVE_STATS
+ }
+
+ bool GCodeTimeEstimator::post_process_remaining_times(const std::string& filename, float interval)
+ {
+ boost::nowide::ifstream in(filename);
+ if (!in.good())
+ throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for reading.\n"));
+
+ std::string path_tmp = filename + ".times";
+
+ FILE* out = boost::nowide::fopen(path_tmp.c_str(), "wb");
+ if (out == nullptr)
+ throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for writing.\n"));
+
+ std::string time_mask;
+ switch (_mode)
+ {
+ default:
+ case Normal:
+ {
+ time_mask = "M73 P%s R%s\n";
+ break;
+ }
+ case Silent:
+ {
+ time_mask = "M73 Q%s S%s\n";
+ break;
+ }
+ }
+
+ unsigned int g1_lines_count = 0;
+ float last_recorded_time = 0.0f;
+ std::string gcode_line;
+ // buffer line to export only when greater than 64K to reduce writing calls
+ std::string export_line;
+ char time_line[64];
+ while (std::getline(in, gcode_line))
+ {
+ if (!in.good())
+ {
+ fclose(out);
+ throw std::runtime_error(std::string("Remaining times export failed.\nError while reading from file.\n"));
+ }
+
+ // replaces placeholders for initial line M73 with the real lines
+ if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) ||
+ ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag)))
+ {
+ sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(_time).c_str());
+ gcode_line = time_line;
+ }
+ else
+ gcode_line += "\n";
+
+ // add remaining time lines where needed
+ _parser.parse_line(gcode_line,
+ [this, &g1_lines_count, &last_recorded_time, &time_line, &gcode_line, time_mask, interval](GCodeReader& reader, const GCodeReader::GCodeLine& line)
+ {
+ if (line.cmd_is("G1"))
+ {
+ ++g1_lines_count;
+
+ if (!line.has_e())
+ return;
+
+ G1LineIdToBlockIdMap::const_iterator it = _g1_line_ids.find(g1_lines_count);
+ if ((it != _g1_line_ids.end()) && (it->second < (unsigned int)_blocks.size()))
+ {
+ const Block& block = _blocks[it->second];
+ if (block.elapsed_time != -1.0f)
+ {
+ float block_remaining_time = _time - block.elapsed_time;
+ if (std::abs(last_recorded_time - block_remaining_time) > interval)
+ {
+ sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block.elapsed_time / _time)).c_str(), _get_time_minutes(block_remaining_time).c_str());
+ gcode_line += time_line;
+
+ last_recorded_time = block_remaining_time;
+ }
+ }
+ }
+ }
+ });
+
+ export_line += gcode_line;
+ if (export_line.length() > 65535)
+ {
+ fwrite((const void*)export_line.c_str(), 1, export_line.length(), out);
+ if (ferror(out))
+ {
+ in.close();
+ fclose(out);
+ boost::nowide::remove(path_tmp.c_str());
+ throw std::runtime_error(std::string("Remaining times export failed.\nIs the disk full?\n"));
+ }
+ export_line.clear();
+ }
+ }
+
+ if (export_line.length() > 0)
+ {
+ fwrite((const void*)export_line.c_str(), 1, export_line.length(), out);
+ if (ferror(out))
+ {
+ in.close();
+ fclose(out);
+ boost::nowide::remove(path_tmp.c_str());
+ throw std::runtime_error(std::string("Remaining times export failed.\nIs the disk full?\n"));
+ }
+ }
+
+ fclose(out);
+ in.close();
+
+ if (rename_file(path_tmp, filename) != 0)
+ throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' +
+ "Is " + path_tmp + " locked?" + '\n');
+
+ return true;
+ }
+
+ void GCodeTimeEstimator::set_axis_position(EAxis axis, float position)
+ {
+ _state.axis[axis].position = position;
+ }
+
+ void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec)
+ {
+ _state.axis[axis].max_feedrate = feedrate_mm_sec;
+ }
+
+ void GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration)
+ {
+ _state.axis[axis].max_acceleration = acceleration;
+ }
+
+ void GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk)
+ {
+ _state.axis[axis].max_jerk = jerk;
+ }
+
+ float GCodeTimeEstimator::get_axis_position(EAxis axis) const
+ {
+ return _state.axis[axis].position;
+ }
+
+ float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const
+ {
+ return _state.axis[axis].max_feedrate;
+ }
+
+ float GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const
+ {
+ return _state.axis[axis].max_acceleration;
+ }
+
+ float GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const
+ {
+ return _state.axis[axis].max_jerk;
+ }
+
+ void GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec)
+ {
+ _state.feedrate = feedrate_mm_sec;
+ }
+
+ float GCodeTimeEstimator::get_feedrate() const
+ {
+ return _state.feedrate;
+ }
+
+ void GCodeTimeEstimator::set_acceleration(float acceleration_mm_sec2)
+ {
+ _state.acceleration = (_state.max_acceleration == 0) ?
+ acceleration_mm_sec2 :
+ // Clamp the acceleration with the maximum.
+ std::min(_state.max_acceleration, acceleration_mm_sec2);
+ }
+
+ float GCodeTimeEstimator::get_acceleration() const
+ {
+ return _state.acceleration;
+ }
+
+ void GCodeTimeEstimator::set_max_acceleration(float acceleration_mm_sec2)
+ {
+ _state.max_acceleration = acceleration_mm_sec2;
+ if (acceleration_mm_sec2 > 0)
+ _state.acceleration = acceleration_mm_sec2;
+ }
+
+ float GCodeTimeEstimator::get_max_acceleration() const
+ {
+ return _state.max_acceleration;
+ }
+
+ void GCodeTimeEstimator::set_retract_acceleration(float acceleration_mm_sec2)
+ {
+ _state.retract_acceleration = acceleration_mm_sec2;
+ }
+
+ float GCodeTimeEstimator::get_retract_acceleration() const
+ {
+ return _state.retract_acceleration;
+ }
+
+ void GCodeTimeEstimator::set_minimum_feedrate(float feedrate_mm_sec)
+ {
+ _state.minimum_feedrate = feedrate_mm_sec;
+ }
+
+ float GCodeTimeEstimator::get_minimum_feedrate() const
+ {
+ return _state.minimum_feedrate;
+ }
+
+ void GCodeTimeEstimator::set_minimum_travel_feedrate(float feedrate_mm_sec)
+ {
+ _state.minimum_travel_feedrate = feedrate_mm_sec;
+ }
+
+ float GCodeTimeEstimator::get_minimum_travel_feedrate() const
+ {
+ return _state.minimum_travel_feedrate;
+ }
+
+ void GCodeTimeEstimator::set_filament_load_times(const std::vector<double> &filament_load_times)
+ {
+ _state.filament_load_times.clear();
+ for (double t : filament_load_times)
+ _state.filament_load_times.push_back(t);
+ }
+
+ void GCodeTimeEstimator::set_filament_unload_times(const std::vector<double> &filament_unload_times)
+ {
+ _state.filament_unload_times.clear();
+ for (double t : filament_unload_times)
+ _state.filament_unload_times.push_back(t);
+ }
+
+ float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder)
+ {
+ return
+ (_state.filament_load_times.empty() || id_extruder == _state.extruder_id_unloaded) ?
+ 0 :
+ (_state.filament_load_times.size() <= id_extruder) ?
+ _state.filament_load_times.front() :
+ _state.filament_load_times[id_extruder];
+ }
+
+ float GCodeTimeEstimator::get_filament_unload_time(unsigned int id_extruder)
+ {
+ return
+ (_state.filament_unload_times.empty() || id_extruder == _state.extruder_id_unloaded) ?
+ 0 :
+ (_state.filament_unload_times.size() <= id_extruder) ?
+ _state.filament_unload_times.front() :
+ _state.filament_unload_times[id_extruder];
+ }
+
+ void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage)
+ {
+ _state.extrude_factor_override_percentage = percentage;
+ }
+
+ float GCodeTimeEstimator::get_extrude_factor_override_percentage() const
+ {
+ return _state.extrude_factor_override_percentage;
+ }
+
+ void GCodeTimeEstimator::set_dialect(GCodeFlavor dialect)
+ {
+ _state.dialect = dialect;
+ }
+
+ GCodeFlavor GCodeTimeEstimator::get_dialect() const
+ {
+ PROFILE_FUNC();
+ return _state.dialect;
+ }
+
+ void GCodeTimeEstimator::set_units(GCodeTimeEstimator::EUnits units)
+ {
+ _state.units = units;
+ }
+
+ GCodeTimeEstimator::EUnits GCodeTimeEstimator::get_units() const
+ {
+ return _state.units;
+ }
+
+ void GCodeTimeEstimator::set_global_positioning_type(GCodeTimeEstimator::EPositioningType type)
+ {
+ _state.global_positioning_type = type;
+ }
+
+ GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_global_positioning_type() const
+ {
+ return _state.global_positioning_type;
+ }
+
+ void GCodeTimeEstimator::set_e_local_positioning_type(GCodeTimeEstimator::EPositioningType type)
+ {
+ _state.e_local_positioning_type = type;
+ }
+
+ GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_e_local_positioning_type() const
+ {
+ return _state.e_local_positioning_type;
+ }
+
+ int GCodeTimeEstimator::get_g1_line_id() const
+ {
+ return _state.g1_line_id;
+ }
+
+ void GCodeTimeEstimator::increment_g1_line_id()
+ {
+ ++_state.g1_line_id;
+ }
+
+ void GCodeTimeEstimator::reset_g1_line_id()
+ {
+ _state.g1_line_id = 0;
+ }
+
+ void GCodeTimeEstimator::set_extruder_id(unsigned int id)
+ {
+ _state.extruder_id = id;
+ }
+
+ unsigned int GCodeTimeEstimator::get_extruder_id() const
+ {
+ return _state.extruder_id;
+ }
+
+ void GCodeTimeEstimator::reset_extruder_id()
+ {
+ // Set the initial extruder ID to unknown. For the multi-material setup it means
+ // that all the filaments are parked in the MMU and no filament is loaded yet.
+ _state.extruder_id = _state.extruder_id_unloaded;
+ }
+
+ void GCodeTimeEstimator::add_additional_time(float timeSec)
+ {
+ PROFILE_FUNC();
+ _state.additional_time += timeSec;
+ }
+
+ void GCodeTimeEstimator::set_additional_time(float timeSec)
+ {
+ _state.additional_time = timeSec;
+ }
+
+ float GCodeTimeEstimator::get_additional_time() const
+ {
+ return _state.additional_time;
+ }
+
+ void GCodeTimeEstimator::set_default()
+ {
+ set_units(Millimeters);
+ set_dialect(gcfRepRap);
+ set_global_positioning_type(Absolute);
+ set_e_local_positioning_type(Absolute);
+
+ set_feedrate(DEFAULT_FEEDRATE);
+ // Setting the maximum acceleration to zero means that the there is no limit and the G-code
+ // is allowed to set excessive values.
+ set_max_acceleration(0);
+ set_acceleration(DEFAULT_ACCELERATION);
+ set_retract_acceleration(DEFAULT_RETRACT_ACCELERATION);
+ set_minimum_feedrate(DEFAULT_MINIMUM_FEEDRATE);
+ set_minimum_travel_feedrate(DEFAULT_MINIMUM_TRAVEL_FEEDRATE);
+ set_extrude_factor_override_percentage(DEFAULT_EXTRUDE_FACTOR_OVERRIDE_PERCENTAGE);
+
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ EAxis axis = (EAxis)a;
+ set_axis_max_feedrate(axis, DEFAULT_AXIS_MAX_FEEDRATE[a]);
+ set_axis_max_acceleration(axis, DEFAULT_AXIS_MAX_ACCELERATION[a]);
+ set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]);
+ }
+
+ _state.filament_load_times.clear();
+ _state.filament_unload_times.clear();
+ }
+
+ void GCodeTimeEstimator::reset()
+ {
+ _reset_time();
+#if ENABLE_MOVE_STATS
+ _moves_stats.clear();
+#endif // ENABLE_MOVE_STATS
+ _reset_blocks();
+ _reset();
+ }
+
+ float GCodeTimeEstimator::get_time() const
+ {
+ return _time;
+ }
+
+ std::string GCodeTimeEstimator::get_time_dhms() const
+ {
+ return _get_time_dhms(get_time());
+ }
+
+ std::string GCodeTimeEstimator::get_time_minutes() const
+ {
+ return _get_time_minutes(get_time());
+ }
+
+ void GCodeTimeEstimator::_reset()
+ {
+ _curr.reset();
+ _prev.reset();
+
+ set_axis_position(X, 0.0f);
+ set_axis_position(Y, 0.0f);
+ set_axis_position(Z, 0.0f);
+
+ set_additional_time(0.0f);
+
+ reset_extruder_id();
+ reset_g1_line_id();
+ _g1_line_ids.clear();
+
+ _last_st_synchronized_block_id = -1;
+ }
+
+ void GCodeTimeEstimator::_reset_time()
+ {
+ _time = 0.0f;
+ }
+
+ void GCodeTimeEstimator::_reset_blocks()
+ {
+ _blocks.clear();
+ }
+
+
+ void GCodeTimeEstimator::_calculate_time()
+ {
+ PROFILE_FUNC();
+ _forward_pass();
+ _reverse_pass();
+ _recalculate_trapezoids();
+
+ _time += get_additional_time();
+
+ for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i)
+ {
+ Block& block = _blocks[i];
+
+#if ENABLE_MOVE_STATS
+ float block_time = 0.0f;
+ block_time += block.acceleration_time();
+ block_time += block.cruise_time();
+ block_time += block.deceleration_time();
+ _time += block_time;
+ block.elapsed_time = _time;
+
+ MovesStatsMap::iterator it = _moves_stats.find(block.move_type);
+ if (it == _moves_stats.end())
+ it = _moves_stats.insert(MovesStatsMap::value_type(block.move_type, MoveStats())).first;
+
+ it->second.count += 1;
+ it->second.time += block_time;
+#else
+ _time += block.acceleration_time();
+ _time += block.cruise_time();
+ _time += block.deceleration_time();
+ block.elapsed_time = _time;
+#endif // ENABLE_MOVE_STATS
+ }
+
+ _last_st_synchronized_block_id = _blocks.size() - 1;
+ // The additional time has been consumed (added to the total time), reset it to zero.
+ set_additional_time(0.);
+ }
+
+ void GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ 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 4: // Dwell
+ {
+ _processG4(line);
+ break;
+ }
+ case 20: // Set Units to Inches
+ {
+ _processG20(line);
+ break;
+ }
+ case 21: // Set Units to Millimeters
+ {
+ _processG21(line);
+ break;
+ }
+ case 28: // Move to Origin (Home)
+ {
+ _processG28(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 1: // Sleep or Conditional stop
+ {
+ _processM1(line);
+ break;
+ }
+ case 82: // Set extruder to absolute mode
+ {
+ _processM82(line);
+ break;
+ }
+ case 83: // Set extruder to relative mode
+ {
+ _processM83(line);
+ break;
+ }
+ case 109: // Set Extruder Temperature and Wait
+ {
+ _processM109(line);
+ break;
+ }
+ case 201: // Set max printing acceleration
+ {
+ _processM201(line);
+ break;
+ }
+ case 203: // Set maximum feedrate
+ {
+ _processM203(line);
+ break;
+ }
+ case 204: // Set default acceleration
+ {
+ _processM204(line);
+ break;
+ }
+ case 205: // Advanced settings
+ {
+ _processM205(line);
+ break;
+ }
+ case 221: // Set extrude factor override percentage
+ {
+ _processM221(line);
+ break;
+ }
+ case 566: // Set allowable instantaneous speed change
+ {
+ _processM566(line);
+ break;
+ }
+ case 702: // MK3 MMU2: Process the final filament unload.
+ {
+ _processM702(line);
+ break;
+ }
+ }
+
+ break;
+ }
+ case 'T': // Select Tools
+ {
+ _processT(line);
+ break;
+ }
+ }
+ }
+ }
+
+ // Returns the new absolute position on the given axis in dependence of the given parameters
+ float axis_absolute_position_from_G1_line(GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeTimeEstimator::EUnits units, bool is_relative, float current_absolute_position)
+ {
+ float lengthsScaleFactor = (units == GCodeTimeEstimator::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 GCodeTimeEstimator::_processG1(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ increment_g1_line_id();
+
+ // 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(std::max(line.f() * MMMIN_TO_MMSEC, get_minimum_feedrate()));
+
+ // fills block data
+ Block block;
+
+ // calculates block movement deltas
+ float max_abs_delta = 0.0f;
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ block.delta_pos[a] = new_pos[a] - get_axis_position((EAxis)a);
+ max_abs_delta = std::max(max_abs_delta, std::abs(block.delta_pos[a]));
+ }
+
+ // is it a move ?
+ if (max_abs_delta == 0.0f)
+ return;
+
+ // calculates block feedrate
+ _curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate());
+
+ float distance = block.move_length();
+ float invDistance = 1.0f / distance;
+
+ float min_feedrate_factor = 1.0f;
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ _curr.axis_feedrate[a] = _curr.feedrate * block.delta_pos[a] * invDistance;
+ if (a == E)
+ _curr.axis_feedrate[a] *= get_extrude_factor_override_percentage();
+
+ _curr.abs_axis_feedrate[a] = std::abs(_curr.axis_feedrate[a]);
+ if (_curr.abs_axis_feedrate[a] > 0.0f)
+ min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / _curr.abs_axis_feedrate[a]);
+ }
+
+ block.feedrate.cruise = min_feedrate_factor * _curr.feedrate;
+
+ if (min_feedrate_factor < 1.0f)
+ {
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ _curr.axis_feedrate[a] *= min_feedrate_factor;
+ _curr.abs_axis_feedrate[a] *= min_feedrate_factor;
+ }
+ }
+
+ // calculates block acceleration
+ float acceleration = block.is_extruder_only_move() ? get_retract_acceleration() : get_acceleration();
+
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ float axis_max_acceleration = get_axis_max_acceleration((EAxis)a);
+ if (acceleration * std::abs(block.delta_pos[a]) * invDistance > axis_max_acceleration)
+ acceleration = axis_max_acceleration;
+ }
+
+ block.acceleration = acceleration;
+
+ // calculates block exit feedrate
+ _curr.safe_feedrate = block.feedrate.cruise;
+
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ float axis_max_jerk = get_axis_max_jerk((EAxis)a);
+ if (_curr.abs_axis_feedrate[a] > axis_max_jerk)
+ _curr.safe_feedrate = std::min(_curr.safe_feedrate, axis_max_jerk);
+ }
+
+ block.feedrate.exit = _curr.safe_feedrate;
+
+ // calculates block entry feedrate
+ float vmax_junction = _curr.safe_feedrate;
+ if (!_blocks.empty() && (_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD))
+ {
+ bool prev_speed_larger = _prev.feedrate > block.feedrate.cruise;
+ float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / _prev.feedrate) : (_prev.feedrate / block.feedrate.cruise);
+ // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting.
+ vmax_junction = prev_speed_larger ? block.feedrate.cruise : _prev.feedrate;
+
+ float v_factor = 1.0f;
+ bool limited = false;
+
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop.
+ float v_exit = _prev.axis_feedrate[a];
+ float v_entry = _curr.axis_feedrate[a];
+
+ if (prev_speed_larger)
+ v_exit *= smaller_speed_factor;
+
+ if (limited)
+ {
+ v_exit *= v_factor;
+ v_entry *= v_factor;
+ }
+
+ // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction.
+ float jerk =
+ (v_exit > v_entry) ?
+ (((v_entry > 0.0f) || (v_exit < 0.0f)) ?
+ // coasting
+ (v_exit - v_entry) :
+ // axis reversal
+ std::max(v_exit, -v_entry)) :
+ // v_exit <= v_entry
+ (((v_entry < 0.0f) || (v_exit > 0.0f)) ?
+ // coasting
+ (v_entry - v_exit) :
+ // axis reversal
+ std::max(-v_exit, v_entry));
+
+ float axis_max_jerk = get_axis_max_jerk((EAxis)a);
+ if (jerk > axis_max_jerk)
+ {
+ v_factor *= axis_max_jerk / jerk;
+ limited = true;
+ }
+ }
+
+ if (limited)
+ vmax_junction *= v_factor;
+
+ // Now the transition velocity is known, which maximizes the shared exit / entry velocity while
+ // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints.
+ float vmax_junction_threshold = vmax_junction * 0.99f;
+
+ // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start.
+ if ((_prev.safe_feedrate > vmax_junction_threshold) && (_curr.safe_feedrate > vmax_junction_threshold))
+ vmax_junction = _curr.safe_feedrate;
+ }
+
+ float v_allowable = Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance);
+ block.feedrate.entry = std::min(vmax_junction, v_allowable);
+
+ block.max_entry_speed = vmax_junction;
+ block.flags.nominal_length = (block.feedrate.cruise <= v_allowable);
+ block.flags.recalculate = true;
+ block.safe_feedrate = _curr.safe_feedrate;
+
+ // calculates block trapezoid
+ block.calculate_trapezoid();
+
+ // updates previous
+ _prev = _curr;
+
+ // updates axis positions
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ set_axis_position((EAxis)a, new_pos[a]);
+ }
+
+#if ENABLE_MOVE_STATS
+ // detects block move type
+ block.move_type = Block::Noop;
+
+ if (block.delta_pos[E] < 0.0f)
+ {
+ if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f) || (block.delta_pos[Z] != 0.0f))
+ block.move_type = Block::Move;
+ else
+ block.move_type = Block::Retract;
+ }
+ else if (block.delta_pos[E] > 0.0f)
+ {
+ if ((block.delta_pos[X] == 0.0f) && (block.delta_pos[Y] == 0.0f) && (block.delta_pos[Z] == 0.0f))
+ block.move_type = Block::Unretract;
+ else if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f))
+ block.move_type = Block::Extrude;
+ }
+ else if ((block.delta_pos[X] != 0.0f) || (block.delta_pos[Y] != 0.0f) || (block.delta_pos[Z] != 0.0f))
+ block.move_type = Block::Move;
+#endif // ENABLE_MOVE_STATS
+
+ // adds block to blocks list
+ _blocks.emplace_back(block);
+ _g1_line_ids.insert(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)_blocks.size() - 1));
+ }
+
+ void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ GCodeFlavor dialect = get_dialect();
+
+ float value;
+ if (line.has_value('P', value))
+ add_additional_time(value * MILLISEC_TO_SEC);
+
+ // see: http://reprap.org/wiki/G-code#G4:_Dwell
+ if ((dialect == gcfRepetier) ||
+ (dialect == gcfMarlin) ||
+ (dialect == gcfSmoothie) ||
+ (dialect == gcfRepRap))
+ {
+ if (line.has_value('S', value))
+ add_additional_time(value);
+ }
+
+ _simulate_st_synchronize();
+ }
+
+ void GCodeTimeEstimator::_processG20(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ set_units(Inches);
+ }
+
+ void GCodeTimeEstimator::_processG21(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ set_units(Millimeters);
+ }
+
+ void GCodeTimeEstimator::_processG28(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ // TODO
+ }
+
+ void GCodeTimeEstimator::_processG90(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ set_global_positioning_type(Absolute);
+ }
+
+ void GCodeTimeEstimator::_processG91(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ set_global_positioning_type(Relative);
+ }
+
+ void GCodeTimeEstimator::_processG92(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ 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;
+ }
+ else
+ _simulate_st_synchronize();
+
+ if (!anyFound)
+ {
+ for (unsigned char a = X; a < Num_Axis; ++a)
+ {
+ set_axis_position((EAxis)a, 0.0f);
+ }
+ }
+ }
+
+ void GCodeTimeEstimator::_processM1(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ _simulate_st_synchronize();
+ }
+
+ void GCodeTimeEstimator::_processM82(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ set_e_local_positioning_type(Absolute);
+ }
+
+ void GCodeTimeEstimator::_processM83(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ set_e_local_positioning_type(Relative);
+ }
+
+ void GCodeTimeEstimator::_processM109(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ // TODO
+ }
+
+ void GCodeTimeEstimator::_processM201(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ GCodeFlavor dialect = get_dialect();
+
+ // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration
+ float factor = ((dialect != gcfRepRap) && (get_units() == GCodeTimeEstimator::Inches)) ? INCHES_TO_MM : 1.0f;
+
+ if (line.has_x())
+ set_axis_max_acceleration(X, line.x() * factor);
+
+ if (line.has_y())
+ set_axis_max_acceleration(Y, line.y() * factor);
+
+ if (line.has_z())
+ set_axis_max_acceleration(Z, line.z() * factor);
+
+ if (line.has_e())
+ set_axis_max_acceleration(E, line.e() * factor);
+ }
+
+ void GCodeTimeEstimator::_processM203(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ GCodeFlavor dialect = get_dialect();
+
+ // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate
+ if (dialect == gcfRepetier)
+ return;
+
+ // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate
+ float factor = (dialect == gcfMarlin) ? 1.0f : MMMIN_TO_MMSEC;
+
+ if (line.has_x())
+ set_axis_max_feedrate(X, line.x() * factor);
+
+ if (line.has_y())
+ set_axis_max_feedrate(Y, line.y() * factor);
+
+ if (line.has_z())
+ set_axis_max_feedrate(Z, line.z() * factor);
+
+ if (line.has_e())
+ set_axis_max_feedrate(E, line.e() * factor);
+ }
+
+ void GCodeTimeEstimator::_processM204(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ float value;
+ if (line.has_value('S', value)) {
+ // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware,
+ // and it is also generated by Slic3r to control acceleration per extrusion type
+ // (there is a separate acceleration settings in Slicer for perimeter, first layer etc).
+ set_acceleration(value);
+ if (line.has_value('T', value))
+ set_retract_acceleration(value);
+ } else {
+ // New acceleration format, compatible with the upstream Marlin.
+ if (line.has_value('P', value))
+ set_acceleration(value);
+ if (line.has_value('R', value))
+ set_retract_acceleration(value);
+ if (line.has_value('T', value)) {
+ // Interpret the T value as the travel acceleration in the new Marlin format.
+ //FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value.
+ // set_travel_acceleration(value);
+ }
+ }
+ }
+
+ void GCodeTimeEstimator::_processM205(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ if (line.has_x())
+ {
+ float max_jerk = line.x();
+ set_axis_max_jerk(X, max_jerk);
+ set_axis_max_jerk(Y, max_jerk);
+ }
+
+ if (line.has_y())
+ set_axis_max_jerk(Y, line.y());
+
+ if (line.has_z())
+ set_axis_max_jerk(Z, line.z());
+
+ if (line.has_e())
+ set_axis_max_jerk(E, line.e());
+
+ float value;
+ if (line.has_value('S', value))
+ set_minimum_feedrate(value);
+
+ if (line.has_value('T', value))
+ set_minimum_travel_feedrate(value);
+ }
+
+ void GCodeTimeEstimator::_processM221(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ float value_s;
+ float value_t;
+ if (line.has_value('S', value_s) && !line.has_value('T', value_t))
+ set_extrude_factor_override_percentage(value_s * 0.01f);
+ }
+
+ void GCodeTimeEstimator::_processM566(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ if (line.has_x())
+ set_axis_max_jerk(X, line.x() * MMMIN_TO_MMSEC);
+
+ if (line.has_y())
+ set_axis_max_jerk(Y, line.y() * MMMIN_TO_MMSEC);
+
+ if (line.has_z())
+ set_axis_max_jerk(Z, line.z() * MMMIN_TO_MMSEC);
+
+ if (line.has_e())
+ set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC);
+ }
+
+ void GCodeTimeEstimator::_processM702(const GCodeReader::GCodeLine& line)
+ {
+ PROFILE_FUNC();
+ if (line.has('C')) {
+ // MK3 MMU2 specific M code:
+ // M702 C is expected to be sent by the custom end G-code when finalizing a print.
+ // The MK3 unit shall unload and park the active filament into the MMU2 unit.
+ add_additional_time(get_filament_unload_time(get_extruder_id()));
+ reset_extruder_id();
+ _simulate_st_synchronize();
+ }
+ }
+
+ void GCodeTimeEstimator::_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)
+ {
+ // Specific to the MK3 MMU2: The initial extruder ID is set to -1 indicating
+ // that the filament is parked in the MMU2 unit and there is nothing to be unloaded yet.
+ add_additional_time(get_filament_unload_time(get_extruder_id()));
+ set_extruder_id(id);
+ add_additional_time(get_filament_load_time(get_extruder_id()));
+ _simulate_st_synchronize();
+ }
+ }
+ }
+
+ void GCodeTimeEstimator::_simulate_st_synchronize()
+ {
+ PROFILE_FUNC();
+ _calculate_time();
+ }
+
+ void GCodeTimeEstimator::_forward_pass()
+ {
+ PROFILE_FUNC();
+ if (_blocks.size() > 1)
+ {
+ for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size() - 1; ++i)
+ {
+ _planner_forward_pass_kernel(_blocks[i], _blocks[i + 1]);
+ }
+ }
+ }
+
+ void GCodeTimeEstimator::_reverse_pass()
+ {
+ PROFILE_FUNC();
+ if (_blocks.size() > 1)
+ {
+ for (int i = (int)_blocks.size() - 1; i >= _last_st_synchronized_block_id + 2; --i)
+ {
+ _planner_reverse_pass_kernel(_blocks[i - 1], _blocks[i]);
+ }
+ }
+ }
+
+ void GCodeTimeEstimator::_planner_forward_pass_kernel(Block& prev, Block& curr)
+ {
+ PROFILE_FUNC();
+ // If the previous block is an acceleration block, but it is not long enough to complete the
+ // full speed change within the block, we need to adjust the entry speed accordingly. Entry
+ // speeds have already been reset, maximized, and reverse planned by reverse planner.
+ // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck.
+ if (!prev.flags.nominal_length)
+ {
+ if (prev.feedrate.entry < curr.feedrate.entry)
+ {
+ float entry_speed = std::min(curr.feedrate.entry, Block::max_allowable_speed(-prev.acceleration, prev.feedrate.entry, prev.move_length()));
+
+ // Check for junction speed change
+ if (curr.feedrate.entry != entry_speed)
+ {
+ curr.feedrate.entry = entry_speed;
+ curr.flags.recalculate = true;
+ }
+ }
+ }
+ }
+
+ void GCodeTimeEstimator::_planner_reverse_pass_kernel(Block& curr, Block& next)
+ {
+ // If entry speed is already at the maximum entry speed, no need to recheck. Block is cruising.
+ // If not, block in state of acceleration or deceleration. Reset entry speed to maximum and
+ // check for maximum allowable speed reductions to ensure maximum possible planned speed.
+ if (curr.feedrate.entry != curr.max_entry_speed)
+ {
+ // If nominal length true, max junction speed is guaranteed to be reached. Only compute
+ // for max allowable speed if block is decelerating and nominal length is false.
+ if (!curr.flags.nominal_length && (curr.max_entry_speed > next.feedrate.entry))
+ curr.feedrate.entry = std::min(curr.max_entry_speed, Block::max_allowable_speed(-curr.acceleration, next.feedrate.entry, curr.move_length()));
+ else
+ curr.feedrate.entry = curr.max_entry_speed;
+
+ curr.flags.recalculate = true;
+ }
+ }
+
+ void GCodeTimeEstimator::_recalculate_trapezoids()
+ {
+ PROFILE_FUNC();
+ Block* curr = nullptr;
+ Block* next = nullptr;
+
+ for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i)
+ {
+ Block& b = _blocks[i];
+
+ curr = next;
+ next = &b;
+
+ if (curr != nullptr)
+ {
+ // Recalculate if current block entry or exit junction speed has changed.
+ if (curr->flags.recalculate || next->flags.recalculate)
+ {
+ // NOTE: Entry and exit factors always > 0 by all previous logic operations.
+ Block block = *curr;
+ block.feedrate.exit = next->feedrate.entry;
+ block.calculate_trapezoid();
+ curr->trapezoid = block.trapezoid;
+ curr->flags.recalculate = false; // Reset current only to ensure next trapezoid is computed
+ }
+ }
+ }
+
+ // Last/newest block in buffer. Always recalculated.
+ if (next != nullptr)
+ {
+ Block block = *next;
+ block.feedrate.exit = next->safe_feedrate;
+ block.calculate_trapezoid();
+ next->trapezoid = block.trapezoid;
+ next->flags.recalculate = false;
+ }
+ }
+
+ std::string GCodeTimeEstimator::_get_time_dhms(float time_in_secs)
+ {
+ int days = (int)(time_in_secs / 86400.0f);
+ time_in_secs -= (float)days * 86400.0f;
+ int hours = (int)(time_in_secs / 3600.0f);
+ time_in_secs -= (float)hours * 3600.0f;
+ int minutes = (int)(time_in_secs / 60.0f);
+ time_in_secs -= (float)minutes * 60.0f;
+
+ char buffer[64];
+ if (days > 0)
+ ::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs);
+ else if (hours > 0)
+ ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs);
+ else if (minutes > 0)
+ ::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs);
+ else
+ ::sprintf(buffer, "%ds", (int)time_in_secs);
+
+ return buffer;
+ }
+
+ std::string GCodeTimeEstimator::_get_time_minutes(float time_in_secs)
+ {
+ return std::to_string((int)(::roundf(time_in_secs / 60.0f)));
+ }
+
+#if ENABLE_MOVE_STATS
+ void GCodeTimeEstimator::_log_moves_stats() const
+ {
+ float moves_count = 0.0f;
+ for (const MovesStatsMap::value_type& move : _moves_stats)
+ {
+ moves_count += (float)move.second.count;
+ }
+
+ for (const MovesStatsMap::value_type& move : _moves_stats)
+ {
+ std::cout << MOVE_TYPE_STR[move.first];
+ std::cout << ": count " << move.second.count << " (" << 100.0f * (float)move.second.count / moves_count << "%)";
+ std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / _time << "%)";
+ std::cout << std::endl;
+ }
+ std::cout << std::endl;
+ }
+#endif // ENABLE_MOVE_STATS
+}
diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp
new file mode 100644
index 000000000..e9da584c3
--- /dev/null
+++ b/src/libslic3r/GCodeTimeEstimator.hpp
@@ -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_ */
diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp
new file mode 100644
index 000000000..6ef17f4f4
--- /dev/null
+++ b/src/libslic3r/GCodeWriter.cpp
@@ -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;
+}
+
+}
diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp
new file mode 100644
index 000000000..664b0e3a1
--- /dev/null
+++ b/src/libslic3r/GCodeWriter.hpp
@@ -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_ */
diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp
new file mode 100644
index 000000000..87b4e223d
--- /dev/null
+++ b/src/libslic3r/Geometry.cpp
@@ -0,0 +1,1169 @@
+#include "Geometry.hpp"
+#include "ClipperUtils.hpp"
+#include "ExPolygon.hpp"
+#include "Line.hpp"
+#include "PolylineCollection.hpp"
+#include "clipper.hpp"
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <list>
+#include <map>
+#include <set>
+#include <utility>
+#include <stack>
+#include <vector>
+
+#ifdef SLIC3R_DEBUG
+#include "SVG.hpp"
+#endif
+
+#ifdef SLIC3R_DEBUG
+namespace boost { namespace polygon {
+
+// The following code for the visualization of the boost Voronoi diagram is based on:
+//
+// Boost.Polygon library voronoi_graphic_utils.hpp header file
+// Copyright Andrii Sydorchuk 2010-2012.
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+template <typename CT>
+class voronoi_visual_utils {
+ public:
+ // Discretize parabolic Voronoi edge.
+ // Parabolic Voronoi edges are always formed by one point and one segment
+ // from the initial input set.
+ //
+ // Args:
+ // point: input point.
+ // segment: input segment.
+ // max_dist: maximum discretization distance.
+ // discretization: point discretization of the given Voronoi edge.
+ //
+ // Template arguments:
+ // InCT: coordinate type of the input geometries (usually integer).
+ // Point: point type, should model point concept.
+ // Segment: segment type, should model segment concept.
+ //
+ // Important:
+ // discretization should contain both edge endpoints initially.
+ template <class InCT1, class InCT2,
+ template<class> class Point,
+ template<class> class Segment>
+ static
+ typename enable_if<
+ typename gtl_and<
+ typename gtl_if<
+ typename is_point_concept<
+ typename geometry_concept< Point<InCT1> >::type
+ >::type
+ >::type,
+ typename gtl_if<
+ typename is_segment_concept<
+ typename geometry_concept< Segment<InCT2> >::type
+ >::type
+ >::type
+ >::type,
+ void
+ >::type discretize(
+ const Point<InCT1>& point,
+ const Segment<InCT2>& segment,
+ const CT max_dist,
+ std::vector< Point<CT> >* discretization) {
+ // Apply the linear transformation to move start point of the segment to
+ // the point with coordinates (0, 0) and the direction of the segment to
+ // coincide the positive direction of the x-axis.
+ CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment)));
+ CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment)));
+ CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y;
+
+ // Compute x-coordinates of the endpoints of the edge
+ // in the transformed space.
+ CT projection_start = sqr_segment_length *
+ get_point_projection((*discretization)[0], segment);
+ CT projection_end = sqr_segment_length *
+ get_point_projection((*discretization)[1], segment);
+
+ // Compute parabola parameters in the transformed space.
+ // Parabola has next representation:
+ // f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y).
+ CT point_vec_x = cast(x(point)) - cast(x(low(segment)));
+ CT point_vec_y = cast(y(point)) - cast(y(low(segment)));
+ CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y;
+ CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x;
+
+ // Save the last point.
+ Point<CT> last_point = (*discretization)[1];
+ discretization->pop_back();
+
+ // Use stack to avoid recursion.
+ std::stack<CT> point_stack;
+ point_stack.push(projection_end);
+ CT cur_x = projection_start;
+ CT cur_y = parabola_y(cur_x, rot_x, rot_y);
+
+ // Adjust max_dist parameter in the transformed space.
+ const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length;
+ while (!point_stack.empty()) {
+ CT new_x = point_stack.top();
+ CT new_y = parabola_y(new_x, rot_x, rot_y);
+
+ // Compute coordinates of the point of the parabola that is
+ // furthest from the current line segment.
+ CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x;
+ CT mid_y = parabola_y(mid_x, rot_x, rot_y);
+
+ // Compute maximum distance between the given parabolic arc
+ // and line segment that discretize it.
+ CT dist = (new_y - cur_y) * (mid_x - cur_x) -
+ (new_x - cur_x) * (mid_y - cur_y);
+ dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) +
+ (new_x - cur_x) * (new_x - cur_x));
+ if (dist <= max_dist_transformed) {
+ // Distance between parabola and line segment is less than max_dist.
+ point_stack.pop();
+ CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) /
+ sqr_segment_length + cast(x(low(segment)));
+ CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) /
+ sqr_segment_length + cast(y(low(segment)));
+ discretization->push_back(Point<CT>(inter_x, inter_y));
+ cur_x = new_x;
+ cur_y = new_y;
+ } else {
+ point_stack.push(mid_x);
+ }
+ }
+
+ // Update last point.
+ discretization->back() = last_point;
+ }
+
+ private:
+ // Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b).
+ static CT parabola_y(CT x, CT a, CT b) {
+ return ((x - a) * (x - a) + b * b) / (b + b);
+ }
+
+ // Get normalized length of the distance between:
+ // 1) point projection onto the segment
+ // 2) start point of the segment
+ // Return this length divided by the segment length. This is made to avoid
+ // sqrt computation during transformation from the initial space to the
+ // transformed one and vice versa. The assumption is made that projection of
+ // the point lies between the start-point and endpoint of the segment.
+ template <class InCT,
+ template<class> class Point,
+ template<class> class Segment>
+ static
+ typename enable_if<
+ typename gtl_and<
+ typename gtl_if<
+ typename is_point_concept<
+ typename geometry_concept< Point<int> >::type
+ >::type
+ >::type,
+ typename gtl_if<
+ typename is_segment_concept<
+ typename geometry_concept< Segment<long> >::type
+ >::type
+ >::type
+ >::type,
+ CT
+ >::type get_point_projection(
+ const Point<CT>& point, const Segment<InCT>& segment) {
+ CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment)));
+ CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment)));
+ CT point_vec_x = x(point) - cast(x(low(segment)));
+ CT point_vec_y = y(point) - cast(y(low(segment)));
+ CT sqr_segment_length =
+ segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y;
+ CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y;
+ return vec_dot / sqr_segment_length;
+ }
+
+ template <typename InCT>
+ static CT cast(const InCT& value) {
+ return static_cast<CT>(value);
+ }
+};
+
+} } // namespace boost::polygon
+#endif
+
+using namespace boost::polygon; // provides also high() and low()
+
+namespace Slic3r { namespace Geometry {
+
+static bool sort_points(const Point& a, const Point& b)
+{
+ return (a(0) < b(0)) || (a(0) == b(0) && a(1) < b(1));
+}
+
+static bool sort_pointfs(const Vec3d& a, const Vec3d& b)
+{
+ return (a(0) < b(0)) || (a(0) == b(0) && a(1) < b(1));
+}
+
+// This implementation is based on Andrew's monotone chain 2D convex hull algorithm
+Polygon
+convex_hull(Points points)
+{
+ assert(points.size() >= 3);
+ // sort input points
+ std::sort(points.begin(), points.end(), sort_points);
+
+ int n = points.size(), k = 0;
+ Polygon hull;
+
+ if (n >= 3) {
+ hull.points.resize(2 * n);
+
+ // Build lower hull
+ for (int i = 0; i < n; i++) {
+ while (k >= 2 && points[i].ccw(hull[k-2], hull[k-1]) <= 0) k--;
+ hull[k++] = points[i];
+ }
+
+ // Build upper hull
+ for (int i = n-2, t = k+1; i >= 0; i--) {
+ while (k >= t && points[i].ccw(hull[k-2], hull[k-1]) <= 0) k--;
+ hull[k++] = points[i];
+ }
+
+ hull.points.resize(k);
+
+ assert(hull.points.front() == hull.points.back());
+ hull.points.pop_back();
+ }
+
+ return hull;
+}
+
+Pointf3s
+convex_hull(Pointf3s points)
+{
+ assert(points.size() >= 3);
+ // sort input points
+ std::sort(points.begin(), points.end(), sort_pointfs);
+
+ int n = points.size(), k = 0;
+ Pointf3s hull;
+
+ if (n >= 3)
+ {
+ hull.resize(2 * n);
+
+ // Build lower hull
+ for (int i = 0; i < n; ++i)
+ {
+ Point p = Point::new_scale(points[i](0), points[i](1));
+ while (k >= 2)
+ {
+ Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1));
+ Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1));
+
+ if (p.ccw(k2, k1) <= 0)
+ --k;
+ else
+ break;
+ }
+
+ hull[k++] = points[i];
+ }
+
+ // Build upper hull
+ for (int i = n - 2, t = k + 1; i >= 0; --i)
+ {
+ Point p = Point::new_scale(points[i](0), points[i](1));
+ while (k >= t)
+ {
+ Point k1 = Point::new_scale(hull[k - 1](0), hull[k - 1](1));
+ Point k2 = Point::new_scale(hull[k - 2](0), hull[k - 2](1));
+
+ if (p.ccw(k2, k1) <= 0)
+ --k;
+ else
+ break;
+ }
+
+ hull[k++] = points[i];
+ }
+
+ hull.resize(k);
+
+ assert(hull.front() == hull.back());
+ hull.pop_back();
+ }
+
+ return hull;
+}
+
+Polygon
+convex_hull(const Polygons &polygons)
+{
+ Points pp;
+ for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) {
+ pp.insert(pp.end(), p->points.begin(), p->points.end());
+ }
+ return convex_hull(std::move(pp));
+}
+
+/* accepts an arrayref of points and returns a list of indices
+ according to a nearest-neighbor walk */
+void
+chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near)
+{
+ PointConstPtrs my_points;
+ std::map<const Point*,Points::size_type> indices;
+ my_points.reserve(points.size());
+ for (Points::const_iterator it = points.begin(); it != points.end(); ++it) {
+ my_points.push_back(&*it);
+ indices[&*it] = it - points.begin();
+ }
+
+ retval.reserve(points.size());
+ while (!my_points.empty()) {
+ Points::size_type idx = start_near.nearest_point_index(my_points);
+ start_near = *my_points[idx];
+ retval.push_back(indices[ my_points[idx] ]);
+ my_points.erase(my_points.begin() + idx);
+ }
+}
+
+void
+chained_path(const Points &points, std::vector<Points::size_type> &retval)
+{
+ if (points.empty()) return; // can't call front() on empty vector
+ chained_path(points, retval, points.front());
+}
+
+/* retval and items must be different containers */
+template<class T>
+void
+chained_path_items(Points &points, T &items, T &retval)
+{
+ std::vector<Points::size_type> indices;
+ chained_path(points, indices);
+ for (std::vector<Points::size_type>::const_iterator it = indices.begin(); it != indices.end(); ++it)
+ retval.push_back(items[*it]);
+}
+template void chained_path_items(Points &points, ClipperLib::PolyNodes &items, ClipperLib::PolyNodes &retval);
+
+bool
+directions_parallel(double angle1, double angle2, double max_diff)
+{
+ double diff = fabs(angle1 - angle2);
+ max_diff += EPSILON;
+ return diff < max_diff || fabs(diff - PI) < max_diff;
+}
+
+template<class T>
+bool
+contains(const std::vector<T> &vector, const Point &point)
+{
+ for (typename std::vector<T>::const_iterator it = vector.begin(); it != vector.end(); ++it) {
+ if (it->contains(point)) return true;
+ }
+ return false;
+}
+template bool contains(const ExPolygons &vector, const Point &point);
+
+double
+rad2deg(double angle)
+{
+ return angle / PI * 180.0;
+}
+
+double
+rad2deg_dir(double angle)
+{
+ angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0);
+ if (angle < 0) angle += PI;
+ return rad2deg(angle);
+}
+
+void
+simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
+{
+ Polygons pp;
+ for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) {
+ Polygon p = *it;
+ p.points.push_back(p.points.front());
+ p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
+ p.points.pop_back();
+ pp.push_back(p);
+ }
+ *retval = Slic3r::simplify_polygons(pp);
+}
+
+double
+linint(double value, double oldmin, double oldmax, double newmin, double newmax)
+{
+ return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin;
+}
+
+#if 0
+// Point with a weight, by which the points are sorted.
+// If the points have the same weight, sort them lexicographically by their positions.
+struct ArrangeItem {
+ ArrangeItem() {}
+ Vec2d pos;
+ coordf_t weight;
+ bool operator<(const ArrangeItem &other) const {
+ return weight < other.weight ||
+ ((weight == other.weight) && (pos(1) < other.pos(1) || (pos(1) == other.pos(1) && pos(0) < other.pos(0))));
+ }
+};
+
+Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box)
+{
+ // Use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm.
+ const Vec2d cell_size(part_size(0) + gap, part_size(1) + gap);
+
+ const BoundingBoxf bed_bbox = (bed_bounding_box != NULL && bed_bounding_box->defined) ?
+ *bed_bounding_box :
+ // Bogus bed size, large enough not to trigger the unsufficient bed size error.
+ BoundingBoxf(
+ Vec2d(0, 0),
+ Vec2d(cell_size(0) * num_parts, cell_size(1) * num_parts));
+
+ // This is how many cells we have available into which to put parts.
+ size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0)));
+ size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1)));
+ if (num_parts > cellw * cellh)
+ throw std::invalid_argument(PRINTF_ZU " parts won't fit in your print area!\n", num_parts);
+
+ // Get a bounding box of cellw x cellh cells, centered at the center of the bed.
+ Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap);
+ Vec2d cells_offset(bed_bbox.center() - 0.5 * cells_size);
+ BoundingBoxf cells_bb(cells_offset, cells_size + cells_offset);
+
+ // List of cells, sorted by distance from center.
+ std::vector<ArrangeItem> cellsorder(cellw * cellh, ArrangeItem());
+ for (size_t j = 0; j < cellh; ++ j) {
+ // Center of the jth row on the bed.
+ coordf_t cy = linint(j + 0.5, 0., double(cellh), cells_bb.min(1), cells_bb.max(1));
+ // Offset from the bed center.
+ coordf_t yd = cells_bb.center()(1) - cy;
+ for (size_t i = 0; i < cellw; ++ i) {
+ // Center of the ith column on the bed.
+ coordf_t cx = linint(i + 0.5, 0., double(cellw), cells_bb.min(0), cells_bb.max(0));
+ // Offset from the bed center.
+ coordf_t xd = cells_bb.center()(0) - cx;
+ // Cell with a distance from the bed center.
+ ArrangeItem &ci = cellsorder[j * cellw + i];
+ // Cell center
+ ci.pos(0) = cx;
+ ci.pos(1) = cy;
+ // Square distance of the cell center to the bed center.
+ ci.weight = xd * xd + yd * yd;
+ }
+ }
+ // Sort the cells lexicographically by their distances to the bed center and left to right / bttom to top.
+ std::sort(cellsorder.begin(), cellsorder.end());
+ cellsorder.erase(cellsorder.begin() + num_parts, cellsorder.end());
+
+ // Return the (left,top) corners of the cells.
+ Pointfs positions;
+ positions.reserve(num_parts);
+ for (std::vector<ArrangeItem>::const_iterator it = cellsorder.begin(); it != cellsorder.end(); ++ it)
+ positions.push_back(Vec2d(it->pos(0) - 0.5 * part_size(0), it->pos(1) - 0.5 * part_size(1)));
+ return positions;
+}
+#else
+class ArrangeItem {
+public:
+ Vec2d pos = Vec2d::Zero();
+ size_t index_x, index_y;
+ coordf_t dist;
+};
+class ArrangeItemIndex {
+public:
+ coordf_t index;
+ ArrangeItem item;
+ ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {};
+};
+
+bool
+arrange(size_t total_parts, const Vec2d &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions)
+{
+ positions.clear();
+
+ Vec2d part = part_size;
+
+ // use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm
+ part(0) += dist;
+ part(1) += dist;
+
+ Vec2d area(Vec2d::Zero());
+ if (bb != NULL && bb->defined) {
+ area = bb->size();
+ } else {
+ // bogus area size, large enough not to trigger the error below
+ area(0) = part(0) * total_parts;
+ area(1) = part(1) * total_parts;
+ }
+
+ // this is how many cells we have available into which to put parts
+ size_t cellw = floor((area(0) + dist) / part(0));
+ size_t cellh = floor((area(1) + dist) / part(1));
+ if (total_parts > (cellw * cellh))
+ return false;
+
+ // total space used by cells
+ Vec2d cells(cellw * part(0), cellh * part(1));
+
+ // bounding box of total space used by cells
+ BoundingBoxf cells_bb;
+ cells_bb.merge(Vec2d(0,0)); // min
+ cells_bb.merge(cells); // max
+
+ // center bounding box to area
+ cells_bb.translate(
+ (area(0) - cells(0)) / 2,
+ (area(1) - cells(1)) / 2
+ );
+
+ // list of cells, sorted by distance from center
+ std::vector<ArrangeItemIndex> cellsorder;
+
+ // work out distance for all cells, sort into list
+ for (size_t i = 0; i <= cellw-1; ++i) {
+ for (size_t j = 0; j <= cellh-1; ++j) {
+ coordf_t cx = linint(i + 0.5, 0, cellw, cells_bb.min(0), cells_bb.max(0));
+ coordf_t cy = linint(j + 0.5, 0, cellh, cells_bb.min(1), cells_bb.max(1));
+
+ coordf_t xd = fabs((area(0) / 2) - cx);
+ coordf_t yd = fabs((area(1) / 2) - cy);
+
+ ArrangeItem c;
+ c.pos(0) = cx;
+ c.pos(1) = cy;
+ c.index_x = i;
+ c.index_y = j;
+ c.dist = xd * xd + yd * yd - fabs((cellw / 2) - (i + 0.5));
+
+ // binary insertion sort
+ {
+ coordf_t index = c.dist;
+ size_t low = 0;
+ size_t high = cellsorder.size();
+ while (low < high) {
+ size_t mid = (low + ((high - low) / 2)) | 0;
+ coordf_t midval = cellsorder[mid].index;
+
+ if (midval < index) {
+ low = mid + 1;
+ } else if (midval > index) {
+ high = mid;
+ } else {
+ cellsorder.insert(cellsorder.begin() + mid, ArrangeItemIndex(index, c));
+ goto ENDSORT;
+ }
+ }
+ cellsorder.insert(cellsorder.begin() + low, ArrangeItemIndex(index, c));
+ }
+ ENDSORT: ;
+ }
+ }
+
+ // the extents of cells actually used by objects
+ coordf_t lx = 0;
+ coordf_t ty = 0;
+ coordf_t rx = 0;
+ coordf_t by = 0;
+
+ // now find cells actually used by objects, map out the extents so we can position correctly
+ for (size_t i = 1; i <= total_parts; ++i) {
+ ArrangeItemIndex c = cellsorder[i - 1];
+ coordf_t cx = c.item.index_x;
+ coordf_t cy = c.item.index_y;
+ if (i == 1) {
+ lx = rx = cx;
+ ty = by = cy;
+ } else {
+ if (cx > rx) rx = cx;
+ if (cx < lx) lx = cx;
+ if (cy > by) by = cy;
+ if (cy < ty) ty = cy;
+ }
+ }
+ // now we actually place objects into cells, positioned such that the left and bottom borders are at 0
+ for (size_t i = 1; i <= total_parts; ++i) {
+ ArrangeItemIndex c = cellsorder.front();
+ cellsorder.erase(cellsorder.begin());
+ coordf_t cx = c.item.index_x - lx;
+ coordf_t cy = c.item.index_y - ty;
+
+ positions.push_back(Vec2d(cx * part(0), cy * part(1)));
+ }
+
+ if (bb != NULL && bb->defined) {
+ for (Pointfs::iterator p = positions.begin(); p != positions.end(); ++p) {
+ p->x() += bb->min(0);
+ p->y() += bb->min(1);
+ }
+ }
+
+ return true;
+}
+#endif
+
+#ifdef SLIC3R_DEBUG
+// The following code for the visualization of the boost Voronoi diagram is based on:
+//
+// Boost.Polygon library voronoi_visualizer.cpp file
+// Copyright Andrii Sydorchuk 2010-2012.
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+namespace Voronoi { namespace Internal {
+
+ typedef double coordinate_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;
+// typedef voronoi_builder<int> VB;
+ typedef boost::polygon::voronoi_diagram<coordinate_type> VD;
+ typedef VD::cell_type cell_type;
+ typedef VD::cell_type::source_index_type source_index_type;
+ typedef VD::cell_type::source_category_type source_category_type;
+ typedef VD::edge_type edge_type;
+ typedef VD::cell_container_type cell_container_type;
+ typedef VD::cell_container_type vertex_container_type;
+ typedef VD::edge_container_type edge_container_type;
+ typedef VD::const_cell_iterator const_cell_iterator;
+ typedef VD::const_vertex_iterator const_vertex_iterator;
+ typedef VD::const_edge_iterator const_edge_iterator;
+
+ static const std::size_t EXTERNAL_COLOR = 1;
+
+ inline void color_exterior(const VD::edge_type* edge)
+ {
+ if (edge->color() == EXTERNAL_COLOR)
+ return;
+ edge->color(EXTERNAL_COLOR);
+ edge->twin()->color(EXTERNAL_COLOR);
+ const VD::vertex_type* v = edge->vertex1();
+ if (v == NULL || !edge->is_primary())
+ return;
+ v->color(EXTERNAL_COLOR);
+ const VD::edge_type* e = v->incident_edge();
+ do {
+ color_exterior(e);
+ e = e->rot_next();
+ } while (e != v->incident_edge());
+ }
+
+ inline point_type retrieve_point(const std::vector<segment_type> &segments, const cell_type& cell)
+ {
+ assert(cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == SOURCE_CATEGORY_SEGMENT_END_POINT);
+ return (cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) ? low(segments[cell.source_index()]) : high(segments[cell.source_index()]);
+ }
+
+ inline void clip_infinite_edge(const std::vector<segment_type> &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector<point_type>* clipped_edge)
+ {
+ const cell_type& cell1 = *edge.cell();
+ const cell_type& cell2 = *edge.twin()->cell();
+ point_type origin, direction;
+ // Infinite edges could not be created by two segment sites.
+ if (cell1.contains_point() && cell2.contains_point()) {
+ point_type p1 = retrieve_point(segments, cell1);
+ point_type p2 = retrieve_point(segments, cell2);
+ origin.x((p1(0) + p2(0)) * 0.5);
+ origin.y((p1(1) + p2(1)) * 0.5);
+ direction.x(p1(1) - p2(1));
+ direction.y(p2(0) - p1(0));
+ } else {
+ origin = cell1.contains_segment() ? retrieve_point(segments, cell2) : retrieve_point(segments, cell1);
+ segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()];
+ coordinate_type dx = high(segment)(0) - low(segment)(0);
+ coordinate_type dy = high(segment)(1) - low(segment)(1);
+ if ((low(segment) == origin) ^ cell1.contains_point()) {
+ direction.x(dy);
+ direction.y(-dx);
+ } else {
+ direction.x(-dy);
+ direction.y(dx);
+ }
+ }
+ coordinate_type koef = bbox_max_size / (std::max)(fabs(direction(0)), fabs(direction(1)));
+ if (edge.vertex0() == NULL) {
+ clipped_edge->push_back(point_type(
+ origin(0) - direction(0) * koef,
+ origin(1) - direction(1) * koef));
+ } else {
+ clipped_edge->push_back(
+ point_type(edge.vertex0()->x(), edge.vertex0()->y()));
+ }
+ if (edge.vertex1() == NULL) {
+ clipped_edge->push_back(point_type(
+ origin(0) + direction(0) * koef,
+ origin(1) + direction(1) * koef));
+ } else {
+ clipped_edge->push_back(
+ point_type(edge.vertex1()->x(), edge.vertex1()->y()));
+ }
+ }
+
+ inline void sample_curved_edge(const std::vector<segment_type> &segments, const edge_type& edge, std::vector<point_type> &sampled_edge, coordinate_type max_dist)
+ {
+ point_type point = edge.cell()->contains_point() ?
+ retrieve_point(segments, *edge.cell()) :
+ retrieve_point(segments, *edge.twin()->cell());
+ segment_type segment = edge.cell()->contains_point() ?
+ segments[edge.twin()->cell()->source_index()] :
+ segments[edge.cell()->source_index()];
+ ::boost::polygon::voronoi_visual_utils<coordinate_type>::discretize(point, segment, max_dist, &sampled_edge);
+ }
+
+} /* namespace Internal */ } // namespace Voronoi
+
+static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_diagram<double> &vd, const ThickPolylines *polylines, const char *path)
+{
+ const double scale = 0.2;
+ const std::string inputSegmentPointColor = "lightseagreen";
+ const coord_t inputSegmentPointRadius = coord_t(0.09 * scale / SCALING_FACTOR);
+ const std::string inputSegmentColor = "lightseagreen";
+ const coord_t inputSegmentLineWidth = coord_t(0.03 * scale / SCALING_FACTOR);
+
+ const std::string voronoiPointColor = "black";
+ const coord_t voronoiPointRadius = coord_t(0.06 * scale / SCALING_FACTOR);
+ const std::string voronoiLineColorPrimary = "black";
+ const std::string voronoiLineColorSecondary = "green";
+ const std::string voronoiArcColor = "red";
+ const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR);
+
+ const bool internalEdgesOnly = false;
+ const bool primaryEdgesOnly = false;
+
+ BoundingBox bbox = BoundingBox(lines);
+ bbox.min(0) -= coord_t(1. / SCALING_FACTOR);
+ bbox.min(1) -= coord_t(1. / SCALING_FACTOR);
+ bbox.max(0) += coord_t(1. / SCALING_FACTOR);
+ bbox.max(1) += coord_t(1. / SCALING_FACTOR);
+
+ ::Slic3r::SVG svg(path, bbox);
+
+ if (polylines != NULL)
+ svg.draw(*polylines, "lime", "lime", voronoiLineWidth);
+
+// bbox.scale(1.2);
+ // For clipping of half-lines to some reasonable value.
+ // The line will then be clipped by the SVG viewer anyway.
+ const double bbox_dim_max = double(bbox.max(0) - bbox.min(0)) + double(bbox.max(1) - bbox.min(1));
+ // For the discretization of the Voronoi parabolic segments.
+ const double discretization_step = 0.0005 * bbox_dim_max;
+
+ // Make a copy of the input segments with the double type.
+ std::vector<Voronoi::Internal::segment_type> segments;
+ for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it)
+ segments.push_back(Voronoi::Internal::segment_type(
+ Voronoi::Internal::point_type(double(it->a(0)), double(it->a(1))),
+ Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1)))));
+
+ // Color exterior edges.
+ for (voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it)
+ if (!it->is_finite())
+ Voronoi::Internal::color_exterior(&(*it));
+
+ // Draw the end points of the input polygon.
+ for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {
+ svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius);
+ svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius);
+ }
+ // Draw the input polygon.
+ for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it)
+ svg.draw(Line(Point(coord_t(it->a(0)), coord_t(it->a(1))), Point(coord_t(it->b(0)), coord_t(it->b(1)))), inputSegmentColor, inputSegmentLineWidth);
+
+#if 1
+ // Draw voronoi vertices.
+ for (voronoi_diagram<double>::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it)
+ if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR)
+ svg.draw(Point(coord_t((*it)(0)), coord_t((*it)(1))), voronoiPointColor, voronoiPointRadius);
+
+ for (voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) {
+ if (primaryEdgesOnly && !it->is_primary())
+ continue;
+ if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR))
+ continue;
+ std::vector<Voronoi::Internal::point_type> samples;
+ std::string color = voronoiLineColorPrimary;
+ if (!it->is_finite()) {
+ Voronoi::Internal::clip_infinite_edge(segments, *it, bbox_dim_max, &samples);
+ if (! it->is_primary())
+ color = voronoiLineColorSecondary;
+ } else {
+ // Store both points of the segment into samples. sample_curved_edge will split the initial line
+ // until the discretization_step is reached.
+ samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y()));
+ samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y()));
+ if (it->is_curved()) {
+ Voronoi::Internal::sample_curved_edge(segments, *it, samples, discretization_step);
+ color = voronoiArcColor;
+ } else if (! it->is_primary())
+ color = voronoiLineColorSecondary;
+ }
+ for (std::size_t i = 0; i + 1 < samples.size(); ++i)
+ svg.draw(Line(Point(coord_t(samples[i](0)), coord_t(samples[i](1))), Point(coord_t(samples[i+1](0)), coord_t(samples[i+1](1)))), color, voronoiLineWidth);
+ }
+#endif
+
+ if (polylines != NULL)
+ svg.draw(*polylines, "blue", voronoiLineWidth);
+
+ svg.Close();
+}
+#endif /* SLIC3R_DEBUG */
+
+// Euclidian distance of two boost::polygon points.
+template<typename T>
+T dist(const boost::polygon::point_data<T> &p1,const boost::polygon::point_data<T> &p2)
+{
+ T dx = p2(0) - p1(0);
+ T dy = p2(1) - p1(1);
+ return sqrt(dx*dx+dy*dy);
+}
+
+// Find a foot point of "px" on a segment "seg".
+template<typename segment_type, typename point_type>
+inline point_type project_point_to_segment(segment_type &seg, point_type &px)
+{
+ typedef typename point_type::coordinate_type T;
+ const point_type &p0 = low(seg);
+ const point_type &p1 = high(seg);
+ const point_type dir(p1(0)-p0(0), p1(1)-p0(1));
+ const point_type dproj(px(0)-p0(0), px(1)-p0(1));
+ const T t = (dir(0)*dproj(0) + dir(1)*dproj(1)) / (dir(0)*dir(0) + dir(1)*dir(1));
+ assert(t >= T(-1e-6) && t <= T(1. + 1e-6));
+ return point_type(p0(0) + t*dir(0), p0(1) + t*dir(1));
+}
+
+template<typename VD, typename SEGMENTS>
+inline const typename VD::point_type retrieve_cell_point(const typename VD::cell_type& cell, const SEGMENTS &segments)
+{
+ assert(cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == SOURCE_CATEGORY_SEGMENT_END_POINT);
+ return (cell.source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) ? low(segments[cell.source_index()]) : high(segments[cell.source_index()]);
+}
+
+template<typename VD, typename SEGMENTS>
+inline std::pair<typename VD::coord_type, typename VD::coord_type>
+measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
+{
+ typedef typename VD::coord_type T;
+ const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y());
+ const typename VD::point_type pb(edge.vertex1()->x(), edge.vertex1()->y());
+ const typename VD::cell_type &cell1 = *edge.cell();
+ const typename VD::cell_type &cell2 = *edge.twin()->cell();
+ if (cell1.contains_segment()) {
+ if (cell2.contains_segment()) {
+ // Both cells contain a linear segment, the left / right cells are symmetric.
+ // Project pa, pb to the left segment.
+ const typename VD::segment_type segment1 = segments[cell1.source_index()];
+ const typename VD::point_type p1a = project_point_to_segment(segment1, pa);
+ const typename VD::point_type p1b = project_point_to_segment(segment1, pb);
+ return std::pair<T, T>(T(2.)*dist(pa, p1a), T(2.)*dist(pb, p1b));
+ } else {
+ // 1st cell contains a linear segment, 2nd cell contains a point.
+ // The medial axis between the cells is a parabolic arc.
+ // Project pa, pb to the left segment.
+ const typename VD::point_type p2 = retrieve_cell_point<VD>(cell2, segments);
+ return std::pair<T, T>(T(2.)*dist(pa, p2), T(2.)*dist(pb, p2));
+ }
+ } else if (cell2.contains_segment()) {
+ // 1st cell contains a point, 2nd cell contains a linear segment.
+ // The medial axis between the cells is a parabolic arc.
+ const typename VD::point_type p1 = retrieve_cell_point<VD>(cell1, segments);
+ return std::pair<T, T>(T(2.)*dist(pa, p1), T(2.)*dist(pb, p1));
+ } else {
+ // Both cells contain a point. The left / right regions are triangular and symmetric.
+ const typename VD::point_type p1 = retrieve_cell_point<VD>(cell1, segments);
+ return std::pair<T, T>(T(2.)*dist(pa, p1), T(2.)*dist(pb, p1));
+ }
+}
+
+// Converts the Line instances of Lines vector to VD::segment_type.
+template<typename VD>
+class Lines2VDSegments
+{
+public:
+ Lines2VDSegments(const Lines &alines) : lines(alines) {}
+ typename VD::segment_type operator[](size_t idx) const {
+ return typename VD::segment_type(
+ typename VD::point_type(typename VD::coord_type(lines[idx].a(0)), typename VD::coord_type(lines[idx].a(1))),
+ typename VD::point_type(typename VD::coord_type(lines[idx].b(0)), typename VD::coord_type(lines[idx].b(1))));
+ }
+private:
+ const Lines &lines;
+};
+
+void
+MedialAxis::build(ThickPolylines* polylines)
+{
+ construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd);
+
+ /*
+ // DEBUG: dump all Voronoi edges
+ {
+ for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
+ if (edge->is_infinite()) continue;
+
+ ThickPolyline polyline;
+ polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
+ polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
+ polylines->push_back(polyline);
+ }
+ return;
+ }
+ */
+
+ typedef const VD::vertex_type vert_t;
+ typedef const VD::edge_type edge_t;
+
+ // collect valid edges (i.e. prune those not belonging to MAT)
+ // note: this keeps twins, so it inserts twice the number of the valid edges
+ this->valid_edges.clear();
+ {
+ std::set<const VD::edge_type*> seen_edges;
+ for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
+ // if we only process segments representing closed loops, none if the
+ // infinite edges (if any) would be part of our MAT anyway
+ if (edge->is_secondary() || edge->is_infinite()) continue;
+
+ // don't re-validate twins
+ if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed?
+ seen_edges.insert(&*edge);
+ seen_edges.insert(edge->twin());
+
+ if (!this->validate_edge(&*edge)) continue;
+ this->valid_edges.insert(&*edge);
+ this->valid_edges.insert(edge->twin());
+ }
+ }
+ this->edges = this->valid_edges;
+
+ // iterate through the valid edges to build polylines
+ while (!this->edges.empty()) {
+ const edge_t* edge = *this->edges.begin();
+
+ // start a polyline
+ ThickPolyline polyline;
+ polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
+ polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
+ polyline.width.push_back(this->thickness[edge].first);
+ polyline.width.push_back(this->thickness[edge].second);
+
+ // remove this edge and its twin from the available edges
+ (void)this->edges.erase(edge);
+ (void)this->edges.erase(edge->twin());
+
+ // get next points
+ this->process_edge_neighbors(edge, &polyline);
+
+ // get previous points
+ {
+ ThickPolyline rpolyline;
+ this->process_edge_neighbors(edge->twin(), &rpolyline);
+ polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend());
+ polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend());
+ polyline.endpoints.first = rpolyline.endpoints.second;
+ }
+
+ assert(polyline.width.size() == polyline.points.size()*2 - 2);
+
+ // prevent loop endpoints from being extended
+ if (polyline.first_point() == polyline.last_point()) {
+ polyline.endpoints.first = false;
+ polyline.endpoints.second = false;
+ }
+
+ // append polyline to result
+ polylines->push_back(polyline);
+ }
+
+ #ifdef SLIC3R_DEBUG
+ {
+ static int iRun = 0;
+ dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str());
+ printf("Thick lines: ");
+ for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) {
+ ThickLines lines = it->thicklines();
+ for (ThickLines::const_iterator it2 = lines.begin(); it2 != lines.end(); ++ it2) {
+ printf("%f,%f ", it2->a_width, it2->b_width);
+ }
+ }
+ printf("\n");
+ }
+ #endif /* SLIC3R_DEBUG */
+}
+
+void
+MedialAxis::build(Polylines* polylines)
+{
+ ThickPolylines tp;
+ this->build(&tp);
+ polylines->insert(polylines->end(), tp.begin(), tp.end());
+}
+
+void
+MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline)
+{
+ while (true) {
+ // Since rot_next() works on the edge starting point but we want
+ // to find neighbors on the ending point, we just swap edge with
+ // its twin.
+ const VD::edge_type* twin = edge->twin();
+
+ // count neighbors for this edge
+ std::vector<const VD::edge_type*> neighbors;
+ for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin;
+ neighbor = neighbor->rot_next()) {
+ if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor);
+ }
+
+ // if we have a single neighbor then we can continue recursively
+ if (neighbors.size() == 1) {
+ const VD::edge_type* neighbor = neighbors.front();
+
+ // break if this is a closed loop
+ if (this->edges.count(neighbor) == 0) return;
+
+ Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y());
+ polyline->points.push_back(new_point);
+ polyline->width.push_back(this->thickness[neighbor].first);
+ polyline->width.push_back(this->thickness[neighbor].second);
+ (void)this->edges.erase(neighbor);
+ (void)this->edges.erase(neighbor->twin());
+ edge = neighbor;
+ } else if (neighbors.size() == 0) {
+ polyline->endpoints.second = true;
+ return;
+ } else {
+ // T-shaped or star-shaped joint
+ return;
+ }
+ }
+}
+
+bool
+MedialAxis::validate_edge(const VD::edge_type* edge)
+{
+ // prevent overflows and detect almost-infinite edges
+ if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
+ std::abs(edge->vertex0()->y()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
+ std::abs(edge->vertex1()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
+ std::abs(edge->vertex1()->y()) > double(CLIPPER_MAX_COORD_UNSCALED))
+ return false;
+
+ // construct the line representing this edge of the Voronoi diagram
+ const Line line(
+ Point( edge->vertex0()->x(), edge->vertex0()->y() ),
+ Point( edge->vertex1()->x(), edge->vertex1()->y() )
+ );
+
+ // discard edge if it lies outside the supplied shape
+ // this could maybe be optimized (checking inclusion of the endpoints
+ // might give false positives as they might belong to the contour itself)
+ if (this->expolygon != NULL) {
+ if (line.a == line.b) {
+ // in this case, contains(line) returns a false positive
+ if (!this->expolygon->contains(line.a)) return false;
+ } else {
+ if (!this->expolygon->contains(line)) return false;
+ }
+ }
+
+ // retrieve the original line segments which generated the edge we're checking
+ const VD::cell_type* cell_l = edge->cell();
+ const VD::cell_type* cell_r = edge->twin()->cell();
+ const Line &segment_l = this->retrieve_segment(cell_l);
+ const Line &segment_r = this->retrieve_segment(cell_r);
+
+ /*
+ SVG svg("edge.svg");
+ svg.draw(*this->expolygon);
+ svg.draw(line);
+ svg.draw(segment_l, "red");
+ svg.draw(segment_r, "blue");
+ svg.Close();
+ */
+
+ /* Calculate thickness of the cross-section at both the endpoints of this edge.
+ Our Voronoi edge is part of a CCW sequence going around its Voronoi cell
+ located on the left side. (segment_l).
+ This edge's twin goes around segment_r. Thus, segment_r is
+ oriented in the same direction as our main edge, and segment_l is oriented
+ in the same direction as our twin edge.
+ We used to only consider the (half-)distances to segment_r, and that works
+ whenever segment_l and segment_r are almost specular and facing. However,
+ at curves they are staggered and they only face for a very little length
+ (our very short edge represents such visibility).
+ Both w0 and w1 can be calculated either towards cell_l or cell_r with equal
+ results by Voronoi definition.
+ When cell_l or cell_r don't refer to the segment but only to an endpoint, we
+ calculate the distance to that endpoint instead. */
+
+ coordf_t w0 = cell_r->contains_segment()
+ ? segment_r.distance_to(line.a)*2
+ : (this->retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2;
+
+ coordf_t w1 = cell_l->contains_segment()
+ ? segment_l.distance_to(line.b)*2
+ : (this->retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2;
+
+ if (cell_l->contains_segment() && cell_r->contains_segment()) {
+ // calculate the relative angle between the two boundary segments
+ double angle = fabs(segment_r.orientation() - segment_l.orientation());
+ if (angle > PI) angle = 2*PI - angle;
+ assert(angle >= 0 && angle <= PI);
+
+ // fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction)
+ // we're interested only in segments close to the second case (facing segments)
+ // so we allow some tolerance.
+ // this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
+ // we don't run it on edges not generated by two segments (thus generated by one segment
+ // and the endpoint of another segment), since their orientation would not be meaningful
+ if (PI - angle > PI/8) {
+ // angle is not narrow enough
+
+ // only apply this filter to segments that are not too short otherwise their
+ // angle could possibly be not meaningful
+ if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width)
+ return false;
+ }
+ } else {
+ if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON)
+ return false;
+ }
+
+ if (w0 < this->min_width && w1 < this->min_width)
+ return false;
+
+ if (w0 > this->max_width && w1 > this->max_width)
+ return false;
+
+ this->thickness[edge] = std::make_pair(w0, w1);
+ this->thickness[edge->twin()] = std::make_pair(w1, w0);
+
+ return true;
+}
+
+const Line&
+MedialAxis::retrieve_segment(const VD::cell_type* cell) const
+{
+ return this->lines[cell->source_index()];
+}
+
+const Point&
+MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const
+{
+ const Line& line = this->retrieve_segment(cell);
+ if (cell->source_category() == SOURCE_CATEGORY_SEGMENT_START_POINT) {
+ return line.a;
+ } else {
+ return line.b;
+ }
+}
+
+} }
diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp
new file mode 100644
index 000000000..3698b996f
--- /dev/null
+++ b/src/libslic3r/Geometry.hpp
@@ -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
diff --git a/src/libslic3r/I18N.hpp b/src/libslic3r/I18N.hpp
new file mode 100644
index 000000000..db4fd22df
--- /dev/null
+++ b/src/libslic3r/I18N.hpp
@@ -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_ */
diff --git a/src/libslic3r/Int128.hpp b/src/libslic3r/Int128.hpp
new file mode 100644
index 000000000..d54b75342
--- /dev/null
+++ b/src/libslic3r/Int128.hpp
@@ -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;
+ }
+};
diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp
new file mode 100644
index 000000000..6c2bd0da9
--- /dev/null
+++ b/src/libslic3r/Layer.cpp
@@ -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());
+}
+
+}
diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp
new file mode 100644
index 000000000..8dbe850cc
--- /dev/null
+++ b/src/libslic3r/Layer.hpp
@@ -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
diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp
new file mode 100644
index 000000000..e0f97703c
--- /dev/null
+++ b/src/libslic3r/LayerRegion.cpp
@@ -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());
+}
+
+}
+ \ No newline at end of file
diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp
new file mode 100644
index 000000000..35cfa2b76
--- /dev/null
+++ b/src/libslic3r/Line.cpp
@@ -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);
+}
+
+}
diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp
new file mode 100644
index 000000000..36e02247c
--- /dev/null
+++ b/src/libslic3r/Line.hpp
@@ -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
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
new file mode 100644
index 000000000..ec7447352
--- /dev/null
+++ b/src/libslic3r/Model.cpp
@@ -0,0 +1,1125 @@
+#include "Model.hpp"
+#include "Geometry.hpp"
+
+#include "Format/AMF.hpp"
+#include "Format/OBJ.hpp"
+#include "Format/PRUS.hpp"
+#include "Format/STL.hpp"
+#include "Format/3mf.hpp"
+
+#include <float.h>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/nowide/iostream.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+#include "SVG.hpp"
+#include <Eigen/Dense>
+
+namespace Slic3r {
+
+ unsigned int Model::s_auto_extruder_id = 1;
+
+Model::Model(const Model &other)
+{
+ // copy materials
+ for (const auto &m : other.materials)
+ this->add_material(m.first, *m.second);
+ // copy objects
+ this->objects.reserve(other.objects.size());
+ for (const ModelObject *o : other.objects)
+ this->add_object(*o, true);
+}
+
+Model& Model::operator=(Model other)
+{
+ this->swap(other);
+ return *this;
+}
+
+void Model::swap(Model &other)
+{
+ std::swap(this->materials, other.materials);
+ std::swap(this->objects, other.objects);
+}
+
+Model Model::read_from_file(const std::string &input_file, bool add_default_instances)
+{
+ Model model;
+
+ bool result = false;
+ if (boost::algorithm::iends_with(input_file, ".stl"))
+ result = load_stl(input_file.c_str(), &model);
+ else if (boost::algorithm::iends_with(input_file, ".obj"))
+ result = load_obj(input_file.c_str(), &model);
+ else if (!boost::algorithm::iends_with(input_file, ".zip.amf") && (boost::algorithm::iends_with(input_file, ".amf") ||
+ boost::algorithm::iends_with(input_file, ".amf.xml")))
+ result = load_amf(input_file.c_str(), nullptr, &model);
+#ifdef SLIC3R_PRUS
+ else if (boost::algorithm::iends_with(input_file, ".prusa"))
+ result = load_prus(input_file.c_str(), &model);
+#endif /* SLIC3R_PRUS */
+ else
+ throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension.");
+
+ if (! result)
+ throw std::runtime_error("Loading of a model file failed.");
+
+ if (model.objects.empty())
+ throw std::runtime_error("The supplied file couldn't be read because it's empty");
+
+ for (ModelObject *o : model.objects)
+ o->input_file = input_file;
+
+ if (add_default_instances)
+ model.add_default_instances();
+
+ return model;
+}
+
+Model Model::read_from_archive(const std::string &input_file, PresetBundle* bundle, bool add_default_instances)
+{
+ Model model;
+
+ bool result = false;
+ if (boost::algorithm::iends_with(input_file, ".3mf"))
+ result = load_3mf(input_file.c_str(), bundle, &model);
+ else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
+ result = load_amf(input_file.c_str(), bundle, &model);
+ else
+ throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension.");
+
+ if (!result)
+ throw std::runtime_error("Loading of a model file failed.");
+
+ if (model.objects.empty())
+ throw std::runtime_error("The supplied file couldn't be read because it's empty");
+
+ for (ModelObject *o : model.objects)
+ {
+ if (boost::algorithm::iends_with(input_file, ".zip.amf"))
+ {
+ // we remove the .zip part of the extension to avoid it be added to filenames when exporting
+ o->input_file = boost::ireplace_last_copy(input_file, ".zip.", ".");
+ }
+ else
+ o->input_file = input_file;
+ }
+
+ if (add_default_instances)
+ model.add_default_instances();
+
+ return model;
+}
+
+ModelObject* Model::add_object()
+{
+ this->objects.emplace_back(new ModelObject(this));
+ return this->objects.back();
+}
+
+ModelObject* Model::add_object(const char *name, const char *path, const TriangleMesh &mesh)
+{
+ ModelObject* new_object = new ModelObject(this);
+ this->objects.push_back(new_object);
+ new_object->name = name;
+ new_object->input_file = path;
+ ModelVolume *new_volume = new_object->add_volume(mesh);
+ new_volume->name = name;
+ new_object->invalidate_bounding_box();
+ return new_object;
+}
+
+ModelObject* Model::add_object(const char *name, const char *path, TriangleMesh &&mesh)
+{
+ ModelObject* new_object = new ModelObject(this);
+ this->objects.push_back(new_object);
+ new_object->name = name;
+ new_object->input_file = path;
+ ModelVolume *new_volume = new_object->add_volume(std::move(mesh));
+ new_volume->name = name;
+ new_object->invalidate_bounding_box();
+ return new_object;
+}
+
+ModelObject* Model::add_object(const ModelObject &other, bool copy_volumes)
+{
+ ModelObject* new_object = new ModelObject(this, other, copy_volumes);
+ this->objects.push_back(new_object);
+ return new_object;
+}
+
+void Model::delete_object(size_t idx)
+{
+ ModelObjectPtrs::iterator i = this->objects.begin() + idx;
+ delete *i;
+ this->objects.erase(i);
+}
+
+void Model::delete_object(ModelObject* object)
+{
+ if (object == nullptr)
+ return;
+
+ for (ModelObjectPtrs::iterator it = objects.begin(); it != objects.end(); ++it)
+ {
+ ModelObject* obj = *it;
+ if (obj == object)
+ {
+ delete obj;
+ objects.erase(it);
+ return;
+ }
+ }
+}
+
+void Model::clear_objects()
+{
+ for (ModelObject *o : this->objects)
+ delete o;
+ this->objects.clear();
+}
+
+void Model::delete_material(t_model_material_id material_id)
+{
+ ModelMaterialMap::iterator i = this->materials.find(material_id);
+ if (i != this->materials.end()) {
+ delete i->second;
+ this->materials.erase(i);
+ }
+}
+
+void Model::clear_materials()
+{
+ for (auto &m : this->materials)
+ delete m.second;
+ this->materials.clear();
+}
+
+ModelMaterial* Model::add_material(t_model_material_id material_id)
+{
+ ModelMaterial* material = this->get_material(material_id);
+ if (material == nullptr)
+ material = this->materials[material_id] = new ModelMaterial(this);
+ return material;
+}
+
+ModelMaterial* Model::add_material(t_model_material_id material_id, const ModelMaterial &other)
+{
+ // delete existing material if any
+ ModelMaterial* material = this->get_material(material_id);
+ delete material;
+ // set new material
+ material = new ModelMaterial(this, other);
+ this->materials[material_id] = material;
+ return material;
+}
+
+// makes sure all objects have at least one instance
+bool Model::add_default_instances()
+{
+ // apply a default position to all objects not having one
+ for (ModelObject *o : this->objects)
+ if (o->instances.empty())
+ o->add_instance();
+ return true;
+}
+
+// this returns the bounding box of the *transformed* instances
+BoundingBoxf3 Model::bounding_box() const
+{
+ BoundingBoxf3 bb;
+ for (ModelObject *o : this->objects)
+ bb.merge(o->bounding_box());
+ return bb;
+}
+
+void Model::center_instances_around_point(const Vec2d &point)
+{
+ BoundingBoxf3 bb;
+ for (ModelObject *o : this->objects)
+ for (size_t i = 0; i < o->instances.size(); ++ i)
+ bb.merge(o->instance_bounding_box(i, false));
+
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ Vec2d shift2 = point - to_2d(bb.center());
+ Vec3d shift3 = Vec3d(shift2(0), shift2(1), 0.0);
+#else
+ Vec2d shift = point - 0.5 * to_2d(bb.size()) - to_2d(bb.min);
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ for (ModelObject *o : this->objects) {
+ for (ModelInstance *i : o->instances)
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ i->set_offset(i->get_offset() + shift3);
+#else
+ i->offset += shift;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ o->invalidate_bounding_box();
+ }
+}
+
+// flattens everything to a single mesh
+TriangleMesh Model::mesh() const
+{
+ TriangleMesh mesh;
+ for (const ModelObject *o : this->objects)
+ mesh.merge(o->mesh());
+ return mesh;
+}
+
+static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out)
+{
+ if (sizes.empty())
+ // return if the list is empty or the following call to BoundingBoxf constructor will lead to a crash
+ return true;
+
+ // we supply unscaled data to arrange()
+ bool result = Slic3r::Geometry::arrange(
+ sizes.size(), // number of parts
+ BoundingBoxf(sizes).max, // width and height of a single cell
+ dist, // distance between cells
+ bb, // bounding box of the area to fill
+ out // output positions
+ );
+
+ if (!result && bb != nullptr) {
+ // Try to arrange again ignoring bb
+ result = Slic3r::Geometry::arrange(
+ sizes.size(), // number of parts
+ BoundingBoxf(sizes).max, // width and height of a single cell
+ dist, // distance between cells
+ nullptr, // bounding box of the area to fill
+ out // output positions
+ );
+ }
+
+ return result;
+}
+
+/* arrange objects preserving their instance count
+ but altering their instance positions */
+bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
+{
+ // get the (transformed) size of each instance so that we take
+ // into account their different transformations when packing
+ Pointfs instance_sizes;
+ Pointfs instance_centers;
+ for (const ModelObject *o : this->objects)
+ for (size_t i = 0; i < o->instances.size(); ++ i) {
+ // an accurate snug bounding box around the transformed mesh.
+ BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
+ instance_sizes.emplace_back(to_2d(bbox.size()));
+ instance_centers.emplace_back(to_2d(bbox.center()));
+ }
+
+ Pointfs positions;
+ if (! _arrange(instance_sizes, dist, bb, positions))
+ return false;
+
+ size_t idx = 0;
+ for (ModelObject *o : this->objects) {
+ for (ModelInstance *i : o->instances) {
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ Vec2d offset_xy = positions[idx] - instance_centers[idx];
+ i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z)));
+#else
+ i->offset = positions[idx] - instance_centers[idx];
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ ++idx;
+ }
+ o->invalidate_bounding_box();
+ }
+
+ return true;
+}
+
+// Duplicate the entire model preserving instance relative positions.
+void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb)
+{
+ Pointfs model_sizes(copies_num-1, to_2d(this->bounding_box().size()));
+ Pointfs positions;
+ if (! _arrange(model_sizes, dist, bb, positions))
+ throw std::invalid_argument("Cannot duplicate part as the resulting objects would not fit on the print bed.\n");
+
+ // note that this will leave the object count unaltered
+
+ for (ModelObject *o : this->objects) {
+ // make a copy of the pointers in order to avoid recursion when appending their copies
+ ModelInstancePtrs instances = o->instances;
+ for (const ModelInstance *i : instances) {
+ for (const Vec2d &pos : positions) {
+ ModelInstance *instance = o->add_instance(*i);
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ instance->set_offset(instance->get_offset() + Vec3d(pos(0), pos(1), 0.0));
+#else
+ instance->offset += pos;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ }
+ }
+ o->invalidate_bounding_box();
+ }
+}
+
+/* this will append more instances to each object
+ and then automatically rearrange everything */
+void Model::duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb)
+{
+ for (ModelObject *o : this->objects) {
+ // make a copy of the pointers in order to avoid recursion when appending their copies
+ ModelInstancePtrs instances = o->instances;
+ for (const ModelInstance *i : instances)
+ for (size_t k = 2; k <= copies_num; ++ k)
+ o->add_instance(*i);
+ }
+
+ this->arrange_objects(dist, bb);
+}
+
+void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist)
+{
+ if (this->objects.size() > 1) throw "Grid duplication is not supported with multiple objects";
+ if (this->objects.empty()) throw "No objects!";
+
+ ModelObject* object = this->objects.front();
+ object->clear_instances();
+
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ Vec3d ext_size = object->bounding_box().size() + dist * Vec3d::Ones();
+#else
+ Vec3d size = object->bounding_box().size();
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+
+ for (size_t x_copy = 1; x_copy <= x; ++x_copy) {
+ for (size_t y_copy = 1; y_copy <= y; ++y_copy) {
+ ModelInstance* instance = object->add_instance();
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ instance->set_offset(Vec3d(ext_size(0) * (double)(x_copy - 1), ext_size(1) * (double)(y_copy - 1), 0.0));
+#else
+ instance->offset(0) = (size(0) + dist) * (x_copy - 1);
+ instance->offset(1) = (size(1) + dist) * (y_copy - 1);
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ }
+ }
+}
+
+bool Model::looks_like_multipart_object() const
+{
+ if (this->objects.size() <= 1)
+ return false;
+ double zmin = std::numeric_limits<double>::max();
+ for (const ModelObject *obj : this->objects) {
+ if (obj->volumes.size() > 1 || obj->config.keys().size() > 1)
+ return false;
+ for (const ModelVolume *vol : obj->volumes) {
+ double zmin_this = vol->mesh.bounding_box().min(2);
+ if (zmin == std::numeric_limits<double>::max())
+ zmin = zmin_this;
+ else if (std::abs(zmin - zmin_this) > EPSILON)
+ // The volumes don't share zmin.
+ return true;
+ }
+ }
+ return false;
+}
+
+void Model::convert_multipart_object(unsigned int max_extruders)
+{
+ if (this->objects.empty())
+ return;
+
+ ModelObject* object = new ModelObject(this);
+ object->input_file = this->objects.front()->input_file;
+
+ reset_auto_extruder_id();
+
+ for (const ModelObject* o : this->objects)
+ for (const ModelVolume* v : o->volumes)
+ {
+ ModelVolume* new_v = object->add_volume(*v);
+ if (new_v != nullptr)
+ {
+ new_v->name = o->name;
+ new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders));
+ }
+ }
+
+ for (const ModelInstance* i : this->objects.front()->instances)
+ object->add_instance(*i);
+
+ this->clear_objects();
+ this->objects.push_back(object);
+}
+
+void Model::adjust_min_z()
+{
+ if (objects.empty())
+ return;
+
+ if (bounding_box().min(2) < 0.0)
+ {
+ for (ModelObject* obj : objects)
+ {
+ if (obj != nullptr)
+ {
+ coordf_t obj_min_z = obj->bounding_box().min(2);
+ if (obj_min_z < 0.0)
+ obj->translate(0.0, 0.0, -obj_min_z);
+ }
+ }
+ }
+}
+
+unsigned int Model::get_auto_extruder_id(unsigned int max_extruders)
+{
+ unsigned int id = s_auto_extruder_id;
+
+ if (++s_auto_extruder_id > max_extruders)
+ reset_auto_extruder_id();
+
+ return id;
+}
+
+std::string Model::get_auto_extruder_id_as_string(unsigned int max_extruders)
+{
+ char str_extruder[64];
+ sprintf(str_extruder, "%ud", get_auto_extruder_id(max_extruders));
+ return str_extruder;
+}
+
+void Model::reset_auto_extruder_id()
+{
+ s_auto_extruder_id = 1;
+}
+
+ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volumes) :
+ name(other.name),
+ input_file(other.input_file),
+ instances(),
+ volumes(),
+ config(other.config),
+ layer_height_ranges(other.layer_height_ranges),
+ layer_height_profile(other.layer_height_profile),
+ layer_height_profile_valid(other.layer_height_profile_valid),
+ origin_translation(other.origin_translation),
+ m_bounding_box(other.m_bounding_box),
+ m_bounding_box_valid(other.m_bounding_box_valid),
+ m_model(model)
+{
+ if (copy_volumes) {
+ this->volumes.reserve(other.volumes.size());
+ for (ModelVolumePtrs::const_iterator i = other.volumes.begin(); i != other.volumes.end(); ++i)
+ this->add_volume(**i);
+ }
+
+ this->instances.reserve(other.instances.size());
+ for (ModelInstancePtrs::const_iterator i = other.instances.begin(); i != other.instances.end(); ++i)
+ this->add_instance(**i);
+}
+
+ModelObject& ModelObject::operator=(ModelObject other)
+{
+ this->swap(other);
+ return *this;
+}
+
+void ModelObject::swap(ModelObject &other)
+{
+ std::swap(this->input_file, other.input_file);
+ std::swap(this->instances, other.instances);
+ std::swap(this->volumes, other.volumes);
+ std::swap(this->config, other.config);
+ std::swap(this->layer_height_ranges, other.layer_height_ranges);
+ std::swap(this->layer_height_profile, other.layer_height_profile);
+ std::swap(this->layer_height_profile_valid, other.layer_height_profile_valid);
+ std::swap(this->origin_translation, other.origin_translation);
+ std::swap(m_bounding_box, other.m_bounding_box);
+ std::swap(m_bounding_box_valid, other.m_bounding_box_valid);
+}
+
+ModelObject::~ModelObject()
+{
+ this->clear_volumes();
+ this->clear_instances();
+}
+
+ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh)
+{
+ ModelVolume* v = new ModelVolume(this, mesh);
+ this->volumes.push_back(v);
+ this->invalidate_bounding_box();
+ return v;
+}
+
+ModelVolume* ModelObject::add_volume(TriangleMesh &&mesh)
+{
+ ModelVolume* v = new ModelVolume(this, std::move(mesh));
+ this->volumes.push_back(v);
+ this->invalidate_bounding_box();
+ return v;
+}
+
+ModelVolume* ModelObject::add_volume(const ModelVolume &other)
+{
+ ModelVolume* v = new ModelVolume(this, other);
+ this->volumes.push_back(v);
+ this->invalidate_bounding_box();
+ return v;
+}
+
+void ModelObject::delete_volume(size_t idx)
+{
+ ModelVolumePtrs::iterator i = this->volumes.begin() + idx;
+ delete *i;
+ this->volumes.erase(i);
+ this->invalidate_bounding_box();
+}
+
+void ModelObject::clear_volumes()
+{
+ for (ModelVolume *v : this->volumes)
+ delete v;
+ this->volumes.clear();
+ this->invalidate_bounding_box();
+}
+
+ModelInstance* ModelObject::add_instance()
+{
+ ModelInstance* i = new ModelInstance(this);
+ this->instances.push_back(i);
+ this->invalidate_bounding_box();
+ return i;
+}
+
+ModelInstance* ModelObject::add_instance(const ModelInstance &other)
+{
+ ModelInstance* i = new ModelInstance(this, other);
+ this->instances.push_back(i);
+ this->invalidate_bounding_box();
+ return i;
+}
+
+void ModelObject::delete_instance(size_t idx)
+{
+ ModelInstancePtrs::iterator i = this->instances.begin() + idx;
+ delete *i;
+ this->instances.erase(i);
+ this->invalidate_bounding_box();
+}
+
+void ModelObject::delete_last_instance()
+{
+ this->delete_instance(this->instances.size() - 1);
+}
+
+void ModelObject::clear_instances()
+{
+ for (ModelInstance *i : this->instances)
+ delete i;
+ this->instances.clear();
+ this->invalidate_bounding_box();
+}
+
+// Returns the bounding box of the transformed instances.
+// This bounding box is approximate and not snug.
+const BoundingBoxf3& ModelObject::bounding_box() const
+{
+ if (! m_bounding_box_valid) {
+ BoundingBoxf3 raw_bbox;
+ for (const ModelVolume *v : this->volumes)
+ if (v->is_model_part())
+ // mesh.bounding_box() returns a cached value.
+ raw_bbox.merge(v->mesh.bounding_box());
+ BoundingBoxf3 bb;
+ for (const ModelInstance *i : this->instances)
+ bb.merge(i->transform_bounding_box(raw_bbox));
+ m_bounding_box = bb;
+ m_bounding_box_valid = true;
+ }
+ return m_bounding_box;
+}
+
+// A mesh containing all transformed instances of this object.
+TriangleMesh ModelObject::mesh() const
+{
+ TriangleMesh mesh;
+ TriangleMesh raw_mesh = this->raw_mesh();
+ for (const ModelInstance *i : this->instances) {
+ TriangleMesh m = raw_mesh;
+ i->transform_mesh(&m);
+ mesh.merge(m);
+ }
+ return mesh;
+}
+
+// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
+// Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D platter
+// and to display the object statistics at ModelObject::print_info().
+TriangleMesh ModelObject::raw_mesh() const
+{
+ TriangleMesh mesh;
+ for (const ModelVolume *v : this->volumes)
+ if (v->is_model_part())
+ mesh.merge(v->mesh);
+ return mesh;
+}
+
+// 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 ModelObject::raw_bounding_box() const
+{
+ BoundingBoxf3 bb;
+ for (const ModelVolume *v : this->volumes)
+ if (v->is_model_part()) {
+ if (this->instances.empty())
+ throw std::invalid_argument("Can't call raw_bounding_box() with no instances");
+ bb.merge(this->instances.front()->transform_mesh_bounding_box(&v->mesh, true));
+ }
+ return bb;
+}
+
+// This returns an accurate snug bounding box of the transformed object instance, without the translation applied.
+BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_translate) const
+{
+ BoundingBoxf3 bb;
+ for (ModelVolume *v : this->volumes)
+ if (v->is_model_part())
+ bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(&v->mesh, dont_translate));
+ return bb;
+}
+
+void ModelObject::center_around_origin()
+{
+ // calculate the displacements needed to
+ // center this object around the origin
+ BoundingBoxf3 bb;
+ for (ModelVolume *v : this->volumes)
+ if (v->is_model_part())
+ bb.merge(v->mesh.bounding_box());
+
+ // Shift is the vector from the center of the bottom face of the bounding box to the origin
+ Vec3d shift = -bb.center();
+ shift(2) = -bb.min(2);
+
+ this->translate(shift);
+ this->origin_translation += shift;
+
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ // set z to zero, translation in z has already been done within the mesh
+ shift(2) = 0.0;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+
+ if (!this->instances.empty()) {
+ for (ModelInstance *i : this->instances) {
+ // apply rotation and scaling to vector as well before translating instance,
+ // in order to leave final position unaltered
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ i->set_offset(i->get_offset() + i->transform_vector(-shift, true));
+#else
+ Vec3d i_shift = i->world_matrix(true) * shift;
+ i->offset -= to_2d(i_shift);
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ }
+ this->invalidate_bounding_box();
+ }
+}
+
+void ModelObject::translate(coordf_t x, coordf_t y, coordf_t z)
+{
+ for (ModelVolume *v : this->volumes)
+ {
+ v->mesh.translate(float(x), float(y), float(z));
+ v->m_convex_hull.translate(float(x), float(y), float(z));
+ }
+
+ if (m_bounding_box_valid)
+ m_bounding_box.translate(x, y, z);
+}
+
+void ModelObject::scale(const Vec3d &versor)
+{
+ for (ModelVolume *v : this->volumes)
+ {
+ v->mesh.scale(versor);
+ v->m_convex_hull.scale(versor);
+ }
+ // reset origin translation since it doesn't make sense anymore
+ this->origin_translation = Vec3d::Zero();
+ this->invalidate_bounding_box();
+}
+
+void ModelObject::rotate(float angle, const Axis& axis)
+{
+ for (ModelVolume *v : this->volumes)
+ {
+ v->mesh.rotate(angle, axis);
+ v->m_convex_hull.rotate(angle, axis);
+ }
+
+ center_around_origin();
+
+ this->origin_translation = Vec3d::Zero();
+ this->invalidate_bounding_box();
+}
+
+void ModelObject::rotate(float angle, const Vec3d& axis)
+{
+ for (ModelVolume *v : this->volumes)
+ {
+ v->mesh.rotate(angle, axis);
+ v->m_convex_hull.rotate(angle, axis);
+ }
+
+ center_around_origin();
+
+ this->origin_translation = Vec3d::Zero();
+ this->invalidate_bounding_box();
+}
+
+void ModelObject::mirror(const Axis &axis)
+{
+ for (ModelVolume *v : this->volumes)
+ {
+ v->mesh.mirror(axis);
+ v->m_convex_hull.mirror(axis);
+ }
+
+ this->origin_translation = Vec3d::Zero();
+ this->invalidate_bounding_box();
+}
+
+size_t ModelObject::materials_count() const
+{
+ std::set<t_model_material_id> material_ids;
+ for (const ModelVolume *v : this->volumes)
+ material_ids.insert(v->material_id());
+ return material_ids.size();
+}
+
+size_t ModelObject::facets_count() const
+{
+ size_t num = 0;
+ for (const ModelVolume *v : this->volumes)
+ if (v->is_model_part())
+ num += v->mesh.stl.stats.number_of_facets;
+ return num;
+}
+
+bool ModelObject::needed_repair() const
+{
+ for (const ModelVolume *v : this->volumes)
+ if (v->is_model_part() && v->mesh.needed_repair())
+ return true;
+ return false;
+}
+
+void ModelObject::cut(coordf_t z, Model* model) const
+{
+ // clone this one to duplicate instances, materials etc.
+ ModelObject* upper = model->add_object(*this);
+ ModelObject* lower = model->add_object(*this);
+ upper->clear_volumes();
+ lower->clear_volumes();
+ upper->input_file = "";
+ lower->input_file = "";
+
+ for (ModelVolume *volume : this->volumes) {
+ if (! volume->is_model_part()) {
+ // don't cut modifiers
+ upper->add_volume(*volume);
+ lower->add_volume(*volume);
+ } else {
+ TriangleMesh upper_mesh, lower_mesh;
+ TriangleMeshSlicer tms(&volume->mesh);
+ tms.cut(z, &upper_mesh, &lower_mesh);
+
+ upper_mesh.repair();
+ lower_mesh.repair();
+ upper_mesh.reset_repair_stats();
+ lower_mesh.reset_repair_stats();
+
+ if (upper_mesh.facets_count() > 0) {
+ ModelVolume* vol = upper->add_volume(upper_mesh);
+ vol->name = volume->name;
+ vol->config = volume->config;
+ vol->set_material(volume->material_id(), *volume->material());
+ }
+ if (lower_mesh.facets_count() > 0) {
+ ModelVolume* vol = lower->add_volume(lower_mesh);
+ vol->name = volume->name;
+ vol->config = volume->config;
+ vol->set_material(volume->material_id(), *volume->material());
+ }
+ }
+ }
+}
+
+void ModelObject::split(ModelObjectPtrs* new_objects)
+{
+ if (this->volumes.size() > 1) {
+ // We can't split meshes if there's more than one volume, because
+ // we can't group the resulting meshes by object afterwards
+ new_objects->push_back(this);
+ return;
+ }
+
+ ModelVolume* volume = this->volumes.front();
+ TriangleMeshPtrs meshptrs = volume->mesh.split();
+ for (TriangleMesh *mesh : meshptrs) {
+ // Snap the mesh to Z=0.
+ float z0 = FLT_MAX;
+
+ mesh->repair();
+
+ ModelObject* new_object = m_model->add_object(*this, false);
+ new_object->input_file = "";
+ ModelVolume* new_volume = new_object->add_volume(*mesh);
+ new_volume->name = volume->name;
+ new_volume->config = volume->config;
+ new_volume->set_type(volume->type());
+ new_volume->material_id(volume->material_id());
+
+ new_objects->push_back(new_object);
+ delete mesh;
+ }
+
+ return;
+}
+
+// Called by Print::validate() from the UI thread.
+void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume)
+{
+ for (const ModelVolume* vol : this->volumes)
+ {
+ if (vol->is_model_part())
+ {
+ for (ModelInstance* inst : this->instances)
+ {
+ BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(inst->world_matrix());
+
+ if (print_volume.contains(bb))
+ inst->print_volume_state = ModelInstance::PVS_Inside;
+ else if (print_volume.intersects(bb))
+ inst->print_volume_state = ModelInstance::PVS_Partly_Outside;
+ else
+ inst->print_volume_state = ModelInstance::PVS_Fully_Outside;
+ }
+ }
+ }
+}
+
+void ModelObject::print_info() const
+{
+ using namespace std;
+ cout << fixed;
+ boost::nowide::cout << "[" << boost::filesystem::path(this->input_file).filename().string() << "]" << endl;
+
+ TriangleMesh mesh = this->raw_mesh();
+ mesh.check_topology();
+ BoundingBoxf3 bb = mesh.bounding_box();
+ Vec3d size = bb.size();
+ cout << "size_x = " << size(0) << endl;
+ cout << "size_y = " << size(1) << endl;
+ cout << "size_z = " << size(2) << endl;
+ cout << "min_x = " << bb.min(0) << endl;
+ cout << "min_y = " << bb.min(1) << endl;
+ cout << "min_z = " << bb.min(2) << endl;
+ cout << "max_x = " << bb.max(0) << endl;
+ cout << "max_y = " << bb.max(1) << endl;
+ cout << "max_z = " << bb.max(2) << endl;
+ cout << "number_of_facets = " << mesh.stl.stats.number_of_facets << endl;
+ cout << "manifold = " << (mesh.is_manifold() ? "yes" : "no") << endl;
+
+ mesh.repair(); // this calculates number_of_parts
+ if (mesh.needed_repair()) {
+ mesh.repair();
+ if (mesh.stl.stats.degenerate_facets > 0)
+ cout << "degenerate_facets = " << mesh.stl.stats.degenerate_facets << endl;
+ if (mesh.stl.stats.edges_fixed > 0)
+ cout << "edges_fixed = " << mesh.stl.stats.edges_fixed << endl;
+ if (mesh.stl.stats.facets_removed > 0)
+ cout << "facets_removed = " << mesh.stl.stats.facets_removed << endl;
+ if (mesh.stl.stats.facets_added > 0)
+ cout << "facets_added = " << mesh.stl.stats.facets_added << endl;
+ if (mesh.stl.stats.facets_reversed > 0)
+ cout << "facets_reversed = " << mesh.stl.stats.facets_reversed << endl;
+ if (mesh.stl.stats.backwards_edges > 0)
+ cout << "backwards_edges = " << mesh.stl.stats.backwards_edges << endl;
+ }
+ cout << "number_of_parts = " << mesh.stl.stats.number_of_parts << endl;
+ cout << "volume = " << mesh.volume() << endl;
+}
+
+void ModelVolume::material_id(t_model_material_id material_id)
+{
+ this->_material_id = material_id;
+
+ // ensure this->_material_id references an existing material
+ (void)this->object->get_model()->add_material(material_id);
+}
+
+ModelMaterial* ModelVolume::material() const
+{
+ return this->object->get_model()->get_material(this->_material_id);
+}
+
+void ModelVolume::set_material(t_model_material_id material_id, const ModelMaterial &material)
+{
+ this->_material_id = material_id;
+ (void)this->object->get_model()->add_material(material_id, material);
+}
+
+ModelMaterial* ModelVolume::assign_unique_material()
+{
+ Model* model = this->get_object()->get_model();
+
+ // as material-id "0" is reserved by the AMF spec we start from 1
+ this->_material_id = 1 + model->materials.size(); // watchout for implicit cast
+ return model->add_material(this->_material_id);
+}
+
+void ModelVolume::calculate_convex_hull()
+{
+ m_convex_hull = mesh.convex_hull_3d();
+}
+
+const TriangleMesh& ModelVolume::get_convex_hull() const
+{
+ return m_convex_hull;
+}
+
+ModelVolume::Type ModelVolume::type_from_string(const std::string &s)
+{
+ // Legacy support
+ if (s == "1")
+ return PARAMETER_MODIFIER;
+ // New type (supporting the support enforcers & blockers)
+ if (s == "ModelPart")
+ return MODEL_PART;
+ if (s == "ParameterModifier")
+ return PARAMETER_MODIFIER;
+ if (s == "SupportEnforcer")
+ return SUPPORT_ENFORCER;
+ if (s == "SupportBlocker")
+ return SUPPORT_BLOCKER;
+ assert(s == "0");
+ // Default value if invalud type string received.
+ return MODEL_PART;
+}
+
+std::string ModelVolume::type_to_string(const Type t)
+{
+ switch (t) {
+ case MODEL_PART: return "ModelPart";
+ case PARAMETER_MODIFIER: return "ParameterModifier";
+ case SUPPORT_ENFORCER: return "SupportEnforcer";
+ case SUPPORT_BLOCKER: return "SupportBlocker";
+ default:
+ assert(false);
+ return "ModelPart";
+ }
+}
+
+// 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 ModelVolume::split(unsigned int max_extruders)
+{
+ TriangleMeshPtrs meshptrs = this->mesh.split();
+ if (meshptrs.size() <= 1) {
+ delete meshptrs.front();
+ return 1;
+ }
+
+ size_t idx = 0;
+ size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin();
+ std::string name = this->name;
+
+ Model::reset_auto_extruder_id();
+
+ for (TriangleMesh *mesh : meshptrs) {
+ mesh->repair();
+ if (idx == 0)
+ this->mesh = std::move(*mesh);
+ else
+ this->object->volumes.insert(this->object->volumes.begin() + (++ ivolume), new ModelVolume(object, *this, std::move(*mesh)));
+ char str_idx[64];
+ sprintf(str_idx, "_%d", idx + 1);
+ this->object->volumes[ivolume]->name = name + str_idx;
+ this->object->volumes[ivolume]->config.set_deserialize("extruder", Model::get_auto_extruder_id_as_string(max_extruders));
+ delete mesh;
+ ++ idx;
+ }
+
+ return idx;
+}
+
+void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const
+{
+ mesh->transform(world_matrix(dont_translate).cast<float>());
+}
+
+BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mesh, bool dont_translate) const
+{
+ // Rotate around mesh origin.
+ TriangleMesh copy(*mesh);
+ copy.transform(world_matrix(true, false, true).cast<float>());
+ BoundingBoxf3 bbox = copy.bounding_box();
+
+ if (!empty(bbox)) {
+ // Scale the bounding box uniformly.
+ if (std::abs(this->scaling_factor - 1.) > EPSILON) {
+ bbox.min *= this->scaling_factor;
+ bbox.max *= this->scaling_factor;
+ }
+ // Translate the bounding box.
+ if (! dont_translate) {
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ bbox.min += this->m_offset;
+ bbox.max += this->m_offset;
+#else
+ Eigen::Map<Vec2d>(bbox.min.data()) += this->offset;
+ Eigen::Map<Vec2d>(bbox.max.data()) += this->offset;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ }
+ }
+ return bbox;
+}
+
+BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const
+{
+ return bbox.transformed(world_matrix(dont_translate));
+}
+
+Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const
+{
+ return world_matrix(dont_translate) * v;
+}
+
+void ModelInstance::transform_polygon(Polygon* polygon) const
+{
+ polygon->rotate(this->rotation); // rotate around polygon origin
+ polygon->scale(this->scaling_factor); // scale around polygon origin
+}
+
+Transform3d ModelInstance::world_matrix(bool dont_translate, bool dont_rotate, bool dont_scale) const
+{
+ Transform3d m = Transform3d::Identity();
+
+ if (!dont_translate)
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ m.translate(m_offset);
+#else
+ m.translate(Vec3d(offset(0), offset(1), 0.0));
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+
+ if (!dont_rotate)
+ m.rotate(Eigen::AngleAxisd(rotation, Vec3d::UnitZ()));
+
+ if (!dont_scale)
+ m.scale(scaling_factor);
+
+ return m;
+}
+
+}
diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp
new file mode 100644
index 000000000..4c2356429
--- /dev/null
+++ b/src/libslic3r/Model.hpp
@@ -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
diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp
new file mode 100644
index 000000000..372689c39
--- /dev/null
+++ b/src/libslic3r/ModelArrange.hpp
@@ -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
diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp
new file mode 100644
index 000000000..ff3475ed8
--- /dev/null
+++ b/src/libslic3r/MotionPlanner.cpp
@@ -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;
+}
+
+}
diff --git a/src/libslic3r/MotionPlanner.hpp b/src/libslic3r/MotionPlanner.hpp
new file mode 100644
index 000000000..e912f2fb4
--- /dev/null
+++ b/src/libslic3r/MotionPlanner.hpp
@@ -0,0 +1,91 @@
+#ifndef slic3r_MotionPlanner_hpp_
+#define slic3r_MotionPlanner_hpp_
+
+#include "libslic3r.h"
+#include "BoundingBox.hpp"
+#include "ClipperUtils.hpp"
+#include "ExPolygonCollection.hpp"
+#include "Polyline.hpp"
+#include <map>
+#include <utility>
+#include <memory>
+#include <vector>
+
+#define MP_INNER_MARGIN scale_(1.0)
+#define MP_OUTER_MARGIN scale_(2.0)
+
+namespace Slic3r {
+
+class MotionPlanner;
+
+class MotionPlannerEnv
+{
+ friend class MotionPlanner;
+
+public:
+ MotionPlannerEnv() {};
+ MotionPlannerEnv(const ExPolygon &island) : m_island(island), m_island_bbox(get_extents(island)) {};
+ Point nearest_env_point(const Point &from, const Point &to) const;
+ bool island_contains(const Point &pt) const
+ { return m_island_bbox.contains(pt) && m_island.contains(pt); }
+ bool island_contains_b(const Point &pt) const
+ { return m_island_bbox.contains(pt) && m_island.contains_b(pt); }
+
+private:
+ ExPolygon m_island;
+ BoundingBox m_island_bbox;
+ // Region, where the travel is allowed.
+ ExPolygonCollection m_env;
+};
+
+// A 2D directed graph for searching a shortest path using the famous Dijkstra algorithm.
+class MotionPlannerGraph
+{
+public:
+ // Add a directed edge into the graph.
+ size_t add_node(const Point &p) { m_nodes.emplace_back(p); return m_nodes.size() - 1; }
+ void add_edge(size_t from, size_t to, double weight);
+ size_t find_closest_node(const Point &point) const { return point.nearest_point_index(m_nodes); }
+
+ bool empty() const { return m_adjacency_list.empty(); }
+ Polyline shortest_path(size_t from, size_t to) const;
+ Polyline shortest_path(const Point &from, const Point &to) const
+ { return this->shortest_path(this->find_closest_node(from), this->find_closest_node(to)); }
+
+private:
+ typedef int node_t;
+ typedef double weight_t;
+ struct Neighbor {
+ Neighbor(node_t target, weight_t weight) : target(target), weight(weight) {}
+ node_t target;
+ weight_t weight;
+ };
+ Points m_nodes;
+ std::vector<std::vector<Neighbor>> m_adjacency_list;
+};
+
+class MotionPlanner
+{
+public:
+ MotionPlanner(const ExPolygons &islands);
+ ~MotionPlanner() {}
+
+ Polyline shortest_path(const Point &from, const Point &to);
+ size_t islands_count() const { return m_islands.size(); }
+
+private:
+ bool m_initialized;
+ std::vector<MotionPlannerEnv> m_islands;
+ MotionPlannerEnv m_outer;
+ // 0th graph is the graph for m_outer. Other graphs are 1 indexed.
+ std::vector<std::unique_ptr<MotionPlannerGraph>> m_graphs;
+
+ void initialize();
+ const MotionPlannerGraph& init_graph(int island_idx);
+ const MotionPlannerEnv& get_env(int island_idx) const
+ { return (island_idx == -1) ? m_outer : m_islands[island_idx]; }
+};
+
+}
+
+#endif
diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp
new file mode 100644
index 000000000..f44897a04
--- /dev/null
+++ b/src/libslic3r/MultiPoint.cpp
@@ -0,0 +1,281 @@
+#include "MultiPoint.hpp"
+#include "BoundingBox.hpp"
+
+namespace Slic3r {
+
+MultiPoint::operator Points() const
+{
+ return this->points;
+}
+
+void MultiPoint::scale(double factor)
+{
+ for (Point &pt : points)
+ pt *= factor;
+}
+
+void MultiPoint::translate(double x, double y)
+{
+ Vector v(x, y);
+ for (Point &pt : points)
+ pt += v;
+}
+
+void MultiPoint::translate(const Point &v)
+{
+ for (Point &pt : points)
+ pt += v;
+}
+
+void MultiPoint::rotate(double cos_angle, double sin_angle)
+{
+ for (Point &pt : this->points) {
+ double cur_x = double(pt(0));
+ double cur_y = double(pt(1));
+ pt(0) = coord_t(round(cos_angle * cur_x - sin_angle * cur_y));
+ pt(1) = coord_t(round(cos_angle * cur_y + sin_angle * cur_x));
+ }
+}
+
+void MultiPoint::rotate(double angle, const Point &center)
+{
+ double s = sin(angle);
+ double c = cos(angle);
+ for (Point &pt : points) {
+ Vec2crd v(pt - center);
+ pt(0) = (coord_t)round(double(center(0)) + c * v[0] - s * v[1]);
+ pt(1) = (coord_t)round(double(center(1)) + c * v[1] + s * v[0]);
+ }
+}
+
+void MultiPoint::reverse()
+{
+ std::reverse(this->points.begin(), this->points.end());
+}
+
+Point MultiPoint::first_point() const
+{
+ return this->points.front();
+}
+
+double
+MultiPoint::length() const
+{
+ Lines lines = this->lines();
+ double len = 0;
+ for (Lines::iterator it = lines.begin(); it != lines.end(); ++it) {
+ len += it->length();
+ }
+ return len;
+}
+
+int
+MultiPoint::find_point(const Point &point) const
+{
+ for (const Point &pt : this->points)
+ if (pt == point)
+ return &pt - &this->points.front();
+ return -1; // not found
+}
+
+bool
+MultiPoint::has_boundary_point(const Point &point) const
+{
+ double dist = (point.projection_onto(*this) - point).cast<double>().norm();
+ return dist < SCALED_EPSILON;
+}
+
+BoundingBox
+MultiPoint::bounding_box() const
+{
+ return BoundingBox(this->points);
+}
+
+bool
+MultiPoint::has_duplicate_points() const
+{
+ for (size_t i = 1; i < points.size(); ++i)
+ if (points[i-1] == points[i])
+ return true;
+ return false;
+}
+
+bool
+MultiPoint::remove_duplicate_points()
+{
+ size_t j = 0;
+ for (size_t i = 1; i < points.size(); ++i) {
+ if (points[j] == points[i]) {
+ // Just increase index i.
+ } else {
+ ++ j;
+ if (j < i)
+ points[j] = points[i];
+ }
+ }
+ if (++ j < points.size()) {
+ points.erase(points.begin() + j, points.end());
+ return true;
+ }
+ return false;
+}
+
+bool
+MultiPoint::intersection(const Line& line, Point* intersection) const
+{
+ Lines lines = this->lines();
+ for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {
+ if (it->intersection(line, intersection)) return true;
+ }
+ return false;
+}
+
+bool MultiPoint::first_intersection(const Line& line, Point* intersection) const
+{
+ bool found = false;
+ double dmin = 0.;
+ for (const Line &l : this->lines()) {
+ Point ip;
+ if (l.intersection(line, &ip)) {
+ if (! found) {
+ found = true;
+ dmin = (line.a - ip).cast<double>().norm();
+ *intersection = ip;
+ } else {
+ double d = (line.a - ip).cast<double>().norm();
+ if (d < dmin) {
+ dmin = d;
+ *intersection = ip;
+ }
+ }
+ }
+ }
+ return found;
+}
+
+//FIXME This is very inefficient in term of memory use.
+// The recursive algorithm shall run in place, not allocating temporary data in each recursion.
+Points
+MultiPoint::_douglas_peucker(const Points &points, const double tolerance)
+{
+ assert(points.size() >= 2);
+ Points results;
+ double dmax = 0;
+ size_t index = 0;
+ Line full(points.front(), points.back());
+ for (Points::const_iterator it = points.begin() + 1; it != points.end(); ++it) {
+ // we use shortest distance, not perpendicular distance
+ double d = full.distance_to(*it);
+ if (d > dmax) {
+ index = it - points.begin();
+ dmax = d;
+ }
+ }
+ if (dmax >= tolerance) {
+ Points dp0;
+ dp0.reserve(index + 1);
+ dp0.insert(dp0.end(), points.begin(), points.begin() + index + 1);
+ // Recursive call.
+ Points dp1 = MultiPoint::_douglas_peucker(dp0, tolerance);
+ results.reserve(results.size() + dp1.size() - 1);
+ results.insert(results.end(), dp1.begin(), dp1.end() - 1);
+
+ dp0.clear();
+ dp0.reserve(points.size() - index);
+ dp0.insert(dp0.end(), points.begin() + index, points.end());
+ // Recursive call.
+ dp1 = MultiPoint::_douglas_peucker(dp0, tolerance);
+ results.reserve(results.size() + dp1.size());
+ results.insert(results.end(), dp1.begin(), dp1.end());
+ } else {
+ results.push_back(points.front());
+ results.push_back(points.back());
+ }
+ return results;
+}
+
+void MultiPoint3::translate(double x, double y)
+{
+ for (Vec3crd &p : points) {
+ p(0) += x;
+ p(1) += y;
+ }
+}
+
+void MultiPoint3::translate(const Point& vector)
+{
+ this->translate(vector(0), vector(1));
+}
+
+double MultiPoint3::length() const
+{
+ double len = 0.0;
+ for (const Line3& line : this->lines())
+ len += line.length();
+ return len;
+}
+
+BoundingBox3 MultiPoint3::bounding_box() const
+{
+ return BoundingBox3(points);
+}
+
+bool MultiPoint3::remove_duplicate_points()
+{
+ size_t j = 0;
+ for (size_t i = 1; i < points.size(); ++i) {
+ if (points[j] == points[i]) {
+ // Just increase index i.
+ } else {
+ ++ j;
+ if (j < i)
+ points[j] = points[i];
+ }
+ }
+
+ if (++j < points.size())
+ {
+ points.erase(points.begin() + j, points.end());
+ return true;
+ }
+
+ return false;
+}
+
+BoundingBox get_extents(const MultiPoint &mp)
+{
+ return BoundingBox(mp.points);
+}
+
+BoundingBox get_extents_rotated(const Points &points, double angle)
+{
+ BoundingBox bbox;
+ if (! points.empty()) {
+ double s = sin(angle);
+ double c = cos(angle);
+ Points::const_iterator it = points.begin();
+ double cur_x = (double)(*it)(0);
+ double cur_y = (double)(*it)(1);
+ bbox.min(0) = bbox.max(0) = (coord_t)round(c * cur_x - s * cur_y);
+ bbox.min(1) = bbox.max(1) = (coord_t)round(c * cur_y + s * cur_x);
+ for (++it; it != points.end(); ++it) {
+ double cur_x = (double)(*it)(0);
+ double cur_y = (double)(*it)(1);
+ coord_t x = (coord_t)round(c * cur_x - s * cur_y);
+ coord_t y = (coord_t)round(c * cur_y + s * cur_x);
+ bbox.min(0) = std::min(x, bbox.min(0));
+ bbox.min(1) = std::min(y, bbox.min(1));
+ bbox.max(0) = std::max(x, bbox.max(0));
+ bbox.max(1) = std::max(y, bbox.max(1));
+ }
+ bbox.defined = true;
+ }
+ return bbox;
+}
+
+BoundingBox get_extents_rotated(const MultiPoint &mp, double angle)
+{
+ return get_extents_rotated(mp.points, angle);
+}
+
+}
diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp
new file mode 100644
index 000000000..03b89df51
--- /dev/null
+++ b/src/libslic3r/MultiPoint.hpp
@@ -0,0 +1,110 @@
+#ifndef slic3r_MultiPoint_hpp_
+#define slic3r_MultiPoint_hpp_
+
+#include "libslic3r.h"
+#include <algorithm>
+#include <vector>
+#include "Line.hpp"
+#include "Point.hpp"
+
+namespace Slic3r {
+
+class BoundingBox;
+class BoundingBox3;
+
+class MultiPoint
+{
+public:
+ Points points;
+
+ operator Points() const;
+ MultiPoint() {}
+ MultiPoint(const MultiPoint &other) : points(other.points) {}
+ MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {}
+ MultiPoint(std::initializer_list<Point> list) : points(list) {}
+ explicit MultiPoint(const Points &_points) : points(_points) {}
+ MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; }
+ MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; }
+ void scale(double factor);
+ void translate(double x, double y);
+ void translate(const Point &vector);
+ void rotate(double angle) { this->rotate(cos(angle), sin(angle)); }
+ void rotate(double cos_angle, double sin_angle);
+ void rotate(double angle, const Point &center);
+ void reverse();
+ Point first_point() const;
+ virtual Point last_point() const = 0;
+ virtual Lines lines() const = 0;
+ size_t size() const { return points.size(); }
+ bool empty() const { return points.empty(); }
+ double length() const;
+ bool is_valid() const { return this->points.size() >= 2; }
+
+ int find_point(const Point &point) const;
+ bool has_boundary_point(const Point &point) const;
+ int closest_point_index(const Point &point) const {
+ int idx = -1;
+ if (! this->points.empty()) {
+ idx = 0;
+ double dist_min = (point - this->points.front()).cast<double>().norm();
+ for (int i = 1; i < int(this->points.size()); ++ i) {
+ double d = (this->points[i] - point).cast<double>().norm();
+ if (d < dist_min) {
+ dist_min = d;
+ idx = i;
+ }
+ }
+ }
+ return idx;
+ }
+ const Point* closest_point(const Point &point) const { return this->points.empty() ? nullptr : &this->points[this->closest_point_index(point)]; }
+ BoundingBox bounding_box() const;
+ // Return true if there are exact duplicates.
+ bool has_duplicate_points() const;
+ // Remove exact duplicates, return true if any duplicate has been removed.
+ bool remove_duplicate_points();
+ void append(const Point &point) { this->points.push_back(point); }
+ void append(const Points &src) { this->append(src.begin(), src.end()); }
+ void append(const Points::const_iterator &begin, const Points::const_iterator &end) { this->points.insert(this->points.end(), begin, end); }
+ void append(Points &&src)
+ {
+ if (this->points.empty()) {
+ this->points = std::move(src);
+ } else {
+ this->points.insert(this->points.end(), src.begin(), src.end());
+ src.clear();
+ }
+ }
+
+ bool intersection(const Line& line, Point* intersection) const;
+ bool first_intersection(const Line& line, Point* intersection) const;
+
+ static Points _douglas_peucker(const Points &points, const double tolerance);
+};
+
+class MultiPoint3
+{
+public:
+ Points3 points;
+
+ void append(const Vec3crd& point) { this->points.push_back(point); }
+
+ void translate(double x, double y);
+ void translate(const Point& vector);
+ virtual Lines3 lines() const = 0;
+ double length() const;
+ bool is_valid() const { return this->points.size() >= 2; }
+
+ BoundingBox3 bounding_box() const;
+
+ // Remove exact duplicates, return true if any duplicate has been removed.
+ bool remove_duplicate_points();
+};
+
+extern BoundingBox get_extents(const MultiPoint &mp);
+extern BoundingBox get_extents_rotated(const std::vector<Point> &points, double angle);
+extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle);
+
+} // namespace Slic3r
+
+#endif
diff --git a/src/libslic3r/MutablePriorityQueue.hpp b/src/libslic3r/MutablePriorityQueue.hpp
new file mode 100644
index 000000000..82e992fd6
--- /dev/null
+++ b/src/libslic3r/MutablePriorityQueue.hpp
@@ -0,0 +1,147 @@
+#ifndef slic3r_MutablePriorityQueue_hpp_
+#define slic3r_MutablePriorityQueue_hpp_
+
+#include <assert.h>
+
+template<typename T, typename IndexSetter, typename LessPredicate>
+class MutablePriorityQueue
+{
+public:
+ MutablePriorityQueue(IndexSetter &&index_setter, LessPredicate &&less_predicate) :
+ m_index_setter(std::forward<IndexSetter>(index_setter)),
+ m_less_predicate(std::forward<LessPredicate>(less_predicate))
+ {}
+ ~MutablePriorityQueue() { clear(); }
+
+ inline void clear() { m_heap.clear(); }
+ inline void reserve(size_t cnt) { m_heap.reserve(cnt); }
+ inline void push(const T &item);
+ inline void push(T &&item);
+ inline void pop();
+ inline T& top() { return m_heap.front(); }
+ inline void remove(size_t idx);
+ inline void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); }
+
+ inline size_t size() const { return m_heap.size(); }
+ inline bool empty() const { return m_heap.empty(); }
+
+protected:
+ inline void update_heap_up(size_t top, size_t bottom);
+ inline void update_heap_down(size_t top, size_t bottom);
+
+private:
+ std::vector<T> m_heap;
+ IndexSetter m_index_setter;
+ LessPredicate m_less_predicate;
+};
+
+template<typename T, typename IndexSetter, typename LessPredicate>
+MutablePriorityQueue<T, IndexSetter, LessPredicate> make_mutable_priority_queue(IndexSetter &&index_setter, LessPredicate &&less_predicate)
+{
+ return MutablePriorityQueue<T, IndexSetter, LessPredicate>(
+ std::forward<IndexSetter>(index_setter), std::forward<LessPredicate>(less_predicate));
+}
+
+template<class T, class LessPredicate, class IndexSetter>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &item)
+{
+ size_t idx = m_heap.size();
+ m_heap.emplace_back(item);
+ m_index_setter(m_heap.back(), idx);
+ update_heap_up(0, idx);
+}
+
+template<class T, class LessPredicate, class IndexSetter>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(T &&item)
+{
+ size_t idx = m_heap.size();
+ m_heap.emplace_back(std::move(item));
+ m_index_setter(m_heap.back(), idx);
+ update_heap_up(0, idx);
+}
+
+template<class T, class LessPredicate, class IndexSetter>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop()
+{
+ assert(! m_heap.empty());
+ if (m_heap.size() > 1) {
+ m_heap.front() = m_heap.back();
+ m_heap.pop_back();
+ m_index_setter(m_heap.front(), 0);
+ update_heap_down(0, m_heap.size() - 1);
+ } else
+ m_heap.clear();
+}
+
+template<class T, class LessPredicate, class IndexSetter>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::remove(size_t idx)
+{
+ assert(idx < m_heap.size());
+ if (idx + 1 == m_heap.size()) {
+ m_heap.pop_back();
+ return;
+ }
+ m_heap[idx] = m_heap.back();
+ m_index_setter(m_heap[idx], idx);
+ m_heap.pop_back();
+ update_heap_down(idx, m_heap.size() - 1);
+ update_heap_up(0, idx);
+}
+
+template<class T, class LessPredicate, class IndexSetter>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::update_heap_up(size_t top, size_t bottom)
+{
+ size_t childIdx = bottom;
+ T *child = &m_heap[childIdx];
+ for (;;) {
+ size_t parentIdx = (childIdx - 1) >> 1;
+ if (childIdx == 0 || parentIdx < top)
+ break;
+ T *parent = &m_heap[parentIdx];
+ // switch nodes
+ if (! m_less_predicate(*parent, *child)) {
+ T tmp = *parent;
+ m_index_setter(*parent, childIdx);
+ m_index_setter(*child, parentIdx);
+ m_heap[parentIdx] = *child;
+ m_heap[childIdx] = tmp;
+ }
+ // shift up
+ childIdx = parentIdx;
+ child = parent;
+ }
+}
+
+template<class T, class LessPredicate, class IndexSetter>
+inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::update_heap_down(size_t top, size_t bottom)
+{
+ size_t parentIdx = top;
+ T *parent = &m_heap[parentIdx];
+ for (;;) {
+ size_t childIdx = (parentIdx << 1) + 1;
+ if (childIdx > bottom)
+ break;
+ T *child = &m_heap[childIdx];
+ size_t child2Idx = childIdx + 1;
+ if (child2Idx <= bottom) {
+ T *child2 = &m_heap[child2Idx];
+ if (! m_less_predicate(*child, *child2)) {
+ child = child2;
+ childIdx = child2Idx;
+ }
+ }
+ if (m_less_predicate(*parent, *child))
+ return;
+ // switch nodes
+ m_index_setter(*parent, childIdx);
+ m_index_setter(*child, parentIdx);
+ T tmp = *parent;
+ m_heap[parentIdx] = *child;
+ m_heap[childIdx] = tmp;
+ // shift down
+ parentIdx = childIdx;
+ parent = child;
+ }
+}
+
+#endif /* slic3r_MutablePriorityQueue_hpp_ */
diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp
new file mode 100644
index 000000000..de8aeeb2a
--- /dev/null
+++ b/src/libslic3r/PerimeterGenerator.cpp
@@ -0,0 +1,480 @@
+#include "PerimeterGenerator.hpp"
+#include "ClipperUtils.hpp"
+#include "ExtrusionEntityCollection.hpp"
+#include <cmath>
+#include <cassert>
+
+namespace Slic3r {
+
+void PerimeterGenerator::process()
+{
+ // other perimeters
+ this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
+ coord_t perimeter_width = this->perimeter_flow.scaled_width();
+ coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
+
+ // external perimeters
+ this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
+ coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
+ coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
+ coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow);
+
+ // overhang perimeters
+ this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
+
+ // solid infill
+ coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing();
+
+ // Calculate the minimum required spacing between two adjacent traces.
+ // This should be equal to the nominal flow spacing but we experiment
+ // with some tolerance in order to avoid triggering medial axis when
+ // some squishing might work. Loops are still spaced by the entire
+ // flow spacing; this only applies to collapsing parts.
+ // For ext_min_spacing we use the ext_perimeter_spacing calculated for two adjacent
+ // external loops (which is the correct way) instead of using ext_perimeter_spacing2
+ // which is the spacing between external and internal, which is not correct
+ // and would make the collapsing (thus the details resolution) dependent on
+ // internal flow which is unrelated.
+ coord_t min_spacing = perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE);
+ coord_t ext_min_spacing = ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE);
+
+ // prepare grown lower layer slices for overhang detection
+ if (this->lower_slices != NULL && this->config->overhangs) {
+ // We consider overhang any part where the entire nozzle diameter is not supported by the
+ // lower layer, so we take lower slices and offset them by half the nozzle diameter used
+ // in the current layer
+ double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1);
+ this->_lower_slices_p = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2)));
+ }
+
+ // we need to process each island separately because we might have different
+ // extra perimeters for each one
+ for (const Surface &surface : this->slices->surfaces) {
+ // detect how many perimeters must be generated for this island
+ int loop_number = this->config->perimeters + surface.extra_perimeters - 1; // 0-indexed loops
+ ExPolygons last = union_ex(surface.expolygon.simplify_p(SCALED_RESOLUTION));
+ ExPolygons gaps;
+ if (loop_number >= 0) {
+ // In case no perimeters are to be generated, loop_number will equal to -1.
+ std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
+ std::vector<PerimeterGeneratorLoops> holes(loop_number+1); // depth => loops
+ ThickPolylines thin_walls;
+ // we loop one time more than needed in order to find gaps after the last perimeter was applied
+ for (int i = 0;; ++ i) { // outer loop is 0
+ // Calculate next onion shell of perimeters.
+ ExPolygons offsets;
+ if (i == 0) {
+ // the minimum thickness of a single loop is:
+ // ext_width/2 + ext_spacing/2 + spacing/2 + width/2
+ offsets = this->config->thin_walls ?
+ offset2_ex(
+ last,
+ -(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1),
+ +(ext_min_spacing / 2 - 1)) :
+ offset_ex(last, - ext_perimeter_width / 2);
+ // look for thin walls
+ if (this->config->thin_walls) {
+ // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
+ // (actually, something larger than that still may exist due to mitering or other causes)
+ coord_t min_width = scale_(this->ext_perimeter_flow.nozzle_diameter / 3);
+ ExPolygons expp = offset2_ex(
+ // medial axis requires non-overlapping geometry
+ diff_ex(to_polygons(last),
+ offset(offsets, ext_perimeter_width / 2),
+ true),
+ - min_width / 2, min_width / 2);
+ // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
+ for (ExPolygon &ex : expp)
+ ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
+ }
+ } else {
+ //FIXME Is this offset correct if the line width of the inner perimeters differs
+ // from the line width of the infill?
+ coord_t distance = (i == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
+ offsets = this->config->thin_walls ?
+ // This path will ensure, that the perimeters do not overfill, as in
+ // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters
+ // excessively, creating gaps, which then need to be filled in by the not very
+ // reliable gap fill algorithm.
+ // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
+ // the original.
+ offset2_ex(last,
+ - (distance + min_spacing / 2 - 1),
+ min_spacing / 2 - 1) :
+ // If "detect thin walls" is not enabled, this paths will be entered, which
+ // leads to overflows, as in prusa3d/Slic3r GH #32
+ offset_ex(last, - distance);
+ // look for gaps
+ if (this->config->gap_fill_speed.value > 0 && this->config->fill_density.value > 0)
+ // not using safety offset here would "detect" very narrow gaps
+ // (but still long enough to escape the area threshold) that gap fill
+ // won't be able to fill but we'd still remove from infill area
+ append(gaps, diff_ex(
+ offset(last, -0.5 * distance),
+ offset(offsets, 0.5 * distance + 10))); // safety offset
+ }
+ if (offsets.empty()) {
+ // Store the number of loops actually generated.
+ loop_number = i - 1;
+ // No region left to be filled in.
+ last.clear();
+ break;
+ } else if (i > loop_number) {
+ // If i > loop_number, we were looking just for gaps.
+ break;
+ }
+ for (const ExPolygon &expolygon : offsets) {
+ contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true));
+ if (! expolygon.holes.empty()) {
+ holes[i].reserve(holes[i].size() + expolygon.holes.size());
+ for (const Polygon &hole : expolygon.holes)
+ holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false));
+ }
+ }
+ last = std::move(offsets);
+ }
+
+ // nest loops: holes first
+ for (int d = 0; d <= loop_number; ++ d) {
+ PerimeterGeneratorLoops &holes_d = holes[d];
+ // loop through all holes having depth == d
+ for (int i = 0; i < (int)holes_d.size(); ++ i) {
+ const PerimeterGeneratorLoop &loop = holes_d[i];
+ // find the hole loop that contains this one, if any
+ for (int t = d + 1; t <= loop_number; ++ t) {
+ for (int j = 0; j < (int)holes[t].size(); ++ j) {
+ PerimeterGeneratorLoop &candidate_parent = holes[t][j];
+ if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
+ candidate_parent.children.push_back(loop);
+ holes_d.erase(holes_d.begin() + i);
+ -- i;
+ goto NEXT_LOOP;
+ }
+ }
+ }
+ // if no hole contains this hole, find the contour loop that contains it
+ for (int t = loop_number; t >= 0; -- t) {
+ for (int j = 0; j < (int)contours[t].size(); ++ j) {
+ PerimeterGeneratorLoop &candidate_parent = contours[t][j];
+ if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
+ candidate_parent.children.push_back(loop);
+ holes_d.erase(holes_d.begin() + i);
+ -- i;
+ goto NEXT_LOOP;
+ }
+ }
+ }
+ NEXT_LOOP: ;
+ }
+ }
+ // nest contour loops
+ for (int d = loop_number; d >= 1; -- d) {
+ PerimeterGeneratorLoops &contours_d = contours[d];
+ // loop through all contours having depth == d
+ for (int i = 0; i < (int)contours_d.size(); ++ i) {
+ const PerimeterGeneratorLoop &loop = contours_d[i];
+ // find the contour loop that contains it
+ for (int t = d - 1; t >= 0; -- t) {
+ for (int j = 0; j < contours[t].size(); ++ j) {
+ PerimeterGeneratorLoop &candidate_parent = contours[t][j];
+ if (candidate_parent.polygon.contains(loop.polygon.first_point())) {
+ candidate_parent.children.push_back(loop);
+ contours_d.erase(contours_d.begin() + i);
+ -- i;
+ goto NEXT_CONTOUR;
+ }
+ }
+ }
+ NEXT_CONTOUR: ;
+ }
+ }
+ // at this point, all loops should be in contours[0]
+ ExtrusionEntityCollection entities = this->_traverse_loops(contours.front(), thin_walls);
+ // if brim will be printed, reverse the order of perimeters so that
+ // we continue inwards after having finished the brim
+ // TODO: add test for perimeter order
+ if (this->config->external_perimeters_first ||
+ (this->layer_id == 0 && this->print_config->brim_width.value > 0))
+ entities.reverse();
+ // append perimeters for this slice as a collection
+ if (! entities.empty())
+ this->loops->append(entities);
+ } // for each loop of an island
+
+ // fill gaps
+ if (! gaps.empty()) {
+ // collapse
+ double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE);
+ double max = 2. * perimeter_spacing;
+ ExPolygons gaps_ex = diff_ex(
+ //FIXME offset2 would be enough and cheaper.
+ offset2_ex(gaps, -min/2, +min/2),
+ offset2_ex(gaps, -max/2, +max/2),
+ true);
+ ThickPolylines polylines;
+ for (const ExPolygon &ex : gaps_ex)
+ ex.medial_axis(max, min, &polylines);
+ if (! polylines.empty()) {
+ ExtrusionEntityCollection gap_fill = this->_variable_width(polylines,
+ erGapFill, this->solid_infill_flow);
+ this->gap_fill->append(gap_fill.entities);
+ /* Make sure we don't infill narrow parts that are already gap-filled
+ (we only consider this surface's gaps to reduce the diff() complexity).
+ Growing actual extrusions ensures that gaps not filled by medial axis
+ are not subtracted from fill surfaces (they might be too short gaps
+ that medial axis skips but infill might join with other infill regions
+ and use zigzag). */
+ //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
+ // therefore it may cover the area, but no the volume.
+ last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f));
+ }
+ }
+
+ // create one more offset to be used as boundary for fill
+ // we offset by half the perimeter spacing (to get to the actual infill boundary)
+ // and then we offset back and forth by half the infill spacing to only consider the
+ // non-collapsing regions
+ coord_t inset =
+ (loop_number < 0) ? 0 :
+ (loop_number == 0) ?
+ // one loop
+ ext_perimeter_spacing / 2 :
+ // two or more loops?
+ perimeter_spacing / 2;
+ // only apply infill overlap if we actually have one perimeter
+ if (inset > 0)
+ inset -= scale_(this->config->get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2)));
+ // simplify infill contours according to resolution
+ Polygons pp;
+ for (ExPolygon &ex : last)
+ ex.simplify_p(SCALED_RESOLUTION, &pp);
+ // collapse too narrow infill areas
+ coord_t min_perimeter_infill_spacing = solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE);
+ // append infill areas to fill_surfaces
+ this->fill_surfaces->append(
+ offset2_ex(
+ union_ex(pp),
+ - inset - min_perimeter_infill_spacing / 2,
+ min_perimeter_infill_spacing / 2),
+ stInternal);
+ } // for each island
+}
+
+ExtrusionEntityCollection PerimeterGenerator::_traverse_loops(
+ const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const
+{
+ // loops is an arrayref of ::Loop objects
+ // turn each one into an ExtrusionLoop object
+ ExtrusionEntityCollection coll;
+ for (PerimeterGeneratorLoops::const_iterator loop = loops.begin();
+ loop != loops.end(); ++loop) {
+ bool is_external = loop->is_external();
+
+ ExtrusionRole role;
+ ExtrusionLoopRole loop_role;
+ role = is_external ? erExternalPerimeter : erPerimeter;
+ if (loop->is_internal_contour()) {
+ // Note that we set loop role to ContourInternalPerimeter
+ // also when loop is both internal and external (i.e.
+ // there's only one contour loop).
+ loop_role = elrContourInternalPerimeter;
+ } else {
+ loop_role = elrDefault;
+ }
+
+ // detect overhanging/bridging perimeters
+ ExtrusionPaths paths;
+ if (this->config->overhangs && this->layer_id > 0
+ && !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) {
+ // get non-overhang paths by intersecting this loop with the grown lower slices
+ extrusion_paths_append(
+ paths,
+ intersection_pl(loop->polygon, this->_lower_slices_p),
+ role,
+ is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm,
+ is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width,
+ this->layer_height);
+
+ // get overhang paths by checking what parts of this loop fall
+ // outside the grown lower slices (thus where the distance between
+ // the loop centerline and original lower slices is >= half nozzle diameter
+ extrusion_paths_append(
+ paths,
+ diff_pl(loop->polygon, this->_lower_slices_p),
+ erOverhangPerimeter,
+ this->_mm3_per_mm_overhang,
+ this->overhang_flow.width,
+ this->overhang_flow.height);
+
+ // reapply the nearest point search for starting point
+ // We allow polyline reversal because Clipper may have randomly
+ // reversed polylines during clipping.
+ paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path();
+ } else {
+ ExtrusionPath path(role);
+ path.polyline = loop->polygon.split_at_first_point();
+ path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm;
+ path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width;
+ path.height = this->layer_height;
+ paths.push_back(path);
+ }
+
+ coll.append(ExtrusionLoop(paths, loop_role));
+ }
+
+ // append thin walls to the nearest-neighbor search (only for first iteration)
+ if (!thin_walls.empty()) {
+ ExtrusionEntityCollection tw = this->_variable_width
+ (thin_walls, erExternalPerimeter, this->ext_perimeter_flow);
+
+ coll.append(tw.entities);
+ thin_walls.clear();
+ }
+
+ // sort entities into a new collection using a nearest-neighbor search,
+ // preserving the original indices which are useful for detecting thin walls
+ ExtrusionEntityCollection sorted_coll;
+ coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices);
+
+ // traverse children and build the final collection
+ ExtrusionEntityCollection entities;
+ for (std::vector<size_t>::const_iterator idx = sorted_coll.orig_indices.begin();
+ idx != sorted_coll.orig_indices.end();
+ ++idx) {
+
+ if (*idx >= loops.size()) {
+ // this is a thin wall
+ // let's get it from the sorted collection as it might have been reversed
+ size_t i = idx - sorted_coll.orig_indices.begin();
+ entities.append(*sorted_coll.entities[i]);
+ } else {
+ const PerimeterGeneratorLoop &loop = loops[*idx];
+ ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[*idx]);
+
+ ExtrusionEntityCollection children = this->_traverse_loops(loop.children, thin_walls);
+ if (loop.is_contour) {
+ eloop.make_counter_clockwise();
+ entities.append(children.entities);
+ entities.append(eloop);
+ } else {
+ eloop.make_clockwise();
+ entities.append(eloop);
+ entities.append(children.entities);
+ }
+ }
+ }
+ return entities;
+}
+
+static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance)
+{
+ ExtrusionPaths paths;
+ ExtrusionPath path(role);
+ ThickLines lines = thick_polyline.thicklines();
+
+ for (int i = 0; i < (int)lines.size(); ++i) {
+ const ThickLine& line = lines[i];
+
+ const coordf_t line_len = line.length();
+ if (line_len < SCALED_EPSILON) continue;
+
+ double thickness_delta = fabs(line.a_width - line.b_width);
+ if (thickness_delta > tolerance) {
+ const unsigned short segments = ceil(thickness_delta / tolerance);
+ const coordf_t seg_len = line_len / segments;
+ Points pp;
+ std::vector<coordf_t> width;
+ {
+ pp.push_back(line.a);
+ width.push_back(line.a_width);
+ for (size_t j = 1; j < segments; ++j) {
+ pp.push_back((line.a.cast<double>() + (line.b - line.a).cast<double>().normalized() * (j * seg_len)).cast<coord_t>());
+
+ coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len;
+ width.push_back(w);
+ width.push_back(w);
+ }
+ pp.push_back(line.b);
+ width.push_back(line.b_width);
+
+ assert(pp.size() == segments + 1);
+ assert(width.size() == segments*2);
+ }
+
+ // delete this line and insert new ones
+ lines.erase(lines.begin() + i);
+ for (size_t j = 0; j < segments; ++j) {
+ ThickLine new_line(pp[j], pp[j+1]);
+ new_line.a_width = width[2*j];
+ new_line.b_width = width[2*j+1];
+ lines.insert(lines.begin() + i + j, new_line);
+ }
+
+ -- i;
+ continue;
+ }
+
+ const double w = fmax(line.a_width, line.b_width);
+ if (path.polyline.points.empty()) {
+ path.polyline.append(line.a);
+ path.polyline.append(line.b);
+ // Convert from spacing to extrusion width based on the extrusion model
+ // of a square extrusion ended with semi circles.
+ flow.width = unscale<float>(w) + flow.height * (1. - 0.25 * PI);
+ #ifdef SLIC3R_DEBUG
+ printf(" filling %f gap\n", flow.width);
+ #endif
+ path.mm3_per_mm = flow.mm3_per_mm();
+ path.width = flow.width;
+ path.height = flow.height;
+ } else {
+ thickness_delta = fabs(scale_(flow.width) - w);
+ if (thickness_delta <= tolerance) {
+ // the width difference between this line and the current flow width is
+ // within the accepted tolerance
+ path.polyline.append(line.b);
+ } else {
+ // we need to initialize a new line
+ paths.emplace_back(std::move(path));
+ path = ExtrusionPath(role);
+ -- i;
+ }
+ }
+ }
+ if (path.polyline.is_valid())
+ paths.emplace_back(std::move(path));
+ return paths;
+}
+
+ExtrusionEntityCollection PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const
+{
+ // This value determines granularity of adaptive width, as G-code does not allow
+ // variable extrusion within a single move; this value shall only affect the amount
+ // of segments, and any pruning shall be performed before we apply this tolerance.
+ ExtrusionEntityCollection coll;
+ const double tolerance = scale_(0.05);
+ for (const ThickPolyline &p : polylines) {
+ ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
+ // Append paths to collection.
+ if (! paths.empty()) {
+ if (paths.front().first_point() == paths.back().last_point())
+ coll.append(ExtrusionLoop(std::move(paths)));
+ else
+ coll.append(std::move(paths));
+ }
+ }
+ return coll;
+}
+
+bool PerimeterGeneratorLoop::is_internal_contour() const
+{
+ // An internal contour is a contour containing no other contours
+ if (! this->is_contour)
+ return false;
+ for (const PerimeterGeneratorLoop &loop : this->children)
+ if (loop.is_contour)
+ return false;
+ return true;
+}
+
+}
diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp
new file mode 100644
index 000000000..44af8c8be
--- /dev/null
+++ b/src/libslic3r/PerimeterGenerator.hpp
@@ -0,0 +1,92 @@
+#ifndef slic3r_PerimeterGenerator_hpp_
+#define slic3r_PerimeterGenerator_hpp_
+
+#include "libslic3r.h"
+#include <vector>
+#include "ExPolygonCollection.hpp"
+#include "Flow.hpp"
+#include "Polygon.hpp"
+#include "PrintConfig.hpp"
+#include "SurfaceCollection.hpp"
+
+namespace Slic3r {
+
+// Hierarchy of perimeters.
+class PerimeterGeneratorLoop {
+public:
+ // Polygon of this contour.
+ Polygon polygon;
+ // Is it a contour or a hole?
+ // Contours are CCW oriented, holes are CW oriented.
+ bool is_contour;
+ // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
+ unsigned short depth;
+ // Children contour, may be both CCW and CW oriented (outer contours or holes).
+ std::vector<PerimeterGeneratorLoop> children;
+
+ PerimeterGeneratorLoop(Polygon polygon, unsigned short depth, bool is_contour) :
+ polygon(polygon), is_contour(is_contour), depth(depth) {}
+ // External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
+ bool is_external() const { return this->depth == 0; }
+ // An island, which may have holes, but it does not have another internal island.
+ bool is_internal_contour() const;
+};
+
+typedef std::vector<PerimeterGeneratorLoop> PerimeterGeneratorLoops;
+
+class PerimeterGenerator {
+public:
+ // Inputs:
+ const SurfaceCollection *slices;
+ const ExPolygonCollection *lower_slices;
+ double layer_height;
+ int layer_id;
+ Flow perimeter_flow;
+ Flow ext_perimeter_flow;
+ Flow overhang_flow;
+ Flow solid_infill_flow;
+ const PrintRegionConfig *config;
+ const PrintObjectConfig *object_config;
+ const PrintConfig *print_config;
+ // Outputs:
+ ExtrusionEntityCollection *loops;
+ ExtrusionEntityCollection *gap_fill;
+ SurfaceCollection *fill_surfaces;
+
+ PerimeterGenerator(
+ // Input:
+ const SurfaceCollection* slices,
+ double layer_height,
+ Flow flow,
+ const PrintRegionConfig* config,
+ const PrintObjectConfig* object_config,
+ const PrintConfig* print_config,
+ // Output:
+ // Loops with the external thin walls
+ ExtrusionEntityCollection* loops,
+ // Gaps without the thin walls
+ ExtrusionEntityCollection* gap_fill,
+ // Infills without the gap fills
+ SurfaceCollection* fill_surfaces)
+ : slices(slices), lower_slices(NULL), layer_height(layer_height),
+ layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow),
+ overhang_flow(flow), solid_infill_flow(flow),
+ config(config), object_config(object_config), print_config(print_config),
+ loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces),
+ _ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1)
+ {};
+ void process();
+
+private:
+ double _ext_mm3_per_mm;
+ double _mm3_per_mm;
+ double _mm3_per_mm_overhang;
+ Polygons _lower_slices_p;
+
+ ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const;
+ ExtrusionEntityCollection _variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const;
+};
+
+}
+
+#endif
diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp
new file mode 100644
index 000000000..cc6f1c75e
--- /dev/null
+++ b/src/libslic3r/PlaceholderParser.cpp
@@ -0,0 +1,1216 @@
+#include "PlaceholderParser.hpp"
+#include <cstring>
+#include <ctime>
+#include <iomanip>
+#include <sstream>
+#include <map>
+#ifdef _MSC_VER
+ #include <stdlib.h> // provides **_environ
+#else
+ #include <unistd.h> // provides **environ
+#endif
+
+#ifdef __APPLE__
+#include <crt_externs.h>
+#undef environ
+#define environ (*_NSGetEnviron())
+#else
+ #ifdef _MSC_VER
+ #define environ _environ
+ #else
+ extern char **environ;
+ #endif
+#endif
+
+#include <boost/algorithm/string.hpp>
+
+// Spirit v2.5 allows you to suppress automatic generation
+// of predefined terminals to speed up complation. With
+// BOOST_SPIRIT_NO_PREDEFINED_TERMINALS defined, you are
+// responsible in creating instances of the terminals that
+// you need (e.g. see qi::uint_type uint_ below).
+//#define BOOST_SPIRIT_NO_PREDEFINED_TERMINALS
+
+#define BOOST_RESULT_OF_USE_DECLTYPE
+#define BOOST_SPIRIT_USE_PHOENIX_V3
+#include <boost/config/warning_disable.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/spirit/include/qi.hpp>
+#include <boost/spirit/include/qi_lit.hpp>
+#include <boost/spirit/include/phoenix_core.hpp>
+#include <boost/spirit/include/phoenix_operator.hpp>
+#include <boost/spirit/include/phoenix_fusion.hpp>
+#include <boost/spirit/include/phoenix_stl.hpp>
+#include <boost/spirit/include/phoenix_object.hpp>
+#include <boost/fusion/include/adapt_struct.hpp>
+#include <boost/spirit/repository/include/qi_distinct.hpp>
+#include <boost/spirit/repository/include/qi_iter_pos.hpp>
+#include <boost/variant/recursive_variant.hpp>
+#include <boost/phoenix/bind/bind_function.hpp>
+
+#include <iostream>
+#include <string>
+
+// #define USE_CPP11_REGEX
+#ifdef USE_CPP11_REGEX
+ #include <regex>
+ #define SLIC3R_REGEX_NAMESPACE std
+#else /* USE_CPP11_REGEX */
+ #include <boost/regex.hpp>
+ #define SLIC3R_REGEX_NAMESPACE boost
+#endif /* USE_CPP11_REGEX */
+
+namespace Slic3r {
+
+PlaceholderParser::PlaceholderParser()
+{
+ this->set("version", std::string(SLIC3R_VERSION));
+ this->apply_env_variables();
+ this->update_timestamp();
+}
+
+void PlaceholderParser::update_timestamp(DynamicConfig &config)
+{
+ time_t rawtime;
+ time(&rawtime);
+ struct tm* timeinfo = localtime(&rawtime);
+
+ {
+ std::ostringstream ss;
+ ss << (1900 + timeinfo->tm_year);
+ ss << std::setw(2) << std::setfill('0') << (1 + timeinfo->tm_mon);
+ ss << std::setw(2) << std::setfill('0') << timeinfo->tm_mday;
+ ss << "-";
+ ss << std::setw(2) << std::setfill('0') << timeinfo->tm_hour;
+ ss << std::setw(2) << std::setfill('0') << timeinfo->tm_min;
+ ss << std::setw(2) << std::setfill('0') << timeinfo->tm_sec;
+ config.set_key_value("timestamp", new ConfigOptionString(ss.str()));
+ }
+ config.set_key_value("year", new ConfigOptionInt(1900 + timeinfo->tm_year));
+ config.set_key_value("month", new ConfigOptionInt(1 + timeinfo->tm_mon));
+ config.set_key_value("day", new ConfigOptionInt(timeinfo->tm_mday));
+ config.set_key_value("hour", new ConfigOptionInt(timeinfo->tm_hour));
+ config.set_key_value("minute", new ConfigOptionInt(timeinfo->tm_min));
+ config.set_key_value("second", new ConfigOptionInt(timeinfo->tm_sec));
+}
+
+// Scalar configuration values are stored into m_single,
+// vector configuration values are stored into m_multiple.
+// All vector configuration values stored into the PlaceholderParser
+// are expected to be addressed by the extruder ID, therefore
+// if a vector configuration value is addressed without an index,
+// a current extruder ID is used.
+void PlaceholderParser::apply_config(const DynamicPrintConfig &rhs)
+{
+ const ConfigDef *def = rhs.def();
+ for (const t_config_option_key &opt_key : rhs.keys()) {
+ const ConfigOptionDef *opt_def = def->get(opt_key);
+ if ((opt_def->multiline && boost::ends_with(opt_key, "_gcode")) || opt_key == "post_process")
+ continue;
+ const ConfigOption *opt = rhs.option(opt_key);
+ // Store a copy of the config option.
+ // Convert FloatOrPercent values to floats first.
+ //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.
+ this->set(opt_key, (opt->type() == coFloatOrPercent) ?
+ new ConfigOptionFloat(rhs.get_abs_value(opt_key)) :
+ opt->clone());
+ }
+}
+
+void PlaceholderParser::apply_env_variables()
+{
+ for (char** env = environ; *env; ++ env) {
+ if (strncmp(*env, "SLIC3R_", 7) == 0) {
+ std::stringstream ss(*env);
+ std::string key, value;
+ std::getline(ss, key, '=');
+ ss >> value;
+ this->set(key, value);
+ }
+ }
+}
+
+namespace spirit = boost::spirit;
+namespace qi = boost::spirit::qi;
+namespace px = boost::phoenix;
+
+namespace client
+{
+ template<typename Iterator>
+ struct OptWithPos {
+ OptWithPos() {}
+ OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range<Iterator> it_range) : opt(opt), it_range(it_range) {}
+ ConfigOptionConstPtr opt = nullptr;
+ boost::iterator_range<Iterator> it_range;
+ };
+
+ template<typename ITERATOR>
+ std::ostream& operator<<(std::ostream& os, OptWithPos<ITERATOR> const& opt)
+ {
+ os << std::string(opt.it_range.begin(), opt.it_range.end());
+ return os;
+ }
+
+ template<typename Iterator>
+ struct expr
+ {
+ expr() : type(TYPE_EMPTY) {}
+ explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; }
+ explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_BOOL), it_range(it_begin, it_end) { data.b = b; }
+ explicit expr(int i) : type(TYPE_INT) { data.i = i; }
+ explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_INT), it_range(it_begin, it_end) { data.i = i; }
+ explicit expr(double d) : type(TYPE_DOUBLE) { data.d = d; }
+ explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; }
+ explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); }
+ explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); }
+ explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) :
+ type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); }
+ expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range)
+ { if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); }
+ explicit expr(expr &&rhs) : type(rhs.type), it_range(rhs.it_range)
+ { data.set(rhs.data); rhs.type = TYPE_EMPTY; }
+ explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : type(rhs.type), it_range(it_begin, it_end)
+ { data.set(rhs.data); rhs.type = TYPE_EMPTY; }
+ ~expr() { this->reset(); }
+
+ expr &operator=(const expr &rhs)
+ {
+ this->type = rhs.type;
+ this->it_range = rhs.it_range;
+ if (rhs.type == TYPE_STRING)
+ this->data.s = new std::string(*rhs.data.s);
+ else
+ this->data.set(rhs.data);
+ return *this;
+ }
+
+ expr &operator=(expr &&rhs)
+ {
+ type = rhs.type;
+ this->it_range = rhs.it_range;
+ data.set(rhs.data);
+ rhs.type = TYPE_EMPTY;
+ return *this;
+ }
+
+ void reset()
+ {
+ if (this->type == TYPE_STRING)
+ delete data.s;
+ this->type = TYPE_EMPTY;
+ }
+
+ bool& b() { return data.b; }
+ bool b() const { return data.b; }
+ void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; }
+ int& i() { return data.i; }
+ int i() const { return data.i; }
+ void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; }
+ int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); }
+ double& d() { return data.d; }
+ double d() const { return data.d; }
+ void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; }
+ double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); }
+ std::string& s() { return *data.s; }
+ const std::string& s() const { return *data.s; }
+ void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; }
+ void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; }
+
+ std::string to_string() const
+ {
+ std::string out;
+ switch (type) {
+ case TYPE_BOOL: out = boost::to_string(data.b); break;
+ case TYPE_INT: out = boost::to_string(data.i); break;
+ case TYPE_DOUBLE: out = boost::to_string(data.d); break;
+ case TYPE_STRING: out = *data.s; break;
+ default: break;
+ }
+ return out;
+ }
+
+ union Data {
+ // Raw image of the other data members.
+ // The C++ compiler will consider a possible aliasing of char* with any other union member,
+ // therefore copying the raw data is safe.
+ char raw[8];
+ bool b;
+ int i;
+ double d;
+ std::string *s;
+
+ // Copy the largest member variable through char*, which will alias with all other union members by default.
+ void set(const Data &rhs) { memcpy(this->raw, rhs.raw, sizeof(rhs.raw)); }
+ } data;
+
+ enum Type {
+ TYPE_EMPTY = 0,
+ TYPE_BOOL,
+ TYPE_INT,
+ TYPE_DOUBLE,
+ TYPE_STRING,
+ };
+
+ Type type;
+
+ // Range of input iterators covering this expression.
+ // Used for throwing parse exceptions.
+ boost::iterator_range<Iterator> it_range;
+
+ expr unary_minus(const Iterator start_pos) const
+ {
+ switch (this->type) {
+ case TYPE_INT :
+ return expr<Iterator>(- this->i(), start_pos, this->it_range.end());
+ case TYPE_DOUBLE:
+ return expr<Iterator>(- this->d(), start_pos, this->it_range.end());
+ default:
+ this->throw_exception("Cannot apply unary minus operator.");
+ }
+ assert(false);
+ // Suppress compiler warnings.
+ return expr();
+ }
+
+ expr unary_not(const Iterator start_pos) const
+ {
+ switch (this->type) {
+ case TYPE_BOOL :
+ return expr<Iterator>(! this->b(), start_pos, this->it_range.end());
+ default:
+ this->throw_exception("Cannot apply a not operator.");
+ }
+ assert(false);
+ // Suppress compiler warnings.
+ return expr();
+ }
+
+ expr &operator+=(const expr &rhs)
+ {
+ if (this->type == TYPE_STRING) {
+ // Convert the right hand side to string and append.
+ *this->data.s += rhs.to_string();
+ } else if (rhs.type == TYPE_STRING) {
+ // Conver the left hand side to string, append rhs.
+ this->data.s = new std::string(this->to_string() + rhs.s());
+ this->type = TYPE_STRING;
+ } else {
+ const char *err_msg = "Cannot add non-numeric types.";
+ this->throw_if_not_numeric(err_msg);
+ rhs.throw_if_not_numeric(err_msg);
+ if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
+ double d = this->as_d() + rhs.as_d();
+ this->data.d = d;
+ this->type = TYPE_DOUBLE;
+ } else
+ this->data.i += rhs.i();
+ }
+ this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
+ return *this;
+ }
+
+ expr &operator-=(const expr &rhs)
+ {
+ const char *err_msg = "Cannot subtract non-numeric types.";
+ this->throw_if_not_numeric(err_msg);
+ rhs.throw_if_not_numeric(err_msg);
+ if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
+ double d = this->as_d() - rhs.as_d();
+ this->data.d = d;
+ this->type = TYPE_DOUBLE;
+ } else
+ this->data.i -= rhs.i();
+ this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
+ return *this;
+ }
+
+ expr &operator*=(const expr &rhs)
+ {
+ const char *err_msg = "Cannot multiply with non-numeric type.";
+ this->throw_if_not_numeric(err_msg);
+ rhs.throw_if_not_numeric(err_msg);
+ if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
+ double d = this->as_d() * rhs.as_d();
+ this->data.d = d;
+ this->type = TYPE_DOUBLE;
+ } else
+ this->data.i *= rhs.i();
+ this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
+ return *this;
+ }
+
+ expr &operator/=(const expr &rhs)
+ {
+ this->throw_if_not_numeric("Cannot divide a non-numeric type.");
+ rhs.throw_if_not_numeric("Cannot divide with a non-numeric type.");
+ if ((this->type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.))
+ rhs.throw_exception("Division by zero");
+ if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
+ double d = this->as_d() / rhs.as_d();
+ this->data.d = d;
+ this->type = TYPE_DOUBLE;
+ } else
+ this->data.i /= rhs.i();
+ this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
+ return *this;
+ }
+
+ static void to_string2(expr &self, std::string &out)
+ {
+ out = self.to_string();
+ }
+
+ static void evaluate_boolean(expr &self, bool &out)
+ {
+ if (self.type != TYPE_BOOL)
+ self.throw_exception("Not a boolean expression");
+ out = self.b();
+ }
+
+ static void evaluate_boolean_to_string(expr &self, std::string &out)
+ {
+ if (self.type != TYPE_BOOL)
+ self.throw_exception("Not a boolean expression");
+ out = self.b() ? "true" : "false";
+ }
+
+ // Is lhs==rhs? Store the result into lhs.
+ static void compare_op(expr &lhs, expr &rhs, char op, bool invert)
+ {
+ bool value = false;
+ if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) &&
+ (rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) {
+ // Both types are numeric.
+ switch (op) {
+ case '=':
+ value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
+ (std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i());
+ break;
+ case '<':
+ value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
+ (lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i());
+ break;
+ case '>':
+ default:
+ value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
+ (lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i());
+ break;
+ }
+ } else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
+ // Both type are bool.
+ if (op != '=')
+ boost::throw_exception(qi::expectation_failure<Iterator>(
+ lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
+ value = lhs.b() == rhs.b();
+ } else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) {
+ // One type is string, the other could be converted to string.
+ value = (op == '=') ? (lhs.to_string() == rhs.to_string()) :
+ (op == '<') ? (lhs.to_string() < rhs.to_string()) : (lhs.to_string() > rhs.to_string());
+ } else {
+ boost::throw_exception(qi::expectation_failure<Iterator>(
+ lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
+ }
+ lhs.type = TYPE_BOOL;
+ lhs.data.b = invert ? ! value : value;
+ }
+ // Compare operators, store the result into lhs.
+ static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', false); }
+ static void not_equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', true ); }
+ static void lower (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '<', false); }
+ static void greater (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '>', false); }
+ static void leq (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '>', true ); }
+ static void geq (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '<', true ); }
+
+ enum Function2ParamsType {
+ FUNCTION_MIN,
+ FUNCTION_MAX,
+ };
+ // Store the result into param1.
+ static void function_2params(expr &param1, expr &param2, Function2ParamsType fun)
+ {
+ const char *err_msg = "Not a numeric type.";
+ param1.throw_if_not_numeric(err_msg);
+ param2.throw_if_not_numeric(err_msg);
+ if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) {
+ double d = 0.;
+ switch (fun) {
+ case FUNCTION_MIN: d = std::min(param1.as_d(), param2.as_d()); break;
+ case FUNCTION_MAX: d = std::max(param1.as_d(), param2.as_d()); break;
+ default: param1.throw_exception("Internal error: invalid function");
+ }
+ param1.data.d = d;
+ param1.type = TYPE_DOUBLE;
+ } else {
+ int i = 0.;
+ switch (fun) {
+ case FUNCTION_MIN: i = std::min(param1.as_i(), param2.as_i()); break;
+ case FUNCTION_MAX: i = std::max(param1.as_i(), param2.as_i()); break;
+ default: param1.throw_exception("Internal error: invalid function");
+ }
+ param1.data.i = i;
+ param1.type = TYPE_INT;
+ }
+ }
+ // Store the result into param1.
+ static void min(expr &param1, expr &param2) { function_2params(param1, param2, FUNCTION_MIN); }
+ static void max(expr &param1, expr &param2) { function_2params(param1, param2, FUNCTION_MAX); }
+
+ static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op)
+ {
+ const std::string *subject = nullptr;
+ const std::string *mask = nullptr;
+ if (lhs.type == TYPE_STRING) {
+ // One type is string, the other could be converted to string.
+ subject = &lhs.s();
+ } else {
+ lhs.throw_exception("Left hand side of a regex match must be a string.");
+ }
+ try {
+ std::string pattern(++ rhs.begin(), -- rhs.end());
+ bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern));
+ if (op == '!')
+ result = ! result;
+ lhs.reset();
+ lhs.type = TYPE_BOOL;
+ lhs.data.b = result;
+ } catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) {
+ // Syntax error in the regular expression
+ boost::throw_exception(qi::expectation_failure<Iterator>(
+ rhs.begin(), rhs.end(), spirit::info(std::string("*Regular expression compilation failed: ") + ex.what())));
+ }
+ }
+
+ static void regex_matches (expr &lhs, boost::iterator_range<Iterator> &rhs) { return regex_op(lhs, rhs, '='); }
+ static void regex_doesnt_match(expr &lhs, boost::iterator_range<Iterator> &rhs) { return regex_op(lhs, rhs, '!'); }
+
+ static void logical_op(expr &lhs, expr &rhs, char op)
+ {
+ bool value = false;
+ if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
+ value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b());
+ } else {
+ boost::throw_exception(qi::expectation_failure<Iterator>(
+ lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot apply logical operation to non-boolean operators.")));
+ }
+ lhs.type = TYPE_BOOL;
+ lhs.data.b = value;
+ }
+ static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); }
+ static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); }
+
+ static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2)
+ {
+ bool value = false;
+ if (lhs.type != TYPE_BOOL)
+ lhs.throw_exception("Not a boolean expression");
+ if (lhs.b())
+ lhs = std::move(rhs1);
+ else
+ lhs = std::move(rhs2);
+ }
+
+ static void set_if(bool &cond, bool &not_yet_consumed, std::string &str_in, std::string &str_out)
+ {
+ if (cond && not_yet_consumed) {
+ str_out = str_in;
+ not_yet_consumed = false;
+ }
+ }
+
+ void throw_exception(const char *message) const
+ {
+ boost::throw_exception(qi::expectation_failure<Iterator>(
+ this->it_range.begin(), this->it_range.end(), spirit::info(std::string("*") + message)));
+ }
+
+ void throw_if_not_numeric(const char *message) const
+ {
+ if (this->type != TYPE_INT && this->type != TYPE_DOUBLE)
+ this->throw_exception(message);
+ }
+ };
+
+ template<typename ITERATOR>
+ std::ostream& operator<<(std::ostream &os, const expr<ITERATOR> &expression)
+ {
+ typedef expr<ITERATOR> Expr;
+ os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - ";
+ switch (expression.type) {
+ case Expr::TYPE_EMPTY: os << "empty"; break;
+ case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break;
+ case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; break;
+ case Expr::TYPE_DOUBLE: os << "double (" << expression.d() << ")"; break;
+ case Expr::TYPE_STRING: os << "string (" << expression.s() << ")"; break;
+ default: os << "unknown";
+ };
+ return os;
+ }
+
+ struct MyContext {
+ const DynamicConfig *config = nullptr;
+ const DynamicConfig *config_override = nullptr;
+ size_t current_extruder_id = 0;
+ // If false, the macro_processor will evaluate a full macro.
+ // If true, the macro processor will evaluate just a boolean condition using the full expressive power of the macro processor.
+ bool just_boolean_expression = false;
+ std::string error_message;
+
+ // Table to translate symbol tag to a human readable error message.
+ static std::map<std::string, std::string> tag_to_error_message;
+
+ static void evaluate_full_macro(const MyContext *ctx, bool &result) { result = ! ctx->just_boolean_expression; }
+
+ const ConfigOption* resolve_symbol(const std::string &opt_key) const
+ {
+ const ConfigOption *opt = nullptr;
+ if (config_override != nullptr)
+ opt = config_override->option(opt_key);
+ if (opt == nullptr)
+ opt = config->option(opt_key);
+ return opt;
+ }
+
+ template <typename Iterator>
+ static void legacy_variable_expansion(
+ const MyContext *ctx,
+ boost::iterator_range<Iterator> &opt_key,
+ std::string &output)
+ {
+ std::string opt_key_str(opt_key.begin(), opt_key.end());
+ const ConfigOption *opt = ctx->resolve_symbol(opt_key_str);
+ size_t idx = ctx->current_extruder_id;
+ if (opt == nullptr) {
+ // Check whether this is a legacy vector indexing.
+ idx = opt_key_str.rfind('_');
+ if (idx != std::string::npos) {
+ opt = ctx->resolve_symbol(opt_key_str.substr(0, idx));
+ if (opt != nullptr) {
+ if (! opt->is_vector())
+ ctx->throw_exception("Trying to index a scalar variable", opt_key);
+ char *endptr = nullptr;
+ idx = strtol(opt_key_str.c_str() + idx + 1, &endptr, 10);
+ if (endptr == nullptr || *endptr != 0)
+ ctx->throw_exception("Invalid vector index", boost::iterator_range<Iterator>(opt_key.begin() + idx + 1, opt_key.end()));
+ }
+ }
+ }
+ if (opt == nullptr)
+ ctx->throw_exception("Variable does not exist", boost::iterator_range<Iterator>(opt_key.begin(), opt_key.end()));
+ if (opt->is_scalar())
+ output = opt->serialize();
+ else {
+ const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt);
+ if (vec->empty())
+ ctx->throw_exception("Indexing an empty vector variable", opt_key);
+ output = vec->vserialize()[(idx >= vec->size()) ? 0 : idx];
+ }
+ }
+
+ template <typename Iterator>
+ static void legacy_variable_expansion2(
+ const MyContext *ctx,
+ boost::iterator_range<Iterator> &opt_key,
+ boost::iterator_range<Iterator> &opt_vector_index,
+ std::string &output)
+ {
+ std::string opt_key_str(opt_key.begin(), opt_key.end());
+ const ConfigOption *opt = ctx->resolve_symbol(opt_key_str);
+ if (opt == nullptr) {
+ // Check whether the opt_key ends with '_'.
+ if (opt_key_str.back() == '_')
+ opt_key_str.resize(opt_key_str.size() - 1);
+ opt = ctx->resolve_symbol(opt_key_str);
+ }
+ if (! opt->is_vector())
+ ctx->throw_exception("Trying to index a scalar variable", opt_key);
+ const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt);
+ if (vec->empty())
+ ctx->throw_exception("Indexing an empty vector variable", boost::iterator_range<Iterator>(opt_key.begin(), opt_key.end()));
+ const ConfigOption *opt_index = ctx->resolve_symbol(std::string(opt_vector_index.begin(), opt_vector_index.end()));
+ if (opt_index == nullptr)
+ ctx->throw_exception("Variable does not exist", opt_key);
+ if (opt_index->type() != coInt)
+ ctx->throw_exception("Indexing variable has to be integer", opt_key);
+ int idx = opt_index->getInt();
+ if (idx < 0)
+ ctx->throw_exception("Negative vector index", opt_key);
+ output = vec->vserialize()[(idx >= (int)vec->size()) ? 0 : idx];
+ }
+
+ template <typename Iterator>
+ static void resolve_variable(
+ const MyContext *ctx,
+ boost::iterator_range<Iterator> &opt_key,
+ OptWithPos<Iterator> &output)
+ {
+ const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end()));
+ if (opt == nullptr)
+ ctx->throw_exception("Not a variable name", opt_key);
+ output.opt = opt;
+ output.it_range = opt_key;
+ }
+
+ template <typename Iterator>
+ static void scalar_variable_reference(
+ const MyContext *ctx,
+ OptWithPos<Iterator> &opt,
+ expr<Iterator> &output)
+ {
+ if (opt.opt->is_vector())
+ ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range);
+ switch (opt.opt->type()) {
+ case coFloat: output.set_d(opt.opt->getFloat()); break;
+ case coInt: output.set_i(opt.opt->getInt()); break;
+ case coString: output.set_s(static_cast<const ConfigOptionString*>(opt.opt)->value); break;
+ case coPercent: output.set_d(opt.opt->getFloat()); break;
+ case coPoint: output.set_s(opt.opt->serialize()); break;
+ case coBool: output.set_b(opt.opt->getBool()); break;
+ case coFloatOrPercent:
+ ctx->throw_exception("FloatOrPercent variables are not supported", opt.it_range);
+ default:
+ ctx->throw_exception("Unknown scalar variable type", opt.it_range);
+ }
+ output.it_range = opt.it_range;
+ }
+
+ template <typename Iterator>
+ static void vector_variable_reference(
+ const MyContext *ctx,
+ OptWithPos<Iterator> &opt,
+ int &index,
+ Iterator it_end,
+ expr<Iterator> &output)
+ {
+ if (opt.opt->is_scalar())
+ ctx->throw_exception("Referencing a scalar variable when vector is expected", opt.it_range);
+ const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt.opt);
+ if (vec->empty())
+ ctx->throw_exception("Indexing an empty vector variable", opt.it_range);
+ size_t idx = (index < 0) ? 0 : (index >= int(vec->size())) ? 0 : size_t(index);
+ switch (opt.opt->type()) {
+ case coFloats: output.set_d(static_cast<const ConfigOptionFloats *>(opt.opt)->values[idx]); break;
+ case coInts: output.set_i(static_cast<const ConfigOptionInts *>(opt.opt)->values[idx]); break;
+ case coStrings: output.set_s(static_cast<const ConfigOptionStrings *>(opt.opt)->values[idx]); break;
+ case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break;
+ case coPoints: output.set_s(to_string(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx])); break;
+ case coBools: output.set_b(static_cast<const ConfigOptionBools *>(opt.opt)->values[idx] != 0); break;
+ default:
+ ctx->throw_exception("Unknown vector variable type", opt.it_range);
+ }
+ output.it_range = boost::iterator_range<Iterator>(opt.it_range.begin(), it_end);
+ }
+
+ // Verify that the expression returns an integer, which may be used
+ // to address a vector.
+ template <typename Iterator>
+ static void evaluate_index(expr<Iterator> &expr_index, int &output)
+ {
+ if (expr_index.type != expr<Iterator>::TYPE_INT)
+ expr_index.throw_exception("Non-integer index is not allowed to address a vector variable.");
+ output = expr_index.i();
+ }
+
+ template <typename Iterator>
+ static void throw_exception(const std::string &msg, const boost::iterator_range<Iterator> &it_range)
+ {
+ // An asterix is added to the start of the string to differentiate the boost::spirit::info::tag content
+ // between the grammer terminal / non-terminal symbol name and a free-form error message.
+ boost::throw_exception(qi::expectation_failure<Iterator>(it_range.begin(), it_range.end(), spirit::info(std::string("*") + msg)));
+ }
+
+ template <typename Iterator>
+ static void process_error_message(const MyContext *context, const boost::spirit::info &info, const Iterator &it_begin, const Iterator &it_end, const Iterator &it_error)
+ {
+ std::string &msg = const_cast<MyContext*>(context)->error_message;
+ std::string first(it_begin, it_error);
+ std::string last(it_error, it_end);
+ auto first_pos = first.rfind('\n');
+ auto last_pos = last.find('\n');
+ int line_nr = 1;
+ if (first_pos == std::string::npos)
+ first_pos = 0;
+ else {
+ // Calculate the current line number.
+ for (size_t i = 0; i <= first_pos; ++ i)
+ if (first[i] == '\n')
+ ++ line_nr;
+ ++ first_pos;
+ }
+ auto error_line = std::string(first, first_pos) + std::string(last, 0, last_pos);
+ // Position of the it_error from the start of its line.
+ auto error_pos = (it_error - it_begin) - first_pos;
+ msg += "Parsing error at line " + std::to_string(line_nr);
+ if (! info.tag.empty() && info.tag.front() == '*') {
+ // The gat contains an explanatory string.
+ msg += ": ";
+ msg += info.tag.substr(1);
+ } else {
+ auto it = tag_to_error_message.find(info.tag);
+ if (it == tag_to_error_message.end()) {
+ // A generic error report based on the nonterminal or terminal symbol name.
+ msg += ". Expecting tag ";
+ msg += info.tag;
+ } else {
+ // Use the human readable error message.
+ msg += ". ";
+ msg + it->second;
+ }
+ }
+ msg += '\n';
+ msg += error_line;
+ msg += '\n';
+ for (size_t i = 0; i < error_pos; ++ i)
+ msg += ' ';
+ msg += "^\n";
+ }
+ };
+
+ // Table to translate symbol tag to a human readable error message.
+ std::map<std::string, std::string> MyContext::tag_to_error_message = {
+ { "eoi", "Unknown syntax error" },
+ { "start", "Unknown syntax error" },
+ { "text", "Invalid text." },
+ { "text_block", "Invalid text block." },
+ { "macro", "Invalid macro." },
+ { "if_else_output", "Not an {if}{else}{endif} macro." },
+ { "switch_output", "Not a {switch} macro." },
+ { "legacy_variable_expansion", "Expecting a legacy variable expansion format" },
+ { "identifier", "Expecting an identifier." },
+ { "conditional_expression", "Expecting a conditional expression." },
+ { "logical_or_expression", "Expecting a boolean expression." },
+ { "logical_and_expression", "Expecting a boolean expression." },
+ { "equality_expression", "Expecting an expression." },
+ { "bool_expr_eval", "Expecting a boolean expression."},
+ { "relational_expression", "Expecting an expression." },
+ { "additive_expression", "Expecting an expression." },
+ { "multiplicative_expression", "Expecting an expression." },
+ { "unary_expression", "Expecting an expression." },
+ { "scalar_variable_reference", "Expecting a scalar variable reference."},
+ { "variable_reference", "Expecting a variable reference."},
+ { "regular_expression", "Expecting a regular expression."}
+ };
+
+ // For debugging the boost::spirit parsers. Print out the string enclosed in it_range.
+ template<typename Iterator>
+ std::ostream& operator<<(std::ostream& os, const boost::iterator_range<Iterator> &it_range)
+ {
+ os << std::string(it_range.begin(), it_range.end());
+ return os;
+ }
+
+ // Disable parsing int numbers (without decimals) and Inf/NaN symbols by the double parser.
+ struct strict_real_policies_without_nan_inf : public qi::strict_real_policies<double>
+ {
+ template <typename It, typename Attr> static bool parse_nan(It&, It const&, Attr&) { return false; }
+ template <typename It, typename Attr> static bool parse_inf(It&, It const&, Attr&) { return false; }
+ };
+
+ // This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character.
+ // If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown.
+ struct utf8_char_skipper_parser : qi::primitive_parser<utf8_char_skipper_parser>
+ {
+ // Define the attribute type exposed by this parser component
+ template <typename Context, typename Iterator>
+ struct attribute
+ {
+ typedef wchar_t type;
+ };
+
+ // This function is called during the actual parsing process
+ template <typename Iterator, typename Context , typename Skipper, typename Attribute>
+ bool parse(Iterator& first, Iterator const& last, Context& context, Skipper const& skipper, Attribute& attr) const
+ {
+ // The skipper shall always be empty, any white space will be accepted.
+ // skip_over(first, last, skipper);
+ if (first == last)
+ return false;
+ // Iterator over the UTF-8 sequence.
+ auto it = first;
+ // Read the first byte of the UTF-8 sequence.
+ unsigned char c = static_cast<boost::uint8_t>(*it ++);
+ unsigned int cnt = 0;
+ // UTF-8 sequence must not start with a continuation character:
+ if ((c & 0xC0) == 0x80)
+ goto err;
+ // Skip high surrogate first if there is one.
+ // If the most significant bit with a zero in it is in position
+ // 8-N then there are N bytes in this UTF-8 sequence:
+ {
+ unsigned char mask = 0x80u;
+ unsigned int result = 0;
+ while (c & mask) {
+ ++ result;
+ mask >>= 1;
+ }
+ cnt = (result == 0) ? 1 : ((result > 4) ? 4 : result);
+ }
+ // Since we haven't read in a value, we need to validate the code points:
+ for (-- cnt; cnt > 0; -- cnt) {
+ if (it == last)
+ goto err;
+ c = static_cast<boost::uint8_t>(*it ++);
+ // We must have a continuation byte:
+ if (cnt > 1 && (c & 0xC0) != 0x80)
+ goto err;
+ }
+ first = it;
+ return true;
+ err:
+ MyContext::throw_exception("Invalid utf8 sequence", boost::iterator_range<Iterator>(first, last));
+ return false;
+ }
+
+ // This function is called during error handling to create a human readable string for the error context.
+ template <typename Context>
+ spirit::info what(Context&) const
+ {
+ return spirit::info("unicode_char");
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Our macro_processor grammar
+ ///////////////////////////////////////////////////////////////////////////
+ // Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html
+ template <typename Iterator>
+ struct macro_processor : qi::grammar<Iterator, std::string(const MyContext*), qi::locals<bool>, spirit::ascii::space_type>
+ {
+ macro_processor() : macro_processor::base_type(start)
+ {
+ using namespace qi::labels;
+ qi::alpha_type alpha;
+ qi::alnum_type alnum;
+ qi::eps_type eps;
+ qi::raw_type raw;
+ qi::lit_type lit;
+ qi::lexeme_type lexeme;
+ qi::no_skip_type no_skip;
+ qi::real_parser<double, strict_real_policies_without_nan_inf> strict_double;
+ spirit::ascii::char_type char_;
+ utf8_char_skipper_parser utf8char;
+ spirit::bool_type bool_;
+ spirit::int_type int_;
+ spirit::double_type double_;
+ spirit::ascii::string_type string;
+ spirit::eoi_type eoi;
+ spirit::repository::qi::iter_pos_type iter_pos;
+ auto kw = spirit::repository::qi::distinct(qi::copy(alnum | '_'));
+
+ qi::_val_type _val;
+ qi::_1_type _1;
+ qi::_2_type _2;
+ qi::_3_type _3;
+ qi::_4_type _4;
+ qi::_a_type _a;
+ qi::_b_type _b;
+ qi::_r1_type _r1;
+
+ // Starting symbol of the grammer.
+ // The leading eps is required by the "expectation point" operator ">".
+ // Without it, some of the errors would not trigger the error handler.
+ // Also the start symbol switches between the "full macro syntax" and a "boolean expression only",
+ // depending on the context->just_boolean_expression flag. This way a single static expression parser
+ // could serve both purposes.
+ start = eps[px::bind(&MyContext::evaluate_full_macro, _r1, _a)] >
+ ( eps(_a==true) > text_block(_r1) [_val=_1]
+ | conditional_expression(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean_to_string, _1, _val) ]
+ ) > eoi;
+ start.name("start");
+ qi::on_error<qi::fail>(start, px::bind(&MyContext::process_error_message<Iterator>, _r1, _4, _1, _2, _3));
+
+ text_block = *(
+ text [_val+=_1]
+ // Allow back tracking after '{' in case of a text_block embedded inside a condition.
+ // In that case the inner-most {else} wins and the {if}/{elsif}/{else} shall be paired.
+ // {elsif}/{else} without an {if} will be allowed to back track from the embedded text_block.
+ | (lit('{') >> macro(_r1) [_val+=_1] > '}')
+ | (lit('[') > legacy_variable_expansion(_r1) [_val+=_1] > ']')
+ );
+ text_block.name("text_block");
+
+ // Free-form text up to a first brace, including spaces and newlines.
+ // The free-form text will be inserted into the processed text without a modification.
+ text = no_skip[raw[+(utf8char - char_('[') - char_('{'))]];
+ text.name("text");
+
+ // New style of macro expansion.
+ // The macro expansion may contain numeric or string expressions, ifs and cases.
+ macro =
+ (kw["if"] > if_else_output(_r1) [_val = _1])
+// | (kw["switch"] > switch_output(_r1) [_val = _1])
+ | additive_expression(_r1) [ px::bind(&expr<Iterator>::to_string2, _1, _val) ];
+ macro.name("macro");
+
+ // An if expression enclosed in {} (the outmost {} are already parsed by the caller).
+ if_else_output =
+ eps[_b=true] >
+ bool_expr_eval(_r1)[_a=_1] > '}' >
+ text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)] > '{' >
+ *(kw["elsif"] > bool_expr_eval(_r1)[_a=_1] > '}' >
+ text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)] > '{') >
+ -(kw["else"] > lit('}') >
+ text_block(_r1)[px::bind(&expr<Iterator>::set_if, _b, _b, _1, _val)] > '{') >
+ kw["endif"];
+ if_else_output.name("if_else_output");
+ // A switch expression enclosed in {} (the outmost {} are already parsed by the caller).
+/*
+ switch_output =
+ eps[_b=true] >
+ omit[expr(_r1)[_a=_1]] > '}' > text_block(_r1)[px::bind(&expr<Iterator>::set_if_equal, _a, _b, _1, _val)] > '{' >
+ *("elsif" > omit[bool_expr_eval(_r1)[_a=_1]] > '}' > text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)]) >>
+ -("else" > '}' >> text_block(_r1)[px::bind(&expr<Iterator>::set_if, _b, _b, _1, _val)]) >
+ "endif";
+*/
+
+ // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index].
+ legacy_variable_expansion =
+ (identifier >> &lit(']'))
+ [ px::bind(&MyContext::legacy_variable_expansion<Iterator>, _r1, _1, _val) ]
+ | (identifier > lit('[') > identifier > ']')
+ [ px::bind(&MyContext::legacy_variable_expansion2<Iterator>, _r1, _1, _2, _val) ]
+ ;
+ legacy_variable_expansion.name("legacy_variable_expansion");
+
+ identifier =
+ ! kw[keywords] >>
+ raw[lexeme[(alpha | '_') >> *(alnum | '_')]];
+ identifier.name("identifier");
+
+ conditional_expression =
+ logical_or_expression(_r1) [_val = _1]
+ >> -('?' > conditional_expression(_r1) > ':' > conditional_expression(_r1)) [px::bind(&expr<Iterator>::ternary_op, _val, _1, _2)];
+ conditional_expression.name("conditional_expression");
+
+ logical_or_expression =
+ logical_and_expression(_r1) [_val = _1]
+ >> *( ((kw["or"] | "||") > logical_and_expression(_r1) ) [px::bind(&expr<Iterator>::logical_or, _val, _1)] );
+ logical_or_expression.name("logical_or_expression");
+
+ logical_and_expression =
+ equality_expression(_r1) [_val = _1]
+ >> *( ((kw["and"] | "&&") > equality_expression(_r1) ) [px::bind(&expr<Iterator>::logical_and, _val, _1)] );
+ logical_and_expression.name("logical_and_expression");
+
+ equality_expression =
+ relational_expression(_r1) [_val = _1]
+ >> *( ("==" > relational_expression(_r1) ) [px::bind(&expr<Iterator>::equal, _val, _1)]
+ | ("!=" > relational_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)]
+ | ("<>" > relational_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)]
+ | ("=~" > regular_expression ) [px::bind(&expr<Iterator>::regex_matches, _val, _1)]
+ | ("!~" > regular_expression ) [px::bind(&expr<Iterator>::regex_doesnt_match, _val, _1)]
+ );
+ equality_expression.name("bool expression");
+
+ // Evaluate a boolean expression stored as expr into a boolean value.
+ // Throw if the equality_expression does not produce a expr of boolean type.
+ bool_expr_eval = conditional_expression(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean, _1, _val) ];
+ bool_expr_eval.name("bool_expr_eval");
+
+ relational_expression =
+ additive_expression(_r1) [_val = _1]
+ >> *( ("<=" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::leq, _val, _1)]
+ | (">=" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::geq, _val, _1)]
+ | (lit('<') > additive_expression(_r1) ) [px::bind(&expr<Iterator>::lower, _val, _1)]
+ | (lit('>') > additive_expression(_r1) ) [px::bind(&expr<Iterator>::greater, _val, _1)]
+ );
+ relational_expression.name("relational_expression");
+
+ additive_expression =
+ multiplicative_expression(_r1) [_val = _1]
+ >> *( (lit('+') > multiplicative_expression(_r1) ) [_val += _1]
+ | (lit('-') > multiplicative_expression(_r1) ) [_val -= _1]
+ );
+ additive_expression.name("additive_expression");
+
+ multiplicative_expression =
+ unary_expression(_r1) [_val = _1]
+ >> *( (lit('*') > unary_expression(_r1) ) [_val *= _1]
+ | (lit('/') > unary_expression(_r1) ) [_val /= _1]
+ );
+ multiplicative_expression.name("multiplicative_expression");
+
+ struct FactorActions {
+ static void set_start_pos(Iterator &start_pos, expr<Iterator> &out)
+ { out.it_range = boost::iterator_range<Iterator>(start_pos, start_pos); }
+ static void int_(int &value, Iterator &end_pos, expr<Iterator> &out)
+ { out = expr<Iterator>(value, out.it_range.begin(), end_pos); }
+ static void double_(double &value, Iterator &end_pos, expr<Iterator> &out)
+ { out = expr<Iterator>(value, out.it_range.begin(), end_pos); }
+ static void bool_(bool &value, Iterator &end_pos, expr<Iterator> &out)
+ { out = expr<Iterator>(value, out.it_range.begin(), end_pos); }
+ static void string_(boost::iterator_range<Iterator> &it_range, expr<Iterator> &out)
+ { out = expr<Iterator>(std::string(it_range.begin() + 1, it_range.end() - 1), it_range.begin(), it_range.end()); }
+ static void expr_(expr<Iterator> &value, Iterator &end_pos, expr<Iterator> &out)
+ { out = expr<Iterator>(std::move(value), out.it_range.begin(), end_pos); }
+ static void minus_(expr<Iterator> &value, expr<Iterator> &out)
+ { out = value.unary_minus(out.it_range.begin()); }
+ static void not_(expr<Iterator> &value, expr<Iterator> &out)
+ { out = value.unary_not(out.it_range.begin()); }
+ };
+ unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> (
+ scalar_variable_reference(_r1) [ _val = _1 ]
+ | (lit('(') > conditional_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ]
+ | (lit('-') > unary_expression(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ]
+ | (lit('+') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ]
+ | ((kw["not"] | '!') > unary_expression(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ]
+ | (kw["min"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')')
+ [ px::bind(&expr<Iterator>::min, _val, _2) ]
+ | (kw["max"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')')
+ [ px::bind(&expr<Iterator>::max, _val, _2) ]
+ | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ]
+ | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ]
+ | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ]
+ | raw[lexeme['"' > *((utf8char - char_('\\') - char_('"')) | ('\\' > char_)) > '"']]
+ [ px::bind(&FactorActions::string_, _1, _val) ]
+ );
+ unary_expression.name("unary_expression");
+
+ scalar_variable_reference =
+ variable_reference(_r1)[_a=_1] >>
+ (
+ ('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index<Iterator>, _1, _b)] > ']' >
+ iter_pos[px::bind(&MyContext::vector_variable_reference<Iterator>, _r1, _a, _b, _1, _val)])
+ | eps[px::bind(&MyContext::scalar_variable_reference<Iterator>, _r1, _a, _val)]
+ );
+ scalar_variable_reference.name("scalar variable reference");
+
+ variable_reference = identifier
+ [ px::bind(&MyContext::resolve_variable<Iterator>, _r1, _1, _val) ];
+ variable_reference.name("variable reference");
+
+ regular_expression = raw[lexeme['/' > *((utf8char - char_('\\') - char_('/')) | ('\\' > char_)) > '/']];
+ regular_expression.name("regular_expression");
+
+ keywords.add
+ ("and")
+ ("if")
+ //("inf")
+ ("else")
+ ("elsif")
+ ("endif")
+ ("false")
+ ("min")
+ ("max")
+ ("not")
+ ("or")
+ ("true");
+
+ if (0) {
+ debug(start);
+ debug(text);
+ debug(text_block);
+ debug(macro);
+ debug(if_else_output);
+// debug(switch_output);
+ debug(legacy_variable_expansion);
+ debug(identifier);
+ debug(conditional_expression);
+ debug(logical_or_expression);
+ debug(logical_and_expression);
+ debug(equality_expression);
+ debug(bool_expr_eval);
+ debug(relational_expression);
+ debug(additive_expression);
+ debug(multiplicative_expression);
+ debug(unary_expression);
+ debug(scalar_variable_reference);
+ debug(variable_reference);
+ debug(regular_expression);
+ }
+ }
+
+ // Generic expression over expr<Iterator>.
+ typedef qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> RuleExpression;
+
+ // The start of the grammar.
+ qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool>, spirit::ascii::space_type> start;
+ // A free-form text.
+ qi::rule<Iterator, std::string(), spirit::ascii::space_type> text;
+ // A free-form text, possibly empty, possibly containing macro expansions.
+ qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> text_block;
+ // Statements enclosed in curely braces {}
+ qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> macro;
+ // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index].
+ qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> legacy_variable_expansion;
+ // Parsed identifier name.
+ qi::rule<Iterator, boost::iterator_range<Iterator>(), spirit::ascii::space_type> identifier;
+ // Ternary operator (?:) over logical_or_expression.
+ RuleExpression conditional_expression;
+ // Logical or over logical_and_expressions.
+ RuleExpression logical_or_expression;
+ // Logical and over relational_expressions.
+ RuleExpression logical_and_expression;
+ // <, >, <=, >=
+ RuleExpression relational_expression;
+ // Math expression consisting of +- operators over multiplicative_expressions.
+ RuleExpression additive_expression;
+ // Boolean expressions over expressions.
+ RuleExpression equality_expression;
+ // Math expression consisting of */ operators over factors.
+ RuleExpression multiplicative_expression;
+ // Number literals, functions, braced expressions, variable references, variable indexing references.
+ RuleExpression unary_expression;
+ // Rule to capture a regular expression enclosed in //.
+ qi::rule<Iterator, boost::iterator_range<Iterator>(), spirit::ascii::space_type> regular_expression;
+ // Evaluate boolean expression into bool.
+ qi::rule<Iterator, bool(const MyContext*), spirit::ascii::space_type> bool_expr_eval;
+ // Reference of a scalar variable, or reference to a field of a vector variable.
+ qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<OptWithPos<Iterator>, int>, spirit::ascii::space_type> scalar_variable_reference;
+ // Rule to translate an identifier to a ConfigOption, or to fail.
+ qi::rule<Iterator, OptWithPos<Iterator>(const MyContext*), spirit::ascii::space_type> variable_reference;
+
+ qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool, bool>, spirit::ascii::space_type> if_else_output;
+// qi::rule<Iterator, std::string(const MyContext*), qi::locals<expr<Iterator>, bool, std::string>, spirit::ascii::space_type> switch_output;
+
+ qi::symbols<char> keywords;
+ };
+}
+
+static std::string process_macro(const std::string &templ, client::MyContext &context)
+{
+ typedef std::string::const_iterator iterator_type;
+ typedef client::macro_processor<iterator_type> macro_processor;
+
+ // Our whitespace skipper.
+ spirit::ascii::space_type space;
+ // Our grammar, statically allocated inside the method, meaning it will be allocated the first time
+ // PlaceholderParser::process() runs.
+ //FIXME this kind of initialization is not thread safe!
+ static macro_processor macro_processor_instance;
+ // Iterators over the source template.
+ std::string::const_iterator iter = templ.begin();
+ std::string::const_iterator end = templ.end();
+ // Accumulator for the processed template.
+ std::string output;
+ bool res = phrase_parse(iter, end, macro_processor_instance(&context), space, output);
+ if (!context.error_message.empty()) {
+ if (context.error_message.back() != '\n' && context.error_message.back() != '\r')
+ context.error_message += '\n';
+ throw std::runtime_error(context.error_message);
+ }
+ return output;
+}
+
+std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const
+{
+ client::MyContext context;
+ context.config = &this->config();
+ context.config_override = config_override;
+ context.current_extruder_id = current_extruder_id;
+ return process_macro(templ, context);
+}
+
+// Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
+// Throws std::runtime_error on syntax or runtime error.
+bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override)
+{
+ client::MyContext context;
+ context.config = &config;
+ context.config_override = config_override;
+ // Let the macro processor parse just a boolean expression, not the full macro language.
+ context.just_boolean_expression = true;
+ return process_macro(templ, context) == "true";
+}
+
+}
diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp
new file mode 100644
index 000000000..49d53ec9e
--- /dev/null
+++ b/src/libslic3r/PlaceholderParser.hpp
@@ -0,0 +1,50 @@
+#ifndef slic3r_PlaceholderParser_hpp_
+#define slic3r_PlaceholderParser_hpp_
+
+#include "libslic3r.h"
+#include <map>
+#include <string>
+#include <vector>
+#include "PrintConfig.hpp"
+
+namespace Slic3r {
+
+class PlaceholderParser
+{
+public:
+ PlaceholderParser();
+
+ void apply_config(const DynamicPrintConfig &config);
+ void apply_env_variables();
+
+ // Add new ConfigOption values to m_config.
+ void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); }
+ void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); }
+ void set(const std::string &key, unsigned int value) { this->set(key, int(value)); }
+ void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); }
+ void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); }
+ void set(const std::string &key, const std::vector<std::string> &values) { this->set(key, new ConfigOptionStrings(values)); }
+ void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); }
+ const DynamicConfig& config() const { return m_config; }
+ const ConfigOption* option(const std::string &key) const { return m_config.option(key); }
+
+ // Fill in the template using a macro processing language.
+ // Throws std::runtime_error on syntax or runtime error.
+ std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr) const;
+
+ // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax.
+ // Throws std::runtime_error on syntax or runtime error.
+ static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr);
+
+ // Update timestamp, year, month, day, hour, minute, second variables at the provided config.
+ static void update_timestamp(DynamicConfig &config);
+ // Update timestamp, year, month, day, hour, minute, second variables at m_config.
+ void update_timestamp() { update_timestamp(m_config); }
+
+private:
+ DynamicConfig m_config;
+};
+
+}
+
+#endif
diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp
new file mode 100644
index 000000000..c2417d0dc
--- /dev/null
+++ b/src/libslic3r/Point.cpp
@@ -0,0 +1,210 @@
+#include "Point.hpp"
+#include "Line.hpp"
+#include "MultiPoint.hpp"
+#include "Int128.hpp"
+#include <algorithm>
+
+namespace Slic3r {
+
+std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t)
+{
+ unsigned int vertices_count = (unsigned int)points.size();
+ if (vertices_count == 0)
+ return std::vector<Vec3f>();
+
+ unsigned int data_size = 3 * vertices_count * sizeof(float);
+
+ Eigen::MatrixXf src(3, vertices_count);
+ ::memcpy((void*)src.data(), (const void*)points.data(), data_size);
+
+ Eigen::MatrixXf dst(3, vertices_count);
+ dst = t * src.colwise().homogeneous();
+
+ std::vector<Vec3f> ret_points(vertices_count, Vec3f::Zero());
+ ::memcpy((void*)ret_points.data(), (const void*)dst.data(), data_size);
+ return ret_points;
+}
+
+Pointf3s transform(const Pointf3s& points, const Transform3d& t)
+{
+ unsigned int vertices_count = (unsigned int)points.size();
+ if (vertices_count == 0)
+ return Pointf3s();
+
+ unsigned int data_size = 3 * vertices_count * sizeof(double);
+
+ Eigen::MatrixXd src(3, vertices_count);
+ ::memcpy((void*)src.data(), (const void*)points.data(), data_size);
+
+ Eigen::MatrixXd dst(3, vertices_count);
+ dst = t * src.colwise().homogeneous();
+
+ Pointf3s ret_points(vertices_count, Vec3d::Zero());
+ ::memcpy((void*)ret_points.data(), (const void*)dst.data(), data_size);
+ return ret_points;
+}
+
+void Point::rotate(double angle)
+{
+ double cur_x = (double)(*this)(0);
+ double cur_y = (double)(*this)(1);
+ double s = ::sin(angle);
+ double c = ::cos(angle);
+ (*this)(0) = (coord_t)round(c * cur_x - s * cur_y);
+ (*this)(1) = (coord_t)round(c * cur_y + s * cur_x);
+}
+
+void Point::rotate(double angle, const Point &center)
+{
+ double cur_x = (double)(*this)(0);
+ double cur_y = (double)(*this)(1);
+ double s = ::sin(angle);
+ double c = ::cos(angle);
+ double dx = cur_x - (double)center(0);
+ double dy = cur_y - (double)center(1);
+ (*this)(0) = (coord_t)round( (double)center(0) + c * dx - s * dy );
+ (*this)(1) = (coord_t)round( (double)center(1) + c * dy + s * dx );
+}
+
+int Point::nearest_point_index(const Points &points) const
+{
+ PointConstPtrs p;
+ p.reserve(points.size());
+ for (Points::const_iterator it = points.begin(); it != points.end(); ++it)
+ p.push_back(&*it);
+ return this->nearest_point_index(p);
+}
+
+int Point::nearest_point_index(const PointConstPtrs &points) const
+{
+ int idx = -1;
+ double distance = -1; // double because long is limited to 2147483647 on some platforms and it's not enough
+
+ for (PointConstPtrs::const_iterator it = points.begin(); it != points.end(); ++it) {
+ /* If the X distance of the candidate is > than the total distance of the
+ best previous candidate, we know we don't want it */
+ double d = sqr<double>((*this)(0) - (*it)->x());
+ if (distance != -1 && d > distance) continue;
+
+ /* If the Y distance of the candidate is > than the total distance of the
+ best previous candidate, we know we don't want it */
+ d += sqr<double>((*this)(1) - (*it)->y());
+ if (distance != -1 && d > distance) continue;
+
+ idx = it - points.begin();
+ distance = d;
+
+ if (distance < EPSILON) break;
+ }
+
+ return idx;
+}
+
+int Point::nearest_point_index(const PointPtrs &points) const
+{
+ PointConstPtrs p;
+ p.reserve(points.size());
+ for (PointPtrs::const_iterator it = points.begin(); it != points.end(); ++it)
+ p.push_back(*it);
+ return this->nearest_point_index(p);
+}
+
+bool Point::nearest_point(const Points &points, Point* point) const
+{
+ int idx = this->nearest_point_index(points);
+ if (idx == -1) return false;
+ *point = points.at(idx);
+ return true;
+}
+
+/* Three points are a counter-clockwise turn if ccw > 0, clockwise if
+ * ccw < 0, and collinear if ccw = 0 because ccw is a determinant that
+ * gives the signed area of the triangle formed by p1, p2 and this point.
+ * In other words it is the 2D cross product of p1-p2 and p1-this, i.e.
+ * z-component of their 3D cross product.
+ * We return double because it must be big enough to hold 2*max(|coordinate|)^2
+ */
+double Point::ccw(const Point &p1, const Point &p2) const
+{
+ return (double)(p2(0) - p1(0))*(double)((*this)(1) - p1(1)) - (double)(p2(1) - p1(1))*(double)((*this)(0) - p1(0));
+}
+
+double Point::ccw(const Line &line) const
+{
+ return this->ccw(line.a, line.b);
+}
+
+// returns the CCW angle between this-p1 and this-p2
+// i.e. this assumes a CCW rotation from p1 to p2 around this
+double Point::ccw_angle(const Point &p1, const Point &p2) const
+{
+ double angle = atan2(p1(0) - (*this)(0), p1(1) - (*this)(1))
+ - atan2(p2(0) - (*this)(0), p2(1) - (*this)(1));
+
+ // we only want to return only positive angles
+ return angle <= 0 ? angle + 2*PI : angle;
+}
+
+Point Point::projection_onto(const MultiPoint &poly) const
+{
+ Point running_projection = poly.first_point();
+ double running_min = (running_projection - *this).cast<double>().norm();
+
+ Lines lines = poly.lines();
+ for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) {
+ Point point_temp = this->projection_onto(*line);
+ if ((point_temp - *this).cast<double>().norm() < running_min) {
+ running_projection = point_temp;
+ running_min = (running_projection - *this).cast<double>().norm();
+ }
+ }
+ return running_projection;
+}
+
+Point Point::projection_onto(const Line &line) const
+{
+ if (line.a == line.b) return line.a;
+
+ /*
+ (Ported from VisiLibity by Karl J. Obermeyer)
+ The projection of point_temp onto the line determined by
+ line_segment_temp can be represented as an affine combination
+ expressed in the form projection of
+ Point = theta*line_segment_temp.first + (1.0-theta)*line_segment_temp.second.
+ If theta is outside the interval [0,1], then one of the Line_Segment's endpoints
+ must be closest to calling Point.
+ */
+ double lx = (double)(line.b(0) - line.a(0));
+ double ly = (double)(line.b(1) - line.a(1));
+ double theta = ( (double)(line.b(0) - (*this)(0))*lx + (double)(line.b(1)- (*this)(1))*ly )
+ / ( sqr<double>(lx) + sqr<double>(ly) );
+
+ if (0.0 <= theta && theta <= 1.0)
+ return (theta * line.a.cast<coordf_t>() + (1.0-theta) * line.b.cast<coordf_t>()).cast<coord_t>();
+
+ // Else pick closest endpoint.
+ return ((line.a - *this).cast<double>().squaredNorm() < (line.b - *this).cast<double>().squaredNorm()) ? line.a : line.b;
+}
+
+std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf)
+{
+ return stm << pointf(0) << "," << pointf(1);
+}
+
+namespace int128 {
+
+int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3)
+{
+ Slic3r::Vector v1(p2 - p1);
+ Slic3r::Vector v2(p3 - p1);
+ return Int128::sign_determinant_2x2_filtered(v1(0), v1(1), v2(0), v2(1));
+}
+
+int cross(const Vec2crd &v1, const Vec2crd &v2)
+{
+ return Int128::sign_determinant_2x2_filtered(v1(0), v1(1), v2(0), v2(1));
+}
+
+}
+
+}
diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp
new file mode 100644
index 000000000..6d9d82d25
--- /dev/null
+++ b/src/libslic3r/Point.hpp
@@ -0,0 +1,265 @@
+#ifndef slic3r_Point_hpp_
+#define slic3r_Point_hpp_
+
+#include "libslic3r.h"
+#include <cstddef>
+#include <vector>
+#include <cmath>
+#include <string>
+#include <sstream>
+#include <unordered_map>
+
+#include <Eigen/Geometry>
+
+namespace Slic3r {
+
+class Line;
+class MultiPoint;
+class Point;
+typedef Point Vector;
+
+// Eigen types, to replace the Slic3r's own types in the future.
+// Vector types with a fixed point coordinate base type.
+typedef Eigen::Matrix<coord_t, 2, 1, Eigen::DontAlign> Vec2crd;
+typedef Eigen::Matrix<coord_t, 3, 1, Eigen::DontAlign> Vec3crd;
+typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i;
+typedef Eigen::Matrix<int64_t, 2, 1, Eigen::DontAlign> Vec2i64;
+typedef Eigen::Matrix<int64_t, 3, 1, Eigen::DontAlign> Vec3i64;
+
+// Vector types with a double coordinate base type.
+typedef Eigen::Matrix<float, 2, 1, Eigen::DontAlign> Vec2f;
+typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
+typedef Eigen::Matrix<double, 2, 1, Eigen::DontAlign> Vec2d;
+typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
+
+typedef std::vector<Point> Points;
+typedef std::vector<Point*> PointPtrs;
+typedef std::vector<const Point*> PointConstPtrs;
+typedef std::vector<Vec3crd> Points3;
+typedef std::vector<Vec2d> Pointfs;
+typedef std::vector<Vec3d> Pointf3s;
+
+typedef Eigen::Transform<float, 2, Eigen::Affine, Eigen::DontAlign> Transform2f;
+typedef Eigen::Transform<double, 2, Eigen::Affine, Eigen::DontAlign> Transform2d;
+typedef Eigen::Transform<float, 3, Eigen::Affine, Eigen::DontAlign> Transform3f;
+typedef Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAlign> Transform3d;
+
+inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0) || (lhs(0) == rhs(0) && lhs(1) < rhs(1)); }
+
+inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
+inline coord_t cross2(const Vec2crd &v1, const Vec2crd &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
+inline float cross2(const Vec2f &v1, const Vec2f &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
+inline double cross2(const Vec2d &v1, const Vec2d &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
+
+inline Vec2crd to_2d(const Vec3crd &pt3) { return Vec2crd(pt3(0), pt3(1)); }
+inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
+inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); }
+inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); }
+
+inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale<double>(x), unscale<double>(y)); }
+inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale<double>(pt(0)), unscale<double>(pt(1))); }
+inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale<double>(pt(0)), unscale<double>(pt(1))); }
+inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale<double>(x), unscale<double>(y), unscale<double>(z)); }
+inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale<double>(pt(0)), unscale<double>(pt(1)), unscale<double>(pt(2))); }
+inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale<double>(pt(0)), unscale<double>(pt(1)), unscale<double>(pt(2))); }
+
+inline std::string to_string(const Vec2crd &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + "]"; }
+inline std::string to_string(const Vec2d &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + "]"; }
+inline std::string to_string(const Vec3crd &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + ", " + std::to_string(pt(2)) + "]"; }
+inline std::string to_string(const Vec3d &pt) { return std::string("[") + std::to_string(pt(0)) + ", " + std::to_string(pt(1)) + ", " + std::to_string(pt(2)) + "]"; }
+
+std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t);
+Pointf3s transform(const Pointf3s& points, const Transform3d& t);
+
+class Point : public Vec2crd
+{
+public:
+ typedef coord_t coord_type;
+
+ Point() : Vec2crd() { (*this)(0) = 0; (*this)(1) = 0; }
+ Point(coord_t x, coord_t y) { (*this)(0) = x; (*this)(1) = y; }
+ Point(int64_t x, int64_t y) { (*this)(0) = coord_t(x); (*this)(1) = coord_t(y); } // for Clipper
+ Point(double x, double y) { (*this)(0) = coord_t(lrint(x)); (*this)(1) = coord_t(lrint(y)); }
+ Point(const Point &rhs) { *this = rhs; }
+ // This constructor allows you to construct Point from Eigen expressions
+ template<typename OtherDerived>
+ Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
+ static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
+
+ // This method allows you to assign Eigen expressions to MyVectorType
+ template<typename OtherDerived>
+ Point& operator=(const Eigen::MatrixBase<OtherDerived> &other)
+ {
+ this->Vec2crd::operator=(other);
+ return *this;
+ }
+
+ bool operator< (const Point& rhs) const { return (*this)(0) < rhs(0) || ((*this)(0) == rhs(0) && (*this)(1) < rhs(1)); }
+
+ Point& operator+=(const Point& rhs) { (*this)(0) += rhs(0); (*this)(1) += rhs(1); return *this; }
+ Point& operator-=(const Point& rhs) { (*this)(0) -= rhs(0); (*this)(1) -= rhs(1); return *this; }
+ Point& operator*=(const double &rhs) { (*this)(0) *= rhs; (*this)(1) *= rhs; return *this; }
+
+ void rotate(double angle);
+ void rotate(double angle, const Point &center);
+ Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; }
+ Point rotated(double angle, const Point &center) const { Point res(*this); res.rotate(angle, center); return res; }
+ int nearest_point_index(const Points &points) const;
+ int nearest_point_index(const PointConstPtrs &points) const;
+ int nearest_point_index(const PointPtrs &points) const;
+ bool nearest_point(const Points &points, Point* point) const;
+ double ccw(const Point &p1, const Point &p2) const;
+ double ccw(const Line &line) const;
+ double ccw_angle(const Point &p1, const Point &p2) const;
+ Point projection_onto(const MultiPoint &poly) const;
+ Point projection_onto(const Line &line) const;
+};
+
+namespace int128 {
+ // Exact orientation predicate,
+ // returns +1: CCW, 0: collinear, -1: CW.
+ int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3);
+ // Exact orientation predicate,
+ // returns +1: CCW, 0: collinear, -1: CW.
+ int cross(const Vec2crd &v1, const Vec2crd &v2);
+}
+
+// To be used by std::unordered_map, std::unordered_multimap and friends.
+struct PointHash {
+ size_t operator()(const Vec2crd &pt) const {
+ return std::hash<coord_t>()(pt(0)) ^ std::hash<coord_t>()(pt(1));
+ }
+};
+
+// A generic class to search for a closest Point in a given radius.
+// It uses std::unordered_multimap to implement an efficient 2D spatial hashing.
+// The PointAccessor has to return const Point*.
+// If a nullptr is returned, it is ignored by the query.
+template<typename ValueType, typename PointAccessor> class ClosestPointInRadiusLookup
+{
+public:
+ ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) :
+ m_search_radius(search_radius), m_point_accessor(point_accessor), m_grid_log2(0)
+ {
+ // Resolution of a grid, twice the search radius + some epsilon.
+ coord_t gridres = 2 * m_search_radius + 4;
+ m_grid_resolution = gridres;
+ assert(m_grid_resolution > 0);
+ assert(m_grid_resolution < (coord_t(1) << 30));
+ // Compute m_grid_log2 = log2(m_grid_resolution)
+ if (m_grid_resolution > 32767) {
+ m_grid_resolution >>= 16;
+ m_grid_log2 += 16;
+ }
+ if (m_grid_resolution > 127) {
+ m_grid_resolution >>= 8;
+ m_grid_log2 += 8;
+ }
+ if (m_grid_resolution > 7) {
+ m_grid_resolution >>= 4;
+ m_grid_log2 += 4;
+ }
+ if (m_grid_resolution > 1) {
+ m_grid_resolution >>= 2;
+ m_grid_log2 += 2;
+ }
+ if (m_grid_resolution > 0)
+ ++ m_grid_log2;
+ m_grid_resolution = 1 << m_grid_log2;
+ assert(m_grid_resolution >= gridres);
+ assert(gridres > m_grid_resolution / 2);
+ }
+
+ void insert(const ValueType &value) {
+ const Vec2crd *pt = m_point_accessor(value);
+ if (pt != nullptr)
+ m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), value));
+ }
+
+ void insert(ValueType &&value) {
+ const Vec2crd *pt = m_point_accessor(value);
+ if (pt != nullptr)
+ m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value)));
+ }
+
+ // Return a pair of <ValueType*, distance_squared>
+ std::pair<const ValueType*, double> find(const Vec2crd &pt) {
+ // Iterate over 4 closest grid cells around pt,
+ // find the closest start point inside these cells to pt.
+ const ValueType *value_min = nullptr;
+ double dist_min = std::numeric_limits<double>::max();
+ // Round pt to a closest grid_cell corner.
+ Vec2crd grid_corner((pt(0)+(m_grid_resolution>>1))>>m_grid_log2, (pt(1)+(m_grid_resolution>>1))>>m_grid_log2);
+ // For four neighbors of grid_corner:
+ for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) {
+ for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) {
+ // Range of fragment starts around grid_corner, close to pt.
+ auto range = m_map.equal_range(Vec2crd(grid_corner(0) + neighbor_x, grid_corner(1) + neighbor_y));
+ // Find the map entry closest to pt.
+ for (auto it = range.first; it != range.second; ++it) {
+ const ValueType &value = it->second;
+ const Vec2crd *pt2 = m_point_accessor(value);
+ if (pt2 != nullptr) {
+ const double d2 = (pt - *pt2).squaredNorm();
+ if (d2 < dist_min) {
+ dist_min = d2;
+ value_min = &value;
+ }
+ }
+ }
+ }
+ }
+ return (value_min != nullptr && dist_min < coordf_t(m_search_radius * m_search_radius)) ?
+ std::make_pair(value_min, dist_min) :
+ std::make_pair(nullptr, std::numeric_limits<double>::max());
+ }
+
+private:
+ typedef typename std::unordered_multimap<Vec2crd, ValueType, PointHash> map_type;
+ PointAccessor m_point_accessor;
+ map_type m_map;
+ coord_t m_search_radius;
+ coord_t m_grid_resolution;
+ coord_t m_grid_log2;
+};
+
+std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf);
+
+} // namespace Slic3r
+
+// start Boost
+#include <boost/version.hpp>
+#include <boost/polygon/polygon.hpp>
+namespace boost { namespace polygon {
+ template <>
+ struct geometry_concept<Slic3r::Point> { typedef point_concept type; };
+
+ template <>
+ struct point_traits<Slic3r::Point> {
+ typedef coord_t coordinate_type;
+
+ static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) {
+ return (orient == HORIZONTAL) ? (coordinate_type)point(0) : (coordinate_type)point(1);
+ }
+ };
+
+ template <>
+ struct point_mutable_traits<Slic3r::Point> {
+ typedef coord_t coordinate_type;
+ static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) {
+ if (orient == HORIZONTAL)
+ point(0) = value;
+ else
+ point(1) = value;
+ }
+ static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) {
+ Slic3r::Point retval;
+ retval(0) = x_value;
+ retval(1) = y_value;
+ return retval;
+ }
+ };
+} }
+// end Boost
+
+#endif
diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp
new file mode 100644
index 000000000..cf0783bae
--- /dev/null
+++ b/src/libslic3r/Polygon.cpp
@@ -0,0 +1,459 @@
+#include "BoundingBox.hpp"
+#include "ClipperUtils.hpp"
+#include "Polygon.hpp"
+#include "Polyline.hpp"
+
+namespace Slic3r {
+
+Polygon::operator Polygons() const
+{
+ Polygons pp;
+ pp.push_back(*this);
+ return pp;
+}
+
+Polygon::operator Polyline() const
+{
+ return this->split_at_first_point();
+}
+
+Point&
+Polygon::operator[](Points::size_type idx)
+{
+ return this->points[idx];
+}
+
+const Point&
+Polygon::operator[](Points::size_type idx) const
+{
+ return this->points[idx];
+}
+
+Point
+Polygon::last_point() const
+{
+ return this->points.front(); // last point == first point for polygons
+}
+
+Lines Polygon::lines() const
+{
+ return to_lines(*this);
+}
+
+Polyline
+Polygon::split_at_vertex(const Point &point) const
+{
+ // find index of point
+ for (const Point &pt : this->points)
+ if (pt == point)
+ return this->split_at_index(&pt - &this->points.front());
+ throw std::invalid_argument("Point not found");
+ return Polyline();
+}
+
+// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
+Polyline
+Polygon::split_at_index(int index) const
+{
+ Polyline polyline;
+ polyline.points.reserve(this->points.size() + 1);
+ for (Points::const_iterator it = this->points.begin() + index; it != this->points.end(); ++it)
+ polyline.points.push_back(*it);
+ for (Points::const_iterator it = this->points.begin(); it != this->points.begin() + index + 1; ++it)
+ polyline.points.push_back(*it);
+ return polyline;
+}
+
+// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
+Polyline
+Polygon::split_at_first_point() const
+{
+ return this->split_at_index(0);
+}
+
+Points
+Polygon::equally_spaced_points(double distance) const
+{
+ return this->split_at_first_point().equally_spaced_points(distance);
+}
+
+/*
+int64_t Polygon::area2x() const
+{
+ size_t n = poly.size();
+ if (n < 3)
+ return 0;
+
+ int64_t a = 0;
+ for (size_t i = 0, j = n - 1; i < n; ++i)
+ a += int64_t(poly[j](0) + poly[i](0)) * int64_t(poly[j](1) - poly[i](1));
+ j = i;
+ }
+ return -a * 0.5;
+}
+*/
+
+double Polygon::area() const
+{
+ size_t n = points.size();
+ if (n < 3)
+ return 0.;
+
+ double a = 0.;
+ for (size_t i = 0, j = n - 1; i < n; ++i) {
+ a += ((double)points[j](0) + (double)points[i](0)) * ((double)points[i](1) - (double)points[j](1));
+ j = i;
+ }
+ return 0.5 * a;
+}
+
+bool
+Polygon::is_counter_clockwise() const
+{
+ return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this));
+}
+
+bool
+Polygon::is_clockwise() const
+{
+ return !this->is_counter_clockwise();
+}
+
+bool
+Polygon::make_counter_clockwise()
+{
+ if (!this->is_counter_clockwise()) {
+ this->reverse();
+ return true;
+ }
+ return false;
+}
+
+bool
+Polygon::make_clockwise()
+{
+ if (this->is_counter_clockwise()) {
+ this->reverse();
+ return true;
+ }
+ return false;
+}
+
+bool
+Polygon::is_valid() const
+{
+ return this->points.size() >= 3;
+}
+
+// Does an unoriented polygon contain a point?
+// Tested by counting intersections along a horizontal line.
+bool
+Polygon::contains(const Point &point) const
+{
+ // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
+ bool result = false;
+ Points::const_iterator i = this->points.begin();
+ Points::const_iterator j = this->points.end() - 1;
+ for (; i != this->points.end(); j = i++) {
+ //FIXME this test is not numerically robust. Particularly, it does not handle horizontal segments at y == point(1) well.
+ // Does the ray with y == point(1) intersect this line segment?
+#if 1
+ if ( (((*i)(1) > point(1)) != ((*j)(1) > point(1)))
+ && ((double)point(0) < (double)((*j)(0) - (*i)(0)) * (double)(point(1) - (*i)(1)) / (double)((*j)(1) - (*i)(1)) + (double)(*i)(0)) )
+ result = !result;
+#else
+ if (((*i)(1) > point(1)) != ((*j)(1) > point(1))) {
+ // Orientation predicated relative to i-th point.
+ double orient = (double)(point(0) - (*i)(0)) * (double)((*j)(1) - (*i)(1)) - (double)(point(1) - (*i)(1)) * (double)((*j)(0) - (*i)(0));
+ if (((*i)(1) > (*j)(1)) ? (orient > 0.) : (orient < 0.))
+ result = !result;
+ }
+#endif
+ }
+ return result;
+}
+
+// this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons()
+Polygons
+Polygon::simplify(double tolerance) const
+{
+ // repeat first point at the end in order to apply Douglas-Peucker
+ // on the whole polygon
+ Points points = this->points;
+ points.push_back(points.front());
+ Polygon p(MultiPoint::_douglas_peucker(points, tolerance));
+ p.points.pop_back();
+
+ Polygons pp;
+ pp.push_back(p);
+ return simplify_polygons(pp);
+}
+
+void
+Polygon::simplify(double tolerance, Polygons &polygons) const
+{
+ Polygons pp = this->simplify(tolerance);
+ polygons.reserve(polygons.size() + pp.size());
+ polygons.insert(polygons.end(), pp.begin(), pp.end());
+}
+
+// Only call this on convex polygons or it will return invalid results
+void
+Polygon::triangulate_convex(Polygons* polygons) const
+{
+ for (Points::const_iterator it = this->points.begin() + 2; it != this->points.end(); ++it) {
+ Polygon p;
+ p.points.reserve(3);
+ p.points.push_back(this->points.front());
+ p.points.push_back(*(it-1));
+ p.points.push_back(*it);
+
+ // this should be replaced with a more efficient call to a merge_collinear_segments() method
+ if (p.area() > 0) polygons->push_back(p);
+ }
+}
+
+// center of mass
+Point
+Polygon::centroid() const
+{
+ double area_temp = this->area();
+ double x_temp = 0;
+ double y_temp = 0;
+
+ Polyline polyline = this->split_at_first_point();
+ for (Points::const_iterator point = polyline.points.begin(); point != polyline.points.end() - 1; ++point) {
+ x_temp += (double)( point->x() + (point+1)->x() ) * ( (double)point->x()*(point+1)->y() - (double)(point+1)->x()*point->y() );
+ y_temp += (double)( point->y() + (point+1)->y() ) * ( (double)point->x()*(point+1)->y() - (double)(point+1)->x()*point->y() );
+ }
+
+ return Point(x_temp/(6*area_temp), y_temp/(6*area_temp));
+}
+
+// find all concave vertices (i.e. having an internal angle greater than the supplied angle)
+// (external = right side, thus we consider ccw orientation)
+Points
+Polygon::concave_points(double angle) const
+{
+ Points points;
+ angle = 2*PI - angle;
+
+ // check whether first point forms a concave angle
+ if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) <= angle)
+ points.push_back(this->points.front());
+
+ // check whether points 1..(n-1) form concave angles
+ for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) {
+ if (p->ccw_angle(*(p-1), *(p+1)) <= angle) points.push_back(*p);
+ }
+
+ // check whether last point forms a concave angle
+ if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) <= angle)
+ points.push_back(this->points.back());
+
+ return points;
+}
+
+// find all convex vertices (i.e. having an internal angle smaller than the supplied angle)
+// (external = right side, thus we consider ccw orientation)
+Points
+Polygon::convex_points(double angle) const
+{
+ Points points;
+ angle = 2*PI - angle;
+
+ // check whether first point forms a convex angle
+ if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle)
+ points.push_back(this->points.front());
+
+ // check whether points 1..(n-1) form convex angles
+ for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) {
+ if (p->ccw_angle(*(p-1), *(p+1)) >= angle) points.push_back(*p);
+ }
+
+ // check whether last point forms a convex angle
+ if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) >= angle)
+ points.push_back(this->points.back());
+
+ return points;
+}
+
+// Projection of a point onto the polygon.
+Point Polygon::point_projection(const Point &point) const
+{
+ Point proj = point;
+ double dmin = std::numeric_limits<double>::max();
+ if (! this->points.empty()) {
+ for (size_t i = 0; i < this->points.size(); ++ i) {
+ const Point &pt0 = this->points[i];
+ const Point &pt1 = this->points[(i + 1 == this->points.size()) ? 0 : i + 1];
+ double d = (point - pt0).cast<double>().norm();
+ if (d < dmin) {
+ dmin = d;
+ proj = pt0;
+ }
+ d = (point - pt1).cast<double>().norm();
+ if (d < dmin) {
+ dmin = d;
+ proj = pt1;
+ }
+ Vec2d v1(coordf_t(pt1(0) - pt0(0)), coordf_t(pt1(1) - pt0(1)));
+ coordf_t div = v1.squaredNorm();
+ if (div > 0.) {
+ Vec2d v2(coordf_t(point(0) - pt0(0)), coordf_t(point(1) - pt0(1)));
+ coordf_t t = v1.dot(v2) / div;
+ if (t > 0. && t < 1.) {
+ Point foot(coord_t(floor(coordf_t(pt0(0)) + t * v1(0) + 0.5)), coord_t(floor(coordf_t(pt0(1)) + t * v1(1) + 0.5)));
+ d = (point - foot).cast<double>().norm();
+ if (d < dmin) {
+ dmin = d;
+ proj = foot;
+ }
+ }
+ }
+ }
+ }
+ return proj;
+}
+
+BoundingBox get_extents(const Polygon &poly)
+{
+ return poly.bounding_box();
+}
+
+BoundingBox get_extents(const Polygons &polygons)
+{
+ BoundingBox bb;
+ if (! polygons.empty()) {
+ bb = get_extents(polygons.front());
+ for (size_t i = 1; i < polygons.size(); ++ i)
+ bb.merge(get_extents(polygons[i]));
+ }
+ return bb;
+}
+
+BoundingBox get_extents_rotated(const Polygon &poly, double angle)
+{
+ return get_extents_rotated(poly.points, angle);
+}
+
+BoundingBox get_extents_rotated(const Polygons &polygons, double angle)
+{
+ BoundingBox bb;
+ if (! polygons.empty()) {
+ bb = get_extents_rotated(polygons.front().points, angle);
+ for (size_t i = 1; i < polygons.size(); ++ i)
+ bb.merge(get_extents_rotated(polygons[i].points, angle));
+ }
+ return bb;
+}
+
+extern std::vector<BoundingBox> get_extents_vector(const Polygons &polygons)
+{
+ std::vector<BoundingBox> out;
+ out.reserve(polygons.size());
+ for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++ it)
+ out.push_back(get_extents(*it));
+ return out;
+}
+
+static inline bool is_stick(const Point &p1, const Point &p2, const Point &p3)
+{
+ Point v1 = p2 - p1;
+ Point v2 = p3 - p2;
+ int64_t dir = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1));
+ if (dir > 0)
+ // p3 does not turn back to p1. Do not remove p2.
+ return false;
+ double l2_1 = double(v1(0)) * double(v1(0)) + double(v1(1)) * double(v1(1));
+ double l2_2 = double(v2(0)) * double(v2(0)) + double(v2(1)) * double(v2(1));
+ if (dir == 0)
+ // p1, p2, p3 may make a perpendicular corner, or there is a zero edge length.
+ // Remove p2 if it is coincident with p1 or p2.
+ return l2_1 == 0 || l2_2 == 0;
+ // p3 turns back to p1 after p2. Are p1, p2, p3 collinear?
+ // Calculate distance from p3 to a segment (p1, p2) or from p1 to a segment(p2, p3),
+ // whichever segment is longer
+ double cross = double(v1(0)) * double(v2(1)) - double(v2(0)) * double(v1(1));
+ double dist2 = cross * cross / std::max(l2_1, l2_2);
+ return dist2 < EPSILON * EPSILON;
+}
+
+bool remove_sticks(Polygon &poly)
+{
+ bool modified = false;
+ size_t j = 1;
+ for (size_t i = 1; i + 1 < poly.points.size(); ++ i) {
+ if (! is_stick(poly[j-1], poly[i], poly[i+1])) {
+ // Keep the point.
+ if (j < i)
+ poly.points[j] = poly.points[i];
+ ++ j;
+ }
+ }
+ if (++ j < poly.points.size()) {
+ poly.points[j-1] = poly.points.back();
+ poly.points.erase(poly.points.begin() + j, poly.points.end());
+ modified = true;
+ }
+ while (poly.points.size() >= 3 && is_stick(poly.points[poly.points.size()-2], poly.points.back(), poly.points.front())) {
+ poly.points.pop_back();
+ modified = true;
+ }
+ while (poly.points.size() >= 3 && is_stick(poly.points.back(), poly.points.front(), poly.points[1]))
+ poly.points.erase(poly.points.begin());
+ return modified;
+}
+
+bool remove_sticks(Polygons &polys)
+{
+ bool modified = false;
+ size_t j = 0;
+ for (size_t i = 0; i < polys.size(); ++ i) {
+ modified |= remove_sticks(polys[i]);
+ if (polys[i].points.size() >= 3) {
+ if (j < i)
+ std::swap(polys[i].points, polys[j].points);
+ ++ j;
+ }
+ }
+ if (j < polys.size())
+ polys.erase(polys.begin() + j, polys.end());
+ return modified;
+}
+
+bool remove_degenerate(Polygons &polys)
+{
+ bool modified = false;
+ size_t j = 0;
+ for (size_t i = 0; i < polys.size(); ++ i) {
+ if (polys[i].points.size() >= 3) {
+ if (j < i)
+ std::swap(polys[i].points, polys[j].points);
+ ++ j;
+ } else
+ modified = true;
+ }
+ if (j < polys.size())
+ polys.erase(polys.begin() + j, polys.end());
+ return modified;
+}
+
+bool remove_small(Polygons &polys, double min_area)
+{
+ bool modified = false;
+ size_t j = 0;
+ for (size_t i = 0; i < polys.size(); ++ i) {
+ if (std::abs(polys[i].area()) >= min_area) {
+ if (j < i)
+ std::swap(polys[i].points, polys[j].points);
+ ++ j;
+ } else
+ modified = true;
+ }
+ if (j < polys.size())
+ polys.erase(polys.begin() + j, polys.end());
+ return modified;
+}
+
+}
diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp
new file mode 100644
index 000000000..63162d953
--- /dev/null
+++ b/src/libslic3r/Polygon.hpp
@@ -0,0 +1,268 @@
+#ifndef slic3r_Polygon_hpp_
+#define slic3r_Polygon_hpp_
+
+#include "libslic3r.h"
+#include <vector>
+#include <string>
+#include "Line.hpp"
+#include "MultiPoint.hpp"
+#include "Polyline.hpp"
+
+namespace Slic3r {
+
+class Polygon;
+typedef std::vector<Polygon> Polygons;
+
+class Polygon : public MultiPoint {
+public:
+ operator Polygons() const;
+ operator Polyline() const;
+ Point& operator[](Points::size_type idx);
+ const Point& operator[](Points::size_type idx) const;
+
+ Polygon() {}
+ explicit Polygon(const Points &points): MultiPoint(points) {}
+ Polygon(const Polygon &other) : MultiPoint(other.points) {}
+ Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {}
+ static Polygon new_scale(const std::vector<Vec2d> &points) {
+ Polygon pgn;
+ pgn.points.reserve(points.size());
+ for (const Vec2d &pt : points)
+ pgn.points.emplace_back(Point::new_scale(pt(0), pt(1)));
+ return pgn;
+ }
+ Polygon& operator=(const Polygon &other) { points = other.points; return *this; }
+ Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; }
+
+ Point last_point() const;
+ virtual Lines lines() const;
+ Polyline split_at_vertex(const Point &point) const;
+ // Split a closed polygon into an open polyline, with the split point duplicated at both ends.
+ Polyline split_at_index(int index) const;
+ // Split a closed polygon into an open polyline, with the split point duplicated at both ends.
+ Polyline split_at_first_point() const;
+ Points equally_spaced_points(double distance) const;
+ double area() const;
+ bool is_counter_clockwise() const;
+ bool is_clockwise() const;
+ bool make_counter_clockwise();
+ bool make_clockwise();
+ bool is_valid() const;
+ // Does an unoriented polygon contain a point?
+ // Tested by counting intersections along a horizontal line.
+ bool contains(const Point &point) const;
+ Polygons simplify(double tolerance) const;
+ void simplify(double tolerance, Polygons &polygons) const;
+ void triangulate_convex(Polygons* polygons) const;
+ Point centroid() const;
+ Points concave_points(double angle = PI) const;
+ Points convex_points(double angle = PI) const;
+ // Projection of a point onto the polygon.
+ Point point_projection(const Point &point) const;
+};
+
+extern BoundingBox get_extents(const Polygon &poly);
+extern BoundingBox get_extents(const Polygons &polygons);
+extern BoundingBox get_extents_rotated(const Polygon &poly, double angle);
+extern BoundingBox get_extents_rotated(const Polygons &polygons, double angle);
+extern std::vector<BoundingBox> get_extents_vector(const Polygons &polygons);
+
+inline double total_length(const Polygons &polylines) {
+ double total = 0;
+ for (Polygons::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
+ total += it->length();
+ return total;
+}
+
+// Remove sticks (tentacles with zero area) from the polygon.
+extern bool remove_sticks(Polygon &poly);
+extern bool remove_sticks(Polygons &polys);
+
+// Remove polygons with less than 3 edges.
+extern bool remove_degenerate(Polygons &polys);
+extern bool remove_small(Polygons &polys, double min_area);
+
+// Append a vector of polygons at the end of another vector of polygons.
+inline void polygons_append(Polygons &dst, const Polygons &src) { dst.insert(dst.end(), src.begin(), src.end()); }
+
+inline void polygons_append(Polygons &dst, Polygons &&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 polygons_rotate(Polygons &polys, double angle)
+{
+ const double cos_angle = cos(angle);
+ const double sin_angle = sin(angle);
+ for (Polygon &p : polys)
+ p.rotate(cos_angle, sin_angle);
+}
+
+inline void polygons_reverse(Polygons &polys)
+{
+ for (Polygon &p : polys)
+ p.reverse();
+}
+
+inline Points to_points(const Polygon &poly)
+{
+ return poly.points;
+}
+
+inline Points to_points(const Polygons &polys)
+{
+ size_t n_points = 0;
+ for (size_t i = 0; i < polys.size(); ++ i)
+ n_points += polys[i].points.size();
+ Points points;
+ points.reserve(n_points);
+ for (const Polygon &poly : polys)
+ append(points, poly.points);
+ return points;
+}
+
+inline Lines to_lines(const Polygon &poly)
+{
+ Lines lines;
+ lines.reserve(poly.points.size());
+ 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 Polygons &polys)
+{
+ size_t n_lines = 0;
+ for (size_t i = 0; i < polys.size(); ++ i)
+ n_lines += polys[i].points.size();
+ Lines lines;
+ lines.reserve(n_lines);
+ for (size_t i = 0; i < polys.size(); ++ i) {
+ const Polygon &poly = polys[i];
+ 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 Polylines to_polylines(const Polygons &polys)
+{
+ Polylines polylines;
+ polylines.assign(polys.size(), Polyline());
+ size_t idx = 0;
+ for (Polygons::const_iterator it = polys.begin(); it != polys.end(); ++ it) {
+ Polyline &pl = polylines[idx ++];
+ pl.points = it->points;
+ pl.points.push_back(it->points.front());
+ }
+ assert(idx == polylines.size());
+ return polylines;
+}
+
+inline Polylines to_polylines(Polygons &&polys)
+{
+ Polylines polylines;
+ polylines.assign(polys.size(), Polyline());
+ size_t idx = 0;
+ for (Polygons::const_iterator it = polys.begin(); it != polys.end(); ++ it) {
+ Polyline &pl = polylines[idx ++];
+ pl.points = std::move(it->points);
+ pl.points.push_back(it->points.front());
+ }
+ assert(idx == polylines.size());
+ return polylines;
+}
+
+} // Slic3r
+
+// start Boost
+#include <boost/polygon/polygon.hpp>
+namespace boost { namespace polygon {
+ template <>
+ struct geometry_concept<Slic3r::Polygon>{ typedef polygon_concept type; };
+
+ template <>
+ struct polygon_traits<Slic3r::Polygon> {
+ 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::Polygon& t) {
+ return t.points.begin();
+ }
+
+ // Get the end iterator
+ static inline iterator_type end_points(const Slic3r::Polygon& t) {
+ return t.points.end();
+ }
+
+ // Get the number of sides of the polygon
+ static inline std::size_t size(const Slic3r::Polygon& t) {
+ return t.points.size();
+ }
+
+ // Get the winding direction of the polygon
+ static inline winding_direction winding(const Slic3r::Polygon& /* t */) {
+ return unknown_winding;
+ }
+ };
+
+ template <>
+ struct polygon_mutable_traits<Slic3r::Polygon> {
+ // expects stl style iterators
+ template <typename iT>
+ static inline Slic3r::Polygon& set_points(Slic3r::Polygon& polygon, iT input_begin, iT input_end) {
+ polygon.points.clear();
+ while (input_begin != input_end) {
+ polygon.points.push_back(Slic3r::Point());
+ boost::polygon::assign(polygon.points.back(), *input_begin);
+ ++input_begin;
+ }
+ // skip last point since Boost will set last point = first point
+ polygon.points.pop_back();
+ return polygon;
+ }
+ };
+
+ template <>
+ struct geometry_concept<Slic3r::Polygons> { typedef polygon_set_concept type; };
+
+ //next we map to the concept through traits
+ template <>
+ struct polygon_set_traits<Slic3r::Polygons> {
+ typedef coord_t coordinate_type;
+ typedef Slic3r::Polygons::const_iterator iterator_type;
+ typedef Slic3r::Polygons operator_arg_type;
+
+ static inline iterator_type begin(const Slic3r::Polygons& polygon_set) {
+ return polygon_set.begin();
+ }
+
+ static inline iterator_type end(const Slic3r::Polygons& polygon_set) {
+ return polygon_set.end();
+ }
+
+ //don't worry about these, just return false from them
+ static inline bool clean(const Slic3r::Polygons& /* polygon_set */) { return false; }
+ static inline bool sorted(const Slic3r::Polygons& /* polygon_set */) { return false; }
+ };
+
+ template <>
+ struct polygon_set_mutable_traits<Slic3r::Polygons> {
+ template <typename input_iterator_type>
+ static inline void set(Slic3r::Polygons& polygons, input_iterator_type input_begin, input_iterator_type input_end) {
+ polygons.assign(input_begin, input_end);
+ }
+ };
+} }
+// end Boost
+
+#endif
diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp
new file mode 100644
index 000000000..af155468a
--- /dev/null
+++ b/src/libslic3r/Polyline.cpp
@@ -0,0 +1,256 @@
+#include "BoundingBox.hpp"
+#include "Polyline.hpp"
+#include "ExPolygon.hpp"
+#include "ExPolygonCollection.hpp"
+#include "Line.hpp"
+#include "Polygon.hpp"
+#include <iostream>
+#include <utility>
+
+namespace Slic3r {
+
+Polyline::operator Polylines() const
+{
+ Polylines polylines;
+ polylines.push_back(*this);
+ return polylines;
+}
+
+Polyline::operator Line() const
+{
+ if (this->points.size() > 2)
+ throw std::invalid_argument("Can't convert polyline with more than two points to a line");
+ return Line(this->points.front(), this->points.back());
+}
+
+Point
+Polyline::last_point() const
+{
+ return this->points.back();
+}
+
+Point
+Polyline::leftmost_point() const
+{
+ Point p = this->points.front();
+ for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) {
+ if ((*it)(0) < p(0)) p = *it;
+ }
+ return p;
+}
+
+Lines
+Polyline::lines() const
+{
+ Lines lines;
+ if (this->points.size() >= 2) {
+ lines.reserve(this->points.size() - 1);
+ for (Points::const_iterator it = this->points.begin(); it != this->points.end()-1; ++it) {
+ lines.push_back(Line(*it, *(it + 1)));
+ }
+ }
+ return lines;
+}
+
+// removes the given distance from the end of the polyline
+void Polyline::clip_end(double distance)
+{
+ while (distance > 0) {
+ Vec2d last_point = this->last_point().cast<double>();
+ this->points.pop_back();
+ if (this->points.empty())
+ break;
+ Vec2d v = this->last_point().cast<double>() - last_point;
+ double lsqr = v.squaredNorm();
+ if (lsqr > distance * distance) {
+ this->points.emplace_back((last_point + v * (distance / sqrt(lsqr))).cast<coord_t>());
+ return;
+ }
+ distance -= sqrt(lsqr);
+ }
+}
+
+// removes the given distance from the start of the polyline
+void Polyline::clip_start(double distance)
+{
+ this->reverse();
+ this->clip_end(distance);
+ if (this->points.size() >= 2)
+ this->reverse();
+}
+
+void Polyline::extend_end(double distance)
+{
+ // relocate last point by extending the last segment by the specified length
+ Vec2d v = (this->points.back() - *(this->points.end() - 2)).cast<double>().normalized();
+ this->points.back() += (v * distance).cast<coord_t>();
+}
+
+void Polyline::extend_start(double distance)
+{
+ // relocate first point by extending the first segment by the specified length
+ Vec2d v = (this->points.front() - this->points[1]).cast<double>().normalized();
+ this->points.front() += (v * distance).cast<coord_t>();
+}
+
+/* this method returns a collection of points picked on the polygon contour
+ so that they are evenly spaced according to the input distance */
+Points Polyline::equally_spaced_points(double distance) const
+{
+ Points points;
+ points.emplace_back(this->first_point());
+ double len = 0;
+
+ for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) {
+ Vec2d p1 = (it-1)->cast<double>();
+ Vec2d v = it->cast<double>() - p1;
+ double segment_length = v.norm();
+ len += segment_length;
+ if (len < distance)
+ continue;
+ if (len == distance) {
+ points.emplace_back(*it);
+ len = 0;
+ continue;
+ }
+ double take = segment_length - (len - distance); // how much we take of this segment
+ points.emplace_back((p1 + v * (take / v.norm())).cast<coord_t>());
+ -- it;
+ len = - take;
+ }
+ return points;
+}
+
+void Polyline::simplify(double tolerance)
+{
+ this->points = MultiPoint::_douglas_peucker(this->points, tolerance);
+}
+
+/* This method simplifies all *lines* contained in the supplied area */
+template <class T>
+void Polyline::simplify_by_visibility(const T &area)
+{
+ Points &pp = this->points;
+
+ size_t s = 0;
+ bool did_erase = false;
+ for (size_t i = s+2; i < pp.size(); i = s + 2) {
+ if (area.contains(Line(pp[s], pp[i]))) {
+ pp.erase(pp.begin() + s + 1, pp.begin() + i);
+ did_erase = true;
+ } else {
+ ++s;
+ }
+ }
+ if (did_erase)
+ this->simplify_by_visibility(area);
+}
+template void Polyline::simplify_by_visibility<ExPolygon>(const ExPolygon &area);
+template void Polyline::simplify_by_visibility<ExPolygonCollection>(const ExPolygonCollection &area);
+
+void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
+{
+ if (this->points.empty()) return;
+
+ // find the line to split at
+ size_t line_idx = 0;
+ Point p = this->first_point();
+ double min = (p - point).cast<double>().norm();
+ Lines lines = this->lines();
+ for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) {
+ Point p_tmp = point.projection_onto(*line);
+ if ((p_tmp - point).cast<double>().norm() < min) {
+ p = p_tmp;
+ min = (p - point).cast<double>().norm();
+ line_idx = line - lines.begin();
+ }
+ }
+
+ // create first half
+ p1->points.clear();
+ for (Lines::const_iterator line = lines.begin(); line != lines.begin() + line_idx + 1; ++line)
+ if (line->a != p)
+ p1->points.push_back(line->a);
+ // we add point instead of p because they might differ because of numerical issues
+ // and caller might want to rely on point belonging to result polylines
+ p1->points.push_back(point);
+
+ // create second half
+ p2->points.clear();
+ p2->points.push_back(point);
+ for (Lines::const_iterator line = lines.begin() + line_idx; line != lines.end(); ++line) {
+ p2->points.push_back(line->b);
+ }
+}
+
+bool Polyline::is_straight() const
+{
+ // Check that each segment's direction is equal to the line connecting
+ // first point and last point. (Checking each line against the previous
+ // one would cause the error to accumulate.)
+ double dir = Line(this->first_point(), this->last_point()).direction();
+ for (const auto &line: this->lines())
+ if (! line.parallel_to(dir))
+ return false;
+ return true;
+}
+
+BoundingBox get_extents(const Polyline &polyline)
+{
+ return polyline.bounding_box();
+}
+
+BoundingBox get_extents(const Polylines &polylines)
+{
+ BoundingBox bb;
+ if (! polylines.empty()) {
+ bb = polylines.front().bounding_box();
+ for (size_t i = 1; i < polylines.size(); ++ i)
+ bb.merge(polylines[i]);
+ }
+ return bb;
+}
+
+bool remove_degenerate(Polylines &polylines)
+{
+ bool modified = false;
+ size_t j = 0;
+ for (size_t i = 0; i < polylines.size(); ++ i) {
+ if (polylines[i].points.size() >= 2) {
+ if (j < i)
+ std::swap(polylines[i].points, polylines[j].points);
+ ++ j;
+ } else
+ modified = true;
+ }
+ if (j < polylines.size())
+ polylines.erase(polylines.begin() + j, polylines.end());
+ return modified;
+}
+
+ThickLines ThickPolyline::thicklines() const
+{
+ ThickLines lines;
+ if (this->points.size() >= 2) {
+ lines.reserve(this->points.size() - 1);
+ for (size_t i = 0; i + 1 < this->points.size(); ++ i)
+ lines.emplace_back(this->points[i], this->points[i + 1], this->width[2 * i], this->width[2 * i + 1]);
+ }
+ return lines;
+}
+
+Lines3 Polyline3::lines() const
+{
+ Lines3 lines;
+ if (points.size() >= 2)
+ {
+ lines.reserve(points.size() - 1);
+ for (Points3::const_iterator it = points.begin(); it != points.end() - 1; ++it)
+ {
+ lines.emplace_back(*it, *(it + 1));
+ }
+ }
+ return lines;
+}
+
+}
diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp
new file mode 100644
index 000000000..925b88aca
--- /dev/null
+++ b/src/libslic3r/Polyline.hpp
@@ -0,0 +1,157 @@
+#ifndef slic3r_Polyline_hpp_
+#define slic3r_Polyline_hpp_
+
+#include "libslic3r.h"
+#include "Line.hpp"
+#include "MultiPoint.hpp"
+#include <string>
+#include <vector>
+
+namespace Slic3r {
+
+class Polyline;
+class ThickPolyline;
+typedef std::vector<Polyline> Polylines;
+typedef std::vector<ThickPolyline> ThickPolylines;
+
+class Polyline : public MultiPoint {
+public:
+ Polyline() {};
+ Polyline(const Polyline &other) : MultiPoint(other.points) {}
+ Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {}
+ Polyline(std::initializer_list<Point> list) : MultiPoint(list) {}
+ explicit Polyline(const Point &p1, const Point &p2) { points.reserve(2); points.emplace_back(p1); points.emplace_back(p2); }
+ explicit Polyline(const Points &points) : MultiPoint(points) {}
+ explicit Polyline(Points &&points) : MultiPoint(std::move(points)) {}
+ Polyline& operator=(const Polyline &other) { points = other.points; return *this; }
+ Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; }
+ static Polyline new_scale(const std::vector<Vec2d> &points) {
+ Polyline pl;
+ pl.points.reserve(points.size());
+ for (const Vec2d &pt : points)
+ pl.points.emplace_back(Point::new_scale(pt(0), pt(1)));
+ return pl;
+ }
+
+ void append(const Point &point) { this->points.push_back(point); }
+ void append(const Points &src) { this->append(src.begin(), src.end()); }
+ void append(const Points::const_iterator &begin, const Points::const_iterator &end) { this->points.insert(this->points.end(), begin, end); }
+ void append(Points &&src)
+ {
+ if (this->points.empty()) {
+ this->points = std::move(src);
+ } else {
+ this->points.insert(this->points.end(), src.begin(), src.end());
+ src.clear();
+ }
+ }
+ void append(const Polyline &src)
+ {
+ points.insert(points.end(), src.points.begin(), src.points.end());
+ }
+
+ void append(Polyline &&src)
+ {
+ if (this->points.empty()) {
+ this->points = std::move(src.points);
+ } else {
+ this->points.insert(this->points.end(), src.points.begin(), src.points.end());
+ src.points.clear();
+ }
+ }
+
+ operator Polylines() const;
+ operator Line() const;
+ Point last_point() const;
+ Point leftmost_point() const;
+ virtual Lines lines() const;
+ void clip_end(double distance);
+ void clip_start(double distance);
+ void extend_end(double distance);
+ void extend_start(double distance);
+ Points equally_spaced_points(double distance) const;
+ void simplify(double tolerance);
+ template <class T> void simplify_by_visibility(const T &area);
+ void split_at(const Point &point, Polyline* p1, Polyline* p2) const;
+ bool is_straight() const;
+};
+
+extern BoundingBox get_extents(const Polyline &polyline);
+extern BoundingBox get_extents(const Polylines &polylines);
+
+inline double total_length(const Polylines &polylines) {
+ double total = 0;
+ for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
+ total += it->length();
+ return total;
+}
+
+inline Lines to_lines(const Polyline &poly)
+{
+ Lines lines;
+ if (poly.points.size() >= 2) {
+ lines.reserve(poly.points.size() - 1);
+ for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it)
+ lines.push_back(Line(*it, *(it + 1)));
+ }
+ return lines;
+}
+
+inline Lines to_lines(const Polylines &polys)
+{
+ size_t n_lines = 0;
+ for (size_t i = 0; i < polys.size(); ++ i)
+ if (polys[i].points.size() > 1)
+ n_lines += polys[i].points.size() - 1;
+ Lines lines;
+ lines.reserve(n_lines);
+ for (size_t i = 0; i < polys.size(); ++ i) {
+ const Polyline &poly = polys[i];
+ for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it)
+ lines.push_back(Line(*it, *(it + 1)));
+ }
+ return lines;
+}
+
+inline void polylines_append(Polylines &dst, const Polylines &src)
+{
+ dst.insert(dst.end(), src.begin(), src.end());
+}
+
+inline void polylines_append(Polylines &dst, Polylines &&src)
+{
+ if (dst.empty()) {
+ dst = std::move(src);
+ } else {
+ std::move(std::begin(src), std::end(src), std::back_inserter(dst));
+ src.clear();
+ }
+}
+
+bool remove_degenerate(Polylines &polylines);
+
+class ThickPolyline : public Polyline {
+public:
+ ThickPolyline() : endpoints(std::make_pair(false, false)) {}
+ ThickLines thicklines() const;
+ void reverse() {
+ Polyline::reverse();
+ std::reverse(this->width.begin(), this->width.end());
+ std::swap(this->endpoints.first, this->endpoints.second);
+ }
+
+ std::vector<coordf_t> width;
+ std::pair<bool,bool> endpoints;
+};
+
+class Polyline3 : public MultiPoint3
+{
+public:
+ virtual Lines3 lines() const;
+};
+
+typedef std::vector<Polyline3> Polylines3;
+
+}
+
+#endif
diff --git a/src/libslic3r/PolylineCollection.cpp b/src/libslic3r/PolylineCollection.cpp
new file mode 100644
index 000000000..1304161c3
--- /dev/null
+++ b/src/libslic3r/PolylineCollection.cpp
@@ -0,0 +1,92 @@
+#include "PolylineCollection.hpp"
+
+namespace Slic3r {
+
+struct Chaining
+{
+ Point first;
+ Point last;
+ size_t idx;
+};
+
+template<typename T>
+inline int nearest_point_index(const std::vector<Chaining> &pairs, const Point &start_near, bool no_reverse)
+{
+ T dmin = std::numeric_limits<T>::max();
+ int idx = 0;
+ for (std::vector<Chaining>::const_iterator it = pairs.begin(); it != pairs.end(); ++it) {
+ T d = sqr(T(start_near(0) - it->first(0)));
+ if (d <= dmin) {
+ d += sqr(T(start_near(1) - it->first(1)));
+ if (d < dmin) {
+ idx = (it - pairs.begin()) * 2;
+ dmin = d;
+ if (dmin < EPSILON)
+ break;
+ }
+ }
+ if (! no_reverse) {
+ d = sqr(T(start_near(0) - it->last(0)));
+ if (d <= dmin) {
+ d += sqr(T(start_near(1) - it->last(1)));
+ if (d < dmin) {
+ idx = (it - pairs.begin()) * 2 + 1;
+ dmin = d;
+ if (dmin < EPSILON)
+ break;
+ }
+ }
+ }
+ }
+ return idx;
+}
+
+Polylines PolylineCollection::_chained_path_from(
+ const Polylines &src,
+ Point start_near,
+ bool no_reverse,
+ bool move_from_src)
+{
+ std::vector<Chaining> endpoints;
+ endpoints.reserve(src.size());
+ for (size_t i = 0; i < src.size(); ++ i) {
+ Chaining c;
+ c.first = src[i].first_point();
+ if (! no_reverse)
+ c.last = src[i].last_point();
+ c.idx = i;
+ endpoints.push_back(c);
+ }
+ Polylines retval;
+ while (! endpoints.empty()) {
+ // find nearest point
+ int endpoint_index = nearest_point_index<double>(endpoints, start_near, no_reverse);
+ assert(endpoint_index >= 0 && endpoint_index < endpoints.size() * 2);
+ if (move_from_src) {
+ retval.push_back(std::move(src[endpoints[endpoint_index/2].idx]));
+ } else {
+ retval.push_back(src[endpoints[endpoint_index/2].idx]);
+ }
+ if (endpoint_index & 1)
+ retval.back().reverse();
+ endpoints.erase(endpoints.begin() + endpoint_index/2);
+ start_near = retval.back().last_point();
+ }
+ return retval;
+}
+
+Point PolylineCollection::leftmost_point(const Polylines &polylines)
+{
+ if (polylines.empty())
+ throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
+ Polylines::const_iterator it = polylines.begin();
+ Point p = it->leftmost_point();
+ for (++ it; it != polylines.end(); ++it) {
+ Point p2 = it->leftmost_point();
+ if (p2(0) < p(0))
+ p = p2;
+ }
+ return p;
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/PolylineCollection.hpp b/src/libslic3r/PolylineCollection.hpp
new file mode 100644
index 000000000..87fc1985b
--- /dev/null
+++ b/src/libslic3r/PolylineCollection.hpp
@@ -0,0 +1,47 @@
+#ifndef slic3r_PolylineCollection_hpp_
+#define slic3r_PolylineCollection_hpp_
+
+#include "libslic3r.h"
+#include "Polyline.hpp"
+
+namespace Slic3r {
+
+class PolylineCollection
+{
+ static Polylines _chained_path_from(
+ const Polylines &src,
+ Point start_near,
+ bool no_reverse,
+ bool move_from_src);
+
+public:
+ Polylines polylines;
+ void chained_path(PolylineCollection* retval, bool no_reverse = false) const
+ { retval->polylines = chained_path(this->polylines, no_reverse); }
+ void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const
+ { retval->polylines = chained_path_from(this->polylines, start_near, no_reverse); }
+ Point leftmost_point() const
+ { return leftmost_point(polylines); }
+ void append(const Polylines &polylines)
+ { this->polylines.insert(this->polylines.end(), polylines.begin(), polylines.end()); }
+
+ static Point leftmost_point(const Polylines &polylines);
+ static Polylines chained_path(Polylines &&src, bool no_reverse = false) {
+ return (src.empty() || src.front().points.empty()) ?
+ Polylines() :
+ _chained_path_from(src, src.front().first_point(), no_reverse, true);
+ }
+ static Polylines chained_path_from(Polylines &&src, Point start_near, bool no_reverse = false)
+ { return _chained_path_from(src, start_near, no_reverse, true); }
+ static Polylines chained_path(const Polylines &src, bool no_reverse = false) {
+ return (src.empty() || src.front().points.empty()) ?
+ Polylines() :
+ _chained_path_from(src, src.front().first_point(), no_reverse, false);
+ }
+ static Polylines chained_path_from(const Polylines &src, Point start_near, bool no_reverse = false)
+ { return _chained_path_from(src, start_near, no_reverse, false); }
+};
+
+}
+
+#endif
diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp
new file mode 100644
index 000000000..cdc12d2d1
--- /dev/null
+++ b/src/libslic3r/Print.cpp
@@ -0,0 +1,1370 @@
+#include "Print.hpp"
+#include "BoundingBox.hpp"
+#include "ClipperUtils.hpp"
+#include "Extruder.hpp"
+#include "Flow.hpp"
+#include "Geometry.hpp"
+#include "I18N.hpp"
+#include "SupportMaterial.hpp"
+#include "GCode.hpp"
+#include "GCode/WipeTowerPrusaMM.hpp"
+#include <algorithm>
+#include <unordered_set>
+#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/log/trivial.hpp>
+
+#include "PrintExport.hpp"
+
+//! macro used to mark string used at localization,
+//! return same string
+#define L(s) Slic3r::I18N::translate(s)
+
+namespace Slic3r {
+
+template class PrintState<PrintStep, psCount>;
+template class PrintState<PrintObjectStep, posCount>;
+
+void Print::clear_objects()
+{
+ tbb::mutex::scoped_lock lock(m_mutex);
+ for (PrintObject *object : m_objects)
+ delete object;
+ m_objects.clear();
+ for (PrintRegion *region : m_regions)
+ delete region;
+ m_regions.clear();
+ this->invalidate_all_steps();
+}
+
+void Print::delete_object(size_t idx)
+{
+ tbb::mutex::scoped_lock lock(m_mutex);
+ // destroy object and remove it from our container
+ delete m_objects[idx];
+ m_objects.erase(m_objects.begin() + idx);
+ this->invalidate_all_steps();
+ // TODO: purge unused regions
+}
+
+void Print::reload_object(size_t /* idx */)
+{
+ ModelObjectPtrs model_objects;
+ {
+ tbb::mutex::scoped_lock lock(m_mutex);
+ /* TODO: this method should check whether the per-object config and per-material configs
+ have changed in such a way that regions need to be rearranged or we can just apply
+ the diff and invalidate something. Same logic as apply_config()
+ For now we just re-add all objects since we haven't implemented this incremental logic yet.
+ This should also check whether object volumes (parts) have changed. */
+ // collect all current model objects
+ model_objects.reserve(m_objects.size());
+ for (PrintObject *object : m_objects)
+ model_objects.push_back(object->model_object());
+ // remove our print objects
+ for (PrintObject *object : m_objects)
+ delete object;
+ m_objects.clear();
+ for (PrintRegion *region : m_regions)
+ delete region;
+ m_regions.clear();
+ this->invalidate_all_steps();
+ }
+ // re-add model objects
+ for (ModelObject *mo : model_objects)
+ this->add_model_object(mo);
+}
+
+// Reloads the model instances into the print class.
+// The slicing shall not be running as the modified model instances at the print
+// are used for the brim & skirt calculation.
+// Returns true if the brim or skirt have been invalidated.
+bool Print::reload_model_instances()
+{
+ tbb::mutex::scoped_lock lock(m_mutex);
+ bool invalidated = false;
+ for (PrintObject *object : m_objects)
+ invalidated |= object->reload_model_instances();
+ return invalidated;
+}
+
+PrintObjectPtrs Print::get_printable_objects() const
+{
+ PrintObjectPtrs printable_objects(m_objects);
+ printable_objects.erase(std::remove_if(printable_objects.begin(), printable_objects.end(), [](PrintObject* o) { return !o->is_printable(); }), printable_objects.end());
+ return printable_objects;
+}
+
+PrintRegion* Print::add_region()
+{
+ m_regions.emplace_back(new PrintRegion(this));
+ return m_regions.back();
+}
+
+PrintRegion* Print::add_region(const PrintRegionConfig &config)
+{
+ m_regions.emplace_back(new PrintRegion(this, config));
+ return m_regions.back();
+}
+
+// Called by Print::apply_config().
+// This method only accepts PrintConfig option keys.
+bool Print::invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys)
+{
+ if (opt_keys.empty())
+ return false;
+
+ // Cache the plenty of parameters, which influence the G-code generator only,
+ // or they are only notes not influencing the generated G-code.
+ static std::unordered_set<std::string> steps_gcode = {
+ "avoid_crossing_perimeters",
+ "bed_shape",
+ "bed_temperature",
+ "before_layer_gcode",
+ "between_objects_gcode",
+ "bridge_acceleration",
+ "bridge_fan_speed",
+ "cooling",
+ "default_acceleration",
+ "deretract_speed",
+ "disable_fan_first_layers",
+ "duplicate_distance",
+ "end_gcode",
+ "end_filament_gcode",
+ "extrusion_axis",
+ "extruder_clearance_height",
+ "extruder_clearance_radius",
+ "extruder_colour",
+ "extruder_offset",
+ "extrusion_multiplier",
+ "fan_always_on",
+ "fan_below_layer_time",
+ "filament_colour",
+ "filament_diameter",
+ "filament_density",
+ "filament_notes",
+ "filament_cost",
+ "filament_max_volumetric_speed",
+ "first_layer_acceleration",
+ "first_layer_bed_temperature",
+ "first_layer_speed",
+ "gcode_comments",
+ "gcode_flavor",
+ "infill_acceleration",
+ "layer_gcode",
+ "min_fan_speed",
+ "max_fan_speed",
+ "max_print_height",
+ "min_print_speed",
+ "max_print_speed",
+ "max_volumetric_speed",
+ "max_volumetric_extrusion_rate_slope_positive",
+ "max_volumetric_extrusion_rate_slope_negative",
+ "notes",
+ "only_retract_when_crossing_perimeters",
+ "output_filename_format",
+ "perimeter_acceleration",
+ "post_process",
+ "printer_notes",
+ "retract_before_travel",
+ "retract_before_wipe",
+ "retract_layer_change",
+ "retract_length",
+ "retract_length_toolchange",
+ "retract_lift",
+ "retract_lift_above",
+ "retract_lift_below",
+ "retract_restart_extra",
+ "retract_restart_extra_toolchange",
+ "retract_speed",
+ "single_extruder_multi_material_priming",
+ "slowdown_below_layer_time",
+ "standby_temperature_delta",
+ "start_gcode",
+ "start_filament_gcode",
+ "toolchange_gcode",
+ "threads",
+ "travel_speed",
+ "use_firmware_retraction",
+ "use_relative_e_distances",
+ "use_volumetric_e",
+ "variable_layer_height",
+ "wipe",
+ "wipe_tower_x",
+ "wipe_tower_y",
+ "wipe_tower_rotation_angle"
+ };
+
+ static std::unordered_set<std::string> steps_ignore;
+
+ std::vector<PrintStep> steps;
+ std::vector<PrintObjectStep> osteps;
+ bool invalidated = false;
+
+ for (const t_config_option_key &opt_key : opt_keys) {
+ if (steps_gcode.find(opt_key) != steps_gcode.end()) {
+ // These options only affect G-code export or they are just notes without influence on the generated G-code,
+ // so there is nothing to invalidate.
+ steps.emplace_back(psGCodeExport);
+ } else if (steps_ignore.find(opt_key) != steps_ignore.end()) {
+ // These steps have no influence on the G-code whatsoever. Just ignore them.
+ } else if (
+ opt_key == "skirts"
+ || opt_key == "skirt_height"
+ || opt_key == "skirt_distance"
+ || opt_key == "min_skirt_length"
+ || opt_key == "ooze_prevention") {
+ steps.emplace_back(psSkirt);
+ } else if (opt_key == "brim_width") {
+ steps.emplace_back(psBrim);
+ steps.emplace_back(psSkirt);
+ } else if (
+ opt_key == "nozzle_diameter"
+ || opt_key == "resolution") {
+ osteps.emplace_back(posSlice);
+ } else if (
+ opt_key == "complete_objects"
+ || opt_key == "filament_type"
+ || opt_key == "filament_soluble"
+ || opt_key == "first_layer_temperature"
+ || opt_key == "filament_loading_speed"
+ || opt_key == "filament_loading_speed_start"
+ || opt_key == "filament_unloading_speed"
+ || opt_key == "filament_unloading_speed_start"
+ || opt_key == "filament_toolchange_delay"
+ || opt_key == "filament_cooling_moves"
+ || opt_key == "filament_minimal_purge_on_wipe_tower"
+ || opt_key == "filament_cooling_initial_speed"
+ || opt_key == "filament_cooling_final_speed"
+ || opt_key == "filament_ramming_parameters"
+ || opt_key == "gcode_flavor"
+ || opt_key == "infill_first"
+ || opt_key == "single_extruder_multi_material"
+ || opt_key == "spiral_vase"
+ || opt_key == "temperature"
+ || opt_key == "wipe_tower"
+ || opt_key == "wipe_tower_width"
+ || opt_key == "wipe_tower_bridging"
+ || opt_key == "wiping_volumes_matrix"
+ || opt_key == "parking_pos_retraction"
+ || opt_key == "cooling_tube_retraction"
+ || opt_key == "cooling_tube_length"
+ || opt_key == "extra_loading_move"
+ || opt_key == "z_offset") {
+ steps.emplace_back(psWipeTower);
+ } else if (
+ opt_key == "first_layer_extrusion_width"
+ || opt_key == "min_layer_height"
+ || opt_key == "max_layer_height") {
+ osteps.emplace_back(posPerimeters);
+ osteps.emplace_back(posInfill);
+ osteps.emplace_back(posSupportMaterial);
+ steps.emplace_back(psSkirt);
+ steps.emplace_back(psBrim);
+ } else {
+ // for legacy, if we can't handle this option let's invalidate all steps
+ //FIXME invalidate all steps of all objects as well?
+ invalidated |= this->invalidate_all_steps();
+ // Continue with the other opt_keys to possibly invalidate any object specific steps.
+ }
+ }
+
+ sort_remove_duplicates(steps);
+ for (PrintStep step : steps)
+ invalidated |= this->invalidate_step(step);
+ sort_remove_duplicates(osteps);
+ for (PrintObjectStep ostep : osteps)
+ for (PrintObject *object : m_objects)
+ invalidated |= object->invalidate_step(ostep);
+ return invalidated;
+}
+
+bool Print::invalidate_step(PrintStep step)
+{
+ bool invalidated = m_state.invalidate(step, m_mutex, m_cancel_callback);
+ // Propagate to dependent steps.
+ //FIXME Why should skirt invalidate brim? Shouldn't it be vice versa?
+ if (step == psSkirt)
+ invalidated |= m_state.invalidate(psBrim, m_mutex, m_cancel_callback);
+ return invalidated;
+}
+
+// returns true if an object step is done on all objects
+// and there's at least one object
+bool Print::is_step_done(PrintObjectStep step) const
+{
+ if (m_objects.empty())
+ return false;
+ for (const PrintObject *object : m_objects)
+ if (!object->m_state.is_done(step))
+ return false;
+ return true;
+}
+
+// returns 0-based indices of used extruders
+std::vector<unsigned int> Print::object_extruders() const
+{
+ std::vector<unsigned int> extruders;
+
+ for (PrintRegion* region : m_regions) {
+ // these checks reflect the same logic used in the GUI for enabling/disabling
+ // extruder selection fields
+ if (region->config().perimeters.value > 0 || m_config.brim_width.value > 0)
+ extruders.push_back(region->config().perimeter_extruder - 1);
+ if (region->config().fill_density.value > 0)
+ extruders.push_back(region->config().infill_extruder - 1);
+ if (region->config().top_solid_layers.value > 0 || region->config().bottom_solid_layers.value > 0)
+ extruders.push_back(region->config().solid_infill_extruder - 1);
+ }
+
+ sort_remove_duplicates(extruders);
+ return extruders;
+}
+
+// returns 0-based indices of used extruders
+std::vector<unsigned int> Print::support_material_extruders() const
+{
+ std::vector<unsigned int> extruders;
+ bool support_uses_current_extruder = false;
+
+ for (PrintObject *object : m_objects) {
+ if (object->has_support_material()) {
+ if (object->config().support_material_extruder == 0)
+ support_uses_current_extruder = true;
+ else
+ extruders.push_back(object->config().support_material_extruder - 1);
+ if (object->config().support_material_interface_extruder == 0)
+ support_uses_current_extruder = true;
+ else
+ extruders.push_back(object->config().support_material_interface_extruder - 1);
+ }
+ }
+
+ if (support_uses_current_extruder)
+ // Add all object extruders to the support extruders as it is not know which one will be used to print supports.
+ append(extruders, this->object_extruders());
+
+ sort_remove_duplicates(extruders);
+ return extruders;
+}
+
+// returns 0-based indices of used extruders
+std::vector<unsigned int> Print::extruders() const
+{
+ std::vector<unsigned int> extruders = this->object_extruders();
+ append(extruders, this->support_material_extruders());
+ sort_remove_duplicates(extruders);
+ return extruders;
+}
+
+void Print::_simplify_slices(double distance)
+{
+ for (PrintObject *object : m_objects) {
+ for (Layer *layer : object->m_layers) {
+ layer->slices.simplify(distance);
+ for (LayerRegion *layerm : layer->regions())
+ layerm->slices.simplify(distance);
+ }
+ }
+}
+
+double Print::max_allowed_layer_height() const
+{
+ double nozzle_diameter_max = 0.;
+ for (unsigned int extruder_id : this->extruders())
+ nozzle_diameter_max = std::max(nozzle_diameter_max, m_config.nozzle_diameter.get_at(extruder_id));
+ return nozzle_diameter_max;
+}
+
+// Caller is responsible for supplying models whose objects don't collide
+// and have explicit instance positions.
+void Print::add_model_object(ModelObject* model_object, int idx)
+{
+ tbb::mutex::scoped_lock lock(m_mutex);
+ // Initialize a new print object and store it at the given position.
+ PrintObject *object = new PrintObject(this, model_object, model_object->raw_bounding_box());
+ if (idx != -1) {
+ delete m_objects[idx];
+ m_objects[idx] = object;
+ } else
+ m_objects.emplace_back(object);
+ // Invalidate all print steps.
+ //FIXME lock mutex!
+ this->invalidate_all_steps();
+
+ size_t volume_id = 0;
+ for (const ModelVolume *volume : model_object->volumes) {
+ if (! volume->is_model_part() && ! volume->is_modifier())
+ continue;
+ // Get the config applied to this volume.
+ PrintRegionConfig config = this->_region_config_from_model_volume(*volume);
+ // Find an existing print region with the same config.
+ size_t region_id = size_t(-1);
+ for (size_t i = 0; i < m_regions.size(); ++ i)
+ if (config.equals(m_regions[i]->config())) {
+ region_id = i;
+ break;
+ }
+ // If no region exists with the same config, create a new one.
+ if (region_id == size_t(-1)) {
+ region_id = m_regions.size();
+ this->add_region(config);
+ }
+ // Assign volume to a region.
+ object->add_region_volume(region_id, volume_id);
+ ++ volume_id;
+ }
+
+ // Apply config to print object.
+ object->config_apply(this->default_object_config());
+ {
+ //normalize_and_apply_config(object->config(), model_object->config);
+ DynamicPrintConfig src_normalized(model_object->config);
+ src_normalized.normalize();
+ object->config_apply(src_normalized, true);
+ }
+
+ // update placeholders
+ {
+ // get the first input file name
+ std::string input_file;
+ std::vector<std::string> v_scale;
+ for (const PrintObject *object : m_objects) {
+ const ModelObject &mobj = *object->model_object();
+ v_scale.push_back(boost::lexical_cast<std::string>(mobj.instances[0]->scaling_factor*100) + "%");
+ if (input_file.empty())
+ input_file = mobj.input_file;
+ }
+
+ PlaceholderParser &pp = m_placeholder_parser;
+ pp.set("scale", v_scale);
+ if (! input_file.empty()) {
+ // get basename with and without suffix
+ const std::string input_basename = boost::filesystem::path(input_file).filename().string();
+ pp.set("input_filename", input_basename);
+ const std::string input_basename_base = input_basename.substr(0, input_basename.find_last_of("."));
+ pp.set("input_filename_base", input_basename_base);
+ }
+ }
+}
+
+bool Print::apply_config(DynamicPrintConfig config)
+{
+ tbb::mutex::scoped_lock lock(m_mutex);
+
+ // we get a copy of the config object so we can modify it safely
+ config.normalize();
+
+ // apply variables to placeholder parser
+ m_placeholder_parser.apply_config(config);
+
+ // handle changes to print config
+ t_config_option_keys print_diff = m_config.diff(config);
+ m_config.apply_only(config, print_diff, true);
+ bool invalidated = this->invalidate_state_by_config_options(print_diff);
+
+ // handle changes to object config defaults
+ m_default_object_config.apply(config, true);
+ for (PrintObject *object : m_objects) {
+ // we don't assume that config contains a full ObjectConfig,
+ // so we base it on the current print-wise default
+ PrintObjectConfig new_config = this->default_object_config();
+ // we override the new config with object-specific options
+ normalize_and_apply_config(new_config, object->model_object()->config);
+ // Force a refresh of a variable layer height profile at the PrintObject if it is not valid.
+ if (! object->layer_height_profile_valid) {
+ // The layer_height_profile is not valid for some reason (updated by the user or invalidated due to some option change).
+ // Invalidate the slicing step, which in turn invalidates everything.
+ object->invalidate_step(posSlice);
+ // Trigger recalculation.
+ invalidated = true;
+ }
+ // check whether the new config is different from the current one
+ t_config_option_keys diff = object->config().diff(new_config);
+ object->config_apply_only(new_config, diff, true);
+ invalidated |= object->invalidate_state_by_config_options(diff);
+ }
+
+ // handle changes to regions config defaults
+ m_default_region_config.apply(config, true);
+
+ // All regions now have distinct settings.
+ // Check whether applying the new region config defaults we'd get different regions.
+ bool rearrange_regions = false;
+ {
+ // Collect the already visited region configs into other_region_configs,
+ // so one may check for duplicates.
+ std::vector<PrintRegionConfig> other_region_configs;
+ for (size_t region_id = 0; region_id < m_regions.size(); ++ region_id) {
+ PrintRegion &region = *m_regions[region_id];
+ PrintRegionConfig this_region_config;
+ bool this_region_config_set = false;
+ for (PrintObject *object : m_objects) {
+ if (region_id < object->region_volumes.size()) {
+ for (int volume_id : object->region_volumes[region_id]) {
+ const ModelVolume &volume = *object->model_object()->volumes[volume_id];
+ if (this_region_config_set) {
+ // If the new config for this volume differs from the other
+ // volume configs currently associated to this region, it means
+ // the region subdivision does not make sense anymore.
+ if (! this_region_config.equals(this->_region_config_from_model_volume(volume))) {
+ rearrange_regions = true;
+ goto exit_for_rearrange_regions;
+ }
+ } else {
+ this_region_config = this->_region_config_from_model_volume(volume);
+ this_region_config_set = true;
+ }
+ for (const PrintRegionConfig &cfg : other_region_configs) {
+ // If the new config for this volume equals any of the other
+ // volume configs that are not currently associated to this
+ // region, it means the region subdivision does not make
+ // sense anymore.
+ if (cfg.equals(this_region_config)) {
+ rearrange_regions = true;
+ goto exit_for_rearrange_regions;
+ }
+ }
+ }
+ }
+ }
+ if (this_region_config_set) {
+ t_config_option_keys diff = region.config().diff(this_region_config);
+ if (! diff.empty()) {
+ region.config_apply_only(this_region_config, diff, false);
+ for (PrintObject *object : m_objects)
+ if (region_id < object->region_volumes.size() && ! object->region_volumes[region_id].empty())
+ invalidated |= object->invalidate_state_by_config_options(diff);
+ }
+ other_region_configs.emplace_back(std::move(this_region_config));
+ }
+ }
+ }
+
+exit_for_rearrange_regions:
+
+ if (rearrange_regions) {
+ // The current subdivision of regions does not make sense anymore.
+ // We need to remove all objects and re-add them.
+ ModelObjectPtrs model_objects;
+ model_objects.reserve(m_objects.size());
+ for (PrintObject *object : m_objects)
+ model_objects.push_back(object->model_object());
+ this->clear_objects();
+ for (ModelObject *mo : model_objects)
+ this->add_model_object(mo);
+ invalidated = true;
+ }
+
+ // Always make sure that the layer_height_profiles are set, as they should not be modified from the worker threads.
+ for (PrintObject *object : m_objects)
+ if (! object->layer_height_profile_valid)
+ object->update_layer_height_profile();
+
+ return invalidated;
+}
+
+bool Print::has_infinite_skirt() const
+{
+ return (m_config.skirt_height == -1 && m_config.skirts > 0)
+ || (m_config.ooze_prevention && this->extruders().size() > 1);
+}
+
+bool Print::has_skirt() const
+{
+ return (m_config.skirt_height > 0 && m_config.skirts > 0)
+ || this->has_infinite_skirt();
+}
+
+std::string Print::validate() const
+{
+ BoundingBox bed_box_2D = get_extents(Polygon::new_scale(m_config.bed_shape.values));
+ BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(m_config.max_print_height)));
+ // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced.
+ print_volume.min(2) = -1e10;
+ unsigned int printable_count = 0;
+ {
+ // Lock due to the po->reload_model_instances()
+ tbb::mutex::scoped_lock lock(m_mutex);
+ for (PrintObject *po : m_objects) {
+ po->model_object()->check_instances_print_volume_state(print_volume);
+ po->reload_model_instances();
+ if (po->is_printable())
+ ++ printable_count;
+ }
+ }
+
+ if (printable_count == 0)
+ return L("All objects are outside of the print volume.");
+
+ if (m_config.complete_objects) {
+ // Check horizontal clearance.
+ {
+ Polygons convex_hulls_other;
+ for (PrintObject *object : m_objects) {
+ // Get convex hull of all meshes assigned to this print object.
+ Polygon convex_hull;
+ {
+ Polygons mesh_convex_hulls;
+ for (const std::vector<int> &volumes : object->region_volumes)
+ for (int volume_id : volumes)
+ mesh_convex_hulls.emplace_back(object->model_object()->volumes[volume_id]->mesh.convex_hull());
+ // make a single convex hull for all of them
+ convex_hull = Slic3r::Geometry::convex_hull(mesh_convex_hulls);
+ }
+ // Apply the same transformations we apply to the actual meshes when slicing them.
+ object->model_object()->instances.front()->transform_polygon(&convex_hull);
+ // Grow convex hull with the clearance margin.
+ convex_hull = offset(convex_hull, scale_(m_config.extruder_clearance_radius.value)/2, jtRound, scale_(0.1)).front();
+ // Now we check that no instance of convex_hull intersects any of the previously checked object instances.
+ for (const Point &copy : object->m_copies) {
+ Polygon p = convex_hull;
+ p.translate(copy);
+ if (! intersection(convex_hulls_other, p).empty())
+ return L("Some objects are too close; your extruder will collide with them.");
+ polygons_append(convex_hulls_other, p);
+ }
+ }
+ }
+ // Check vertical clearance.
+ {
+ std::vector<coord_t> object_height;
+ for (const PrintObject *object : m_objects)
+ object_height.insert(object_height.end(), object->copies().size(), object->size(2));
+ std::sort(object_height.begin(), object_height.end());
+ // Ignore the tallest *copy* (this is why we repeat height for all of them):
+ // it will be printed as last one so its height doesn't matter.
+ object_height.pop_back();
+ if (! object_height.empty() && object_height.back() > scale_(m_config.extruder_clearance_height.value))
+ return L("Some objects are too tall and cannot be printed without extruder collisions.");
+ }
+ } // end if (m_config.complete_objects)
+
+ if (m_config.spiral_vase) {
+ size_t total_copies_count = 0;
+ for (const PrintObject *object : m_objects)
+ total_copies_count += object->copies().size();
+ // #4043
+ if (total_copies_count > 1 && ! m_config.complete_objects.value)
+ return L("The Spiral Vase option can only be used when printing a single object.");
+ if (m_regions.size() > 1)
+ return L("The Spiral Vase option can only be used when printing single material objects.");
+ }
+
+ if (m_config.single_extruder_multi_material) {
+ for (size_t i=1; i<m_config.nozzle_diameter.values.size(); ++i)
+ if (m_config.nozzle_diameter.values[i] != m_config.nozzle_diameter.values[i-1])
+ return L("All extruders must have the same diameter for single extruder multimaterial printer.");
+ }
+
+ if (this->has_wipe_tower() && ! m_objects.empty()) {
+ if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfMarlin)
+ return L("The Wipe Tower is currently only supported for the Marlin and RepRap/Sprinter G-code flavors.");
+ if (! m_config.use_relative_e_distances)
+ return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1).");
+ SlicingParameters slicing_params0 = m_objects.front()->slicing_parameters();
+
+ const PrintObject* tallest_object = m_objects.front(); // let's find the tallest object
+ for (const auto* object : m_objects)
+ if (*(object->layer_height_profile.end()-2) > *(tallest_object->layer_height_profile.end()-2) )
+ tallest_object = object;
+
+ for (PrintObject *object : m_objects) {
+ SlicingParameters slicing_params = object->slicing_parameters();
+ if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON ||
+ std::abs(slicing_params.layer_height - slicing_params0.layer_height ) > EPSILON)
+ return L("The Wipe Tower is only supported for multiple objects if they have equal layer heigths");
+ if (slicing_params.raft_layers() != slicing_params0.raft_layers())
+ return L("The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers");
+ if (object->config().support_material_contact_distance != m_objects.front()->config().support_material_contact_distance)
+ return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance");
+ if (! equal_layering(slicing_params, slicing_params0))
+ return L("The Wipe Tower is only supported for multiple objects if they are sliced equally.");
+ bool was_layer_height_profile_valid = object->layer_height_profile_valid;
+ object->update_layer_height_profile();
+ object->layer_height_profile_valid = was_layer_height_profile_valid;
+
+ if ( m_config.variable_layer_height ) { // comparing layer height profiles
+ bool failed = false;
+ if (tallest_object->layer_height_profile.size() >= object->layer_height_profile.size() ) {
+ int i = 0;
+ while ( i < object->layer_height_profile.size() && i < tallest_object->layer_height_profile.size()) {
+ if (std::abs(tallest_object->layer_height_profile[i] - object->layer_height_profile[i])) {
+ failed = true;
+ break;
+ }
+ ++i;
+ if (i == object->layer_height_profile.size()-2) // this element contains this objects max z
+ if (tallest_object->layer_height_profile[i] > object->layer_height_profile[i]) // the difference does not matter in this case
+ ++i;
+ }
+ }
+ else
+ failed = true;
+
+ if (failed)
+ return L("The Wipe tower is only supported if all objects have the same layer height profile");
+ }
+ }
+ }
+
+ {
+ // find the smallest nozzle diameter
+ std::vector<unsigned int> extruders = this->extruders();
+ if (extruders.empty())
+ return L("The supplied settings will cause an empty print.");
+
+ std::vector<double> nozzle_diameters;
+ for (unsigned int extruder_id : extruders)
+ nozzle_diameters.push_back(m_config.nozzle_diameter.get_at(extruder_id));
+ double min_nozzle_diameter = *std::min_element(nozzle_diameters.begin(), nozzle_diameters.end());
+ unsigned int total_extruders_count = m_config.nozzle_diameter.size();
+ for (const auto& extruder_idx : extruders)
+ if ( extruder_idx >= total_extruders_count )
+ return L("One or more object were assigned an extruder that the printer does not have.");
+
+ for (PrintObject *object : m_objects) {
+ if ((object->config().support_material_extruder == -1 || object->config().support_material_interface_extruder == -1) &&
+ (object->config().raft_layers > 0 || object->config().support_material.value)) {
+ // The object has some form of support and either support_material_extruder or support_material_interface_extruder
+ // will be printed with the current tool without a forced tool change. Play safe, assert that all object nozzles
+ // are of the same diameter.
+ if (nozzle_diameters.size() > 1)
+ return L("Printing with multiple extruders of differing nozzle diameters. "
+ "If support is to be printed with the current extruder (support_material_extruder == 0 or support_material_interface_extruder == 0), "
+ "all nozzles have to be of the same diameter.");
+ }
+
+ // validate first_layer_height
+ double first_layer_height = object->config().get_abs_value(L("first_layer_height"));
+ double first_layer_min_nozzle_diameter;
+ if (object->config().raft_layers > 0) {
+ // if we have raft layers, only support material extruder is used on first layer
+ size_t first_layer_extruder = object->config().raft_layers == 1
+ ? object->config().support_material_interface_extruder-1
+ : object->config().support_material_extruder-1;
+ first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ?
+ min_nozzle_diameter :
+ m_config.nozzle_diameter.get_at(first_layer_extruder);
+ } else {
+ // if we don't have raft layers, any nozzle diameter is potentially used in first layer
+ first_layer_min_nozzle_diameter = min_nozzle_diameter;
+ }
+ if (first_layer_height > first_layer_min_nozzle_diameter)
+ return L("First layer height can't be greater than nozzle diameter");
+
+ // validate layer_height
+ if (object->config().layer_height.value > min_nozzle_diameter)
+ return L("Layer height can't be greater than nozzle diameter");
+ }
+ }
+
+ return std::string();
+}
+
+// the bounding box of objects placed in copies position
+// (without taking skirt/brim/support material into account)
+BoundingBox Print::bounding_box() const
+{
+ BoundingBox bb;
+ for (const PrintObject *object : m_objects)
+ for (Point copy : object->m_copies) {
+ bb.merge(copy);
+ copy += to_2d(object->size);
+ bb.merge(copy);
+ }
+ return bb;
+}
+
+// the total bounding box of extrusions, including skirt/brim/support material
+// this methods needs to be called even when no steps were processed, so it should
+// only use configuration values
+BoundingBox Print::total_bounding_box() const
+{
+ // get objects bounding box
+ BoundingBox bb = this->bounding_box();
+
+ // we need to offset the objects bounding box by at least half the perimeters extrusion width
+ Flow perimeter_flow = m_objects.front()->get_layer(0)->get_region(0)->flow(frPerimeter);
+ double extra = perimeter_flow.width/2;
+
+ // consider support material
+ if (this->has_support_material()) {
+ extra = std::max(extra, SUPPORT_MATERIAL_MARGIN);
+ }
+
+ // consider brim and skirt
+ if (m_config.brim_width.value > 0) {
+ Flow brim_flow = this->brim_flow();
+ extra = std::max(extra, m_config.brim_width.value + brim_flow.width/2);
+ }
+ if (this->has_skirt()) {
+ int skirts = m_config.skirts.value;
+ if (skirts == 0 && this->has_infinite_skirt()) skirts = 1;
+ Flow skirt_flow = this->skirt_flow();
+ extra = std::max(
+ extra,
+ m_config.brim_width.value
+ + m_config.skirt_distance.value
+ + skirts * skirt_flow.spacing()
+ + skirt_flow.width/2
+ );
+ }
+
+ if (extra > 0)
+ bb.offset(scale_(extra));
+
+ return bb;
+}
+
+double Print::skirt_first_layer_height() const
+{
+ if (m_objects.empty())
+ throw std::invalid_argument("skirt_first_layer_height() can't be called without PrintObjects");
+ return m_objects.front()->config().get_abs_value("first_layer_height");
+}
+
+Flow Print::brim_flow() const
+{
+ ConfigOptionFloatOrPercent width = m_config.first_layer_extrusion_width;
+ if (width.value == 0)
+ width = m_regions.front()->config().perimeter_extrusion_width;
+ if (width.value == 0)
+ width = m_objects.front()->config().extrusion_width;
+
+ /* We currently use a random region's perimeter extruder.
+ While this works for most cases, we should probably consider all of the perimeter
+ extruders and take the one with, say, the smallest index.
+ The same logic should be applied to the code that selects the extruder during G-code
+ generation as well. */
+ return Flow::new_from_config_width(
+ frPerimeter,
+ width,
+ m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1),
+ this->skirt_first_layer_height(),
+ 0
+ );
+}
+
+Flow Print::skirt_flow() const
+{
+ ConfigOptionFloatOrPercent width = m_config.first_layer_extrusion_width;
+ if (width.value == 0)
+ width = m_regions.front()->config().perimeter_extrusion_width;
+ if (width.value == 0)
+ width = m_objects.front()->config().extrusion_width;
+
+ /* We currently use a random object's support material extruder.
+ While this works for most cases, we should probably consider all of the support material
+ extruders and take the one with, say, the smallest index;
+ The same logic should be applied to the code that selects the extruder during G-code
+ generation as well. */
+ return Flow::new_from_config_width(
+ frPerimeter,
+ width,
+ m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1),
+ this->skirt_first_layer_height(),
+ 0
+ );
+}
+
+PrintRegionConfig Print::_region_config_from_model_volume(const ModelVolume &volume)
+{
+ PrintRegionConfig config = this->default_region_config();
+ normalize_and_apply_config(config, volume.get_object()->config);
+ normalize_and_apply_config(config, volume.config);
+ if (! volume.material_id().empty())
+ normalize_and_apply_config(config, volume.material()->config);
+ return config;
+}
+
+bool Print::has_support_material() const
+{
+ for (const PrintObject *object : m_objects)
+ if (object->has_support_material())
+ return true;
+ return false;
+}
+
+/* This method assigns extruders to the volumes having a material
+ but not having extruders set in the volume config. */
+void Print::auto_assign_extruders(ModelObject* model_object) const
+{
+ // only assign extruders if object has more than one volume
+ if (model_object->volumes.size() < 2)
+ return;
+
+// size_t extruders = m_config.nozzle_diameter.values.size();
+ for (size_t volume_id = 0; volume_id < model_object->volumes.size(); ++ volume_id) {
+ ModelVolume *volume = model_object->volumes[volume_id];
+ //FIXME Vojtech: This assigns an extruder ID even to a modifier volume, if it has a material assigned.
+ if ((volume->is_model_part() || volume->is_modifier()) && ! volume->material_id().empty() && ! volume->config.has("extruder"))
+ volume->config.opt<ConfigOptionInt>("extruder", true)->value = int(volume_id + 1);
+ }
+}
+
+// Slicing process, running at a background thread.
+void Print::process()
+{
+ BOOST_LOG_TRIVIAL(info) << "Staring the slicing process.";
+ for (PrintObject *obj : m_objects)
+ obj->make_perimeters();
+ this->throw_if_canceled();
+ this->set_status(70, "Infilling layers");
+ for (PrintObject *obj : m_objects)
+ obj->infill();
+ this->throw_if_canceled();
+ for (PrintObject *obj : m_objects)
+ obj->generate_support_material();
+ this->throw_if_canceled();
+ if (! m_state.is_done(psSkirt)) {
+ this->set_started(psSkirt);
+ m_skirt.clear();
+ if (this->has_skirt()) {
+ this->set_status(88, "Generating skirt");
+ this->_make_skirt();
+ }
+ this->set_done(psSkirt);
+ }
+ this->throw_if_canceled();
+ if (! m_state.is_done(psBrim)) {
+ this->set_started(psBrim);
+ m_brim.clear();
+ if (m_config.brim_width > 0) {
+ this->set_status(88, "Generating brim");
+ this->_make_brim();
+ }
+ this->set_done(psBrim);
+ }
+ this->throw_if_canceled();
+ if (! m_state.is_done(psWipeTower)) {
+ this->set_started(psWipeTower);
+ m_wipe_tower_data.clear();
+ if (this->has_wipe_tower()) {
+ //this->set_status(95, "Generating wipe tower");
+ this->_make_wipe_tower();
+ }
+ this->set_done(psWipeTower);
+ }
+ BOOST_LOG_TRIVIAL(info) << "Slicing process finished.";
+}
+
+// G-code export process, running at a background thread.
+// The export_gcode may die for various reasons (fails to process output_filename_format,
+// write error into the G-code, cannot execute post-processing scripts).
+// It is up to the caller to show an error message.
+void Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data)
+{
+ // prerequisites
+ this->process();
+
+ // output everything to a G-code file
+ // The following call may die if the output_filename_format template substitution fails.
+ std::string path = this->output_filepath(path_template);
+ std::string message = "Exporting G-code";
+ if (! path.empty()) {
+ message += " to ";
+ message += path;
+ }
+ this->set_status(90, message);
+
+ // The following line may die for multiple reasons.
+ GCode gcode;
+ gcode.do_export(this, path.c_str(), preview_data);
+}
+
+void Print::_make_skirt()
+{
+ // First off we need to decide how tall the skirt must be.
+ // The skirt_height option from config is expressed in layers, but our
+ // object might have different layer heights, so we need to find the print_z
+ // of the highest layer involved.
+ // Note that unless has_infinite_skirt() == true
+ // the actual skirt might not reach this $skirt_height_z value since the print
+ // order of objects on each layer is not guaranteed and will not generally
+ // include the thickest object first. It is just guaranteed that a skirt is
+ // prepended to the first 'n' layers (with 'n' = skirt_height).
+ // $skirt_height_z in this case is the highest possible skirt height for safety.
+ coordf_t skirt_height_z = 0.;
+ PrintObjectPtrs printable_objects = get_printable_objects();
+ for (const PrintObject *object : printable_objects) {
+ size_t skirt_layers = this->has_infinite_skirt() ?
+ object->layer_count() :
+ std::min(size_t(m_config.skirt_height.value), object->layer_count());
+ skirt_height_z = std::max(skirt_height_z, object->m_layers[skirt_layers-1]->print_z);
+ }
+
+ // Collect points from all layers contained in skirt height.
+ Points points;
+ for (const PrintObject *object : printable_objects) {
+ Points object_points;
+ // Get object layers up to skirt_height_z.
+ for (const Layer *layer : object->m_layers) {
+ if (layer->print_z > skirt_height_z)
+ break;
+ for (const ExPolygon &expoly : layer->slices.expolygons)
+ // Collect the outer contour points only, ignore holes for the calculation of the convex hull.
+ append(object_points, expoly.contour.points);
+ }
+ // Get support layers up to skirt_height_z.
+ for (const SupportLayer *layer : object->support_layers()) {
+ if (layer->print_z > skirt_height_z)
+ break;
+ for (const ExtrusionEntity *extrusion_entity : layer->support_fills.entities)
+ append(object_points, extrusion_entity->as_polyline().points);
+ }
+ // Repeat points for each object copy.
+ for (const Point &shift : object->m_copies) {
+ Points copy_points = object_points;
+ for (Point &pt : copy_points)
+ pt += shift;
+ append(points, copy_points);
+ }
+ }
+
+ if (points.size() < 3)
+ // At least three points required for a convex hull.
+ return;
+
+ this->throw_if_canceled();
+ Polygon convex_hull = Slic3r::Geometry::convex_hull(points);
+
+ // Skirt may be printed on several layers, having distinct layer heights,
+ // but loops must be aligned so can't vary width/spacing
+ // TODO: use each extruder's own flow
+ double first_layer_height = this->skirt_first_layer_height();
+ Flow flow = this->skirt_flow();
+ float spacing = flow.spacing();
+ double mm3_per_mm = flow.mm3_per_mm();
+
+ std::vector<size_t> extruders;
+ std::vector<double> extruders_e_per_mm;
+ {
+ auto set_extruders = this->extruders();
+ extruders.reserve(set_extruders.size());
+ extruders_e_per_mm.reserve(set_extruders.size());
+ for (auto &extruder_id : set_extruders) {
+ extruders.push_back(extruder_id);
+ extruders_e_per_mm.push_back(Extruder((unsigned int)extruder_id, &m_config).e_per_mm(mm3_per_mm));
+ }
+ }
+
+ // Number of skirt loops per skirt layer.
+ int n_skirts = m_config.skirts.value;
+ if (this->has_infinite_skirt() && n_skirts == 0)
+ n_skirts = 1;
+
+ // Initial offset of the brim inner edge from the object (possible with a support & raft).
+ // The skirt will touch the brim if the brim is extruded.
+ Flow brim_flow = this->brim_flow();
+ double actual_brim_width = brim_flow.spacing() * floor(m_config.brim_width.value / brim_flow.spacing());
+ coord_t distance = scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.);
+ // Draw outlines from outside to inside.
+ // Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
+ std::vector<coordf_t> extruded_length(extruders.size(), 0.);
+ for (int i = n_skirts, extruder_idx = 0; i > 0; -- i) {
+ this->throw_if_canceled();
+ // Offset the skirt outside.
+ distance += coord_t(scale_(spacing));
+ // Generate the skirt centerline.
+ Polygon loop;
+ {
+ Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, scale_(0.1));
+ Geometry::simplify_polygons(loops, scale_(0.05), &loops);
+ loop = loops.front();
+ }
+ // Extrude the skirt loop.
+ ExtrusionLoop eloop(elrSkirt);
+ eloop.paths.emplace_back(ExtrusionPath(
+ ExtrusionPath(
+ erSkirt,
+ mm3_per_mm, // this will be overridden at G-code export time
+ flow.width,
+ first_layer_height // this will be overridden at G-code export time
+ )));
+ eloop.paths.back().polyline = loop.split_at_first_point();
+ m_skirt.append(eloop);
+ if (m_config.min_skirt_length.value > 0) {
+ // The skirt length is limited. Sum the total amount of filament length extruded, in mm.
+ extruded_length[extruder_idx] += unscale<double>(loop.length()) * extruders_e_per_mm[extruder_idx];
+ if (extruded_length[extruder_idx] < m_config.min_skirt_length.value) {
+ // Not extruded enough yet with the current extruder. Add another loop.
+ if (i == 1)
+ ++ i;
+ } else {
+ assert(extruded_length[extruder_idx] >= m_config.min_skirt_length.value);
+ // Enough extruded with the current extruder. Extrude with the next one,
+ // until the prescribed number of skirt loops is extruded.
+ if (extruder_idx + 1 < extruders.size())
+ ++ extruder_idx;
+ }
+ } else {
+ // The skirt lenght is not limited, extrude the skirt with the 1st extruder only.
+ }
+ }
+ // Brims were generated inside out, reverse to print the outmost contour first.
+ m_skirt.reverse();
+}
+
+void Print::_make_brim()
+{
+ // Brim is only printed on first layer and uses perimeter extruder.
+ Flow flow = this->brim_flow();
+ Polygons islands;
+ PrintObjectPtrs printable_objects = get_printable_objects();
+ for (PrintObject *object : printable_objects) {
+ Polygons object_islands;
+ for (ExPolygon &expoly : object->m_layers.front()->slices.expolygons)
+ object_islands.push_back(expoly.contour);
+ if (! object->support_layers().empty())
+ object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON));
+ islands.reserve(islands.size() + object_islands.size() * object->m_copies.size());
+ for (const Point &pt : object->m_copies)
+ for (Polygon &poly : object_islands) {
+ islands.push_back(poly);
+ islands.back().translate(pt);
+ }
+ }
+ Polygons loops;
+ size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing()));
+ for (size_t i = 0; i < num_loops; ++ i) {
+ this->throw_if_canceled();
+ islands = offset(islands, float(flow.scaled_spacing()), jtSquare);
+ for (Polygon &poly : islands) {
+ // poly.simplify(SCALED_RESOLUTION);
+ poly.points.push_back(poly.points.front());
+ Points p = MultiPoint::_douglas_peucker(poly.points, SCALED_RESOLUTION);
+ p.pop_back();
+ poly.points = std::move(p);
+ }
+ polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing())));
+ }
+
+ loops = union_pt_chained(loops, false);
+ std::reverse(loops.begin(), loops.end());
+ extrusion_entities_append_loops(m_brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
+}
+
+// Wipe tower support.
+bool Print::has_wipe_tower() const
+{
+ return
+ m_config.single_extruder_multi_material.value &&
+ ! m_config.spiral_vase.value &&
+ m_config.wipe_tower.value &&
+ m_config.nozzle_diameter.values.size() > 1;
+}
+
+void Print::_make_wipe_tower()
+{
+ m_wipe_tower_data.clear();
+ if (! this->has_wipe_tower())
+ return;
+
+ // Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
+ std::vector<float> wiping_matrix(cast<float>(m_config.wiping_volumes_matrix.values));
+ // Extract purging volumes for each extruder pair:
+ std::vector<std::vector<float>> wipe_volumes;
+ const unsigned int number_of_extruders = (unsigned int)(sqrt(wiping_matrix.size())+EPSILON);
+ for (unsigned int i = 0; i<number_of_extruders; ++i)
+ wipe_volumes.push_back(std::vector<float>(wiping_matrix.begin()+i*number_of_extruders, wiping_matrix.begin()+(i+1)*number_of_extruders));
+
+ // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print.
+ m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int)-1, true);
+ if (! m_wipe_tower_data.tool_ordering.has_wipe_tower())
+ // Don't generate any wipe tower.
+ return;
+
+ // Check whether there are any layers in m_tool_ordering, which are marked with has_wipe_tower,
+ // they print neither object, nor support. These layers are above the raft and below the object, and they
+ // shall be added to the support layers to be printed.
+ // see https://github.com/prusa3d/Slic3r/issues/607
+ {
+ size_t idx_begin = size_t(-1);
+ size_t idx_end = m_wipe_tower_data.tool_ordering.layer_tools().size();
+ // Find the first wipe tower layer, which does not have a counterpart in an object or a support layer.
+ for (size_t i = 0; i < idx_end; ++ i) {
+ const LayerTools &lt = m_wipe_tower_data.tool_ordering.layer_tools()[i];
+ if (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support) {
+ idx_begin = i;
+ break;
+ }
+ }
+ if (idx_begin != size_t(-1)) {
+ // Find the position in m_objects.first()->support_layers to insert these new support layers.
+ double wipe_tower_new_layer_print_z_first = m_wipe_tower_data.tool_ordering.layer_tools()[idx_begin].print_z;
+ SupportLayerPtrs::const_iterator it_layer = m_objects.front()->support_layers().begin();
+ SupportLayerPtrs::const_iterator it_end = m_objects.front()->support_layers().end();
+ for (; it_layer != it_end && (*it_layer)->print_z - EPSILON < wipe_tower_new_layer_print_z_first; ++ it_layer);
+ // Find the stopper of the sequence of wipe tower layers, which do not have a counterpart in an object or a support layer.
+ for (size_t i = idx_begin; i < idx_end; ++ i) {
+ LayerTools &lt = const_cast<LayerTools&>(m_wipe_tower_data.tool_ordering.layer_tools()[i]);
+ if (! (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support))
+ break;
+ lt.has_support = true;
+ // Insert the new support layer.
+ double height = lt.print_z - m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z;
+ //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway.
+ it_layer = m_objects.front()->insert_support_layer(it_layer, size_t(-1), height, lt.print_z, lt.print_z - 0.5 * height);
+ ++ it_layer;
+ }
+ }
+ }
+ this->throw_if_canceled();
+
+ // Initialize the wipe tower.
+ WipeTowerPrusaMM wipe_tower(
+ float(m_config.wipe_tower_x.value), float(m_config.wipe_tower_y.value),
+ float(m_config.wipe_tower_width.value),
+ float(m_config.wipe_tower_rotation_angle.value), float(m_config.cooling_tube_retraction.value),
+ float(m_config.cooling_tube_length.value), float(m_config.parking_pos_retraction.value),
+ float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), wipe_volumes,
+ m_wipe_tower_data.tool_ordering.first_extruder());
+
+ //wipe_tower.set_retract();
+ //wipe_tower.set_zhop();
+
+ // Set the extruder & material properties at the wipe tower object.
+ for (size_t i = 0; i < number_of_extruders; ++ i)
+ wipe_tower.set_extruder(
+ i,
+ WipeTowerPrusaMM::parse_material(m_config.filament_type.get_at(i).c_str()),
+ m_config.temperature.get_at(i),
+ m_config.first_layer_temperature.get_at(i),
+ m_config.filament_loading_speed.get_at(i),
+ m_config.filament_loading_speed_start.get_at(i),
+ m_config.filament_unloading_speed.get_at(i),
+ m_config.filament_unloading_speed_start.get_at(i),
+ m_config.filament_toolchange_delay.get_at(i),
+ m_config.filament_cooling_moves.get_at(i),
+ m_config.filament_cooling_initial_speed.get_at(i),
+ m_config.filament_cooling_final_speed.get_at(i),
+ m_config.filament_ramming_parameters.get_at(i),
+ m_config.nozzle_diameter.get_at(i));
+
+ m_wipe_tower_data.priming = Slic3r::make_unique<WipeTower::ToolChangeResult>(
+ wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false));
+
+ // Lets go through the wipe tower layers and determine pairs of extruder changes for each
+ // to pass to wipe_tower (so that it can use it for planning the layout of the tower)
+ {
+ unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.all_extruders().back();
+ for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers
+ if (!layer_tools.has_wipe_tower) continue;
+ bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front();
+ wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false);
+ for (const auto extruder_id : layer_tools.extruders) {
+ if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) {
+ float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange
+ // Not all of that can be used for infill purging:
+ volume_to_wipe -= m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
+
+ // try to assign some infills/objects for the wiping:
+ volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe);
+
+ // add back the minimal amount toforce on the wipe tower:
+ volume_to_wipe += m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
+
+ // request a toolchange at the wipe tower with at least volume_to_wipe purging amount
+ wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id,
+ first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back(), volume_to_wipe);
+ current_extruder_id = extruder_id;
+ }
+ }
+ layer_tools.wiping_extrusions().ensure_perimeters_infills_order(*this);
+ if (&layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0)
+ break;
+ }
+ }
+
+ // Generate the wipe tower layers.
+ m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size());
+ wipe_tower.generate(m_wipe_tower_data.tool_changes);
+ m_wipe_tower_data.depth = wipe_tower.get_depth();
+
+ // Unload the current filament over the purge tower.
+ coordf_t layer_height = m_objects.front()->config().layer_height.value;
+ if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) {
+ // The wipe tower goes up to the last layer of the print.
+ if (wipe_tower.layer_finished()) {
+ // The wipe tower is printed to the top of the print and it has no space left for the final extruder purge.
+ // Lift Z to the next layer.
+ wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z + layer_height), float(layer_height), 0, false, true);
+ } else {
+ // There is yet enough space at this layer of the wipe tower for the final purge.
+ }
+ } else {
+ // The wipe tower does not reach the last print layer, perform the pruge at the last print layer.
+ assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0);
+ wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true);
+ }
+ m_wipe_tower_data.final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(
+ wipe_tower.tool_change((unsigned int)-1, false));
+
+ m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
+ m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
+}
+
+std::string Print::output_filename() const
+{
+ DynamicConfig cfg_timestamp;
+ PlaceholderParser::update_timestamp(cfg_timestamp);
+ try {
+ return this->placeholder_parser().process(m_config.output_filename_format.value, 0, &cfg_timestamp);
+ } catch (std::runtime_error &err) {
+ throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what());
+ }
+}
+
+std::string Print::output_filepath(const std::string &path) const
+{
+ // if we were supplied no path, generate an automatic one based on our first object's input file
+ if (path.empty()) {
+ // get the first input file name
+ std::string input_file;
+ for (const PrintObject *object : m_objects) {
+ input_file = object->model_object()->input_file;
+ if (! input_file.empty())
+ break;
+ }
+ return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string();
+ }
+
+ // if we were supplied a directory, use it and append our automatically generated filename
+ boost::filesystem::path p(path);
+ if (boost::filesystem::is_directory(p))
+ return (p / this->output_filename()).make_preferred().string();
+
+ // if we were supplied a file which is not a directory, use it
+ return path;
+}
+
+void Print::export_png(const std::string &dirpath)
+{
+// size_t idx = 0;
+// for (PrintObject *obj : m_objects) {
+// obj->slice();
+// this->set_status(int(floor(idx * 100. / m_objects.size() + 0.5)), "Slicing...");
+// ++ idx;
+// }
+// this->set_status(90, "Exporting zipped archive...");
+// print_to<FilePrinterFormat::PNG>(*this,
+// dirpath,
+// float(m_config.bed_size_x.value),
+// float(m_config.bed_size_y.value),
+// int(m_config.pixel_width.value),
+// int(m_config.pixel_height.value),
+// float(m_config.exp_time.value),
+// float(m_config.exp_time_first.value));
+// this->set_status(100, "Done.");
+}
+
+// Returns extruder this eec should be printed with, according to PrintRegion config
+int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion &region)
+{
+ return is_infill(fill.role()) ? std::max<int>(0, (is_solid_infill(fill.entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) - 1) :
+ std::max<int>(region.config().perimeter_extruder.value - 1, 0);
+}
+
+} // namespace Slic3r
+
diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp
new file mode 100644
index 000000000..7f88110bc
--- /dev/null
+++ b/src/libslic3r/Print.hpp
@@ -0,0 +1,535 @@
+#ifndef slic3r_Print_hpp_
+#define slic3r_Print_hpp_
+
+#include "libslic3r.h"
+#include <atomic>
+#include <set>
+#include <vector>
+#include <string>
+#include <functional>
+#include "BoundingBox.hpp"
+#include "Flow.hpp"
+#include "PrintConfig.hpp"
+#include "Point.hpp"
+#include "Layer.hpp"
+#include "Model.hpp"
+#include "PlaceholderParser.hpp"
+#include "Slicing.hpp"
+#include "GCode/ToolOrdering.hpp"
+#include "GCode/WipeTower.hpp"
+
+#include "tbb/atomic.h"
+// tbb/mutex.h includes Windows, which in turn defines min/max macros. Convince Windows.h to not define these min/max macros.
+#ifndef NOMINMAX
+ #define NOMINMAX
+#endif
+#include "tbb/mutex.h"
+
+namespace Slic3r {
+
+class Print;
+class PrintObject;
+class ModelObject;
+class GCode;
+class GCodePreviewData;
+
+// Print step IDs for keeping track of the print state.
+enum PrintStep {
+ psSkirt, psBrim, psWipeTower, psGCodeExport, psCount,
+};
+enum PrintObjectStep {
+ posSlice, posPerimeters, posPrepareInfill,
+ posInfill, posSupportMaterial, posCount,
+};
+
+class CanceledException : public std::exception {
+public:
+ const char* what() const throw() { return "Background processing has been canceled"; }
+};
+
+// To be instantiated over PrintStep or PrintObjectStep enums.
+template <class StepType, size_t COUNT>
+class PrintState
+{
+public:
+ PrintState() { for (size_t i = 0; i < COUNT; ++ i) m_state[i].store(INVALID, std::memory_order_relaxed); }
+
+ enum State {
+ INVALID,
+ STARTED,
+ DONE,
+ };
+
+ // With full memory barrier.
+ bool is_done(StepType step) const { return m_state[step] == DONE; }
+
+ // Set the step as started. Block on mutex while the Print / PrintObject / PrintRegion objects are being
+ // modified by the UI thread.
+ // This is necessary to block until the Print::apply_config() updates its state, which may
+ // influence the processing step being entered.
+ void set_started(StepType step, tbb::mutex &mtx) {
+ mtx.lock();
+ m_state[step].store(STARTED, std::memory_order_relaxed);
+ mtx.unlock();
+ }
+
+ // Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being
+ // modified by the UI thread.
+ void set_done(StepType step, tbb::mutex &mtx) {
+ mtx.lock();
+ m_state[step].store(DONE, std::memory_order_relaxed);
+ mtx.unlock();
+ }
+
+ // Make the step invalid.
+ // The provided mutex should be locked at this point, guarding access to m_state.
+ // In case the step has already been entered or finished, cancel the background
+ // processing by calling the cancel callback.
+ template<typename CancelationCallback>
+ bool invalidate(StepType step, tbb::mutex &mtx, CancelationCallback &cancel) {
+ bool invalidated = m_state[step].load(std::memory_order_relaxed) != INVALID;
+ if (invalidated) {
+#if 0
+ if (mtx.state != mtx.HELD) {
+ printf("Not held!\n");
+ }
+#endif
+ mtx.unlock();
+ cancel();
+ mtx.lock();
+ }
+ return invalidated;
+ }
+
+ // Make all steps invalid.
+ // The provided mutex should be locked at this point, guarding access to m_state.
+ // In case any step has already been entered or finished, cancel the background
+ // processing by calling the cancel callback.
+ template<typename CancelationCallback>
+ bool invalidate_all(tbb::mutex &mtx, CancelationCallback &cancel) {
+ bool invalidated = false;
+ for (size_t i = 0; i < COUNT; ++ i)
+ if (m_state[i].load(std::memory_order_relaxed) != INVALID) {
+ if (! invalidated) {
+ mtx.unlock();
+ cancel();
+ mtx.lock();
+ invalidated = true;
+ }
+ m_state[i].store(INVALID, std::memory_order_relaxed);
+ }
+ return invalidated;
+ }
+
+private:
+ std::atomic<State> m_state[COUNT];
+};
+
+// A PrintRegion object represents a group of volumes to print
+// sharing the same config (including the same assigned extruder(s))
+class PrintRegion
+{
+ friend class Print;
+
+// Methods NOT modifying the PrintRegion's state:
+public:
+ const Print* print() const { return m_print; }
+ const PrintRegionConfig& config() const { return m_config; }
+ Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const;
+ // Average diameter of nozzles participating on extruding this region.
+ coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const;
+ // Average diameter of nozzles participating on extruding this region.
+ coordf_t bridging_height_avg(const PrintConfig &print_config) const;
+
+// Methods modifying the PrintRegion's state:
+public:
+ Print* print() { return m_print; }
+ void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); }
+
+private:
+ Print *m_print;
+ PrintRegionConfig m_config;
+
+ PrintRegion(Print* print) : m_print(print) {}
+ PrintRegion(Print* print, const PrintRegionConfig &config) : m_print(print), m_config(config) {}
+ ~PrintRegion() {}
+};
+
+
+typedef std::vector<Layer*> LayerPtrs;
+typedef std::vector<SupportLayer*> SupportLayerPtrs;
+class BoundingBoxf3; // TODO: for temporary constructor parameter
+
+class PrintObject
+{
+ friend class Print;
+
+public:
+ // vector of (vectors of volume ids), indexed by region_id
+ std::vector<std::vector<int>> region_volumes;
+ 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.
+ // layer_height_profile must not be set by the background thread.
+ std::vector<coordf_t> layer_height_profile;
+ // There is a layer_height_profile at both PrintObject and ModelObject. The layer_height_profile at the ModelObject
+ // is used for interactive editing and for loading / storing into a project file (AMF file as of today).
+ // This flag indicates that the layer_height_profile at the UI has been updated, therefore the backend needs to get it.
+ // This flag is necessary as we cannot safely clear the layer_height_profile if the background calculation is running.
+ bool layer_height_profile_valid;
+
+ // this is set to true when LayerRegion->slices is split in top/internal/bottom
+ // so that next call to make_perimeters() performs a union() before computing loops
+ bool typed_slices;
+
+ Vec3crd size; // XYZ in scaled coordinates
+
+ Print* print() { return m_print; }
+ const Print* print() const { return m_print; }
+ ModelObject* model_object() { return m_model_object; }
+ const ModelObject* model_object() const { return m_model_object; }
+ const PrintObjectConfig& config() const { return m_config; }
+ void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); }
+ void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); }
+ const LayerPtrs& layers() const { return m_layers; }
+ const SupportLayerPtrs& support_layers() const { return m_support_layers; }
+
+ const Points& copies() const { return m_copies; }
+ bool add_copy(const Vec2d &point);
+ bool delete_last_copy();
+ bool delete_all_copies() { return this->set_copies(Points()); }
+ bool set_copies(const Points &points);
+ bool reload_model_instances();
+ // since the object is aligned to origin, bounding box coincides with size
+ BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); }
+
+ // adds region_id, too, if necessary
+ void add_region_volume(unsigned int region_id, int volume_id) {
+ if (region_id >= region_volumes.size())
+ region_volumes.resize(region_id + 1);
+ region_volumes[region_id].push_back(volume_id);
+ }
+ // This is the *total* layer count (including support layers)
+ // this value is not supposed to be compared with Layer::id
+ // since they have different semantics.
+ size_t total_layer_count() const { return this->layer_count() + this->support_layer_count(); }
+ size_t layer_count() const { return m_layers.size(); }
+ void clear_layers();
+ Layer* get_layer(int idx) { return m_layers[idx]; }
+ const Layer* get_layer(int idx) const { return m_layers[idx]; }
+
+ // print_z: top of the layer; slice_z: center of the layer.
+ Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
+
+ size_t support_layer_count() const { return m_support_layers.size(); }
+ void clear_support_layers();
+ SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; }
+ SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z);
+ SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z);
+ void delete_support_layer(int idx);
+
+ // methods for handling state
+ bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
+ bool invalidate_step(PrintObjectStep step);
+ bool invalidate_all_steps();
+ bool is_step_done(PrintObjectStep step) const { return m_state.is_done(step); }
+
+ // To be used over the layer_height_profile of both the PrintObject and ModelObject
+ // to initialize the height profile with the height ranges.
+ bool update_layer_height_profile(std::vector<coordf_t> &layer_height_profile) const;
+
+ // Process layer_height_ranges, the raft layers and first layer thickness into layer_height_profile.
+ // The layer_height_profile may be later modified interactively by the user to refine layers at sloping surfaces.
+ bool update_layer_height_profile();
+
+ void reset_layer_height_profile();
+
+ void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action);
+
+ // Collect the slicing parameters, to be used by variable layer thickness algorithm,
+ // by the interactive layer height editor and by the printing process itself.
+ // The slicing parameters are dependent on various configuration values
+ // (layer height, first layer height, raft settings, print nozzle diameter etc).
+ SlicingParameters slicing_parameters() const;
+
+ // Called when slicing to SVG (see Print.pm sub export_svg), and used by perimeters.t
+ void slice();
+
+ // Helpers to slice support enforcer / blocker meshes by the support generator.
+ std::vector<ExPolygons> slice_support_enforcers() const;
+ std::vector<ExPolygons> slice_support_blockers() const;
+
+private:
+ void make_perimeters();
+ void prepare_infill();
+ void infill();
+ void generate_support_material();
+
+ void _slice();
+ std::string _fix_slicing_errors();
+ void _simplify_slices(double distance);
+ void _make_perimeters();
+ bool has_support_material() const;
+ void detect_surfaces_type();
+ void process_external_surfaces();
+ void discover_vertical_shells();
+ void bridge_over_infill();
+ void clip_fill_surfaces();
+ void discover_horizontal_shells();
+ void combine_infill();
+ void _generate_support_material();
+
+ bool is_printable() const { return ! m_copies.empty(); }
+
+ Print *m_print;
+ ModelObject *m_model_object;
+ PrintObjectConfig m_config;
+ // Slic3r::Point objects in scaled G-code coordinates
+ Points m_copies;
+ // scaled coordinates to add to copies (to compensate for the alignment
+ // operated when creating the object but still preserving a coherent API
+ // for external callers)
+ Point m_copies_shift;
+
+ LayerPtrs m_layers;
+ SupportLayerPtrs m_support_layers;
+
+ PrintState<PrintObjectStep, posCount> m_state;
+
+ // TODO: call model_object->get_bounding_box() instead of accepting
+ // parameter
+ PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox);
+ ~PrintObject() {}
+
+ void set_started(PrintObjectStep step);
+ void set_done(PrintObjectStep step);
+ std::vector<ExPolygons> _slice_region(size_t region_id, const std::vector<float> &z, bool modifier);
+ std::vector<ExPolygons> _slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const;
+};
+
+struct WipeTowerData
+{
+ // Following section will be consumed by the GCodeGenerator.
+ // Tool ordering of a non-sequential print has to be known to calculate the wipe tower.
+ // Cache it here, so it does not need to be recalculated during the G-code generation.
+ ToolOrdering tool_ordering;
+ // Cache of tool changes per print layer.
+ std::unique_ptr<WipeTower::ToolChangeResult> priming;
+ std::vector<std::vector<WipeTower::ToolChangeResult>> tool_changes;
+ std::unique_ptr<WipeTower::ToolChangeResult> final_purge;
+ std::vector<float> used_filament;
+ int number_of_toolchanges;
+
+ // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
+ float depth;
+
+ void clear() {
+ tool_ordering.clear();
+ priming.reset(nullptr);
+ tool_changes.clear();
+ final_purge.reset(nullptr);
+ used_filament.clear();
+ number_of_toolchanges = -1;
+ depth = 0.f;
+ }
+};
+
+struct PrintStatistics
+{
+ PrintStatistics() { clear(); }
+ std::string estimated_normal_print_time;
+ std::string estimated_silent_print_time;
+ double total_used_filament;
+ double total_extruded_volume;
+ double total_cost;
+ double total_weight;
+ double total_wipe_tower_cost;
+ double total_wipe_tower_filament;
+ std::map<size_t, float> filament_stats;
+
+ void clear() {
+ estimated_normal_print_time.clear();
+ estimated_silent_print_time.clear();
+ total_used_filament = 0.;
+ total_extruded_volume = 0.;
+ total_cost = 0.;
+ total_weight = 0.;
+ total_wipe_tower_cost = 0.;
+ total_wipe_tower_filament = 0.;
+ filament_stats.clear();
+ }
+};
+
+typedef std::vector<PrintObject*> PrintObjectPtrs;
+typedef std::vector<PrintRegion*> PrintRegionPtrs;
+
+// The complete print tray with possibly multiple objects.
+class Print
+{
+public:
+ Print() { restart(); }
+ ~Print() { clear_objects(); }
+
+ // Methods, which change the state of Print / PrintObject / PrintRegion.
+ // The following methods are synchronized with process() and export_gcode(),
+ // so that process() and export_gcode() may be called from a background thread.
+ // In case the following methods need to modify data processed by process() or export_gcode(),
+ // a cancellation callback is executed to stop the background processing before the operation.
+ void clear_objects();
+ void delete_object(size_t idx);
+ void reload_object(size_t idx);
+ bool reload_model_instances();
+ void add_model_object(ModelObject* model_object, int idx = -1);
+ bool apply_config(DynamicPrintConfig config);
+ void process();
+ void export_gcode(const std::string &path_template, GCodePreviewData *preview_data);
+ // SLA export, temporary.
+ void export_png(const std::string &dirpath);
+
+ // methods for handling state
+ bool is_step_done(PrintStep step) const { return m_state.is_done(step); }
+ bool is_step_done(PrintObjectStep step) const;
+
+ bool has_infinite_skirt() const;
+ bool has_skirt() const;
+ PrintObjectPtrs get_printable_objects() const;
+ float get_wipe_tower_depth() const { return m_wipe_tower_data.depth; }
+
+ // Returns an empty string if valid, otherwise returns an error message.
+ std::string validate() const;
+ BoundingBox bounding_box() const;
+ BoundingBox total_bounding_box() const;
+ double skirt_first_layer_height() const;
+ Flow brim_flow() const;
+ Flow skirt_flow() const;
+
+ std::vector<unsigned int> object_extruders() const;
+ std::vector<unsigned int> support_material_extruders() const;
+ std::vector<unsigned int> extruders() const;
+ double max_allowed_layer_height() const;
+ bool has_support_material() const;
+ // Make sure the background processing has no access to this model_object during this call!
+ void auto_assign_extruders(ModelObject* model_object) const;
+
+ const PrintConfig& config() const { return m_config; }
+ const PrintObjectConfig& default_object_config() const { return m_default_object_config; }
+ const PrintRegionConfig& default_region_config() const { return m_default_region_config; }
+ const PrintObjectPtrs& objects() const { return m_objects; }
+ const PrintObject* get_object(int idx) const { return m_objects[idx]; }
+ const PrintRegionPtrs& regions() const { return m_regions; }
+ const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; }
+
+ // Returns extruder this eec should be printed with, according to PrintRegion config:
+ static int get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion &region);
+
+ const ExtrusionEntityCollection& skirt() const { return m_skirt; }
+ const ExtrusionEntityCollection& brim() const { return m_brim; }
+
+ const PrintStatistics& print_statistics() const { return m_print_statistics; }
+
+ // Wipe tower support.
+ bool has_wipe_tower() const;
+ const WipeTowerData& wipe_tower_data() const { return m_wipe_tower_data; }
+
+ std::string output_filename() const;
+ std::string output_filepath(const std::string &path) const;
+
+ typedef std::function<void(int, const std::string&)> status_callback_type;
+ // Default status console print out in the form of percent => message.
+ void set_status_default() { m_status_callback = nullptr; }
+ // No status output or callback whatsoever, useful mostly for automatic tests.
+ void set_status_silent() { m_status_callback = [](int, const std::string&){}; }
+ // Register a custom status callback.
+ void set_status_callback(status_callback_type cb) { m_status_callback = cb; }
+ // Calls a registered callback to update the status, or print out the default message.
+ void set_status(int percent, const std::string &message) {
+ if (m_status_callback) m_status_callback(percent, message);
+ else printf("%d => %s\n", percent, message.c_str());
+ }
+
+ typedef std::function<void()> cancel_callback_type;
+ // Various methods will call this callback to stop the background processing (the Print::process() call)
+ // in case a successive change of the Print / PrintObject / PrintRegion instances changed
+ // the state of the finished or running calculations.
+ void set_cancel_callback(cancel_callback_type cancel_callback) { m_cancel_callback = cancel_callback; }
+ // Has the calculation been canceled?
+ bool canceled() const { return m_canceled; }
+ // Cancel the running computation. Stop execution of all the background threads.
+ void cancel() { m_canceled = true; }
+ // Cancel the running computation. Stop execution of all the background threads.
+ void restart() { m_canceled = false; }
+
+ // Accessed by SupportMaterial
+ const PrintRegion* get_region(size_t idx) const { return m_regions[idx]; }
+
+protected:
+ void set_started(PrintStep step) { m_state.set_started(step, m_mutex); throw_if_canceled(); }
+ void set_done(PrintStep step) { m_state.set_done(step, m_mutex); throw_if_canceled(); }
+ bool invalidate_step(PrintStep step);
+ bool invalidate_all_steps() { return m_state.invalidate_all(m_mutex, m_cancel_callback); }
+
+ // methods for handling regions
+ PrintRegion* get_region(size_t idx) { return m_regions[idx]; }
+ PrintRegion* add_region();
+ PrintRegion* add_region(const PrintRegionConfig &config);
+
+private:
+ bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
+ PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume);
+
+ // If the background processing stop was requested, throw CanceledException.
+ // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
+ void throw_if_canceled() const { if (m_canceled) throw CanceledException(); }
+
+ void _make_skirt();
+ void _make_brim();
+ void _make_wipe_tower();
+ void _simplify_slices(double distance);
+
+ PrintState<PrintStep, psCount> m_state;
+ // Mutex used for synchronization of the worker thread with the UI thread:
+ // The mutex will be used to guard the worker thread against entering a stage
+ // while the data influencing the stage is modified.
+ mutable tbb::mutex m_mutex;
+
+ // Has the calculation been canceled?
+ tbb::atomic<bool> m_canceled;
+ // Callback to be evoked regularly to update state of the UI thread.
+ status_callback_type m_status_callback;
+
+ // Callback to be evoked to stop the background processing before a state is updated.
+ cancel_callback_type m_cancel_callback = [](){};
+
+ PrintConfig m_config;
+ PrintObjectConfig m_default_object_config;
+ PrintRegionConfig m_default_region_config;
+ PrintObjectPtrs m_objects;
+ PrintRegionPtrs m_regions;
+ PlaceholderParser m_placeholder_parser;
+
+ // Ordered collections of extrusion paths to build skirt loops and brim.
+ ExtrusionEntityCollection m_skirt;
+ ExtrusionEntityCollection m_brim;
+
+ // Following section will be consumed by the GCodeGenerator.
+ WipeTowerData m_wipe_tower_data;
+
+ // Estimated print time, filament consumed.
+ PrintStatistics m_print_statistics;
+
+ // To allow GCode to set the Print's GCodeExport step status.
+ friend class GCode;
+ // Allow PrintObject to access m_mutex and m_cancel_callback.
+ friend class PrintObject;
+};
+
+
+#define FOREACH_BASE(type, container, iterator) for (type::const_iterator iterator = (container).begin(); iterator != (container).end(); ++iterator)
+#define FOREACH_OBJECT(print, object) FOREACH_BASE(PrintObjectPtrs, (print)->m_objects, object)
+#define FOREACH_LAYER(object, layer) FOREACH_BASE(LayerPtrs, (object)->m_layers, layer)
+#define FOREACH_LAYERREGION(layer, layerm) FOREACH_BASE(LayerRegionPtrs, (layer)->m_regions, layerm)
+
+}
+
+#endif
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
new file mode 100644
index 000000000..ed02f6d43
--- /dev/null
+++ b/src/libslic3r/PrintConfig.cpp
@@ -0,0 +1,2604 @@
+#include "PrintConfig.hpp"
+#include "I18N.hpp"
+
+#include <set>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/thread.hpp>
+
+#include <float.h>
+
+namespace Slic3r {
+
+//! macro used to mark string used at localization,
+//! return same string
+#define L(s) Slic3r::I18N::translate(s)
+
+PrintConfigDef::PrintConfigDef()
+{
+ this->init_common_params();
+ this->init_fff_params();
+ this->init_sla_params();
+}
+
+void PrintConfigDef::init_common_params()
+{
+ t_optiondef_map &Options = this->options;
+ ConfigOptionDef* def;
+
+ def = this->add("printer_technology", coEnum);
+ def->label = L("Printer technology");
+ def->tooltip = L("Printer technology");
+ def->cli = "printer-technology=s";
+ def->enum_keys_map = &ConfigOptionEnum<PrinterTechnology>::get_enum_values();
+ def->enum_values.push_back("FFF");
+ def->enum_values.push_back("SLA");
+ def->default_value = new ConfigOptionEnum<PrinterTechnology>(ptFFF);
+
+ def = this->add("bed_shape", coPoints);
+ def->label = L("Bed shape");
+ def->default_value = new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) };
+
+ def = this->add("layer_height", coFloat);
+ def->label = L("Layer height");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("This setting controls the height (and thus the total number) of the slices/layers. "
+ "Thinner layers give better accuracy but take more time to print.");
+ def->sidetext = L("mm");
+ def->cli = "layer-height=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0.3);
+
+ def = this->add("max_print_height", coFloat);
+ def->label = L("Max print height");
+ def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing.");
+ def->sidetext = L("mm");
+ def->cli = "max-print-height=f";
+ def->default_value = new ConfigOptionFloat(200.0);
+}
+
+void PrintConfigDef::init_fff_params()
+{
+ t_optiondef_map &Options = this->options;
+ ConfigOptionDef* def;
+
+ // Maximum extruder temperature, bumped to 1500 to support printing of glass.
+ const int max_temp = 1500;
+
+ def = this->add("avoid_crossing_perimeters", coBool);
+ def->label = L("Avoid crossing perimeters");
+ def->tooltip = L("Optimize travel moves in order to minimize the crossing of perimeters. "
+ "This is mostly useful with Bowden extruders which suffer from oozing. "
+ "This feature slows down both the print and the G-code generation.");
+ def->cli = "avoid-crossing-perimeters!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("bed_temperature", coInts);
+ def->label = L("Other layers");
+ def->tooltip = L("Bed temperature for layers after the first one. "
+ "Set this to zero to disable bed temperature control commands in the output.");
+ def->cli = "bed-temperature=i@";
+ def->full_label = L("Bed temperature");
+ def->min = 0;
+ def->max = 300;
+ def->default_value = new ConfigOptionInts { 0 };
+
+ def = this->add("before_layer_gcode", coString);
+ def->label = L("Before layer change G-code");
+ def->tooltip = L("This custom code is inserted at every layer change, right before the Z move. "
+ "Note that you can use placeholder variables for all Slic3r settings as well "
+ "as [layer_num] and [layer_z].");
+ def->cli = "before-layer-gcode=s";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 50;
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("between_objects_gcode", coString);
+ def->label = L("Between objects G-code");
+ def->tooltip = L("This code is inserted between objects when using sequential printing. By default extruder and bed temperature are reset using non-wait command; however if M104, M109, M140 or M190 are detected in this custom code, Slic3r will not add temperature commands. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want.");
+ def->cli = "between-objects-gcode=s";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 120;
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("bottom_solid_layers", coInt);
+ def->label = L("Bottom");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("Number of solid layers to generate on bottom surfaces.");
+ def->cli = "bottom-solid-layers=i";
+ def->full_label = L("Bottom solid layers");
+ def->min = 0;
+ def->default_value = new ConfigOptionInt(3);
+
+ def = this->add("bridge_acceleration", coFloat);
+ def->label = L("Bridge");
+ def->tooltip = L("This is the acceleration your printer will use for bridges. "
+ "Set zero to disable acceleration control for bridges.");
+ def->sidetext = L("mm/s²");
+ def->cli = "bridge-acceleration=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("bridge_angle", coFloat);
+ def->label = L("Bridging angle");
+ def->category = L("Infill");
+ def->tooltip = L("Bridging angle override. If left to zero, the bridging angle will be calculated "
+ "automatically. Otherwise the provided angle will be used for all bridges. "
+ "Use 180° for zero angle.");
+ def->sidetext = L("°");
+ def->cli = "bridge-angle=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0.);
+
+ def = this->add("bridge_fan_speed", coInts);
+ def->label = L("Bridges fan speed");
+ def->tooltip = L("This fan speed is enforced during all bridges and overhangs.");
+ def->sidetext = L("%");
+ def->cli = "bridge-fan-speed=i@";
+ def->min = 0;
+ def->max = 100;
+ def->default_value = new ConfigOptionInts { 100 };
+
+ def = this->add("bridge_flow_ratio", coFloat);
+ def->label = L("Bridge flow ratio");
+ def->category = L("Advanced");
+ def->tooltip = L("This factor affects the amount of plastic for bridging. "
+ "You can decrease it slightly to pull the extrudates and prevent sagging, "
+ "although default settings are usually good and you should experiment "
+ "with cooling (use a fan) before tweaking this.");
+ def->cli = "bridge-flow-ratio=f";
+ def->min = 0;
+ def->max = 2;
+ def->default_value = new ConfigOptionFloat(1);
+
+ def = this->add("bridge_speed", coFloat);
+ def->label = L("Bridges");
+ def->category = L("Speed");
+ def->tooltip = L("Speed for printing bridges.");
+ def->sidetext = L("mm/s");
+ def->cli = "bridge-speed=f";
+ def->aliases = { "bridge_feed_rate" };
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(60);
+
+ def = this->add("brim_width", coFloat);
+ def->label = L("Brim width");
+ def->tooltip = L("Horizontal width of the brim that will be printed around each object on the first layer.");
+ def->sidetext = L("mm");
+ def->cli = "brim-width=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("clip_multipart_objects", coBool);
+ def->label = L("Clip multi-part objects");
+ def->tooltip = L("When printing multi-material objects, this settings will make slic3r "
+ "to clip the overlapping object parts one by the other "
+ "(2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc).");
+ def->cli = "clip-multipart-objects!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("compatible_printers", coStrings);
+ def->label = L("Compatible printers");
+ def->default_value = new ConfigOptionStrings();
+
+ def = this->add("compatible_printers_condition", coString);
+ def->label = L("Compatible printers condition");
+ def->tooltip = L("A boolean expression using the configuration values of an active printer profile. "
+ "If this expression evaluates to true, this profile is considered compatible "
+ "with the active printer profile.");
+ def->default_value = new ConfigOptionString();
+
+ // The following value is to be stored into the project file (AMF, 3MF, Config ...)
+ // and it contains a sum of "compatible_printers_condition" values over the print and filament profiles.
+ def = this->add("compatible_printers_condition_cummulative", coStrings);
+ def->default_value = new ConfigOptionStrings();
+
+ def = this->add("complete_objects", coBool);
+ def->label = L("Complete individual objects");
+ def->tooltip = L("When printing multiple objects or copies, this feature will complete "
+ "each object before moving onto next one (and starting it from its bottom layer). "
+ "This feature is useful to avoid the risk of ruined prints. "
+ "Slic3r should warn and prevent you from extruder collisions, but beware.");
+ def->cli = "complete-objects!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("cooling", coBools);
+ def->label = L("Enable auto cooling");
+ def->tooltip = L("This flag enables the automatic cooling logic that adjusts print speed "
+ "and fan speed according to layer printing time.");
+ def->cli = "cooling!";
+ def->default_value = new ConfigOptionBools { true };
+
+ def = this->add("cooling_tube_retraction", coFloat);
+ def->label = L("Cooling tube position");
+ def->tooltip = L("Distance of the center-point of the cooling tube from the extruder tip ");
+ def->sidetext = L("mm");
+ def->cli = "cooling_tube_retraction=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(91.5f);
+
+ def = this->add("cooling_tube_length", coFloat);
+ def->label = L("Cooling tube length");
+ def->tooltip = L("Length of the cooling tube to limit space for cooling moves inside it ");
+ def->sidetext = L("mm");
+ def->cli = "cooling_tube_length=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(5.f);
+
+ def = this->add("default_acceleration", coFloat);
+ def->label = L("Default");
+ def->tooltip = L("This is the acceleration your printer will be reset to after "
+ "the role-specific acceleration values are used (perimeter/infill). "
+ "Set zero to prevent resetting acceleration at all.");
+ def->sidetext = L("mm/s²");
+ def->cli = "default-acceleration=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("default_filament_profile", coStrings);
+ def->label = L("Default filament profile");
+ def->tooltip = L("Default filament profile associated with the current printer profile. "
+ "On selection of the current printer profile, this filament profile will be activated.");
+ def->default_value = new ConfigOptionStrings();
+
+ def = this->add("default_print_profile", coString);
+ def->label = L("Default print profile");
+ def->tooltip = L("Default print profile associated with the current printer profile. "
+ "On selection of the current printer profile, this print profile will be activated.");
+ def->default_value = new ConfigOptionString();
+
+ def = this->add("disable_fan_first_layers", coInts);
+ def->label = L("Disable fan for the first");
+ def->tooltip = L("You can set this to a positive value to disable fan at all "
+ "during the first layers, so that it does not make adhesion worse.");
+ def->sidetext = L("layers");
+ def->cli = "disable-fan-first-layers=i@";
+ def->min = 0;
+ def->max = 1000;
+ def->default_value = new ConfigOptionInts { 3 };
+
+ def = this->add("dont_support_bridges", coBool);
+ def->label = L("Don't support bridges");
+ def->category = L("Support material");
+ def->tooltip = L("Experimental option for preventing support material from being generated "
+ "under bridged areas.");
+ def->cli = "dont-support-bridges!";
+ def->default_value = new ConfigOptionBool(true);
+
+ def = this->add("duplicate_distance", coFloat);
+ def->label = L("Distance between copies");
+ def->tooltip = L("Distance used for the auto-arrange feature of the plater.");
+ def->sidetext = L("mm");
+ def->cli = "duplicate-distance=f";
+ def->aliases = { "multiply_distance" };
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(6);
+
+ def = this->add("elefant_foot_compensation", coFloat);
+ def->label = L("Elephant foot compensation");
+ def->category = L("Advanced");
+ def->tooltip = L("The first layer will be shrunk in the XY plane by the configured value "
+ "to compensate for the 1st layer squish aka an Elephant Foot effect.");
+ def->sidetext = L("mm");
+ def->cli = "elefant-foot-compensation=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("end_gcode", coString);
+ def->label = L("End G-code");
+ def->tooltip = L("This end procedure is inserted at the end of the output file. "
+ "Note that you can use placeholder variables for all Slic3r settings.");
+ def->cli = "end-gcode=s";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 120;
+ def->default_value = new ConfigOptionString("M104 S0 ; turn off temperature\nG28 X0 ; home X axis\nM84 ; disable motors\n");
+
+ def = this->add("end_filament_gcode", coStrings);
+ def->label = L("End G-code");
+ def->tooltip = L("This end procedure is inserted at the end of the output file, before the printer end gcode. "
+ "Note that you can use placeholder variables for all Slic3r settings. "
+ "If you have multiple extruders, the gcode is processed in extruder order.");
+ def->cli = "end-filament-gcode=s@";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 120;
+ def->default_value = new ConfigOptionStrings { "; Filament-specific end gcode \n;END gcode for filament\n" };
+
+ def = this->add("ensure_vertical_shell_thickness", coBool);
+ def->label = L("Ensure vertical shell thickness");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("Add solid infill near sloping surfaces to guarantee the vertical shell thickness "
+ "(top+bottom solid layers).");
+ def->cli = "ensure-vertical-shell-thickness!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("external_fill_pattern", coEnum);
+ def->label = L("Top/bottom fill pattern");
+ def->category = L("Infill");
+ def->tooltip = L("Fill pattern for top/bottom infill. This only affects the external visible layer, "
+ "and not its adjacent solid shells.");
+ def->cli = "external-fill-pattern|solid-fill-pattern=s";
+ def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
+ def->enum_values.push_back("rectilinear");
+ def->enum_values.push_back("concentric");
+ def->enum_values.push_back("hilbertcurve");
+ def->enum_values.push_back("archimedeanchords");
+ def->enum_values.push_back("octagramspiral");
+ def->enum_labels.push_back(L("Rectilinear"));
+ def->enum_labels.push_back(L("Concentric"));
+ def->enum_labels.push_back(L("Hilbert Curve"));
+ def->enum_labels.push_back(L("Archimedean Chords"));
+ def->enum_labels.push_back(L("Octagram Spiral"));
+ // solid_fill_pattern is an obsolete equivalent to external_fill_pattern.
+ def->aliases = { "solid_fill_pattern" };
+ def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear);
+
+ def = this->add("external_perimeter_extrusion_width", coFloatOrPercent);
+ def->label = L("External perimeters");
+ def->category = L("Extrusion Width");
+ def->tooltip = L("Set this to a non-zero value to set a manual extrusion width for external perimeters. "
+ "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. "
+ "If expressed as percentage (for example 200%), it will be computed over layer height.");
+ def->sidetext = L("mm or % (leave 0 for default)");
+ def->cli = "external-perimeter-extrusion-width=s";
+ def->default_value = new ConfigOptionFloatOrPercent(0, false);
+
+ def = this->add("external_perimeter_speed", coFloatOrPercent);
+ def->label = L("External perimeters");
+ def->category = L("Speed");
+ def->tooltip = L("This separate setting will affect the speed of external perimeters (the visible ones). "
+ "If expressed as percentage (for example: 80%) it will be calculated "
+ "on the perimeters speed setting above. Set to zero for auto.");
+ def->sidetext = L("mm/s or %");
+ def->cli = "external-perimeter-speed=s";
+ def->ratio_over = "perimeter_speed";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloatOrPercent(50, true);
+
+ def = this->add("external_perimeters_first", coBool);
+ def->label = L("External perimeters first");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("Print contour perimeters from the outermost one to the innermost one "
+ "instead of the default inverse order.");
+ def->cli = "external-perimeters-first!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("extra_perimeters", coBool);
+ def->label = L("Extra perimeters if needed");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("Add more perimeters when needed for avoiding gaps in sloping walls. "
+ "Slic3r keeps adding perimeters, until more than 70% of the loop immediately above "
+ "is supported.");
+ def->cli = "extra-perimeters!";
+ def->default_value = new ConfigOptionBool(true);
+
+ def = this->add("extruder", coInt);
+ def->gui_type = "i_enum_open";
+ def->label = L("Extruder");
+ def->category = L("Extruders");
+ def->tooltip = L("The extruder to use (unless more specific extruder settings are specified). "
+ "This value overrides perimeter and infill extruders, but not the support extruders.");
+ def->cli = "extruder=i";
+ def->min = 0; // 0 = inherit defaults
+ def->enum_labels.push_back("default"); // override label for item 0
+ def->enum_labels.push_back("1");
+ def->enum_labels.push_back("2");
+ def->enum_labels.push_back("3");
+ def->enum_labels.push_back("4");
+ def->enum_labels.push_back("5");
+
+ def = this->add("extruder_clearance_height", coFloat);
+ def->label = L("Height");
+ def->tooltip = L("Set this to the vertical distance between your nozzle tip and (usually) the X carriage rods. "
+ "In other words, this is the height of the clearance cylinder around your extruder, "
+ "and it represents the maximum depth the extruder can peek before colliding with "
+ "other printed objects.");
+ def->sidetext = L("mm");
+ def->cli = "extruder-clearance-height=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(20);
+
+ def = this->add("extruder_clearance_radius", coFloat);
+ def->label = L("Radius");
+ def->tooltip = L("Set this to the clearance radius around your extruder. "
+ "If the extruder is not centered, choose the largest value for safety. "
+ "This setting is used to check for collisions and to display the graphical preview "
+ "in the plater.");
+ def->sidetext = L("mm");
+ def->cli = "extruder-clearance-radius=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(20);
+
+ def = this->add("extruder_colour", coStrings);
+ def->label = L("Extruder Color");
+ def->tooltip = L("This is only used in the Slic3r interface as a visual help.");
+ def->cli = "extruder-color=s@";
+ def->gui_type = "color";
+ // Empty string means no color assigned yet.
+ def->default_value = new ConfigOptionStrings { "" };
+
+ def = this->add("extruder_offset", coPoints);
+ def->label = L("Extruder offset");
+ def->tooltip = L("If your firmware doesn't handle the extruder displacement you need the G-code "
+ "to take it into account. This option lets you specify the displacement of each extruder "
+ "with respect to the first one. It expects positive coordinates (they will be subtracted "
+ "from the XY coordinate).");
+ def->sidetext = L("mm");
+ def->cli = "extruder-offset=s@";
+ def->default_value = new ConfigOptionPoints { Vec2d(0,0) };
+
+ def = this->add("extrusion_axis", coString);
+ def->label = L("Extrusion axis");
+ def->tooltip = L("Use this option to set the axis letter associated to your printer's extruder "
+ "(usually E but some printers use A).");
+ def->cli = "extrusion-axis=s";
+ def->default_value = new ConfigOptionString("E");
+
+ def = this->add("extrusion_multiplier", coFloats);
+ def->label = L("Extrusion multiplier");
+ def->tooltip = L("This factor changes the amount of flow proportionally. You may need to tweak "
+ "this setting to get nice surface finish and correct single wall widths. "
+ "Usual values are between 0.9 and 1.1. If you think you need to change this more, "
+ "check filament diameter and your firmware E steps.");
+ def->cli = "extrusion-multiplier=f@";
+ def->default_value = new ConfigOptionFloats { 1. };
+
+ def = this->add("extrusion_width", coFloatOrPercent);
+ def->label = L("Default extrusion width");
+ def->category = L("Extrusion Width");
+ def->tooltip = L("Set this to a non-zero value to allow a manual extrusion width. "
+ "If left to zero, Slic3r derives extrusion widths from the nozzle diameter "
+ "(see the tooltips for perimeter extrusion width, infill extrusion width etc). "
+ "If expressed as percentage (for example: 230%), it will be computed over layer height.");
+ def->sidetext = L("mm or % (leave 0 for auto)");
+ def->cli = "extrusion-width=s";
+ def->default_value = new ConfigOptionFloatOrPercent(0, false);
+
+ def = this->add("fan_always_on", coBools);
+ def->label = L("Keep fan always on");
+ def->tooltip = L("If this is enabled, fan will never be disabled and will be kept running at least "
+ "at its minimum speed. Useful for PLA, harmful for ABS.");
+ def->cli = "fan-always-on!";
+ def->default_value = new ConfigOptionBools { false };
+
+ def = this->add("fan_below_layer_time", coInts);
+ def->label = L("Enable fan if layer print time is below");
+ def->tooltip = L("If layer print time is estimated below this number of seconds, fan will be enabled "
+ "and its speed will be calculated by interpolating the minimum and maximum speeds.");
+ def->sidetext = L("approximate seconds");
+ def->cli = "fan-below-layer-time=i@";
+ def->width = 60;
+ def->min = 0;
+ def->max = 1000;
+ def->default_value = new ConfigOptionInts { 60 };
+
+ def = this->add("filament_colour", coStrings);
+ def->label = L("Color");
+ def->tooltip = L("This is only used in the Slic3r interface as a visual help.");
+ def->cli = "filament-color=s@";
+ def->gui_type = "color";
+ def->default_value = new ConfigOptionStrings { "#29B2B2" };
+
+ def = this->add("filament_notes", coStrings);
+ def->label = L("Filament notes");
+ def->tooltip = L("You can put your notes regarding the filament here.");
+ def->cli = "filament-notes=s@";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 130;
+ def->default_value = new ConfigOptionStrings { "" };
+
+ def = this->add("filament_max_volumetric_speed", coFloats);
+ def->label = L("Max volumetric speed");
+ def->tooltip = L("Maximum volumetric speed allowed for this filament. Limits the maximum volumetric "
+ "speed of a print to the minimum of print and filament volumetric speed. "
+ "Set to zero for no limit.");
+ def->sidetext = L("mm³/s");
+ def->cli = "filament-max-volumetric-speed=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("filament_loading_speed", coFloats);
+ def->label = L("Loading speed");
+ def->tooltip = L("Speed used for loading the filament on the wipe tower. ");
+ def->sidetext = L("mm/s");
+ def->cli = "filament-loading-speed=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 28. };
+
+ def = this->add("filament_loading_speed_start", coFloats);
+ def->label = L("Loading speed at the start");
+ def->tooltip = L("Speed used at the very beginning of loading phase. ");
+ def->sidetext = L("mm/s");
+ def->cli = "filament-loading-speed-start=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 3. };
+
+ def = this->add("filament_unloading_speed", coFloats);
+ def->label = L("Unloading speed");
+ def->tooltip = L("Speed used for unloading the filament on the wipe tower (does not affect "
+ " initial part of unloading just after ramming). ");
+ def->sidetext = L("mm/s");
+ def->cli = "filament-unloading-speed=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 90. };
+
+ def = this->add("filament_unloading_speed_start", coFloats);
+ def->label = L("Unloading speed at the start");
+ def->tooltip = L("Speed used for unloading the tip of the filament immediately after ramming. ");
+ def->sidetext = L("mm/s");
+ def->cli = "filament-unloading-speed-start=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 100. };
+
+ def = this->add("filament_toolchange_delay", coFloats);
+ def->label = L("Delay after unloading");
+ def->tooltip = L("Time to wait after the filament is unloaded. "
+ "May help to get reliable toolchanges with flexible materials "
+ "that may need more time to shrink to original dimensions. ");
+ def->sidetext = L("s");
+ def->cli = "filament-toolchange-delay=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("filament_cooling_moves", coInts);
+ def->label = L("Number of cooling moves");
+ def->tooltip = L("Filament is cooled by being moved back and forth in the "
+ "cooling tubes. Specify desired number of these moves ");
+ def->cli = "filament-cooling-moves=i@";
+ def->max = 0;
+ def->max = 20;
+ def->default_value = new ConfigOptionInts { 4 };
+
+ def = this->add("filament_cooling_initial_speed", coFloats);
+ def->label = L("Speed of the first cooling move");
+ def->tooltip = L("Cooling moves are gradually accelerating beginning at this speed. ");
+ def->cli = "filament-cooling-initial-speed=f@";
+ def->sidetext = L("mm/s");
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 2.2f };
+
+ def = this->add("filament_minimal_purge_on_wipe_tower", coFloats);
+ def->label = L("Minimal purge on wipe tower");
+ def->tooltip = L("After a tool change, the exact position of the newly loaded filament inside "
+ "the nozzle may not be known, and the filament pressure is likely not yet stable. "
+ "Before purging the print head into an infill or a sacrificial object, Slic3r will always prime "
+ "this amount of material into the wipe tower to produce successive infill or sacrificial object extrusions reliably.");
+ def->cli = "filament-minimal-purge-on-wipe-tower=f@";
+ def->sidetext = L("mm³");
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 15.f };
+
+ def = this->add("filament_cooling_final_speed", coFloats);
+ def->label = L("Speed of the last cooling move");
+ def->tooltip = L("Cooling moves are gradually accelerating towards this speed. ");
+ def->cli = "filament-cooling-final-speed=f@";
+ def->sidetext = L("mm/s");
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 3.4f };
+
+ def = this->add("filament_load_time", coFloats);
+ def->label = L("Filament load time");
+ def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to load a new filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator.");
+ def->cli = "filament-load-time=i@";
+ def->sidetext = L("s");
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 0.0f };
+
+ def = this->add("filament_ramming_parameters", coStrings);
+ def->label = L("Ramming parameters");
+ def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters ");
+ def->cli = "filament-ramming-parameters=s@";
+ def->default_value = new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|"
+ " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" };
+
+ def = this->add("filament_unload_time", coFloats);
+ def->label = L("Filament unload time");
+ def->tooltip = L("Time for the printer firmware (or the Multi Material Unit 2.0) to unload a filament during a tool change (when executing the T code). This time is added to the total print time by the G-code time estimator.");
+ def->cli = "filament-unload-time=i@";
+ def->sidetext = L("s");
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 0.0f };
+
+ def = this->add("filament_diameter", coFloats);
+ def->label = L("Diameter");
+ def->tooltip = L("Enter your filament diameter here. Good precision is required, so use a caliper "
+ "and do multiple measurements along the filament, then compute the average.");
+ def->sidetext = L("mm");
+ def->cli = "filament-diameter=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 3. };
+
+ def = this->add("filament_density", coFloats);
+ def->label = L("Density");
+ def->tooltip = L("Enter your filament density here. This is only for statistical information. "
+ "A decent way is to weigh a known length of filament and compute the ratio "
+ "of the length to volume. Better is to calculate the volume directly through displacement.");
+ def->sidetext = L("g/cm³");
+ def->cli = "filament-density=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("filament_type", coStrings);
+ def->label = L("Filament type");
+ def->tooltip = L("The filament material type for use in custom G-codes.");
+ def->cli = "filament_type=s@";
+ def->gui_type = "f_enum_open";
+ def->gui_flags = "show_value";
+ def->enum_values.push_back("PLA");
+ def->enum_values.push_back("ABS");
+ def->enum_values.push_back("PET");
+ def->enum_values.push_back("HIPS");
+ def->enum_values.push_back("FLEX");
+ def->enum_values.push_back("SCAFF");
+ def->enum_values.push_back("EDGE");
+ def->enum_values.push_back("NGEN");
+ def->enum_values.push_back("PVA");
+ def->default_value = new ConfigOptionStrings { "PLA" };
+
+ def = this->add("filament_soluble", coBools);
+ def->label = L("Soluble material");
+ def->tooltip = L("Soluble material is most likely used for a soluble support.");
+ def->cli = "filament-soluble!";
+ def->default_value = new ConfigOptionBools { false };
+
+ def = this->add("filament_cost", coFloats);
+ def->label = L("Cost");
+ def->tooltip = L("Enter your filament cost per kg here. This is only for statistical information.");
+ def->sidetext = L("money/kg");
+ def->cli = "filament-cost=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("filament_settings_id", coStrings);
+ def->default_value = new ConfigOptionStrings { "" };
+
+ def = this->add("fill_angle", coFloat);
+ def->label = L("Fill angle");
+ def->category = L("Infill");
+ def->tooltip = L("Default base angle for infill orientation. Cross-hatching will be applied to this. "
+ "Bridges will be infilled using the best direction Slic3r can detect, so this setting "
+ "does not affect them.");
+ def->sidetext = L("°");
+ def->cli = "fill-angle=f";
+ def->min = 0;
+ def->max = 360;
+ def->default_value = new ConfigOptionFloat(45);
+
+ def = this->add("fill_density", coPercent);
+ def->gui_type = "f_enum_open";
+ def->gui_flags = "show_value";
+ def->label = L("Fill density");
+ def->category = L("Infill");
+ def->tooltip = L("Density of internal infill, expressed in the range 0% - 100%.");
+ def->sidetext = L("%");
+ def->cli = "fill-density=s";
+ def->min = 0;
+ def->max = 100;
+ def->enum_values.push_back("0");
+ def->enum_values.push_back("5");
+ def->enum_values.push_back("10");
+ def->enum_values.push_back("15");
+ def->enum_values.push_back("20");
+ def->enum_values.push_back("25");
+ def->enum_values.push_back("30");
+ def->enum_values.push_back("40");
+ def->enum_values.push_back("50");
+ def->enum_values.push_back("60");
+ def->enum_values.push_back("70");
+ def->enum_values.push_back("80");
+ def->enum_values.push_back("90");
+ def->enum_values.push_back("100");
+ def->enum_labels.push_back("0%");
+ def->enum_labels.push_back("5%");
+ def->enum_labels.push_back("10%");
+ def->enum_labels.push_back("15%");
+ def->enum_labels.push_back("20%");
+ def->enum_labels.push_back("25%");
+ def->enum_labels.push_back("30%");
+ def->enum_labels.push_back("40%");
+ def->enum_labels.push_back("50%");
+ def->enum_labels.push_back("60%");
+ def->enum_labels.push_back("70%");
+ def->enum_labels.push_back("80%");
+ def->enum_labels.push_back("90%");
+ def->enum_labels.push_back("100%");
+ def->default_value = new ConfigOptionPercent(20);
+
+ def = this->add("fill_pattern", coEnum);
+ def->label = L("Fill pattern");
+ def->category = L("Infill");
+ def->tooltip = L("Fill pattern for general low-density infill.");
+ def->cli = "fill-pattern=s";
+ def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
+ def->enum_values.push_back("rectilinear");
+ def->enum_values.push_back("grid");
+ def->enum_values.push_back("triangles");
+ def->enum_values.push_back("stars");
+ def->enum_values.push_back("cubic");
+ def->enum_values.push_back("line");
+ def->enum_values.push_back("concentric");
+ def->enum_values.push_back("honeycomb");
+ def->enum_values.push_back("3dhoneycomb");
+ def->enum_values.push_back("gyroid");
+ def->enum_values.push_back("hilbertcurve");
+ def->enum_values.push_back("archimedeanchords");
+ def->enum_values.push_back("octagramspiral");
+ def->enum_labels.push_back(L("Rectilinear"));
+ def->enum_labels.push_back(L("Grid"));
+ def->enum_labels.push_back(L("Triangles"));
+ def->enum_labels.push_back(L("Stars"));
+ def->enum_labels.push_back(L("Cubic"));
+ def->enum_labels.push_back(L("Line"));
+ def->enum_labels.push_back(L("Concentric"));
+ def->enum_labels.push_back(L("Honeycomb"));
+ def->enum_labels.push_back(L("3D Honeycomb"));
+ def->enum_labels.push_back(L("Gyroid"));
+ def->enum_labels.push_back(L("Hilbert Curve"));
+ def->enum_labels.push_back(L("Archimedean Chords"));
+ def->enum_labels.push_back(L("Octagram Spiral"));
+ def->default_value = new ConfigOptionEnum<InfillPattern>(ipStars);
+
+ def = this->add("first_layer_acceleration", coFloat);
+ def->label = L("First layer");
+ def->tooltip = L("This is the acceleration your printer will use for first layer. Set zero "
+ "to disable acceleration control for first layer.");
+ def->sidetext = L("mm/s²");
+ def->cli = "first-layer-acceleration=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("first_layer_bed_temperature", coInts);
+ def->label = L("First layer");
+ def->tooltip = L("Heated build plate temperature for the first layer. Set this to zero to disable "
+ "bed temperature control commands in the output.");
+ def->cli = "first-layer-bed-temperature=i@";
+ def->max = 0;
+ def->max = 300;
+ def->default_value = new ConfigOptionInts { 0 };
+
+ def = this->add("first_layer_extrusion_width", coFloatOrPercent);
+ def->label = L("First layer");
+ def->category = L("Extrusion Width");
+ def->tooltip = L("Set this to a non-zero value to set a manual extrusion width for first layer. "
+ "You can use this to force fatter extrudates for better adhesion. If expressed "
+ "as percentage (for example 120%) it will be computed over first layer height. "
+ "If set to zero, it will use the default extrusion width.");
+ def->sidetext = L("mm or % (leave 0 for default)");
+ def->cli = "first-layer-extrusion-width=s";
+ def->ratio_over = "first_layer_height";
+ def->default_value = new ConfigOptionFloatOrPercent(200, true);
+
+ def = this->add("first_layer_height", coFloatOrPercent);
+ def->label = L("First layer height");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("When printing with very low layer heights, you might still want to print a thicker "
+ "bottom layer to improve adhesion and tolerance for non perfect build plates. "
+ "This can be expressed as an absolute value or as a percentage (for example: 150%) "
+ "over the default layer height.");
+ def->sidetext = L("mm or %");
+ def->cli = "first-layer-height=s";
+ def->ratio_over = "layer_height";
+ def->default_value = new ConfigOptionFloatOrPercent(0.35, false);
+
+ def = this->add("first_layer_speed", coFloatOrPercent);
+ def->label = L("First layer speed");
+ def->tooltip = L("If expressed as absolute value in mm/s, this speed will be applied to all the print moves "
+ "of the first layer, regardless of their type. If expressed as a percentage "
+ "(for example: 40%) it will scale the default speeds.");
+ def->sidetext = L("mm/s or %");
+ def->cli = "first-layer-speed=s";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloatOrPercent(30, false);
+
+ def = this->add("first_layer_temperature", coInts);
+ def->label = L("First layer");
+ def->tooltip = L("Extruder temperature for first layer. If you want to control temperature manually "
+ "during print, set this to zero to disable temperature control commands in the output file.");
+ def->cli = "first-layer-temperature=i@";
+ def->min = 0;
+ def->max = max_temp;
+ def->default_value = new ConfigOptionInts { 200 };
+
+ def = this->add("gap_fill_speed", coFloat);
+ def->label = L("Gap fill");
+ def->category = L("Speed");
+ def->tooltip = L("Speed for filling small gaps using short zigzag moves. Keep this reasonably low "
+ "to avoid too much shaking and resonance issues. Set zero to disable gaps filling.");
+ def->sidetext = L("mm/s");
+ def->cli = "gap-fill-speed=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(20);
+
+ def = this->add("gcode_comments", coBool);
+ def->label = L("Verbose G-code");
+ def->tooltip = L("Enable this to get a commented G-code file, with each line explained by a descriptive text. "
+ "If you print from SD card, the additional weight of the file could make your firmware "
+ "slow down.");
+ def->cli = "gcode-comments!";
+ def->default_value = new ConfigOptionBool(0);
+
+ def = this->add("gcode_flavor", coEnum);
+ def->label = L("G-code flavor");
+ def->tooltip = L("Some G/M-code commands, including temperature control and others, are not universal. "
+ "Set this option to your printer's firmware to get a compatible output. "
+ "The \"No extrusion\" flavor prevents Slic3r from exporting any extrusion value at all.");
+ def->cli = "gcode-flavor=s";
+ def->enum_keys_map = &ConfigOptionEnum<GCodeFlavor>::get_enum_values();
+ def->enum_values.push_back("reprap");
+ def->enum_values.push_back("repetier");
+ def->enum_values.push_back("teacup");
+ def->enum_values.push_back("makerware");
+ def->enum_values.push_back("marlin");
+ def->enum_values.push_back("sailfish");
+ def->enum_values.push_back("mach3");
+ def->enum_values.push_back("machinekit");
+ def->enum_values.push_back("smoothie");
+ def->enum_values.push_back("no-extrusion");
+ def->enum_labels.push_back("RepRap/Sprinter");
+ def->enum_labels.push_back("Repetier");
+ def->enum_labels.push_back("Teacup");
+ def->enum_labels.push_back("MakerWare (MakerBot)");
+ def->enum_labels.push_back("Marlin");
+ def->enum_labels.push_back("Sailfish (MakerBot)");
+ def->enum_labels.push_back("Mach3/LinuxCNC");
+ def->enum_labels.push_back("Machinekit");
+ def->enum_labels.push_back("Smoothie");
+ def->enum_labels.push_back(L("No extrusion"));
+ def->default_value = new ConfigOptionEnum<GCodeFlavor>(gcfRepRap);
+
+ def = this->add("infill_acceleration", coFloat);
+ def->label = L("Infill");
+ def->tooltip = L("This is the acceleration your printer will use for infill. Set zero to disable "
+ "acceleration control for infill.");
+ def->sidetext = L("mm/s²");
+ def->cli = "infill-acceleration=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("infill_every_layers", coInt);
+ def->label = L("Combine infill every");
+ def->category = L("Infill");
+ def->tooltip = L("This feature allows to combine infill and speed up your print by extruding thicker "
+ "infill layers while preserving thin perimeters, thus accuracy.");
+ def->sidetext = L("layers");
+ def->cli = "infill-every-layers=i";
+ def->full_label = L("Combine infill every n layers");
+ def->min = 1;
+ def->default_value = new ConfigOptionInt(1);
+
+ def = this->add("infill_extruder", coInt);
+ def->label = L("Infill extruder");
+ def->category = L("Extruders");
+ def->tooltip = L("The extruder to use when printing infill.");
+ def->cli = "infill-extruder=i";
+ def->min = 1;
+ def->default_value = new ConfigOptionInt(1);
+
+ def = this->add("infill_extrusion_width", coFloatOrPercent);
+ def->label = L("Infill");
+ def->category = L("Extrusion Width");
+ def->tooltip = L("Set this to a non-zero value to set a manual extrusion width for infill. "
+ "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. "
+ "You may want to use fatter extrudates to speed up the infill and make your parts stronger. "
+ "If expressed as percentage (for example 90%) it will be computed over layer height.");
+ def->sidetext = L("mm or % (leave 0 for default)");
+ def->cli = "infill-extrusion-width=s";
+ def->default_value = new ConfigOptionFloatOrPercent(0, false);
+
+ def = this->add("infill_first", coBool);
+ def->label = L("Infill before perimeters");
+ def->tooltip = L("This option will switch the print order of perimeters and infill, making the latter first.");
+ def->cli = "infill-first!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("infill_only_where_needed", coBool);
+ def->label = L("Only infill where needed");
+ def->category = L("Infill");
+ def->tooltip = L("This option will limit infill to the areas actually needed for supporting ceilings "
+ "(it will act as internal support material). If enabled, slows down the G-code generation "
+ "due to the multiple checks involved.");
+ def->cli = "infill-only-where-needed!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("infill_overlap", coFloatOrPercent);
+ def->label = L("Infill/perimeters overlap");
+ def->category = L("Advanced");
+ def->tooltip = L("This setting applies an additional overlap between infill and perimeters for better bonding. "
+ "Theoretically this shouldn't be needed, but backlash might cause gaps. If expressed "
+ "as percentage (example: 15%) it is calculated over perimeter extrusion width.");
+ def->sidetext = L("mm or %");
+ def->cli = "infill-overlap=s";
+ def->ratio_over = "perimeter_extrusion_width";
+ def->default_value = new ConfigOptionFloatOrPercent(25, true);
+
+ def = this->add("infill_speed", coFloat);
+ def->label = L("Infill");
+ def->category = L("Speed");
+ def->tooltip = L("Speed for printing the internal fill. Set to zero for auto.");
+ def->sidetext = L("mm/s");
+ def->cli = "infill-speed=f";
+ def->aliases = { "print_feed_rate", "infill_feed_rate" };
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(80);
+
+ def = this->add("inherits", coString);
+ def->label = L("Inherits profile");
+ def->tooltip = L("Name of the profile, from which this profile inherits.");
+ def->full_width = true;
+ def->height = 50;
+ def->default_value = new ConfigOptionString();
+
+ // The following value is to be stored into the project file (AMF, 3MF, Config ...)
+ // and it contains a sum of "inherits" values over the print and filament profiles.
+ def = this->add("inherits_cummulative", coStrings);
+ def->default_value = new ConfigOptionStrings();
+
+ def = this->add("interface_shells", coBool);
+ def->label = L("Interface shells");
+ def->tooltip = L("Force the generation of solid shells between adjacent materials/volumes. "
+ "Useful for multi-extruder prints with translucent materials or manual soluble "
+ "support material.");
+ def->cli = "interface-shells!";
+ def->category = L("Layers and Perimeters");
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("layer_gcode", coString);
+ def->label = L("After layer change G-code");
+ def->tooltip = L("This custom code is inserted at every layer change, right after the Z move "
+ "and before the extruder moves to the first layer point. Note that you can use "
+ "placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z].");
+ def->cli = "after-layer-gcode|layer-gcode=s";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 50;
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("remaining_times", coBool);
+ def->label = L("Supports remaining times");
+ def->tooltip = L("Emit M73 P[percent printed] R[remaining time in minutes] at 1 minute"
+ " intervals into the G-code to let the firmware show accurate remaining time."
+ " As of now only the Prusa i3 MK3 firmware recognizes M73."
+ " Also the i3 MK3 firmware supports M73 Qxx Sxx for the silent mode.");
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("silent_mode", coBool);
+ def->label = L("Supports silent mode");
+ def->tooltip = L("Set silent mode for the G-code flavor");
+ def->default_value = new ConfigOptionBool(true);
+
+ const int machine_limits_opt_width = 70;
+ {
+ struct AxisDefault {
+ std::string name;
+ std::vector<double> max_feedrate;
+ std::vector<double> max_acceleration;
+ std::vector<double> max_jerk;
+ };
+ std::vector<AxisDefault> axes {
+ // name, max_feedrate, max_acceleration, max_jerk
+ { "x", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } },
+ { "y", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } },
+ { "z", { 12., 12. }, { 500., 200. }, { 0.2, 0.4 } },
+ { "e", { 120., 120. }, { 10000., 5000. }, { 2.5, 2.5 } }
+ };
+ for (const AxisDefault &axis : axes) {
+ std::string axis_upper = boost::to_upper_copy<std::string>(axis.name);
+ // Add the machine feedrate limits for XYZE axes. (M203)
+ def = this->add("machine_max_feedrate_" + axis.name, coFloats);
+ def->full_label = (boost::format(L("Maximum feedrate %1%")) % axis_upper).str();
+ def->category = L("Machine limits");
+ def->tooltip = (boost::format(L("Maximum feedrate of the %1% axis")) % axis_upper).str();
+ def->sidetext = L("mm/s");
+ def->min = 0;
+ def->width = machine_limits_opt_width;
+ def->default_value = new ConfigOptionFloats(axis.max_feedrate);
+ // Add the machine acceleration limits for XYZE axes (M201)
+ def = this->add("machine_max_acceleration_" + axis.name, coFloats);
+ def->full_label = (boost::format(L("Maximum acceleration %1%")) % axis_upper).str();
+ def->category = L("Machine limits");
+ def->tooltip = (boost::format(L("Maximum acceleration of the %1% axis")) % axis_upper).str();
+ def->sidetext = L("mm/s²");
+ def->min = 0;
+ def->width = machine_limits_opt_width;
+ def->default_value = new ConfigOptionFloats(axis.max_acceleration);
+ // Add the machine jerk limits for XYZE axes (M205)
+ def = this->add("machine_max_jerk_" + axis.name, coFloats);
+ def->full_label = (boost::format(L("Maximum jerk %1%")) % axis_upper).str();
+ def->category = L("Machine limits");
+ def->tooltip = (boost::format(L("Maximum jerk of the %1% axis")) % axis_upper).str();
+ def->sidetext = L("mm/s");
+ def->min = 0;
+ def->width = machine_limits_opt_width;
+ def->default_value = new ConfigOptionFloats(axis.max_jerk);
+ }
+ }
+
+ // M205 S... [mm/sec]
+ def = this->add("machine_min_extruding_rate", coFloats);
+ def->full_label = L("Minimum feedrate when extruding");
+ def->category = L("Machine limits");
+ def->tooltip = L("Minimum feedrate when extruding") + " (M205 S)";
+ def->sidetext = L("mm/s");
+ def->min = 0;
+ def->width = machine_limits_opt_width;
+ def->default_value = new ConfigOptionFloats{ 0., 0. };
+
+ // M205 T... [mm/sec]
+ def = this->add("machine_min_travel_rate", coFloats);
+ def->full_label = L("Minimum travel feedrate");
+ def->category = L("Machine limits");
+ def->tooltip = L("Minimum travel feedrate") + " (M205 T)";
+ def->sidetext = L("mm/s");
+ def->min = 0;
+ def->width = machine_limits_opt_width;
+ def->default_value = new ConfigOptionFloats{ 0., 0. };
+
+ // M204 S... [mm/sec^2]
+ def = this->add("machine_max_acceleration_extruding", coFloats);
+ def->full_label = L("Maximum acceleration when extruding");
+ def->category = L("Machine limits");
+ def->tooltip = L("Maximum acceleration when extruding") + " (M204 S)";
+ def->sidetext = L("mm/s²");
+ def->min = 0;
+ def->width = machine_limits_opt_width;
+ def->default_value = new ConfigOptionFloats{ 1500., 1250. };
+
+ // M204 T... [mm/sec^2]
+ def = this->add("machine_max_acceleration_retracting", coFloats);
+ def->full_label = L("Maximum acceleration when retracting");
+ def->category = L("Machine limits");
+ def->tooltip = L("Maximum acceleration when retracting") + " (M204 T)";
+ def->sidetext = L("mm/s²");
+ def->min = 0;
+ def->width = machine_limits_opt_width;
+ def->default_value = new ConfigOptionFloats{ 1500., 1250. };
+
+ def = this->add("max_fan_speed", coInts);
+ def->label = L("Max");
+ def->tooltip = L("This setting represents the maximum speed of your fan.");
+ def->sidetext = L("%");
+ def->cli = "max-fan-speed=i@";
+ def->min = 0;
+ def->max = 100;
+ def->default_value = new ConfigOptionInts { 100 };
+
+ def = this->add("max_layer_height", coFloats);
+ def->label = L("Max");
+ def->tooltip = L("This is the highest printable layer height for this extruder, used to cap "
+ "the variable layer height and support layer height. Maximum recommended layer height "
+ "is 75% of the extrusion width to achieve reasonable inter-layer adhesion. "
+ "If set to 0, layer height is limited to 75% of the nozzle diameter.");
+ def->sidetext = L("mm");
+ def->cli = "max-layer-height=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("max_print_speed", coFloat);
+ def->label = L("Max print speed");
+ def->tooltip = L("When setting other speed settings to 0 Slic3r will autocalculate the optimal speed "
+ "in order to keep constant extruder pressure. This experimental setting is used "
+ "to set the highest print speed you want to allow.");
+ def->sidetext = L("mm/s");
+ def->cli = "max-print-speed=f";
+ def->min = 1;
+ def->default_value = new ConfigOptionFloat(80);
+
+ def = this->add("max_volumetric_speed", coFloat);
+ def->label = L("Max volumetric speed");
+ def->tooltip = L("This experimental setting is used to set the maximum volumetric speed your "
+ "extruder supports.");
+ def->sidetext = L("mm³/s");
+ def->cli = "max-volumetric-speed=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("max_volumetric_extrusion_rate_slope_positive", coFloat);
+ def->label = L("Max volumetric slope positive");
+ def->tooltip = L("This experimental setting is used to limit the speed of change in extrusion rate. "
+ "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate "
+ "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) "
+ "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds.");
+ def->sidetext = L("mm³/s²");
+ def->cli = "max-volumetric-extrusion-rate-slope-positive=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("max_volumetric_extrusion_rate_slope_negative", coFloat);
+ def->label = L("Max volumetric slope negative");
+ def->tooltip = L("This experimental setting is used to limit the speed of change in extrusion rate. "
+ "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate "
+ "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) "
+ "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds.");
+ def->sidetext = L("mm³/s²");
+ def->cli = "max-volumetric-extrusion-rate-slope-negative=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("min_fan_speed", coInts);
+ def->label = L("Min");
+ def->tooltip = L("This setting represents the minimum PWM your fan needs to work.");
+ def->sidetext = L("%");
+ def->cli = "min-fan-speed=i@";
+ def->min = 0;
+ def->max = 100;
+ def->default_value = new ConfigOptionInts { 35 };
+
+ def = this->add("min_layer_height", coFloats);
+ def->label = L("Min");
+ def->tooltip = L("This is the lowest printable layer height for this extruder and limits "
+ "the resolution for variable layer height. Typical values are between 0.05 mm and 0.1 mm.");
+ def->sidetext = L("mm");
+ def->cli = "min-layer-height=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 0.07 };
+
+ def = this->add("min_print_speed", coFloats);
+ def->label = L("Min print speed");
+ def->tooltip = L("Slic3r will not scale speed down below this speed.");
+ def->sidetext = L("mm/s");
+ def->cli = "min-print-speed=f@";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats { 10. };
+
+ def = this->add("min_skirt_length", coFloat);
+ def->label = L("Minimal filament extrusion length");
+ def->tooltip = L("Generate no less than the number of skirt loops required to consume "
+ "the specified amount of filament on the bottom layer. For multi-extruder machines, "
+ "this minimum applies to each extruder.");
+ def->sidetext = L("mm");
+ def->cli = "min-skirt-length=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("notes", coString);
+ def->label = L("Configuration notes");
+ def->tooltip = L("You can put here your personal notes. This text will be added to the G-code "
+ "header comments.");
+ def->cli = "notes=s";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 130;
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("nozzle_diameter", coFloats);
+ def->label = L("Nozzle diameter");
+ def->tooltip = L("This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)");
+ def->sidetext = L("mm");
+ def->cli = "nozzle-diameter=f@";
+ def->default_value = new ConfigOptionFloats { 0.5 };
+
+ def = this->add("host_type", coEnum);
+ def->label = L("Host Type");
+ def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain "
+ "the kind of the host.");
+ def->cli = "host-type=s";
+ def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values();
+ def->enum_values.push_back("octoprint");
+ def->enum_values.push_back("duet");
+ def->enum_labels.push_back("OctoPrint");
+ def->enum_labels.push_back("Duet");
+ def->default_value = new ConfigOptionEnum<PrintHostType>(htOctoPrint);
+
+ def = this->add("printhost_apikey", coString);
+ def->label = L("API Key / Password");
+ def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
+ "the API Key or the password required for authentication.");
+ def->cli = "printhost-apikey=s";
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("printhost_cafile", coString);
+ def->label = "HTTPS CA file";
+ def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. "
+ "If left blank, the default OS CA certificate repository is used.";
+ def->cli = "printhost-cafile=s";
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("print_host", coString);
+ def->label = L("Hostname, IP or URL");
+ def->tooltip = L("Slic3r can upload G-code files to a printer host. This field should contain "
+ "the hostname, IP address or URL of the printer host instance.");
+ def->cli = "print-host=s";
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("only_retract_when_crossing_perimeters", coBool);
+ def->label = L("Only retract when crossing perimeters");
+ def->tooltip = L("Disables retraction when the travel path does not exceed the upper layer's perimeters "
+ "(and thus any ooze will be probably invisible).");
+ def->cli = "only-retract-when-crossing-perimeters!";
+ def->default_value = new ConfigOptionBool(true);
+
+ def = this->add("ooze_prevention", coBool);
+ def->label = L("Enable");
+ def->tooltip = L("This option will drop the temperature of the inactive extruders to prevent oozing. "
+ "It will enable a tall skirt automatically and move extruders outside such "
+ "skirt when changing temperatures.");
+ def->cli = "ooze-prevention!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("output_filename_format", coString);
+ def->label = L("Output filename format");
+ def->tooltip = L("You can use all configuration options as variables inside this template. "
+ "For example: [layer_height], [fill_density] etc. You can also use [timestamp], "
+ "[year], [month], [day], [hour], [minute], [second], [version], [input_filename], "
+ "[input_filename_base].");
+ def->cli = "output-filename-format=s";
+ def->full_width = true;
+ def->default_value = new ConfigOptionString("[input_filename_base].gcode");
+
+ def = this->add("overhangs", coBool);
+ def->label = L("Detect bridging perimeters");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("Experimental option to adjust flow for overhangs (bridge flow will be used), "
+ "to apply bridge speed to them and enable fan.");
+ def->cli = "overhangs!";
+ def->default_value = new ConfigOptionBool(true);
+
+ def = this->add("parking_pos_retraction", coFloat);
+ def->label = L("Filament parking position");
+ def->tooltip = L("Distance of the extruder tip from the position where the filament is parked "
+ "when unloaded. This should match the value in printer firmware. ");
+ def->sidetext = L("mm");
+ def->cli = "parking_pos_retraction=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(92.f);
+
+ def = this->add("extra_loading_move", coFloat);
+ def->label = L("Extra loading distance");
+ def->tooltip = L("When set to zero, the distance the filament is moved from parking position during load "
+ "is exactly the same as it was moved back during unload. When positive, it is loaded further, "
+ " if negative, the loading move is shorter than unloading. ");
+ def->sidetext = L("mm");
+ def->cli = "extra_loading_move=f";
+ def->default_value = new ConfigOptionFloat(-2.f);
+
+ def = this->add("perimeter_acceleration", coFloat);
+ def->label = L("Perimeters");
+ def->tooltip = L("This is the acceleration your printer will use for perimeters. "
+ "A high value like 9000 usually gives good results if your hardware is up to the job. "
+ "Set zero to disable acceleration control for perimeters.");
+ def->sidetext = L("mm/s²");
+ def->cli = "perimeter-acceleration=f";
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("perimeter_extruder", coInt);
+ def->label = L("Perimeter extruder");
+ def->category = L("Extruders");
+ def->tooltip = L("The extruder to use when printing perimeters and brim. First extruder is 1.");
+ def->cli = "perimeter-extruder=i";
+ def->aliases = { "perimeters_extruder" };
+ def->min = 1;
+ def->default_value = new ConfigOptionInt(1);
+
+ def = this->add("perimeter_extrusion_width", coFloatOrPercent);
+ def->label = L("Perimeters");
+ def->category = L("Extrusion Width");
+ def->tooltip = L("Set this to a non-zero value to set a manual extrusion width for perimeters. "
+ "You may want to use thinner extrudates to get more accurate surfaces. "
+ "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. "
+ "If expressed as percentage (for example 200%) it will be computed over layer height.");
+ def->sidetext = L("mm or % (leave 0 for default)");
+ def->cli = "perimeter-extrusion-width=s";
+ def->aliases = { "perimeters_extrusion_width" };
+ def->default_value = new ConfigOptionFloatOrPercent(0, false);
+
+ def = this->add("perimeter_speed", coFloat);
+ def->label = L("Perimeters");
+ def->category = L("Speed");
+ def->tooltip = L("Speed for perimeters (contours, aka vertical shells). Set to zero for auto.");
+ def->sidetext = L("mm/s");
+ def->cli = "perimeter-speed=f";
+ def->aliases = { "perimeter_feed_rate" };
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(60);
+
+ def = this->add("perimeters", coInt);
+ def->label = L("Perimeters");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("This option sets the number of perimeters to generate for each layer. "
+ "Note that Slic3r may increase this number automatically when it detects "
+ "sloping surfaces which benefit from a higher number of perimeters "
+ "if the Extra Perimeters option is enabled.");
+ def->sidetext = L("(minimum)");
+ def->cli = "perimeters=i";
+ def->aliases = { "perimeter_offsets" };
+ def->min = 0;
+ def->default_value = new ConfigOptionInt(3);
+
+ def = this->add("post_process", coStrings);
+ def->label = L("Post-processing scripts");
+ def->tooltip = L("If you want to process the output G-code through custom scripts, "
+ "just list their absolute paths here. Separate multiple scripts with a semicolon. "
+ "Scripts will be passed the absolute path to the G-code file as the first argument, "
+ "and they can access the Slic3r config settings by reading environment variables.");
+ def->cli = "post-process=s@";
+ def->gui_flags = "serialized";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 60;
+ def->default_value = new ConfigOptionStrings();
+
+ def = this->add("printer_model", coString);
+ def->label = L("Printer type");
+ def->tooltip = L("Type of the printer.");
+ def->default_value = new ConfigOptionString();
+
+ def = this->add("printer_notes", coString);
+ def->label = L("Printer notes");
+ def->tooltip = L("You can put your notes regarding the printer here.");
+ def->cli = "printer-notes=s";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 130;
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("printer_vendor", coString);
+ def->label = L("Printer vendor");
+ def->tooltip = L("Name of the printer vendor.");
+ def->default_value = new ConfigOptionString();
+
+ def = this->add("printer_variant", coString);
+ def->label = L("Printer variant");
+ def->tooltip = L("Name of the printer variant. For example, the printer variants may be differentiated by a nozzle diameter.");
+ def->default_value = new ConfigOptionString();
+
+ def = this->add("print_settings_id", coString);
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("printer_settings_id", coString);
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("raft_layers", coInt);
+ def->label = L("Raft layers");
+ def->category = L("Support material");
+ def->tooltip = L("The object will be raised by this number of layers, and support material "
+ "will be generated under it.");
+ def->sidetext = L("layers");
+ def->cli = "raft-layers=i";
+ def->min = 0;
+ def->default_value = new ConfigOptionInt(0);
+
+ def = this->add("resolution", coFloat);
+ def->label = L("Resolution");
+ def->tooltip = L("Minimum detail resolution, used to simplify the input file for speeding up "
+ "the slicing job and reducing memory usage. High-resolution models often carry "
+ "more detail than printers can render. Set to zero to disable any simplification "
+ "and use full resolution from input.");
+ def->sidetext = L("mm");
+ def->cli = "resolution=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("retract_before_travel", coFloats);
+ def->label = L("Minimum travel after retraction");
+ def->tooltip = L("Retraction is not triggered when travel moves are shorter than this length.");
+ def->sidetext = L("mm");
+ def->cli = "retract-before-travel=f@";
+ def->default_value = new ConfigOptionFloats { 2. };
+
+ def = this->add("retract_before_wipe", coPercents);
+ def->label = L("Retract amount before wipe");
+ def->tooltip = L("With bowden extruders, it may be wise to do some amount of quick retract "
+ "before doing the wipe movement.");
+ def->sidetext = L("%");
+ def->cli = "retract-before-wipe=s@";
+ def->default_value = new ConfigOptionPercents { 0. };
+
+ def = this->add("retract_layer_change", coBools);
+ def->label = L("Retract on layer change");
+ def->tooltip = L("This flag enforces a retraction whenever a Z move is done.");
+ def->cli = "retract-layer-change!";
+ def->default_value = new ConfigOptionBools { false };
+
+ def = this->add("retract_length", coFloats);
+ def->label = L("Length");
+ def->full_label = L("Retraction Length");
+ def->tooltip = L("When retraction is triggered, filament is pulled back by the specified amount "
+ "(the length is measured on raw filament, before it enters the extruder).");
+ def->sidetext = L("mm (zero to disable)");
+ def->cli = "retract-length=f@";
+ def->default_value = new ConfigOptionFloats { 2. };
+
+ def = this->add("retract_length_toolchange", coFloats);
+ def->label = L("Length");
+ def->full_label = L("Retraction Length (Toolchange)");
+ def->tooltip = L("When retraction is triggered before changing tool, filament is pulled back "
+ "by the specified amount (the length is measured on raw filament, before it enters "
+ "the extruder).");
+ def->sidetext = L("mm (zero to disable)");
+ def->cli = "retract-length-toolchange=f@";
+ def->default_value = new ConfigOptionFloats { 10. };
+
+ def = this->add("retract_lift", coFloats);
+ def->label = L("Lift Z");
+ def->tooltip = L("If you set this to a positive value, Z is quickly raised every time a retraction "
+ "is triggered. When using multiple extruders, only the setting for the first extruder "
+ "will be considered.");
+ def->sidetext = L("mm");
+ def->cli = "retract-lift=f@";
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("retract_lift_above", coFloats);
+ def->label = L("Above Z");
+ def->full_label = L("Only lift Z above");
+ def->tooltip = L("If you set this to a positive value, Z lift will only take place above the specified "
+ "absolute Z. You can tune this setting for skipping lift on the first layers.");
+ def->sidetext = L("mm");
+ def->cli = "retract-lift-above=f@";
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("retract_lift_below", coFloats);
+ def->label = L("Below Z");
+ def->full_label = L("Only lift Z below");
+ def->tooltip = L("If you set this to a positive value, Z lift will only take place below "
+ "the specified absolute Z. You can tune this setting for limiting lift "
+ "to the first layers.");
+ def->sidetext = L("mm");
+ def->cli = "retract-lift-below=f@";
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("retract_restart_extra", coFloats);
+ def->label = L("Extra length on restart");
+ def->tooltip = L("When the retraction is compensated after the travel move, the extruder will push "
+ "this additional amount of filament. This setting is rarely needed.");
+ def->sidetext = L("mm");
+ def->cli = "retract-restart-extra=f@";
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("retract_restart_extra_toolchange", coFloats);
+ def->label = L("Extra length on restart");
+ def->tooltip = L("When the retraction is compensated after changing tool, the extruder will push "
+ "this additional amount of filament.");
+ def->sidetext = L("mm");
+ def->cli = "retract-restart-extra-toolchange=f@";
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("retract_speed", coFloats);
+ def->label = L("Retraction Speed");
+ def->full_label = L("Retraction Speed");
+ def->tooltip = L("The speed for retractions (it only applies to the extruder motor).");
+ def->sidetext = L("mm/s");
+ def->cli = "retract-speed=f@";
+ def->default_value = new ConfigOptionFloats { 40. };
+
+ def = this->add("deretract_speed", coFloats);
+ def->label = L("Deretraction Speed");
+ def->full_label = L("Deretraction Speed");
+ def->tooltip = L("The speed for loading of a filament into extruder after retraction "
+ "(it only applies to the extruder motor). If left to zero, the retraction speed is used.");
+ def->sidetext = L("mm/s");
+ def->cli = "retract-speed=f@";
+ def->default_value = new ConfigOptionFloats { 0. };
+
+ def = this->add("seam_position", coEnum);
+ def->label = L("Seam position");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("Position of perimeters starting points.");
+ def->cli = "seam-position=s";
+ def->enum_keys_map = &ConfigOptionEnum<SeamPosition>::get_enum_values();
+ def->enum_values.push_back("random");
+ def->enum_values.push_back("nearest");
+ def->enum_values.push_back("aligned");
+ def->enum_values.push_back("rear");
+ def->enum_labels.push_back(L("Random"));
+ def->enum_labels.push_back(L("Nearest"));
+ def->enum_labels.push_back(L("Aligned"));
+ def->enum_labels.push_back(L("Rear"));
+ def->default_value = new ConfigOptionEnum<SeamPosition>(spAligned);
+
+#if 0
+ def = this->add("seam_preferred_direction", coFloat);
+// def->gui_type = "slider";
+ def->label = L("Direction");
+ def->sidetext = L("°");
+ def->full_label = L("Preferred direction of the seam");
+ def->tooltip = L("Seam preferred direction");
+ def->cli = "seam-preferred-direction=f";
+ def->min = 0;
+ def->max = 360;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("seam_preferred_direction_jitter", coFloat);
+// def->gui_type = "slider";
+ def->label = L("Jitter");
+ def->sidetext = L("°");
+ def->full_label = L("Seam preferred direction jitter");
+ def->tooltip = L("Preferred direction of the seam - jitter");
+ def->cli = "seam-preferred-direction-jitter=f";
+ def->min = 0;
+ def->max = 360;
+ def->default_value = new ConfigOptionFloat(30);
+#endif
+
+ def = this->add("serial_port", coString);
+ def->gui_type = "select_open";
+ def->label = "";
+ def->full_label = L("Serial port");
+ def->tooltip = L("USB/serial port for printer connection.");
+ def->cli = "serial-port=s";
+ def->width = 200;
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("serial_speed", coInt);
+ def->gui_type = "i_enum_open";
+ def->label = L("Speed");
+ def->full_label = L("Serial port speed");
+ def->tooltip = L("Speed (baud) of USB/serial port for printer connection.");
+ def->cli = "serial-speed=i";
+ def->min = 1;
+ def->max = 300000;
+ def->enum_values.push_back("115200");
+ def->enum_values.push_back("250000");
+ def->default_value = new ConfigOptionInt(250000);
+
+ def = this->add("skirt_distance", coFloat);
+ def->label = L("Distance from object");
+ def->tooltip = L("Distance between skirt and object(s). Set this to zero to attach the skirt "
+ "to the object(s) and get a brim for better adhesion.");
+ def->sidetext = L("mm");
+ def->cli = "skirt-distance=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(6);
+
+ def = this->add("skirt_height", coInt);
+ def->label = L("Skirt height");
+ def->tooltip = L("Height of skirt expressed in layers. Set this to a tall value to use skirt "
+ "as a shield against drafts.");
+ def->sidetext = L("layers");
+ def->cli = "skirt-height=i";
+ def->default_value = new ConfigOptionInt(1);
+
+ def = this->add("skirts", coInt);
+ def->label = L("Loops (minimum)");
+ def->full_label = L("Skirt Loops");
+ def->tooltip = L("Number of loops for the skirt. If the Minimum Extrusion Length option is set, "
+ "the number of loops might be greater than the one configured here. Set this to zero "
+ "to disable skirt completely.");
+ def->cli = "skirts=i";
+ def->min = 0;
+ def->default_value = new ConfigOptionInt(1);
+
+ def = this->add("slowdown_below_layer_time", coInts);
+ def->label = L("Slow down if layer print time is below");
+ def->tooltip = L("If layer print time is estimated below this number of seconds, print moves "
+ "speed will be scaled down to extend duration to this value.");
+ def->sidetext = L("approximate seconds");
+ def->cli = "slowdown-below-layer-time=i@";
+ def->width = 60;
+ def->min = 0;
+ def->max = 1000;
+ def->default_value = new ConfigOptionInts { 5 };
+
+ def = this->add("small_perimeter_speed", coFloatOrPercent);
+ def->label = L("Small perimeters");
+ def->category = L("Speed");
+ def->tooltip = L("This separate setting will affect the speed of perimeters having radius <= 6.5mm "
+ "(usually holes). If expressed as percentage (for example: 80%) it will be calculated "
+ "on the perimeters speed setting above. Set to zero for auto.");
+ def->sidetext = L("mm/s or %");
+ def->cli = "small-perimeter-speed=s";
+ def->ratio_over = "perimeter_speed";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloatOrPercent(15, false);
+
+ def = this->add("solid_infill_below_area", coFloat);
+ def->label = L("Solid infill threshold area");
+ def->category = L("Infill");
+ def->tooltip = L("Force solid infill for regions having a smaller area than the specified threshold.");
+ def->sidetext = L("mm²");
+ def->cli = "solid-infill-below-area=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(70);
+
+ def = this->add("solid_infill_extruder", coInt);
+ def->label = L("Solid infill extruder");
+ def->category = L("Extruders");
+ def->tooltip = L("The extruder to use when printing solid infill.");
+ def->cli = "solid-infill-extruder=i";
+ def->min = 1;
+ def->default_value = new ConfigOptionInt(1);
+
+ def = this->add("solid_infill_every_layers", coInt);
+ def->label = L("Solid infill every");
+ def->category = L("Infill");
+ def->tooltip = L("This feature allows to force a solid layer every given number of layers. "
+ "Zero to disable. You can set this to any value (for example 9999); "
+ "Slic3r will automatically choose the maximum possible number of layers "
+ "to combine according to nozzle diameter and layer height.");
+ def->sidetext = L("layers");
+ def->cli = "solid-infill-every-layers=i";
+ def->min = 0;
+ def->default_value = new ConfigOptionInt(0);
+
+ def = this->add("solid_infill_extrusion_width", coFloatOrPercent);
+ def->label = L("Solid infill");
+ def->category = L("Extrusion Width");
+ def->tooltip = L("Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. "
+ "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. "
+ "If expressed as percentage (for example 90%) it will be computed over layer height.");
+ def->sidetext = L("mm or % (leave 0 for default)");
+ def->cli = "solid-infill-extrusion-width=s";
+ def->default_value = new ConfigOptionFloatOrPercent(0, false);
+
+ def = this->add("solid_infill_speed", coFloatOrPercent);
+ def->label = L("Solid infill");
+ def->category = L("Speed");
+ def->tooltip = L("Speed for printing solid regions (top/bottom/internal horizontal shells). "
+ "This can be expressed as a percentage (for example: 80%) over the default "
+ "infill speed above. Set to zero for auto.");
+ def->sidetext = L("mm/s or %");
+ def->cli = "solid-infill-speed=s";
+ def->ratio_over = "infill_speed";
+ def->aliases = { "solid_infill_feed_rate" };
+ def->min = 0;
+ def->default_value = new ConfigOptionFloatOrPercent(20, false);
+
+ def = this->add("solid_layers", coInt);
+ def->label = L("Solid layers");
+ def->tooltip = L("Number of solid layers to generate on top and bottom surfaces.");
+ def->cli = "solid-layers=i";
+ def->shortcut.push_back("top_solid_layers");
+ def->shortcut.push_back("bottom_solid_layers");
+ def->min = 0;
+
+ def = this->add("spiral_vase", coBool);
+ def->label = L("Spiral vase");
+ def->tooltip = L("This feature will raise Z gradually while printing a single-walled object "
+ "in order to remove any visible seam. This option requires a single perimeter, "
+ "no infill, no top solid layers and no support material. You can still set "
+ "any number of bottom solid layers as well as skirt/brim loops. "
+ "It won't work when printing more than an object.");
+ def->cli = "spiral-vase!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("standby_temperature_delta", coInt);
+ def->label = L("Temperature variation");
+ def->tooltip = L("Temperature difference to be applied when an extruder is not active. "
+ "Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped.");
+ def->sidetext = "∆°C";
+ def->cli = "standby-temperature-delta=i";
+ def->min = -max_temp;
+ def->max = max_temp;
+ def->default_value = new ConfigOptionInt(-5);
+
+ def = this->add("start_gcode", coString);
+ def->label = L("Start G-code");
+ def->tooltip = L("This start procedure is inserted at the beginning, after bed has reached "
+ "the target temperature and extruder just started heating, and before extruder "
+ "has finished heating. If Slic3r detects M104 or M190 in your custom codes, "
+ "such commands will not be prepended automatically so you're free to customize "
+ "the order of heating commands and other custom actions. Note that you can use "
+ "placeholder variables for all Slic3r settings, so you can put "
+ "a \"M109 S[first_layer_temperature]\" command wherever you want.");
+ def->cli = "start-gcode=s";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 120;
+ def->default_value = new ConfigOptionString("G28 ; home all axes\nG1 Z5 F5000 ; lift nozzle\n");
+
+ def = this->add("start_filament_gcode", coStrings);
+ def->label = L("Start G-code");
+ def->tooltip = L("This start procedure is inserted at the beginning, after any printer start gcode. "
+ "This is used to override settings for a specific filament. If Slic3r detects "
+ "M104, M109, M140 or M190 in your custom codes, such commands will "
+ "not be prepended automatically so you're free to customize the order "
+ "of heating commands and other custom actions. Note that you can use placeholder variables "
+ "for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command "
+ "wherever you want. If you have multiple extruders, the gcode is processed "
+ "in extruder order.");
+ def->cli = "start-filament-gcode=s@";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 120;
+ def->default_value = new ConfigOptionStrings { "; Filament gcode\n" };
+
+ def = this->add("single_extruder_multi_material", coBool);
+ def->label = L("Single Extruder Multi Material");
+ def->tooltip = L("The printer multiplexes filaments into a single hot end.");
+ def->cli = "single-extruder-multi-material!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("single_extruder_multi_material_priming", coBool);
+ def->label = L("Prime all printing extruders");
+ def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print.");
+ def->cli = "single-extruder-multi-material-priming!";
+ def->default_value = new ConfigOptionBool(true);
+
+ def = this->add("support_material", coBool);
+ def->label = L("Generate support material");
+ def->category = L("Support material");
+ def->tooltip = L("Enable support material generation.");
+ def->cli = "support-material!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("support_material_auto", coBool);
+ def->label = L("Auto generated supports");
+ def->category = L("Support material");
+ def->tooltip = L("If checked, supports will be generated automatically based on the overhang threshold value."\
+ " If unchecked, supports will be generated inside the \"Support Enforcer\" volumes only.");
+ def->cli = "support-material-auto!";
+ def->default_value = new ConfigOptionBool(true);
+
+ def = this->add("support_material_xy_spacing", coFloatOrPercent);
+ def->label = L("XY separation between an object and its support");
+ def->category = L("Support material");
+ def->tooltip = L("XY separation between an object and its support. If expressed as percentage "
+ "(for example 50%), it will be calculated over external perimeter width.");
+ def->sidetext = L("mm or %");
+ def->cli = "support-material-xy-spacing=s";
+ def->ratio_over = "external_perimeter_extrusion_width";
+ def->min = 0;
+ // Default is half the external perimeter width.
+ def->default_value = new ConfigOptionFloatOrPercent(50, true);
+
+ def = this->add("support_material_angle", coFloat);
+ def->label = L("Pattern angle");
+ def->category = L("Support material");
+ def->tooltip = L("Use this setting to rotate the support material pattern on the horizontal plane.");
+ def->sidetext = L("°");
+ def->cli = "support-material-angle=f";
+ def->min = 0;
+ def->max = 359;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("support_material_buildplate_only", coBool);
+ def->label = L("Support on build plate only");
+ def->category = L("Support material");
+ def->tooltip = L("Only create support if it lies on a build plate. Don't create support on a print.");
+ def->cli = "support-material-buildplate-only!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("support_material_contact_distance", coFloat);
+ def->gui_type = "f_enum_open";
+ def->label = L("Contact Z distance");
+ def->category = L("Support material");
+ def->tooltip = L("The vertical distance between object and support material interface. "
+ "Setting this to 0 will also prevent Slic3r from using bridge flow and speed "
+ "for the first object layer.");
+ def->sidetext = L("mm");
+ def->cli = "support-material-contact-distance=f";
+// def->min = 0;
+ def->enum_values.push_back("0");
+ def->enum_values.push_back("0.2");
+ def->enum_labels.push_back((boost::format("0 (%1%)") % L("soluble")).str());
+ def->enum_labels.push_back((boost::format("0.2 (%1%)") % L("detachable")).str());
+ def->default_value = new ConfigOptionFloat(0.2);
+
+ def = this->add("support_material_enforce_layers", coInt);
+ def->label = L("Enforce support for the first");
+ def->category = L("Support material");
+ def->tooltip = L("Generate support material for the specified number of layers counting from bottom, "
+ "regardless of whether normal support material is enabled or not and regardless "
+ "of any angle threshold. This is useful for getting more adhesion of objects "
+ "having a very thin or poor footprint on the build plate.");
+ def->sidetext = L("layers");
+ def->cli = "support-material-enforce-layers=f";
+ def->full_label = L("Enforce support for the first n layers");
+ def->min = 0;
+ def->default_value = new ConfigOptionInt(0);
+
+ def = this->add("support_material_extruder", coInt);
+ def->label = L("Support material/raft/skirt extruder");
+ def->category = L("Extruders");
+ def->tooltip = L("The extruder to use when printing support material, raft and skirt "
+ "(1+, 0 to use the current extruder to minimize tool changes).");
+ def->cli = "support-material-extruder=i";
+ def->min = 0;
+ def->default_value = new ConfigOptionInt(1);
+
+ def = this->add("support_material_extrusion_width", coFloatOrPercent);
+ def->label = L("Support material");
+ def->category = L("Extrusion Width");
+ def->tooltip = L("Set this to a non-zero value to set a manual extrusion width for support material. "
+ "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. "
+ "If expressed as percentage (for example 90%) it will be computed over layer height.");
+ def->sidetext = L("mm or % (leave 0 for default)");
+ def->cli = "support-material-extrusion-width=s";
+ def->default_value = new ConfigOptionFloatOrPercent(0, false);
+
+ def = this->add("support_material_interface_contact_loops", coBool);
+ def->label = L("Interface loops");
+ def->category = L("Support material");
+ def->tooltip = L("Cover the top contact layer of the supports with loops. Disabled by default.");
+ def->cli = "support-material-interface-contact-loops!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("support_material_interface_extruder", coInt);
+ def->label = L("Support material/raft interface extruder");
+ def->category = L("Extruders");
+ def->tooltip = L("The extruder to use when printing support material interface "
+ "(1+, 0 to use the current extruder to minimize tool changes). This affects raft too.");
+ def->cli = "support-material-interface-extruder=i";
+ def->min = 0;
+ def->default_value = new ConfigOptionInt(1);
+
+ def = this->add("support_material_interface_layers", coInt);
+ def->label = L("Interface layers");
+ def->category = L("Support material");
+ def->tooltip = L("Number of interface layers to insert between the object(s) and support material.");
+ def->sidetext = L("layers");
+ def->cli = "support-material-interface-layers=i";
+ def->min = 0;
+ def->default_value = new ConfigOptionInt(3);
+
+ def = this->add("support_material_interface_spacing", coFloat);
+ def->label = L("Interface pattern spacing");
+ def->category = L("Support material");
+ def->tooltip = L("Spacing between interface lines. Set zero to get a solid interface.");
+ def->sidetext = L("mm");
+ def->cli = "support-material-interface-spacing=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("support_material_interface_speed", coFloatOrPercent);
+ def->label = L("Support material interface");
+ def->category = L("Support material");
+ def->tooltip = L("Speed for printing support material interface layers. If expressed as percentage "
+ "(for example 50%) it will be calculated over support material speed.");
+ def->sidetext = L("mm/s or %");
+ def->cli = "support-material-interface-speed=s";
+ def->ratio_over = "support_material_speed";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloatOrPercent(100, true);
+
+ def = this->add("support_material_pattern", coEnum);
+ def->label = L("Pattern");
+ def->category = L("Support material");
+ def->tooltip = L("Pattern used to generate support material.");
+ def->cli = "support-material-pattern=s";
+ def->enum_keys_map = &ConfigOptionEnum<SupportMaterialPattern>::get_enum_values();
+ def->enum_values.push_back("rectilinear");
+ def->enum_values.push_back("rectilinear-grid");
+ def->enum_values.push_back("honeycomb");
+ def->enum_labels.push_back(L("Rectilinear"));
+ def->enum_labels.push_back(L("Rectilinear grid"));
+ def->enum_labels.push_back(L("Honeycomb"));
+ def->default_value = new ConfigOptionEnum<SupportMaterialPattern>(smpRectilinear);
+
+ def = this->add("support_material_spacing", coFloat);
+ def->label = L("Pattern spacing");
+ def->category = L("Support material");
+ def->tooltip = L("Spacing between support material lines.");
+ def->sidetext = L("mm");
+ def->cli = "support-material-spacing=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(2.5);
+
+ def = this->add("support_material_speed", coFloat);
+ def->label = L("Support material");
+ def->category = L("Support material");
+ def->tooltip = L("Speed for printing support material.");
+ def->sidetext = L("mm/s");
+ def->cli = "support-material-speed=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(60);
+
+ def = this->add("support_material_synchronize_layers", coBool);
+ def->label = L("Synchronize with object layers");
+ def->category = L("Support material");
+ def->tooltip = L("Synchronize support layers with the object print layers. This is useful "
+ "with multi-material printers, where the extruder switch is expensive.");
+ def->cli = "support-material-synchronize-layers!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("support_material_threshold", coInt);
+ def->label = L("Overhang threshold");
+ def->category = L("Support material");
+ def->tooltip = L("Support material will not be generated for overhangs whose slope angle "
+ "(90° = vertical) is above the given threshold. In other words, this value "
+ "represent the most horizontal slope (measured from the horizontal plane) "
+ "that you can print without support material. Set to zero for automatic detection "
+ "(recommended).");
+ def->sidetext = L("°");
+ def->cli = "support-material-threshold=i";
+ def->min = 0;
+ def->max = 90;
+ def->default_value = new ConfigOptionInt(0);
+
+ def = this->add("support_material_with_sheath", coBool);
+ def->label = L("With sheath around the support");
+ def->category = L("Support material");
+ def->tooltip = L("Add a sheath (a single perimeter line) around the base support. This makes "
+ "the support more reliable, but also more difficult to remove.");
+ def->cli = "support-material-with-sheath!";
+ def->default_value = new ConfigOptionBool(true);
+
+ def = this->add("temperature", coInts);
+ def->label = L("Other layers");
+ def->tooltip = L("Extruder temperature for layers after the first one. Set this to zero to disable "
+ "temperature control commands in the output.");
+ def->cli = "temperature=i@";
+ def->full_label = L("Temperature");
+ def->min = 0;
+ def->max = max_temp;
+ def->default_value = new ConfigOptionInts { 200 };
+
+ def = this->add("thin_walls", coBool);
+ def->label = L("Detect thin walls");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("Detect single-width walls (parts where two extrusions don't fit and we need "
+ "to collapse them into a single trace).");
+ def->cli = "thin-walls!";
+ def->default_value = new ConfigOptionBool(true);
+
+ def = this->add("threads", coInt);
+ def->label = L("Threads");
+ def->tooltip = L("Threads are used to parallelize long-running tasks. Optimal threads number "
+ "is slightly above the number of available cores/processors.");
+ def->cli = "threads|j=i";
+ def->readonly = true;
+ def->min = 1;
+ {
+ int threads = (unsigned int)boost::thread::hardware_concurrency();
+ def->default_value = new ConfigOptionInt(threads > 0 ? threads : 2);
+ }
+
+ def = this->add("toolchange_gcode", coString);
+ def->label = L("Tool change G-code");
+ def->tooltip = L("This custom code is inserted right before every extruder change. "
+ "Note that you can use placeholder variables for all Slic3r settings as well "
+ "as [previous_extruder] and [next_extruder].");
+ def->cli = "toolchange-gcode=s";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 50;
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("top_infill_extrusion_width", coFloatOrPercent);
+ def->label = L("Top solid infill");
+ def->category = L("Extrusion Width");
+ def->tooltip = L("Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. "
+ "You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. "
+ "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. "
+ "If expressed as percentage (for example 90%) it will be computed over layer height.");
+ def->sidetext = L("mm or % (leave 0 for default)");
+ def->cli = "top-infill-extrusion-width=s";
+ def->default_value = new ConfigOptionFloatOrPercent(0, false);
+
+ def = this->add("top_solid_infill_speed", coFloatOrPercent);
+ def->label = L("Top solid infill");
+ def->category = L("Speed");
+ def->tooltip = L("Speed for printing top solid layers (it only applies to the uppermost "
+ "external layers and not to their internal solid layers). You may want "
+ "to slow down this to get a nicer surface finish. This can be expressed "
+ "as a percentage (for example: 80%) over the solid infill speed above. "
+ "Set to zero for auto.");
+ def->sidetext = L("mm/s or %");
+ def->cli = "top-solid-infill-speed=s";
+ def->ratio_over = "solid_infill_speed";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloatOrPercent(15, false);
+
+ def = this->add("top_solid_layers", coInt);
+ def->label = L("Top");
+ def->category = L("Layers and Perimeters");
+ def->tooltip = L("Number of solid layers to generate on top surfaces.");
+ def->cli = "top-solid-layers=i";
+ def->full_label = L("Top solid layers");
+ def->min = 0;
+ def->default_value = new ConfigOptionInt(3);
+
+ def = this->add("travel_speed", coFloat);
+ def->label = L("Travel");
+ def->tooltip = L("Speed for travel moves (jumps between distant extrusion points).");
+ def->sidetext = L("mm/s");
+ def->cli = "travel-speed=f";
+ def->aliases = { "travel_feed_rate" };
+ def->min = 1;
+ def->default_value = new ConfigOptionFloat(130);
+
+ def = this->add("use_firmware_retraction", coBool);
+ def->label = L("Use firmware retraction");
+ def->tooltip = L("This experimental setting uses G10 and G11 commands to have the firmware "
+ "handle the retraction. This is only supported in recent Marlin.");
+ def->cli = "use-firmware-retraction!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("use_relative_e_distances", coBool);
+ def->label = L("Use relative E distances");
+ def->tooltip = L("If your firmware requires relative E values, check this, "
+ "otherwise leave it unchecked. Most firmwares use absolute values.");
+ def->cli = "use-relative-e-distances!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("use_volumetric_e", coBool);
+ def->label = L("Use volumetric E");
+ def->tooltip = L("This experimental setting uses outputs the E values in cubic millimeters "
+ "instead of linear millimeters. If your firmware doesn't already know "
+ "filament diameter(s), you can put commands like 'M200 D[filament_diameter_0] T0' "
+ "in your start G-code in order to turn volumetric mode on and use the filament "
+ "diameter associated to the filament selected in Slic3r. This is only supported "
+ "in recent Marlin.");
+ def->cli = "use-volumetric-e!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("variable_layer_height", coBool);
+ def->label = L("Enable variable layer height feature");
+ def->tooltip = L("Some printers or printer setups may have difficulties printing "
+ "with a variable layer height. Enabled by default.");
+ def->cli = "variable-layer-height!";
+ def->default_value = new ConfigOptionBool(true);
+
+ def = this->add("wipe", coBools);
+ def->label = L("Wipe while retracting");
+ def->tooltip = L("This flag will move the nozzle while retracting to minimize the possible blob "
+ "on leaky extruders.");
+ def->cli = "wipe!";
+ def->default_value = new ConfigOptionBools { false };
+
+ def = this->add("wipe_tower", coBool);
+ def->label = L("Enable");
+ def->tooltip = L("Multi material printers may need to prime or purge extruders on tool changes. "
+ "Extrude the excess material into the wipe tower.");
+ def->cli = "wipe-tower!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("wiping_volumes_extruders", coFloats);
+ def->label = L("Purging volumes - load/unload volumes");
+ def->tooltip = L("This vector saves required volumes to change from/to each tool used on the "
+ "wipe tower. These values are used to simplify creation of the full purging "
+ "volumes below. ");
+ def->cli = "wiping-volumes-extruders=f@";
+ def->default_value = new ConfigOptionFloats { 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f, 70.f };
+
+ def = this->add("wiping_volumes_matrix", coFloats);
+ def->label = L("Purging volumes - matrix");
+ def->tooltip = L("This matrix describes volumes (in cubic milimetres) required to purge the"
+ " new filament on the wipe tower for any given pair of tools. ");
+ def->cli = "wiping-volumes-matrix=f@";
+ def->default_value = new ConfigOptionFloats { 0.f, 140.f, 140.f, 140.f, 140.f,
+ 140.f, 0.f, 140.f, 140.f, 140.f,
+ 140.f, 140.f, 0.f, 140.f, 140.f,
+ 140.f, 140.f, 140.f, 0.f, 140.f,
+ 140.f, 140.f, 140.f, 140.f, 0.f };
+
+ def = this->add("wipe_tower_x", coFloat);
+ def->label = L("Position X");
+ def->tooltip = L("X coordinate of the left front corner of a wipe tower");
+ def->sidetext = L("mm");
+ def->cli = "wipe-tower-x=f";
+ def->default_value = new ConfigOptionFloat(180.);
+
+ def = this->add("wipe_tower_y", coFloat);
+ def->label = L("Position Y");
+ def->tooltip = L("Y coordinate of the left front corner of a wipe tower");
+ def->sidetext = L("mm");
+ def->cli = "wipe-tower-y=f";
+ def->default_value = new ConfigOptionFloat(140.);
+
+ def = this->add("wipe_tower_width", coFloat);
+ def->label = L("Width");
+ def->tooltip = L("Width of a wipe tower");
+ def->sidetext = L("mm");
+ def->cli = "wipe-tower-width=f";
+ def->default_value = new ConfigOptionFloat(60.);
+
+ def = this->add("wipe_tower_rotation_angle", coFloat);
+ def->label = L("Wipe tower rotation angle");
+ def->tooltip = L("Wipe tower rotation angle with respect to x-axis ");
+ def->sidetext = L("degrees");
+ def->cli = "wipe-tower-rotation-angle=f";
+ def->default_value = new ConfigOptionFloat(0.);
+
+ def = this->add("wipe_into_infill", coBool);
+ def->category = L("Extruders");
+ def->label = L("Wipe into this object's infill");
+ def->tooltip = L("Purging after toolchange will done inside this object's infills. "
+ "This lowers the amount of waste but may result in longer print time "
+ " due to additional travel moves.");
+ def->cli = "wipe-into-infill!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("wipe_into_objects", coBool);
+ def->category = L("Extruders");
+ def->label = L("Wipe into this object");
+ def->tooltip = L("Object will be used to purge the nozzle after a toolchange to save material "
+ "that would otherwise end up in the wipe tower and decrease print time. "
+ "Colours of the objects will be mixed as a result.");
+ def->cli = "wipe-into-objects!";
+ def->default_value = new ConfigOptionBool(false);
+
+ def = this->add("wipe_tower_bridging", coFloat);
+ def->label = L("Maximal bridging distance");
+ def->tooltip = L("Maximal distance between supports on sparse infill sections. ");
+ def->sidetext = L("mm");
+ def->cli = "wipe-tower-bridging=f";
+ def->default_value = new ConfigOptionFloat(10.);
+
+ def = this->add("xy_size_compensation", coFloat);
+ def->label = L("XY Size Compensation");
+ def->category = L("Advanced");
+ def->tooltip = L("The object will be grown/shrunk in the XY plane by the configured value "
+ "(negative = inwards, positive = outwards). This might be useful "
+ "for fine-tuning hole sizes.");
+ def->sidetext = L("mm");
+ def->cli = "xy-size-compensation=f";
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("z_offset", coFloat);
+ def->label = L("Z offset");
+ def->tooltip = L("This value will be added (or subtracted) from all the Z coordinates "
+ "in the output G-code. It is used to compensate for bad Z endstop position: "
+ "for example, if your endstop zero actually leaves the nozzle 0.3mm far "
+ "from the print bed, set this to -0.3 (or fix your endstop).");
+ def->sidetext = L("mm");
+ def->cli = "z-offset=f";
+ def->default_value = new ConfigOptionFloat(0);
+
+ def = this->add("bed_size_x", coFloat);
+ def->label = L("Bed size X");
+ def->category = L("Dwarf");
+ def->sidetext = L("mm");
+ def->cli = "bed-size-x=f";
+ def->default_value = new ConfigOptionFloat(68.);
+
+ def = this->add("bed_size_y", coFloat);
+ def->label = L("Bed size Y");
+ def->category = L("Dwarf");
+ def->sidetext = L("mm");
+ def->cli = "bed-size-y=f";
+ def->default_value = new ConfigOptionFloat(120.);
+
+ def = this->add("pixel_width", coInt);
+ def->label = L("Picture resolution X");
+ def->category = L("Dwarf");
+ def->sidetext = L("px");
+ def->cli = "pixel-width=i";
+ def->min = 1;
+ def->default_value = new ConfigOptionInt(1440);
+
+ def = this->add("pixel_height", coInt);
+ def->label = L("Picture resolution Y");
+ def->category = L("Dwarf");
+ def->sidetext = L("px");
+ def->cli = "pixel-height=i";
+ def->min = 1;
+ def->default_value = new ConfigOptionInt(2560);
+
+ def = this->add("exp_time", coFloat);
+ def->label = L("Exposure time");
+ def->category = L("Dwarf");
+ def->sidetext = L("s");
+ def->cli = "exp-time=f";
+ def->min = 1;
+ def->default_value = new ConfigOptionFloat(8.);
+
+ def = this->add("exp_time_first", coFloat);
+ def->label = L("Exposure time first layers");
+ def->category = L("Dwarf");
+ def->sidetext = L("s");
+ def->cli = "exp-time-first=f";
+ def->min = 1;
+ def->default_value = new ConfigOptionFloat(35.);
+}
+
+void PrintConfigDef::init_sla_params()
+{
+ t_optiondef_map &Options = this->options;
+ ConfigOptionDef* def;
+
+ // SLA Printer settings
+ def = this->add("display_width", coFloat);
+ def->label = L("Display width");
+ def->tooltip = L("Width of the display");
+ def->cli = "display-width=f";
+ def->min = 1;
+ def->default_value = new ConfigOptionFloat(150.);
+
+ def = this->add("display_height", coFloat);
+ def->label = L("Display height");
+ def->tooltip = L("Height of the display");
+ def->cli = "display-height=f";
+ def->min = 1;
+ def->default_value = new ConfigOptionFloat(100.);
+
+ def = this->add("display_pixels_x", coInt);
+ def->full_label = L("Number of pixels in");
+ def->label = ("X");
+ def->tooltip = L("Number of pixels in X");
+ def->cli = "display-pixels-x=i";
+ def->min = 100;
+ def->default_value = new ConfigOptionInt(2000);
+
+ def = this->add("display_pixels_y", coInt);
+ def->label = ("Y");
+ def->tooltip = L("Number of pixels in Y");
+ def->cli = "display-pixels-y=i";
+ def->min = 100;
+ def->default_value = new ConfigOptionInt(1000);
+
+ def = this->add("printer_correction", coFloats);
+ def->full_label = L("Printer scaling correction");
+ def->tooltip = L("Printer scaling correction");
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats( { 1., 1., 1. } );
+
+ // SLA Material settings.
+ def = this->add("initial_layer_height", coFloat);
+ def->label = L("Initial layer height");
+ def->tooltip = L("Initial layer height");
+ def->sidetext = L("mm");
+ def->cli = "initial-layer-height=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(0.3);
+
+ def = this->add("exposure_time", coFloat);
+ def->label = L("Exposure time");
+ def->tooltip = L("Exposure time");
+ def->sidetext = L("s");
+ def->cli = "exposure-time=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(10);
+
+ def = this->add("initial_exposure_time", coFloat);
+ def->label = L("Initial exposure time");
+ def->tooltip = L("Initial exposure time");
+ def->sidetext = L("s");
+ def->cli = "initial-exposure-time=f";
+ def->min = 0;
+ def->default_value = new ConfigOptionFloat(15);
+
+ def = this->add("material_correction_printing", coFloats);
+ def->full_label = L("Correction for expansion when printing");
+ def->tooltip = L("Correction for expansion when printing");
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } );
+
+ def = this->add("material_correction_curing", coFloats);
+ def->full_label = L("Correction for expansion after curing");
+ def->tooltip = L("Correction for expansion after curing");
+ def->min = 0;
+ def->default_value = new ConfigOptionFloats( { 1. , 1., 1. } );
+
+ def = this->add("material_notes", coString);
+ def->label = L("SLA print material notes");
+ def->tooltip = L("You can put your notes regarding the SLA print material here.");
+ def->cli = "material-notes=s";
+ def->multiline = true;
+ def->full_width = true;
+ def->height = 130;
+ def->default_value = new ConfigOptionString("");
+
+ def = this->add("default_sla_material_profile", coString);
+ def->label = L("Default SLA material profile");
+ def->tooltip = L("Default print profile associated with the current printer profile. "
+ "On selection of the current printer profile, this print profile will be activated.");
+ def->default_value = new ConfigOptionString();
+
+ def = this->add("sla_material_settings_id", coString);
+ def->default_value = new ConfigOptionString("");
+}
+
+void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
+{
+ // handle legacy options
+ if (opt_key == "extrusion_width_ratio" || opt_key == "bottom_layer_speed_ratio"
+ || opt_key == "first_layer_height_ratio") {
+ boost::replace_first(opt_key, "_ratio", "");
+ if (opt_key == "bottom_layer_speed") opt_key = "first_layer_speed";
+ try {
+ float v = boost::lexical_cast<float>(value);
+ if (v != 0)
+ value = boost::lexical_cast<std::string>(v*100) + "%";
+ } catch (boost::bad_lexical_cast &) {
+ value = "0";
+ }
+ } else if (opt_key == "gcode_flavor" && value == "makerbot") {
+ value = "makerware";
+ } else if (opt_key == "fill_density" && value.find("%") == std::string::npos) {
+ try {
+ // fill_density was turned into a percent value
+ float v = boost::lexical_cast<float>(value);
+ value = boost::lexical_cast<std::string>(v*100) + "%";
+ } catch (boost::bad_lexical_cast &) {}
+ } else if (opt_key == "randomize_start" && value == "1") {
+ opt_key = "seam_position";
+ value = "random";
+ } else if (opt_key == "bed_size" && !value.empty()) {
+ opt_key = "bed_shape";
+ ConfigOptionPoint p;
+ p.deserialize(value);
+ std::ostringstream oss;
+ oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1);
+ value = oss.str();
+ } else if ((opt_key == "perimeter_acceleration" && value == "25")
+ || (opt_key == "infill_acceleration" && value == "50")) {
+ /* For historical reasons, the world's full of configs having these very low values;
+ to avoid unexpected behavior we need to ignore them. Banning these two hard-coded
+ values is a dirty hack and will need to be removed sometime in the future, but it
+ will avoid lots of complaints for now. */
+ value = "0";
+ } else if (opt_key == "support_material_pattern" && value == "pillars") {
+ // Slic3r PE does not support the pillars. They never worked well.
+ value = "rectilinear";
+ } else if (opt_key == "octoprint_host") {
+ opt_key = "print_host";
+ } else if (opt_key == "octoprint_cafile") {
+ opt_key = "printhost_cafile";
+ } else if (opt_key == "octoprint_apikey") {
+ opt_key = "printhost_apikey";
+ }
+
+ // Ignore the following obsolete configuration keys:
+ static std::set<std::string> ignore = {
+ "duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y",
+ "support_material_tool", "acceleration", "adjust_overhang_flow",
+ "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid",
+ "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start",
+ "seal_position", "vibration_limit", "bed_size",
+ "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe"
+ };
+
+ if (ignore.find(opt_key) != ignore.end()) {
+ opt_key = "";
+ return;
+ }
+
+ if (! print_config_def.has(opt_key)) {
+ opt_key = "";
+ return;
+ }
+}
+
+PrintConfigDef print_config_def;
+
+DynamicPrintConfig* DynamicPrintConfig::new_from_defaults()
+{
+ return new_from_defaults_keys(FullPrintConfig::defaults().keys());
+}
+
+DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector<std::string> &keys)
+{
+ auto *out = new DynamicPrintConfig();
+ out->apply_only(FullPrintConfig::defaults(), keys);
+ return out;
+}
+
+void DynamicPrintConfig::normalize()
+{
+ if (this->has("extruder")) {
+ int extruder = this->option("extruder")->getInt();
+ this->erase("extruder");
+ if (extruder != 0) {
+ if (!this->has("infill_extruder"))
+ this->option("infill_extruder", true)->setInt(extruder);
+ if (!this->has("perimeter_extruder"))
+ this->option("perimeter_extruder", true)->setInt(extruder);
+ // Don't propagate the current extruder to support.
+ // For non-soluble supports, the default "0" extruder means to use the active extruder,
+ // for soluble supports one certainly does not want to set the extruder to non-soluble.
+ // if (!this->has("support_material_extruder"))
+ // this->option("support_material_extruder", true)->setInt(extruder);
+ // if (!this->has("support_material_interface_extruder"))
+ // this->option("support_material_interface_extruder", true)->setInt(extruder);
+ }
+ }
+
+ if (!this->has("solid_infill_extruder") && this->has("infill_extruder"))
+ this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt());
+
+ if (this->has("spiral_vase") && this->opt<ConfigOptionBool>("spiral_vase", true)->value) {
+ {
+ // this should be actually done only on the spiral layers instead of all
+ ConfigOptionBools* opt = this->opt<ConfigOptionBools>("retract_layer_change", true);
+ opt->values.assign(opt->values.size(), false); // set all values to false
+ }
+ {
+ this->opt<ConfigOptionInt>("perimeters", true)->value = 1;
+ this->opt<ConfigOptionInt>("top_solid_layers", true)->value = 0;
+ this->opt<ConfigOptionPercent>("fill_density", true)->value = 0;
+ }
+ }
+}
+
+std::string DynamicPrintConfig::validate()
+{
+ // Full print config is initialized from the defaults.
+ FullPrintConfig fpc;
+ fpc.apply(*this, true);
+ // Verify this print options through the FullPrintConfig.
+ return fpc.validate();
+}
+
+double PrintConfig::min_object_distance() const
+{
+ return PrintConfig::min_object_distance(static_cast<const ConfigBase*>(this));
+}
+
+double PrintConfig::min_object_distance(const ConfigBase *config)
+{
+ double extruder_clearance_radius = config->option("extruder_clearance_radius")->getFloat();
+ double duplicate_distance = config->option("duplicate_distance")->getFloat();
+
+ // min object distance is max(duplicate_distance, clearance_radius)
+ return (config->option("complete_objects")->getBool() && extruder_clearance_radius > duplicate_distance)
+ ? extruder_clearance_radius
+ : duplicate_distance;
+}
+
+std::string FullPrintConfig::validate()
+{
+ // --layer-height
+ if (this->get_abs_value("layer_height") <= 0)
+ return "Invalid value for --layer-height";
+ if (fabs(fmod(this->get_abs_value("layer_height"), SCALING_FACTOR)) > 1e-4)
+ return "--layer-height must be a multiple of print resolution";
+
+ // --first-layer-height
+ if (this->get_abs_value("first_layer_height") <= 0)
+ return "Invalid value for --first-layer-height";
+
+ // --filament-diameter
+ for (double fd : this->filament_diameter.values)
+ if (fd < 1)
+ return "Invalid value for --filament-diameter";
+
+ // --nozzle-diameter
+ for (double nd : this->nozzle_diameter.values)
+ if (nd < 0.005)
+ return "Invalid value for --nozzle-diameter";
+
+ // --perimeters
+ if (this->perimeters.value < 0)
+ return "Invalid value for --perimeters";
+
+ // --solid-layers
+ if (this->top_solid_layers < 0)
+ return "Invalid value for --top-solid-layers";
+ if (this->bottom_solid_layers < 0)
+ return "Invalid value for --bottom-solid-layers";
+
+ if (this->use_firmware_retraction.value &&
+ this->gcode_flavor.value != gcfSmoothie &&
+ this->gcode_flavor.value != gcfRepRap &&
+ this->gcode_flavor.value != gcfMarlin &&
+ this->gcode_flavor.value != gcfMachinekit &&
+ this->gcode_flavor.value != gcfRepetier)
+ return "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware";
+
+ if (this->use_firmware_retraction.value)
+ for (bool wipe : this->wipe.values)
+ if (wipe)
+ return "--use-firmware-retraction is not compatible with --wipe";
+
+ // --gcode-flavor
+ if (! print_config_def.get("gcode_flavor")->has_enum_value(this->gcode_flavor.serialize()))
+ return "Invalid value for --gcode-flavor";
+
+ // --fill-pattern
+ if (! print_config_def.get("fill_pattern")->has_enum_value(this->fill_pattern.serialize()))
+ return "Invalid value for --fill-pattern";
+
+ // --external-fill-pattern
+ if (! print_config_def.get("external_fill_pattern")->has_enum_value(this->external_fill_pattern.serialize()))
+ return "Invalid value for --external-fill-pattern";
+
+ // --fill-density
+ if (fabs(this->fill_density.value - 100.) < EPSILON &&
+ ! print_config_def.get("external_fill_pattern")->has_enum_value(this->fill_pattern.serialize()))
+ return "The selected fill pattern is not supposed to work at 100% density";
+
+ // --infill-every-layers
+ if (this->infill_every_layers < 1)
+ return "Invalid value for --infill-every-layers";
+
+ // --skirt-height
+ if (this->skirt_height < -1) // -1 means as tall as the object
+ return "Invalid value for --skirt-height";
+
+ // --bridge-flow-ratio
+ if (this->bridge_flow_ratio <= 0)
+ return "Invalid value for --bridge-flow-ratio";
+
+ // extruder clearance
+ if (this->extruder_clearance_radius <= 0)
+ return "Invalid value for --extruder-clearance-radius";
+ if (this->extruder_clearance_height <= 0)
+ return "Invalid value for --extruder-clearance-height";
+
+ // --extrusion-multiplier
+ for (float em : this->extrusion_multiplier.values)
+ if (em <= 0)
+ return "Invalid value for --extrusion-multiplier";
+
+ // --default-acceleration
+ if ((this->perimeter_acceleration != 0. || this->infill_acceleration != 0. || this->bridge_acceleration != 0. || this->first_layer_acceleration != 0.) &&
+ this->default_acceleration == 0.)
+ return "Invalid zero value for --default-acceleration when using other acceleration settings";
+
+ // --spiral-vase
+ if (this->spiral_vase) {
+ // Note that we might want to have more than one perimeter on the bottom
+ // solid layers.
+ if (this->perimeters > 1)
+ return "Can't make more than one perimeter when spiral vase mode is enabled";
+ else if (this->perimeters < 1)
+ return "Can't make less than one perimeter when spiral vase mode is enabled";
+ if (this->fill_density > 0)
+ return "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0";
+ if (this->top_solid_layers > 0)
+ return "Spiral vase mode is not compatible with top solid layers";
+ if (this->support_material || this->support_material_enforce_layers > 0)
+ return "Spiral vase mode is not compatible with support material";
+ }
+
+ // extrusion widths
+ {
+ double max_nozzle_diameter = 0.;
+ for (double dmr : this->nozzle_diameter.values)
+ max_nozzle_diameter = std::max(max_nozzle_diameter, dmr);
+ const char *widths[] = { "external_perimeter", "perimeter", "infill", "solid_infill", "top_infill", "support_material", "first_layer" };
+ for (size_t i = 0; i < sizeof(widths) / sizeof(widths[i]); ++ i) {
+ std::string key(widths[i]);
+ key += "_extrusion_width";
+ if (this->get_abs_value(key, max_nozzle_diameter) > 10. * max_nozzle_diameter)
+ return std::string("Invalid extrusion width (too large): ") + key;
+ }
+ }
+
+ // Out of range validation of numeric values.
+ for (const std::string &opt_key : this->keys()) {
+ const ConfigOption *opt = this->optptr(opt_key);
+ assert(opt != nullptr);
+ const ConfigOptionDef *optdef = print_config_def.get(opt_key);
+ assert(optdef != nullptr);
+ bool out_of_range = false;
+ switch (opt->type()) {
+ case coFloat:
+ case coPercent:
+ case coFloatOrPercent:
+ {
+ auto *fopt = static_cast<const ConfigOptionFloat*>(opt);
+ out_of_range = fopt->value < optdef->min || fopt->value > optdef->max;
+ break;
+ }
+ case coFloats:
+ case coPercents:
+ for (double v : static_cast<const ConfigOptionFloats*>(opt)->values)
+ if (v < optdef->min || v > optdef->max) {
+ out_of_range = true;
+ break;
+ }
+ break;
+ case coInt:
+ {
+ auto *iopt = static_cast<const ConfigOptionInt*>(opt);
+ out_of_range = iopt->value < optdef->min || iopt->value > optdef->max;
+ break;
+ }
+ case coInts:
+ for (int v : static_cast<const ConfigOptionInts*>(opt)->values)
+ if (v < optdef->min || v > optdef->max) {
+ out_of_range = true;
+ break;
+ }
+ break;
+ default:;
+ }
+ if (out_of_range)
+ return std::string("Value out of range: " + opt_key);
+ }
+
+ // The configuration is valid.
+ return "";
+}
+
+// Declare the static caches for each StaticPrintConfig derived class.
+StaticPrintConfig::StaticCache<class Slic3r::PrintObjectConfig> PrintObjectConfig::s_cache_PrintObjectConfig;
+StaticPrintConfig::StaticCache<class Slic3r::PrintRegionConfig> PrintRegionConfig::s_cache_PrintRegionConfig;
+StaticPrintConfig::StaticCache<class Slic3r::MachineEnvelopeConfig> MachineEnvelopeConfig::s_cache_MachineEnvelopeConfig;
+StaticPrintConfig::StaticCache<class Slic3r::GCodeConfig> GCodeConfig::s_cache_GCodeConfig;
+StaticPrintConfig::StaticCache<class Slic3r::PrintConfig> PrintConfig::s_cache_PrintConfig;
+StaticPrintConfig::StaticCache<class Slic3r::HostConfig> HostConfig::s_cache_HostConfig;
+StaticPrintConfig::StaticCache<class Slic3r::FullPrintConfig> FullPrintConfig::s_cache_FullPrintConfig;
+
+StaticPrintConfig::StaticCache<class Slic3r::SLAMaterialConfig> SLAMaterialConfig::s_cache_SLAMaterialConfig;
+StaticPrintConfig::StaticCache<class Slic3r::SLAPrinterConfig> SLAPrinterConfig::s_cache_SLAPrinterConfig;
+StaticPrintConfig::StaticCache<class Slic3r::SLAFullPrintConfig> SLAFullPrintConfig::s_cache_SLAFullPrintConfig;
+
+}
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
new file mode 100644
index 000000000..bc3b0ef49
--- /dev/null
+++ b/src/libslic3r/PrintConfig.hpp
@@ -0,0 +1,973 @@
+// Configuration store of Slic3r.
+//
+// The configuration store is either static or dynamic.
+// DynamicPrintConfig is used mainly at the user interface. while the StaticPrintConfig is used
+// during the slicing and the g-code generation.
+//
+// The classes derived from StaticPrintConfig form a following hierarchy.
+//
+// FullPrintConfig
+// PrintObjectConfig
+// PrintRegionConfig
+// PrintConfig
+// GCodeConfig
+// HostConfig
+//
+
+#ifndef slic3r_PrintConfig_hpp_
+#define slic3r_PrintConfig_hpp_
+
+#include "libslic3r.h"
+#include "Config.hpp"
+
+namespace Slic3r {
+
+enum PrinterTechnology
+{
+ // Fused Filament Fabrication
+ ptFFF,
+ // Stereolitography
+ ptSLA,
+};
+
+enum GCodeFlavor {
+ gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit,
+ gcfSmoothie, gcfNoExtrusion,
+};
+
+enum PrintHostType {
+ htOctoPrint, htDuet,
+};
+
+enum InfillPattern {
+ ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
+ ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral,
+};
+
+enum SupportMaterialPattern {
+ smpRectilinear, smpRectilinearGrid, smpHoneycomb,
+};
+
+enum SeamPosition {
+ spRandom, spNearest, spAligned, spRear
+};
+
+enum FilamentType {
+ ftPLA, ftABS, ftPET, ftHIPS, ftFLEX, ftSCAFF, ftEDGE, ftNGEN, ftPVA
+};
+
+template<> inline const t_config_enum_values& ConfigOptionEnum<PrinterTechnology>::get_enum_values() {
+ static t_config_enum_values keys_map;
+ if (keys_map.empty()) {
+ keys_map["FFF"] = ptFFF;
+ keys_map["SLA"] = ptSLA;
+ }
+ return keys_map;
+}
+
+template<> inline const t_config_enum_values& ConfigOptionEnum<GCodeFlavor>::get_enum_values() {
+ static t_config_enum_values keys_map;
+ if (keys_map.empty()) {
+ keys_map["reprap"] = gcfRepRap;
+ keys_map["repetier"] = gcfRepetier;
+ keys_map["teacup"] = gcfTeacup;
+ keys_map["makerware"] = gcfMakerWare;
+ keys_map["marlin"] = gcfMarlin;
+ keys_map["sailfish"] = gcfSailfish;
+ keys_map["smoothie"] = gcfSmoothie;
+ keys_map["mach3"] = gcfMach3;
+ keys_map["machinekit"] = gcfMachinekit;
+ keys_map["no-extrusion"] = gcfNoExtrusion;
+ }
+ return keys_map;
+}
+
+template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::get_enum_values() {
+ static t_config_enum_values keys_map;
+ if (keys_map.empty()) {
+ keys_map["octoprint"] = htOctoPrint;
+ keys_map["duet"] = htDuet;
+ }
+ return keys_map;
+}
+
+template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::get_enum_values() {
+ static t_config_enum_values keys_map;
+ if (keys_map.empty()) {
+ keys_map["rectilinear"] = ipRectilinear;
+ keys_map["grid"] = ipGrid;
+ keys_map["triangles"] = ipTriangles;
+ keys_map["stars"] = ipStars;
+ keys_map["cubic"] = ipCubic;
+ keys_map["line"] = ipLine;
+ keys_map["concentric"] = ipConcentric;
+ keys_map["honeycomb"] = ipHoneycomb;
+ keys_map["3dhoneycomb"] = ip3DHoneycomb;
+ keys_map["gyroid"] = ipGyroid;
+ keys_map["hilbertcurve"] = ipHilbertCurve;
+ keys_map["archimedeanchords"] = ipArchimedeanChords;
+ keys_map["octagramspiral"] = ipOctagramSpiral;
+ }
+ return keys_map;
+}
+
+template<> inline const t_config_enum_values& ConfigOptionEnum<SupportMaterialPattern>::get_enum_values() {
+ static t_config_enum_values keys_map;
+ if (keys_map.empty()) {
+ keys_map["rectilinear"] = smpRectilinear;
+ keys_map["rectilinear-grid"] = smpRectilinearGrid;
+ keys_map["honeycomb"] = smpHoneycomb;
+ }
+ return keys_map;
+}
+
+template<> inline const t_config_enum_values& ConfigOptionEnum<SeamPosition>::get_enum_values() {
+ static t_config_enum_values keys_map;
+ if (keys_map.empty()) {
+ keys_map["random"] = spRandom;
+ keys_map["nearest"] = spNearest;
+ keys_map["aligned"] = spAligned;
+ keys_map["rear"] = spRear;
+ }
+ return keys_map;
+}
+
+template<> inline const t_config_enum_values& ConfigOptionEnum<FilamentType>::get_enum_values() {
+ static t_config_enum_values keys_map;
+ if (keys_map.empty()) {
+ keys_map["PLA"] = ftPLA;
+ keys_map["ABS"] = ftABS;
+ keys_map["PET"] = ftPET;
+ keys_map["HIPS"] = ftHIPS;
+ keys_map["FLEX"] = ftFLEX;
+ keys_map["SCAFF"] = ftSCAFF;
+ keys_map["EDGE"] = ftEDGE;
+ keys_map["NGEN"] = ftNGEN;
+ keys_map["PVA"] = ftPVA;
+ }
+ return keys_map;
+}
+
+// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
+// Does not store the actual values, but defines default values.
+class PrintConfigDef : public ConfigDef
+{
+public:
+ PrintConfigDef();
+
+ static void handle_legacy(t_config_option_key &opt_key, std::string &value);
+
+private:
+ void init_common_params();
+ void init_fff_params();
+ void init_sla_params();
+};
+
+// The one and only global definition of SLic3r configuration options.
+// This definition is constant.
+extern PrintConfigDef print_config_def;
+
+// Slic3r dynamic configuration, used to override the configuration
+// per object, per modification volume or per printing material.
+// The dynamic configuration is also used to store user modifications of the print global parameters,
+// so the modified configuration values may be diffed against the active configuration
+// to invalidate the proper slicing resp. g-code generation processing steps.
+// This object is mapped to Perl as Slic3r::Config.
+class DynamicPrintConfig : public DynamicConfig
+{
+public:
+ DynamicPrintConfig() {}
+ DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {}
+
+ static DynamicPrintConfig* new_from_defaults();
+ static DynamicPrintConfig* new_from_defaults_keys(const std::vector<std::string> &keys);
+
+ // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
+ const ConfigDef* def() const override { return &print_config_def; }
+
+ void normalize();
+
+ // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned.
+ std::string validate();
+
+ // Verify whether the opt_key has not been obsoleted or renamed.
+ // 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().
+ // handle_legacy() is called internally by set_deserialize().
+ void handle_legacy(t_config_option_key &opt_key, std::string &value) const override
+ { PrintConfigDef::handle_legacy(opt_key, value); }
+};
+
+template<typename CONFIG>
+void normalize_and_apply_config(CONFIG &dst, const DynamicPrintConfig &src)
+{
+ DynamicPrintConfig src_normalized(src);
+ src_normalized.normalize();
+ dst.apply(src_normalized, true);
+}
+
+class StaticPrintConfig : public StaticConfig
+{
+public:
+ StaticPrintConfig() {}
+
+ // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
+ const ConfigDef* def() const override { return &print_config_def; }
+
+protected:
+ // Verify whether the opt_key has not been obsoleted or renamed.
+ // 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().
+ // handle_legacy() is called internally by set_deserialize().
+ void handle_legacy(t_config_option_key &opt_key, std::string &value) const override
+ { PrintConfigDef::handle_legacy(opt_key, value); }
+
+ // Internal class for keeping a dynamic map to static options.
+ class StaticCacheBase
+ {
+ public:
+ // To be called during the StaticCache setup.
+ // Add one ConfigOption into m_map_name_to_offset.
+ template<typename T>
+ void opt_add(const std::string &name, const char *base_ptr, const T &opt)
+ {
+ assert(m_map_name_to_offset.find(name) == m_map_name_to_offset.end());
+ m_map_name_to_offset[name] = (const char*)&opt - base_ptr;
+ }
+
+ protected:
+ std::map<std::string, ptrdiff_t> m_map_name_to_offset;
+ };
+
+ // Parametrized by the type of the topmost class owning the options.
+ template<typename T>
+ class StaticCache : public StaticCacheBase
+ {
+ public:
+ // Calling the constructor of m_defaults with 0 forces m_defaults to not run the initialization.
+ StaticCache() : m_defaults(nullptr) {}
+ ~StaticCache() { delete m_defaults; m_defaults = nullptr; }
+
+ bool initialized() const { return ! m_keys.empty(); }
+
+ ConfigOption* optptr(const std::string &name, T *owner) const
+ {
+ const auto it = m_map_name_to_offset.find(name);
+ return (it == m_map_name_to_offset.end()) ? nullptr : reinterpret_cast<ConfigOption*>((char*)owner + it->second);
+ }
+
+ const ConfigOption* optptr(const std::string &name, const T *owner) const
+ {
+ const auto it = m_map_name_to_offset.find(name);
+ return (it == m_map_name_to_offset.end()) ? nullptr : reinterpret_cast<const ConfigOption*>((const char*)owner + it->second);
+ }
+
+ const std::vector<std::string>& keys() const { return m_keys; }
+ const T& defaults() const { return *m_defaults; }
+
+ // To be called during the StaticCache setup.
+ // Collect option keys from m_map_name_to_offset,
+ // assign default values to m_defaults.
+ void finalize(T *defaults, const ConfigDef *defs)
+ {
+ assert(defs != nullptr);
+ m_defaults = defaults;
+ m_keys.clear();
+ m_keys.reserve(m_map_name_to_offset.size());
+ for (const auto &kvp : defs->options) {
+ // Find the option given the option name kvp.first by an offset from (char*)m_defaults.
+ ConfigOption *opt = this->optptr(kvp.first, m_defaults);
+ if (opt == nullptr)
+ // This option is not defined by the ConfigBase of type T.
+ continue;
+ m_keys.emplace_back(kvp.first);
+ const ConfigOptionDef *def = defs->get(kvp.first);
+ assert(def != nullptr);
+ if (def->default_value != nullptr)
+ opt->set(def->default_value);
+ }
+ }
+
+ private:
+ T *m_defaults;
+ std::vector<std::string> m_keys;
+ };
+};
+
+#define STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \
+public: \
+ /* Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. */ \
+ ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override \
+ { return s_cache_##CLASS_NAME.optptr(opt_key, this); } \
+ /* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \
+ t_config_option_keys keys() const override { return s_cache_##CLASS_NAME.keys(); } \
+ static const CLASS_NAME& defaults() { initialize_cache(); return s_cache_##CLASS_NAME.defaults(); } \
+private: \
+ static void initialize_cache() \
+ { \
+ if (! s_cache_##CLASS_NAME.initialized()) { \
+ CLASS_NAME *inst = new CLASS_NAME(1); \
+ inst->initialize(s_cache_##CLASS_NAME, (const char*)inst); \
+ s_cache_##CLASS_NAME.finalize(inst, inst->def()); \
+ } \
+ } \
+ /* Cache object holding a key/option map, a list of option keys and a copy of this static config initialized with the defaults. */ \
+ static StaticPrintConfig::StaticCache<CLASS_NAME> s_cache_##CLASS_NAME;
+
+#define STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \
+ STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \
+public: \
+ /* Public default constructor will initialize the key/option cache and the default object copy if needed. */ \
+ CLASS_NAME() { initialize_cache(); *this = s_cache_##CLASS_NAME.defaults(); } \
+protected: \
+ /* Protected constructor to be called when compounded. */ \
+ CLASS_NAME(int) {}
+
+#define STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \
+ STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \
+public: \
+ /* Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. */ \
+ const ConfigDef* def() const override { return &print_config_def; } \
+ /* Handle legacy and obsoleted config keys */ \
+ void handle_legacy(t_config_option_key &opt_key, std::string &value) const override \
+ { PrintConfigDef::handle_legacy(opt_key, value); }
+
+#define OPT_PTR(KEY) cache.opt_add(#KEY, base_ptr, this->KEY)
+
+// This object is mapped to Perl as Slic3r::Config::PrintObject.
+class PrintObjectConfig : public StaticPrintConfig
+{
+ STATIC_PRINT_CONFIG_CACHE(PrintObjectConfig)
+public:
+ ConfigOptionBool clip_multipart_objects;
+ ConfigOptionBool dont_support_bridges;
+ ConfigOptionFloat elefant_foot_compensation;
+ ConfigOptionFloatOrPercent extrusion_width;
+ ConfigOptionFloatOrPercent first_layer_height;
+ ConfigOptionBool infill_only_where_needed;
+ // Force the generation of solid shells between adjacent materials/volumes.
+ ConfigOptionBool interface_shells;
+ ConfigOptionFloat layer_height;
+ ConfigOptionInt raft_layers;
+ ConfigOptionEnum<SeamPosition> seam_position;
+// ConfigOptionFloat seam_preferred_direction;
+// ConfigOptionFloat seam_preferred_direction_jitter;
+ ConfigOptionBool support_material;
+ // Automatic supports (generated based on support_material_threshold).
+ ConfigOptionBool support_material_auto;
+ // Direction of the support pattern (in XY plane).
+ ConfigOptionFloat support_material_angle;
+ ConfigOptionBool support_material_buildplate_only;
+ ConfigOptionFloat support_material_contact_distance;
+ ConfigOptionInt support_material_enforce_layers;
+ ConfigOptionInt support_material_extruder;
+ ConfigOptionFloatOrPercent support_material_extrusion_width;
+ ConfigOptionBool support_material_interface_contact_loops;
+ ConfigOptionInt support_material_interface_extruder;
+ ConfigOptionInt support_material_interface_layers;
+ // Spacing between interface lines (the hatching distance). Set zero to get a solid interface.
+ ConfigOptionFloat support_material_interface_spacing;
+ ConfigOptionFloatOrPercent support_material_interface_speed;
+ ConfigOptionEnum<SupportMaterialPattern> support_material_pattern;
+ // Spacing between support material lines (the hatching distance).
+ ConfigOptionFloat support_material_spacing;
+ ConfigOptionFloat support_material_speed;
+ ConfigOptionBool support_material_synchronize_layers;
+ // Overhang angle threshold.
+ ConfigOptionInt support_material_threshold;
+ ConfigOptionBool support_material_with_sheath;
+ ConfigOptionFloatOrPercent support_material_xy_spacing;
+ ConfigOptionFloat xy_size_compensation;
+ ConfigOptionBool wipe_into_objects;
+
+protected:
+ void initialize(StaticCacheBase &cache, const char *base_ptr)
+ {
+ OPT_PTR(clip_multipart_objects);
+ OPT_PTR(dont_support_bridges);
+ OPT_PTR(elefant_foot_compensation);
+ OPT_PTR(extrusion_width);
+ OPT_PTR(first_layer_height);
+ OPT_PTR(infill_only_where_needed);
+ OPT_PTR(interface_shells);
+ OPT_PTR(layer_height);
+ OPT_PTR(raft_layers);
+ OPT_PTR(seam_position);
+// OPT_PTR(seam_preferred_direction);
+// OPT_PTR(seam_preferred_direction_jitter);
+ OPT_PTR(support_material);
+ OPT_PTR(support_material_auto);
+ OPT_PTR(support_material_angle);
+ OPT_PTR(support_material_buildplate_only);
+ OPT_PTR(support_material_contact_distance);
+ OPT_PTR(support_material_enforce_layers);
+ OPT_PTR(support_material_interface_contact_loops);
+ OPT_PTR(support_material_extruder);
+ OPT_PTR(support_material_extrusion_width);
+ OPT_PTR(support_material_interface_extruder);
+ OPT_PTR(support_material_interface_layers);
+ OPT_PTR(support_material_interface_spacing);
+ OPT_PTR(support_material_interface_speed);
+ OPT_PTR(support_material_pattern);
+ OPT_PTR(support_material_spacing);
+ OPT_PTR(support_material_speed);
+ OPT_PTR(support_material_synchronize_layers);
+ OPT_PTR(support_material_xy_spacing);
+ OPT_PTR(support_material_threshold);
+ OPT_PTR(support_material_with_sheath);
+ OPT_PTR(xy_size_compensation);
+ OPT_PTR(wipe_into_objects);
+ }
+};
+
+// This object is mapped to Perl as Slic3r::Config::PrintRegion.
+class PrintRegionConfig : public StaticPrintConfig
+{
+ STATIC_PRINT_CONFIG_CACHE(PrintRegionConfig)
+public:
+ ConfigOptionFloat bridge_angle;
+ ConfigOptionInt bottom_solid_layers;
+ ConfigOptionFloat bridge_flow_ratio;
+ ConfigOptionFloat bridge_speed;
+ ConfigOptionBool ensure_vertical_shell_thickness;
+ ConfigOptionEnum<InfillPattern> external_fill_pattern;
+ ConfigOptionFloatOrPercent external_perimeter_extrusion_width;
+ ConfigOptionFloatOrPercent external_perimeter_speed;
+ ConfigOptionBool external_perimeters_first;
+ ConfigOptionBool extra_perimeters;
+ ConfigOptionFloat fill_angle;
+ ConfigOptionPercent fill_density;
+ ConfigOptionEnum<InfillPattern> fill_pattern;
+ ConfigOptionFloat gap_fill_speed;
+ ConfigOptionInt infill_extruder;
+ ConfigOptionFloatOrPercent infill_extrusion_width;
+ ConfigOptionInt infill_every_layers;
+ ConfigOptionFloatOrPercent infill_overlap;
+ ConfigOptionFloat infill_speed;
+ // Detect bridging perimeters
+ ConfigOptionBool overhangs;
+ ConfigOptionInt perimeter_extruder;
+ ConfigOptionFloatOrPercent perimeter_extrusion_width;
+ ConfigOptionFloat perimeter_speed;
+ // Total number of perimeters.
+ ConfigOptionInt perimeters;
+ ConfigOptionFloatOrPercent small_perimeter_speed;
+ ConfigOptionFloat solid_infill_below_area;
+ ConfigOptionInt solid_infill_extruder;
+ ConfigOptionFloatOrPercent solid_infill_extrusion_width;
+ ConfigOptionInt solid_infill_every_layers;
+ ConfigOptionFloatOrPercent solid_infill_speed;
+ // Detect thin walls.
+ ConfigOptionBool thin_walls;
+ ConfigOptionFloatOrPercent top_infill_extrusion_width;
+ ConfigOptionInt top_solid_layers;
+ ConfigOptionFloatOrPercent top_solid_infill_speed;
+ ConfigOptionBool wipe_into_infill;
+
+protected:
+ void initialize(StaticCacheBase &cache, const char *base_ptr)
+ {
+ OPT_PTR(bridge_angle);
+ OPT_PTR(bottom_solid_layers);
+ OPT_PTR(bridge_flow_ratio);
+ OPT_PTR(bridge_speed);
+ OPT_PTR(ensure_vertical_shell_thickness);
+ OPT_PTR(external_fill_pattern);
+ OPT_PTR(external_perimeter_extrusion_width);
+ OPT_PTR(external_perimeter_speed);
+ OPT_PTR(external_perimeters_first);
+ OPT_PTR(extra_perimeters);
+ OPT_PTR(fill_angle);
+ OPT_PTR(fill_density);
+ OPT_PTR(fill_pattern);
+ OPT_PTR(gap_fill_speed);
+ OPT_PTR(infill_extruder);
+ OPT_PTR(infill_extrusion_width);
+ OPT_PTR(infill_every_layers);
+ OPT_PTR(infill_overlap);
+ OPT_PTR(infill_speed);
+ OPT_PTR(overhangs);
+ OPT_PTR(perimeter_extruder);
+ OPT_PTR(perimeter_extrusion_width);
+ OPT_PTR(perimeter_speed);
+ OPT_PTR(perimeters);
+ OPT_PTR(small_perimeter_speed);
+ OPT_PTR(solid_infill_below_area);
+ OPT_PTR(solid_infill_extruder);
+ OPT_PTR(solid_infill_extrusion_width);
+ OPT_PTR(solid_infill_every_layers);
+ OPT_PTR(solid_infill_speed);
+ OPT_PTR(thin_walls);
+ OPT_PTR(top_infill_extrusion_width);
+ OPT_PTR(top_solid_infill_speed);
+ OPT_PTR(top_solid_layers);
+ OPT_PTR(wipe_into_infill);
+ }
+};
+
+class MachineEnvelopeConfig : public StaticPrintConfig
+{
+ STATIC_PRINT_CONFIG_CACHE(MachineEnvelopeConfig)
+public:
+ // M201 X... Y... Z... E... [mm/sec^2]
+ ConfigOptionFloats machine_max_acceleration_x;
+ ConfigOptionFloats machine_max_acceleration_y;
+ ConfigOptionFloats machine_max_acceleration_z;
+ ConfigOptionFloats machine_max_acceleration_e;
+ // M203 X... Y... Z... E... [mm/sec]
+ ConfigOptionFloats machine_max_feedrate_x;
+ ConfigOptionFloats machine_max_feedrate_y;
+ ConfigOptionFloats machine_max_feedrate_z;
+ ConfigOptionFloats machine_max_feedrate_e;
+ // M204 S... [mm/sec^2]
+ ConfigOptionFloats machine_max_acceleration_extruding;
+ // M204 T... [mm/sec^2]
+ ConfigOptionFloats machine_max_acceleration_retracting;
+ // M205 X... Y... Z... E... [mm/sec]
+ ConfigOptionFloats machine_max_jerk_x;
+ ConfigOptionFloats machine_max_jerk_y;
+ ConfigOptionFloats machine_max_jerk_z;
+ ConfigOptionFloats machine_max_jerk_e;
+ // M205 T... [mm/sec]
+ ConfigOptionFloats machine_min_travel_rate;
+ // M205 S... [mm/sec]
+ ConfigOptionFloats machine_min_extruding_rate;
+
+protected:
+ void initialize(StaticCacheBase &cache, const char *base_ptr)
+ {
+ OPT_PTR(machine_max_acceleration_x);
+ OPT_PTR(machine_max_acceleration_y);
+ OPT_PTR(machine_max_acceleration_z);
+ OPT_PTR(machine_max_acceleration_e);
+ OPT_PTR(machine_max_feedrate_x);
+ OPT_PTR(machine_max_feedrate_y);
+ OPT_PTR(machine_max_feedrate_z);
+ OPT_PTR(machine_max_feedrate_e);
+ OPT_PTR(machine_max_acceleration_extruding);
+ OPT_PTR(machine_max_acceleration_retracting);
+ OPT_PTR(machine_max_jerk_x);
+ OPT_PTR(machine_max_jerk_y);
+ OPT_PTR(machine_max_jerk_z);
+ OPT_PTR(machine_max_jerk_e);
+ OPT_PTR(machine_min_travel_rate);
+ OPT_PTR(machine_min_extruding_rate);
+ }
+};
+
+// This object is mapped to Perl as Slic3r::Config::GCode.
+class GCodeConfig : public StaticPrintConfig
+{
+ STATIC_PRINT_CONFIG_CACHE(GCodeConfig)
+public:
+ ConfigOptionString before_layer_gcode;
+ ConfigOptionString between_objects_gcode;
+ ConfigOptionFloats deretract_speed;
+ ConfigOptionString end_gcode;
+ ConfigOptionStrings end_filament_gcode;
+ ConfigOptionString extrusion_axis;
+ ConfigOptionFloats extrusion_multiplier;
+ ConfigOptionFloats filament_diameter;
+ ConfigOptionFloats filament_density;
+ ConfigOptionStrings filament_type;
+ ConfigOptionBools filament_soluble;
+ ConfigOptionFloats filament_cost;
+ ConfigOptionFloats filament_max_volumetric_speed;
+ ConfigOptionFloats filament_loading_speed;
+ ConfigOptionFloats filament_loading_speed_start;
+ ConfigOptionFloats filament_load_time;
+ ConfigOptionFloats filament_unloading_speed;
+ ConfigOptionFloats filament_unloading_speed_start;
+ ConfigOptionFloats filament_toolchange_delay;
+ ConfigOptionFloats filament_unload_time;
+ ConfigOptionInts filament_cooling_moves;
+ ConfigOptionFloats filament_cooling_initial_speed;
+ ConfigOptionFloats filament_minimal_purge_on_wipe_tower;
+ ConfigOptionFloats filament_cooling_final_speed;
+ ConfigOptionStrings filament_ramming_parameters;
+ ConfigOptionBool gcode_comments;
+ ConfigOptionEnum<GCodeFlavor> gcode_flavor;
+ ConfigOptionString layer_gcode;
+ ConfigOptionFloat max_print_speed;
+ ConfigOptionFloat max_volumetric_speed;
+ ConfigOptionFloat max_volumetric_extrusion_rate_slope_positive;
+ ConfigOptionFloat max_volumetric_extrusion_rate_slope_negative;
+ ConfigOptionPercents retract_before_wipe;
+ ConfigOptionFloats retract_length;
+ ConfigOptionFloats retract_length_toolchange;
+ ConfigOptionFloats retract_lift;
+ ConfigOptionFloats retract_lift_above;
+ ConfigOptionFloats retract_lift_below;
+ ConfigOptionFloats retract_restart_extra;
+ ConfigOptionFloats retract_restart_extra_toolchange;
+ ConfigOptionFloats retract_speed;
+ ConfigOptionString start_gcode;
+ ConfigOptionStrings start_filament_gcode;
+ ConfigOptionBool single_extruder_multi_material;
+ ConfigOptionBool single_extruder_multi_material_priming;
+ ConfigOptionString toolchange_gcode;
+ ConfigOptionFloat travel_speed;
+ ConfigOptionBool use_firmware_retraction;
+ ConfigOptionBool use_relative_e_distances;
+ ConfigOptionBool use_volumetric_e;
+ ConfigOptionBool variable_layer_height;
+ ConfigOptionFloat cooling_tube_retraction;
+ ConfigOptionFloat cooling_tube_length;
+ ConfigOptionFloat parking_pos_retraction;
+ ConfigOptionBool remaining_times;
+ ConfigOptionBool silent_mode;
+ ConfigOptionFloat extra_loading_move;
+
+ std::string get_extrusion_axis() const
+ {
+ return
+ ((this->gcode_flavor.value == gcfMach3) || (this->gcode_flavor.value == gcfMachinekit)) ? "A" :
+ (this->gcode_flavor.value == gcfNoExtrusion) ? "" : this->extrusion_axis.value;
+ }
+
+protected:
+ void initialize(StaticCacheBase &cache, const char *base_ptr)
+ {
+ OPT_PTR(before_layer_gcode);
+ OPT_PTR(between_objects_gcode);
+ OPT_PTR(deretract_speed);
+ OPT_PTR(end_gcode);
+ OPT_PTR(end_filament_gcode);
+ OPT_PTR(extrusion_axis);
+ OPT_PTR(extrusion_multiplier);
+ OPT_PTR(filament_diameter);
+ OPT_PTR(filament_density);
+ OPT_PTR(filament_type);
+ OPT_PTR(filament_soluble);
+ OPT_PTR(filament_cost);
+ OPT_PTR(filament_max_volumetric_speed);
+ OPT_PTR(filament_loading_speed);
+ OPT_PTR(filament_loading_speed_start);
+ OPT_PTR(filament_load_time);
+ OPT_PTR(filament_unloading_speed);
+ OPT_PTR(filament_unloading_speed_start);
+ OPT_PTR(filament_unload_time);
+ OPT_PTR(filament_toolchange_delay);
+ OPT_PTR(filament_cooling_moves);
+ OPT_PTR(filament_cooling_initial_speed);
+ OPT_PTR(filament_minimal_purge_on_wipe_tower);
+ OPT_PTR(filament_cooling_final_speed);
+ OPT_PTR(filament_ramming_parameters);
+ OPT_PTR(gcode_comments);
+ OPT_PTR(gcode_flavor);
+ OPT_PTR(layer_gcode);
+ OPT_PTR(max_print_speed);
+ OPT_PTR(max_volumetric_speed);
+ OPT_PTR(max_volumetric_extrusion_rate_slope_positive);
+ OPT_PTR(max_volumetric_extrusion_rate_slope_negative);
+ OPT_PTR(retract_before_wipe);
+ OPT_PTR(retract_length);
+ OPT_PTR(retract_length_toolchange);
+ OPT_PTR(retract_lift);
+ OPT_PTR(retract_lift_above);
+ OPT_PTR(retract_lift_below);
+ OPT_PTR(retract_restart_extra);
+ OPT_PTR(retract_restart_extra_toolchange);
+ OPT_PTR(retract_speed);
+ OPT_PTR(single_extruder_multi_material);
+ OPT_PTR(single_extruder_multi_material_priming);
+ OPT_PTR(start_gcode);
+ OPT_PTR(start_filament_gcode);
+ OPT_PTR(toolchange_gcode);
+ OPT_PTR(travel_speed);
+ OPT_PTR(use_firmware_retraction);
+ OPT_PTR(use_relative_e_distances);
+ OPT_PTR(use_volumetric_e);
+ OPT_PTR(variable_layer_height);
+ OPT_PTR(cooling_tube_retraction);
+ OPT_PTR(cooling_tube_length);
+ OPT_PTR(parking_pos_retraction);
+ OPT_PTR(remaining_times);
+ OPT_PTR(silent_mode);
+ OPT_PTR(extra_loading_move);
+ }
+};
+
+// This object is mapped to Perl as Slic3r::Config::Print.
+class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig
+{
+ STATIC_PRINT_CONFIG_CACHE_DERIVED(PrintConfig)
+ PrintConfig() : GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); }
+public:
+ double min_object_distance() const;
+ static double min_object_distance(const ConfigBase *config);
+
+ ConfigOptionBool avoid_crossing_perimeters;
+ ConfigOptionPoints bed_shape;
+ ConfigOptionInts bed_temperature;
+ ConfigOptionFloat bridge_acceleration;
+ ConfigOptionInts bridge_fan_speed;
+ ConfigOptionFloat brim_width;
+ ConfigOptionBool complete_objects;
+ ConfigOptionBools cooling;
+ ConfigOptionFloat default_acceleration;
+ ConfigOptionInts disable_fan_first_layers;
+ ConfigOptionFloat duplicate_distance;
+ ConfigOptionFloat extruder_clearance_height;
+ ConfigOptionFloat extruder_clearance_radius;
+ ConfigOptionStrings extruder_colour;
+ ConfigOptionPoints extruder_offset;
+ ConfigOptionBools fan_always_on;
+ ConfigOptionInts fan_below_layer_time;
+ ConfigOptionStrings filament_colour;
+ ConfigOptionStrings filament_notes;
+ ConfigOptionFloat first_layer_acceleration;
+ ConfigOptionInts first_layer_bed_temperature;
+ ConfigOptionFloatOrPercent first_layer_extrusion_width;
+ ConfigOptionFloatOrPercent first_layer_speed;
+ ConfigOptionInts first_layer_temperature;
+ ConfigOptionFloat infill_acceleration;
+ ConfigOptionBool infill_first;
+ ConfigOptionInts max_fan_speed;
+ ConfigOptionFloats max_layer_height;
+ ConfigOptionInts min_fan_speed;
+ ConfigOptionFloats min_layer_height;
+ ConfigOptionFloat max_print_height;
+ ConfigOptionFloats min_print_speed;
+ ConfigOptionFloat min_skirt_length;
+ ConfigOptionString notes;
+ ConfigOptionFloats nozzle_diameter;
+ ConfigOptionBool only_retract_when_crossing_perimeters;
+ ConfigOptionBool ooze_prevention;
+ ConfigOptionString output_filename_format;
+ ConfigOptionFloat perimeter_acceleration;
+ ConfigOptionStrings post_process;
+ ConfigOptionString printer_model;
+ ConfigOptionString printer_notes;
+ ConfigOptionFloat resolution;
+ ConfigOptionFloats retract_before_travel;
+ ConfigOptionBools retract_layer_change;
+ ConfigOptionFloat skirt_distance;
+ ConfigOptionInt skirt_height;
+ ConfigOptionInt skirts;
+ ConfigOptionInts slowdown_below_layer_time;
+ ConfigOptionBool spiral_vase;
+ ConfigOptionInt standby_temperature_delta;
+ ConfigOptionInts temperature;
+ ConfigOptionInt threads;
+ ConfigOptionBools wipe;
+ ConfigOptionBool wipe_tower;
+ ConfigOptionFloat wipe_tower_x;
+ ConfigOptionFloat wipe_tower_y;
+ ConfigOptionFloat wipe_tower_width;
+ ConfigOptionFloat wipe_tower_per_color_wipe;
+ ConfigOptionFloat wipe_tower_rotation_angle;
+ ConfigOptionFloat wipe_tower_bridging;
+ ConfigOptionFloats wiping_volumes_matrix;
+ ConfigOptionFloats wiping_volumes_extruders;
+ ConfigOptionFloat z_offset;
+ ConfigOptionFloat bed_size_x;
+ ConfigOptionFloat bed_size_y;
+ ConfigOptionInt pixel_width;
+ ConfigOptionInt pixel_height;
+ ConfigOptionFloat exp_time;
+ ConfigOptionFloat exp_time_first;
+
+protected:
+ PrintConfig(int) : GCodeConfig(1) {}
+ void initialize(StaticCacheBase &cache, const char *base_ptr)
+ {
+ this->MachineEnvelopeConfig::initialize(cache, base_ptr);
+ this->GCodeConfig::initialize(cache, base_ptr);
+ OPT_PTR(avoid_crossing_perimeters);
+ OPT_PTR(bed_shape);
+ OPT_PTR(bed_temperature);
+ OPT_PTR(bridge_acceleration);
+ OPT_PTR(bridge_fan_speed);
+ OPT_PTR(brim_width);
+ OPT_PTR(complete_objects);
+ OPT_PTR(cooling);
+ OPT_PTR(default_acceleration);
+ OPT_PTR(disable_fan_first_layers);
+ OPT_PTR(duplicate_distance);
+ OPT_PTR(extruder_clearance_height);
+ OPT_PTR(extruder_clearance_radius);
+ OPT_PTR(extruder_colour);
+ OPT_PTR(extruder_offset);
+ OPT_PTR(fan_always_on);
+ OPT_PTR(fan_below_layer_time);
+ OPT_PTR(filament_colour);
+ OPT_PTR(filament_notes);
+ OPT_PTR(first_layer_acceleration);
+ OPT_PTR(first_layer_bed_temperature);
+ OPT_PTR(first_layer_extrusion_width);
+ OPT_PTR(first_layer_speed);
+ OPT_PTR(first_layer_temperature);
+ OPT_PTR(infill_acceleration);
+ OPT_PTR(infill_first);
+ OPT_PTR(max_fan_speed);
+ OPT_PTR(max_layer_height);
+ OPT_PTR(min_fan_speed);
+ OPT_PTR(min_layer_height);
+ OPT_PTR(max_print_height);
+ OPT_PTR(min_print_speed);
+ OPT_PTR(min_skirt_length);
+ OPT_PTR(notes);
+ OPT_PTR(nozzle_diameter);
+ OPT_PTR(only_retract_when_crossing_perimeters);
+ OPT_PTR(ooze_prevention);
+ OPT_PTR(output_filename_format);
+ OPT_PTR(perimeter_acceleration);
+ OPT_PTR(post_process);
+ OPT_PTR(printer_model);
+ OPT_PTR(printer_notes);
+ OPT_PTR(resolution);
+ OPT_PTR(retract_before_travel);
+ OPT_PTR(retract_layer_change);
+ OPT_PTR(skirt_distance);
+ OPT_PTR(skirt_height);
+ OPT_PTR(skirts);
+ OPT_PTR(slowdown_below_layer_time);
+ OPT_PTR(spiral_vase);
+ OPT_PTR(standby_temperature_delta);
+ OPT_PTR(temperature);
+ OPT_PTR(threads);
+ OPT_PTR(wipe);
+ OPT_PTR(wipe_tower);
+ OPT_PTR(wipe_tower_x);
+ OPT_PTR(wipe_tower_y);
+ OPT_PTR(wipe_tower_width);
+ OPT_PTR(wipe_tower_per_color_wipe);
+ OPT_PTR(wipe_tower_rotation_angle);
+ OPT_PTR(wipe_tower_bridging);
+ OPT_PTR(wiping_volumes_matrix);
+ OPT_PTR(wiping_volumes_extruders);
+ OPT_PTR(z_offset);
+ OPT_PTR(bed_size_x);
+ OPT_PTR(bed_size_y);
+ OPT_PTR(pixel_width);
+ OPT_PTR(pixel_height);
+ OPT_PTR(exp_time);
+ OPT_PTR(exp_time_first);
+ }
+};
+
+class HostConfig : public StaticPrintConfig
+{
+ STATIC_PRINT_CONFIG_CACHE(HostConfig)
+public:
+ ConfigOptionEnum<PrintHostType> host_type;
+ ConfigOptionString print_host;
+ ConfigOptionString printhost_apikey;
+ ConfigOptionString printhost_cafile;
+ ConfigOptionString serial_port;
+ ConfigOptionInt serial_speed;
+
+protected:
+ void initialize(StaticCacheBase &cache, const char *base_ptr)
+ {
+ OPT_PTR(host_type);
+ OPT_PTR(print_host);
+ OPT_PTR(printhost_apikey);
+ OPT_PTR(printhost_cafile);
+ OPT_PTR(serial_port);
+ OPT_PTR(serial_speed);
+ }
+};
+
+// This object is mapped to Perl as Slic3r::Config::Full.
+class FullPrintConfig :
+ public PrintObjectConfig,
+ public PrintRegionConfig,
+ public PrintConfig,
+ public HostConfig
+{
+ STATIC_PRINT_CONFIG_CACHE_DERIVED(FullPrintConfig)
+ FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); }
+
+public:
+ // Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned.
+ std::string validate();
+
+protected:
+ // Protected constructor to be called to initialize ConfigCache::m_default.
+ FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) {}
+ void initialize(StaticCacheBase &cache, const char *base_ptr)
+ {
+ this->PrintObjectConfig::initialize(cache, base_ptr);
+ this->PrintRegionConfig::initialize(cache, base_ptr);
+ this->PrintConfig ::initialize(cache, base_ptr);
+ this->HostConfig ::initialize(cache, base_ptr);
+ }
+};
+
+class SLAMaterialConfig : public StaticPrintConfig
+{
+ STATIC_PRINT_CONFIG_CACHE(SLAMaterialConfig)
+public:
+ ConfigOptionFloat layer_height;
+ ConfigOptionFloat initial_layer_height;
+ ConfigOptionFloat exposure_time;
+ ConfigOptionFloat initial_exposure_time;
+ ConfigOptionFloats material_correction_printing;
+ ConfigOptionFloats material_correction_curing;
+protected:
+ void initialize(StaticCacheBase &cache, const char *base_ptr)
+ {
+ OPT_PTR(layer_height);
+ OPT_PTR(initial_layer_height);
+ OPT_PTR(exposure_time);
+ OPT_PTR(initial_exposure_time);
+ OPT_PTR(material_correction_printing);
+ OPT_PTR(material_correction_curing);
+ }
+};
+
+class SLAPrinterConfig : public StaticPrintConfig
+{
+ STATIC_PRINT_CONFIG_CACHE(SLAPrinterConfig)
+public:
+ ConfigOptionEnum<PrinterTechnology> printer_technology;
+ ConfigOptionPoints bed_shape;
+ ConfigOptionFloat max_print_height;
+ ConfigOptionFloat display_width;
+ ConfigOptionFloat display_height;
+ ConfigOptionInt display_pixels_x;
+ ConfigOptionInt display_pixels_y;
+ ConfigOptionFloats printer_correction;
+protected:
+ void initialize(StaticCacheBase &cache, const char *base_ptr)
+ {
+ OPT_PTR(printer_technology);
+ OPT_PTR(bed_shape);
+ OPT_PTR(max_print_height);
+ OPT_PTR(display_width);
+ OPT_PTR(display_height);
+ OPT_PTR(display_pixels_x);
+ OPT_PTR(display_pixels_y);
+ OPT_PTR(printer_correction);
+ }
+};
+
+class SLAFullPrintConfig : public SLAPrinterConfig, public SLAMaterialConfig
+{
+ STATIC_PRINT_CONFIG_CACHE_DERIVED(SLAFullPrintConfig)
+ SLAFullPrintConfig() : SLAPrinterConfig(0), SLAMaterialConfig(0) { initialize_cache(); *this = s_cache_SLAFullPrintConfig.defaults(); }
+
+public:
+ // Validate the SLAFullPrintConfig. Returns an empty string on success, otherwise an error message is returned.
+// std::string validate();
+
+protected:
+ // Protected constructor to be called to initialize ConfigCache::m_default.
+ SLAFullPrintConfig(int) : SLAPrinterConfig(0), SLAMaterialConfig(0) {}
+ void initialize(StaticCacheBase &cache, const char *base_ptr)
+ {
+ this->SLAPrinterConfig ::initialize(cache, base_ptr);
+ this->SLAMaterialConfig::initialize(cache, base_ptr);
+ }
+};
+
+#undef STATIC_PRINT_CONFIG_CACHE
+#undef STATIC_PRINT_CONFIG_CACHE_BASE
+#undef STATIC_PRINT_CONFIG_CACHE_DERIVED
+#undef OPT_PTR
+
+}
+
+#endif
diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp
new file mode 100644
index 000000000..7c3871251
--- /dev/null
+++ b/src/libslic3r/PrintExport.hpp
@@ -0,0 +1,383 @@
+#ifndef PRINTEXPORT_HPP
+#define PRINTEXPORT_HPP
+
+#include "Print.hpp"
+
+// For png export of the sliced model
+#include <fstream>
+#include <sstream>
+
+#include <wx/stdstream.h>
+#include <wx/wfstream.h>
+#include <wx/zipstrm.h>
+
+#include <boost/log/trivial.hpp>
+
+#include "Rasterizer/Rasterizer.hpp"
+#include <tbb/parallel_for.h>
+#include <tbb/spin_mutex.h>//#include "tbb/mutex.h"
+
+namespace Slic3r {
+
+enum class FilePrinterFormat {
+ PNG,
+ SVG
+};
+
+/*
+ * Interface for a file printer of the slices. Implementation can be an SVG
+ * or PNG printer or any other format.
+ *
+ * The format argument specifies the output format of the printer and it enables
+ * different implementations of this class template for each supported format.
+ *
+ */
+template<FilePrinterFormat format>
+class FilePrinter {
+public:
+
+ void printConfig(const Print&);
+
+ // Draw an ExPolygon which is a polygon inside a slice on the specified layer.
+ void drawPolygon(const ExPolygon& p, unsigned lyr);
+
+ // Tell the printer how many layers should it consider.
+ void layers(unsigned layernum);
+
+ // Get the number of layers in the print.
+ unsigned layers() const;
+
+ /* Switch to a particular layer. If there where less layers then the
+ * specified layer number than an appropriate number of layers will be
+ * allocated in the printer.
+ */
+ void beginLayer(unsigned layer);
+
+ // Allocate a new layer on top of the last and switch to it.
+ void beginLayer();
+
+ /*
+ * Finish the selected layer. It means that no drawing is allowed on that
+ * layer anymore. This fact can be used to prepare the file system output
+ * data like png comprimation and so on.
+ */
+ void finishLayer(unsigned layer);
+
+ // Finish the top layer.
+ void finishLayer();
+
+ // Save all the layers into the file (or dir) specified in the path argument
+ void save(const std::string& path);
+
+ // Save only the selected layer to the file specified in path argument.
+ void saveLayer(unsigned lyr, const std::string& path);
+};
+
+// Implementation for PNG raster output
+// Be aware that if a large number of layers are allocated, it can very well
+// exhaust the available memory especially on 32 bit platform.
+template<> class FilePrinter<FilePrinterFormat::PNG> {
+
+ struct Layer {
+ Raster first;
+ std::stringstream second;
+
+ Layer() {}
+
+ Layer(const Layer&) = delete;
+ Layer(Layer&& m):
+ first(std::move(m.first))/*, second(std::move(m.second))*/ {}
+ };
+
+ // We will save the compressed PNG data into stringstreams which can be done
+ // in parallel. Later we can write every layer to the disk sequentially.
+ std::vector<Layer> layers_rst_;
+ Raster::Resolution res_;
+ Raster::PixelDim pxdim_;
+ const Print *print_ = nullptr;
+ double exp_time_s_ = .0, exp_time_first_s_ = .0;
+
+ std::string createIniContent(const std::string& projectname) {
+ double layer_height = print_?
+ print_->default_object_config().layer_height.getFloat() :
+ 0.05;
+
+ using std::string;
+ using std::to_string;
+
+ auto expt_str = to_string(exp_time_s_);
+ auto expt_first_str = to_string(exp_time_first_s_);
+ auto stepnum_str = to_string(static_cast<unsigned>(800*layer_height));
+ auto layerh_str = to_string(layer_height);
+
+ return string(
+ "action = print\n"
+ "jobDir = ") + projectname + "\n" +
+ "expTime = " + expt_str + "\n"
+ "expTimeFirst = " + expt_first_str + "\n"
+ "stepNum = " + stepnum_str + "\n"
+ "wifiOn = 1\n"
+ "tiltSlow = 60\n"
+ "tiltFast = 15\n"
+ "numFade = 10\n"
+ "startdelay = 0\n"
+ "layerHeight = " + layerh_str + "\n"
+ "noteInfo = "
+ "expTime="+expt_str+"+resinType=generic+layerHeight="
+ +layerh_str+"+printer=DWARF3\n";
+ }
+
+ // Change this to TOP_LEFT if you want correct PNG orientation
+ static const Raster::Origin ORIGIN = Raster::Origin::BOTTOM_LEFT;
+
+public:
+ inline FilePrinter(double width_mm, double height_mm,
+ unsigned width_px, unsigned height_px,
+ double exp_time, double exp_time_first):
+ res_(width_px, height_px),
+ pxdim_(width_mm/width_px, height_mm/height_px),
+ exp_time_s_(exp_time),
+ exp_time_first_s_(exp_time_first)
+ {
+ }
+
+ FilePrinter(const FilePrinter& ) = delete;
+ FilePrinter(FilePrinter&& m):
+ layers_rst_(std::move(m.layers_rst_)),
+ res_(m.res_),
+ pxdim_(m.pxdim_) {}
+
+ inline void layers(unsigned cnt) { if(cnt > 0) layers_rst_.resize(cnt); }
+ inline unsigned layers() const { return layers_rst_.size(); }
+
+ void printConfig(const Print& printconf) { print_ = &printconf; }
+
+ inline void drawPolygon(const ExPolygon& p, unsigned lyr) {
+ assert(lyr < layers_rst_.size());
+ layers_rst_[lyr].first.draw(p);
+ }
+
+ inline void beginLayer(unsigned lyr) {
+ if(layers_rst_.size() <= lyr) layers_rst_.resize(lyr+1);
+ layers_rst_[lyr].first.reset(res_, pxdim_, ORIGIN);
+ }
+
+ inline void beginLayer() {
+ layers_rst_.emplace_back();
+ layers_rst_.front().first.reset(res_, pxdim_, ORIGIN);
+ }
+
+ inline void finishLayer(unsigned lyr_id) {
+ assert(lyr_id < layers_rst_.size());
+ layers_rst_[lyr_id].first.save(layers_rst_[lyr_id].second,
+ Raster::Compression::PNG);
+ layers_rst_[lyr_id].first.reset();
+ }
+
+ inline void finishLayer() {
+ if(!layers_rst_.empty()) {
+ layers_rst_.back().first.save(layers_rst_.back().second,
+ Raster::Compression::PNG);
+ layers_rst_.back().first.reset();
+ }
+ }
+
+ inline void save(const std::string& path) {
+
+ wxFileName filepath(path);
+
+ wxFFileOutputStream zipfile(path);
+
+ std::string project = filepath.GetName().ToStdString();
+
+ if(!zipfile.IsOk()) {
+ BOOST_LOG_TRIVIAL(error) << "Can't create zip file for layers! "
+ << path;
+ return;
+ }
+
+ wxZipOutputStream zipstream(zipfile);
+ wxStdOutputStream pngstream(zipstream);
+
+ zipstream.PutNextEntry("config.ini");
+ pngstream << createIniContent(project);
+
+ for(unsigned i = 0; i < layers_rst_.size(); i++) {
+ if(layers_rst_[i].second.rdbuf()->in_avail() > 0) {
+ char lyrnum[6];
+ std::sprintf(lyrnum, "%.5d", i);
+ auto zfilename = project + lyrnum + ".png";
+ zipstream.PutNextEntry(zfilename);
+ pngstream << layers_rst_[i].second.rdbuf();
+ layers_rst_[i].second.str("");
+ }
+ }
+
+ zipstream.Close();
+ zipfile.Close();
+ }
+
+ void saveLayer(unsigned lyr, const std::string& path) {
+ unsigned i = lyr;
+ assert(i < layers_rst_.size());
+
+ char lyrnum[6];
+ std::sprintf(lyrnum, "%.5d", lyr);
+ std::string loc = path + "layer" + lyrnum + ".png";
+
+ std::fstream out(loc, std::fstream::out | std::fstream::binary);
+ if(out.good()) {
+ layers_rst_[i].first.save(out, Raster::Compression::PNG);
+ } else {
+ BOOST_LOG_TRIVIAL(error) << "Can't create file for layer";
+ }
+
+ out.close();
+ layers_rst_[i].first.reset();
+ }
+};
+
+// Let's shadow this eigen interface
+inline coord_t px(const Point& p) { return p(0); }
+inline coord_t py(const Point& p) { return p(1); }
+inline coordf_t px(const Vec2d& p) { return p(0); }
+inline coordf_t py(const Vec2d& p) { return p(1); }
+
+template<FilePrinterFormat format, class...Args>
+void print_to(Print& print,
+ std::string dirpath,
+ double width_mm,
+ double height_mm,
+ Args&&...args)
+{
+
+ std::string& dir = dirpath;
+
+ // This map will hold the layers sorted by z coordinate. Layers on the
+ // same height (from different objects) will be mapped to the same key and
+ // rasterized to the same image.
+ std::map<long long, LayerPtrs> layers;
+
+ auto& objects = print.objects();
+
+ // Merge the sliced layers with the support layers
+ std::for_each(objects.cbegin(), objects.cend(), [&layers](const PrintObject *o) {
+ for(const auto l : o->layers()) {
+ auto& lyrs = layers[static_cast<long long>(scale_(l->print_z))];
+ lyrs.push_back(l);
+ }
+
+ for(const auto l : o->support_layers()) {
+ auto& lyrs = layers[static_cast<long long>(scale_(l->print_z))];
+ lyrs.push_back(l);
+ }
+ });
+
+ auto print_bb = print.bounding_box();
+ Vec2d punsc = unscale(print_bb.size());
+
+ // If the print does not fit into the print area we should cry about it.
+ if(px(punsc) > width_mm || py(punsc) > height_mm) {
+ BOOST_LOG_TRIVIAL(warning) << "Warning: Print will not fit!" << "\n"
+ << "Width needed: " << px(punsc) << "\n"
+ << "Height needed: " << py(punsc) << "\n";
+ }
+
+ // Offset for centering the print onto the print area
+ auto cx = scale_(width_mm)/2 - (px(print_bb.center()) - px(print_bb.min));
+ auto cy = scale_(height_mm)/2 - (py(print_bb.center()) - py(print_bb.min));
+
+ // Create the actual printer, forward any additional arguments to it.
+ FilePrinter<format> printer(width_mm, height_mm,
+ std::forward<Args>(args)...);
+
+ printer.printConfig(print);
+
+ printer.layers(layers.size()); // Allocate space for all the layers
+
+ int st_prev = 0;
+ const std::string jobdesc = "Rasterizing and compressing sliced layers";
+ tbb::spin_mutex m;
+
+ std::vector<long long> keys;
+ keys.reserve(layers.size());
+ for(auto& e : layers) keys.push_back(e.first);
+
+ //FIXME
+ int initstatus = //print.progressindicator? print.progressindicator->state() :
+ 0;
+ print.set_status(initstatus, jobdesc);
+
+ // Method that prints one layer
+ auto process_layer = [&layers, &keys, &printer, &st_prev, &m,
+ &jobdesc, print_bb, dir, cx, cy, &print, initstatus]
+ (unsigned layer_id)
+ {
+ LayerPtrs lrange = layers[keys[layer_id]];
+
+ printer.beginLayer(layer_id); // Switch to the appropriate layer
+
+ for(Layer *lp : lrange) {
+ Layer& l = *lp;
+
+ ExPolygonCollection slices = l.slices; // Copy the layer slices
+
+ // Sort the polygons in the layer
+ std::stable_sort(slices.expolygons.begin(), slices.expolygons.end(),
+ [](const ExPolygon& a, const ExPolygon& b) {
+ return a.contour.contains(b.contour.first_point()) ? false :
+ true;
+ });
+
+ // Draw all the polygons in the slice to the actual layer.
+ for (const Point &d : l.object()->copies())
+ for (ExPolygon slice : slices.expolygons) {
+ slice.translate(px(d), py(d));
+ slice.translate(-px(print_bb.min) + cx,
+ -py(print_bb.min) + cy);
+
+ printer.drawPolygon(slice, layer_id);
+ }
+
+ /*if(print.has_support_material() && layer_id > 0) {
+ BOOST_LOG_TRIVIAL(warning) << "support material for layer "
+ << layer_id
+ << " defined but export is "
+ "not yet implemented.";
+
+ }*/
+
+ }
+
+ printer.finishLayer(layer_id); // Finish the layer for later saving it.
+
+ auto st = static_cast<int>(layer_id*80.0/layers.size());
+ m.lock();
+ if( st - st_prev > 10) {
+ print.set_status(initstatus + st, jobdesc);
+ st_prev = st;
+ }
+ m.unlock();
+
+ // printer.saveLayer(layer_id, dir); We could save the layer immediately
+ };
+
+ // Print all the layers in parallel
+ tbb::parallel_for<size_t, decltype(process_layer)>(0,
+ layers.size(),
+ process_layer);
+
+ // Sequential version (for testing)
+ // for(unsigned l = 0; l < layers.size(); ++l) process_layer(l);
+
+// print.set_status(100, jobdesc);
+
+ // Save the print into the file system.
+ print.set_status(initstatus + 90, "Writing layers to disk");
+ printer.save(dir);
+ print.set_status(initstatus + 100, "Writing layers completed");
+}
+
+}
+
+#endif // PRINTEXPORT_HPP
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
new file mode 100644
index 000000000..ef2364dc4
--- /dev/null
+++ b/src/libslic3r/PrintObject.cpp
@@ -0,0 +1,2263 @@
+#include "Print.hpp"
+#include "BoundingBox.hpp"
+#include "ClipperUtils.hpp"
+#include "Geometry.hpp"
+#include "SupportMaterial.hpp"
+#include "Surface.hpp"
+#include "Slicing.hpp"
+
+#include <utility>
+#include <boost/log/trivial.hpp>
+#include <float.h>
+
+#include <tbb/task_scheduler_init.h>
+#include <tbb/parallel_for.h>
+#include <tbb/atomic.h>
+
+#include <Shiny/Shiny.h>
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+#define SLIC3R_DEBUG
+#endif
+
+// #define SLIC3R_DEBUG
+
+// Make assert active if SLIC3R_DEBUG
+#ifdef SLIC3R_DEBUG
+ #undef NDEBUG
+ #define DEBUG
+ #define _DEBUG
+ #include "SVG.hpp"
+ #undef assert
+ #include <cassert>
+#endif
+
+namespace Slic3r {
+
+PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) :
+ typed_slices(false),
+ m_print(print),
+ m_model_object(model_object),
+ size(Vec3crd::Zero()),
+ layer_height_profile_valid(false)
+{
+ // Compute the translation to be applied to our meshes so that we work with smaller coordinates
+ {
+ // Translate meshes so that our toolpath generation algorithms work with smaller
+ // XY coordinates; this translation is an optimization and not strictly required.
+ // A cloned mesh will be aligned to 0 before slicing in _slice_region() since we
+ // don't assume it's already aligned and we don't alter the original position in model.
+ // We store the XY translation so that we can place copies correctly in the output G-code
+ // (copies are expressed in G-code coordinates and this translation is not publicly exposed).
+ m_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1));
+ // Scale the object size and store it
+ this->size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast<coord_t>();
+ }
+
+ this->reload_model_instances();
+ this->layer_height_ranges = model_object->layer_height_ranges;
+ this->layer_height_profile = model_object->layer_height_profile;
+}
+
+void PrintObject::set_started(PrintObjectStep step)
+{
+ m_state.set_started(step, m_print->m_mutex);
+}
+
+void PrintObject::set_done(PrintObjectStep step)
+{
+ m_state.set_done(step, m_print->m_mutex);
+}
+
+bool PrintObject::add_copy(const Vec2d &point)
+{
+ tbb::mutex::scoped_lock lock(m_print->m_mutex);
+ Points points = m_copies;
+ points.push_back(Point::new_scale(point(0), point(1)));
+ return this->set_copies(points);
+}
+
+bool PrintObject::delete_last_copy()
+{
+ tbb::mutex::scoped_lock lock(m_print->m_mutex);
+ Points points = m_copies;
+ points.pop_back();
+ return this->set_copies(points);
+}
+
+bool PrintObject::set_copies(const Points &points)
+{
+ bool copies_num_changed = m_copies.size() != points.size();
+
+ // order copies with a nearest neighbor search and translate them by _copies_shift
+ m_copies.clear();
+ m_copies.reserve(points.size());
+
+ // order copies with a nearest-neighbor search
+ std::vector<Points::size_type> ordered_copies;
+ Slic3r::Geometry::chained_path(points, ordered_copies);
+
+ for (size_t point_idx : ordered_copies)
+ m_copies.push_back(points[point_idx] + m_copies_shift);
+
+ bool invalidated = m_print->invalidate_step(psSkirt);
+ invalidated |= m_print->invalidate_step(psBrim);
+ if (copies_num_changed)
+ invalidated |= m_print->invalidate_step(psWipeTower);
+ return invalidated;
+}
+
+bool PrintObject::reload_model_instances()
+{
+ Points copies;
+ copies.reserve(m_model_object->instances.size());
+ for (const ModelInstance *mi : m_model_object->instances)
+ {
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ if (mi->is_printable())
+ {
+ const Vec3d& offset = mi->get_offset();
+ copies.emplace_back(Point::new_scale(offset(0), offset(1)));
+ }
+#else
+ if (mi->is_printable())
+ copies.emplace_back(Point::new_scale(mi->offset(0), mi->offset(1)));
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ }
+ return this->set_copies(copies);
+}
+
+// 1) Decides Z positions of the layers,
+// 2) Initializes layers and their regions
+// 3) Slices the object meshes
+// 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
+// 5) Applies size compensation (offsets the slices in XY plane)
+// 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
+// Resulting expolygons of layer regions are marked as Internal.
+//
+// this should be idempotent
+void PrintObject::slice()
+{
+ if (this->is_step_done(posSlice))
+ return;
+ this->set_started(posSlice);
+ m_print->set_status(10, "Processing triangulated mesh");
+ this->_slice();
+ m_print->throw_if_canceled();
+ // Fix the model.
+ //FIXME is this the right place to do? It is done repeateadly at the UI and now here at the backend.
+ std::string warning = this->_fix_slicing_errors();
+ m_print->throw_if_canceled();
+ if (! warning.empty())
+ BOOST_LOG_TRIVIAL(info) << warning;
+ // Simplify slices if required.
+ if (m_print->config().resolution)
+ this->_simplify_slices(scale_(this->print()->config().resolution));
+ if (m_layers.empty())
+ throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n");
+ this->set_done(posSlice);
+}
+
+// 1) Merges typed region slices into stInternal type.
+// 2) Increases an "extra perimeters" counter at region slices where needed.
+// 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal).
+void PrintObject::make_perimeters()
+{
+ // prerequisites
+ this->slice();
+
+ if (this->is_step_done(posPerimeters))
+ return;
+
+ this->set_started(posPerimeters);
+ m_print->set_status(20, "Generating perimeters");
+ BOOST_LOG_TRIVIAL(info) << "Generating perimeters...";
+
+ // merge slices if they were split into types
+ if (this->typed_slices) {
+ FOREACH_LAYER(this, layer_it) {
+ (*layer_it)->merge_slices();
+ m_print->throw_if_canceled();
+ }
+ this->typed_slices = false;
+ }
+
+ // compare each layer to the one below, and mark those slices needing
+ // one additional inner perimeter, like the top of domed objects-
+
+ // this algorithm makes sure that at least one perimeter is overlapping
+ // but we don't generate any extra perimeter if fill density is zero, as they would be floating
+ // inside the object - infill_only_where_needed should be the method of choice for printing
+ // hollow objects
+ for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) {
+ const PrintRegion &region = *m_print->regions()[region_id];
+ if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2)
+ continue;
+
+ BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size() - 1),
+ [this, &region, region_id](const tbb::blocked_range<size_t>& range) {
+ for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
+ m_print->throw_if_canceled();
+ LayerRegion &layerm = *m_layers[layer_idx]->m_regions[region_id];
+ const LayerRegion &upper_layerm = *m_layers[layer_idx+1]->m_regions[region_id];
+ const Polygons upper_layerm_polygons = upper_layerm.slices;
+ // Filter upper layer polygons in intersection_ppl by their bounding boxes?
+ // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
+ const double total_loop_length = total_length(upper_layerm_polygons);
+ const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing();
+ const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter);
+ const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width();
+ const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing();
+
+ for (Surface &slice : layerm.slices.surfaces) {
+ for (;;) {
+ // compute the total thickness of perimeters
+ const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2
+ + (region.config().perimeters-1 + slice.extra_perimeters) * perimeter_spacing;
+ // define a critical area where we don't want the upper slice to fall into
+ // (it should either lay over our perimeters or outside this area)
+ const coord_t critical_area_depth = coord_t(perimeter_spacing * 1.5);
+ const Polygons critical_area = diff(
+ offset(slice.expolygon, float(- perimeters_thickness)),
+ offset(slice.expolygon, float(- perimeters_thickness - critical_area_depth))
+ );
+ // check whether a portion of the upper slices falls inside the critical area
+ const Polylines intersection = intersection_pl(to_polylines(upper_layerm_polygons), critical_area);
+ // only add an additional loop if at least 30% of the slice loop would benefit from it
+ if (total_length(intersection) <= total_loop_length*0.3)
+ break;
+ /*
+ if (0) {
+ require "Slic3r/SVG.pm";
+ Slic3r::SVG::output(
+ "extra.svg",
+ no_arrows => 1,
+ expolygons => union_ex($critical_area),
+ polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
+ );
+ }
+ */
+ ++ slice.extra_perimeters;
+ }
+ #ifdef DEBUG
+ if (slice.extra_perimeters > 0)
+ printf(" adding %d more perimeter(s) at layer %zu\n", slice.extra_perimeters, layer_idx);
+ #endif
+ }
+ }
+ });
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end";
+ }
+
+ BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size()),
+ [this](const tbb::blocked_range<size_t>& range) {
+ for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
+ m_print->throw_if_canceled();
+ m_layers[layer_idx]->make_perimeters();
+ }
+ }
+ );
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
+
+ /*
+ simplify slices (both layer and region slices),
+ we only need the max resolution for perimeters
+ ### This makes this method not-idempotent, so we keep it disabled for now.
+ ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION);
+ */
+
+ this->set_done(posPerimeters);
+}
+
+void PrintObject::prepare_infill()
+{
+ if (this->is_step_done(posPrepareInfill))
+ return;
+
+ this->set_started(posPrepareInfill);
+ m_print->set_status(30, "Preparing infill");
+
+ // This will assign a type (top/bottom/internal) to $layerm->slices.
+ // Then the classifcation of $layerm->slices is transfered onto
+ // the $layerm->fill_surfaces by clipping $layerm->fill_surfaces
+ // by the cummulative area of the previous $layerm->fill_surfaces.
+ this->detect_surfaces_type();
+ m_print->throw_if_canceled();
+
+ // Decide what surfaces are to be filled.
+ // Here the S_TYPE_TOP / S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is turned to just S_TYPE_INTERNAL if zero top / bottom infill layers are configured.
+ // Also tiny S_TYPE_INTERNAL surfaces are turned to S_TYPE_INTERNAL_SOLID.
+ BOOST_LOG_TRIVIAL(info) << "Preparing fill surfaces...";
+ for (auto *layer : m_layers)
+ for (auto *region : layer->m_regions) {
+ region->prepare_fill_surfaces();
+ m_print->throw_if_canceled();
+ }
+
+ // this will detect bridges and reverse bridges
+ // and rearrange top/bottom/internal surfaces
+ // It produces enlarged overlapping bridging areas.
+ //
+ // 1) S_TYPE_BOTTOMBRIDGE / S_TYPE_BOTTOM infill is grown by 3mm and clipped by the total infill area. Bridges are detected. The areas may overlap.
+ // 2) S_TYPE_TOP is grown by 3mm and clipped by the grown bottom areas. The areas may overlap.
+ // 3) Clip the internal surfaces by the grown top/bottom surfaces.
+ // 4) Merge surfaces with the same style. This will mostly get rid of the overlaps.
+ //FIXME This does not likely merge surfaces, which are supported by a material with different colors, but same properties.
+ this->process_external_surfaces();
+ m_print->throw_if_canceled();
+
+ // Add solid fills to ensure the shell vertical thickness.
+ this->discover_vertical_shells();
+ m_print->throw_if_canceled();
+
+ // Debugging output.
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
+ for (const Layer *layer : m_layers) {
+ LayerRegion *layerm = layer->m_regions[region_id];
+ layerm->export_region_slices_to_svg_debug("6_discover_vertical_shells-final");
+ layerm->export_region_fill_surfaces_to_svg_debug("6_discover_vertical_shells-final");
+ } // for each layer
+ } // for each region
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ // Detect, which fill surfaces are near external layers.
+ // They will be split in internal and internal-solid surfaces.
+ // The purpose is to add a configurable number of solid layers to support the TOP surfaces
+ // and to add a configurable number of solid layers above the BOTTOM / BOTTOMBRIDGE surfaces
+ // to close these surfaces reliably.
+ //FIXME Vojtech: Is this a good place to add supporting infills below sloping perimeters?
+ this->discover_horizontal_shells();
+ m_print->throw_if_canceled();
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
+ for (const Layer *layer : m_layers) {
+ LayerRegion *layerm = layer->m_regions[region_id];
+ layerm->export_region_slices_to_svg_debug("7_discover_horizontal_shells-final");
+ layerm->export_region_fill_surfaces_to_svg_debug("7_discover_horizontal_shells-final");
+ } // for each layer
+ } // for each region
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ // Only active if config->infill_only_where_needed. This step trims the sparse infill,
+ // so it acts as an internal support. It maintains all other infill types intact.
+ // Here the internal surfaces and perimeters have to be supported by the sparse infill.
+ //FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
+ // Likely the sparse infill will not be anchored correctly, so it will not work as intended.
+ // Also one wishes the perimeters to be supported by a full infill.
+ this->clip_fill_surfaces();
+ m_print->throw_if_canceled();
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
+ for (const Layer *layer : m_layers) {
+ LayerRegion *layerm = layer->m_regions[region_id];
+ layerm->export_region_slices_to_svg_debug("8_clip_surfaces-final");
+ layerm->export_region_fill_surfaces_to_svg_debug("8_clip_surfaces-final");
+ } // for each layer
+ } // for each region
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ // the following step needs to be done before combination because it may need
+ // to remove only half of the combined infill
+ this->bridge_over_infill();
+ m_print->throw_if_canceled();
+
+ // combine fill surfaces to honor the "infill every N layers" option
+ this->combine_infill();
+ m_print->throw_if_canceled();
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
+ for (const Layer *layer : m_layers) {
+ LayerRegion *layerm = layer->m_regions[region_id];
+ layerm->export_region_slices_to_svg_debug("9_prepare_infill-final");
+ layerm->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
+ } // for each layer
+ } // for each region
+ for (const Layer *layer : m_layers) {
+ layer->export_region_slices_to_svg_debug("9_prepare_infill-final");
+ layer->export_region_fill_surfaces_to_svg_debug("9_prepare_infill-final");
+ } // for each layer
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ this->set_done(posPrepareInfill);
+}
+
+void PrintObject::infill()
+{
+ if (! this->is_printable())
+ return;
+
+ // prerequisites
+ this->prepare_infill();
+
+ if (! this->is_step_done(posInfill)) {
+ this->set_started(posInfill);
+ BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size()),
+ [this](const tbb::blocked_range<size_t>& range) {
+ for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
+ m_print->throw_if_canceled();
+ m_layers[layer_idx]->make_fills();
+ }
+ }
+ );
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - end";
+ /* we could free memory now, but this would make this step not idempotent
+ ### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers};
+ */
+ this->set_done(posInfill);
+ }
+}
+
+void PrintObject::generate_support_material()
+{
+ if (! this->is_step_done(posSupportMaterial)) {
+ this->set_started(posSupportMaterial);
+ this->clear_support_layers();
+ if ((m_config.support_material || m_config.raft_layers > 0) && m_layers.size() > 1) {
+ m_print->set_status(85, "Generating support material");
+ this->_generate_support_material();
+ m_print->throw_if_canceled();
+ }
+ this->set_done(posSupportMaterial);
+ }
+}
+
+void PrintObject::clear_layers()
+{
+ for (Layer *l : m_layers)
+ delete l;
+ m_layers.clear();
+}
+
+Layer* PrintObject::add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z)
+{
+ m_layers.emplace_back(new Layer(id, this, height, print_z, slice_z));
+ return m_layers.back();
+}
+
+void PrintObject::clear_support_layers()
+{
+ for (Layer *l : m_support_layers)
+ delete l;
+ m_support_layers.clear();
+}
+
+SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t print_z)
+{
+ m_support_layers.emplace_back(new SupportLayer(id, this, height, print_z, -1));
+ return m_support_layers.back();
+}
+
+SupportLayerPtrs::const_iterator PrintObject::insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z)
+{
+ return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z));
+}
+
+// Called by Print::apply_config().
+// This method only accepts PrintObjectConfig and PrintRegionConfig option keys.
+bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys)
+{
+ if (opt_keys.empty())
+ return false;
+
+ std::vector<PrintObjectStep> steps;
+ bool invalidated = false;
+ for (const t_config_option_key &opt_key : opt_keys) {
+ if ( opt_key == "perimeters"
+ || opt_key == "extra_perimeters"
+ || opt_key == "gap_fill_speed"
+ || opt_key == "overhangs"
+ || opt_key == "first_layer_extrusion_width"
+ || opt_key == "perimeter_extrusion_width"
+ || opt_key == "infill_overlap"
+ || opt_key == "thin_walls"
+ || opt_key == "external_perimeters_first") {
+ steps.emplace_back(posPerimeters);
+ } else if (
+ opt_key == "layer_height"
+ || opt_key == "first_layer_height"
+ || opt_key == "raft_layers") {
+ steps.emplace_back(posSlice);
+ this->reset_layer_height_profile();
+ }
+ else if (
+ opt_key == "clip_multipart_objects"
+ || opt_key == "elefant_foot_compensation"
+ || opt_key == "support_material_contact_distance"
+ || opt_key == "xy_size_compensation") {
+ steps.emplace_back(posSlice);
+ } else if (
+ opt_key == "support_material"
+ || opt_key == "support_material_auto"
+ || opt_key == "support_material_angle"
+ || opt_key == "support_material_buildplate_only"
+ || opt_key == "support_material_enforce_layers"
+ || opt_key == "support_material_extruder"
+ || opt_key == "support_material_extrusion_width"
+ || opt_key == "support_material_interface_layers"
+ || opt_key == "support_material_interface_contact_loops"
+ || opt_key == "support_material_interface_extruder"
+ || opt_key == "support_material_interface_spacing"
+ || opt_key == "support_material_pattern"
+ || opt_key == "support_material_xy_spacing"
+ || opt_key == "support_material_spacing"
+ || opt_key == "support_material_synchronize_layers"
+ || opt_key == "support_material_threshold"
+ || opt_key == "support_material_with_sheath"
+ || opt_key == "dont_support_bridges"
+ || opt_key == "first_layer_extrusion_width") {
+ steps.emplace_back(posSupportMaterial);
+ } else if (
+ opt_key == "interface_shells"
+ || opt_key == "infill_only_where_needed"
+ || opt_key == "infill_every_layers"
+ || opt_key == "solid_infill_every_layers"
+ || opt_key == "bottom_solid_layers"
+ || opt_key == "top_solid_layers"
+ || opt_key == "solid_infill_below_area"
+ || opt_key == "infill_extruder"
+ || opt_key == "solid_infill_extruder"
+ || opt_key == "infill_extrusion_width"
+ || opt_key == "ensure_vertical_shell_thickness"
+ || opt_key == "bridge_angle") {
+ steps.emplace_back(posPrepareInfill);
+ } else if (
+ opt_key == "external_fill_pattern"
+ || opt_key == "external_fill_link_max_length"
+ || opt_key == "fill_angle"
+ || opt_key == "fill_pattern"
+ || opt_key == "fill_link_max_length"
+ || opt_key == "top_infill_extrusion_width"
+ || opt_key == "first_layer_extrusion_width") {
+ steps.emplace_back(posInfill);
+ } else if (
+ opt_key == "fill_density"
+ || opt_key == "solid_infill_extrusion_width") {
+ steps.emplace_back(posPerimeters);
+ steps.emplace_back(posPrepareInfill);
+ } else if (
+ opt_key == "external_perimeter_extrusion_width"
+ || opt_key == "perimeter_extruder") {
+ steps.emplace_back(posPerimeters);
+ steps.emplace_back(posSupportMaterial);
+ } else if (opt_key == "bridge_flow_ratio") {
+ steps.emplace_back(posPerimeters);
+ steps.emplace_back(posInfill);
+ } else if (
+ opt_key == "seam_position"
+ || opt_key == "seam_preferred_direction"
+ || opt_key == "seam_preferred_direction_jitter"
+ || opt_key == "support_material_speed"
+ || opt_key == "support_material_interface_speed"
+ || opt_key == "bridge_speed"
+ || opt_key == "external_perimeter_speed"
+ || opt_key == "infill_speed"
+ || opt_key == "perimeter_speed"
+ || opt_key == "small_perimeter_speed"
+ || opt_key == "solid_infill_speed"
+ || opt_key == "top_solid_infill_speed"
+ || opt_key == "wipe_into_infill" // when these these two are changed, we only need to invalidate the wipe tower,
+ || opt_key == "wipe_into_objects" // which we already did at the very beginning - nothing more to be done
+ ) {
+ // these options only affect G-code export, so nothing to invalidate
+ } else {
+ // for legacy, if we can't handle this option let's invalidate all steps
+ this->invalidate_all_steps();
+ this->reset_layer_height_profile();
+ invalidated = true;
+ }
+ }
+
+ sort_remove_duplicates(steps);
+ for (PrintObjectStep step : steps)
+ invalidated |= this->invalidate_step(step);
+ return invalidated;
+}
+
+bool PrintObject::invalidate_step(PrintObjectStep step)
+{
+ bool invalidated = m_state.invalidate(step, m_print->m_mutex, m_print->m_cancel_callback);
+
+ // propagate to dependent steps
+ if (step == posPerimeters) {
+ invalidated |= this->invalidate_step(posPrepareInfill);
+ invalidated |= m_print->invalidate_step(psSkirt);
+ invalidated |= m_print->invalidate_step(psBrim);
+ } else if (step == posPrepareInfill) {
+ invalidated |= this->invalidate_step(posInfill);
+ } else if (step == posInfill) {
+ invalidated |= m_print->invalidate_step(psSkirt);
+ invalidated |= m_print->invalidate_step(psBrim);
+ } else if (step == posSlice) {
+ invalidated |= this->invalidate_step(posPerimeters);
+ invalidated |= this->invalidate_step(posSupportMaterial);
+ invalidated |= m_print->invalidate_step(psWipeTower);
+ } else if (step == posSupportMaterial) {
+ invalidated |= m_print->invalidate_step(psSkirt);
+ invalidated |= m_print->invalidate_step(psBrim);
+ }
+
+ // Wipe tower depends on the ordering of extruders, which in turn depends on everything.
+ // It also decides about what the wipe_into_infill / wipe_into_object features will do,
+ // and that too depends on many of the settings.
+ invalidated |= m_print->invalidate_step(psWipeTower);
+ return invalidated;
+}
+
+bool PrintObject::invalidate_all_steps()
+{
+ return m_state.invalidate_all(m_print->m_mutex, m_print->m_cancel_callback);
+}
+
+bool PrintObject::has_support_material() const
+{
+ return m_config.support_material
+ || m_config.raft_layers > 0
+ || m_config.support_material_enforce_layers > 0;
+}
+
+// This function analyzes slices of a region (SurfaceCollection slices).
+// Each region slice (instance of Surface) is analyzed, whether it is supported or whether it is the top surface.
+// Initially all slices are of type stInternal.
+// Slices are compared against the top / bottom slices and regions and classified to the following groups:
+// stTop - Part of a region, which is not covered by any upper layer. This surface will be filled with a top solid infill.
+// stBottomBridge - Part of a region, which is not fully supported, but it hangs in the air, or it hangs losely on a support or a raft.
+// stBottom - Part of a region, which is not supported by the same region, but it is supported either by another region, or by a soluble interface layer.
+// stInternal - Part of a region, which is supported by the same region type.
+// If a part of a region is of stBottom and stTop, the stBottom wins.
+void PrintObject::detect_surfaces_type()
+{
+ BOOST_LOG_TRIVIAL(info) << "Detecting solid surfaces...";
+
+ // Interface shells: the intersecting parts are treated as self standing objects supporting each other.
+ // Each of the objects will have a full number of top / bottom layers, even if these top / bottom layers
+ // are completely hidden inside a collective body of intersecting parts.
+ // This is useful if one of the parts is to be dissolved, or if it is transparent and the internal shells
+ // should be visible.
+ bool interface_shells = m_config.interface_shells.value;
+
+ for (int idx_region = 0; idx_region < m_print->m_regions.size(); ++ idx_region) {
+ BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start";
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ for (Layer *layer : m_layers)
+ layer->m_regions[idx_region]->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-initial");
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ // If interface shells are allowed, the region->surfaces cannot be overwritten as they may be used by other threads.
+ // Cache the result of the following parallel_loop.
+ std::vector<Surfaces> surfaces_new;
+ if (interface_shells)
+ surfaces_new.assign(m_layers.size(), Surfaces());
+
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size()),
+ [this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range<size_t>& range) {
+ // If we have raft layers, consider bottom layer as a bridge just like any other bottom surface lying on the void.
+ SurfaceType surface_type_bottom_1st =
+ (m_config.raft_layers.value > 0 && m_config.support_material_contact_distance.value > 0) ?
+ stBottomBridge : stBottom;
+ // If we have soluble support material, don't bridge. The overhang will be squished against a soluble layer separating
+ // the support from the print.
+ SurfaceType surface_type_bottom_other =
+ (m_config.support_material.value && m_config.support_material_contact_distance.value == 0) ?
+ stBottom : stBottomBridge;
+ for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
+ m_print->throw_if_canceled();
+ // BOOST_LOG_TRIVIAL(trace) << "Detecting solid surfaces for region " << idx_region << " and layer " << layer->print_z;
+ Layer *layer = m_layers[idx_layer];
+ LayerRegion *layerm = layer->get_region(idx_region);
+ // comparison happens against the *full* slices (considering all regions)
+ // unless internal shells are requested
+ Layer *upper_layer = (idx_layer + 1 < this->layer_count()) ? m_layers[idx_layer + 1] : nullptr;
+ Layer *lower_layer = (idx_layer > 0) ? m_layers[idx_layer - 1] : nullptr;
+ // collapse very narrow parts (using the safety offset in the diff is not enough)
+ float offset = layerm->flow(frExternalPerimeter).scaled_width() / 10.f;
+
+ Polygons layerm_slices_surfaces = to_polygons(layerm->slices.surfaces);
+
+ // find top surfaces (difference between current surfaces
+ // of current layer and upper one)
+ Surfaces top;
+ if (upper_layer) {
+ Polygons upper_slices = interface_shells ?
+ to_polygons(upper_layer->get_region(idx_region)->slices.surfaces) :
+ to_polygons(upper_layer->slices);
+ surfaces_append(top,
+ //FIXME implement offset2_ex working over ExPolygons, that should be a bit more efficient than calling offset_ex twice.
+ offset_ex(offset_ex(diff_ex(layerm_slices_surfaces, upper_slices, true), -offset), offset),
+ stTop);
+ } else {
+ // if no upper layer, all surfaces of this one are solid
+ // we clone surfaces because we're going to clear the slices collection
+ top = layerm->slices.surfaces;
+ for (Surface &surface : top)
+ surface.surface_type = stTop;
+ }
+
+ // Find bottom surfaces (difference between current surfaces of current layer and lower one).
+ Surfaces bottom;
+ if (lower_layer) {
+#if 0
+ //FIXME Why is this branch failing t\multi.t ?
+ Polygons lower_slices = interface_shells ?
+ to_polygons(lower_layer->get_region(idx_region)->slices.surfaces) :
+ to_polygons(lower_layer->slices);
+ surfaces_append(bottom,
+ offset2_ex(diff(layerm_slices_surfaces, lower_slices, true), -offset, offset),
+ surface_type_bottom_other);
+#else
+ // Any surface lying on the void is a true bottom bridge (an overhang)
+ surfaces_append(
+ bottom,
+ offset2_ex(
+ diff(layerm_slices_surfaces, to_polygons(lower_layer->slices), true),
+ -offset, offset),
+ surface_type_bottom_other);
+ // if user requested internal shells, we need to identify surfaces
+ // lying on other slices not belonging to this region
+ if (interface_shells) {
+ // non-bridging bottom surfaces: any part of this layer lying
+ // on something else, excluding those lying on our own region
+ surfaces_append(
+ bottom,
+ offset2_ex(
+ diff(
+ intersection(layerm_slices_surfaces, to_polygons(lower_layer->slices)), // supported
+ to_polygons(lower_layer->get_region(idx_region)->slices.surfaces),
+ true),
+ -offset, offset),
+ stBottom);
+ }
+#endif
+ } else {
+ // if no lower layer, all surfaces of this one are solid
+ // we clone surfaces because we're going to clear the slices collection
+ bottom = layerm->slices.surfaces;
+ for (Surface &surface : bottom)
+ surface.surface_type = surface_type_bottom_1st;
+ }
+
+ // now, if the object contained a thin membrane, we could have overlapping bottom
+ // and top surfaces; let's do an intersection to discover them and consider them
+ // as bottom surfaces (to allow for bridge detection)
+ if (! top.empty() && ! bottom.empty()) {
+ // Polygons overlapping = intersection(to_polygons(top), to_polygons(bottom));
+ // Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->layer->id, scalar(@$overlapping)
+ // if $Slic3r::debug;
+ Polygons top_polygons = to_polygons(std::move(top));
+ top.clear();
+ surfaces_append(top,
+ diff_ex(top_polygons, to_polygons(bottom), false),
+ stTop);
+ }
+
+ #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ static int iRun = 0;
+ std::vector<std::pair<Slic3r::ExPolygons, SVG::ExPolygonAttributes>> expolygons_with_attributes;
+ expolygons_with_attributes.emplace_back(std::make_pair(union_ex(top), SVG::ExPolygonAttributes("green")));
+ expolygons_with_attributes.emplace_back(std::make_pair(union_ex(bottom), SVG::ExPolygonAttributes("brown")));
+ expolygons_with_attributes.emplace_back(std::make_pair(to_expolygons(layerm->slices.surfaces), SVG::ExPolygonAttributes("black")));
+ SVG::export_expolygons(debug_out_path("1_detect_surfaces_type_%d_region%d-layer_%f.svg", iRun ++, idx_region, layer->print_z).c_str(), expolygons_with_attributes);
+ }
+ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ // save surfaces to layer
+ Surfaces &surfaces_out = interface_shells ? surfaces_new[idx_layer] : layerm->slices.surfaces;
+ surfaces_out.clear();
+
+ // find internal surfaces (difference between top/bottom surfaces and others)
+ {
+ Polygons topbottom = to_polygons(top);
+ polygons_append(topbottom, to_polygons(bottom));
+ surfaces_append(surfaces_out,
+ diff_ex(layerm_slices_surfaces, topbottom, false),
+ stInternal);
+ }
+
+ surfaces_append(surfaces_out, std::move(top));
+ surfaces_append(surfaces_out, std::move(bottom));
+
+ // Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n",
+ // $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug;
+
+ #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ layerm->export_region_slices_to_svg_debug("detect_surfaces_type-final");
+ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+ }
+ }
+ ); // for each layer of a region
+ m_print->throw_if_canceled();
+
+ if (interface_shells) {
+ // Move surfaces_new to layerm->slices.surfaces
+ for (size_t idx_layer = 0; idx_layer < m_layers.size(); ++ idx_layer)
+ m_layers[idx_layer]->get_region(idx_region)->slices.surfaces = std::move(surfaces_new[idx_layer]);
+ }
+
+ BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " - clipping in parallel - start";
+ // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size()),
+ [this, idx_region, interface_shells, &surfaces_new](const tbb::blocked_range<size_t>& range) {
+ for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
+ m_print->throw_if_canceled();
+ LayerRegion *layerm = m_layers[idx_layer]->get_region(idx_region);
+ layerm->slices_to_fill_surfaces_clipped();
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ layerm->export_region_fill_surfaces_to_svg_debug("1_detect_surfaces_type-final");
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+ } // for each layer of a region
+ });
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " - clipping in parallel - end";
+ } // for each this->print->region_count
+
+ // Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
+ this->typed_slices = true;
+}
+
+void PrintObject::process_external_surfaces()
+{
+ BOOST_LOG_TRIVIAL(info) << "Processing external surfaces...";
+
+ for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) {
+ const PrintRegion &region = *m_print->regions()[region_id];
+
+ BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size()),
+ [this, region_id](const tbb::blocked_range<size_t>& range) {
+ for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
+ m_print->throw_if_canceled();
+ // BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << m_layers[layer_idx]->print_z;
+ m_layers[layer_idx]->get_region(region_id)->process_external_surfaces((layer_idx == 0) ? NULL : m_layers[layer_idx - 1]);
+ }
+ }
+ );
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end";
+ }
+}
+
+void PrintObject::discover_vertical_shells()
+{
+ PROFILE_FUNC();
+
+ BOOST_LOG_TRIVIAL(info) << "Discovering vertical shells...";
+
+ struct DiscoverVerticalShellsCacheEntry
+ {
+ // Collected polygons, offsetted
+ Polygons top_surfaces;
+ Polygons bottom_surfaces;
+ Polygons holes;
+ };
+ std::vector<DiscoverVerticalShellsCacheEntry> cache_top_botom_regions(m_layers.size(), DiscoverVerticalShellsCacheEntry());
+ bool top_bottom_surfaces_all_regions = m_print->regions().size() > 1 && ! m_config.interface_shells.value;
+ if (top_bottom_surfaces_all_regions) {
+ // This is a multi-material print and interface_shells are disabled, meaning that the vertical shell thickness
+ // is calculated over all materials.
+ // Is the "ensure vertical wall thickness" applicable to any region?
+ bool has_extra_layers = false;
+ for (size_t idx_region = 0; idx_region < m_print->regions().size(); ++ idx_region) {
+ const PrintRegion &region = *m_print->get_region(idx_region);
+ if (region.config().ensure_vertical_shell_thickness.value &&
+ (region.config().top_solid_layers.value > 1 || region.config().bottom_solid_layers.value > 1)) {
+ has_extra_layers = true;
+ }
+ }
+ if (! has_extra_layers)
+ // The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit.
+ return;
+ BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom";
+ //FIXME Improve the heuristics for a grain size.
+ size_t grain_size = std::max(m_layers.size() / 16, size_t(1));
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size(), grain_size),
+ [this, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
+ const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge };
+ const size_t num_regions = m_print->regions().size();
+ for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
+ m_print->throw_if_canceled();
+ const Layer &layer = *m_layers[idx_layer];
+ DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer];
+ // Simulate single set of perimeters over all merged regions.
+ float perimeter_offset = 0.f;
+ float perimeter_min_spacing = FLT_MAX;
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ static size_t debug_idx = 0;
+ ++ debug_idx;
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+ for (size_t idx_region = 0; idx_region < num_regions; ++ idx_region) {
+ LayerRegion &layerm = *layer.m_regions[idx_region];
+ float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f;
+ // Top surfaces.
+ append(cache.top_surfaces, offset(to_expolygons(layerm.slices.filter_by_type(stTop)), min_perimeter_infill_spacing));
+ append(cache.top_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stTop)), min_perimeter_infill_spacing));
+ // Bottom surfaces.
+ append(cache.bottom_surfaces, offset(to_expolygons(layerm.slices.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing));
+ append(cache.bottom_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing));
+ // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only.
+ // First find the maxium number of perimeters per region slice.
+ unsigned int perimeters = 0;
+ for (Surface &s : layerm.slices.surfaces)
+ perimeters = std::max<unsigned int>(perimeters, s.extra_perimeters);
+ perimeters += layerm.region()->config().perimeters.value;
+ // Then calculate the infill offset.
+ if (perimeters > 0) {
+ Flow extflow = layerm.flow(frExternalPerimeter);
+ Flow flow = layerm.flow(frPerimeter);
+ perimeter_offset = std::max(perimeter_offset,
+ 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing());
+ perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing())));
+ }
+ polygons_append(cache.holes, to_polygons(layerm.fill_expolygons));
+ }
+ // Save some computing time by reducing the number of polygons.
+ cache.top_surfaces = union_(cache.top_surfaces, false);
+ cache.bottom_surfaces = union_(cache.bottom_surfaces, false);
+ // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print.
+ if (perimeter_offset > 0.) {
+ // The layer.slices are forced to merge by expanding them first.
+ polygons_append(cache.holes, offset(offset_ex(layer.slices, 0.3f * perimeter_min_spacing), - perimeter_offset - 0.3f * perimeter_min_spacing));
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.slices.expolygons));
+ svg.draw(layer.slices.expolygons, "blue");
+ svg.draw(union_ex(cache.holes), "red");
+ svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05));
+ svg.Close();
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+ }
+ cache.holes = union_(cache.holes, false);
+ }
+ });
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom";
+ }
+
+ for (size_t idx_region = 0; idx_region < m_print->regions().size(); ++ idx_region) {
+ PROFILE_BLOCK(discover_vertical_shells_region);
+
+ const PrintRegion &region = *m_print->get_region(idx_region);
+ if (! region.config().ensure_vertical_shell_thickness.value)
+ // This region will be handled by discover_horizontal_shells().
+ continue;
+ int n_extra_top_layers = std::max(0, region.config().top_solid_layers.value - 1);
+ int n_extra_bottom_layers = std::max(0, region.config().bottom_solid_layers.value - 1);
+ if (n_extra_top_layers + n_extra_bottom_layers == 0)
+ // Zero or 1 layer, there is no additional vertical wall thickness enforced.
+ continue;
+
+ //FIXME Improve the heuristics for a grain size.
+ size_t grain_size = std::max(m_layers.size() / 16, size_t(1));
+
+ if (! top_bottom_surfaces_all_regions) {
+ // This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
+ // is calculated over a single material.
+ BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : cache top / bottom";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size(), grain_size),
+ [this, idx_region, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
+ const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge };
+ for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
+ m_print->throw_if_canceled();
+ Layer &layer = *m_layers[idx_layer];
+ LayerRegion &layerm = *layer.m_regions[idx_region];
+ float min_perimeter_infill_spacing = float(layerm.flow(frSolidInfill).scaled_spacing()) * 1.05f;
+ // Top surfaces.
+ auto &cache = cache_top_botom_regions[idx_layer];
+ cache.top_surfaces = offset(to_expolygons(layerm.slices.filter_by_type(stTop)), min_perimeter_infill_spacing);
+ append(cache.top_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_type(stTop)), min_perimeter_infill_spacing));
+ // Bottom surfaces.
+ cache.bottom_surfaces = offset(to_expolygons(layerm.slices.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing);
+ append(cache.bottom_surfaces, offset(to_expolygons(layerm.fill_surfaces.filter_by_types(surfaces_bottom, 2)), min_perimeter_infill_spacing));
+ // Holes over all regions. Only collect them once, they are valid for all idx_region iterations.
+ if (cache.holes.empty()) {
+ for (size_t idx_region = 0; idx_region < layer.regions().size(); ++ idx_region)
+ polygons_append(cache.holes, to_polygons(layer.regions()[idx_region]->fill_expolygons));
+ }
+ }
+ });
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - end : cache top / bottom";
+ }
+
+ BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : ensure vertical wall thickness";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size(), grain_size),
+ [this, idx_region, n_extra_top_layers, n_extra_bottom_layers, &cache_top_botom_regions]
+ (const tbb::blocked_range<size_t>& range) {
+ // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
+ for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
+ PROFILE_BLOCK(discover_vertical_shells_region_layer);
+ m_print->throw_if_canceled();
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ static size_t debug_idx = 0;
+ ++ debug_idx;
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ Layer *layer = m_layers[idx_layer];
+ LayerRegion *layerm = layer->m_regions[idx_region];
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-initial");
+ layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells-initial");
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ Flow solid_infill_flow = layerm->flow(frSolidInfill);
+ coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
+ // Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
+ Polygons shell;
+ Polygons holes;
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ ExPolygons shell_ex;
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+ float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
+ {
+ PROFILE_BLOCK(discover_vertical_shells_region_layer_collect);
+#if 0
+// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box());
+ for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) {
+ if (n < 0 || n >= (int)m_layers.size())
+ continue;
+ ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
+ for (size_t i = 0; i < expolys.size(); ++ i) {
+ Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
+ svg.draw(expolys[i]);
+ svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
+ svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
+ svg.Close();
+
+ svg_cummulative.draw(expolys[i]);
+ svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
+ svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
+ }
+ }
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+ // Reset the top / bottom inflated regions caches of entries, which are out of the moving window.
+ bool hole_first = true;
+ for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n)
+ if (n >= 0 && n < (int)m_layers.size()) {
+ Layer &neighbor_layer = *m_layers[n];
+ const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n];
+ if (hole_first) {
+ hole_first = false;
+ polygons_append(holes, cache.holes);
+ }
+ else if (! holes.empty()) {
+ holes = intersection(holes, cache.holes);
+ }
+ size_t n_shell_old = shell.size();
+ if (n > int(idx_layer))
+ // Collect top surfaces.
+ polygons_append(shell, cache.top_surfaces);
+ else if (n < int(idx_layer))
+ // Collect bottom and bottom bridge surfaces.
+ polygons_append(shell, cache.bottom_surfaces);
+ // Running the union_ using the Clipper library piece by piece is cheaper
+ // than running the union_ all at once.
+ if (n_shell_old < shell.size())
+ shell = union_(shell, false);
+ }
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell));
+ svg.draw(shell);
+ svg.draw_outline(shell, "black", scale_(0.05));
+ svg.Close();
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+#if 0
+ {
+ PROFILE_BLOCK(discover_vertical_shells_region_layer_shell_);
+ // shell = union_(shell, true);
+ shell = union_(shell, false);
+ }
+#endif
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ shell_ex = union_ex(shell, true);
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+ }
+
+ //if (shell.empty())
+ // continue;
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-after-union-%d.svg", debug_idx), get_extents(shell));
+ svg.draw(shell_ex);
+ svg.draw_outline(shell_ex, "black", "blue", scale_(0.05));
+ svg.Close();
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internal-wshell-%d.svg", debug_idx), get_extents(shell));
+ svg.draw(layerm->fill_surfaces.filter_by_type(stInternal), "yellow", 0.5);
+ svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternal), "black", "blue", scale_(0.05));
+ svg.draw(shell_ex, "blue", 0.5);
+ svg.draw_outline(shell_ex, "black", "blue", scale_(0.05));
+ svg.Close();
+ }
+ {
+ Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell));
+ svg.draw(layerm->fill_surfaces.filter_by_type(stInternalVoid), "yellow", 0.5);
+ svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternalVoid), "black", "blue", scale_(0.05));
+ svg.draw(shell_ex, "blue", 0.5);
+ svg.draw_outline(shell_ex, "black", "blue", scale_(0.05));
+ svg.Close();
+ }
+ {
+ Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell));
+ svg.draw(layerm->fill_surfaces.filter_by_type(stInternalVoid), "yellow", 0.5);
+ svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternalVoid), "black", "blue", scale_(0.05));
+ svg.draw(shell_ex, "blue", 0.5);
+ svg.draw_outline(shell_ex, "black", "blue", scale_(0.05));
+ svg.Close();
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ // Trim the shells region by the internal & internal void surfaces.
+ const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid };
+ const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 3));
+ shell = intersection(shell, polygonsInternal, true);
+ polygons_append(shell, diff(polygonsInternal, holes));
+ if (shell.empty())
+ continue;
+
+ // Append the internal solids, so they will be merged with the new ones.
+ polygons_append(shell, to_polygons(layerm->fill_surfaces.filter_by_type(stInternalSolid)));
+
+ // These regions will be filled by a rectilinear full infill. Currently this type of infill
+ // only fills regions, which fit at least a single line. To avoid gaps in the sparse infill,
+ // make sure that this region does not contain parts narrower than the infill spacing width.
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ Polygons shell_before = shell;
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+#if 1
+ // Intentionally inflate a bit more than how much the region has been shrunk,
+ // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill).
+ shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare);
+ if (shell.empty())
+ continue;
+#else
+ // Ensure each region is at least 3x infill line width wide, so it could be filled in.
+ // float margin = float(infill_line_spacing) * 3.f;
+ float margin = float(infill_line_spacing) * 1.5f;
+ // we use a higher miterLimit here to handle areas with acute angles
+ // in those cases, the default miterLimit would cut the corner and we'd
+ // get a triangle in $too_narrow; if we grow it below then the shell
+ // would have a different shape from the external surface and we'd still
+ // have the same angle, so the next shell would be grown even more and so on.
+ Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, ClipperLib::jtMiter, 5.), true);
+ if (! too_narrow.empty()) {
+ // grow the collapsing parts and add the extra area to the neighbor layer
+ // as well as to our original surfaces so that we support this
+ // additional area in the next shell too
+ // make sure our grown surfaces don't exceed the fill area
+ polygons_append(shell, intersection(offset(too_narrow, margin), polygonsInternal));
+ }
+#endif
+ ExPolygons new_internal_solid = intersection_ex(polygonsInternal, shell, false);
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before));
+ // Source shell.
+ svg.draw(union_ex(shell_before, true));
+ // Shell trimmed to the internal surfaces.
+ svg.draw_outline(union_ex(shell, true), "black", "blue", scale_(0.05));
+ // Regularized infill region.
+ svg.draw_outline(new_internal_solid, "red", "magenta", scale_(0.05));
+ svg.Close();
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ // Trim the internal & internalvoid by the shell.
+ Slic3r::ExPolygons new_internal = diff_ex(
+ to_polygons(layerm->fill_surfaces.filter_by_type(stInternal)),
+ shell,
+ false
+ );
+ Slic3r::ExPolygons new_internal_void = diff_ex(
+ to_polygons(layerm->fill_surfaces.filter_by_type(stInternalVoid)),
+ shell,
+ false
+ );
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ {
+ SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal-%d.svg", debug_idx), get_extents(shell), new_internal, "black", "blue", scale_(0.05));
+ SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_void-%d.svg", debug_idx), get_extents(shell), new_internal_void, "black", "blue", scale_(0.05));
+ SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_solid-%d.svg", debug_idx), get_extents(shell), new_internal_solid, "black", "blue", scale_(0.05));
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+
+ // Assign resulting internal surfaces to layer.
+ const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge };
+ layerm->fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType));
+ layerm->fill_surfaces.append(new_internal, stInternal);
+ layerm->fill_surfaces.append(new_internal_void, stInternalVoid);
+ layerm->fill_surfaces.append(new_internal_solid, stInternalSolid);
+ } // for each layer
+ });
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - end";
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ for (size_t idx_layer = 0; idx_layer < m_layers.size(); ++idx_layer) {
+ LayerRegion *layerm = m_layers[idx_layer]->get_region(idx_region);
+ layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-final");
+ layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells-final");
+ }
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+ } // for each region
+
+ // Write the profiler measurements to file
+// PROFILE_UPDATE();
+// PROFILE_OUTPUT(debug_out_path("discover_vertical_shells-profile.txt").c_str());
+}
+
+/* This method applies bridge flow to the first internal solid layer above
+ sparse infill */
+void PrintObject::bridge_over_infill()
+{
+ BOOST_LOG_TRIVIAL(info) << "Bridge over infill...";
+
+ for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) {
+ const PrintRegion &region = *m_print->regions()[region_id];
+
+ // skip bridging in case there are no voids
+ if (region.config().fill_density.value == 100) continue;
+
+ // get bridge flow
+ Flow bridge_flow = region.flow(
+ frSolidInfill,
+ -1, // layer height, not relevant for bridge flow
+ true, // bridge
+ false, // first layer
+ -1, // custom width, not relevant for bridge flow
+ *this
+ );
+
+ FOREACH_LAYER(this, layer_it) {
+ // skip first layer
+ if (layer_it == m_layers.begin()) continue;
+
+ Layer* layer = *layer_it;
+ LayerRegion* layerm = layer->m_regions[region_id];
+
+ // extract the stInternalSolid surfaces that might be transformed into bridges
+ Polygons internal_solid;
+ layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid);
+
+ // check whether the lower area is deep enough for absorbing the extra flow
+ // (for obvious physical reasons but also for preventing the bridge extrudates
+ // from overflowing in 3D preview)
+ ExPolygons to_bridge;
+ {
+ Polygons to_bridge_pp = internal_solid;
+
+ // iterate through lower layers spanned by bridge_flow
+ double bottom_z = layer->print_z - bridge_flow.height;
+ for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) {
+ const Layer* lower_layer = m_layers[i];
+
+ // stop iterating if layer is lower than bottom_z
+ if (lower_layer->print_z < bottom_z) break;
+
+ // iterate through regions and collect internal surfaces
+ Polygons lower_internal;
+ FOREACH_LAYERREGION(lower_layer, lower_layerm_it)
+ (*lower_layerm_it)->fill_surfaces.filter_by_type(stInternal, &lower_internal);
+
+ // intersect such lower internal surfaces with the candidate solid surfaces
+ to_bridge_pp = intersection(to_bridge_pp, lower_internal);
+ }
+
+ // there's no point in bridging too thin/short regions
+ //FIXME Vojtech: The offset2 function is not a geometric offset,
+ // therefore it may create 1) gaps, and 2) sharp corners, which are outside the original contour.
+ // The gaps will be filled by a separate region, which makes the infill less stable and it takes longer.
+ {
+ float min_width = float(bridge_flow.scaled_width()) * 3.f;
+ to_bridge_pp = offset2(to_bridge_pp, -min_width, +min_width);
+ }
+
+ if (to_bridge_pp.empty()) continue;
+
+ // convert into ExPolygons
+ to_bridge = union_ex(to_bridge_pp);
+ }
+
+ #ifdef SLIC3R_DEBUG
+ printf("Bridging " PRINTF_ZU " internal areas at layer " PRINTF_ZU "\n", to_bridge.size(), layer->id());
+ #endif
+
+ // compute the remaning internal solid surfaces as difference
+ ExPolygons not_to_bridge = diff_ex(internal_solid, to_polygons(to_bridge), true);
+ to_bridge = intersection_ex(to_polygons(to_bridge), internal_solid, true);
+ // build the new collection of fill_surfaces
+ layerm->fill_surfaces.remove_type(stInternalSolid);
+ for (ExPolygon &ex : to_bridge)
+ layerm->fill_surfaces.surfaces.push_back(Surface(stInternalBridge, ex));
+ for (ExPolygon &ex : not_to_bridge)
+ layerm->fill_surfaces.surfaces.push_back(Surface(stInternalSolid, ex));
+ /*
+ # exclude infill from the layers below if needed
+ # see discussion at https://github.com/alexrj/Slic3r/issues/240
+ # Update: do not exclude any infill. Sparse infill is able to absorb the excess material.
+ if (0) {
+ my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height;
+ for (my $i = $layer_id-1; $excess >= $self->get_layer($i)->height; $i--) {
+ Slic3r::debugf " skipping infill below those areas at layer %d\n", $i;
+ foreach my $lower_layerm (@{$self->get_layer($i)->regions}) {
+ my @new_surfaces = ();
+ # subtract the area from all types of surfaces
+ foreach my $group (@{$lower_layerm->fill_surfaces->group}) {
+ push @new_surfaces, map $group->[0]->clone(expolygon => $_),
+ @{diff_ex(
+ [ map $_->p, @$group ],
+ [ map @$_, @$to_bridge ],
+ )};
+ push @new_surfaces, map Slic3r::Surface->new(
+ expolygon => $_,
+ surface_type => S_TYPE_INTERNALVOID,
+ ), @{intersection_ex(
+ [ map $_->p, @$group ],
+ [ map @$_, @$to_bridge ],
+ )};
+ }
+ $lower_layerm->fill_surfaces->clear;
+ $lower_layerm->fill_surfaces->append($_) for @new_surfaces;
+ }
+
+ $excess -= $self->get_layer($i)->height;
+ }
+ }
+ */
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ layerm->export_region_slices_to_svg_debug("7_bridge_over_infill");
+ layerm->export_region_fill_surfaces_to_svg_debug("7_bridge_over_infill");
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+ m_print->throw_if_canceled();
+ }
+ }
+}
+
+SlicingParameters PrintObject::slicing_parameters() const
+{
+ return SlicingParameters::create_from_config(
+ this->print()->config(), m_config,
+ unscale<double>(this->size(2)), this->print()->object_extruders());
+}
+
+bool PrintObject::update_layer_height_profile(std::vector<coordf_t> &layer_height_profile) const
+{
+ bool updated = false;
+
+ // If the layer height profile is not set, try to use the one stored at the ModelObject.
+ if (layer_height_profile.empty() && layer_height_profile.data() != this->model_object()->layer_height_profile.data()) {
+ layer_height_profile = this->model_object()->layer_height_profile;
+ updated = true;
+ }
+
+ // Verify the layer_height_profile.
+ SlicingParameters slicing_params = this->slicing_parameters();
+ if (! layer_height_profile.empty() &&
+ // Must not be of even length.
+ ((layer_height_profile.size() & 1) != 0 ||
+ // Last entry must be at the top of the object.
+ std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) > 1e-3))
+ layer_height_profile.clear();
+
+ if (layer_height_profile.empty()) {
+ if (0)
+// if (this->layer_height_profile.empty())
+ layer_height_profile = layer_height_profile_adaptive(slicing_params, this->layer_height_ranges,
+ this->model_object()->volumes);
+ else
+ layer_height_profile = layer_height_profile_from_ranges(slicing_params, this->layer_height_ranges);
+ updated = true;
+ }
+ return updated;
+}
+
+// This must be called from the main thread as it modifies the layer_height_profile.
+bool PrintObject::update_layer_height_profile()
+{
+ // If the layer height profile has been marked as invalid for some reason (modified at the UI level
+ // or invalidated due to the slicing parameters), clear it now.
+ if (! this->layer_height_profile_valid) {
+ this->layer_height_profile.clear();
+ this->layer_height_profile_valid = true;
+ }
+ return this->update_layer_height_profile(this->layer_height_profile);
+}
+
+// 1) Decides Z positions of the layers,
+// 2) Initializes layers and their regions
+// 3) Slices the object meshes
+// 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes
+// 5) Applies size compensation (offsets the slices in XY plane)
+// 6) Replaces bad slices by the slices reconstructed from the upper/lower layer
+// Resulting expolygons of layer regions are marked as Internal.
+//
+// this should be idempotent
+void PrintObject::_slice()
+{
+ BOOST_LOG_TRIVIAL(info) << "Slicing objects...";
+
+ this->typed_slices = false;
+
+#ifdef SLIC3R_PROFILE
+ // Disable parallelization so the Shiny profiler works
+ static tbb::task_scheduler_init *tbb_init = nullptr;
+ tbb_init = new tbb::task_scheduler_init(1);
+#endif
+
+ SlicingParameters slicing_params = this->slicing_parameters();
+
+ // 1) Initialize layers and their slice heights.
+ std::vector<float> slice_zs;
+ {
+ this->clear_layers();
+ // Object layers (pairs of bottom/top Z coordinate), without the raft.
+ std::vector<coordf_t> object_layers = generate_object_layers(slicing_params, this->layer_height_profile);
+ // Reserve object layers for the raft. Last layer of the raft is the contact layer.
+ int id = int(slicing_params.raft_layers());
+ slice_zs.reserve(object_layers.size());
+ Layer *prev = nullptr;
+ for (size_t i_layer = 0; i_layer < object_layers.size(); i_layer += 2) {
+ coordf_t lo = object_layers[i_layer];
+ coordf_t hi = object_layers[i_layer + 1];
+ coordf_t slice_z = 0.5 * (lo + hi);
+ Layer *layer = this->add_layer(id ++, hi - lo, hi + slicing_params.object_print_z_min, slice_z);
+ slice_zs.push_back(float(slice_z));
+ if (prev != nullptr) {
+ prev->upper_layer = layer;
+ layer->lower_layer = prev;
+ }
+ // Make sure all layers contain layer region objects for all regions.
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id)
+ layer->add_region(this->print()->regions()[region_id]);
+ prev = layer;
+ }
+ }
+
+ // Slice all non-modifier volumes.
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
+ BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id;
+ std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(region_id, slice_zs, false);
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " start";
+ for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id)
+ m_layers[layer_id]->regions()[region_id]->slices.append(std::move(expolygons_by_layer[layer_id]), stInternal);
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " end";
+ }
+
+ // Slice all modifier volumes.
+ if (this->print()->regions().size() > 1) {
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
+ BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id;
+ std::vector<ExPolygons> expolygons_by_layer = this->_slice_region(region_id, slice_zs, true);
+ m_print->throw_if_canceled();
+ // loop through the other regions and 'steal' the slices belonging to this one
+ BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - stealing " << region_id << " start";
+ for (size_t other_region_id = 0; other_region_id < this->print()->regions().size(); ++ other_region_id) {
+ if (region_id == other_region_id)
+ continue;
+ for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) {
+ Layer *layer = m_layers[layer_id];
+ LayerRegion *layerm = layer->m_regions[region_id];
+ LayerRegion *other_layerm = layer->m_regions[other_region_id];
+ if (layerm == nullptr || other_layerm == nullptr)
+ continue;
+ Polygons other_slices = to_polygons(other_layerm->slices);
+ ExPolygons my_parts = intersection_ex(other_slices, to_polygons(expolygons_by_layer[layer_id]));
+ if (my_parts.empty())
+ continue;
+ // Remove such parts from original region.
+ other_layerm->slices.set(diff_ex(other_slices, to_polygons(my_parts)), stInternal);
+ // Append new parts to our region.
+ layerm->slices.append(std::move(my_parts), stInternal);
+ }
+ }
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - stealing " << region_id << " end";
+ }
+ }
+
+ BOOST_LOG_TRIVIAL(debug) << "Slicing objects - removing top empty layers";
+ while (! m_layers.empty()) {
+ const Layer *layer = m_layers.back();
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id)
+ if (layer->m_regions[region_id] != nullptr && ! layer->m_regions[region_id]->slices.empty())
+ // Non empty layer.
+ goto end;
+ delete layer;
+ m_layers.pop_back();
+ if (! m_layers.empty())
+ m_layers.back()->upper_layer = nullptr;
+ }
+ m_print->throw_if_canceled();
+end:
+ ;
+
+ BOOST_LOG_TRIVIAL(debug) << "Slicing objects - make_slices in parallel - begin";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size()),
+ [this](const tbb::blocked_range<size_t>& range) {
+ for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
+ m_print->throw_if_canceled();
+ Layer *layer = m_layers[layer_id];
+ // Apply size compensation and perform clipping of multi-part objects.
+ float delta = float(scale_(m_config.xy_size_compensation.value));
+ if (layer_id == 0)
+ delta -= float(scale_(m_config.elefant_foot_compensation.value));
+ bool scale = delta != 0.f;
+ bool clip = m_config.clip_multipart_objects.value || delta > 0.f;
+ if (layer->m_regions.size() == 1) {
+ if (scale) {
+ // Single region, growing or shrinking.
+ LayerRegion *layerm = layer->m_regions.front();
+ layerm->slices.set(offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta), stInternal);
+ }
+ } else if (scale || clip) {
+ // Multiple regions, growing, shrinking or just clipping one region by the other.
+ // When clipping the regions, priority is given to the first regions.
+ Polygons processed;
+ for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) {
+ LayerRegion *layerm = layer->m_regions[region_id];
+ ExPolygons slices = to_expolygons(std::move(layerm->slices.surfaces));
+ if (scale)
+ slices = offset_ex(slices, delta);
+ if (region_id > 0 && clip)
+ // Trim by the slices of already processed regions.
+ slices = diff_ex(to_polygons(std::move(slices)), processed);
+ if (clip && region_id + 1 < layer->m_regions.size())
+ // Collect the already processed regions to trim the to be processed regions.
+ polygons_append(processed, slices);
+ layerm->slices.set(std::move(slices), stInternal);
+ }
+ }
+ // Merge all regions' slices to get islands, chain them by a shortest path.
+ layer->make_slices();
+ }
+ });
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Slicing objects - make_slices in parallel - end";
+}
+
+std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::vector<float> &z, bool modifier)
+{
+ std::vector<const ModelVolume*> volumes;
+ if (region_id < this->region_volumes.size()) {
+ for (int volume_id : this->region_volumes[region_id]) {
+ const ModelVolume *volume = this->model_object()->volumes[volume_id];
+ if (modifier ? volume->is_modifier() : volume->is_model_part())
+ volumes.emplace_back(volume);
+ }
+ }
+ return this->_slice_volumes(z, volumes);
+}
+
+std::vector<ExPolygons> PrintObject::slice_support_enforcers() const
+{
+ std::vector<const ModelVolume*> volumes;
+ for (const ModelVolume *volume : this->model_object()->volumes)
+ if (volume->is_support_enforcer())
+ volumes.emplace_back(volume);
+ std::vector<float> zs;
+ zs.reserve(this->layers().size());
+ for (const Layer *l : this->layers())
+ zs.emplace_back(l->slice_z);
+ return this->_slice_volumes(zs, volumes);
+}
+
+std::vector<ExPolygons> PrintObject::slice_support_blockers() const
+{
+ std::vector<const ModelVolume*> volumes;
+ for (const ModelVolume *volume : this->model_object()->volumes)
+ if (volume->is_support_blocker())
+ volumes.emplace_back(volume);
+ std::vector<float> zs;
+ zs.reserve(this->layers().size());
+ for (const Layer *l : this->layers())
+ zs.emplace_back(l->slice_z);
+ return this->_slice_volumes(zs, volumes);
+}
+
+std::vector<ExPolygons> PrintObject::_slice_volumes(const std::vector<float> &z, const std::vector<const ModelVolume*> &volumes) const
+{
+ std::vector<ExPolygons> layers;
+ if (! volumes.empty()) {
+ // Compose mesh.
+ //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them.
+ TriangleMesh mesh;
+ for (const ModelVolume *v : volumes)
+ mesh.merge(v->mesh);
+ if (mesh.stl.stats.number_of_facets > 0) {
+ // transform mesh
+ // we ignore the per-instance transformations currently and only
+ // consider the first one
+ this->model_object()->instances.front()->transform_mesh(&mesh, true);
+ // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift
+ mesh.translate(- unscale<float>(m_copies_shift(0)), - unscale<float>(m_copies_shift(1)), - float(this->model_object()->bounding_box().min(2)));
+ // perform actual slicing
+ TriangleMeshSlicer mslicer;
+ const Print *print = this->print();
+ auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();});
+ mslicer.init(&mesh, callback);
+ mslicer.slice(z, &layers, callback);
+ m_print->throw_if_canceled();
+ }
+ }
+ return layers;
+}
+
+std::string PrintObject::_fix_slicing_errors()
+{
+ // Collect layers with slicing errors.
+ // These layers will be fixed in parallel.
+ std::vector<size_t> buggy_layers;
+ buggy_layers.reserve(m_layers.size());
+ for (size_t idx_layer = 0; idx_layer < m_layers.size(); ++ idx_layer)
+ if (m_layers[idx_layer]->slicing_errors)
+ buggy_layers.push_back(idx_layer);
+
+ BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - begin";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, buggy_layers.size()),
+ [this, &buggy_layers](const tbb::blocked_range<size_t>& range) {
+ for (size_t buggy_layer_idx = range.begin(); buggy_layer_idx < range.end(); ++ buggy_layer_idx) {
+ m_print->throw_if_canceled();
+ size_t idx_layer = buggy_layers[buggy_layer_idx];
+ Layer *layer = m_layers[idx_layer];
+ assert(layer->slicing_errors);
+ // Try to repair the layer surfaces by merging all contours and all holes from neighbor layers.
+ // BOOST_LOG_TRIVIAL(trace) << "Attempting to repair layer" << idx_layer;
+ for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id) {
+ LayerRegion *layerm = layer->m_regions[region_id];
+ // Find the first valid layer below / above the current layer.
+ const Surfaces *upper_surfaces = nullptr;
+ const Surfaces *lower_surfaces = nullptr;
+ for (size_t j = idx_layer + 1; j < m_layers.size(); ++ j)
+ if (! m_layers[j]->slicing_errors) {
+ upper_surfaces = &m_layers[j]->regions()[region_id]->slices.surfaces;
+ break;
+ }
+ for (int j = int(idx_layer) - 1; j >= 0; -- j)
+ if (! m_layers[j]->slicing_errors) {
+ lower_surfaces = &m_layers[j]->regions()[region_id]->slices.surfaces;
+ break;
+ }
+ // Collect outer contours and holes from the valid layers above & below.
+ Polygons outer;
+ outer.reserve(
+ ((upper_surfaces == nullptr) ? 0 : upper_surfaces->size()) +
+ ((lower_surfaces == nullptr) ? 0 : lower_surfaces->size()));
+ size_t num_holes = 0;
+ if (upper_surfaces)
+ for (const auto &surface : *upper_surfaces) {
+ outer.push_back(surface.expolygon.contour);
+ num_holes += surface.expolygon.holes.size();
+ }
+ if (lower_surfaces)
+ for (const auto &surface : *lower_surfaces) {
+ outer.push_back(surface.expolygon.contour);
+ num_holes += surface.expolygon.holes.size();
+ }
+ Polygons holes;
+ holes.reserve(num_holes);
+ if (upper_surfaces)
+ for (const auto &surface : *upper_surfaces)
+ polygons_append(holes, surface.expolygon.holes);
+ if (lower_surfaces)
+ for (const auto &surface : *lower_surfaces)
+ polygons_append(holes, surface.expolygon.holes);
+ layerm->slices.set(diff_ex(union_(outer), holes, false), stInternal);
+ }
+ // Update layer slices after repairing the single regions.
+ layer->make_slices();
+ }
+ });
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - end";
+
+ // remove empty layers from bottom
+ while (! m_layers.empty() && m_layers.front()->slices.expolygons.empty()) {
+ delete m_layers.front();
+ m_layers.erase(m_layers.begin());
+ m_layers.front()->lower_layer = nullptr;
+ for (size_t i = 0; i < m_layers.size(); ++ i)
+ m_layers[i]->set_id(m_layers[i]->id() - 1);
+ }
+
+ return buggy_layers.empty() ? "" :
+ "The model has overlapping or self-intersecting facets. I tried to repair it, "
+ "however you might want to check the results or repair the input file and retry.\n";
+}
+
+// Simplify the sliced model, if "resolution" configuration parameter > 0.
+// The simplification is problematic, because it simplifies the slices independent from each other,
+// which makes the simplified discretization visible on the object surface.
+void PrintObject::_simplify_slices(double distance)
+{
+ BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - begin";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size()),
+ [this, distance](const tbb::blocked_range<size_t>& range) {
+ for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
+ m_print->throw_if_canceled();
+ Layer *layer = m_layers[layer_idx];
+ for (size_t region_idx = 0; region_idx < layer->m_regions.size(); ++ region_idx)
+ layer->m_regions[region_idx]->slices.simplify(distance);
+ layer->slices.simplify(distance);
+ }
+ });
+ BOOST_LOG_TRIVIAL(debug) << "Slicing objects - siplifying slices in parallel - end";
+}
+
+void PrintObject::_make_perimeters()
+{
+ if (!this->is_printable())
+ return;
+
+ if (this->is_step_done(posPerimeters))
+ return;
+ this->set_started(posPerimeters);
+
+ BOOST_LOG_TRIVIAL(info) << "Generating perimeters...";
+
+ // merge slices if they were split into types
+ if (this->typed_slices) {
+ FOREACH_LAYER(this, layer_it)
+ (*layer_it)->merge_slices();
+ this->typed_slices = false;
+ this->invalidate_step(posPrepareInfill);
+ }
+
+ // compare each layer to the one below, and mark those slices needing
+ // one additional inner perimeter, like the top of domed objects-
+
+ // this algorithm makes sure that at least one perimeter is overlapping
+ // but we don't generate any extra perimeter if fill density is zero, as they would be floating
+ // inside the object - infill_only_where_needed should be the method of choice for printing
+ // hollow objects
+ for (size_t region_id = 0; region_id < m_print->regions().size(); ++ region_id) {
+ const PrintRegion &region = *m_print->regions()[region_id];
+ if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2)
+ continue;
+
+ BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size() - 1),
+ [this, &region, region_id](const tbb::blocked_range<size_t>& range) {
+ for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
+ LayerRegion &layerm = *m_layers[layer_idx]->regions()[region_id];
+ const LayerRegion &upper_layerm = *m_layers[layer_idx+1]->regions()[region_id];
+ const Polygons upper_layerm_polygons = upper_layerm.slices;
+ // Filter upper layer polygons in intersection_ppl by their bounding boxes?
+ // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ];
+ const double total_loop_length = total_length(upper_layerm_polygons);
+ const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing();
+ const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter);
+ const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width();
+ const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing();
+
+ for (Surface &slice : layerm.slices.surfaces) {
+ for (;;) {
+ // compute the total thickness of perimeters
+ const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2
+ + (region.config().perimeters-1 + slice.extra_perimeters) * perimeter_spacing;
+ // define a critical area where we don't want the upper slice to fall into
+ // (it should either lay over our perimeters or outside this area)
+ const coord_t critical_area_depth = coord_t(perimeter_spacing * 1.5);
+ const Polygons critical_area = diff(
+ offset(slice.expolygon, float(- perimeters_thickness)),
+ offset(slice.expolygon, float(- perimeters_thickness - critical_area_depth))
+ );
+ // check whether a portion of the upper slices falls inside the critical area
+ const Polylines intersection = intersection_pl(to_polylines(upper_layerm_polygons), critical_area);
+ // only add an additional loop if at least 30% of the slice loop would benefit from it
+ if (total_length(intersection) <= total_loop_length*0.3)
+ break;
+ /*
+ if (0) {
+ require "Slic3r/SVG.pm";
+ Slic3r::SVG::output(
+ "extra.svg",
+ no_arrows => 1,
+ expolygons => union_ex($critical_area),
+ polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ],
+ );
+ }
+ */
+ ++ slice.extra_perimeters;
+ }
+ #ifdef DEBUG
+ if (slice.extra_perimeters > 0)
+ printf(" adding %d more perimeter(s) at layer %zu\n", slice.extra_perimeters, layer_idx);
+ #endif
+ }
+ }
+ });
+ BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - end";
+ }
+
+ BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - start";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, m_layers.size()),
+ [this](const tbb::blocked_range<size_t>& range) {
+ for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx)
+ m_layers[layer_idx]->make_perimeters();
+ }
+ );
+ BOOST_LOG_TRIVIAL(debug) << "Generating perimeters in parallel - end";
+
+ /*
+ simplify slices (both layer and region slices),
+ we only need the max resolution for perimeters
+ ### This makes this method not-idempotent, so we keep it disabled for now.
+ ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION);
+ */
+
+ this->set_done(posPerimeters);
+}
+
+// Only active if config->infill_only_where_needed. This step trims the sparse infill,
+// so it acts as an internal support. It maintains all other infill types intact.
+// Here the internal surfaces and perimeters have to be supported by the sparse infill.
+//FIXME The surfaces are supported by a sparse infill, but the sparse infill is only as large as the area to support.
+// Likely the sparse infill will not be anchored correctly, so it will not work as intended.
+// Also one wishes the perimeters to be supported by a full infill.
+// Idempotence of this method is guaranteed by the fact that we don't remove things from
+// fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries.
+void PrintObject::clip_fill_surfaces()
+{
+ if (! m_config.infill_only_where_needed.value ||
+ ! std::any_of(this->print()->regions().begin(), this->print()->regions().end(),
+ [](const PrintRegion *region) { return region->config().fill_density > 0; }))
+ return;
+
+ // We only want infill under ceilings; this is almost like an
+ // internal support material.
+ // Proceed top-down, skipping the bottom layer.
+ Polygons upper_internal;
+ for (int layer_id = int(m_layers.size()) - 1; layer_id > 0; -- layer_id) {
+ Layer *layer = m_layers[layer_id];
+ Layer *lower_layer = m_layers[layer_id - 1];
+ // Detect things that we need to support.
+ // Cummulative slices.
+ Polygons slices;
+ for (const ExPolygon &expoly : layer->slices.expolygons)
+ polygons_append(slices, to_polygons(expoly));
+ // Cummulative fill surfaces.
+ Polygons fill_surfaces;
+ // Solid surfaces to be supported.
+ Polygons overhangs;
+ for (const LayerRegion *layerm : layer->m_regions)
+ for (const Surface &surface : layerm->fill_surfaces.surfaces) {
+ Polygons polygons = to_polygons(surface.expolygon);
+ if (surface.is_solid())
+ polygons_append(overhangs, polygons);
+ polygons_append(fill_surfaces, std::move(polygons));
+ }
+ Polygons lower_layer_fill_surfaces;
+ Polygons lower_layer_internal_surfaces;
+ for (const LayerRegion *layerm : lower_layer->m_regions)
+ for (const Surface &surface : layerm->fill_surfaces.surfaces) {
+ Polygons polygons = to_polygons(surface.expolygon);
+ if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
+ polygons_append(lower_layer_internal_surfaces, polygons);
+ polygons_append(lower_layer_fill_surfaces, std::move(polygons));
+ }
+ // We also need to support perimeters when there's at least one full unsupported loop
+ {
+ // Get perimeters area as the difference between slices and fill_surfaces
+ // Only consider the area that is not supported by lower perimeters
+ Polygons perimeters = intersection(diff(slices, fill_surfaces), lower_layer_fill_surfaces);
+ // Only consider perimeter areas that are at least one extrusion width thick.
+ //FIXME Offset2 eats out from both sides, while the perimeters are create outside in.
+ //Should the pw not be half of the current value?
+ float pw = FLT_MAX;
+ for (const LayerRegion *layerm : layer->m_regions)
+ pw = std::min<float>(pw, layerm->flow(frPerimeter).scaled_width());
+ // Append such thick perimeters to the areas that need support
+ polygons_append(overhangs, offset2(perimeters, -pw, +pw));
+ }
+ // Find new internal infill.
+ polygons_append(overhangs, std::move(upper_internal));
+ upper_internal = intersection(overhangs, lower_layer_internal_surfaces);
+ // Apply new internal infill to regions.
+ for (LayerRegion *layerm : lower_layer->m_regions) {
+ if (layerm->region()->config().fill_density.value == 0)
+ continue;
+ SurfaceType internal_surface_types[] = { stInternal, stInternalVoid };
+ Polygons internal;
+ for (Surface &surface : layerm->fill_surfaces.surfaces)
+ if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
+ polygons_append(internal, std::move(surface.expolygon));
+ layerm->fill_surfaces.remove_types(internal_surface_types, 2);
+ layerm->fill_surfaces.append(intersection_ex(internal, upper_internal, true), stInternal);
+ layerm->fill_surfaces.append(diff_ex (internal, upper_internal, true), stInternalVoid);
+ // If there are voids it means that our internal infill is not adjacent to
+ // perimeters. In this case it would be nice to add a loop around infill to
+ // make it more robust and nicer. TODO.
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ layerm->export_region_fill_surfaces_to_svg_debug("6_clip_fill_surfaces");
+#endif
+ }
+ m_print->throw_if_canceled();
+ }
+}
+
+void PrintObject::discover_horizontal_shells()
+{
+ BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()";
+
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
+ for (int i = 0; i < int(m_layers.size()); ++ i) {
+ m_print->throw_if_canceled();
+ LayerRegion *layerm = m_layers[i]->regions()[region_id];
+ const PrintRegionConfig &region_config = layerm->region()->config();
+ if (region_config.solid_infill_every_layers.value > 0 && region_config.fill_density.value > 0 &&
+ (i % region_config.solid_infill_every_layers) == 0) {
+ // Insert a solid internal layer. Mark stInternal surfaces as stInternalSolid or stInternalBridge.
+ SurfaceType type = (region_config.fill_density == 100) ? stInternalSolid : stInternalBridge;
+ for (Surface &surface : layerm->fill_surfaces.surfaces)
+ if (surface.surface_type == stInternal)
+ surface.surface_type = type;
+ }
+
+ // If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells().
+ if (region_config.ensure_vertical_shell_thickness.value)
+ continue;
+
+ for (int idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) {
+ m_print->throw_if_canceled();
+ SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge;
+ // Find slices of current type for current layer.
+ // Use slices instead of fill_surfaces, because they also include the perimeter area,
+ // which needs to be propagated in shells; we need to grow slices like we did for
+ // fill_surfaces though. Using both ungrown slices and grown fill_surfaces will
+ // not work in some situations, as there won't be any grown region in the perimeter
+ // area (this was seen in a model where the top layer had one extra perimeter, thus
+ // its fill_surfaces were thinner than the lower layer's infill), however it's the best
+ // solution so far. Growing the external slices by EXTERNAL_INFILL_MARGIN will put
+ // too much solid infill inside nearly-vertical slopes.
+
+ // Surfaces including the area of perimeters. Everything, that is visible from the top / bottom
+ // (not covered by a layer above / below).
+ // This does not contain the areas covered by perimeters!
+ Polygons solid;
+ for (const Surface &surface : layerm->slices.surfaces)
+ if (surface.surface_type == type)
+ polygons_append(solid, to_polygons(surface.expolygon));
+ // Infill areas (slices without the perimeters).
+ for (const Surface &surface : layerm->fill_surfaces.surfaces)
+ if (surface.surface_type == type)
+ polygons_append(solid, to_polygons(surface.expolygon));
+ if (solid.empty())
+ continue;
+// Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom';
+
+ size_t solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value;
+ for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - i) < solid_layers; (type == stTop) ? -- n : ++ n) {
+ if (n < 0 || n >= int(m_layers.size()))
+ continue;
+// Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
+ // Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface.
+ LayerRegion *neighbor_layerm = m_layers[n]->regions()[region_id];
+
+ // find intersection between neighbor and current layer's surfaces
+ // intersections have contours and holes
+ // we update $solid so that we limit the next neighbor layer to the areas that were
+ // found on this one - in other words, solid shells on one layer (for a given external surface)
+ // are always a subset of the shells found on the previous shell layer
+ // this approach allows for DWIM in hollow sloping vases, where we want bottom
+ // shells to be generated in the base but not in the walls (where there are many
+ // narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the
+ // upper perimeter as an obstacle and shell will not be propagated to more upper layers
+ //FIXME How does it work for S_TYPE_INTERNALBRIDGE? This is set for sparse infill. Likely this does not work.
+ Polygons new_internal_solid;
+ {
+ Polygons internal;
+ for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces)
+ if (surface.surface_type == stInternal || surface.surface_type == stInternalSolid)
+ polygons_append(internal, to_polygons(surface.expolygon));
+ new_internal_solid = intersection(solid, internal, true);
+ }
+ if (new_internal_solid.empty()) {
+ // No internal solid needed on this layer. In order to decide whether to continue
+ // searching on the next neighbor (thus enforcing the configured number of solid
+ // layers, use different strategies according to configured infill density:
+ if (region_config.fill_density.value == 0) {
+ // If user expects the object to be void (for example a hollow sloping vase),
+ // don't continue the search. In this case, we only generate the external solid
+ // shell if the object would otherwise show a hole (gap between perimeters of
+ // the two layers), and internal solid shells are a subset of the shells found
+ // on each previous layer.
+ goto EXTERNAL;
+ } else {
+ // If we have internal infill, we can generate internal solid shells freely.
+ continue;
+ }
+ }
+
+ if (region_config.fill_density.value == 0) {
+ // if we're printing a hollow object we discard any solid shell thinner
+ // than a perimeter width, since it's probably just crossing a sloping wall
+ // and it's not wanted in a hollow print even if it would make sense when
+ // obeying the solid shell count option strictly (DWIM!)
+ float margin = float(neighbor_layerm->flow(frExternalPerimeter).scaled_width());
+ Polygons too_narrow = diff(
+ new_internal_solid,
+ offset2(new_internal_solid, -margin, +margin, jtMiter, 5),
+ true);
+ // Trim the regularized region by the original region.
+ if (! too_narrow.empty())
+ new_internal_solid = solid = diff(new_internal_solid, too_narrow);
+ }
+
+ // make sure the new internal solid is wide enough, as it might get collapsed
+ // when spacing is added in Fill.pm
+ {
+ //FIXME Vojtech: Disable this and you will be sorry.
+ // https://github.com/prusa3d/Slic3r/issues/26 bottom
+ float margin = 3.f * layerm->flow(frSolidInfill).scaled_width(); // require at least this size
+ // we use a higher miterLimit here to handle areas with acute angles
+ // in those cases, the default miterLimit would cut the corner and we'd
+ // get a triangle in $too_narrow; if we grow it below then the shell
+ // would have a different shape from the external surface and we'd still
+ // have the same angle, so the next shell would be grown even more and so on.
+ Polygons too_narrow = diff(
+ new_internal_solid,
+ offset2(new_internal_solid, -margin, +margin, ClipperLib::jtMiter, 5),
+ true);
+ if (! too_narrow.empty()) {
+ // grow the collapsing parts and add the extra area to the neighbor layer
+ // as well as to our original surfaces so that we support this
+ // additional area in the next shell too
+ // make sure our grown surfaces don't exceed the fill area
+ Polygons internal;
+ for (const Surface &surface : neighbor_layerm->fill_surfaces.surfaces)
+ if (surface.is_internal() && !surface.is_bridge())
+ polygons_append(internal, to_polygons(surface.expolygon));
+ polygons_append(new_internal_solid,
+ intersection(
+ offset(too_narrow, +margin),
+ // Discard bridges as they are grown for anchoring and we can't
+ // remove such anchors. (This may happen when a bridge is being
+ // anchored onto a wall where little space remains after the bridge
+ // is grown, and that little space is an internal solid shell so
+ // it triggers this too_narrow logic.)
+ internal));
+ solid = new_internal_solid;
+ }
+ }
+
+ // internal-solid are the union of the existing internal-solid surfaces
+ // and new ones
+ SurfaceCollection backup = std::move(neighbor_layerm->fill_surfaces);
+ polygons_append(new_internal_solid, to_polygons(backup.filter_by_type(stInternalSolid)));
+ ExPolygons internal_solid = union_ex(new_internal_solid, false);
+ // assign new internal-solid surfaces to layer
+ neighbor_layerm->fill_surfaces.set(internal_solid, stInternalSolid);
+ // subtract intersections from layer surfaces to get resulting internal surfaces
+ Polygons polygons_internal = to_polygons(std::move(internal_solid));
+ ExPolygons internal = diff_ex(
+ to_polygons(backup.filter_by_type(stInternal)),
+ polygons_internal,
+ true);
+ // assign resulting internal surfaces to layer
+ neighbor_layerm->fill_surfaces.append(internal, stInternal);
+ polygons_append(polygons_internal, to_polygons(std::move(internal)));
+ // assign top and bottom surfaces to layer
+ SurfaceType surface_types_solid[] = { stTop, stBottom, stBottomBridge };
+ backup.keep_types(surface_types_solid, 3);
+ std::vector<SurfacesPtr> top_bottom_groups;
+ backup.group(&top_bottom_groups);
+ for (SurfacesPtr &group : top_bottom_groups)
+ neighbor_layerm->fill_surfaces.append(
+ diff_ex(to_polygons(group), polygons_internal),
+ // Use an existing surface as a template, it carries the bridge angle etc.
+ *group.front());
+ }
+ EXTERNAL:;
+ } // foreach type (stTop, stBottom, stBottomBridge)
+ } // for each layer
+ } // for each region
+
+#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
+ for (const Layer *layer : m_layers) {
+ const LayerRegion *layerm = layer->m_regions[region_id];
+ layerm->export_region_slices_to_svg_debug("5_discover_horizontal_shells");
+ layerm->export_region_fill_surfaces_to_svg_debug("5_discover_horizontal_shells");
+ } // for each layer
+ } // for each region
+#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
+}
+
+// combine fill surfaces across layers to honor the "infill every N layers" option
+// Idempotence of this method is guaranteed by the fact that we don't remove things from
+// fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries.
+void PrintObject::combine_infill()
+{
+ // Work on each region separately.
+ for (size_t region_id = 0; region_id < this->print()->regions().size(); ++ region_id) {
+ const PrintRegion *region = this->print()->regions()[region_id];
+ const int every = region->config().infill_every_layers.value;
+ if (every < 2 || region->config().fill_density == 0.)
+ continue;
+ // Limit the number of combined layers to the maximum height allowed by this regions' nozzle.
+ //FIXME limit the layer height to max_layer_height
+ double nozzle_diameter = std::min(
+ this->print()->config().nozzle_diameter.get_at(region->config().infill_extruder.value - 1),
+ this->print()->config().nozzle_diameter.get_at(region->config().solid_infill_extruder.value - 1));
+ // define the combinations
+ std::vector<size_t> combine(m_layers.size(), 0);
+ {
+ double current_height = 0.;
+ size_t num_layers = 0;
+ for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) {
+ m_print->throw_if_canceled();
+ const Layer *layer = m_layers[layer_idx];
+ if (layer->id() == 0)
+ // Skip first print layer (which may not be first layer in array because of raft).
+ continue;
+ // Check whether the combination of this layer with the lower layers' buffer
+ // would exceed max layer height or max combined layer count.
+ if (current_height + layer->height >= nozzle_diameter + EPSILON || num_layers >= every) {
+ // Append combination to lower layer.
+ combine[layer_idx - 1] = num_layers;
+ current_height = 0.;
+ num_layers = 0;
+ }
+ current_height += layer->height;
+ ++ num_layers;
+ }
+
+ // Append lower layers (if any) to uppermost layer.
+ combine[m_layers.size() - 1] = num_layers;
+ }
+
+ // loop through layers to which we have assigned layers to combine
+ for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) {
+ m_print->throw_if_canceled();
+ size_t num_layers = combine[layer_idx];
+ if (num_layers <= 1)
+ continue;
+ // Get all the LayerRegion objects to be combined.
+ std::vector<LayerRegion*> layerms;
+ layerms.reserve(num_layers);
+ for (size_t i = layer_idx + 1 - num_layers; i <= layer_idx; ++ i)
+ layerms.emplace_back(m_layers[i]->regions()[region_id]);
+ // We need to perform a multi-layer intersection, so let's split it in pairs.
+ // Initialize the intersection with the candidates of the lowest layer.
+ ExPolygons intersection = to_expolygons(layerms.front()->fill_surfaces.filter_by_type(stInternal));
+ // Start looping from the second layer and intersect the current intersection with it.
+ for (size_t i = 1; i < layerms.size(); ++ i)
+ intersection = intersection_ex(
+ to_polygons(intersection),
+ to_polygons(layerms[i]->fill_surfaces.filter_by_type(stInternal)),
+ false);
+ double area_threshold = layerms.front()->infill_area_threshold();
+ if (! intersection.empty() && area_threshold > 0.)
+ intersection.erase(std::remove_if(intersection.begin(), intersection.end(),
+ [area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }),
+ intersection.end());
+ if (intersection.empty())
+ continue;
+// Slic3r::debugf " combining %d %s regions from layers %d-%d\n",
+// scalar(@$intersection),
+// ($type == S_TYPE_INTERNAL ? 'internal' : 'internal-solid'),
+// $layer_idx-($every-1), $layer_idx;
+ // intersection now contains the regions that can be combined across the full amount of layers,
+ // so let's remove those areas from all layers.
+ Polygons intersection_with_clearance;
+ intersection_with_clearance.reserve(intersection.size());
+ float clearance_offset =
+ 0.5f * layerms.back()->flow(frPerimeter).scaled_width() +
+ // Because fill areas for rectilinear and honeycomb are grown
+ // later to overlap perimeters, we need to counteract that too.
+ ((region->config().fill_pattern == ipRectilinear ||
+ region->config().fill_pattern == ipGrid ||
+ region->config().fill_pattern == ipLine ||
+ region->config().fill_pattern == ipHoneycomb) ? 1.5f : 0.5f) *
+ layerms.back()->flow(frSolidInfill).scaled_width();
+ for (ExPolygon &expoly : intersection)
+ polygons_append(intersection_with_clearance, offset(expoly, clearance_offset));
+ for (LayerRegion *layerm : layerms) {
+ Polygons internal = to_polygons(layerm->fill_surfaces.filter_by_type(stInternal));
+ layerm->fill_surfaces.remove_type(stInternal);
+ layerm->fill_surfaces.append(diff_ex(internal, intersection_with_clearance, false), stInternal);
+ if (layerm == layerms.back()) {
+ // Apply surfaces back with adjusted depth to the uppermost layer.
+ Surface templ(stInternal, ExPolygon());
+ templ.thickness = 0.;
+ for (LayerRegion *layerm2 : layerms)
+ templ.thickness += layerm2->layer()->height;
+ templ.thickness_layers = (unsigned short)layerms.size();
+ layerm->fill_surfaces.append(intersection, templ);
+ } else {
+ // Save void surfaces.
+ layerm->fill_surfaces.append(
+ intersection_ex(internal, intersection_with_clearance, false),
+ stInternalVoid);
+ }
+ }
+ }
+ }
+}
+
+void PrintObject::_generate_support_material()
+{
+ if (!this->is_printable())
+ return;
+
+ PrintObjectSupportMaterial support_material(this, PrintObject::slicing_parameters());
+ support_material.generate(*this);
+}
+
+void PrintObject::reset_layer_height_profile()
+{
+ // Reset the layer_heigth_profile.
+ this->layer_height_profile.clear();
+ this->layer_height_profile_valid = false;
+ // Reset the source layer_height_profile if it exists at the ModelObject.
+ this->model_object()->layer_height_profile.clear();
+ this->model_object()->layer_height_profile_valid = false;
+}
+
+void PrintObject::adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action)
+{
+ update_layer_height_profile(m_model_object->layer_height_profile);
+ Slic3r::adjust_layer_height_profile(slicing_parameters(), m_model_object->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action));
+ m_model_object->layer_height_profile_valid = true;
+ layer_height_profile_valid = false;
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp
new file mode 100644
index 000000000..4ea777b4a
--- /dev/null
+++ b/src/libslic3r/PrintRegion.cpp
@@ -0,0 +1,64 @@
+#include "Print.hpp"
+
+namespace Slic3r {
+
+Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const
+{
+ ConfigOptionFloatOrPercent config_width;
+ if (width != -1) {
+ // use the supplied custom width, if any
+ config_width.value = width;
+ config_width.percent = false;
+ } else {
+ // otherwise, get extrusion width from configuration
+ // (might be an absolute value, or a percent value, or zero for auto)
+ if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) {
+ config_width = m_print->config().first_layer_extrusion_width;
+ } else if (role == frExternalPerimeter) {
+ config_width = m_config.external_perimeter_extrusion_width;
+ } else if (role == frPerimeter) {
+ config_width = m_config.perimeter_extrusion_width;
+ } else if (role == frInfill) {
+ config_width = m_config.infill_extrusion_width;
+ } else if (role == frSolidInfill) {
+ config_width = m_config.solid_infill_extrusion_width;
+ } else if (role == frTopSolidInfill) {
+ config_width = m_config.top_infill_extrusion_width;
+ } else {
+ throw std::invalid_argument("Unknown role");
+ }
+ }
+ if (config_width.value == 0) {
+ config_width = object.config().extrusion_width;
+ }
+
+ // get the configured nozzle_diameter for the extruder associated
+ // to the flow role requested
+ size_t extruder = 0; // 1-based
+ if (role == frPerimeter || role == frExternalPerimeter) {
+ extruder = m_config.perimeter_extruder;
+ } else if (role == frInfill) {
+ extruder = m_config.infill_extruder;
+ } else if (role == frSolidInfill || role == frTopSolidInfill) {
+ extruder = m_config.solid_infill_extruder;
+ } else {
+ throw std::invalid_argument("Unknown role");
+ }
+ double nozzle_diameter = m_print->config().nozzle_diameter.get_at(extruder-1);
+
+ return Flow::new_from_config_width(role, config_width, nozzle_diameter, layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0);
+}
+
+coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const
+{
+ return (print_config.nozzle_diameter.get_at(m_config.perimeter_extruder.value - 1) +
+ print_config.nozzle_diameter.get_at(m_config.infill_extruder.value - 1) +
+ print_config.nozzle_diameter.get_at(m_config.solid_infill_extruder.value - 1)) / 3.;
+}
+
+coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const
+{
+ return this->nozzle_dmr_avg(print_config) * sqrt(m_config.bridge_flow_ratio.value);
+}
+
+}
diff --git a/src/libslic3r/Rasterizer/Rasterizer.cpp b/src/libslic3r/Rasterizer/Rasterizer.cpp
new file mode 100644
index 000000000..b0bf04343
--- /dev/null
+++ b/src/libslic3r/Rasterizer/Rasterizer.cpp
@@ -0,0 +1,214 @@
+#include "Rasterizer.hpp"
+#include <ExPolygon.hpp>
+
+#include <cstdint>
+
+// For rasterizing
+#include <agg/agg_basics.h>
+#include <agg/agg_rendering_buffer.h>
+#include <agg/agg_pixfmt_gray.h>
+#include <agg/agg_pixfmt_rgb.h>
+#include <agg/agg_renderer_base.h>
+#include <agg/agg_renderer_scanline.h>
+
+#include <agg/agg_scanline_p.h>
+#include <agg/agg_rasterizer_scanline_aa.h>
+#include <agg/agg_path_storage.h>
+
+// For png compression
+#include <png/writer.hpp>
+
+namespace Slic3r {
+
+class Raster::Impl {
+public:
+ using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24;
+ using TRawRenderer = agg::renderer_base<TPixelRenderer>;
+ using TPixel = TPixelRenderer::color_type;
+ using TRawBuffer = agg::rendering_buffer;
+
+ using TBuffer = std::vector<TPixelRenderer::pixel_type>;
+
+ using TRendererAA = agg::renderer_scanline_aa_solid<TRawRenderer>;
+
+ static const TPixel ColorWhite;
+ static const TPixel ColorBlack;
+
+ using Origin = Raster::Origin;
+
+private:
+ Raster::Resolution resolution_;
+ Raster::PixelDim pxdim_;
+ TBuffer buf_;
+ TRawBuffer rbuf_;
+ TPixelRenderer pixfmt_;
+ TRawRenderer raw_renderer_;
+ TRendererAA renderer_;
+ Origin o_;
+ std::function<void(agg::path_storage&)> flipy_ = [](agg::path_storage&) {};
+public:
+ inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd,
+ Origin o):
+ resolution_(res), pxdim_(pd),
+ buf_(res.pixels()),
+ rbuf_(reinterpret_cast<TPixelRenderer::value_type*>(buf_.data()),
+ res.width_px, res.height_px,
+ res.width_px*TPixelRenderer::num_components),
+ pixfmt_(rbuf_),
+ raw_renderer_(pixfmt_),
+ renderer_(raw_renderer_),
+ o_(o)
+ {
+ renderer_.color(ColorWhite);
+
+ // If we would like to play around with gamma
+ // ras.gamma(agg::gamma_power(1.0));
+
+ clear();
+
+ if(o_ == Origin::TOP_LEFT) flipy_ = [this](agg::path_storage& path) {
+ path.flip_y(0, resolution_.height_px);
+ };
+ }
+
+ void draw(const ExPolygon &poly) {
+ agg::rasterizer_scanline_aa<> ras;
+ agg::scanline_p8 scanlines;
+
+ auto&& path = to_path(poly.contour);
+ flipy_(path);
+ ras.add_path(path);
+
+ for(auto h : poly.holes) {
+ auto&& holepath = to_path(h);
+ flipy_(holepath);
+ ras.add_path(holepath);
+ }
+
+ agg::render_scanlines(ras, scanlines, renderer_);
+ }
+
+ inline void clear() {
+ raw_renderer_.clear(ColorBlack);
+ }
+
+ inline TBuffer& buffer() { return buf_; }
+
+ inline const Raster::Resolution resolution() { return resolution_; }
+
+ inline Origin origin() const /*noexcept*/ { return o_; }
+
+private:
+ double getPx(const Point& p) {
+ return p(0) * SCALING_FACTOR/pxdim_.w_mm;
+ }
+
+ double getPy(const Point& p) {
+ return p(1) * SCALING_FACTOR/pxdim_.h_mm;
+ }
+
+ agg::path_storage to_path(const Polygon& poly) {
+ agg::path_storage path;
+ auto it = poly.points.begin();
+ path.move_to(getPx(*it), getPy(*it));
+ while(++it != poly.points.end())
+ path.line_to(getPx(*it), getPy(*it));
+
+ path.line_to(getPx(poly.points.front()), getPy(poly.points.front()));
+ return path;
+ }
+
+};
+
+const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255);
+const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0);
+
+Raster::Raster(const Resolution &r, const PixelDim &pd, Origin o):
+ impl_(new Impl(r, pd, o)) {}
+
+Raster::Raster() {}
+
+Raster::~Raster() {}
+
+Raster::Raster(Raster &&m):
+ impl_(std::move(m.impl_)) {}
+
+void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd)
+{
+ // Free up the unnecessary memory and make sure it stays clear after
+ // an exception
+ auto o = impl_? impl_->origin() : Origin::TOP_LEFT;
+ reset(r, pd, o);
+}
+
+void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd,
+ Raster::Origin o)
+{
+ impl_.reset();
+ impl_.reset(new Impl(r, pd, o));
+}
+
+void Raster::reset()
+{
+ impl_.reset();
+}
+
+Raster::Resolution Raster::resolution() const
+{
+ if(impl_) return impl_->resolution();
+
+ return Resolution(0, 0);
+}
+
+void Raster::clear()
+{
+ assert(impl_);
+ impl_->clear();
+}
+
+void Raster::draw(const ExPolygon &poly)
+{
+ assert(impl_);
+ impl_->draw(poly);
+}
+
+void Raster::save(std::ostream& stream, Compression comp)
+{
+ assert(impl_);
+ switch(comp) {
+ case Compression::PNG: {
+
+ png::writer<std::ostream> wr(stream);
+
+ wr.set_bit_depth(8);
+ wr.set_color_type(png::color_type_gray);
+ wr.set_width(resolution().width_px);
+ wr.set_height(resolution().height_px);
+ wr.set_compression_type(png::compression_type_default);
+
+ wr.write_info();
+
+ auto& b = impl_->buffer();
+ auto ptr = reinterpret_cast<png::byte*>( b.data() );
+ unsigned stride =
+ sizeof(Impl::TBuffer::value_type) * resolution().width_px;
+
+ for(unsigned r = 0; r < resolution().height_px; r++, ptr+=stride) {
+ wr.write_row(ptr);
+ }
+
+ break;
+ }
+ case Compression::RAW: {
+ stream << "P5 "
+ << impl_->resolution().width_px << " "
+ << impl_->resolution().height_px << " "
+ << "255 ";
+
+ stream.write(reinterpret_cast<const char*>(impl_->buffer().data()),
+ impl_->buffer().size()*sizeof(Impl::TBuffer::value_type));
+ }
+ }
+}
+
+}
diff --git a/src/libslic3r/Rasterizer/Rasterizer.hpp b/src/libslic3r/Rasterizer/Rasterizer.hpp
new file mode 100644
index 000000000..cbb39bc6b
--- /dev/null
+++ b/src/libslic3r/Rasterizer/Rasterizer.hpp
@@ -0,0 +1,86 @@
+#ifndef RASTERIZER_HPP
+#define RASTERIZER_HPP
+
+#include <ostream>
+#include <memory>
+
+namespace Slic3r {
+
+class ExPolygon;
+
+/**
+ * @brief Raster captures an anti-aliased monochrome canvas where vectorial
+ * polygons can be rasterized. Fill color is always white and the background is
+ * black. Contours are anti-aliased.
+ *
+ * It also supports saving the raster data into a standard output stream in raw
+ * or PNG format.
+ */
+class Raster {
+ class Impl;
+ std::unique_ptr<Impl> impl_;
+public:
+
+ /// Supported compression types
+ enum class Compression {
+ RAW, //!> Uncompressed pixel data
+ PNG //!> PNG compression
+ };
+
+ enum class Origin {
+ TOP_LEFT,
+ BOTTOM_LEFT
+ };
+
+ /// Type that represents a resolution in pixels.
+ struct Resolution {
+ unsigned width_px;
+ unsigned height_px;
+ inline Resolution(unsigned w, unsigned h): width_px(w), height_px(h) {}
+ inline unsigned pixels() const /*noexcept*/ {
+ return width_px * height_px;
+ }
+ };
+
+ /// Types that represents the dimension of a pixel in millimeters.
+ struct PixelDim {
+ double w_mm;
+ double h_mm;
+ inline PixelDim(double px_width_mm, double px_height_mm ):
+ w_mm(px_width_mm), h_mm(px_height_mm) {}
+ };
+
+ /// Constructor taking the resolution and the pixel dimension.
+ explicit Raster(const Resolution& r, const PixelDim& pd,
+ Origin o = Origin::BOTTOM_LEFT );
+ Raster();
+ Raster(const Raster& cpy) = delete;
+ Raster& operator=(const Raster& cpy) = delete;
+ Raster(Raster&& m);
+ ~Raster();
+
+ /// Reallocated everything for the given resolution and pixel dimension.
+ void reset(const Resolution& r, const PixelDim& pd);
+ void reset(const Resolution& r, const PixelDim& pd, Origin o);
+
+ /**
+ * Release the allocated resources. Drawing in this state ends in
+ * unspecified behaviour.
+ */
+ void reset();
+
+ /// Get the resolution of the raster.
+ Resolution resolution() const;
+
+ /// Clear the raster with black color.
+ void clear();
+
+ /// Draw a polygon with holes.
+ void draw(const ExPolygon& poly);
+
+ /// Save the raster on the specified stream.
+ void save(std::ostream& stream, Compression comp = Compression::RAW);
+};
+
+}
+#endif // RASTERIZER_HPP
diff --git a/src/libslic3r/SLABasePool.cpp b/src/libslic3r/SLABasePool.cpp
new file mode 100644
index 000000000..f3683865c
--- /dev/null
+++ b/src/libslic3r/SLABasePool.cpp
@@ -0,0 +1,531 @@
+#include <functional>
+#include <numeric>
+
+#include "SLABasePool.hpp"
+#include "ExPolygon.hpp"
+#include "TriangleMesh.hpp"
+#include "ClipperUtils.hpp"
+#include "boost/log/trivial.hpp"
+
+//#include "SVG.hpp"
+
+namespace Slic3r { namespace sla {
+
+namespace {
+
+using coord_t = Point::coord_type;
+
+/// get the scaled clipper units for a millimeter value
+inline coord_t mm(double v) { return coord_t(v/SCALING_FACTOR); }
+
+/// Get x and y coordinates (because we are eigenizing...)
+inline coord_t x(const Point& p) { return p(0); }
+inline coord_t y(const Point& p) { return p(1); }
+inline coord_t& x(Point& p) { return p(0); }
+inline coord_t& y(Point& p) { return p(1); }
+
+inline coordf_t x(const Vec3d& p) { return p(0); }
+inline coordf_t y(const Vec3d& p) { return p(1); }
+inline coordf_t z(const Vec3d& p) { return p(2); }
+inline coordf_t& x(Vec3d& p) { return p(0); }
+inline coordf_t& y(Vec3d& p) { return p(1); }
+inline coordf_t& z(Vec3d& p) { return p(2); }
+
+inline coord_t& x(Vec3crd& p) { return p(0); }
+inline coord_t& y(Vec3crd& p) { return p(1); }
+inline coord_t& z(Vec3crd& p) { return p(2); }
+inline coord_t x(const Vec3crd& p) { return p(0); }
+inline coord_t y(const Vec3crd& p) { return p(1); }
+inline coord_t z(const Vec3crd& p) { return p(2); }
+
+inline void triangulate(const ExPolygon& expoly, Polygons& triangles) {
+ expoly.triangulate_p2t(&triangles);
+}
+
+inline Polygons triangulate(const ExPolygon& expoly) {
+ Polygons tri; triangulate(expoly, tri); return tri;
+}
+
+using Indices = std::vector<Vec3crd>;
+
+/// Intermediate struct for a 3D mesh
+struct Contour3D {
+ Pointf3s points;
+ Indices indices;
+
+ void merge(const Contour3D& ctr) {
+ auto s3 = coord_t(points.size());
+ auto s = coord_t(indices.size());
+
+ points.insert(points.end(), ctr.points.begin(), ctr.points.end());
+ indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end());
+
+ for(auto n = s; n < indices.size(); n++) {
+ auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3;
+ }
+ }
+};
+
+/// Convert the triangulation output to an intermediate mesh.
+inline Contour3D convert(const Polygons& triangles, coord_t z, bool dir) {
+
+ Pointf3s points;
+ points.reserve(3*triangles.size());
+ Indices indices;
+ indices.reserve(points.size());
+
+ for(auto& tr : triangles) {
+ auto c = coord_t(points.size()), b = c++, a = c++;
+ if(dir) indices.emplace_back(a, b, c);
+ else indices.emplace_back(c, b, a);
+ for(auto& p : tr.points) {
+ points.emplace_back(unscale(x(p), y(p), z));
+ }
+ }
+
+ return {points, indices};
+}
+
+/// Only a debug function to generate top and bottom plates from a 2D shape.
+/// It is not used in the algorithm directly.
+inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) {
+ Polygons triangles = triangulate(poly);
+
+ auto lower = convert(triangles, 0, false);
+ auto upper = convert(triangles, z_distance, true);
+ lower.merge(upper);
+ return lower;
+}
+
+inline Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling,
+ double floor_z_mm, double ceiling_z_mm) {
+ using std::transform; using std::back_inserter;
+
+ ExPolygon poly;
+ poly.contour.points = floor_plate.contour.points;
+ poly.holes.emplace_back(ceiling.contour);
+ auto& h = poly.holes.front();
+ std::reverse(h.points.begin(), h.points.end());
+ Polygons tri = triangulate(poly);
+
+ Contour3D ret;
+ ret.points.reserve(tri.size() * 3);
+
+ double fz = floor_z_mm;
+ double cz = ceiling_z_mm;
+ auto& rp = ret.points;
+ auto& rpi = ret.indices;
+ ret.indices.reserve(tri.size() * 3);
+
+ coord_t idx = 0;
+
+ auto hlines = h.lines();
+ auto is_upper = [&hlines](const Point& p) {
+ return std::any_of(hlines.begin(), hlines.end(),
+ [&p](const Line& l) {
+ return l.distance_to(p) < mm(0.01);
+ });
+ };
+
+ std::for_each(tri.begin(), tri.end(),
+ [&rp, &rpi, &poly, &idx, is_upper, fz, cz](const Polygon& pp)
+ {
+ for(auto& p : pp.points)
+ if(is_upper(p))
+ rp.emplace_back(unscale(x(p), y(p), mm(cz)));
+ else rp.emplace_back(unscale(x(p), y(p), mm(fz)));
+
+ coord_t a = idx++, b = idx++, c = idx++;
+ if(fz > cz) rpi.emplace_back(c, b, a);
+ else rpi.emplace_back(a, b, c);
+ });
+
+ return ret;
+}
+
+/// Mesh from an existing contour.
+inline TriangleMesh mesh(const Contour3D& ctour) {
+ return {ctour.points, ctour.indices};
+}
+
+/// Mesh from an evaporating 3D contour
+inline TriangleMesh mesh(Contour3D&& ctour) {
+ return {std::move(ctour.points), std::move(ctour.indices)};
+}
+
+/// Offsetting with clipper and smoothing the edges into a curvature.
+inline void offset(ExPolygon& sh, coord_t distance) {
+ using ClipperLib::ClipperOffset;
+ using ClipperLib::jtRound;
+ using ClipperLib::etClosedPolygon;
+ using ClipperLib::Paths;
+ using ClipperLib::Path;
+
+ auto&& ctour = Slic3rMultiPoint_to_ClipperPath(sh.contour);
+ auto&& holes = Slic3rMultiPoints_to_ClipperPaths(sh.holes);
+
+ // If the input is not at least a triangle, we can not do this algorithm
+ if(ctour.size() < 3 ||
+ std::any_of(holes.begin(), holes.end(),
+ [](const Path& p) { return p.size() < 3; })
+ ) {
+ BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!";
+ return;
+ }
+
+ ClipperOffset offs;
+ offs.ArcTolerance = 0.01*mm(1);
+ Paths result;
+ offs.AddPath(ctour, jtRound, etClosedPolygon);
+ offs.AddPaths(holes, jtRound, etClosedPolygon);
+ offs.Execute(result, static_cast<double>(distance));
+
+ // Offsetting reverts the orientation and also removes the last vertex
+ // so boost will not have a closed polygon.
+
+ bool found_the_contour = false;
+ sh.holes.clear();
+ for(auto& r : result) {
+ if(ClipperLib::Orientation(r)) {
+ // We don't like if the offsetting generates more than one contour
+ // but throwing would be an overkill. Instead, we should warn the
+ // caller about the inability to create correct geometries
+ if(!found_the_contour) {
+ auto rr = ClipperPath_to_Slic3rPolygon(r);
+ sh.contour.points.swap(rr.points);
+ found_the_contour = true;
+ } else {
+ BOOST_LOG_TRIVIAL(warning)
+ << "Warning: offsetting result is invalid!";
+ }
+ } else {
+ // TODO If there are multiple contours we can't be sure which hole
+ // belongs to the first contour. (But in this case the situation is
+ // bad enough to let it go...)
+ sh.holes.emplace_back(ClipperPath_to_Slic3rPolygon(r));
+ }
+ }
+}
+
+template<class ExP, class D>
+inline Contour3D round_edges(const ExPolygon& base_plate,
+ double radius_mm,
+ double degrees,
+ double ceilheight_mm,
+ bool dir,
+ ExP&& last_offset = ExP(), D&& last_height = D())
+{
+ auto ob = base_plate;
+ auto ob_prev = ob;
+ double wh = ceilheight_mm, wh_prev = wh;
+ Contour3D curvedwalls;
+
+ const size_t steps = 6; // steps for 180 degrees
+ degrees = std::fmod(degrees, 180);
+ const int portion = int(steps*degrees / 90);
+ const double ystep_mm = radius_mm/steps;
+ coord_t s = dir? 1 : -1;
+ double xxprev = 0;
+ for(int i = 0; i < portion; i++) {
+ ob = base_plate;
+
+ // The offset is given by the equation: x = sqrt(r^2 - y^2)
+ // which can be derived from the circle equation. y is the current
+ // height for which the offset is calculated and x is the offset itself
+ // r is the radius of the circle that is used to smooth the edges
+
+ double r2 = radius_mm * radius_mm;
+ double y2 = steps*ystep_mm - i*ystep_mm;
+ y2 *= y2;
+
+ double xx = sqrt(r2 - y2);
+
+ offset(ob, s*mm(xx));
+ wh = ceilheight_mm - i*ystep_mm;
+
+ Contour3D pwalls;
+ if(xxprev < xx) pwalls = walls(ob, ob_prev, wh, wh_prev);
+ else pwalls = walls(ob_prev, ob, wh_prev, wh);
+
+ curvedwalls.merge(pwalls);
+ ob_prev = ob;
+ wh_prev = wh;
+ xxprev = xx;
+ }
+
+ last_offset = std::move(ob);
+ last_height = wh;
+
+ return curvedwalls;
+}
+
+/// Generating the concave part of the 3D pool with the bottom plate and the
+/// side walls.
+inline Contour3D inner_bed(const ExPolygon& poly, double depth_mm,
+ double begin_h_mm = 0) {
+
+ Polygons triangles = triangulate(poly);
+
+ coord_t depth = mm(depth_mm);
+ coord_t begin_h = mm(begin_h_mm);
+
+ auto bottom = convert(triangles, -depth + begin_h, false);
+ auto lines = poly.lines();
+
+ // Generate outer walls
+ auto fp = [](const Point& p, Point::coord_type z) {
+ return unscale(x(p), y(p), z);
+ };
+
+ for(auto& l : lines) {
+ auto s = coord_t(bottom.points.size());
+
+ bottom.points.emplace_back(fp(l.a, -depth + begin_h));
+ bottom.points.emplace_back(fp(l.b, -depth + begin_h));
+ bottom.points.emplace_back(fp(l.a, begin_h));
+ bottom.points.emplace_back(fp(l.b, begin_h));
+
+ bottom.indices.emplace_back(s + 3, s + 1, s);
+ bottom.indices.emplace_back(s + 2, s + 3, s);
+ }
+
+ return bottom;
+}
+
+/// Unification of polygons (with clipper) preserving holes as well.
+inline ExPolygons unify(const ExPolygons& shapes) {
+ using ClipperLib::ptSubject;
+
+ ExPolygons retv;
+
+ bool closed = true;
+ bool valid = true;
+
+ ClipperLib::Clipper clipper;
+
+ for(auto& path : shapes) {
+ auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path.contour);
+
+ if(!clipperpath.empty())
+ valid &= clipper.AddPath(clipperpath, ptSubject, closed);
+
+ auto clipperholes = Slic3rMultiPoints_to_ClipperPaths(path.holes);
+
+ for(auto& hole : clipperholes) {
+ if(!hole.empty())
+ valid &= clipper.AddPath(hole, ptSubject, closed);
+ }
+ }
+
+ if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!";
+
+ ClipperLib::PolyTree result;
+ clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero);
+
+ retv.reserve(static_cast<size_t>(result.Total()));
+
+ // Now we will recursively traverse the polygon tree and serialize it
+ // into an ExPolygon with holes. The polygon tree has the clipper-ish
+ // PolyTree structure which alternates its nodes as contours and holes
+
+ // A "declaration" of function for traversing leafs which are holes
+ std::function<void(ClipperLib::PolyNode*, ExPolygon&)> processHole;
+
+ // Process polygon which calls processHoles which than calls processPoly
+ // again until no leafs are left.
+ auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) {
+ ExPolygon poly;
+ poly.contour.points = ClipperPath_to_Slic3rPolygon(pptr->Contour);
+ for(auto h : pptr->Childs) { processHole(h, poly); }
+ retv.push_back(poly);
+ };
+
+ // Body of the processHole function
+ processHole = [&processPoly](ClipperLib::PolyNode *pptr, ExPolygon& poly)
+ {
+ poly.holes.emplace_back();
+ poly.holes.back().points = ClipperPath_to_Slic3rPolygon(pptr->Contour);
+ for(auto c : pptr->Childs) processPoly(c);
+ };
+
+ // Wrapper for traversing.
+ auto traverse = [&processPoly] (ClipperLib::PolyNode *node)
+ {
+ for(auto ch : node->Childs) {
+ processPoly(ch);
+ }
+ };
+
+ // Here is the actual traverse
+ traverse(&result);
+
+ return retv;
+}
+
+inline Point centroid(Points& pp) {
+ Point c;
+ switch(pp.size()) {
+ case 0: break;
+ case 1: c = pp.front(); break;
+ case 2: c = (pp[0] + pp[1]) / 2; break;
+ default: {
+ Polygon p;
+ p.points.swap(pp);
+ c = p.centroid();
+ pp.swap(p.points);
+ break;
+ }
+ }
+
+ return c;
+}
+
+inline Point centroid(const ExPolygon& poly) {
+ return poly.contour.centroid();
+}
+
+/// A fake concave hull that is constructed by connecting separate shapes
+/// with explicit bridges. Bridges are generated from each shape's centroid
+/// to the center of the "scene" which is the centroid calculated from the shape
+/// centroids (a star is created...)
+inline ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50)
+{
+ if(polys.empty()) return ExPolygons();
+
+ ExPolygons punion = unify(polys); // could be redundant
+
+ if(punion.size() == 1) return punion;
+
+ // We get the centroids of all the islands in the 2D slice
+ Points centroids; centroids.reserve(punion.size());
+ std::transform(punion.begin(), punion.end(), std::back_inserter(centroids),
+ [](const ExPolygon& poly) { return centroid(poly); });
+
+ // Centroid of the centroids of islands. This is where the additional
+ // connector sticks are routed.
+ Point cc = centroid(centroids);
+
+ punion.reserve(punion.size() + centroids.size());
+
+ std::transform(centroids.begin(), centroids.end(),
+ std::back_inserter(punion),
+ [cc, max_dist_mm](const Point& c) {
+
+ double dx = x(c) - x(cc), dy = y(c) - y(cc);
+ double l = std::sqrt(dx * dx + dy * dy);
+ double nx = dx / l, ny = dy / l;
+ double max_dist = mm(max_dist_mm);
+
+ if(l > max_dist) return ExPolygon();
+
+ ExPolygon r;
+ auto& ctour = r.contour.points;
+
+ ctour.reserve(3);
+ ctour.emplace_back(cc);
+
+ Point d(coord_t(mm(1)*nx), coord_t(mm(1)*ny));
+ ctour.emplace_back(c + Point( -y(d), x(d) ));
+ ctour.emplace_back(c + Point( y(d), -x(d) ));
+ offset(r, mm(1));
+
+ return r;
+ });
+
+ punion = unify(punion);
+
+ return punion;
+}
+
+}
+
+void ground_layer(const TriangleMesh &mesh, ExPolygons &output, float h)
+{
+ TriangleMesh m = mesh;
+ TriangleMeshSlicer slicer(&m);
+
+ std::vector<ExPolygons> tmp;
+
+ slicer.slice({h}, &tmp, [](){});
+
+ output = tmp.front();
+}
+
+void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
+ double min_wall_thickness_mm,
+ double min_wall_height_mm,
+ double max_merge_distance_mm)
+{
+ auto concavehs = concave_hull(ground_layer, max_merge_distance_mm);
+ for(ExPolygon& concaveh : concavehs) {
+ if(concaveh.contour.points.empty()) return;
+ concaveh.holes.clear();
+
+ BoundingBox bb(concaveh);
+ coord_t w = x(bb.max) - x(bb.min);
+ coord_t h = y(bb.max) - y(bb.min);
+
+ auto wall_thickness = coord_t((w+h)*0.01);
+
+ const coord_t WALL_THICKNESS = mm(min_wall_thickness_mm) +
+ wall_thickness;
+
+ const coord_t WALL_DISTANCE = coord_t(0.3*WALL_THICKNESS);
+ const coord_t HEIGHT = mm(min_wall_height_mm);
+
+ auto outer_base = concaveh;
+ offset(outer_base, WALL_THICKNESS+WALL_DISTANCE);
+ auto inner_base = outer_base;
+ offset(inner_base, -WALL_THICKNESS);
+ inner_base.holes.clear(); outer_base.holes.clear();
+
+ ExPolygon top_poly;
+ top_poly.contour = outer_base.contour;
+ top_poly.holes.emplace_back(inner_base.contour);
+ auto& tph = top_poly.holes.back().points;
+ std::reverse(tph.begin(), tph.end());
+
+ Contour3D pool;
+
+ ExPolygon ob = outer_base; double wh = 0;
+ auto curvedwalls = round_edges(ob,
+ 1, // radius 1 mm
+ 170, // 170 degrees
+ 0, // z position of the input plane
+ true,
+ ob, wh);
+ pool.merge(curvedwalls);
+
+ ExPolygon ob_contr = ob;
+ ob_contr.holes.clear();
+
+ auto pwalls = walls(ob_contr, inner_base, wh, -min_wall_height_mm);
+ pool.merge(pwalls);
+
+ Polygons top_triangles, bottom_triangles;
+ triangulate(top_poly, top_triangles);
+ triangulate(inner_base, bottom_triangles);
+ auto top_plate = convert(top_triangles, 0, false);
+ auto bottom_plate = convert(bottom_triangles, -HEIGHT, true);
+
+ ob = inner_base; wh = 0;
+ curvedwalls = round_edges(ob,
+ 1, // radius 1 mm
+ 90, // 170 degrees
+ 0, // z position of the input plane
+ false,
+ ob, wh);
+ pool.merge(curvedwalls);
+
+ auto innerbed = inner_bed(ob, min_wall_height_mm/2 + wh, wh);
+
+ pool.merge(top_plate);
+ pool.merge(bottom_plate);
+ pool.merge(innerbed);
+
+ out.merge(mesh(pool));
+ }
+}
+
+}
+}
diff --git a/src/libslic3r/SLABasePool.hpp b/src/libslic3r/SLABasePool.hpp
new file mode 100644
index 000000000..55c94df07
--- /dev/null
+++ b/src/libslic3r/SLABasePool.hpp
@@ -0,0 +1,32 @@
+#ifndef SLASUPPORTPOOL_HPP
+#define SLASUPPORTPOOL_HPP
+
+#include <vector>
+
+namespace Slic3r {
+
+class ExPolygon;
+class TriangleMesh;
+
+namespace sla {
+
+using ExPolygons = std::vector<ExPolygon>;
+
+/// Calculate the polygon representing the slice of the lowest layer of mesh
+void ground_layer(const TriangleMesh& mesh,
+ ExPolygons& output,
+ float height = 0.1f);
+
+/// Calculate the pool for the mesh for SLA printing
+void create_base_pool(const ExPolygons& ground_layer,
+ TriangleMesh& output_mesh,
+ double min_wall_thickness_mm = 2,
+ double min_wall_height_mm = 5,
+ double max_merge_distance_mm = 50
+ );
+
+}
+
+}
+
+#endif // SLASUPPORTPOOL_HPP
diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp
new file mode 100644
index 000000000..03f55802e
--- /dev/null
+++ b/src/libslic3r/SVG.cpp
@@ -0,0 +1,374 @@
+#include "SVG.hpp"
+#include <iostream>
+
+#include <boost/nowide/cstdio.hpp>
+
+#define COORD(x) (unscale<float>((x))*10)
+
+namespace Slic3r {
+
+bool SVG::open(const char* afilename)
+{
+ this->filename = afilename;
+ this->f = boost::nowide::fopen(afilename, "w");
+ if (this->f == NULL)
+ return false;
+ fprintf(this->f,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"
+ "<svg height=\"2000\" width=\"2000\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n"
+ " <marker id=\"endArrow\" markerHeight=\"8\" markerUnits=\"strokeWidth\" markerWidth=\"10\" orient=\"auto\" refX=\"1\" refY=\"5\" viewBox=\"0 0 10 10\">\n"
+ " <polyline fill=\"darkblue\" points=\"0,0 10,5 0,10 1,5\" />\n"
+ " </marker>\n"
+ );
+ return true;
+}
+
+bool SVG::open(const char* afilename, const BoundingBox &bbox, const coord_t bbox_offset, bool aflipY)
+{
+ this->filename = afilename;
+ this->origin = bbox.min - Point(bbox_offset, bbox_offset);
+ this->flipY = aflipY;
+ this->f = boost::nowide::fopen(afilename, "w");
+ if (f == NULL)
+ return false;
+ float w = COORD(bbox.max(0) - bbox.min(0) + 2 * bbox_offset);
+ float h = COORD(bbox.max(1) - bbox.min(1) + 2 * bbox_offset);
+ fprintf(this->f,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n"
+ "<svg height=\"%f\" width=\"%f\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n"
+ " <marker id=\"endArrow\" markerHeight=\"8\" markerUnits=\"strokeWidth\" markerWidth=\"10\" orient=\"auto\" refX=\"1\" refY=\"5\" viewBox=\"0 0 10 10\">\n"
+ " <polyline fill=\"darkblue\" points=\"0,0 10,5 0,10 1,5\" />\n"
+ " </marker>\n",
+ h, w);
+ return true;
+}
+
+void
+SVG::draw(const Line &line, std::string stroke, coordf_t stroke_width)
+{
+ fprintf(this->f,
+ " <line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke: %s; stroke-width: %f\"",
+ COORD(line.a(0) - origin(0)), COORD(line.a(1) - origin(1)), COORD(line.b(0) - origin(0)), COORD(line.b(1) - origin(1)), stroke.c_str(), (stroke_width == 0) ? 1.f : COORD(stroke_width));
+ if (this->arrows)
+ fprintf(this->f, " marker-end=\"url(#endArrow)\"");
+ fprintf(this->f, "/>\n");
+}
+
+void SVG::draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coordf_t stroke_width)
+{
+ Vec2d dir(line.b(0)-line.a(0), line.b(1)-line.a(1));
+ Vec2d perp(-dir(1), dir(0));
+ coordf_t len = sqrt(perp(0)*perp(0) + perp(1)*perp(1));
+ coordf_t da = coordf_t(0.5)*line.a_width/len;
+ coordf_t db = coordf_t(0.5)*line.b_width/len;
+ fprintf(this->f,
+ " <polygon points=\"%f,%f %f,%f %f,%f %f,%f\" style=\"fill:%s; stroke: %s; stroke-width: %f\"/>\n",
+ COORD(line.a(0)-da*perp(0)-origin(0)),
+ COORD(line.a(1)-da*perp(1)-origin(1)),
+ COORD(line.b(0)-db*perp(0)-origin(0)),
+ COORD(line.b(1)-db*perp(1)-origin(1)),
+ COORD(line.b(0)+db*perp(0)-origin(0)),
+ COORD(line.b(1)+db*perp(1)-origin(1)),
+ COORD(line.a(0)+da*perp(0)-origin(0)),
+ COORD(line.a(1)+da*perp(1)-origin(1)),
+ fill.c_str(), stroke.c_str(),
+ (stroke_width == 0) ? 1.f : COORD(stroke_width));
+}
+
+void
+SVG::draw(const Lines &lines, std::string stroke, coordf_t stroke_width)
+{
+ for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it)
+ this->draw(*it, stroke, stroke_width);
+}
+
+void
+SVG::draw(const IntersectionLines &lines, std::string stroke)
+{
+ for (IntersectionLines::const_iterator it = lines.begin(); it != lines.end(); ++it)
+ this->draw((Line)*it, stroke);
+}
+
+void
+SVG::draw(const ExPolygon &expolygon, std::string fill, const float fill_opacity)
+{
+ this->fill = fill;
+
+ std::string d;
+ Polygons pp = expolygon;
+ for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) {
+ d += this->get_path_d(*p, true) + " ";
+ }
+ this->path(d, true, 0, fill_opacity);
+}
+
+void
+SVG::draw_outline(const ExPolygon &expolygon, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width)
+{
+ draw_outline(expolygon.contour, stroke_outer, stroke_width);
+ for (Polygons::const_iterator it = expolygon.holes.begin(); it != expolygon.holes.end(); ++ it) {
+ draw_outline(*it, stroke_holes, stroke_width);
+ }
+}
+
+void
+SVG::draw(const ExPolygons &expolygons, std::string fill, const float fill_opacity)
+{
+ for (ExPolygons::const_iterator it = expolygons.begin(); it != expolygons.end(); ++it)
+ this->draw(*it, fill, fill_opacity);
+}
+
+void
+SVG::draw_outline(const ExPolygons &expolygons, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width)
+{
+ for (ExPolygons::const_iterator it = expolygons.begin(); it != expolygons.end(); ++ it)
+ draw_outline(*it, stroke_outer, stroke_holes, stroke_width);
+}
+
+void
+SVG::draw(const Surface &surface, std::string fill, const float fill_opacity)
+{
+ draw(surface.expolygon, fill, fill_opacity);
+}
+
+void
+SVG::draw_outline(const Surface &surface, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width)
+{
+ draw_outline(surface.expolygon, stroke_outer, stroke_holes, stroke_width);
+}
+
+void
+SVG::draw(const Surfaces &surfaces, std::string fill, const float fill_opacity)
+{
+ for (Surfaces::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it)
+ this->draw(*it, fill, fill_opacity);
+}
+
+void
+SVG::draw_outline(const Surfaces &surfaces, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width)
+{
+ for (Surfaces::const_iterator it = surfaces.begin(); it != surfaces.end(); ++ it)
+ draw_outline(*it, stroke_outer, stroke_holes, stroke_width);
+}
+
+void
+SVG::draw(const SurfacesPtr &surfaces, std::string fill, const float fill_opacity)
+{
+ for (SurfacesPtr::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it)
+ this->draw(*(*it), fill, fill_opacity);
+}
+
+void
+SVG::draw_outline(const SurfacesPtr &surfaces, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width)
+{
+ for (SurfacesPtr::const_iterator it = surfaces.begin(); it != surfaces.end(); ++ it)
+ draw_outline(*(*it), stroke_outer, stroke_holes, stroke_width);
+}
+
+void
+SVG::draw(const Polygon &polygon, std::string fill)
+{
+ this->fill = fill;
+ this->path(this->get_path_d(polygon, true), !fill.empty(), 0, 1.f);
+}
+
+void
+SVG::draw(const Polygons &polygons, std::string fill)
+{
+ for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it)
+ this->draw(*it, fill);
+}
+
+void
+SVG::draw(const Polyline &polyline, std::string stroke, coordf_t stroke_width)
+{
+ this->stroke = stroke;
+ this->path(this->get_path_d(polyline, false), false, stroke_width, 1.f);
+}
+
+void
+SVG::draw(const Polylines &polylines, std::string stroke, coordf_t stroke_width)
+{
+ for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
+ this->draw(*it, stroke, stroke_width);
+}
+
+void SVG::draw(const ThickLines &thicklines, const std::string &fill, const std::string &stroke, coordf_t stroke_width)
+{
+ for (ThickLines::const_iterator it = thicklines.begin(); it != thicklines.end(); ++it)
+ this->draw(*it, fill, stroke, stroke_width);
+}
+
+void
+SVG::draw(const ThickPolylines &polylines, const std::string &stroke, coordf_t stroke_width)
+{
+ for (ThickPolylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it)
+ this->draw((Polyline)*it, stroke, stroke_width);
+}
+
+void
+SVG::draw(const ThickPolylines &thickpolylines, const std::string &fill, const std::string &stroke, coordf_t stroke_width)
+{
+ for (ThickPolylines::const_iterator it = thickpolylines.begin(); it != thickpolylines.end(); ++ it)
+ draw(it->thicklines(), fill, stroke, stroke_width);
+}
+
+void
+SVG::draw(const Point &point, std::string fill, coord_t iradius)
+{
+ float radius = (iradius == 0) ? 3.f : COORD(iradius);
+ std::ostringstream svg;
+ svg << " <circle cx=\"" << COORD(point(0) - origin(0)) << "\" cy=\"" << COORD(point(1) - origin(1))
+ << "\" r=\"" << radius << "\" "
+ << "style=\"stroke: none; fill: " << fill << "\" />";
+
+ fprintf(this->f, "%s\n", svg.str().c_str());
+}
+
+void
+SVG::draw(const Points &points, std::string fill, coord_t radius)
+{
+ for (Points::const_iterator it = points.begin(); it != points.end(); ++it)
+ this->draw(*it, fill, radius);
+}
+
+void
+SVG::draw(const ClipperLib::Path &polygon, double scale, std::string stroke, coordf_t stroke_width)
+{
+ this->stroke = stroke;
+ this->path(this->get_path_d(polygon, scale, true), false, stroke_width, 1.f);
+}
+
+void
+SVG::draw(const ClipperLib::Paths &polygons, double scale, std::string stroke, coordf_t stroke_width)
+{
+ for (ClipperLib::Paths::const_iterator it = polygons.begin(); it != polygons.end(); ++ it)
+ draw(*it, scale, stroke, stroke_width);
+}
+
+void
+SVG::draw_outline(const Polygon &polygon, std::string stroke, coordf_t stroke_width)
+{
+ this->stroke = stroke;
+ this->path(this->get_path_d(polygon, true), false, stroke_width, 1.f);
+}
+
+void
+SVG::draw_outline(const Polygons &polygons, std::string stroke, coordf_t stroke_width)
+{
+ for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++ it)
+ draw_outline(*it, stroke, stroke_width);
+}
+
+void
+SVG::path(const std::string &d, bool fill, coordf_t stroke_width, const float fill_opacity)
+{
+ float lineWidth = 0.f;
+ if (! fill)
+ lineWidth = (stroke_width == 0) ? 2.f : COORD(stroke_width);
+
+ fprintf(
+ this->f,
+ " <path d=\"%s\" style=\"fill: %s; stroke: %s; stroke-width: %f; fill-type: evenodd\" %s fill-opacity=\"%f\" />\n",
+ d.c_str(),
+ fill ? this->fill.c_str() : "none",
+ this->stroke.c_str(),
+ lineWidth,
+ (this->arrows && !fill) ? " marker-end=\"url(#endArrow)\"" : "",
+ fill_opacity
+ );
+}
+
+std::string
+SVG::get_path_d(const MultiPoint &mp, bool closed) const
+{
+ std::ostringstream d;
+ d << "M ";
+ for (Points::const_iterator p = mp.points.begin(); p != mp.points.end(); ++p) {
+ d << COORD((*p)(0) - origin(0)) << " ";
+ d << COORD((*p)(1) - origin(1)) << " ";
+ }
+ if (closed) d << "z";
+ return d.str();
+}
+
+std::string
+SVG::get_path_d(const ClipperLib::Path &path, double scale, bool closed) const
+{
+ std::ostringstream d;
+ d << "M ";
+ for (ClipperLib::Path::const_iterator p = path.begin(); p != path.end(); ++p) {
+ d << COORD(scale * p->X - origin(0)) << " ";
+ d << COORD(scale * p->Y - origin(1)) << " ";
+ }
+ if (closed) d << "z";
+ return d.str();
+}
+
+void SVG::draw_text(const Point &pt, const char *text, const char *color)
+{
+ fprintf(this->f,
+ "<text x=\"%f\" y=\"%f\" font-family=\"sans-serif\" font-size=\"20px\" fill=\"%s\">%s</text>",
+ COORD(pt(0)-origin(0)),
+ COORD(pt(1)-origin(1)),
+ color, text);
+}
+
+void SVG::draw_legend(const Point &pt, const char *text, const char *color)
+{
+ fprintf(this->f,
+ "<circle cx=\"%f\" cy=\"%f\" r=\"10\" fill=\"%s\"/>",
+ COORD(pt(0)-origin(0)),
+ COORD(pt(1)-origin(1)),
+ color);
+ fprintf(this->f,
+ "<text x=\"%f\" y=\"%f\" font-family=\"sans-serif\" font-size=\"10px\" fill=\"%s\">%s</text>",
+ COORD(pt(0)-origin(0)) + 20.f,
+ COORD(pt(1)-origin(1)),
+ "black", text);
+}
+
+void
+SVG::Close()
+{
+ fprintf(this->f, "</svg>\n");
+ fclose(this->f);
+ this->f = NULL;
+// printf("SVG written to %s\n", this->filename.c_str());
+}
+
+void SVG::export_expolygons(const char *path, const BoundingBox &bbox, const Slic3r::ExPolygons &expolygons, std::string stroke_outer, std::string stroke_holes, coordf_t stroke_width)
+{
+ SVG svg(path, bbox);
+ svg.draw(expolygons);
+ svg.draw_outline(expolygons, stroke_outer, stroke_holes, stroke_width);
+ svg.Close();
+}
+
+void SVG::export_expolygons(const char *path, const std::vector<std::pair<Slic3r::ExPolygons, ExPolygonAttributes>> &expolygons_with_attributes)
+{
+ if (expolygons_with_attributes.empty())
+ return;
+
+ BoundingBox bbox = get_extents(expolygons_with_attributes.front().first);
+ for (size_t i = 0; i < expolygons_with_attributes.size(); ++ i)
+ bbox.merge(get_extents(expolygons_with_attributes[i].first));
+
+ SVG svg(path, bbox);
+ for (const auto &exp_with_attr : expolygons_with_attributes)
+ svg.draw(exp_with_attr.first, exp_with_attr.second.color_fill, exp_with_attr.second.fill_opacity);
+ for (const auto &exp_with_attr : expolygons_with_attributes) {
+ std::string color_contour = exp_with_attr.second.color_contour;
+ if (color_contour.empty())
+ color_contour = exp_with_attr.second.color_fill;
+ std::string color_holes = exp_with_attr.second.color_holes;
+ if (color_holes.empty())
+ color_holes = color_contour;
+ svg.draw_outline(exp_with_attr.first, color_contour, color_holes, exp_with_attr.second.outline_width);
+ }
+ svg.Close();
+}
+
+}
diff --git a/src/libslic3r/SVG.hpp b/src/libslic3r/SVG.hpp
new file mode 100644
index 000000000..3a5602196
--- /dev/null
+++ b/src/libslic3r/SVG.hpp
@@ -0,0 +1,128 @@
+#ifndef slic3r_SVG_hpp_
+#define slic3r_SVG_hpp_
+
+#include "libslic3r.h"
+#include "clipper.hpp"
+#include "ExPolygon.hpp"
+#include "Line.hpp"
+#include "TriangleMesh.hpp"
+#include "Surface.hpp"
+
+namespace Slic3r {
+
+class SVG
+{
+public:
+ bool arrows;
+ std::string fill, stroke;
+ Point origin;
+ bool flipY;
+
+ SVG(const char* afilename) :
+ arrows(false), fill("grey"), stroke("black"), filename(afilename), flipY(false)
+ { open(filename); }
+ SVG(const char* afilename, const BoundingBox &bbox, const coord_t bbox_offset = scale_(1.), bool aflipY = false) :
+ arrows(false), fill("grey"), stroke("black"), filename(afilename), origin(bbox.min - Point(bbox_offset, bbox_offset)), flipY(aflipY)
+ { open(filename, bbox, bbox_offset, aflipY); }
+ SVG(const std::string &filename) :
+ arrows(false), fill("grey"), stroke("black"), filename(filename), flipY(false)
+ { open(filename); }
+ SVG(const std::string &filename, const BoundingBox &bbox, const coord_t bbox_offset = scale_(1.), bool aflipY = false) :
+ arrows(false), fill("grey"), stroke("black"), filename(filename), origin(bbox.min - Point(bbox_offset, bbox_offset)), flipY(aflipY)
+ { open(filename, bbox, bbox_offset, aflipY); }
+ ~SVG() { if (f != NULL) Close(); }
+
+ bool open(const char* filename);
+ bool open(const char* filename, const BoundingBox &bbox, const coord_t bbox_offset = scale_(1.), bool flipY = false);
+ bool open(const std::string &filename)
+ { return open(filename.c_str()); }
+ bool open(const std::string &filename, const BoundingBox &bbox, const coord_t bbox_offset = scale_(1.), bool flipY = false)
+ { return open(filename.c_str(), bbox, bbox_offset, flipY); }
+
+ void draw(const Line &line, std::string stroke = "black", coordf_t stroke_width = 0);
+ void draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coordf_t stroke_width = 0);
+ void draw(const Lines &lines, std::string stroke = "black", coordf_t stroke_width = 0);
+ void draw(const IntersectionLines &lines, std::string stroke = "black");
+
+ void draw(const ExPolygon &expolygon, std::string fill = "grey", const float fill_opacity=1.f);
+ void draw_outline(const ExPolygon &polygon, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0);
+ void draw(const ExPolygons &expolygons, std::string fill = "grey", const float fill_opacity=1.f);
+ void draw_outline(const ExPolygons &polygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0);
+
+ void draw(const Surface &surface, std::string fill = "grey", const float fill_opacity=1.f);
+ void draw_outline(const Surface &surface, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0);
+ void draw(const Surfaces &surfaces, std::string fill = "grey", const float fill_opacity=1.f);
+ void draw_outline(const Surfaces &surfaces, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0);
+ void draw(const SurfacesPtr &surfaces, std::string fill = "grey", const float fill_opacity=1.f);
+ void draw_outline(const SurfacesPtr &surfaces, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0);
+
+ void draw(const Polygon &polygon, std::string fill = "grey");
+ void draw_outline(const Polygon &polygon, std::string stroke = "black", coordf_t stroke_width = 0);
+ void draw(const Polygons &polygons, std::string fill = "grey");
+ void draw_outline(const Polygons &polygons, std::string stroke = "black", coordf_t stroke_width = 0);
+ void draw(const Polyline &polyline, std::string stroke = "black", coordf_t stroke_width = 0);
+ void draw(const Polylines &polylines, std::string stroke = "black", coordf_t stroke_width = 0);
+ void draw(const ThickLines &thicklines, const std::string &fill = "lime", const std::string &stroke = "black", coordf_t stroke_width = 0);
+ void draw(const ThickPolylines &polylines, const std::string &stroke = "black", coordf_t stroke_width = 0);
+ void draw(const ThickPolylines &thickpolylines, const std::string &fill, const std::string &stroke, coordf_t stroke_width);
+ void draw(const Point &point, std::string fill = "black", coord_t radius = 0);
+ void draw(const Points &points, std::string fill = "black", coord_t radius = 0);
+
+ // Support for rendering the ClipperLib paths
+ void draw(const ClipperLib::Path &polygon, double scale, std::string fill = "grey", coordf_t stroke_width = 0);
+ void draw(const ClipperLib::Paths &polygons, double scale, std::string fill = "grey", coordf_t stroke_width = 0);
+
+ void draw_text(const Point &pt, const char *text, const char *color);
+ void draw_legend(const Point &pt, const char *text, const char *color);
+
+ void Close();
+
+ private:
+ std::string filename;
+ FILE* f;
+
+ void path(const std::string &d, bool fill, coordf_t stroke_width, const float fill_opacity);
+ std::string get_path_d(const MultiPoint &mp, bool closed = false) const;
+ std::string get_path_d(const ClipperLib::Path &mp, double scale, bool closed = false) const;
+
+public:
+ static void export_expolygons(const char *path, const BoundingBox &bbox, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0);
+ static void export_expolygons(const std::string &path, const BoundingBox &bbox, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0)
+ { export_expolygons(path.c_str(), bbox, expolygons, stroke_outer, stroke_holes, stroke_width); }
+ static void export_expolygons(const char *path, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0)
+ { export_expolygons(path, get_extents(expolygons), expolygons, stroke_outer, stroke_holes, stroke_width); }
+ static void export_expolygons(const std::string &path, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0)
+ { export_expolygons(path.c_str(), get_extents(expolygons), expolygons, stroke_outer, stroke_holes, stroke_width); }
+
+ struct ExPolygonAttributes
+ {
+ ExPolygonAttributes() : ExPolygonAttributes("gray", "black", "blue") {}
+ ExPolygonAttributes(const std::string &color) :
+ ExPolygonAttributes(color, color, color) {}
+
+ ExPolygonAttributes(
+ const std::string &color_fill,
+ const std::string &color_contour,
+ const std::string &color_holes,
+ const coord_t outline_width = scale_(0.05),
+ const float fill_opacity = 0.5f) :
+ color_fill (color_fill),
+ color_contour (color_contour),
+ color_holes (color_holes),
+ outline_width (outline_width),
+ fill_opacity (fill_opacity)
+ {}
+
+ std::string color_fill;
+ std::string color_contour;
+ std::string color_holes;
+ coord_t outline_width;
+ float fill_opacity;
+ };
+
+ static void export_expolygons(const char *path, const std::vector<std::pair<Slic3r::ExPolygons, ExPolygonAttributes>> &expolygons_with_attributes);
+};
+
+}
+
+#endif
diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp
new file mode 100644
index 000000000..b3e314549
--- /dev/null
+++ b/src/libslic3r/Slicing.cpp
@@ -0,0 +1,670 @@
+#include <limits>
+
+#include "libslic3r.h"
+#include "Slicing.hpp"
+#include "SlicingAdaptive.hpp"
+#include "PrintConfig.hpp"
+#include "Model.hpp"
+
+// #define SLIC3R_DEBUG
+
+// Make assert active if SLIC3R_DEBUG
+#ifdef SLIC3R_DEBUG
+ #undef NDEBUG
+ #define DEBUG
+ #define _DEBUG
+ #include "SVG.hpp"
+ #undef assert
+ #include <cassert>
+#endif
+
+namespace Slic3r
+{
+
+static const coordf_t MIN_LAYER_HEIGHT = 0.01;
+static const coordf_t MIN_LAYER_HEIGHT_DEFAULT = 0.07;
+
+// Minimum layer height for the variable layer height algorithm.
+inline coordf_t min_layer_height_from_nozzle(const PrintConfig &print_config, int idx_nozzle)
+{
+ coordf_t min_layer_height = print_config.min_layer_height.get_at(idx_nozzle - 1);
+ return (min_layer_height == 0.) ? MIN_LAYER_HEIGHT_DEFAULT : std::max(MIN_LAYER_HEIGHT, min_layer_height);
+}
+
+// Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default,
+// it should not be smaller than the minimum layer height.
+inline coordf_t max_layer_height_from_nozzle(const PrintConfig &print_config, int idx_nozzle)
+{
+ coordf_t min_layer_height = min_layer_height_from_nozzle(print_config, idx_nozzle);
+ coordf_t max_layer_height = print_config.max_layer_height.get_at(idx_nozzle - 1);
+ coordf_t nozzle_dmr = print_config.nozzle_diameter.get_at(idx_nozzle - 1);
+ return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height);
+}
+
+SlicingParameters SlicingParameters::create_from_config(
+ const PrintConfig &print_config,
+ const PrintObjectConfig &object_config,
+ coordf_t object_height,
+ const std::vector<unsigned int> &object_extruders)
+{
+ coordf_t first_layer_height = (object_config.first_layer_height.value <= 0) ?
+ object_config.layer_height.value :
+ object_config.first_layer_height.get_abs_value(object_config.layer_height.value);
+ // If object_config.support_material_extruder == 0 resp. object_config.support_material_interface_extruder == 0,
+ // print_config.nozzle_diameter.get_at(size_t(-1)) returns the 0th nozzle diameter,
+ // which is consistent with the requirement that if support_material_extruder == 0 resp. support_material_interface_extruder == 0,
+ // support will not trigger tool change, but it will use the current nozzle instead.
+ // In that case all the nozzles have to be of the same diameter.
+ coordf_t support_material_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_material_extruder.value - 1);
+ coordf_t support_material_interface_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_material_interface_extruder.value - 1);
+ bool soluble_interface = object_config.support_material_contact_distance.value == 0.;
+
+ SlicingParameters params;
+ params.layer_height = object_config.layer_height.value;
+ params.first_print_layer_height = first_layer_height;
+ params.first_object_layer_height = first_layer_height;
+ params.object_print_z_min = 0.;
+ params.object_print_z_max = object_height;
+ params.base_raft_layers = object_config.raft_layers.value;
+ params.soluble_interface = soluble_interface;
+
+ // Miniumum/maximum of the minimum layer height over all extruders.
+ params.min_layer_height = MIN_LAYER_HEIGHT;
+ params.max_layer_height = std::numeric_limits<double>::max();
+ if (object_config.support_material.value || params.base_raft_layers > 0) {
+ // Has some form of support. Add the support layers to the minimum / maximum layer height limits.
+ params.min_layer_height = std::max(
+ min_layer_height_from_nozzle(print_config, object_config.support_material_extruder),
+ min_layer_height_from_nozzle(print_config, object_config.support_material_interface_extruder));
+ params.max_layer_height = std::min(
+ max_layer_height_from_nozzle(print_config, object_config.support_material_extruder),
+ max_layer_height_from_nozzle(print_config, object_config.support_material_interface_extruder));
+ params.max_suport_layer_height = params.max_layer_height;
+ }
+ if (object_extruders.empty()) {
+ params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, 0));
+ params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, 0));
+ } else {
+ for (unsigned int extruder_id : object_extruders) {
+ params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, extruder_id));
+ params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, extruder_id));
+ }
+ }
+ params.min_layer_height = std::min(params.min_layer_height, params.layer_height);
+ params.max_layer_height = std::max(params.max_layer_height, params.layer_height);
+
+ if (! soluble_interface) {
+ params.gap_raft_object = object_config.support_material_contact_distance.value;
+ params.gap_object_support = object_config.support_material_contact_distance.value;
+ params.gap_support_object = object_config.support_material_contact_distance.value;
+ }
+
+ if (params.base_raft_layers > 0) {
+ params.interface_raft_layers = (params.base_raft_layers + 1) / 2;
+ params.base_raft_layers -= params.interface_raft_layers;
+ // Use as large as possible layer height for the intermediate raft layers.
+ params.base_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_extruder_dmr);
+ params.interface_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_interface_extruder_dmr);
+ params.contact_raft_layer_height_bridging = false;
+ params.first_object_layer_bridging = false;
+ #if 1
+ params.contact_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_interface_extruder_dmr);
+ if (! soluble_interface) {
+ // Compute the average of all nozzles used for printing the object over a raft.
+ //FIXME It is expected, that the 1st layer of the object is printed with a bridging flow over a full raft. Shall it not be vice versa?
+ coordf_t average_object_extruder_dmr = 0.;
+ if (! object_extruders.empty()) {
+ for (unsigned int extruder_id : object_extruders)
+ average_object_extruder_dmr += print_config.nozzle_diameter.get_at(extruder_id);
+ average_object_extruder_dmr /= coordf_t(object_extruders.size());
+ }
+ params.first_object_layer_height = average_object_extruder_dmr;
+ params.first_object_layer_bridging = true;
+ }
+ #else
+ params.contact_raft_layer_height = soluble_interface ? support_material_interface_extruder_dmr : 0.75 * support_material_interface_extruder_dmr;
+ params.contact_raft_layer_height_bridging = ! soluble_interface;
+ ...
+ #endif
+ }
+
+ if (params.has_raft()) {
+ // Raise first object layer Z by the thickness of the raft itself plus the extra distance required by the support material logic.
+ //FIXME The last raft layer is the contact layer, which shall be printed with a bridging flow for ease of separation. Currently it is not the case.
+ if (params.raft_layers() == 1) {
+ // There is only the contact layer.
+ params.contact_raft_layer_height = first_layer_height;
+ params.raft_contact_top_z = first_layer_height;
+ } else {
+ assert(params.base_raft_layers > 0);
+ assert(params.interface_raft_layers > 0);
+ // Number of the base raft layers is decreased by the first layer.
+ params.raft_base_top_z = first_layer_height + coordf_t(params.base_raft_layers - 1) * params.base_raft_layer_height;
+ // Number of the interface raft layers is decreased by the contact layer.
+ params.raft_interface_top_z = params.raft_base_top_z + coordf_t(params.interface_raft_layers - 1) * params.interface_raft_layer_height;
+ params.raft_contact_top_z = params.raft_interface_top_z + params.contact_raft_layer_height;
+ }
+ coordf_t print_z = params.raft_contact_top_z + params.gap_raft_object;
+ params.object_print_z_min = print_z;
+ params.object_print_z_max += print_z;
+ }
+
+ return params;
+}
+
+// Convert layer_height_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for
+// in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation.
+std::vector<coordf_t> layer_height_profile_from_ranges(
+ const SlicingParameters &slicing_params,
+ const t_layer_height_ranges &layer_height_ranges)
+{
+ // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed.
+ std::vector<std::pair<t_layer_height_range,coordf_t>> ranges_non_overlapping;
+ ranges_non_overlapping.reserve(layer_height_ranges.size() * 4);
+ if (slicing_params.first_object_layer_height_fixed())
+ ranges_non_overlapping.push_back(std::pair<t_layer_height_range,coordf_t>(
+ t_layer_height_range(0., slicing_params.first_object_layer_height),
+ slicing_params.first_object_layer_height));
+ // The height ranges are sorted lexicographically by low / high layer boundaries.
+ for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) {
+ coordf_t lo = it_range->first.first;
+ coordf_t hi = std::min(it_range->first.second, slicing_params.object_print_z_height());
+ coordf_t height = it_range->second;
+ if (! ranges_non_overlapping.empty())
+ // Trim current low with the last high.
+ lo = std::max(lo, ranges_non_overlapping.back().first.second);
+ if (lo + EPSILON < hi)
+ // Ignore too narrow ranges.
+ ranges_non_overlapping.push_back(std::pair<t_layer_height_range,coordf_t>(t_layer_height_range(lo, hi), height));
+ }
+
+ // 2) Convert the trimmed ranges to a height profile, fill in the undefined intervals between z=0 and z=slicing_params.object_print_z_max()
+ // with slicing_params.layer_height
+ std::vector<coordf_t> layer_height_profile;
+ for (std::vector<std::pair<t_layer_height_range,coordf_t>>::const_iterator it_range = ranges_non_overlapping.begin(); it_range != ranges_non_overlapping.end(); ++ it_range) {
+ coordf_t lo = it_range->first.first;
+ coordf_t hi = it_range->first.second;
+ coordf_t height = it_range->second;
+ coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2];
+ coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1];
+ if (lo > last_z + EPSILON) {
+ // Insert a step of normal layer height.
+ layer_height_profile.push_back(last_z);
+ layer_height_profile.push_back(slicing_params.layer_height);
+ layer_height_profile.push_back(lo);
+ layer_height_profile.push_back(slicing_params.layer_height);
+ }
+ // Insert a step of the overriden layer height.
+ layer_height_profile.push_back(lo);
+ layer_height_profile.push_back(height);
+ layer_height_profile.push_back(hi);
+ layer_height_profile.push_back(height);
+ }
+
+ coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2];
+ coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1];
+ if (last_z < slicing_params.object_print_z_height()) {
+ // Insert a step of normal layer height up to the object top.
+ layer_height_profile.push_back(last_z);
+ layer_height_profile.push_back(slicing_params.layer_height);
+ layer_height_profile.push_back(slicing_params.object_print_z_height());
+ layer_height_profile.push_back(slicing_params.layer_height);
+ }
+
+ return layer_height_profile;
+}
+
+// Based on the work of @platsch
+// Fill layer_height_profile by heights ensuring a prescribed maximum cusp height.
+std::vector<coordf_t> layer_height_profile_adaptive(
+ const SlicingParameters &slicing_params,
+ const t_layer_height_ranges &layer_height_ranges,
+ const ModelVolumePtrs &volumes)
+{
+ // 1) Initialize the SlicingAdaptive class with the object meshes.
+ SlicingAdaptive as;
+ as.set_slicing_parameters(slicing_params);
+ for (const ModelVolume *volume : volumes)
+ if (volume->is_model_part())
+ as.add_mesh(&volume->mesh);
+ as.prepare();
+
+ // 2) Generate layers using the algorithm of @platsch
+ // loop until we have at least one layer and the max slice_z reaches the object height
+ //FIXME make it configurable
+ // Cusp value: A maximum allowed distance from a corner of a rectangular extrusion to a chrodal line, in mm.
+ const coordf_t cusp_value = 0.2; // $self->config->get_value('cusp_value');
+
+ std::vector<coordf_t> layer_height_profile;
+ layer_height_profile.push_back(0.);
+ layer_height_profile.push_back(slicing_params.first_object_layer_height);
+ if (slicing_params.first_object_layer_height_fixed()) {
+ layer_height_profile.push_back(slicing_params.first_object_layer_height);
+ layer_height_profile.push_back(slicing_params.first_object_layer_height);
+ }
+ coordf_t slice_z = slicing_params.first_object_layer_height;
+ coordf_t height = slicing_params.first_object_layer_height;
+ coordf_t cusp_height = 0.;
+ int current_facet = 0;
+ while ((slice_z - height) <= slicing_params.object_print_z_height()) {
+ height = 999;
+ // Slic3r::debugf "\n Slice layer: %d\n", $id;
+ // determine next layer height
+ coordf_t cusp_height = as.cusp_height(slice_z, cusp_value, current_facet);
+ // check for horizontal features and object size
+ /*
+ if($self->config->get_value('match_horizontal_surfaces')) {
+ my $horizontal_dist = $adaptive_slicing[$region_id]->horizontal_facet_distance(scale $slice_z+$cusp_height, $min_height);
+ if(($horizontal_dist < $min_height) && ($horizontal_dist > 0)) {
+ Slic3r::debugf "Horizontal feature ahead, distance: %f\n", $horizontal_dist;
+ # can we shrink the current layer a bit?
+ if($cusp_height-($min_height-$horizontal_dist) > $min_height) {
+ # yes we can
+ $cusp_height = $cusp_height-($min_height-$horizontal_dist);
+ Slic3r::debugf "Shrink layer height to %f\n", $cusp_height;
+ }else{
+ # no, current layer would become too thin
+ $cusp_height = $cusp_height+$horizontal_dist;
+ Slic3r::debugf "Widen layer height to %f\n", $cusp_height;
+ }
+ }
+ }
+ */
+ height = std::min(cusp_height, height);
+
+ // apply z-gradation
+ /*
+ my $gradation = $self->config->get_value('adaptive_slicing_z_gradation');
+ if($gradation > 0) {
+ $height = $height - unscale((scale($height)) % (scale($gradation)));
+ }
+ */
+
+ // look for an applicable custom range
+ /*
+ if (my $range = first { $_->[0] <= $slice_z && $_->[1] > $slice_z } @{$self->layer_height_ranges}) {
+ $height = $range->[2];
+
+ # if user set custom height to zero we should just skip the range and resume slicing over it
+ if ($height == 0) {
+ $slice_z += $range->[1] - $range->[0];
+ next;
+ }
+ }
+ */
+
+ layer_height_profile.push_back(slice_z);
+ layer_height_profile.push_back(height);
+ slice_z += height;
+ layer_height_profile.push_back(slice_z);
+ layer_height_profile.push_back(height);
+ }
+
+ coordf_t last = std::max(slicing_params.first_object_layer_height, layer_height_profile[layer_height_profile.size() - 2]);
+ layer_height_profile.push_back(last);
+ layer_height_profile.push_back(slicing_params.first_object_layer_height);
+ layer_height_profile.push_back(slicing_params.object_print_z_height());
+ layer_height_profile.push_back(slicing_params.first_object_layer_height);
+
+ return layer_height_profile;
+}
+
+void adjust_layer_height_profile(
+ const SlicingParameters &slicing_params,
+ std::vector<coordf_t> &layer_height_profile,
+ coordf_t z,
+ coordf_t layer_thickness_delta,
+ coordf_t band_width,
+ LayerHeightEditActionType action)
+{
+ // Constrain the profile variability by the 1st layer height.
+ std::pair<coordf_t, coordf_t> z_span_variable =
+ std::pair<coordf_t, coordf_t>(
+ slicing_params.first_object_layer_height_fixed() ? slicing_params.first_object_layer_height : 0.,
+ slicing_params.object_print_z_height());
+ if (z < z_span_variable.first || z > z_span_variable.second)
+ return;
+
+ assert(layer_height_profile.size() >= 2);
+ assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) < EPSILON);
+
+ // 1) Get the current layer thickness at z.
+ coordf_t current_layer_height = slicing_params.layer_height;
+ for (size_t i = 0; i < layer_height_profile.size(); i += 2) {
+ if (i + 2 == layer_height_profile.size()) {
+ current_layer_height = layer_height_profile[i + 1];
+ break;
+ } else if (layer_height_profile[i + 2] > z) {
+ coordf_t z1 = layer_height_profile[i];
+ coordf_t h1 = layer_height_profile[i + 1];
+ coordf_t z2 = layer_height_profile[i + 2];
+ coordf_t h2 = layer_height_profile[i + 3];
+ current_layer_height = lerp(h1, h2, (z - z1) / (z2 - z1));
+ break;
+ }
+ }
+
+ // 2) Is it possible to apply the delta?
+ switch (action) {
+ case LAYER_HEIGHT_EDIT_ACTION_DECREASE:
+ layer_thickness_delta = - layer_thickness_delta;
+ // fallthrough
+ case LAYER_HEIGHT_EDIT_ACTION_INCREASE:
+ if (layer_thickness_delta > 0) {
+ if (current_layer_height >= slicing_params.max_layer_height - EPSILON)
+ return;
+ layer_thickness_delta = std::min(layer_thickness_delta, slicing_params.max_layer_height - current_layer_height);
+ } else {
+ if (current_layer_height <= slicing_params.min_layer_height + EPSILON)
+ return;
+ layer_thickness_delta = std::max(layer_thickness_delta, slicing_params.min_layer_height - current_layer_height);
+ }
+ break;
+ case LAYER_HEIGHT_EDIT_ACTION_REDUCE:
+ case LAYER_HEIGHT_EDIT_ACTION_SMOOTH:
+ layer_thickness_delta = std::abs(layer_thickness_delta);
+ layer_thickness_delta = std::min(layer_thickness_delta, std::abs(slicing_params.layer_height - current_layer_height));
+ if (layer_thickness_delta < EPSILON)
+ return;
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ // 3) Densify the profile inside z +- band_width/2, remove duplicate Zs from the height profile inside the band.
+ coordf_t lo = std::max(z_span_variable.first, z - 0.5 * band_width);
+ // Do not limit the upper side of the band, so that the modifications to the top point of the profile will be allowed.
+ coordf_t hi = z + 0.5 * band_width;
+ coordf_t z_step = 0.1;
+ size_t idx = 0;
+ while (idx < layer_height_profile.size() && layer_height_profile[idx] < lo)
+ idx += 2;
+ idx -= 2;
+
+ std::vector<double> profile_new;
+ profile_new.reserve(layer_height_profile.size());
+ assert(idx >= 0 && idx + 1 < layer_height_profile.size());
+ profile_new.insert(profile_new.end(), layer_height_profile.begin(), layer_height_profile.begin() + idx + 2);
+ coordf_t zz = lo;
+ size_t i_resampled_start = profile_new.size();
+ while (zz < hi) {
+ size_t next = idx + 2;
+ coordf_t z1 = layer_height_profile[idx];
+ coordf_t h1 = layer_height_profile[idx + 1];
+ coordf_t height = h1;
+ if (next < layer_height_profile.size()) {
+ coordf_t z2 = layer_height_profile[next];
+ coordf_t h2 = layer_height_profile[next + 1];
+ height = lerp(h1, h2, (zz - z1) / (z2 - z1));
+ }
+ // Adjust height by layer_thickness_delta.
+ coordf_t weight = std::abs(zz - z) < 0.5 * band_width ? (0.5 + 0.5 * cos(2. * M_PI * (zz - z) / band_width)) : 0.;
+ coordf_t height_new = height;
+ switch (action) {
+ case LAYER_HEIGHT_EDIT_ACTION_INCREASE:
+ case LAYER_HEIGHT_EDIT_ACTION_DECREASE:
+ height += weight * layer_thickness_delta;
+ break;
+ case LAYER_HEIGHT_EDIT_ACTION_REDUCE:
+ {
+ coordf_t delta = height - slicing_params.layer_height;
+ coordf_t step = weight * layer_thickness_delta;
+ step = (std::abs(delta) > step) ?
+ (delta > 0) ? -step : step :
+ -delta;
+ height += step;
+ break;
+ }
+ case LAYER_HEIGHT_EDIT_ACTION_SMOOTH:
+ {
+ // Don't modify the profile during resampling process, do it at the next step.
+ break;
+ }
+ default:
+ assert(false);
+ break;
+ }
+ height = clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, height);
+ if (zz == z_span_variable.second) {
+ // This is the last point of the profile.
+ if (profile_new[profile_new.size() - 2] + EPSILON > zz) {
+ profile_new.pop_back();
+ profile_new.pop_back();
+ }
+ profile_new.push_back(zz);
+ profile_new.push_back(height);
+ idx = layer_height_profile.size();
+ break;
+ }
+ // Avoid entering a too short segment.
+ if (profile_new[profile_new.size() - 2] + EPSILON < zz) {
+ profile_new.push_back(zz);
+ profile_new.push_back(height);
+ }
+ // Limit zz to the object height, so the next iteration the last profile point will be set.
+ zz = std::min(zz + z_step, z_span_variable.second);
+ idx = next;
+ while (idx < layer_height_profile.size() && layer_height_profile[idx] < zz)
+ idx += 2;
+ idx -= 2;
+ }
+
+ idx += 2;
+ assert(idx > 0);
+ size_t i_resampled_end = profile_new.size();
+ if (idx < layer_height_profile.size()) {
+ assert(zz >= layer_height_profile[idx - 2]);
+ assert(zz <= layer_height_profile[idx]);
+ profile_new.insert(profile_new.end(), layer_height_profile.begin() + idx, layer_height_profile.end());
+ }
+ else if (profile_new[profile_new.size() - 2] + 0.5 * EPSILON < z_span_variable.second) {
+ profile_new.insert(profile_new.end(), layer_height_profile.end() - 2, layer_height_profile.end());
+ }
+ layer_height_profile = std::move(profile_new);
+
+ if (action == LAYER_HEIGHT_EDIT_ACTION_SMOOTH) {
+ if (i_resampled_start == 0)
+ ++ i_resampled_start;
+ if (i_resampled_end == layer_height_profile.size())
+ i_resampled_end -= 2;
+ size_t n_rounds = 6;
+ for (size_t i_round = 0; i_round < n_rounds; ++ i_round) {
+ profile_new = layer_height_profile;
+ for (size_t i = i_resampled_start; i < i_resampled_end; i += 2) {
+ coordf_t zz = profile_new[i];
+ coordf_t t = std::abs(zz - z) < 0.5 * band_width ? (0.25 + 0.25 * cos(2. * M_PI * (zz - z) / band_width)) : 0.;
+ assert(t >= 0. && t <= 0.5000001);
+ if (i == 0)
+ layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + t * profile_new[i + 3];
+ else if (i + 1 == profile_new.size())
+ layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + t * profile_new[i - 1];
+ else
+ layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + 0.5 * t * (profile_new[i - 1] + profile_new[i + 3]);
+ }
+ }
+ }
+
+ assert(layer_height_profile.size() > 2);
+ assert(layer_height_profile.size() % 2 == 0);
+ assert(layer_height_profile[0] == 0.);
+ assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) < EPSILON);
+#ifdef _DEBUG
+ for (size_t i = 2; i < layer_height_profile.size(); i += 2)
+ assert(layer_height_profile[i - 2] <= layer_height_profile[i]);
+ for (size_t i = 1; i < layer_height_profile.size(); i += 2) {
+ assert(layer_height_profile[i] > slicing_params.min_layer_height - EPSILON);
+ assert(layer_height_profile[i] < slicing_params.max_layer_height + EPSILON);
+ }
+#endif /* _DEBUG */
+}
+
+// Produce object layers as pairs of low / high layer boundaries, stored into a linear vector.
+std::vector<coordf_t> generate_object_layers(
+ const SlicingParameters &slicing_params,
+ const std::vector<coordf_t> &layer_height_profile)
+{
+ assert(! layer_height_profile.empty());
+
+ coordf_t print_z = 0;
+ coordf_t height = 0;
+
+ std::vector<coordf_t> out;
+
+ if (slicing_params.first_object_layer_height_fixed()) {
+ out.push_back(0);
+ print_z = slicing_params.first_object_layer_height;
+ out.push_back(print_z);
+ }
+
+ size_t idx_layer_height_profile = 0;
+ // loop until we have at least one layer and the max slice_z reaches the object height
+ coordf_t slice_z = print_z + 0.5 * slicing_params.min_layer_height;
+ while (slice_z < slicing_params.object_print_z_height()) {
+ height = slicing_params.min_layer_height;
+ if (idx_layer_height_profile < layer_height_profile.size()) {
+ size_t next = idx_layer_height_profile + 2;
+ for (;;) {
+ if (next >= layer_height_profile.size() || slice_z < layer_height_profile[next])
+ break;
+ idx_layer_height_profile = next;
+ next += 2;
+ }
+ coordf_t z1 = layer_height_profile[idx_layer_height_profile];
+ coordf_t h1 = layer_height_profile[idx_layer_height_profile + 1];
+ height = h1;
+ if (next < layer_height_profile.size()) {
+ coordf_t z2 = layer_height_profile[next];
+ coordf_t h2 = layer_height_profile[next + 1];
+ height = lerp(h1, h2, (slice_z - z1) / (z2 - z1));
+ assert(height >= slicing_params.min_layer_height - EPSILON && height <= slicing_params.max_layer_height + EPSILON);
+ }
+ }
+ slice_z = print_z + 0.5 * height;
+ if (slice_z >= slicing_params.object_print_z_height())
+ break;
+ assert(height > slicing_params.min_layer_height - EPSILON);
+ assert(height < slicing_params.max_layer_height + EPSILON);
+ out.push_back(print_z);
+ print_z += height;
+ slice_z = print_z + 0.5 * slicing_params.min_layer_height;
+ out.push_back(print_z);
+ }
+
+ //FIXME Adjust the last layer to align with the top object layer exactly?
+ return out;
+}
+
+int generate_layer_height_texture(
+ const SlicingParameters &slicing_params,
+ const std::vector<coordf_t> &layers,
+ void *data, int rows, int cols, bool level_of_detail_2nd_level)
+{
+// https://github.com/aschn/gnuplot-colorbrewer
+ std::vector<Vec3crd> palette_raw;
+ palette_raw.push_back(Vec3crd(0x01A, 0x098, 0x050));
+ palette_raw.push_back(Vec3crd(0x066, 0x0BD, 0x063));
+ palette_raw.push_back(Vec3crd(0x0A6, 0x0D9, 0x06A));
+ palette_raw.push_back(Vec3crd(0x0D9, 0x0F1, 0x0EB));
+ palette_raw.push_back(Vec3crd(0x0FE, 0x0E6, 0x0EB));
+ palette_raw.push_back(Vec3crd(0x0FD, 0x0AE, 0x061));
+ palette_raw.push_back(Vec3crd(0x0F4, 0x06D, 0x043));
+ palette_raw.push_back(Vec3crd(0x0D7, 0x030, 0x027));
+
+ // Clear the main texture and the 2nd LOD level.
+// memset(data, 0, rows * cols * (level_of_detail_2nd_level ? 5 : 4));
+ // 2nd LOD level data start
+ unsigned char *data1 = reinterpret_cast<unsigned char*>(data) + rows * cols * 4;
+ int ncells = std::min((cols-1) * rows, int(ceil(16. * (slicing_params.object_print_z_height() / slicing_params.min_layer_height))));
+ int ncells1 = ncells / 2;
+ int cols1 = cols / 2;
+ coordf_t z_to_cell = coordf_t(ncells-1) / slicing_params.object_print_z_height();
+ coordf_t cell_to_z = slicing_params.object_print_z_height() / coordf_t(ncells-1);
+ coordf_t z_to_cell1 = coordf_t(ncells1-1) / slicing_params.object_print_z_height();
+ // for color scaling
+ coordf_t hscale = 2.f * std::max(slicing_params.max_layer_height - slicing_params.layer_height, slicing_params.layer_height - slicing_params.min_layer_height);
+ if (hscale == 0)
+ // All layers have the same height. Provide some height scale to avoid division by zero.
+ hscale = slicing_params.layer_height;
+ for (size_t idx_layer = 0; idx_layer < layers.size(); idx_layer += 2) {
+ coordf_t lo = layers[idx_layer];
+ coordf_t hi = layers[idx_layer + 1];
+ coordf_t mid = 0.5f * (lo + hi);
+ assert(mid <= slicing_params.object_print_z_height());
+ coordf_t h = hi - lo;
+ hi = std::min(hi, slicing_params.object_print_z_height());
+ int cell_first = clamp(0, ncells-1, int(ceil(lo * z_to_cell)));
+ int cell_last = clamp(0, ncells-1, int(floor(hi * z_to_cell)));
+ for (int cell = cell_first; cell <= cell_last; ++ cell) {
+ coordf_t idxf = (0.5 * hscale + (h - slicing_params.layer_height)) * coordf_t(palette_raw.size()-1) / hscale;
+ int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf)));
+ int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1);
+ coordf_t t = idxf - coordf_t(idx1);
+ const Vec3crd &color1 = palette_raw[idx1];
+ const Vec3crd &color2 = palette_raw[idx2];
+ coordf_t z = cell_to_z * coordf_t(cell);
+ assert(z >= lo && z <= hi);
+ // Intensity profile to visualize the layers.
+ coordf_t intensity = cos(M_PI * 0.7 * (mid - z) / h);
+ // Color mapping from layer height to RGB.
+ Vec3d color(
+ intensity * lerp(coordf_t(color1(0)), coordf_t(color2(0)), t),
+ intensity * lerp(coordf_t(color1(1)), coordf_t(color2(1)), t),
+ intensity * lerp(coordf_t(color1(2)), coordf_t(color2(2)), t));
+ int row = cell / (cols - 1);
+ int col = cell - row * (cols - 1);
+ assert(row >= 0 && row < rows);
+ assert(col >= 0 && col < cols);
+ unsigned char *ptr = (unsigned char*)data + (row * cols + col) * 4;
+ ptr[0] = (unsigned char)clamp<int>(0, 255, int(floor(color(0) + 0.5)));
+ ptr[1] = (unsigned char)clamp<int>(0, 255, int(floor(color(1) + 0.5)));
+ ptr[2] = (unsigned char)clamp<int>(0, 255, int(floor(color(2) + 0.5)));
+ ptr[3] = 255;
+ if (col == 0 && row > 0) {
+ // Duplicate the first value in a row as a last value of the preceding row.
+ ptr[-4] = ptr[0];
+ ptr[-3] = ptr[1];
+ ptr[-2] = ptr[2];
+ ptr[-1] = ptr[3];
+ }
+ }
+ if (level_of_detail_2nd_level) {
+ cell_first = clamp(0, ncells1-1, int(ceil(lo * z_to_cell1)));
+ cell_last = clamp(0, ncells1-1, int(floor(hi * z_to_cell1)));
+ for (int cell = cell_first; cell <= cell_last; ++ cell) {
+ coordf_t idxf = (0.5 * hscale + (h - slicing_params.layer_height)) * coordf_t(palette_raw.size()-1) / hscale;
+ int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf)));
+ int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1);
+ coordf_t t = idxf - coordf_t(idx1);
+ const Vec3crd &color1 = palette_raw[idx1];
+ const Vec3crd &color2 = palette_raw[idx2];
+ // Color mapping from layer height to RGB.
+ Vec3d color(
+ lerp(coordf_t(color1(0)), coordf_t(color2(0)), t),
+ lerp(coordf_t(color1(1)), coordf_t(color2(1)), t),
+ lerp(coordf_t(color1(2)), coordf_t(color2(2)), t));
+ int row = cell / (cols1 - 1);
+ int col = cell - row * (cols1 - 1);
+ assert(row >= 0 && row < rows/2);
+ assert(col >= 0 && col < cols/2);
+ unsigned char *ptr = data1 + (row * cols1 + col) * 4;
+ ptr[0] = (unsigned char)clamp<int>(0, 255, int(floor(color(0) + 0.5)));
+ ptr[1] = (unsigned char)clamp<int>(0, 255, int(floor(color(1) + 0.5)));
+ ptr[2] = (unsigned char)clamp<int>(0, 255, int(floor(color(2) + 0.5)));
+ ptr[3] = 255;
+ if (col == 0 && row > 0) {
+ // Duplicate the first value in a row as a last value of the preceding row.
+ ptr[-4] = ptr[0];
+ ptr[-3] = ptr[1];
+ ptr[-2] = ptr[2];
+ ptr[-1] = ptr[3];
+ }
+ }
+ }
+ }
+
+ // Returns number of cells of the 0th LOD level.
+ return ncells;
+}
+
+}; // namespace Slic3r
diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp
new file mode 100644
index 000000000..b4a074bb5
--- /dev/null
+++ b/src/libslic3r/Slicing.hpp
@@ -0,0 +1,164 @@
+// Based on implementation by @platsch
+
+#ifndef slic3r_Slicing_hpp_
+#define slic3r_Slicing_hpp_
+
+#include <set>
+#include <vector>
+
+#include "libslic3r.h"
+namespace Slic3r
+{
+
+class PrintConfig;
+class PrintObjectConfig;
+class ModelVolume;
+typedef std::vector<ModelVolume*> ModelVolumePtrs;
+
+// Parameters to guide object slicing and support generation.
+// The slicing parameters account for a raft and whether the 1st object layer is printed with a normal or a bridging flow
+// (using a normal flow over a soluble support, using a bridging flow over a non-soluble support).
+struct SlicingParameters
+{
+ SlicingParameters() { memset(this, 0, sizeof(SlicingParameters)); }
+
+ static SlicingParameters create_from_config(
+ const PrintConfig &print_config,
+ const PrintObjectConfig &object_config,
+ coordf_t object_height,
+ const std::vector<unsigned int> &object_extruders);
+
+ // Has any raft layers?
+ bool has_raft() const { return raft_layers() > 0; }
+ size_t raft_layers() const { return base_raft_layers + interface_raft_layers; }
+
+ // Is the 1st object layer height fixed, or could it be varied?
+ bool first_object_layer_height_fixed() const { return ! has_raft() || first_object_layer_bridging; }
+
+ // Height of the object to be printed. This value does not contain the raft height.
+ coordf_t object_print_z_height() const { return object_print_z_max - object_print_z_min; }
+
+ // Number of raft layers.
+ size_t base_raft_layers;
+ // Number of interface layers including the contact layer.
+ size_t interface_raft_layers;
+
+ // Layer heights of the raft (base, interface and a contact layer).
+ coordf_t base_raft_layer_height;
+ coordf_t interface_raft_layer_height;
+ coordf_t contact_raft_layer_height;
+ bool contact_raft_layer_height_bridging;
+
+ // The regular layer height, applied for all but the first layer, if not overridden by layer ranges
+ // or by the variable layer thickness table.
+ coordf_t layer_height;
+ // Minimum / maximum layer height, to be used for the automatic adaptive layer height algorithm,
+ // or by an interactive layer height editor.
+ coordf_t min_layer_height;
+ coordf_t max_layer_height;
+ coordf_t max_suport_layer_height;
+
+ // First layer height of the print, this may be used for the first layer of the raft
+ // or for the first layer of the print.
+ coordf_t first_print_layer_height;
+
+ // Thickness of the first layer. This is either the first print layer thickness if printed without a raft,
+ // or a bridging flow thickness if printed over a non-soluble raft,
+ // or a normal layer height if printed over a soluble raft.
+ coordf_t first_object_layer_height;
+
+ // If the object is printed over a non-soluble raft, the first layer may be printed with a briding flow.
+ bool first_object_layer_bridging;
+
+ // Soluble interface? (PLA soluble in water, HIPS soluble in lemonen)
+ // otherwise the interface must be broken off.
+ bool soluble_interface;
+ // Gap when placing object over raft.
+ coordf_t gap_raft_object;
+ // Gap when placing support over object.
+ coordf_t gap_object_support;
+ // Gap when placing object over support.
+ coordf_t gap_support_object;
+
+ // Bottom and top of the printed object.
+ // If printed without a raft, object_print_z_min = 0 and object_print_z_max = object height.
+ // Otherwise object_print_z_min is equal to the raft height.
+ coordf_t raft_base_top_z;
+ coordf_t raft_interface_top_z;
+ coordf_t raft_contact_top_z;
+ // In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer.
+ coordf_t object_print_z_min;
+ coordf_t object_print_z_max;
+};
+
+// The two slicing parameters lead to the same layering as long as the variable layer thickness is not in action.
+inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters &sp2)
+{
+ return sp1.base_raft_layers == sp2.base_raft_layers &&
+ sp1.interface_raft_layers == sp2.interface_raft_layers &&
+ sp1.base_raft_layer_height == sp2.base_raft_layer_height &&
+ sp1.interface_raft_layer_height == sp2.interface_raft_layer_height &&
+ sp1.contact_raft_layer_height == sp2.contact_raft_layer_height &&
+ sp1.contact_raft_layer_height_bridging == sp2.contact_raft_layer_height_bridging &&
+ sp1.layer_height == sp2.layer_height &&
+ sp1.min_layer_height == sp2.min_layer_height &&
+ sp1.max_layer_height == sp2.max_layer_height &&
+// sp1.max_suport_layer_height == sp2.max_suport_layer_height &&
+ sp1.first_print_layer_height == sp2.first_print_layer_height &&
+ sp1.first_object_layer_height == sp2.first_object_layer_height &&
+ sp1.first_object_layer_bridging == sp2.first_object_layer_bridging &&
+ sp1.soluble_interface == sp2.soluble_interface &&
+ sp1.gap_raft_object == sp2.gap_raft_object &&
+ sp1.gap_object_support == sp2.gap_object_support &&
+ sp1.gap_support_object == sp2.gap_support_object &&
+ sp1.raft_base_top_z == sp2.raft_base_top_z &&
+ sp1.raft_interface_top_z == sp2.raft_interface_top_z &&
+ sp1.raft_contact_top_z == sp2.raft_contact_top_z &&
+ sp1.object_print_z_min == sp2.object_print_z_min;
+}
+
+typedef std::pair<coordf_t,coordf_t> t_layer_height_range;
+typedef std::map<t_layer_height_range,coordf_t> t_layer_height_ranges;
+
+extern std::vector<coordf_t> layer_height_profile_from_ranges(
+ const SlicingParameters &slicing_params,
+ const t_layer_height_ranges &layer_height_ranges);
+
+extern std::vector<coordf_t> layer_height_profile_adaptive(
+ const SlicingParameters &slicing_params,
+ const t_layer_height_ranges &layer_height_ranges,
+ const ModelVolumePtrs &volumes);
+
+
+enum LayerHeightEditActionType {
+ LAYER_HEIGHT_EDIT_ACTION_INCREASE = 0,
+ LAYER_HEIGHT_EDIT_ACTION_DECREASE = 1,
+ LAYER_HEIGHT_EDIT_ACTION_REDUCE = 2,
+ LAYER_HEIGHT_EDIT_ACTION_SMOOTH = 3
+};
+
+extern void adjust_layer_height_profile(
+ const SlicingParameters &slicing_params,
+ std::vector<coordf_t> &layer_height_profile,
+ coordf_t z,
+ coordf_t layer_thickness_delta,
+ coordf_t band_width,
+ LayerHeightEditActionType action);
+
+// Produce object layers as pairs of low / high layer boundaries, stored into a linear vector.
+// The object layers are based at z=0, ignoring the raft layers.
+extern std::vector<coordf_t> generate_object_layers(
+ const SlicingParameters &slicing_params,
+ const std::vector<coordf_t> &layer_height_profile);
+
+// Produce a 1D texture packed into a 2D texture describing in the RGBA format
+// the planned object layers.
+// Returns number of cells used by the texture of the 0th LOD level.
+extern int generate_layer_height_texture(
+ const SlicingParameters &slicing_params,
+ const std::vector<coordf_t> &layers,
+ void *data, int rows, int cols, bool level_of_detail_2nd_level);
+
+}; // namespace Slic3r
+
+#endif /* slic3r_Slicing_hpp_ */
diff --git a/src/libslic3r/SlicingAdaptive.cpp b/src/libslic3r/SlicingAdaptive.cpp
new file mode 100644
index 000000000..2ef4aec8c
--- /dev/null
+++ b/src/libslic3r/SlicingAdaptive.cpp
@@ -0,0 +1,140 @@
+#include "libslic3r.h"
+#include "TriangleMesh.hpp"
+#include "SlicingAdaptive.hpp"
+
+namespace Slic3r
+{
+
+void SlicingAdaptive::clear()
+{
+ m_meshes.clear();
+ m_faces.clear();
+ m_face_normal_z.clear();
+}
+
+std::pair<float, float> face_z_span(const stl_facet *f)
+{
+ return std::pair<float, float>(
+ std::min(std::min(f->vertex[0](2), f->vertex[1](2)), f->vertex[2](2)),
+ std::max(std::max(f->vertex[0](2), f->vertex[1](2)), f->vertex[2](2)));
+}
+
+void SlicingAdaptive::prepare()
+{
+ // 1) Collect faces of all meshes.
+ int nfaces_total = 0;
+ for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh)
+ nfaces_total += (*it_mesh)->stl.stats.number_of_facets;
+ m_faces.reserve(nfaces_total);
+ for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh)
+ for (int i = 0; i < (*it_mesh)->stl.stats.number_of_facets; ++ i)
+ m_faces.push_back((*it_mesh)->stl.facet_start + i);
+
+ // 2) Sort faces lexicographically by their Z span.
+ std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) {
+ std::pair<float, float> span1 = face_z_span(f1);
+ std::pair<float, float> span2 = face_z_span(f2);
+ return span1 < span2;
+ });
+
+ // 3) Generate Z components of the facet normals.
+ m_face_normal_z.assign(m_faces.size(), 0.f);
+ for (size_t iface = 0; iface < m_faces.size(); ++ iface)
+ m_face_normal_z[iface] = m_faces[iface]->normal(2);
+}
+
+float SlicingAdaptive::cusp_height(float z, float cusp_value, int &current_facet)
+{
+ float height = m_slicing_params.max_layer_height;
+ bool first_hit = false;
+
+ // find all facets intersecting the slice-layer
+ int ordered_id = current_facet;
+ for (; ordered_id < int(m_faces.size()); ++ ordered_id) {
+ std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]);
+ // facet's minimum is higher than slice_z -> end loop
+ if (zspan.first >= z)
+ break;
+ // facet's maximum is higher than slice_z -> store the first event for next cusp_height call to begin at this point
+ if (zspan.second > z) {
+ // first event?
+ if (! first_hit) {
+ first_hit = true;
+ current_facet = ordered_id;
+ }
+ // skip touching facets which could otherwise cause small cusp values
+ if (zspan.second <= z + EPSILON)
+ continue;
+ // compute cusp-height for this facet and store minimum of all heights
+ float normal_z = m_face_normal_z[ordered_id];
+ height = std::min(height, (normal_z == 0.f) ? 9999.f : std::abs(cusp_value / normal_z));
+ }
+ }
+
+ // lower height limit due to printer capabilities
+ height = std::max(height, float(m_slicing_params.min_layer_height));
+
+ // check for sloped facets inside the determined layer and correct height if necessary
+ if (height > m_slicing_params.min_layer_height) {
+ for (; ordered_id < int(m_faces.size()); ++ ordered_id) {
+ std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]);
+ // facet's minimum is higher than slice_z + height -> end loop
+ if (zspan.first >= z + height)
+ break;
+
+ // skip touching facets which could otherwise cause small cusp values
+ if (zspan.second <= z + EPSILON)
+ continue;
+
+ // Compute cusp-height for this facet and check against height.
+ float normal_z = m_face_normal_z[ordered_id];
+ float cusp = (normal_z == 0) ? 9999 : abs(cusp_value / normal_z);
+
+ float z_diff = zspan.first - z;
+
+ // handle horizontal facets
+ if (m_face_normal_z[ordered_id] > 0.999) {
+ // Slic3r::debugf "cusp computation, height is reduced from %f", $height;
+ height = z_diff;
+ // Slic3r::debugf "to %f due to near horizontal facet\n", $height;
+ } else if (cusp > z_diff) {
+ if (cusp < height) {
+ // Slic3r::debugf "cusp computation, height is reduced from %f", $height;
+ height = cusp;
+ // Slic3r::debugf "to %f due to new cusp height\n", $height;
+ }
+ } else {
+ // Slic3r::debugf "cusp computation, height is reduced from %f", $height;
+ height = z_diff;
+ // Slic3r::debugf "to z-diff: %f\n", $height;
+ }
+ }
+ // lower height limit due to printer capabilities again
+ height = std::max(height, float(m_slicing_params.min_layer_height));
+ }
+
+// Slic3r::debugf "cusp computation, layer-bottom at z:%f, cusp_value:%f, resulting layer height:%f\n", unscale $z, $cusp_value, $height;
+ return height;
+}
+
+// Returns the distance to the next horizontal facet in Z-dir
+// to consider horizontal object features in slice thickness
+float SlicingAdaptive::horizontal_facet_distance(float z)
+{
+ for (size_t i = 0; i < m_faces.size(); ++ i) {
+ std::pair<float, float> zspan = face_z_span(m_faces[i]);
+ // facet's minimum is higher than max forward distance -> end loop
+ if (zspan.first > z + m_slicing_params.max_layer_height)
+ break;
+ // min_z == max_z -> horizontal facet
+ if (zspan.first > z && zspan.first == zspan.second)
+ return zspan.first - z;
+ }
+
+ // objects maximum?
+ return (z + m_slicing_params.max_layer_height > m_slicing_params.object_print_z_height()) ?
+ std::max<float>(m_slicing_params.object_print_z_height() - z, 0.f) :
+ m_slicing_params.max_layer_height;
+}
+
+}; // namespace Slic3r
diff --git a/src/libslic3r/SlicingAdaptive.hpp b/src/libslic3r/SlicingAdaptive.hpp
new file mode 100644
index 000000000..bfd081d81
--- /dev/null
+++ b/src/libslic3r/SlicingAdaptive.hpp
@@ -0,0 +1,36 @@
+// Based on implementation by @platsch
+
+#ifndef slic3r_SlicingAdaptive_hpp_
+#define slic3r_SlicingAdaptive_hpp_
+
+#include "Slicing.hpp"
+#include "admesh/stl.h"
+
+namespace Slic3r
+{
+
+class TriangleMesh;
+
+class SlicingAdaptive
+{
+public:
+ void clear();
+ void set_slicing_parameters(SlicingParameters params) { m_slicing_params = params; }
+ void add_mesh(const TriangleMesh *mesh) { m_meshes.push_back(mesh); }
+ void prepare();
+ float cusp_height(float z, float cusp_value, int &current_facet);
+ float horizontal_facet_distance(float z);
+
+protected:
+ SlicingParameters m_slicing_params;
+
+ std::vector<const TriangleMesh*> m_meshes;
+ // Collected faces of all meshes, sorted by raising Z of the bottom most face.
+ std::vector<const stl_facet*> m_faces;
+ // Z component of face normals, normalized.
+ std::vector<float> m_face_normal_z;
+};
+
+}; // namespace Slic3r
+
+#endif /* slic3r_SlicingAdaptive_hpp_ */
diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp
new file mode 100644
index 000000000..2bcf597e6
--- /dev/null
+++ b/src/libslic3r/SupportMaterial.cpp
@@ -0,0 +1,3224 @@
+#include "ClipperUtils.hpp"
+#include "ExtrusionEntityCollection.hpp"
+#include "PerimeterGenerator.hpp"
+#include "Layer.hpp"
+#include "Print.hpp"
+#include "SupportMaterial.hpp"
+#include "Fill/FillBase.hpp"
+#include "EdgeGrid.hpp"
+#include "Geometry.hpp"
+
+#include <cmath>
+#include <memory>
+#include <boost/log/trivial.hpp>
+
+#include <tbb/parallel_for.h>
+#include <tbb/atomic.h>
+#include <tbb/spin_mutex.h>
+#include <tbb/task_group.h>
+
+// #define SLIC3R_DEBUG
+
+// Make assert active if SLIC3R_DEBUG
+#ifdef SLIC3R_DEBUG
+ #define DEBUG
+ #define _DEBUG
+ #undef NDEBUG
+ #include "SVG.hpp"
+#endif
+
+// #undef NDEBUG
+#include <cassert>
+
+namespace Slic3r {
+
+// Increment used to reach MARGIN in steps to avoid trespassing thin objects
+#define NUM_MARGIN_STEPS 3
+
+// Dimensions of a tree-like structure to save material
+#define PILLAR_SIZE (2.5)
+#define PILLAR_SPACING 10
+
+//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
+//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
+#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
+
+#ifdef SLIC3R_DEBUG
+const char* support_surface_type_to_color_name(const PrintObjectSupportMaterial::SupporLayerType surface_type)
+{
+ switch (surface_type) {
+ case PrintObjectSupportMaterial::sltTopContact: return "rgb(255,0,0)"; // "red";
+ case PrintObjectSupportMaterial::sltTopInterface: return "rgb(0,255,0)"; // "green";
+ case PrintObjectSupportMaterial::sltBase: return "rgb(0,0,255)"; // "blue";
+ case PrintObjectSupportMaterial::sltBottomInterface:return "rgb(255,255,128)"; // yellow
+ case PrintObjectSupportMaterial::sltBottomContact: return "rgb(255,0,255)"; // magenta
+ case PrintObjectSupportMaterial::sltRaftInterface: return "rgb(0,255,255)";
+ case PrintObjectSupportMaterial::sltRaftBase: return "rgb(128,128,128)";
+ case PrintObjectSupportMaterial::sltUnknown: return "rgb(128,0,0)"; // maroon
+ default: return "rgb(64,64,64)";
+ };
+}
+
+Point export_support_surface_type_legend_to_svg_box_size()
+{
+ return Point(scale_(1.+10.*8.), scale_(3.));
+}
+
+void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos)
+{
+ // 1st row
+ coord_t pos_x0 = pos(0) + scale_(1.);
+ coord_t pos_x = pos_x0;
+ coord_t pos_y = pos(1) + scale_(1.5);
+ coord_t step_x = scale_(10.);
+ svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopContact));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopInterface));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBase));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomInterface));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomContact));
+ // 2nd row
+ pos_x = pos_x0;
+ pos_y = pos(1)+scale_(2.8);
+ svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftInterface));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftBase));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltUnknown));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltIntermediate));
+}
+
+void export_print_z_polygons_to_svg(const char *path, PrintObjectSupportMaterial::MyLayer ** const layers, size_t n_layers)
+{
+ BoundingBox bbox;
+ for (int i = 0; i < n_layers; ++ i)
+ bbox.merge(get_extents(layers[i]->polygons));
+ Point legend_size = export_support_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 (int i = 0; i < n_layers; ++ i)
+ svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency);
+ for (int i = 0; i < n_layers; ++ i)
+ svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type));
+ export_support_surface_type_legend_to_svg(svg, legend_pos);
+ svg.Close();
+}
+
+void export_print_z_polygons_and_extrusions_to_svg(
+ const char *path,
+ PrintObjectSupportMaterial::MyLayer ** const layers,
+ size_t n_layers,
+ SupportLayer &support_layer)
+{
+ BoundingBox bbox;
+ for (int i = 0; i < n_layers; ++ i)
+ bbox.merge(get_extents(layers[i]->polygons));
+ Point legend_size = export_support_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 (int i = 0; i < n_layers; ++ i)
+ svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency);
+ for (int i = 0; i < n_layers; ++ i)
+ svg.draw(to_polylines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type));
+
+ Polygons polygons_support, polygons_interface;
+ support_layer.support_fills.polygons_covered_by_width(polygons_support, SCALED_EPSILON);
+// support_layer.support_interface_fills.polygons_covered_by_width(polygons_interface, SCALED_EPSILON);
+ svg.draw(union_ex(polygons_support), "brown");
+ svg.draw(union_ex(polygons_interface), "black");
+
+ export_support_surface_type_legend_to_svg(svg, legend_pos);
+ svg.Close();
+}
+#endif /* SLIC3R_DEBUG */
+
+PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) :
+ m_object (object),
+ m_print_config (&object->print()->config()),
+ m_object_config (&object->config()),
+ m_slicing_params (slicing_params),
+ m_first_layer_flow (support_material_1st_layer_flow(object, float(slicing_params.first_print_layer_height))),
+ m_support_material_flow (support_material_flow(object, float(slicing_params.layer_height))),
+ m_support_material_interface_flow(support_material_interface_flow(object, float(slicing_params.layer_height))),
+ m_support_layer_height_min(0.01)
+{
+ // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
+ m_support_layer_height_min = 1000000.;
+ for (auto lh : m_print_config->min_layer_height.values)
+ m_support_layer_height_min = std::min(m_support_layer_height_min, std::max(0.01, lh));
+
+ if (m_object_config->support_material_interface_layers.value == 0) {
+ // No interface layers allowed, print everything with the base support pattern.
+ m_support_material_interface_flow = m_support_material_flow;
+ }
+
+ // Evaluate the XY gap between the object outer perimeters and the support structures.
+ coordf_t external_perimeter_width = 0.;
+ for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
+ if (! object->region_volumes[region_id].empty()) {
+ const PrintRegionConfig &config = object->print()->get_region(region_id)->config();
+ coordf_t width = config.external_perimeter_extrusion_width.get_abs_value(slicing_params.layer_height);
+ if (width <= 0.)
+ width = m_print_config->nozzle_diameter.get_at(config.perimeter_extruder-1);
+ external_perimeter_width = std::max(external_perimeter_width, width);
+ }
+ }
+ m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width);
+
+ m_can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value;
+ if (! m_can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) {
+ // One of the support extruders is of "don't care" type.
+ auto object_extruders = m_object->print()->object_extruders();
+ if (object_extruders.size() == 1 &&
+ *object_extruders.begin() == std::max<unsigned int>(m_object_config->support_material_extruder.value, m_object_config->support_material_interface_extruder.value))
+ // Object is printed with the same extruder as the support.
+ m_can_merge_support_regions = true;
+ }
+}
+
+// Using the std::deque as an allocator.
+inline PrintObjectSupportMaterial::MyLayer& layer_allocate(
+ std::deque<PrintObjectSupportMaterial::MyLayer> &layer_storage,
+ PrintObjectSupportMaterial::SupporLayerType layer_type)
+{
+ layer_storage.push_back(PrintObjectSupportMaterial::MyLayer());
+ layer_storage.back().layer_type = layer_type;
+ return layer_storage.back();
+}
+
+inline PrintObjectSupportMaterial::MyLayer& layer_allocate(
+ std::deque<PrintObjectSupportMaterial::MyLayer> &layer_storage,
+ tbb::spin_mutex &layer_storage_mutex,
+ PrintObjectSupportMaterial::SupporLayerType layer_type)
+{
+ layer_storage_mutex.lock();
+ layer_storage.push_back(PrintObjectSupportMaterial::MyLayer());
+ PrintObjectSupportMaterial::MyLayer *layer_new = &layer_storage.back();
+ layer_storage_mutex.unlock();
+ layer_new->layer_type = layer_type;
+ return *layer_new;
+}
+
+inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const PrintObjectSupportMaterial::MyLayersPtr &src)
+{
+ dst.insert(dst.end(), src.begin(), src.end());
+}
+
+// Compare layers lexicographically.
+struct MyLayersPtrCompare
+{
+ bool operator()(const PrintObjectSupportMaterial::MyLayer* layer1, const PrintObjectSupportMaterial::MyLayer* layer2) const {
+ return *layer1 < *layer2;
+ }
+};
+
+void PrintObjectSupportMaterial::generate(PrintObject &object)
+{
+ BOOST_LOG_TRIVIAL(info) << "Support generator - Start";
+
+ coordf_t max_object_layer_height = 0.;
+ for (size_t i = 0; i < object.layer_count(); ++ i)
+ max_object_layer_height = std::max(max_object_layer_height, object.layers()[i]->height);
+
+ // Layer instances will be allocated by std::deque and they will be kept until the end of this function call.
+ // The layers will be referenced by various LayersPtr (of type std::vector<Layer*>)
+ MyLayerStorage layer_storage;
+
+ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts";
+
+ // Determine the top contact surfaces of the support, defined as:
+ // contact = overhangs - clearance + margin
+ // This method is responsible for identifying what contact surfaces
+ // should the support material expose to the object in order to guarantee
+ // that it will be effective, regardless of how it's built below.
+ // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette without holes.
+ MyLayersPtr top_contacts = this->top_contact_layers(object, layer_storage);
+ if (top_contacts.empty())
+ // Nothing is supported, no supports are generated.
+ return;
+
+#ifdef SLIC3R_DEBUG
+ static int iRun = 0;
+ iRun ++;
+ for (const MyLayer *layer : top_contacts)
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer->print_z),
+ union_ex(layer->polygons, false));
+#endif /* SLIC3R_DEBUG */
+
+ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts";
+
+ // Determine the bottom contact surfaces of the supports over the top surfaces of the object.
+ // Depending on whether the support is soluble or not, the contact layer thickness is decided.
+ // layer_support_areas contains the per object layer support areas. These per object layer support areas
+ // may get merged and trimmed by this->generate_base_layers() if the support layers are not synchronized with object layers.
+ std::vector<Polygons> layer_support_areas;
+ MyLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas(
+ object, top_contacts, layer_storage,
+ layer_support_areas);
+
+#ifdef SLIC3R_DEBUG
+ for (size_t layer_id = 0; layer_id < object.layers().size(); ++ layer_id)
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-areas-%d-%lf.svg", iRun, object.layers()[layer_id]->print_z),
+ union_ex(layer_support_areas[layer_id], false));
+#endif /* SLIC3R_DEBUG */
+
+ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating intermediate layers - indices";
+
+ // Allocate empty layers between the top / bottom support contact layers
+ // as placeholders for the base and intermediate support layers.
+ // The layers may or may not be synchronized with the object layers, depending on the configuration.
+ // For example, a single nozzle multi material printing will need to generate a waste tower, which in turn
+ // wastes less material, if there are as little tool changes as possible.
+ MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers(
+ object, bottom_contacts, top_contacts, layer_storage);
+
+// this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy);
+ this->trim_support_layers_by_object(object, top_contacts,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
+
+#ifdef SLIC3R_DEBUG
+ for (const MyLayer *layer : top_contacts)
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-top-contacts-trimmed-by-object-%d-%lf.svg", iRun, layer->print_z),
+ union_ex(layer->polygons, false));
+#endif
+
+ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers";
+
+ // Fill in intermediate layers between the top / bottom support contact layers, trimm them by the object.
+ this->generate_base_layers(object, bottom_contacts, top_contacts, intermediate_layers, layer_support_areas);
+
+#ifdef SLIC3R_DEBUG
+ for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it)
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-base-layers-%d-%lf.svg", iRun, (*it)->print_z),
+ union_ex((*it)->polygons, false));
+#endif /* SLIC3R_DEBUG */
+
+ BOOST_LOG_TRIVIAL(info) << "Support generator - Trimming top contacts by bottom contacts";
+
+ // Because the top and bottom contacts are thick slabs, they may overlap causing over extrusion
+ // and unwanted strong bonds to the object.
+ // Rather trim the top contacts by their overlapping bottom contacts to leave a gap instead of over extruding
+ // top contacts over the bottom contacts.
+ this->trim_top_contacts_by_bottom_contacts(object, bottom_contacts, top_contacts);
+
+
+ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating interfaces";
+
+ // Propagate top / bottom contact layers to generate interface layers.
+ MyLayersPtr interface_layers = this->generate_interface_layers(
+ bottom_contacts, top_contacts, intermediate_layers, layer_storage);
+
+ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating raft";
+
+ // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled.
+ // There is also a 1st intermediate layer containing bases of support columns.
+ // Inflate the bases of the support columns and create the raft base under the object.
+ MyLayersPtr raft_layers = this->generate_raft_base(top_contacts, interface_layers, intermediate_layers, layer_storage);
+
+#ifdef SLIC3R_DEBUG
+ for (MyLayersPtr::const_iterator it = interface_layers.begin(); it != interface_layers.end(); ++ it)
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-interface-layers-%d-%lf.svg", iRun, (*it)->print_z),
+ union_ex((*it)->polygons, false));
+#endif /* SLIC3R_DEBUG */
+
+/*
+ // Clip with the pillars.
+ if (! shape.empty()) {
+ this->clip_with_shape(interface, shape);
+ this->clip_with_shape(base, shape);
+ }
+*/
+
+ BOOST_LOG_TRIVIAL(info) << "Support generator - Creating layers";
+
+// For debugging purposes, one may want to show only some of the support extrusions.
+// raft_layers.clear();
+// bottom_contacts.clear();
+// top_contacts.clear();
+// intermediate_layers.clear();
+// interface_layers.clear();
+
+ // Install support layers into the object.
+ // A support layer installed on a PrintObject has a unique print_z.
+ MyLayersPtr layers_sorted;
+ layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size());
+ layers_append(layers_sorted, raft_layers);
+ layers_append(layers_sorted, bottom_contacts);
+ layers_append(layers_sorted, top_contacts);
+ layers_append(layers_sorted, intermediate_layers);
+ layers_append(layers_sorted, interface_layers);
+ // Sort the layers lexicographically by a raising print_z and a decreasing height.
+ std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare());
+ int layer_id = 0;
+ assert(object.support_layers().empty());
+ for (int i = 0; i < int(layers_sorted.size());) {
+ // Find the last layer with roughly the same print_z, find the minimum layer height of all.
+ // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should.
+ int j = i + 1;
+ coordf_t zmax = layers_sorted[i]->print_z + EPSILON;
+ for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ;
+ // Assign an average print_z to the set of layers with nearly equal print_z.
+ coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z);
+ coordf_t height_min = layers_sorted[i]->height;
+ bool empty = true;
+ for (int u = i; u < j; ++u) {
+ MyLayer &layer = *layers_sorted[u];
+ if (! layer.polygons.empty())
+ empty = false;
+ layer.print_z = zavg;
+ height_min = std::min(height_min, layer.height);
+ }
+ if (! empty) {
+ // Here the upper_layer and lower_layer pointers are left to null at the support layers,
+ // as they are never used. These pointers are candidates for removal.
+ object.add_support_layer(layer_id ++, height_min, zavg);
+ }
+ i = j;
+ }
+
+ BOOST_LOG_TRIVIAL(info) << "Support generator - Generating tool paths";
+
+ // Generate the actual toolpaths and save them into each layer.
+ this->generate_toolpaths(object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers);
+
+#ifdef SLIC3R_DEBUG
+ {
+ size_t layer_id = 0;
+ for (int i = 0; i < int(layers_sorted.size());) {
+ // Find the last layer with roughly the same print_z, find the minimum layer height of all.
+ // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should.
+ int j = i + 1;
+ coordf_t zmax = layers_sorted[i]->print_z + EPSILON;
+ bool empty = true;
+ for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j)
+ if (! layers_sorted[j]->polygons.empty())
+ empty = false;
+ if (! empty) {
+ export_print_z_polygons_to_svg(
+ debug_out_path("support-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(),
+ layers_sorted.data() + i, j - i);
+ export_print_z_polygons_and_extrusions_to_svg(
+ debug_out_path("support-w-fills-%d-%lf.svg", iRun, layers_sorted[i]->print_z).c_str(),
+ layers_sorted.data() + i, j - i,
+ *object.support_layers[layer_id]);
+ ++layer_id;
+ }
+ i = j;
+ }
+ }
+#endif /* SLIC3R_DEBUG */
+
+ BOOST_LOG_TRIVIAL(info) << "Support generator - End";
+}
+
+// Collect all polygons of all regions in a layer with a given surface type.
+Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type)
+{
+ // 1) Count the new polygons first.
+ size_t n_polygons_new = 0;
+ for (const LayerRegion *region : layer.regions())
+ for (const Surface &surface : region->slices.surfaces)
+ if (surface.surface_type == surface_type)
+ n_polygons_new += surface.expolygon.holes.size() + 1;
+ // 2) Collect the new polygons.
+ Polygons out;
+ out.reserve(n_polygons_new);
+ for (const LayerRegion *region : layer.regions())
+ for (const Surface &surface : region->slices.surfaces)
+ if (surface.surface_type == surface_type)
+ polygons_append(out, surface.expolygon);
+ return out;
+}
+
+// Collect outer contours of all slices of this layer.
+// This is useful for calculating the support base with holes filled.
+Polygons collect_slices_outer(const Layer &layer)
+{
+ Polygons out;
+ out.reserve(out.size() + layer.slices.expolygons.size());
+ for (const ExPolygon &expoly : layer.slices.expolygons)
+ out.emplace_back(expoly.contour);
+ return out;
+}
+
+class SupportGridPattern
+{
+public:
+ SupportGridPattern(
+ // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy)
+ const Polygons &support_polygons,
+ // Trimming polygons, to trim the stretched support islands. support_polygons were already trimmed with trimming_polygons.
+ const Polygons &trimming_polygons,
+ // Grid spacing, given by "support_material_spacing" + m_support_material_flow.spacing()
+ coordf_t support_spacing,
+ coordf_t support_angle) :
+ m_support_polygons(&support_polygons), m_trimming_polygons(&trimming_polygons),
+ m_support_spacing(support_spacing), m_support_angle(support_angle)
+ {
+ if (m_support_angle != 0.) {
+ // Create a copy of the rotated contours.
+ m_support_polygons_rotated = support_polygons;
+ m_trimming_polygons_rotated = trimming_polygons;
+ m_support_polygons = &m_support_polygons_rotated;
+ m_trimming_polygons = &m_trimming_polygons_rotated;
+ polygons_rotate(m_support_polygons_rotated, - support_angle);
+ polygons_rotate(m_trimming_polygons_rotated, - support_angle);
+ }
+ // Create an EdgeGrid, initialize it with projection, initialize signed distance field.
+ coord_t grid_resolution = coord_t(scale_(m_support_spacing));
+ BoundingBox bbox = get_extents(*m_support_polygons);
+ bbox.offset(20);
+ bbox.align_to_grid(grid_resolution);
+ m_grid.set_bbox(bbox);
+ m_grid.create(*m_support_polygons, grid_resolution);
+ m_grid.calculate_sdf();
+ // Sample a single point per input support polygon, keep it as a reference to maintain corresponding
+ // polygons if ever these polygons get split into parts by the trimming polygons.
+ m_island_samples = island_samples(*m_support_polygons);
+ }
+
+ // Extract polygons from the grid, offsetted by offset_in_grid,
+ // and trim the extracted polygons by trimming_polygons.
+ // Trimming by the trimming_polygons may split the extracted polygons into pieces.
+ // Remove all the pieces, which do not contain any of the island_samples.
+ Polygons extract_support(const coord_t offset_in_grid, bool fill_holes)
+ {
+ // Generate islands, so each island may be tested for overlap with m_island_samples.
+ assert(std::abs(2 * offset_in_grid) < m_grid.resolution());
+ ExPolygons islands = diff_ex(
+ m_grid.contours_simplified(offset_in_grid, fill_holes),
+ *m_trimming_polygons, false);
+
+ // Extract polygons, which contain some of the m_island_samples.
+ Polygons out;
+ for (ExPolygon &island : islands) {
+ BoundingBox bbox = get_extents(island.contour);
+ // Samples are sorted lexicographically.
+ auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), Point(bbox.min - Point(1, 1)));
+ auto it_upper = std::upper_bound(m_island_samples.begin(), m_island_samples.end(), Point(bbox.max + Point(1, 1)));
+ std::vector<std::pair<Point,bool>> samples_inside;
+ for (auto it = it_lower; it != it_upper; ++ it)
+ if (bbox.contains(*it))
+ samples_inside.push_back(std::make_pair(*it, false));
+ if (! samples_inside.empty()) {
+ // For all samples_inside count the boundary crossing.
+ for (size_t i_contour = 0; i_contour <= island.holes.size(); ++ i_contour) {
+ Polygon &contour = (i_contour == 0) ? island.contour : island.holes[i_contour - 1];
+ Points::const_iterator i = contour.points.begin();
+ Points::const_iterator j = contour.points.end() - 1;
+ for (; i != contour.points.end(); j = i ++) {
+ //FIXME this test is not numerically robust. Particularly, it does not handle horizontal segments at y == point(1) well.
+ // Does the ray with y == point(1) intersect this line segment?
+ for (auto &sample_inside : samples_inside) {
+ if (((*i)(1) > sample_inside.first(1)) != ((*j)(1) > sample_inside.first(1))) {
+ double x1 = (double)sample_inside.first(0);
+ double x2 = (double)(*i)(0) + (double)((*j)(0) - (*i)(0)) * (double)(sample_inside.first(1) - (*i)(1)) / (double)((*j)(1) - (*i)(1));
+ if (x1 < x2)
+ sample_inside.second = !sample_inside.second;
+ }
+ }
+ }
+ }
+ // If any of the sample is inside this island, add this island to the output.
+ for (auto &sample_inside : samples_inside)
+ if (sample_inside.second) {
+ polygons_append(out, std::move(island));
+ island.clear();
+ break;
+ }
+ }
+ }
+
+ #ifdef SLIC3R_DEBUG
+ static int iRun = 0;
+ ++iRun;
+ BoundingBox bbox = get_extents(*m_trimming_polygons);
+ if (! islands.empty())
+ bbox.merge(get_extents(islands));
+ if (!out.empty())
+ bbox.merge(get_extents(out));
+ SVG svg(debug_out_path("extract_support_from_grid_trimmed-%d.svg", iRun).c_str(), bbox);
+ svg.draw(islands, "red", 0.5f);
+ svg.draw(union_ex(out), "green", 0.5f);
+ svg.draw(union_ex(*m_support_polygons), "blue", 0.5f);
+ svg.draw_outline(islands, "red", "red", scale_(0.05));
+ svg.draw_outline(union_ex(out), "green", "green", scale_(0.05));
+ svg.draw_outline(union_ex(*m_support_polygons), "blue", "blue", scale_(0.05));
+ for (const Point &pt : m_island_samples)
+ svg.draw(pt, "black", coord_t(scale_(0.15)));
+ svg.Close();
+ #endif /* SLIC3R_DEBUG */
+
+ if (m_support_angle != 0.)
+ polygons_rotate(out, m_support_angle);
+ return out;
+ }
+
+private:
+ SupportGridPattern& operator=(const SupportGridPattern &rhs);
+
+#if 0
+ // Get some internal point of an expolygon, to be used as a representative
+ // sample to test, whether this island is inside another island.
+ //FIXME this was quick, but not sufficiently robust.
+ static Point island_sample(const ExPolygon &expoly)
+ {
+ // Find the lowest point lexicographically.
+ const Point *pt_min = &expoly.contour.points.front();
+ for (size_t i = 1; i < expoly.contour.points.size(); ++ i)
+ if (expoly.contour.points[i] < *pt_min)
+ pt_min = &expoly.contour.points[i];
+
+ // Lowest corner will always be convex, in worst case denegenerate with zero angle.
+ const Point &p1 = (pt_min == &expoly.contour.points.front()) ? expoly.contour.points.back() : *(pt_min - 1);
+ const Point &p2 = *pt_min;
+ const Point &p3 = (pt_min == &expoly.contour.points.back()) ? expoly.contour.points.front() : *(pt_min + 1);
+
+ Vector v = (p3 - p2) + (p1 - p2);
+ double l2 = double(v(0))*double(v(0))+double(v(1))*double(v(1));
+ if (l2 == 0.)
+ return p2;
+ double coef = 20. / sqrt(l2);
+ return Point(p2(0) + coef * v(0), p2(1) + coef * v(1));
+ }
+#endif
+
+ // Sample one internal point per expolygon.
+ // FIXME this is quite an overkill to calculate a complete offset just to get a single point, but at least it is robust.
+ static Points island_samples(const ExPolygons &expolygons)
+ {
+ Points pts;
+ pts.reserve(expolygons.size());
+ for (const ExPolygon &expoly : expolygons)
+ if (expoly.contour.points.size() > 2) {
+ #if 0
+ pts.push_back(island_sample(expoly));
+ #else
+ Polygons polygons = offset(expoly, - 20.f);
+ for (const Polygon &poly : polygons)
+ if (! poly.points.empty()) {
+ pts.push_back(poly.points.front());
+ break;
+ }
+ #endif
+ }
+ // Sort the points lexicographically, so a binary search could be used to locate points inside a bounding box.
+ std::sort(pts.begin(), pts.end());
+ return pts;
+ }
+
+ static Points island_samples(const Polygons &polygons)
+ {
+ return island_samples(union_ex(polygons));
+ }
+
+ const Polygons *m_support_polygons;
+ const Polygons *m_trimming_polygons;
+ Polygons m_support_polygons_rotated;
+ Polygons m_trimming_polygons_rotated;
+ // Angle in radians, by which the whole support is rotated.
+ coordf_t m_support_angle;
+ // X spacing of the support lines parallel with the Y axis.
+ coordf_t m_support_spacing;
+
+ Slic3r::EdgeGrid::Grid m_grid;
+ // Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding
+ // to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons.
+ Points m_island_samples;
+};
+
+namespace SupportMaterialInternal {
+ static inline bool has_bridging_perimeters(const ExtrusionLoop &loop)
+ {
+ for (const ExtrusionPath &ep : loop.paths)
+ if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty())
+ return ep.size() >= (ep.is_closed() ? 3 : 2);
+ return false;
+ }
+ static bool has_bridging_perimeters(const ExtrusionEntityCollection &perimeters)
+ {
+ for (const ExtrusionEntity *ee : perimeters.entities) {
+ if (ee->is_collection()) {
+ for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) {
+ assert(! ee2->is_collection());
+ if (ee2->is_loop())
+ if (has_bridging_perimeters(*static_cast<const ExtrusionLoop*>(ee2)))
+ return true;
+ }
+ } else if (ee->is_loop() && has_bridging_perimeters(*static_cast<const ExtrusionLoop*>(ee)))
+ return true;
+ }
+ return false;
+ }
+ static bool has_bridging_fills(const ExtrusionEntityCollection &fills)
+ {
+ for (const ExtrusionEntity *ee : fills.entities) {
+ assert(ee->is_collection());
+ for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) {
+ assert(! ee2->is_collection());
+ assert(! ee2->is_loop());
+ if (ee2->role() == erBridgeInfill)
+ return true;
+ }
+ }
+ return false;
+ }
+ static bool has_bridging_extrusions(const Layer &layer)
+ {
+ for (const LayerRegion *region : layer.regions()) {
+ if (SupportMaterialInternal::has_bridging_perimeters(region->perimeters))
+ return true;
+ if (region->fill_surfaces.has(stBottomBridge) && has_bridging_fills(region->fills))
+ return true;
+ }
+ return false;
+ }
+
+ static inline void collect_bridging_perimeter_areas(const ExtrusionLoop &loop, const float expansion_scaled, Polygons &out)
+ {
+ assert(expansion_scaled >= 0.f);
+ for (const ExtrusionPath &ep : loop.paths)
+ if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) {
+ float exp = 0.5f * scale_(ep.width) + expansion_scaled;
+ if (ep.is_closed()) {
+ if (ep.size() >= 3) {
+ // This is a complete loop.
+ // Add the outer contour first.
+ Polygon poly;
+ poly.points = ep.polyline.points;
+ poly.points.pop_back();
+ if (poly.area() < 0)
+ poly.reverse();
+ polygons_append(out, offset(poly, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ Polygons holes = offset(poly, - exp, SUPPORT_SURFACES_OFFSET_PARAMETERS);
+ polygons_reverse(holes);
+ polygons_append(out, holes);
+ }
+ } else if (ep.size() >= 2) {
+ // Offset the polyline.
+ polygons_append(out, offset(ep.polyline, exp, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ }
+ }
+ }
+ static void collect_bridging_perimeter_areas(const ExtrusionEntityCollection &perimeters, const float expansion_scaled, Polygons &out)
+ {
+ for (const ExtrusionEntity *ee : perimeters.entities) {
+ if (ee->is_collection()) {
+ for (const ExtrusionEntity *ee2 : static_cast<const ExtrusionEntityCollection*>(ee)->entities) {
+ assert(! ee2->is_collection());
+ if (ee2->is_loop())
+ collect_bridging_perimeter_areas(*static_cast<const ExtrusionLoop*>(ee2), expansion_scaled, out);
+ }
+ } else if (ee->is_loop())
+ collect_bridging_perimeter_areas(*static_cast<const ExtrusionLoop*>(ee), expansion_scaled, out);
+ }
+ }
+
+ static void remove_bridges_from_contacts(
+ const PrintConfig &print_config,
+ const Layer &lower_layer,
+ const Polygons &lower_layer_polygons,
+ LayerRegion *layerm,
+ float fw,
+ Polygons &contact_polygons)
+ {
+ // compute the area of bridging perimeters
+ Polygons bridges;
+ {
+ // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported.
+ Polygons lower_grown_slices = offset(lower_layer_polygons,
+ //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width.
+ 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm->region()->config().perimeter_extruder-1))),
+ SUPPORT_SURFACES_OFFSET_PARAMETERS);
+ // Collect perimeters of this layer.
+ //FIXME split_at_first_point() could split a bridge mid-way
+ #if 0
+ Polylines overhang_perimeters = layerm->perimeters.as_polylines();
+ // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline()
+ for (Polyline &polyline : overhang_perimeters)
+ polyline.points[0].x += 1;
+ // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters.
+ overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices);
+ #else
+ Polylines overhang_perimeters = diff_pl(layerm->perimeters.as_polylines(), lower_grown_slices);
+ #endif
+
+ // only consider straight overhangs
+ // only consider overhangs having endpoints inside layer's slices
+ // convert bridging polylines into polygons by inflating them with their thickness
+ // since we're dealing with bridges, we can't assume width is larger than spacing,
+ // so we take the largest value and also apply safety offset to be ensure no gaps
+ // are left in between
+ Flow bridge_flow = layerm->flow(frPerimeter, true);
+ float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing()));
+ for (Polyline &polyline : overhang_perimeters)
+ if (polyline.is_straight()) {
+ // This is a bridge
+ polyline.extend_start(fw);
+ polyline.extend_end(fw);
+ // Is the straight perimeter segment supported at both sides?
+ if (lower_layer.slices.contains(polyline.first_point()) && lower_layer.slices.contains(polyline.last_point()))
+ // Offset a polyline into a thick line.
+ polygons_append(bridges, offset(polyline, 0.5f * w + 10.f));
+ }
+ bridges = union_(bridges);
+ }
+ // remove the entire bridges and only support the unsupported edges
+ //FIXME the brided regions are already collected as layerm->bridged. Use it?
+ for (const Surface &surface : layerm->fill_surfaces.surfaces)
+ if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1)
+ polygons_append(bridges, surface.expolygon);
+ //FIXME add the gap filled areas. Extrude the gaps with a bridge flow?
+ contact_polygons = diff(contact_polygons, bridges, true);
+ // Add the bridge anchors into the region.
+ //FIXME add supports at regular intervals to support long bridges!
+ polygons_append(contact_polygons,
+ intersection(
+ // Offset unsupported edges into polygons.
+ offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS),
+ bridges));
+ }
+}
+
+// Generate top contact layers supporting overhangs.
+// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined.
+// If supports over bed surface only are requested, don't generate contact layers over an object.
+PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers(
+ const PrintObject &object, MyLayerStorage &layer_storage) const
+{
+#ifdef SLIC3R_DEBUG
+ static int iRun = 0;
+ ++ iRun;
+#endif /* SLIC3R_DEBUG */
+
+ // Slice support enforcers / support blockers.
+ std::vector<ExPolygons> enforcers = object.slice_support_enforcers();
+ std::vector<ExPolygons> blockers = object.slice_support_blockers();
+
+ // Output layers, sorted by top Z.
+ MyLayersPtr contact_out;
+
+ const bool support_auto = m_object_config->support_material_auto.value;
+ // If user specified a custom angle threshold, convert it to radians.
+ // Zero means automatic overhang detection.
+ const double threshold_rad = (m_object_config->support_material_threshold.value > 0) ?
+ M_PI * double(m_object_config->support_material_threshold.value + 1) / 180. : // +1 makes the threshold inclusive
+ 0.;
+
+ // Build support on a build plate only? If so, then collect and union all the surfaces below the current layer.
+ // Unfortunately this is an inherently serial process.
+ const bool buildplate_only = this->build_plate_only();
+ std::vector<Polygons> buildplate_covered;
+ if (buildplate_only) {
+ BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() - collecting regions covering the print bed.";
+ buildplate_covered.assign(object.layers().size(), Polygons());
+ for (size_t layer_id = 1; layer_id < object.layers().size(); ++ layer_id) {
+ const Layer &lower_layer = *object.layers()[layer_id-1];
+ // Merge the new slices with the preceding slices.
+ // Apply the safety offset to the newly added polygons, so they will connect
+ // with the polygons collected before,
+ // but don't apply the safety offset during the union operation as it would
+ // inflate the polygons over and over.
+ Polygons &covered = buildplate_covered[layer_id];
+ covered = buildplate_covered[layer_id - 1];
+ polygons_append(covered, offset(lower_layer.slices.expolygons, scale_(0.01)));
+ covered = union_(covered, false); // don't apply the safety offset.
+ }
+ }
+
+ BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - start";
+ // Determine top contact areas.
+ // If generating raft only (no support), only calculate top contact areas for the 0th layer.
+ // If having a raft, start with 0th layer, otherwise with 1st layer.
+ // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers.
+ // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers.
+ size_t num_layers = this->has_support() ? object.layer_count() : 1;
+ // For each overhang layer, two supporting layers may be generated: One for the overhangs extruded with a bridging flow,
+ // and the other for the overhangs extruded with a normal flow.
+ contact_out.assign(num_layers * 2, nullptr);
+ tbb::spin_mutex layer_storage_mutex;
+ tbb::parallel_for(tbb::blocked_range<size_t>(this->has_raft() ? 0 : 1, num_layers),
+ [this, &object, &buildplate_covered, &enforcers, &blockers, support_auto, threshold_rad, &layer_storage, &layer_storage_mutex, &contact_out]
+ (const tbb::blocked_range<size_t>& range) {
+ for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id)
+ {
+ const Layer &layer = *object.layers()[layer_id];
+
+ // Detect overhangs and contact areas needed to support them.
+ // Collect overhangs and contacts of all regions of this layer supported by the layer immediately below.
+ Polygons overhang_polygons;
+ Polygons contact_polygons;
+ Polygons slices_margin_cached;
+ float slices_margin_cached_offset = -1.;
+ Polygons lower_layer_polygons = (layer_id == 0) ? Polygons() : to_polygons(object.layers()[layer_id-1]->slices.expolygons);
+ // Offset of the lower layer, to trim the support polygons with to calculate dense supports.
+ float no_interface_offset = 0.f;
+ if (layer_id == 0) {
+ // This is the first object layer, so the object is being printed on a raft and
+ // we're here just to get the object footprint for the raft.
+ // We only consider contours and discard holes to get a more continuous raft.
+ overhang_polygons = collect_slices_outer(layer);
+ // Extend by SUPPORT_MATERIAL_MARGIN, which is 1.5mm
+ contact_polygons = offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN));
+ } else {
+ // Generate overhang / contact_polygons for non-raft layers.
+ const Layer &lower_layer = *object.layers()[layer_id-1];
+ for (LayerRegion *layerm : layer.regions()) {
+ // Extrusion width accounts for the roundings of the extrudates.
+ // It is the maximum widh of the extrudate.
+ float fw = float(layerm->flow(frExternalPerimeter).scaled_width());
+ no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw);
+ float lower_layer_offset =
+ (layer_id < m_object_config->support_material_enforce_layers.value) ?
+ // Enforce a full possible support, ignore the overhang angle.
+ 0.f :
+ (threshold_rad > 0. ?
+ // Overhang defined by an angle.
+ float(scale_(lower_layer.height / tan(threshold_rad))) :
+ // Overhang defined by half the extrusion width.
+ 0.5f * fw);
+ // Overhang polygons for this layer and region.
+ Polygons diff_polygons;
+ Polygons layerm_polygons = to_polygons(layerm->slices);
+ if (lower_layer_offset == 0.f) {
+ // Support everything.
+ diff_polygons = diff(layerm_polygons, lower_layer_polygons);
+ if (! buildplate_covered.empty()) {
+ // Don't support overhangs above the top surfaces.
+ // This step is done before the contact surface is calculated by growing the overhang region.
+ diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]);
+ }
+ } else {
+ if (support_auto) {
+ // Get the regions needing a suport, collapse very tiny spots.
+ //FIXME cache the lower layer offset if this layer has multiple regions.
+ #if 1
+ diff_polygons = offset2(
+ diff(layerm_polygons,
+ offset2(lower_layer_polygons, - 0.5f * fw, lower_layer_offset + 0.5f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS)),
+ //FIXME This offset2 is targeted to reduce very thin regions to support, but it may lead to
+ // no support at all for not so steep overhangs.
+ - 0.1f * fw, 0.1f * fw);
+ #else
+ diff_polygons =
+ diff(layerm_polygons,
+ offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ #endif
+ if (! buildplate_covered.empty()) {
+ // Don't support overhangs above the top surfaces.
+ // This step is done before the contact surface is calculated by growing the overhang region.
+ diff_polygons = diff(diff_polygons, buildplate_covered[layer_id]);
+ }
+ if (! diff_polygons.empty()) {
+ // Offset the support regions back to a full overhang, restrict them to the full overhang.
+ // This is done to increase size of the supporting columns below, as they are calculated by
+ // propagating these contact surfaces downwards.
+ diff_polygons = diff(
+ intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons),
+ lower_layer_polygons);
+ }
+ }
+ if (! enforcers.empty()) {
+ // Apply the "support enforcers".
+ //FIXME add the "enforcers" to the sparse support regions only.
+ const ExPolygons &enforcer = enforcers[layer_id - 1];
+ if (! enforcer.empty()) {
+ // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes.
+ Polygons new_contacts = diff(intersection(layerm_polygons, to_polygons(enforcer)),
+ offset(lower_layer_polygons, 0.05f * fw, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ if (! new_contacts.empty()) {
+ if (diff_polygons.empty())
+ diff_polygons = std::move(new_contacts);
+ else
+ diff_polygons = union_(diff_polygons, new_contacts);
+ }
+ }
+ }
+ }
+ // Apply the "support blockers".
+ if (! diff_polygons.empty() && ! blockers.empty() && ! blockers[layer_id].empty()) {
+ // Enforce supports (as if with 90 degrees of slope) for the regions covered by the enforcer meshes.
+ diff_polygons = diff(diff_polygons, to_polygons(blockers[layer_id]));
+ }
+ if (diff_polygons.empty())
+ continue;
+
+ #ifdef SLIC3R_DEBUG
+ {
+ ::Slic3r::SVG svg(debug_out_path("support-top-contacts-raw-run%d-layer%d-region%d.svg",
+ iRun, layer_id,
+ std::find_if(layer.regions.begin(), layer.regions.end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions.begin()),
+ get_extents(diff_polygons));
+ Slic3r::ExPolygons expolys = union_ex(diff_polygons, false);
+ svg.draw(expolys);
+ }
+ #endif /* SLIC3R_DEBUG */
+
+ if (this->m_object_config->dont_support_bridges)
+ SupportMaterialInternal::remove_bridges_from_contacts(
+ *m_print_config, lower_layer, lower_layer_polygons, layerm, fw, diff_polygons);
+
+ if (diff_polygons.empty())
+ continue;
+
+ #ifdef SLIC3R_DEBUG
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-top-contacts-filtered-run%d-layer%d-region%d-z%f.svg",
+ iRun, layer_id,
+ std::find_if(layer.regions.begin(), layer.regions.end(), [layerm](const LayerRegion* other){return other == layerm;}) - layer.regions.begin(),
+ layer.print_z),
+ union_ex(diff_polygons, false));
+ #endif /* SLIC3R_DEBUG */
+
+ //FIXME the overhang_polygons are used to construct the support towers as well.
+ //if (this->has_contact_loops())
+ // Store the exact contour of the overhang for the contact loops.
+ polygons_append(overhang_polygons, diff_polygons);
+
+ // Let's define the required contact area by using a max gap of half the upper
+ // extrusion width and extending the area according to the configured margin.
+ // We increment the area in steps because we don't want our support to overflow
+ // on the other side of the object (if it's very thin).
+ {
+ //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable.
+ //FIXME one should trim with the layer span colliding with the support layer, this layer
+ // may be lower than lower_layer, so the support area needed may need to be actually bigger!
+ // For the same reason, the non-bridging support area may be smaller than the bridging support area!
+ float slices_margin_offset = std::min(lower_layer_offset, float(scale_(m_gap_xy)));
+ if (slices_margin_cached_offset != slices_margin_offset) {
+ slices_margin_cached_offset = slices_margin_offset;
+ slices_margin_cached = (slices_margin_offset == 0.f) ?
+ lower_layer_polygons :
+ offset2(to_polygons(lower_layer.slices.expolygons), - no_interface_offset * 0.5f, slices_margin_offset + no_interface_offset * 0.5f, SUPPORT_SURFACES_OFFSET_PARAMETERS);
+ if (! buildplate_covered.empty()) {
+ // Trim the inflated contact surfaces by the top surfaces as well.
+ polygons_append(slices_margin_cached, buildplate_covered[layer_id]);
+ slices_margin_cached = union_(slices_margin_cached);
+ }
+ }
+ // Offset the contact polygons outside.
+ for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) {
+ diff_polygons = diff(
+ offset(
+ diff_polygons,
+ SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS,
+ ClipperLib::jtRound,
+ // round mitter limit
+ scale_(0.05)),
+ slices_margin_cached);
+ }
+ }
+ polygons_append(contact_polygons, diff_polygons);
+ } // for each layer.region
+ } // end of Generate overhang/contact_polygons for non-raft layers.
+
+ // Now apply the contact areas to the layer where they need to be made.
+ if (! contact_polygons.empty()) {
+ MyLayer &new_layer = layer_allocate(layer_storage, layer_storage_mutex, sltTopContact);
+ new_layer.idx_object_layer_above = layer_id;
+ MyLayer *bridging_layer = nullptr;
+ if (layer_id == 0) {
+ // This is a raft contact layer sitting directly on the print bed.
+ assert(this->has_raft());
+ new_layer.print_z = m_slicing_params.raft_contact_top_z;
+ new_layer.bottom_z = m_slicing_params.raft_interface_top_z;
+ new_layer.height = m_slicing_params.contact_raft_layer_height;
+ } else if (m_slicing_params.soluble_interface) {
+ // Align the contact surface height with a layer immediately below the supported layer.
+ // Interface layer will be synchronized with the object.
+ new_layer.print_z = layer.print_z - layer.height;
+ new_layer.height = object.layers()[layer_id - 1]->height;
+ new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z;
+ } else {
+ new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance;
+ new_layer.bottom_z = new_layer.print_z;
+ new_layer.height = 0.;
+ // Ignore this contact area if it's too low.
+ // Don't want to print a layer below the first layer height as it may not stick well.
+ //FIXME there may be a need for a single layer support, then one may decide to print it either as a bottom contact or a top contact
+ // and it may actually make sense to do it with a thinner layer than the first layer height.
+ if (new_layer.print_z < m_slicing_params.first_print_layer_height - EPSILON) {
+ // This contact layer is below the first layer height, therefore not printable. Don't support this surface.
+ continue;
+ } else if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) {
+ // Align the layer with the 1st layer height.
+ new_layer.print_z = m_slicing_params.first_print_layer_height;
+ new_layer.bottom_z = 0;
+ new_layer.height = m_slicing_params.first_print_layer_height;
+ } else {
+ // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and
+ // its height will be set adaptively later on.
+ }
+
+ // Contact layer will be printed with a normal flow, but
+ // it will support layers printed with a bridging flow.
+ if (SupportMaterialInternal::has_bridging_extrusions(layer)) {
+ coordf_t bridging_height = 0.;
+ for (const LayerRegion *region : layer.regions())
+ bridging_height += region->region()->bridging_height_avg(*m_print_config);
+ bridging_height /= coordf_t(layer.regions().size());
+ coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance;
+ if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) {
+ // Not below the first layer height means this layer is printable.
+ if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) {
+ // Align the layer with the 1st layer height.
+ bridging_print_z = m_slicing_params.first_print_layer_height;
+ }
+ if (bridging_print_z < new_layer.print_z - EPSILON) {
+ // Allocate the new layer.
+ bridging_layer = &layer_allocate(layer_storage, layer_storage_mutex, sltTopContact);
+ bridging_layer->idx_object_layer_above = layer_id;
+ bridging_layer->print_z = bridging_print_z;
+ if (bridging_print_z == m_slicing_params.first_print_layer_height) {
+ bridging_layer->bottom_z = 0;
+ bridging_layer->height = m_slicing_params.first_print_layer_height;
+ } else {
+ // Don't know the height yet.
+ bridging_layer->bottom_z = bridging_print_z;
+ bridging_layer->height = 0;
+ }
+ }
+ }
+ }
+ }
+
+ SupportGridPattern support_grid_pattern(
+ // Support islands, to be stretched into a grid.
+ contact_polygons,
+ // Trimming polygons, to trim the stretched support islands.
+ slices_margin_cached,
+ // Grid resolution.
+ m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
+ Geometry::deg2rad(m_object_config->support_material_angle.value));
+ // 1) Contact polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells.
+ new_layer.contact_polygons = new Polygons(support_grid_pattern.extract_support(-3, true));
+ // 2) infill polygons, expand them by half the extrusion width + a tiny bit of extra.
+ if (layer_id == 0 || m_slicing_params.soluble_interface) {
+ // if (no_interface_offset == 0.f) {
+ new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, true);
+ } else {
+ // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions.
+ Polygons dense_interface_polygons = diff(overhang_polygons,
+ offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS));
+// offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ if (! dense_interface_polygons.empty()) {
+ //FIXME do it for the bridges only?
+ SupportGridPattern support_grid_pattern(
+ // Support islands, to be stretched into a grid.
+ dense_interface_polygons,
+ // Trimming polygons, to trim the stretched support islands.
+ slices_margin_cached,
+ // Grid resolution.
+ m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
+ Geometry::deg2rad(m_object_config->support_material_angle.value));
+ new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false);
+ }
+ }
+
+ // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded.
+ // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons.
+
+ // Store the overhang polygons.
+ // The overhang polygons are used in the path generator for planning of the contact loops.
+ // if (this->has_contact_loops()). Compared to "polygons", "overhang_polygons" are snug.
+ new_layer.overhang_polygons = new Polygons(std::move(overhang_polygons));
+ contact_out[layer_id * 2] = &new_layer;
+ if (bridging_layer != nullptr) {
+ bridging_layer->polygons = new_layer.polygons;
+ bridging_layer->contact_polygons = new Polygons(*new_layer.contact_polygons);
+ bridging_layer->overhang_polygons = new Polygons(*new_layer.overhang_polygons);
+ contact_out[layer_id * 2 + 1] = bridging_layer;
+ }
+ }
+ }
+ });
+
+ // Compress contact_out, remove the nullptr items.
+ remove_nulls(contact_out);
+ // Sort the layers, as one layer may produce bridging and non-bridging contact layers with different print_z.
+ std::sort(contact_out.begin(), contact_out.end(), [](const MyLayer *l1, const MyLayer *l2) { return l1->print_z < l2->print_z; });
+
+ // Merge close contact layers conservatively: If two layers are closer than the minimum allowed print layer height (the min_layer_height parameter),
+ // the top contact layer is merged into the bottom contact layer.
+ {
+ int i = 0;
+ int k = 0;
+ {
+ // Find the span of layers, which are to be printed at the first layer height.
+ int j = 0;
+ for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j);
+ if (j > 0) {
+ // Merge the contact_out layers (0) to (j - 1) into the contact_out[0].
+ MyLayer &dst = *contact_out.front();
+ for (int u = 1; u < j; ++ u) {
+ MyLayer &src = *contact_out[u];
+ // The union_() does not support move semantic yet, but maybe one day it will.
+ dst.polygons = union_(dst.polygons, std::move(src.polygons));
+ *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons));
+ *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons));
+ // Source polygon is no more needed, it will not be refrenced. Release its data.
+ src.reset();
+ }
+ // Snap the first layer to the 1st layer height.
+ dst.print_z = m_slicing_params.first_print_layer_height;
+ dst.height = m_slicing_params.first_print_layer_height;
+ dst.bottom_z = 0;
+ ++ k;
+ }
+ i = j;
+ }
+ for (; i < int(contact_out.size()); ++ k) {
+ // Find the span of layers closer than m_support_layer_height_min.
+ int j = i + 1;
+ coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON;
+ for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ;
+ if (i + 1 < j) {
+ // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i].
+ MyLayer &dst = *contact_out[i];
+ for (int u = i + 1; u < j; ++ u) {
+ MyLayer &src = *contact_out[u];
+ // The union_() does not support move semantic yet, but maybe one day it will.
+ dst.polygons = union_(dst.polygons, std::move(src.polygons));
+ *dst.contact_polygons = union_(*dst.contact_polygons, std::move(*src.contact_polygons));
+ *dst.overhang_polygons = union_(*dst.overhang_polygons, std::move(*src.overhang_polygons));
+ // Source polygon is no more needed, it will not be refrenced. Release its data.
+ src.reset();
+ }
+ }
+ if (k < i)
+ contact_out[k] = contact_out[i];
+ i = j;
+ }
+ if (k < contact_out.size())
+ contact_out.erase(contact_out.begin() + k, contact_out.end());
+ }
+
+ BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::top_contact_layers() in parallel - end";
+
+ return contact_out;
+}
+
+// Generate bottom contact layers supporting the top contact layers.
+// For a soluble interface material synchronize the layer heights with the object,
+// otherwise set the layer height to a bridging flow of a support interface nozzle.
+PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas(
+ const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage,
+ std::vector<Polygons> &layer_support_areas) const
+{
+#ifdef SLIC3R_DEBUG
+ static int iRun = 0;
+ ++ iRun;
+#endif /* SLIC3R_DEBUG */
+
+ // Allocate empty surface areas, one per object layer.
+ layer_support_areas.assign(object.total_layer_count(), Polygons());
+
+ // find object top surfaces
+ // we'll use them to clip our support and detect where does it stick
+ MyLayersPtr bottom_contacts;
+
+ if (! top_contacts.empty())
+ {
+ // There is some support to be built, if there are non-empty top surfaces detected.
+ // Sum of unsupported contact areas above the current layer.print_z.
+ Polygons projection;
+ // Last top contact layer visited when collecting the projection of contact areas.
+ int contact_idx = int(top_contacts.size()) - 1;
+ for (int layer_id = int(object.total_layer_count()) - 2; layer_id >= 0; -- layer_id) {
+ BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id;
+ const Layer &layer = *object.get_layer(layer_id);
+ // Collect projections of all contact areas above or at the same level as this top surface.
+ for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z > layer.print_z - EPSILON; -- contact_idx) {
+ Polygons polygons_new;
+ // Contact surfaces are expanded away from the object, trimmed by the object.
+ // Use a slight positive offset to overlap the touching regions.
+#if 0
+ // Merge and collect the contact polygons. The contact polygons are inflated, but not extended into a grid form.
+ polygons_append(polygons_new, offset(*top_contacts[contact_idx]->contact_polygons, SCALED_EPSILON));
+#else
+ // Consume the contact_polygons. The contact polygons are already expanded into a grid form, and they are a tiny bit smaller
+ // than the grid cells.
+ polygons_append(polygons_new, std::move(*top_contacts[contact_idx]->contact_polygons));
+#endif
+ // These are the overhang surfaces. They are touching the object and they are not expanded away from the object.
+ // Use a slight positive offset to overlap the touching regions.
+ polygons_append(polygons_new, offset(*top_contacts[contact_idx]->overhang_polygons, float(SCALED_EPSILON)));
+ polygons_append(projection, union_(polygons_new));
+ }
+ if (projection.empty())
+ continue;
+ Polygons projection_raw = union_(projection);
+
+ tbb::task_group task_group;
+ if (! m_object_config->support_material_buildplate_only)
+ // Find the bottom contact layers above the top surfaces of this layer.
+ task_group.run([this, &object, &top_contacts, contact_idx, &layer, layer_id, &layer_storage, &layer_support_areas, &bottom_contacts, &projection_raw] {
+ Polygons top = collect_region_slices_by_type(layer, stTop);
+ #ifdef SLIC3R_DEBUG
+ {
+ BoundingBox bbox = get_extents(projection_raw);
+ bbox.merge(get_extents(top));
+ ::Slic3r::SVG svg(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z), bbox);
+ svg.draw(union_ex(top, false), "blue", 0.5f);
+ svg.draw(union_ex(projection_raw, true), "red", 0.5f);
+ svg.draw_outline(union_ex(projection_raw, true), "red", "blue", scale_(0.1f));
+ svg.draw(layer.slices.expolygons, "green", 0.5f);
+ }
+ #endif /* SLIC3R_DEBUG */
+
+ // Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any
+ // top surfaces above layer.print_z falls onto this top surface.
+ // Touching are the contact surfaces supported exclusively by this top surfaces.
+ // Don't use a safety offset as it has been applied during insertion of polygons.
+ if (! top.empty()) {
+ Polygons touching = intersection(top, projection_raw, false);
+ if (! touching.empty()) {
+ // Allocate a new bottom contact layer.
+ MyLayer &layer_new = layer_allocate(layer_storage, sltBottomContact);
+ bottom_contacts.push_back(&layer_new);
+ // Grow top surfaces so that interface and support generation are generated
+ // with some spacing from object - it looks we don't need the actual
+ // top shapes so this can be done here
+ //FIXME calculate layer height based on the actual thickness of the layer:
+ // If the layer is extruded with no bridging flow, support just the normal extrusions.
+ layer_new.height = m_slicing_params.soluble_interface ?
+ // Align the interface layer with the object's layer height.
+ object.layers()[layer_id + 1]->height :
+ // Place a bridge flow interface layer over the top surface.
+ //FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?)
+ // According to Jindrich the bottom surfaces work well.
+ //FIXME test the bridging flow instead?
+ m_support_material_interface_flow.nozzle_diameter;
+ layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z :
+ layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value;
+ layer_new.bottom_z = layer.print_z;
+ layer_new.idx_object_layer_below = layer_id;
+ layer_new.bridging = ! m_slicing_params.soluble_interface;
+ //FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
+ //FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks.
+ layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
+ if (! m_slicing_params.soluble_interface) {
+ // Walk the top surfaces, snap the top of the new bottom surface to the closest top of the top surface,
+ // so there will be no support surfaces generated with thickness lower than m_support_layer_height_min.
+ for (size_t top_idx = size_t(std::max<int>(0, contact_idx));
+ top_idx < top_contacts.size() && top_contacts[top_idx]->print_z < layer_new.print_z + this->m_support_layer_height_min + EPSILON;
+ ++ top_idx) {
+ if (top_contacts[top_idx]->print_z > layer_new.print_z - this->m_support_layer_height_min - EPSILON) {
+ // A top layer has been found, which is close to the new bottom layer.
+ coordf_t diff = layer_new.print_z - top_contacts[top_idx]->print_z;
+ assert(std::abs(diff) <= this->m_support_layer_height_min + EPSILON);
+ if (diff > 0.) {
+ // The top contact layer is below this layer. Make the bridging layer thinner to align with the existing top layer.
+ assert(diff < layer_new.height + EPSILON);
+ assert(layer_new.height - diff >= m_support_layer_height_min - EPSILON);
+ layer_new.print_z = top_contacts[top_idx]->print_z;
+ layer_new.height -= diff;
+ } else {
+ // The top contact layer is above this layer. One may either make this layer thicker or thinner.
+ // By making the layer thicker, one will decrease the number of discrete layers with the price of extruding a bit too thick bridges.
+ // By making the layer thinner, one adds one more discrete layer.
+ layer_new.print_z = top_contacts[top_idx]->print_z;
+ layer_new.height -= diff;
+ }
+ break;
+ }
+ }
+ }
+ #ifdef SLIC3R_DEBUG
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z),
+ union_ex(layer_new.polygons, false));
+ #endif /* SLIC3R_DEBUG */
+ // Trim the already created base layers above the current layer intersecting with the new bottom contacts layer.
+ //FIXME Maybe this is no more needed, as the overlapping base layers are trimmed by the bottom layers at the final stage?
+ touching = offset(touching, float(SCALED_EPSILON));
+ for (int layer_id_above = layer_id + 1; layer_id_above < int(object.total_layer_count()); ++ layer_id_above) {
+ const Layer &layer_above = *object.layers()[layer_id_above];
+ if (layer_above.print_z > layer_new.print_z - EPSILON)
+ break;
+ if (! layer_support_areas[layer_id_above].empty()) {
+#ifdef SLIC3R_DEBUG
+ {
+ BoundingBox bbox = get_extents(touching);
+ bbox.merge(get_extents(layer_support_areas[layer_id_above]));
+ ::Slic3r::SVG svg(debug_out_path("support-support-areas-raw-before-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z), bbox);
+ svg.draw(union_ex(touching, false), "blue", 0.5f);
+ svg.draw(union_ex(layer_support_areas[layer_id_above], true), "red", 0.5f);
+ svg.draw_outline(union_ex(layer_support_areas[layer_id_above], true), "red", "blue", scale_(0.1f));
+ }
+#endif /* SLIC3R_DEBUG */
+ layer_support_areas[layer_id_above] = diff(layer_support_areas[layer_id_above], touching);
+#ifdef SLIC3R_DEBUG
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-support-areas-raw-after-trimming-%d-with-%f-%lf.svg", iRun, layer.print_z, layer_above.print_z),
+ union_ex(layer_support_areas[layer_id_above], false));
+#endif /* SLIC3R_DEBUG */
+ }
+ }
+ }
+ } // ! top.empty()
+ });
+
+ Polygons &layer_support_area = layer_support_areas[layer_id];
+ task_group.run([this, &projection, &projection_raw, &layer, &layer_support_area, layer_id] {
+ // Remove the areas that touched from the projection that will continue on next, lower, top surfaces.
+ // Polygons trimming = union_(to_polygons(layer.slices.expolygons), touching, true);
+ Polygons trimming = offset(layer.slices.expolygons, float(SCALED_EPSILON));
+ projection = diff(projection_raw, trimming, false);
+ #ifdef SLIC3R_DEBUG
+ {
+ BoundingBox bbox = get_extents(projection_raw);
+ bbox.merge(get_extents(trimming));
+ ::Slic3r::SVG svg(debug_out_path("support-support-areas-raw-%d-%lf.svg", iRun, layer.print_z), bbox);
+ svg.draw(union_ex(trimming, false), "blue", 0.5f);
+ svg.draw(union_ex(projection, true), "red", 0.5f);
+ svg.draw_outline(union_ex(projection, true), "red", "blue", scale_(0.1f));
+ }
+ #endif /* SLIC3R_DEBUG */
+ remove_sticks(projection);
+ remove_degenerate(projection);
+ #ifdef SLIC3R_DEBUG
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-support-areas-raw-cleaned-%d-%lf.svg", iRun, layer.print_z),
+ union_ex(projection, false));
+ #endif /* SLIC3R_DEBUG */
+ SupportGridPattern support_grid_pattern(
+ // Support islands, to be stretched into a grid.
+ projection,
+ // Trimming polygons, to trim the stretched support islands.
+ trimming,
+ // Grid spacing.
+ m_object_config->support_material_spacing.value + m_support_material_flow.spacing(),
+ Geometry::deg2rad(m_object_config->support_material_angle.value));
+ tbb::task_group task_group_inner;
+ // 1) Cache the slice of a support volume. The support volume is expanded by 1/2 of support material flow spacing
+ // to allow a placement of suppot zig-zag snake along the grid lines.
+ task_group_inner.run([this, &support_grid_pattern, &layer_support_area
+ #ifdef SLIC3R_DEBUG
+ , &layer
+ #endif /* SLIC3R_DEBUG */
+ ] {
+ layer_support_area = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 25, true);
+ #ifdef SLIC3R_DEBUG
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-layer_support_area-gridded-%d-%lf.svg", iRun, layer.print_z),
+ union_ex(layer_support_area, false));
+ #endif /* SLIC3R_DEBUG */
+ });
+ // 2) Support polygons will be projected down. To keep the interface and base layers from growing, return a contour a tiny bit smaller than the grid cells.
+ Polygons projection_new;
+ task_group_inner.run([&projection_new, &support_grid_pattern
+ #ifdef SLIC3R_DEBUG
+ , &layer
+ #endif /* SLIC3R_DEBUG */
+ ] {
+ projection_new = support_grid_pattern.extract_support(-5, true);
+ #ifdef SLIC3R_DEBUG
+ Slic3r::SVG::export_expolygons(
+ debug_out_path("support-projection_new-gridded-%d-%lf.svg", iRun, layer.print_z),
+ union_ex(projection_new, false));
+ #endif /* SLIC3R_DEBUG */
+ });
+ task_group_inner.wait();
+ projection = std::move(projection_new);
+ });
+ task_group.wait();
+ }
+ std::reverse(bottom_contacts.begin(), bottom_contacts.end());
+// trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy);
+ trim_support_layers_by_object(object, bottom_contacts,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
+
+ } // ! top_contacts.empty()
+
+ return bottom_contacts;
+}
+
+// FN_HIGHER_EQUAL: the provided object pointer has a Z value >= of an internal threshold.
+// Find the first item with Z value >= of an internal threshold of fn_higher_equal.
+// If no vec item with Z value >= of an internal threshold of fn_higher_equal is found, return vec.size()
+// If the initial idx is size_t(-1), then use binary search.
+// Otherwise search linearly upwards.
+template<typename T, typename FN_HIGHER_EQUAL>
+size_t idx_higher_or_equal(const std::vector<T*> &vec, size_t idx, FN_HIGHER_EQUAL fn_higher_equal)
+{
+ if (vec.empty()) {
+ idx = 0;
+ } else if (idx == size_t(-1)) {
+ // First of the batch of layers per thread pool invocation. Use binary search.
+ int idx_low = 0;
+ int idx_high = std::max(0, int(vec.size()) - 1);
+ while (idx_low + 1 < idx_high) {
+ int idx_mid = (idx_low + idx_high) / 2;
+ if (fn_higher_equal(vec[idx_mid]))
+ idx_high = idx_mid;
+ else
+ idx_low = idx_mid;
+ }
+ idx = fn_higher_equal(vec[idx_low]) ? idx_low :
+ (fn_higher_equal(vec[idx_high]) ? idx_high : vec.size());
+ } else {
+ // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search.
+ while (idx < vec.size() && ! fn_higher_equal(vec[idx]))
+ ++ idx;
+ }
+ return idx;
+}
+
+// FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold.
+// Find the first item with Z value <= of an internal threshold of fn_lower_equal.
+// If no vec item with Z value <= of an internal threshold of fn_lower_equal is found, return -1.
+// If the initial idx is < -1, then use binary search.
+// Otherwise search linearly downwards.
+template<typename T, typename FN_LOWER_EQUAL>
+int idx_lower_or_equal(const std::vector<T*> &vec, int idx, FN_LOWER_EQUAL fn_lower_equal)
+{
+ if (vec.empty()) {
+ idx = -1;
+ } else if (idx < -1) {
+ // First of the batch of layers per thread pool invocation. Use binary search.
+ int idx_low = 0;
+ int idx_high = std::max(0, int(vec.size()) - 1);
+ while (idx_low + 1 < idx_high) {
+ int idx_mid = (idx_low + idx_high) / 2;
+ if (fn_lower_equal(vec[idx_mid]))
+ idx_low = idx_mid;
+ else
+ idx_high = idx_mid;
+ }
+ idx = fn_lower_equal(vec[idx_high]) ? idx_high :
+ (fn_lower_equal(vec[idx_low ]) ? idx_low : -1);
+ } else {
+ // For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search.
+ while (idx >= 0 && ! fn_lower_equal(vec[idx]))
+ -- idx;
+ }
+ return idx;
+}
+
+// Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them.
+void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts(
+ const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const
+{
+ tbb::parallel_for(tbb::blocked_range<int>(0, int(top_contacts.size())),
+ [this, &object, &bottom_contacts, &top_contacts](const tbb::blocked_range<int>& range) {
+ int idx_bottom_overlapping_first = -2;
+ // For all top contact layers, counting downwards due to the way idx_higher_or_equal caches the last index to avoid repeated binary search.
+ for (int idx_top = range.end() - 1; idx_top >= range.begin(); -- idx_top) {
+ MyLayer &layer_top = *top_contacts[idx_top];
+ // Find the first bottom layer overlapping with layer_top.
+ idx_bottom_overlapping_first = idx_lower_or_equal(bottom_contacts, idx_bottom_overlapping_first, [&layer_top](const MyLayer *layer_bottom){ return layer_bottom->bottom_print_z() - EPSILON <= layer_top.bottom_z; });
+ // For all top contact layers overlapping with the thick bottom contact layer:
+ for (int idx_bottom_overlapping = idx_bottom_overlapping_first; idx_bottom_overlapping >= 0; -- idx_bottom_overlapping) {
+ const MyLayer &layer_bottom = *bottom_contacts[idx_bottom_overlapping];
+ assert(layer_bottom.bottom_print_z() - EPSILON <= layer_top.bottom_z);
+ if (layer_top.print_z < layer_bottom.print_z + EPSILON) {
+ // Layers overlap. Trim layer_top with layer_bottom.
+ layer_top.polygons = diff(layer_top.polygons, layer_bottom.polygons);
+ } else
+ break;
+ }
+ }
+ });
+}
+
+PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_support_layers(
+ const PrintObject &object,
+ const MyLayersPtr &bottom_contacts,
+ const MyLayersPtr &top_contacts,
+ MyLayerStorage &layer_storage) const
+{
+ MyLayersPtr intermediate_layers;
+
+ // Collect and sort the extremes (bottoms of the top contacts and tops of the bottom contacts).
+ MyLayersPtr extremes;
+ extremes.reserve(top_contacts.size() + bottom_contacts.size());
+ for (size_t i = 0; i < top_contacts.size(); ++ i)
+ // Bottoms of the top contact layers. In case of non-soluble supports,
+ // the top contact layer thickness is not known yet.
+ extremes.push_back(top_contacts[i]);
+ for (size_t i = 0; i < bottom_contacts.size(); ++ i)
+ // Tops of the bottom contact layers.
+ extremes.push_back(bottom_contacts[i]);
+ if (extremes.empty())
+ return intermediate_layers;
+
+ auto layer_extreme_lower = [](const MyLayer *l1, const MyLayer *l2) {
+ coordf_t z1 = l1->extreme_z();
+ coordf_t z2 = l2->extreme_z();
+ // If the layers are aligned, return the top contact surface first.
+ return z1 < z2 || (z1 == z2 && l1->layer_type == PrintObjectSupportMaterial::sltTopContact && l2->layer_type == PrintObjectSupportMaterial::sltBottomContact);
+ };
+ std::sort(extremes.begin(), extremes.end(), layer_extreme_lower);
+
+ assert(extremes.empty() ||
+ (extremes.front()->extreme_z() > m_slicing_params.raft_interface_top_z - EPSILON &&
+ (m_slicing_params.raft_layers() == 1 || // only raft contact layer
+ extremes.front()->layer_type == sltTopContact || // first extreme is a top contact layer
+ extremes.front()->extreme_z() > m_slicing_params.first_print_layer_height - EPSILON)));
+
+ bool synchronize = this->synchronize_layers();
+
+#ifdef _DEBUG
+ // Verify that the extremes are separated by m_support_layer_height_min.
+ for (size_t i = 1; i < extremes.size(); ++ i) {
+ assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() == 0. ||
+ extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > m_support_layer_height_min - EPSILON);
+ assert(extremes[i]->extreme_z() - extremes[i-1]->extreme_z() > 0. ||
+ extremes[i]->layer_type == extremes[i-1]->layer_type ||
+ (extremes[i]->layer_type == sltBottomContact && extremes[i - 1]->layer_type == sltTopContact));
+ }
+#endif
+
+ // Generate intermediate layers.
+ // The first intermediate layer is the same as the 1st layer if there is no raft,
+ // or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer.
+ // Intermediate layers are always printed with a normal etrusion flow (non-bridging).
+ size_t idx_layer_object = 0;
+ for (size_t idx_extreme = 0; idx_extreme < extremes.size(); ++ idx_extreme) {
+ MyLayer *extr2 = extremes[idx_extreme];
+ coordf_t extr2z = extr2->extreme_z();
+ if (std::abs(extr2z - m_slicing_params.raft_interface_top_z) < EPSILON) {
+ // This is a raft contact layer, its height has been decided in this->top_contact_layers().
+ assert(extr2->layer_type == sltTopContact);
+ continue;
+ }
+ if (std::abs(extr2z - m_slicing_params.first_print_layer_height) < EPSILON) {
+ // This is a bottom of a synchronized (or soluble) top contact layer, its height has been decided in this->top_contact_layers().
+ assert(extr2->layer_type == sltTopContact);
+ assert(extr2->bottom_z == m_slicing_params.first_print_layer_height);
+ assert(extr2->print_z >= m_slicing_params.first_print_layer_height + m_support_layer_height_min - EPSILON);
+ if (intermediate_layers.empty() || intermediate_layers.back()->print_z < m_slicing_params.first_print_layer_height) {
+ MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate);
+ layer_new.bottom_z = 0.;
+ layer_new.print_z = m_slicing_params.first_print_layer_height;
+ layer_new.height = m_slicing_params.first_print_layer_height;
+ intermediate_layers.push_back(&layer_new);
+ }
+ continue;
+ }
+ assert(extr2z >= m_slicing_params.raft_interface_top_z + EPSILON);
+ assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON);
+ MyLayer *extr1 = (idx_extreme == 0) ? nullptr : extremes[idx_extreme - 1];
+ // Fuse a support layer firmly to the raft top interface (not to the raft contacts).
+ coordf_t extr1z = (extr1 == nullptr) ? m_slicing_params.raft_interface_top_z : extr1->extreme_z();
+ assert(extr2z >= extr1z);
+ assert(extr2z > extr1z || (extr1 != nullptr && extr2->layer_type == sltBottomContact));
+ if (std::abs(extr1z) < EPSILON) {
+ // This layer interval starts with the 1st layer. Print the 1st layer using the prescribed 1st layer thickness.
+ assert(! m_slicing_params.has_raft());
+ assert(intermediate_layers.empty() || intermediate_layers.back()->print_z <= m_slicing_params.first_print_layer_height);
+ // At this point only layers above first_print_layer_heigth + EPSILON are expected as the other cases were captured earlier.
+ assert(extr2z >= m_slicing_params.first_print_layer_height + EPSILON);
+ // Generate a new intermediate layer.
+ MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate);
+ layer_new.bottom_z = 0.;
+ layer_new.print_z = extr1z = m_slicing_params.first_print_layer_height;
+ layer_new.height = extr1z;
+ intermediate_layers.push_back(&layer_new);
+ // Continue printing the other layers up to extr2z.
+ }
+ coordf_t dist = extr2z - extr1z;
+ assert(dist >= 0.);
+ if (dist == 0.)
+ continue;
+ // The new layers shall be at least m_support_layer_height_min thick.
+ assert(dist >= m_support_layer_height_min - EPSILON);
+ if (synchronize) {
+ // Emit support layers synchronized with the object layers.
+ // Find the first object layer, which has its print_z in this support Z range.
+ while (idx_layer_object < object.layers().size() && object.layers()[idx_layer_object]->print_z < extr1z + EPSILON)
+ ++ idx_layer_object;
+ if (idx_layer_object == 0 && extr1z == m_slicing_params.raft_interface_top_z) {
+ // Insert one base support layer below the object.
+ MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate);
+ layer_new.print_z = m_slicing_params.object_print_z_min;
+ layer_new.bottom_z = m_slicing_params.raft_interface_top_z;
+ layer_new.height = layer_new.print_z - layer_new.bottom_z;
+ intermediate_layers.push_back(&layer_new);
+ }
+ // Emit all intermediate support layers synchronized with object layers up to extr2z.
+ for (; idx_layer_object < object.layers().size() && object.layers()[idx_layer_object]->print_z < extr2z + EPSILON; ++ idx_layer_object) {
+ MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate);
+ layer_new.print_z = object.layers()[idx_layer_object]->print_z;
+ layer_new.height = object.layers()[idx_layer_object]->height;
+ layer_new.bottom_z = (idx_layer_object > 0) ? object.layers()[idx_layer_object - 1]->print_z : (layer_new.print_z - layer_new.height);
+ assert(intermediate_layers.empty() || intermediate_layers.back()->print_z < layer_new.print_z + EPSILON);
+ intermediate_layers.push_back(&layer_new);
+ }
+ } else {
+ // Insert intermediate layers.
+ size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height));
+ assert(n_layers_extra > 0);
+ coordf_t step = dist / coordf_t(n_layers_extra);
+ if (extr1 != nullptr && extr1->layer_type == sltTopContact &&
+ extr1->print_z + m_support_layer_height_min > extr1->bottom_z + step) {
+ // The bottom extreme is a bottom of a top surface. Ensure that the gap
+ // between the 1st intermediate layer print_z and extr1->print_z is not too small.
+ assert(extr1->bottom_z + m_support_layer_height_min < extr1->print_z + EPSILON);
+ // Generate the first intermediate layer.
+ MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate);
+ layer_new.bottom_z = extr1->bottom_z;
+ layer_new.print_z = extr1z = extr1->print_z;
+ layer_new.height = extr1->height;
+ intermediate_layers.push_back(&layer_new);
+ dist = extr2z - extr1z;
+ n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height));
+ if (n_layers_extra == 0)
+ continue;
+ // Continue printing the other layers up to extr2z.
+ step = dist / coordf_t(n_layers_extra);
+ }
+ if (! m_slicing_params.soluble_interface && extr2->layer_type == sltTopContact) {
+ // This is a top interface layer, which does not have a height assigned yet. Do it now.
+ assert(extr2->height == 0.);
+ assert(extr1z > m_slicing_params.first_print_layer_height - EPSILON);
+ extr2->height = step;
+ extr2->bottom_z = extr2z = extr2->print_z - step;
+ if (-- n_layers_extra == 0)
+ continue;
+ }
+ coordf_t extr2z_large_steps = extr2z;
+ // Take the largest allowed step in the Z axis until extr2z_large_steps is reached.
+ for (size_t i = 0; i < n_layers_extra; ++ i) {
+ MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate);
+ if (i + 1 == n_layers_extra) {
+ // Last intermediate layer added. Align the last entered layer with extr2z_large_steps exactly.
+ layer_new.bottom_z = (i == 0) ? extr1z : intermediate_layers.back()->print_z;
+ layer_new.print_z = extr2z_large_steps;
+ layer_new.height = layer_new.print_z - layer_new.bottom_z;
+ }
+ else {
+ // Intermediate layer, not the last added.
+ layer_new.height = step;
+ layer_new.bottom_z = extr1z + i * step;
+ layer_new.print_z = layer_new.bottom_z + step;
+ }
+ assert(intermediate_layers.empty() || intermediate_layers.back()->print_z <= layer_new.print_z);
+ intermediate_layers.push_back(&layer_new);
+ }
+ }
+ }
+
+#ifdef _DEBUG
+ for (size_t i = 0; i < top_contacts.size(); ++i)
+ assert(top_contacts[i]->height > 0.);
+#endif /* _DEBUG */
+
+ return intermediate_layers;
+}
+
+// At this stage there shall be intermediate_layers allocated between bottom_contacts and top_contacts, but they have no polygons assigned.
+// Also the bottom/top_contacts shall have a layer thickness assigned already.
+void PrintObjectSupportMaterial::generate_base_layers(
+ const PrintObject &object,
+ const MyLayersPtr &bottom_contacts,
+ const MyLayersPtr &top_contacts,
+ MyLayersPtr &intermediate_layers,
+ const std::vector<Polygons> &layer_support_areas) const
+{
+#ifdef SLIC3R_DEBUG
+ static int iRun = 0;
+#endif /* SLIC3R_DEBUG */
+
+ if (top_contacts.empty())
+ // No top contacts -> no intermediate layers will be produced.
+ return;
+
+ // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing);
+
+ BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_base_layers() in parallel - start";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, intermediate_layers.size()),
+ [this, &object, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_support_areas](const tbb::blocked_range<size_t>& range) {
+ // index -2 means not initialized yet, -1 means intialized and decremented to 0 and then -1.
+ int idx_top_contact_above = -2;
+ int idx_bottom_contact_overlapping = -2;
+ int idx_object_layer_above = -2;
+ // Counting down due to the way idx_lower_or_equal caches indices to avoid repeated binary search over the complete sequence.
+ for (int idx_intermediate = int(range.end()) - 1; idx_intermediate >= int(range.begin()); -- idx_intermediate)
+ {
+ BOOST_LOG_TRIVIAL(trace) << "Support generator - generate_base_layers - creating layer " <<
+ idx_intermediate << " of " << intermediate_layers.size();
+ MyLayer &layer_intermediate = *intermediate_layers[idx_intermediate];
+ // Layers must be sorted by print_z.
+ assert(idx_intermediate == 0 || layer_intermediate.print_z >= intermediate_layers[idx_intermediate - 1]->print_z);
+
+ // Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new.
+ // New polygons for layer_intermediate.
+ Polygons polygons_new;
+
+ // Use the precomputed layer_support_areas.
+ idx_object_layer_above = std::max(0, idx_lower_or_equal(object.layers(), idx_object_layer_above,
+ [&layer_intermediate](const Layer *layer){ return layer->print_z <= layer_intermediate.print_z + EPSILON; }));
+ polygons_new = layer_support_areas[idx_object_layer_above];
+
+ // Polygons to trim polygons_new.
+ Polygons polygons_trimming;
+
+ // Trimming the base layer with any overlapping top layer.
+ // Following cases are recognized:
+ // 1) top.bottom_z >= base.top_z -> No overlap, no trimming needed.
+ // 2) base.bottom_z >= top.print_z -> No overlap, no trimming needed.
+ // 3) base.print_z > top.print_z && base.bottom_z >= top.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the base layer height where it overlaps the top layer. No trimming needed here.
+ // 4) base.print_z > top.bottom_z && base.bottom_z < top.bottom_z -> Base overlaps with top.bottom_z. This must not happen.
+ // 5) base.print_z <= top.print_z && base.bottom_z >= top.bottom_z -> Base is fully inside top. Trim base by top.
+ idx_top_contact_above = idx_lower_or_equal(top_contacts, idx_top_contact_above,
+ [&layer_intermediate](const MyLayer *layer){ return layer->bottom_z <= layer_intermediate.print_z - EPSILON; });
+ // Collect all the top_contact layer intersecting with this layer.
+ for ( int idx_top_contact_overlapping = idx_top_contact_above; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) {
+ MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping];
+ if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON)
+ break;
+ // Base must not overlap with top.bottom_z.
+ assert(! (layer_intermediate.print_z > layer_top_overlapping.bottom_z + EPSILON && layer_intermediate.bottom_z < layer_top_overlapping.bottom_z - EPSILON));
+ if (layer_intermediate.print_z <= layer_top_overlapping.print_z + EPSILON && layer_intermediate.bottom_z >= layer_top_overlapping.bottom_z - EPSILON)
+ // Base is fully inside top. Trim base by top.
+ polygons_append(polygons_trimming, layer_top_overlapping.polygons);
+ }
+
+ // Trimming the base layer with any overlapping bottom layer.
+ // Following cases are recognized:
+ // 1) bottom.bottom_z >= base.top_z -> No overlap, no trimming needed.
+ // 2) base.bottom_z >= bottom.print_z -> No overlap, no trimming needed.
+ // 3) base.print_z > bottom.bottom_z && base.bottom_z < bottom.bottom_z -> Overlap, which will be solved inside generate_toolpaths() by reducing the bottom layer height where it overlaps the base layer. No trimming needed here.
+ // 4) base.print_z > bottom.print_z && base.bottom_z >= bottom.print_z -> Base overlaps with bottom.print_z. This must not happen.
+ // 5) base.print_z <= bottom.print_z && base.bottom_z >= bottom.bottom_z -> Base is fully inside top. Trim base by top.
+ idx_bottom_contact_overlapping = idx_lower_or_equal(bottom_contacts, idx_bottom_contact_overlapping,
+ [&layer_intermediate](const MyLayer *layer){ return layer->bottom_print_z() <= layer_intermediate.print_z - EPSILON; });
+ // Collect all the bottom_contacts layer intersecting with this layer.
+ for (int i = idx_bottom_contact_overlapping; i >= 0; -- i) {
+ MyLayer &layer_bottom_overlapping = *bottom_contacts[i];
+ if (layer_bottom_overlapping.print_z < layer_intermediate.bottom_print_z() + EPSILON)
+ break;
+ // Base must not overlap with bottom.top_z.
+ assert(! (layer_intermediate.print_z > layer_bottom_overlapping.print_z + EPSILON && layer_intermediate.bottom_z < layer_bottom_overlapping.print_z - EPSILON));
+ if (layer_intermediate.print_z <= layer_bottom_overlapping.print_z + EPSILON && layer_intermediate.bottom_z >= layer_bottom_overlapping.bottom_print_z() - EPSILON)
+ // Base is fully inside bottom. Trim base by bottom.
+ polygons_append(polygons_trimming, layer_bottom_overlapping.polygons);
+ }
+
+ #ifdef SLIC3R_DEBUG
+ {
+ BoundingBox bbox = get_extents(polygons_new);
+ bbox.merge(get_extents(polygons_trimming));
+ ::Slic3r::SVG svg(debug_out_path("support-intermediate-layers-raw-%d-%lf.svg", iRun, layer_intermediate.print_z), bbox);
+ svg.draw(union_ex(polygons_new, false), "blue", 0.5f);
+ svg.draw(to_polylines(polygons_new), "blue");
+ svg.draw(union_ex(polygons_trimming, true), "red", 0.5f);
+ svg.draw(to_polylines(polygons_trimming), "red");
+ }
+ #endif /* SLIC3R_DEBUG */
+
+ // Trim the polygons, store them.
+ if (polygons_trimming.empty())
+ layer_intermediate.polygons = std::move(polygons_new);
+ else
+ layer_intermediate.polygons = diff(
+ polygons_new,
+ polygons_trimming,
+ true); // safety offset to merge the touching source polygons
+ layer_intermediate.layer_type = sltBase;
+
+ #if 0
+ // Fillet the base polygons and trim them again with the top, interface and contact layers.
+ $base->{$i} = diff(
+ offset2(
+ $base->{$i},
+ $fillet_radius_scaled,
+ -$fillet_radius_scaled,
+ # Use a geometric offsetting for filleting.
+ JT_ROUND,
+ 0.2*$fillet_radius_scaled),
+ $trim_polygons,
+ false); // don't apply the safety offset.
+ }
+ #endif
+ }
+ });
+ BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_base_layers() in parallel - end";
+
+#ifdef SLIC3R_DEBUG
+ for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it)
+ ::Slic3r::SVG::export_expolygons(
+ debug_out_path("support-intermediate-layers-untrimmed-%d-%lf.svg", iRun, (*it)->print_z),
+ union_ex((*it)->polygons, false));
+ ++ iRun;
+#endif /* SLIC3R_DEBUG */
+
+// trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy);
+ this->trim_support_layers_by_object(object, intermediate_layers,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
+ m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
+}
+
+void PrintObjectSupportMaterial::trim_support_layers_by_object(
+ const PrintObject &object,
+ MyLayersPtr &support_layers,
+ const coordf_t gap_extra_above,
+ const coordf_t gap_extra_below,
+ const coordf_t gap_xy) const
+{
+ const float gap_xy_scaled = float(scale_(gap_xy));
+
+ // Collect non-empty layers to be processed in parallel.
+ // This is a good idea as pulling a thread from a thread pool for an empty task is expensive.
+ MyLayersPtr nonempty_layers;
+ nonempty_layers.reserve(support_layers.size());
+ for (size_t idx_layer = 0; idx_layer < support_layers.size(); ++ idx_layer) {
+ MyLayer *support_layer = support_layers[idx_layer];
+ if (! support_layer->polygons.empty() && support_layer->print_z >= m_slicing_params.raft_contact_top_z + EPSILON)
+ // Non-empty support layer and not a raft layer.
+ nonempty_layers.push_back(support_layer);
+ }
+
+ // For all intermediate support layers:
+ BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - start";
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, nonempty_layers.size()),
+ [this, &object, &nonempty_layers, gap_extra_above, gap_extra_below, gap_xy_scaled](const tbb::blocked_range<size_t>& range) {
+ size_t idx_object_layer_overlapping = size_t(-1);
+ for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
+ MyLayer &support_layer = *nonempty_layers[idx_layer];
+ // BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming non-empty layer " << idx_layer << " of " << nonempty_layers.size();
+ assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON);
+ // Find the overlapping object layers including the extra above / below gap.
+ coordf_t z_threshold = support_layer.print_z - support_layer.height - gap_extra_below + EPSILON;
+ idx_object_layer_overlapping = idx_higher_or_equal(
+ object.layers(), idx_object_layer_overlapping,
+ [z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; });
+ // Collect all the object layers intersecting with this layer.
+ Polygons polygons_trimming;
+ size_t i = idx_object_layer_overlapping;
+ for (; i < object.layers().size(); ++ i) {
+ const Layer &object_layer = *object.layers()[i];
+ if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON)
+ break;
+ polygons_append(polygons_trimming, offset(object_layer.slices.expolygons, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ }
+ if (! m_slicing_params.soluble_interface) {
+ // Collect all bottom surfaces, which will be extruded with a bridging flow.
+ for (; i < object.layers().size(); ++ i) {
+ const Layer &object_layer = *object.layers()[i];
+ bool some_region_overlaps = false;
+ for (LayerRegion *region : object_layer.regions()) {
+ coordf_t bridging_height = region->region()->bridging_height_avg(*this->m_print_config);
+ if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON)
+ break;
+ some_region_overlaps = true;
+ polygons_append(polygons_trimming,
+ offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)),
+ gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ if (region->region()->config().overhangs.value)
+ SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming);
+ }
+ if (! some_region_overlaps)
+ break;
+ }
+ }
+ // $layer->slices contains the full shape of layer, thus including
+ // perimeter's width. $support contains the full shape of support
+ // material, thus including the width of its foremost extrusion.
+ // We leave a gap equal to a full extrusion width.
+ support_layer.polygons = diff(support_layer.polygons, polygons_trimming);
+ }
+ });
+ BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::trim_support_layers_by_object() in parallel - end";
+}
+
+PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raft_base(
+ const MyLayersPtr &top_contacts,
+ const MyLayersPtr &interface_layers,
+ const MyLayersPtr &base_layers,
+ MyLayerStorage &layer_storage) const
+{
+ // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed.
+ const float inflate_factor_fine = float(scale_((m_slicing_params.raft_layers() > 1) ? 0.5 : EPSILON));
+ const float inflate_factor_1st_layer = float(scale_(3.)) - inflate_factor_fine;
+ MyLayer *contacts = top_contacts .empty() ? nullptr : top_contacts .front();
+ MyLayer *interfaces = interface_layers.empty() ? nullptr : interface_layers.front();
+ MyLayer *columns_base = base_layers .empty() ? nullptr : base_layers .front();
+ if (contacts != nullptr && contacts->print_z > std::max(m_slicing_params.first_print_layer_height, m_slicing_params.raft_contact_top_z) + EPSILON)
+ // This is not the raft contact layer.
+ contacts = nullptr;
+ if (interfaces != nullptr && interfaces->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON)
+ // This is not the raft column base layer.
+ interfaces = nullptr;
+ if (columns_base != nullptr && columns_base->bottom_print_z() > m_slicing_params.raft_interface_top_z + EPSILON)
+ // This is not the raft interface layer.
+ columns_base = nullptr;
+
+ Polygons interface_polygons;
+ if (contacts != nullptr && ! contacts->polygons.empty())
+ polygons_append(interface_polygons, offset(contacts->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ if (interfaces != nullptr && ! interfaces->polygons.empty())
+ polygons_append(interface_polygons, offset(interfaces->polygons, inflate_factor_fine, SUPPORT_SURFACES_OFFSET_PARAMETERS));
+
+ // Output vector.
+ MyLayersPtr raft_layers;
+
+ if (m_slicing_params.raft_layers() > 1) {
+ Polygons base;
+ Polygons columns;
+ if (columns_base != nullptr) {
+ base = columns_base->polygons;
+ columns = base;
+ if (! interface_polygons.empty())
+ // Trim the 1st layer columns with the inflated interface polygons.
+ columns = diff(columns, interface_polygons);
+ }
+ if (! interface_polygons.empty()) {
+ // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface.
+ base = union_(base, interface_polygons);
+ }
+ // Do not add the raft contact layer, only add the raft layers below the contact layer.
+ // Insert the 1st layer.
+ {
+ MyLayer &new_layer = layer_allocate(layer_storage, (m_slicing_params.base_raft_layers > 0) ? sltRaftBase : sltRaftInterface);
+ raft_layers.push_back(&new_layer);
+ new_layer.print_z = m_slicing_params.first_print_layer_height;
+ new_layer.height = m_slicing_params.first_print_layer_height;
+ new_layer.bottom_z = 0.;
+ new_layer.polygons = offset(base, inflate_factor_1st_layer);
+ }
+ // Insert the base layers.
+ for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) {
+ coordf_t print_z = raft_layers.back()->print_z;
+ MyLayer &new_layer = layer_allocate(layer_storage, sltRaftBase);
+ raft_layers.push_back(&new_layer);
+ new_layer.print_z = print_z + m_slicing_params.base_raft_layer_height;
+ new_layer.height = m_slicing_params.base_raft_layer_height;
+ new_layer.bottom_z = print_z;
+ new_layer.polygons = base;
+ }
+ // Insert the interface layers.
+ for (size_t i = 1; i < m_slicing_params.interface_raft_layers; ++ i) {
+ coordf_t print_z = raft_layers.back()->print_z;
+ MyLayer &new_layer = layer_allocate(layer_storage, sltRaftInterface);
+ raft_layers.push_back(&new_layer);
+ new_layer.print_z = print_z + m_slicing_params.interface_raft_layer_height;
+ new_layer.height = m_slicing_params.interface_raft_layer_height;
+ new_layer.bottom_z = print_z;
+ new_layer.polygons = interface_polygons;
+ //FIXME misusing contact_polygons for support columns.
+ new_layer.contact_polygons = new Polygons(columns);
+ }
+ } else if (columns_base != nullptr) {
+ // Expand the bases of the support columns in the 1st layer.
+ columns_base->polygons = diff(
+ offset(columns_base->polygons, inflate_factor_1st_layer),
+ offset(m_object->layers().front()->slices.expolygons, scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS));
+ if (contacts != nullptr)
+ columns_base->polygons = diff(columns_base->polygons, interface_polygons);
+ }
+
+ return raft_layers;
+}
+
+// Convert some of the intermediate layers into top/bottom interface layers.
+PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_interface_layers(
+ const MyLayersPtr &bottom_contacts,
+ const MyLayersPtr &top_contacts,
+ MyLayersPtr &intermediate_layers,
+ MyLayerStorage &layer_storage) const
+{
+// my $area_threshold = $self->interface_flow->scaled_spacing ** 2;
+
+ MyLayersPtr interface_layers;
+ // Contact layer is considered an interface layer, therefore run the following block only if support_material_interface_layers > 1.
+ if (! intermediate_layers.empty() && m_object_config->support_material_interface_layers.value > 1) {
+ // For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers.
+ BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start";
+ interface_layers.assign(intermediate_layers.size(), nullptr);
+ tbb::spin_mutex layer_storage_mutex;
+ tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate_layers.size()),
+ [this, &bottom_contacts, &top_contacts, &intermediate_layers, &layer_storage, &layer_storage_mutex, &interface_layers](const tbb::blocked_range<size_t>& range) {
+ // Index of the first top contact layer intersecting the current intermediate layer.
+ size_t idx_top_contact_first = size_t(-1);
+ // Index of the first bottom contact layer intersecting the current intermediate layer.
+ size_t idx_bottom_contact_first = size_t(-1);
+ for (size_t idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) {
+ MyLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer];
+ // Top / bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces.
+ coordf_t top_z = intermediate_layers[std::min<int>(intermediate_layers.size()-1, idx_intermediate_layer + m_object_config->support_material_interface_layers - 1)]->print_z;
+ coordf_t bottom_z = intermediate_layers[std::max<int>(0, int(idx_intermediate_layer) - int(m_object_config->support_material_interface_layers) + 1)]->bottom_z;
+ // Move idx_top_contact_first up until above the current print_z.
+ idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
+ // Collect the top contact areas above this intermediate layer, below top_z.
+ Polygons polygons_top_contact_projected;
+ for (size_t idx_top_contact = idx_top_contact_first; idx_top_contact < top_contacts.size(); ++ idx_top_contact) {
+ const MyLayer &top_contact_layer = *top_contacts[idx_top_contact];
+ //FIXME maybe this adds one interface layer in excess?
+ if (top_contact_layer.bottom_z - EPSILON > top_z)
+ break;
+ polygons_append(polygons_top_contact_projected, top_contact_layer.polygons);
+ }
+ // Move idx_bottom_contact_first up until touching bottom_z.
+ idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; });
+ // Collect the top contact areas above this intermediate layer, below top_z.
+ Polygons polygons_bottom_contact_projected;
+ for (size_t idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < bottom_contacts.size(); ++ idx_bottom_contact) {
+ const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact];
+ if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z)
+ break;
+ polygons_append(polygons_bottom_contact_projected, bottom_contact_layer.polygons);
+ }
+
+ if (polygons_top_contact_projected.empty() && polygons_bottom_contact_projected.empty())
+ continue;
+
+ // Insert a new layer into top_interface_layers.
+ MyLayer &layer_new = layer_allocate(layer_storage, layer_storage_mutex,
+ polygons_top_contact_projected.empty() ? sltBottomInterface : sltTopInterface);
+ layer_new.print_z = intermediate_layer.print_z;
+ layer_new.bottom_z = intermediate_layer.bottom_z;
+ layer_new.height = intermediate_layer.height;
+ layer_new.bridging = intermediate_layer.bridging;
+ interface_layers[idx_intermediate_layer] = &layer_new;
+
+ polygons_append(polygons_top_contact_projected, polygons_bottom_contact_projected);
+ polygons_top_contact_projected = union_(polygons_top_contact_projected, true);
+ layer_new.polygons = intersection(intermediate_layer.polygons, polygons_top_contact_projected);
+ //FIXME filter layer_new.polygons islands by a minimum area?
+ // $interface_area = [ grep abs($_->area) >= $area_threshold, @$interface_area ];
+ intermediate_layer.polygons = diff(intermediate_layer.polygons, polygons_top_contact_projected, false);
+ }
+ });
+
+ // Compress contact_out, remove the nullptr items.
+ remove_nulls(interface_layers);
+ BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start";
+ }
+
+ return interface_layers;
+}
+
+static inline void fill_expolygons_generate_paths(
+ ExtrusionEntitiesPtr &dst,
+ const ExPolygons &expolygons,
+ Fill *filler,
+ float density,
+ ExtrusionRole role,
+ const Flow &flow)
+{
+ FillParams fill_params;
+ fill_params.density = density;
+ fill_params.complete = true;
+ fill_params.dont_adjust = true;
+ for (const ExPolygon &expoly : expolygons) {
+ Surface surface(stInternal, expoly);
+ extrusion_entities_append_paths(
+ dst,
+ filler->fill_surface(&surface, fill_params),
+ role,
+ flow.mm3_per_mm(), flow.width, flow.height);
+ }
+}
+
+static inline void fill_expolygons_generate_paths(
+ ExtrusionEntitiesPtr &dst,
+ ExPolygons &&expolygons,
+ Fill *filler,
+ float density,
+ ExtrusionRole role,
+ const Flow &flow)
+{
+ FillParams fill_params;
+ fill_params.density = density;
+ fill_params.complete = true;
+ fill_params.dont_adjust = true;
+ for (ExPolygon &expoly : expolygons) {
+ Surface surface(stInternal, std::move(expoly));
+ extrusion_entities_append_paths(
+ dst,
+ filler->fill_surface(&surface, fill_params),
+ role,
+ flow.mm3_per_mm(), flow.width, flow.height);
+ }
+}
+
+// Support layers, partially processed.
+struct MyLayerExtruded
+{
+ MyLayerExtruded() : layer(nullptr), m_polygons_to_extrude(nullptr) {}
+ ~MyLayerExtruded() { delete m_polygons_to_extrude; m_polygons_to_extrude = nullptr; }
+
+ bool empty() const {
+ return layer == nullptr || layer->polygons.empty();
+ }
+
+ void set_polygons_to_extrude(Polygons &&polygons) {
+ if (m_polygons_to_extrude == nullptr)
+ m_polygons_to_extrude = new Polygons(std::move(polygons));
+ else
+ *m_polygons_to_extrude = std::move(polygons);
+ }
+ Polygons& polygons_to_extrude() { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; }
+ const Polygons& polygons_to_extrude() const { return (m_polygons_to_extrude == nullptr) ? layer->polygons : *m_polygons_to_extrude; }
+
+ bool could_merge(const MyLayerExtruded &other) const {
+ return ! this->empty() && ! other.empty() &&
+ std::abs(this->layer->height - other.layer->height) < EPSILON &&
+ this->layer->bridging == other.layer->bridging;
+ }
+
+ // Merge regions, perform boolean union over the merged polygons.
+ void merge(MyLayerExtruded &&other) {
+ assert(this->could_merge(other));
+ // 1) Merge the rest polygons to extrude, if there are any.
+ if (other.m_polygons_to_extrude != nullptr) {
+ if (m_polygons_to_extrude == nullptr) {
+ // This layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet).
+ assert(this->extrusions.empty());
+ m_polygons_to_extrude = new Polygons(this->layer->polygons);
+ }
+ Slic3r::polygons_append(*m_polygons_to_extrude, std::move(*other.m_polygons_to_extrude));
+ *m_polygons_to_extrude = union_(*m_polygons_to_extrude, true);
+ delete other.m_polygons_to_extrude;
+ other.m_polygons_to_extrude = nullptr;
+ } else if (m_polygons_to_extrude != nullptr) {
+ assert(other.m_polygons_to_extrude == nullptr);
+ // The other layer has no extrusions generated yet, if it has no m_polygons_to_extrude (its area to extrude was not reduced yet).
+ assert(other.extrusions.empty());
+ Slic3r::polygons_append(*m_polygons_to_extrude, other.layer->polygons);
+ *m_polygons_to_extrude = union_(*m_polygons_to_extrude, true);
+ }
+ // 2) Merge the extrusions.
+ this->extrusions.insert(this->extrusions.end(), other.extrusions.begin(), other.extrusions.end());
+ other.extrusions.clear();
+ // 3) Merge the infill polygons.
+ Slic3r::polygons_append(this->layer->polygons, std::move(other.layer->polygons));
+ this->layer->polygons = union_(this->layer->polygons, true);
+ other.layer->polygons.clear();
+ }
+
+ void polygons_append(Polygons &dst) const {
+ if (layer != NULL && ! layer->polygons.empty())
+ Slic3r::polygons_append(dst, layer->polygons);
+ }
+
+ // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height).
+ PrintObjectSupportMaterial::MyLayer *layer;
+ // Collect extrusions. They will be exported sorted by the bottom height.
+ ExtrusionEntitiesPtr extrusions;
+ // In case the extrusions are non-empty, m_polygons_to_extrude may contain the rest areas yet to be filled by additional support.
+ // This is useful mainly for the loop interfaces, which are generated before the zig-zag infills.
+ Polygons *m_polygons_to_extrude;
+};
+
+typedef std::vector<MyLayerExtruded*> MyLayerExtrudedPtrs;
+
+struct LoopInterfaceProcessor
+{
+ LoopInterfaceProcessor(coordf_t circle_r) :
+ n_contact_loops(0),
+ circle_radius(circle_r),
+ circle_distance(circle_r * 3.)
+ {
+ // Shape of the top contact area.
+ circle.points.reserve(6);
+ for (size_t i = 0; i < 6; ++ i) {
+ double angle = double(i) * M_PI / 3.;
+ circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle)));
+ }
+ }
+
+ // Generate loop contacts at the top_contact_layer,
+ // trim the top_contact_layer->polygons with the areas covered by the loops.
+ void generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const;
+
+ int n_contact_loops;
+ coordf_t circle_radius;
+ coordf_t circle_distance;
+ Polygon circle;
+};
+
+void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) const
+{
+ if (n_contact_loops == 0 || top_contact_layer.empty())
+ return;
+
+ Flow flow = interface_flow_src;
+ flow.height = float(top_contact_layer.layer->height);
+
+ Polygons overhang_polygons;
+ if (top_contact_layer.layer->overhang_polygons != nullptr)
+ overhang_polygons = std::move(*top_contact_layer.layer->overhang_polygons);
+
+ // Generate the outermost loop.
+ // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped)
+ ExPolygons top_contact_expolygons = offset_ex(union_ex(top_contact_layer.layer->polygons), - 0.5f * flow.scaled_width());
+
+ // Grid size and bit shifts for quick and exact to/from grid coordinates manipulation.
+ coord_t circle_grid_resolution = 1;
+ coord_t circle_grid_powerof2 = 0;
+ {
+ // epsilon to account for rounding errors
+ coord_t circle_grid_resolution_non_powerof2 = coord_t(2. * circle_distance + 3.);
+ while (circle_grid_resolution < circle_grid_resolution_non_powerof2) {
+ circle_grid_resolution <<= 1;
+ ++ circle_grid_powerof2;
+ }
+ }
+
+ struct PointAccessor {
+ const Point* operator()(const Point &pt) const { return &pt; }
+ };
+ typedef ClosestPointInRadiusLookup<Point, PointAccessor> ClosestPointLookupType;
+
+ Polygons loops0;
+ {
+ // find centerline of the external loop of the contours
+ // Only consider the loops facing the overhang.
+ Polygons external_loops;
+ // Holes in the external loops.
+ Polygons circles;
+ Polygons overhang_with_margin = offset(union_ex(overhang_polygons), 0.5f * flow.scaled_width());
+ for (ExPolygons::iterator it_contact_expoly = top_contact_expolygons.begin(); it_contact_expoly != top_contact_expolygons.end(); ++ it_contact_expoly) {
+ // Store the circle centers placed for an expolygon into a regular grid, hashed by the circle centers.
+ ClosestPointLookupType circle_centers_lookup(coord_t(circle_distance - SCALED_EPSILON));
+ Points circle_centers;
+ Point center_last;
+ // For each contour of the expolygon, start with the outer contour, continue with the holes.
+ for (size_t i_contour = 0; i_contour <= it_contact_expoly->holes.size(); ++ i_contour) {
+ Polygon &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1];
+ const Point *seg_current_pt = nullptr;
+ coordf_t seg_current_t = 0.;
+ if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) {
+ // The contour is below the overhang at least to some extent.
+ //FIXME ideally one would place the circles below the overhang only.
+ // Walk around the contour and place circles so their centers are not closer than circle_distance from each other.
+ if (circle_centers.empty()) {
+ // Place the first circle.
+ seg_current_pt = &contour.points.front();
+ seg_current_t = 0.;
+ center_last = *seg_current_pt;
+ circle_centers_lookup.insert(center_last);
+ circle_centers.push_back(center_last);
+ }
+ for (Points::const_iterator it = contour.points.begin() + 1; it != contour.points.end(); ++it) {
+ // Is it possible to place a circle on this segment? Is it not too close to any of the circles already placed on this contour?
+ const Point &p1 = *(it-1);
+ const Point &p2 = *it;
+ // Intersection of a ray (p1, p2) with a circle placed at center_last, with radius of circle_distance.
+ const Vec2d v_seg(coordf_t(p2(0)) - coordf_t(p1(0)), coordf_t(p2(1)) - coordf_t(p1(1)));
+ const Vec2d v_cntr(coordf_t(p1(0) - center_last(0)), coordf_t(p1(1) - center_last(1)));
+ coordf_t a = v_seg.squaredNorm();
+ coordf_t b = 2. * v_seg.dot(v_cntr);
+ coordf_t c = v_cntr.squaredNorm() - circle_distance * circle_distance;
+ coordf_t disc = b * b - 4. * a * c;
+ if (disc > 0.) {
+ // The circle intersects a ray. Avoid the parts of the segment inside the circle.
+ coordf_t t1 = (-b - sqrt(disc)) / (2. * a);
+ coordf_t t2 = (-b + sqrt(disc)) / (2. * a);
+ coordf_t t0 = (seg_current_pt == &p1) ? seg_current_t : 0.;
+ // Take the lowest t in <t0, 1.>, excluding <t1, t2>.
+ coordf_t t;
+ if (t0 <= t1)
+ t = t0;
+ else if (t2 <= 1.)
+ t = t2;
+ else {
+ // Try the following segment.
+ seg_current_pt = nullptr;
+ continue;
+ }
+ seg_current_pt = &p1;
+ seg_current_t = t;
+ center_last = Point(p1(0) + coord_t(v_seg(0) * t), p1(1) + coord_t(v_seg(1) * t));
+ // It has been verified that the new point is far enough from center_last.
+ // Ensure, that it is far enough from all the centers.
+ std::pair<const Point*, coordf_t> circle_closest = circle_centers_lookup.find(center_last);
+ if (circle_closest.first != nullptr) {
+ -- it;
+ continue;
+ }
+ } else {
+ // All of the segment is outside the circle. Take the first point.
+ seg_current_pt = &p1;
+ seg_current_t = 0.;
+ center_last = p1;
+ }
+ // Place the first circle.
+ circle_centers_lookup.insert(center_last);
+ circle_centers.push_back(center_last);
+ }
+ external_loops.push_back(std::move(contour));
+ for (const Point &center : circle_centers) {
+ circles.push_back(circle);
+ circles.back().translate(center);
+ }
+ }
+ }
+ }
+ // Apply a pattern to the external loops.
+ loops0 = diff(external_loops, circles);
+ }
+
+ Polylines loop_lines;
+ {
+ // make more loops
+ Polygons loop_polygons = loops0;
+ for (size_t i = 1; i < n_contact_loops; ++ i)
+ polygons_append(loop_polygons,
+ offset2(
+ loops0,
+ - int(i) * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(),
+ 0.5f * flow.scaled_spacing()));
+ // Clip such loops to the side oriented towards the object.
+ // Collect split points, so they will be recognized after the clipping.
+ // At the split points the clipped pieces will be stitched back together.
+ loop_lines.reserve(loop_polygons.size());
+ std::unordered_map<Point, int, PointHash> map_split_points;
+ for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) {
+ assert(map_split_points.find(it->first_point()) == map_split_points.end());
+ map_split_points[it->first_point()] = -1;
+ loop_lines.push_back(it->split_at_first_point());
+ }
+ loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN)));
+ // Because a closed loop has been split to a line, loop_lines may contain continuous segments split to 2 pieces.
+ // Try to connect them.
+ for (int i_line = 0; i_line < int(loop_lines.size()); ++ i_line) {
+ Polyline &polyline = loop_lines[i_line];
+ auto it = map_split_points.find(polyline.first_point());
+ if (it != map_split_points.end()) {
+ // This is a stitching point.
+ // If this assert triggers, multiple source polygons likely intersected at this point.
+ assert(it->second != -2);
+ if (it->second < 0) {
+ // First occurence.
+ it->second = i_line;
+ } else {
+ // Second occurence. Join the lines.
+ Polyline &polyline_1st = loop_lines[it->second];
+ assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first);
+ if (polyline_1st.first_point() == it->first)
+ polyline_1st.reverse();
+ polyline_1st.append(std::move(polyline));
+ it->second = -2;
+ }
+ continue;
+ }
+ it = map_split_points.find(polyline.last_point());
+ if (it != map_split_points.end()) {
+ // This is a stitching point.
+ // If this assert triggers, multiple source polygons likely intersected at this point.
+ assert(it->second != -2);
+ if (it->second < 0) {
+ // First occurence.
+ it->second = i_line;
+ } else {
+ // Second occurence. Join the lines.
+ Polyline &polyline_1st = loop_lines[it->second];
+ assert(polyline_1st.first_point() == it->first || polyline_1st.last_point() == it->first);
+ if (polyline_1st.first_point() == it->first)
+ polyline_1st.reverse();
+ polyline.reverse();
+ polyline_1st.append(std::move(polyline));
+ it->second = -2;
+ }
+ }
+ }
+ // Remove empty lines.
+ remove_degenerate(loop_lines);
+ }
+
+ // add the contact infill area to the interface area
+ // note that growing loops by $circle_radius ensures no tiny
+ // extrusions are left inside the circles; however it creates
+ // a very large gap between loops and contact_infill_polygons, so maybe another
+ // solution should be found to achieve both goals
+ // Store the trimmed polygons into a separate polygon set, so the original infill area remains intact for
+ // "modulate by layer thickness".
+ top_contact_layer.set_polygons_to_extrude(diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1))));
+
+ // Transform loops into ExtrusionPath objects.
+ extrusion_entities_append_paths(
+ top_contact_layer.extrusions,
+ STDMOVE(loop_lines),
+ erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height);
+}
+
+#ifdef SLIC3R_DEBUG
+static std::string dbg_index_to_color(int idx)
+{
+ if (idx < 0)
+ return "yellow";
+ idx = idx % 3;
+ switch (idx) {
+ case 0: return "red";
+ case 1: return "green";
+ default: return "blue";
+ }
+}
+#endif /* SLIC3R_DEBUG */
+
+// When extruding a bottom interface layer over an object, the bottom interface layer is extruded in a thin air, therefore
+// it is being extruded with a bridging flow to not shrink excessively (the die swell effect).
+// Tiny extrusions are better avoided and it is always better to anchor the thread to an existing support structure if possible.
+// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers,
+// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers
+// to stick too firmly to the object.
+void modulate_extrusion_by_overlapping_layers(
+ // Extrusions generated for this_layer.
+ ExtrusionEntitiesPtr &extrusions_in_out,
+ const PrintObjectSupportMaterial::MyLayer &this_layer,
+ // Multiple layers overlapping with this_layer, sorted bottom up.
+ const PrintObjectSupportMaterial::MyLayersPtr &overlapping_layers)
+{
+ size_t n_overlapping_layers = overlapping_layers.size();
+ if (n_overlapping_layers == 0 || extrusions_in_out.empty())
+ // The extrusions do not overlap with any other extrusion.
+ return;
+
+ // Get the initial extrusion parameters.
+ ExtrusionPath *extrusion_path_template = dynamic_cast<ExtrusionPath*>(extrusions_in_out.front());
+ assert(extrusion_path_template != nullptr);
+ ExtrusionRole extrusion_role = extrusion_path_template->role();
+ float extrusion_width = extrusion_path_template->width;
+
+ struct ExtrusionPathFragment
+ {
+ ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {};
+ ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {};
+
+ Polylines polylines;
+ double mm3_per_mm;
+ float width;
+ float height;
+ };
+
+ // Split the extrusions by the overlapping layers, reduce their extrusion rate.
+ // The last path_fragment is from this_layer.
+ std::vector<ExtrusionPathFragment> path_fragments(
+ n_overlapping_layers + 1,
+ ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height));
+ // Don't use it, it will be released.
+ extrusion_path_template = nullptr;
+
+#ifdef SLIC3R_DEBUG
+ static int iRun = 0;
+ ++ iRun;
+ BoundingBox bbox;
+ for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) {
+ const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer];
+ bbox.merge(get_extents(overlapping_layer.polygons));
+ }
+ for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) {
+ ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(*it);
+ assert(path != nullptr);
+ bbox.merge(get_extents(path->polyline));
+ }
+ SVG svg(debug_out_path("support-fragments-%d-%lf.svg", iRun, this_layer.print_z).c_str(), bbox);
+ const float transparency = 0.5f;
+ // Filled polygons for the overlapping regions.
+ svg.draw(union_ex(this_layer.polygons), dbg_index_to_color(-1), transparency);
+ for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) {
+ const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer];
+ svg.draw(union_ex(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), transparency);
+ }
+ // Contours of the overlapping regions.
+ svg.draw(to_polylines(this_layer.polygons), dbg_index_to_color(-1), scale_(0.2));
+ for (size_t i_overlapping_layer = 0; i_overlapping_layer < n_overlapping_layers; ++ i_overlapping_layer) {
+ const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer];
+ svg.draw(to_polylines(overlapping_layer.polygons), dbg_index_to_color(int(i_overlapping_layer)), scale_(0.1));
+ }
+ // Fill extrusion, the source.
+ for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) {
+ ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(*it);
+ std::string color_name;
+ switch ((it - extrusions_in_out.begin()) % 9) {
+ case 0: color_name = "magenta"; break;
+ case 1: color_name = "deepskyblue"; break;
+ case 2: color_name = "coral"; break;
+ case 3: color_name = "goldenrod"; break;
+ case 4: color_name = "orange"; break;
+ case 5: color_name = "olivedrab"; break;
+ case 6: color_name = "blueviolet"; break;
+ case 7: color_name = "brown"; break;
+ default: color_name = "orchid"; break;
+ }
+ svg.draw(path->polyline, color_name, scale_(0.2));
+ }
+#endif /* SLIC3R_DEBUG */
+
+ // End points of the original paths.
+ std::vector<std::pair<Point, Point>> path_ends;
+ // Collect the paths of this_layer.
+ {
+ Polylines &polylines = path_fragments.back().polylines;
+ for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) {
+ ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(*it);
+ assert(path != nullptr);
+ polylines.emplace_back(Polyline(std::move(path->polyline)));
+ path_ends.emplace_back(std::pair<Point, Point>(polylines.back().points.front(), polylines.back().points.back()));
+ }
+ }
+ // Destroy the original extrusion paths, their polylines were moved to path_fragments already.
+ // This will be the destination for the new paths.
+ extrusions_in_out.clear();
+
+ // Fragment the path segments by overlapping layers. The overlapping layers are sorted by an increasing print_z.
+ // Trim by the highest overlapping layer first.
+ for (int i_overlapping_layer = int(n_overlapping_layers) - 1; i_overlapping_layer >= 0; -- i_overlapping_layer) {
+ const PrintObjectSupportMaterial::MyLayer &overlapping_layer = *overlapping_layers[i_overlapping_layer];
+ ExtrusionPathFragment &frag = path_fragments[i_overlapping_layer];
+ Polygons polygons_trimming = offset(union_ex(overlapping_layer.polygons), float(scale_(0.5*extrusion_width)));
+ frag.polylines = intersection_pl(path_fragments.back().polylines, polygons_trimming, false);
+ path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming, false);
+ // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter).
+ assert(this_layer.print_z > overlapping_layer.print_z);
+ frag.height = float(this_layer.print_z - overlapping_layer.print_z);
+ frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f, false).mm3_per_mm();
+#ifdef SLIC3R_DEBUG
+ svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1));
+#endif /* SLIC3R_DEBUG */
+ }
+
+#ifdef SLIC3R_DEBUG
+ svg.draw(path_fragments.back().polylines, dbg_index_to_color(-1), scale_(0.1));
+ svg.Close();
+#endif /* SLIC3R_DEBUG */
+
+ // Now chain the split segments using hashing and a nearly exact match, maintaining the order of segments.
+ // Create a single ExtrusionPath or ExtrusionEntityCollection per source ExtrusionPath.
+ // Map of fragment start/end points to a pair of <i_overlapping_layer, i_polyline_in_layer>
+ // Because a non-exact matching is used for the end points, a multi-map is used.
+ // As the clipper library may reverse the order of some clipped paths, store both ends into the map.
+ struct ExtrusionPathFragmentEnd
+ {
+ ExtrusionPathFragmentEnd(size_t alayer_idx, size_t apolyline_idx, bool ais_start) :
+ layer_idx(alayer_idx), polyline_idx(apolyline_idx), is_start(ais_start) {}
+ size_t layer_idx;
+ size_t polyline_idx;
+ bool is_start;
+ };
+ class ExtrusionPathFragmentEndPointAccessor {
+ public:
+ ExtrusionPathFragmentEndPointAccessor(const std::vector<ExtrusionPathFragment> &path_fragments) : m_path_fragments(path_fragments) {}
+ // Return an end point of a fragment, or nullptr if the fragment has been consumed already.
+ const Point* operator()(const ExtrusionPathFragmentEnd &fragment_end) const {
+ const Polyline &polyline = m_path_fragments[fragment_end.layer_idx].polylines[fragment_end.polyline_idx];
+ return polyline.points.empty() ? nullptr :
+ (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back());
+ }
+ private:
+ ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {}
+ const std::vector<ExtrusionPathFragment> &m_path_fragments;
+ };
+ const coord_t search_radius = 7;
+ ClosestPointInRadiusLookup<ExtrusionPathFragmentEnd, ExtrusionPathFragmentEndPointAccessor> map_fragment_starts(
+ search_radius, ExtrusionPathFragmentEndPointAccessor(path_fragments));
+ for (size_t i_overlapping_layer = 0; i_overlapping_layer <= n_overlapping_layers; ++ i_overlapping_layer) {
+ const Polylines &polylines = path_fragments[i_overlapping_layer].polylines;
+ for (size_t i_polyline = 0; i_polyline < polylines.size(); ++ i_polyline) {
+ // Map a starting point of a polyline to a pair of <layer, polyline>
+ if (polylines[i_polyline].points.size() >= 2) {
+ map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, true));
+ map_fragment_starts.insert(ExtrusionPathFragmentEnd(i_overlapping_layer, i_polyline, false));
+ }
+ }
+ }
+
+ // For each source path:
+ for (size_t i_path = 0; i_path < path_ends.size(); ++ i_path) {
+ const Point &pt_start = path_ends[i_path].first;
+ const Point &pt_end = path_ends[i_path].second;
+ Point pt_current = pt_start;
+ // Find a chain of fragments with the original / reduced print height.
+ ExtrusionMultiPath multipath;
+ for (;;) {
+ // Find a closest end point to pt_current.
+ std::pair<const ExtrusionPathFragmentEnd*, coordf_t> end_and_dist2 = map_fragment_starts.find(pt_current);
+ // There may be a bug in Clipper flipping the order of two last points in a fragment?
+ // assert(end_and_dist2.first != nullptr);
+ assert(end_and_dist2.first == nullptr || end_and_dist2.second < search_radius * search_radius);
+ if (end_and_dist2.first == nullptr) {
+ // New fragment connecting to pt_current was not found.
+ // Verify that the last point found is close to the original end point of the unfragmented path.
+ //const double d2 = (pt_end - pt_current).squaredNorm();
+ //assert(d2 < coordf_t(search_radius * search_radius));
+ // End of the path.
+ break;
+ }
+ const ExtrusionPathFragmentEnd &fragment_end_min = *end_and_dist2.first;
+ // Fragment to consume.
+ ExtrusionPathFragment &frag = path_fragments[fragment_end_min.layer_idx];
+ Polyline &frag_polyline = frag.polylines[fragment_end_min.polyline_idx];
+ // Path to append the fragment to.
+ ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back();
+ if (path != nullptr) {
+ // Verify whether the path is compatible with the current fragment.
+ assert(this_layer.layer_type == PrintObjectSupportMaterial::sltBottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm);
+ if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) {
+ path = nullptr;
+ }
+ // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer.
+ }
+ if (path == nullptr) {
+ // Allocate a new path.
+ multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height));
+ path = &multipath.paths.back();
+ }
+ // The Clipper library may flip the order of the clipped polylines arbitrarily.
+ // Reverse the source polyline, if connecting to the end.
+ if (! fragment_end_min.is_start)
+ frag_polyline.reverse();
+ // Enforce exact overlap of the end points of successive fragments.
+ assert(frag_polyline.points.front() == pt_current);
+ frag_polyline.points.front() = pt_current;
+ // Don't repeat the first point.
+ if (! path->polyline.points.empty())
+ path->polyline.points.pop_back();
+ // Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time.
+ path->polyline.append(std::move(frag_polyline));
+ frag_polyline.points.clear();
+ pt_current = path->polyline.points.back();
+ if (pt_current == pt_end) {
+ // End of the path.
+ break;
+ }
+ }
+ if (!multipath.paths.empty()) {
+ if (multipath.paths.size() == 1) {
+ // This path was not fragmented.
+ extrusions_in_out.push_back(new ExtrusionPath(std::move(multipath.paths.front())));
+ } else {
+ // This path was fragmented. Copy the collection as a whole object, so the order inside the collection will not be changed
+ // during the chaining of extrusions_in_out.
+ extrusions_in_out.push_back(new ExtrusionMultiPath(std::move(multipath)));
+ }
+ }
+ }
+ // If there are any non-consumed fragments, add them separately.
+ //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected.
+ for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment)
+ extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height);
+}
+
+void PrintObjectSupportMaterial::generate_toolpaths(
+ const PrintObject &object,
+ const MyLayersPtr &raft_layers,
+ const MyLayersPtr &bottom_contacts,
+ const MyLayersPtr &top_contacts,
+ const MyLayersPtr &intermediate_layers,
+ const MyLayersPtr &interface_layers) const
+{
+// Slic3r::debugf "Generating patterns\n";
+ // loop_interface_processor with a given circle radius.
+ LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width());
+ loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0;
+
+ float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value));
+ float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.));
+ coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_material_interface_flow.spacing();
+ coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing);
+ coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing();
+ coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing);
+ if (m_object_config->support_material_interface_layers.value == 0) {
+ // No interface layers allowed, print everything with the base support pattern.
+ interface_spacing = support_spacing;
+ interface_density = support_density;
+ }
+
+ // Prepare fillers.
+ SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
+ bool with_sheath = m_object_config->support_material_with_sheath;
+ InfillPattern infill_pattern;
+ std::vector<float> angles;
+ angles.push_back(base_angle);
+ switch (support_pattern) {
+ case smpRectilinearGrid:
+ angles.push_back(interface_angle);
+ // fall through
+ case smpRectilinear:
+ infill_pattern = ipRectilinear;
+ break;
+ case smpHoneycomb:
+ infill_pattern = ipHoneycomb;
+ break;
+ }
+ BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
+
+// const coordf_t link_max_length_factor = 3.;
+ const coordf_t link_max_length_factor = 0.;
+
+ float raft_angle_1st_layer = 0.f;
+ float raft_angle_base = 0.f;
+ float raft_angle_interface = 0.f;
+ if (m_slicing_params.base_raft_layers > 1) {
+ // There are all raft layer types (1st layer, base, interface & contact layers) available.
+ raft_angle_1st_layer = interface_angle;
+ raft_angle_base = base_angle;
+ raft_angle_interface = interface_angle;
+ } else if (m_slicing_params.base_raft_layers == 1 || m_slicing_params.interface_raft_layers > 1) {
+ // 1st layer, interface & contact layers available.
+ raft_angle_1st_layer = base_angle;
+ if (this->has_support())
+ // Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer.
+ raft_angle_1st_layer += 0.7854f;
+ raft_angle_interface = interface_angle;
+ } else if (m_slicing_params.interface_raft_layers == 1) {
+ // Only the contact raft layer is non-empty, which will be printed as the 1st layer.
+ assert(m_slicing_params.base_raft_layers == 0);
+ assert(m_slicing_params.interface_raft_layers == 1);
+ assert(m_slicing_params.raft_layers() == 1 && raft_layers.size() == 0);
+ } else {
+ // No raft.
+ assert(m_slicing_params.base_raft_layers == 0);
+ assert(m_slicing_params.interface_raft_layers == 0);
+ assert(m_slicing_params.raft_layers() == 0 && raft_layers.size() == 0);
+ }
+
+ // Insert the raft base layers.
+ size_t n_raft_layers = size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1));
+ tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers),
+ [this, &object, &raft_layers,
+ infill_pattern, &bbox_object, support_density, interface_density, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor, with_sheath]
+ (const tbb::blocked_range<size_t>& range) {
+ for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
+ {
+ assert(support_layer_id < raft_layers.size());
+ SupportLayer &support_layer = *object.support_layers()[support_layer_id];
+ assert(support_layer.support_fills.entities.empty());
+ MyLayer &raft_layer = *raft_layers[support_layer_id];
+
+ std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(ipRectilinear));
+ std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern));
+ filler_interface->set_bounding_box(bbox_object);
+ filler_support->set_bounding_box(bbox_object);
+
+ // Print the support base below the support columns, or the support base for the support columns plus the contacts.
+ if (support_layer_id > 0) {
+ Polygons to_infill_polygons = (support_layer_id < m_slicing_params.base_raft_layers) ?
+ raft_layer.polygons :
+ //FIXME misusing contact_polygons for support columns.
+ ((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons);
+ if (! to_infill_polygons.empty()) {
+ Flow flow(float(m_support_material_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging);
+ // find centerline of the external loop/extrusions
+ ExPolygons to_infill = (support_layer_id == 0 || ! with_sheath) ?
+ // union_ex(base_polygons, true) :
+ offset2_ex(to_infill_polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON)) :
+ offset2_ex(to_infill_polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()));
+ if (! to_infill.empty() && with_sheath) {
+ // Draw a perimeter all around the support infill. This makes the support stable, but difficult to remove.
+ // TODO: use brim ordering algorithm
+ to_infill_polygons = to_polygons(to_infill);
+ // TODO: use offset2_ex()
+ to_infill = offset_ex(to_infill, float(- 0.4 * flow.scaled_spacing()));
+ extrusion_entities_append_paths(
+ support_layer.support_fills.entities,
+ to_polylines(STDMOVE(to_infill_polygons)),
+ erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height);
+ }
+ if (! to_infill.empty()) {
+ // We don't use $base_flow->spacing because we need a constant spacing
+ // value that guarantees that all layers are correctly aligned.
+ Fill *filler = filler_support.get();
+ filler->angle = raft_angle_base;
+ filler->spacing = m_support_material_flow.spacing();
+ filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density));
+ fill_expolygons_generate_paths(
+ // Destination
+ support_layer.support_fills.entities,
+ // Regions to fill
+ STDMOVE(to_infill),
+ // Filler and its parameters
+ filler, float(support_density),
+ // Extrusion parameters
+ erSupportMaterial, flow);
+ }
+ }
+ }
+
+ Fill *filler = filler_interface.get();
+ Flow flow = m_first_layer_flow;
+ float density = 0.f;
+ if (support_layer_id == 0) {
+ // Base flange.
+ filler->angle = raft_angle_1st_layer;
+ filler->spacing = m_first_layer_flow.spacing();
+ // 70% of density on the 1st layer.
+ density = 0.7f;
+ } else if (support_layer_id >= m_slicing_params.base_raft_layers) {
+ filler->angle = raft_angle_interface;
+ // We don't use $base_flow->spacing because we need a constant spacing
+ // value that guarantees that all layers are correctly aligned.
+ filler->spacing = m_support_material_flow.spacing();
+ flow = Flow(float(m_support_material_interface_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging);
+ density = float(interface_density);
+ } else
+ continue;
+ filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
+ fill_expolygons_generate_paths(
+ // Destination
+ support_layer.support_fills.entities,
+ // Regions to fill
+ offset2_ex(raft_layer.polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON)),
+ // Filler and its parameters
+ filler, density,
+ // Extrusion parameters
+ (support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow);
+ }
+ });
+
+ struct LayerCacheItem {
+ LayerCacheItem(MyLayerExtruded *layer_extruded = nullptr) : layer_extruded(layer_extruded) {}
+ MyLayerExtruded *layer_extruded;
+ std::vector<MyLayer*> overlapping;
+ };
+ struct LayerCache {
+ MyLayerExtruded bottom_contact_layer;
+ MyLayerExtruded top_contact_layer;
+ MyLayerExtruded base_layer;
+ MyLayerExtruded interface_layer;
+ std::vector<LayerCacheItem> overlaps;
+ };
+ std::vector<LayerCache> layer_caches(object.support_layers().size(), LayerCache());
+
+ tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, object.support_layers().size()),
+ [this, &object, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &layer_caches, &loop_interface_processor,
+ infill_pattern, &bbox_object, support_density, interface_density, interface_angle, &angles, link_max_length_factor, with_sheath]
+ (const tbb::blocked_range<size_t>& range) {
+ // Indices of the 1st layer in their respective container at the support layer height.
+ size_t idx_layer_bottom_contact = size_t(-1);
+ size_t idx_layer_top_contact = size_t(-1);
+ size_t idx_layer_intermediate = size_t(-1);
+ size_t idx_layer_inteface = size_t(-1);
+ std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_slicing_params.soluble_interface ? ipConcentric : ipRectilinear));
+ std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(infill_pattern));
+ filler_interface->set_bounding_box(bbox_object);
+ filler_support->set_bounding_box(bbox_object);
+ for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
+ {
+ SupportLayer &support_layer = *object.support_layers()[support_layer_id];
+ LayerCache &layer_cache = layer_caches[support_layer_id];
+
+ // Find polygons with the same print_z.
+ MyLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
+ MyLayerExtruded &top_contact_layer = layer_cache.top_contact_layer;
+ MyLayerExtruded &base_layer = layer_cache.base_layer;
+ MyLayerExtruded &interface_layer = layer_cache.interface_layer;
+ // Increment the layer indices to find a layer at support_layer.print_z.
+ {
+ auto fun = [&support_layer](const MyLayer *l){ return l->print_z >= support_layer.print_z - EPSILON; };
+ idx_layer_bottom_contact = idx_higher_or_equal(bottom_contacts, idx_layer_bottom_contact, fun);
+ idx_layer_top_contact = idx_higher_or_equal(top_contacts, idx_layer_top_contact, fun);
+ idx_layer_intermediate = idx_higher_or_equal(intermediate_layers, idx_layer_intermediate, fun);
+ idx_layer_inteface = idx_higher_or_equal(interface_layers, idx_layer_inteface, fun);
+ }
+ // Copy polygons from the layers.
+ if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON)
+ bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact];
+ if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON)
+ top_contact_layer.layer = top_contacts[idx_layer_top_contact];
+ if (idx_layer_inteface < interface_layers.size() && interface_layers[idx_layer_inteface]->print_z < support_layer.print_z + EPSILON)
+ interface_layer.layer = interface_layers[idx_layer_inteface];
+ if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON)
+ base_layer.layer = intermediate_layers[idx_layer_intermediate];
+
+ if (m_object_config->support_material_interface_layers == 0) {
+ // If no interface layers were requested, we treat the contact layer exactly as a generic base layer.
+ if (m_can_merge_support_regions) {
+ if (base_layer.could_merge(top_contact_layer))
+ base_layer.merge(std::move(top_contact_layer));
+ else if (base_layer.empty() && !top_contact_layer.empty() && !top_contact_layer.layer->bridging)
+ std::swap(base_layer, top_contact_layer);
+ if (base_layer.could_merge(bottom_contact_layer))
+ base_layer.merge(std::move(bottom_contact_layer));
+ else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging)
+ std::swap(base_layer, bottom_contact_layer);
+ }
+ } else {
+ loop_interface_processor.generate(top_contact_layer, m_support_material_interface_flow);
+ // If no loops are allowed, we treat the contact layer exactly as a generic interface layer.
+ // Merge interface_layer into top_contact_layer, as the top_contact_layer is not synchronized and therefore it will be used
+ // to trim other layers.
+ if (top_contact_layer.could_merge(interface_layer))
+ top_contact_layer.merge(std::move(interface_layer));
+ }
+
+ if (! interface_layer.empty() && ! base_layer.empty()) {
+ // turn base support into interface when it's contained in our holes
+ // (this way we get wider interface anchoring)
+ //FIXME one wants to fill in the inner most holes of the interfaces, not all the holes.
+ Polygons islands = top_level_islands(interface_layer.layer->polygons);
+ polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands));
+ base_layer.layer->polygons = diff(base_layer.layer->polygons, islands);
+ }
+
+ // Top and bottom contacts, interface layers.
+ for (size_t i = 0; i < 3; ++ i) {
+ MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer);
+ if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty())
+ continue;
+ //FIXME When paralellizing, each thread shall have its own copy of the fillers.
+ bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0;
+ //FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
+ // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
+ Flow interface_flow(
+ float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)),
+ float(layer_ex.layer->height),
+ m_support_material_interface_flow.nozzle_diameter,
+ layer_ex.layer->bridging);
+ filler_interface->angle = interface_as_base ?
+ // If zero interface layers are configured, use the same angle as for the base layers.
+ angles[support_layer_id % angles.size()] :
+ // Use interface angle for the interface layers.
+ interface_angle;
+ filler_interface->spacing = m_support_material_interface_flow.spacing();
+ filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / interface_density));
+ fill_expolygons_generate_paths(
+ // Destination
+ layer_ex.extrusions,
+ // Regions to fill
+ union_ex(layer_ex.polygons_to_extrude(), true),
+ // Filler and its parameters
+ filler_interface.get(), float(interface_density),
+ // Extrusion parameters
+ erSupportMaterialInterface, interface_flow);
+ }
+
+ // Base support or flange.
+ if (! base_layer.empty() && ! base_layer.polygons_to_extrude().empty()) {
+ //FIXME When paralellizing, each thread shall have its own copy of the fillers.
+ Fill *filler = filler_support.get();
+ filler->angle = angles[support_layer_id % angles.size()];
+ // We don't use $base_flow->spacing because we need a constant spacing
+ // value that guarantees that all layers are correctly aligned.
+ Flow flow(
+ float(base_layer.layer->bridging ? base_layer.layer->height : m_support_material_flow.width),
+ float(base_layer.layer->height),
+ m_support_material_flow.nozzle_diameter,
+ base_layer.layer->bridging);
+ filler->spacing = m_support_material_flow.spacing();
+ filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density));
+ float density = float(support_density);
+ // find centerline of the external loop/extrusions
+ ExPolygons to_infill = (support_layer_id == 0 || ! with_sheath) ?
+ // union_ex(base_polygons, true) :
+ offset2_ex(base_layer.polygons_to_extrude(), float(SCALED_EPSILON), float(- SCALED_EPSILON)) :
+ offset2_ex(base_layer.polygons_to_extrude(), float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()));
+ if (base_layer.layer->bottom_z < EPSILON) {
+ // Base flange (the 1st layer).
+ filler = filler_interface.get();
+ filler->angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.));
+ density = 0.5f;
+ flow = m_first_layer_flow;
+ // use the proper spacing for first layer as we don't need to align
+ // its pattern to the other layers
+ //FIXME When paralellizing, each thread shall have its own copy of the fillers.
+ filler->spacing = flow.spacing();
+ filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
+ } else if (with_sheath) {
+ // Draw a perimeter all around the support infill. This makes the support stable, but difficult to remove.
+ // TODO: use brim ordering algorithm
+ Polygons to_infill_polygons = to_polygons(to_infill);
+ // TODO: use offset2_ex()
+ to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing()));
+ extrusion_entities_append_paths(
+ base_layer.extrusions,
+ to_polylines(STDMOVE(to_infill_polygons)),
+ erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height);
+ }
+ fill_expolygons_generate_paths(
+ // Destination
+ base_layer.extrusions,
+ // Regions to fill
+ STDMOVE(to_infill),
+ // Filler and its parameters
+ filler, density,
+ // Extrusion parameters
+ erSupportMaterial, flow);
+ }
+
+ layer_cache.overlaps.reserve(4);
+ if (! bottom_contact_layer.empty())
+ layer_cache.overlaps.push_back(&bottom_contact_layer);
+ if (! top_contact_layer.empty())
+ layer_cache.overlaps.push_back(&top_contact_layer);
+ if (! interface_layer.empty())
+ layer_cache.overlaps.push_back(&interface_layer);
+ if (! base_layer.empty())
+ layer_cache.overlaps.push_back(&base_layer);
+ // Sort the layers with the same print_z coordinate by their heights, thickest first.
+ std::sort(layer_cache.overlaps.begin(), layer_cache.overlaps.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; });
+ // Collect the support areas with this print_z into islands, as there is no need
+ // for retraction over these islands.
+ Polygons polys;
+ // Collect the extrusions, sorted by the bottom extrusion height.
+ for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) {
+ // Collect islands to polys.
+ layer_cache_item.layer_extruded->polygons_append(polys);
+ // The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free"
+ // in a sense that they are not synchronized with other support layers. As the top and bottom contact surfaces
+ // are inflated to achieve a better anchoring, it may happen, that these surfaces will at least partially
+ // overlap in Z with another support layers, leading to over-extrusion.
+ // Mitigate the over-extrusion by modulating the extrusion rate over these regions.
+ // The print head will follow the same print_z, but the layer thickness will be reduced
+ // where it overlaps with another support layer.
+ //FIXME When printing a briging path, what is an equivalent height of the squished extrudate of the same width?
+ // Collect overlapping top/bottom surfaces.
+ layer_cache_item.overlapping.reserve(16);
+ coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON;
+ for (int i = int(idx_layer_bottom_contact) - 1; i >= 0 && bottom_contacts[i]->print_z > bottom_z; -- i)
+ layer_cache_item.overlapping.push_back(bottom_contacts[i]);
+ for (int i = int(idx_layer_top_contact) - 1; i >= 0 && top_contacts[i]->print_z > bottom_z; -- i)
+ layer_cache_item.overlapping.push_back(top_contacts[i]);
+ if (layer_cache_item.layer_extruded->layer->layer_type == sltBottomContact) {
+ // Bottom contact layer may overlap with a base layer, which may be changed to interface layer.
+ for (int i = int(idx_layer_intermediate) - 1; i >= 0 && intermediate_layers[i]->print_z > bottom_z; -- i)
+ layer_cache_item.overlapping.push_back(intermediate_layers[i]);
+ for (int i = int(idx_layer_inteface) - 1; i >= 0 && interface_layers[i]->print_z > bottom_z; -- i)
+ layer_cache_item.overlapping.push_back(interface_layers[i]);
+ }
+ std::sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), MyLayersPtrCompare());
+ }
+ if (! polys.empty())
+ expolygons_append(support_layer.support_islands.expolygons, union_ex(polys));
+ /* {
+ require "Slic3r/SVG.pm";
+ Slic3r::SVG::output("islands_" . $z . ".svg",
+ red_expolygons => union_ex($contact),
+ green_expolygons => union_ex($interface),
+ green_polylines => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ],
+ polylines => [ map $_->unpack->polyline, @{$layer->support_fills} ],
+ );
+ } */
+ } // for each support_layer_id
+ });
+
+ // Now modulate the support layer height in parallel.
+ tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, object.support_layers().size()),
+ [this, &object, &layer_caches]
+ (const tbb::blocked_range<size_t>& range) {
+ for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) {
+ SupportLayer &support_layer = *object.support_layers()[support_layer_id];
+ LayerCache &layer_cache = layer_caches[support_layer_id];
+ for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) {
+ modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping);
+ support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions));
+ }
+ }
+ });
+}
+
+/*
+void PrintObjectSupportMaterial::clip_by_pillars(
+ const PrintObject &object,
+ LayersPtr &bottom_contacts,
+ LayersPtr &top_contacts,
+ LayersPtr &intermediate_contacts);
+
+{
+ // this prevents supplying an empty point set to BoundingBox constructor
+ if (top_contacts.empty())
+ return;
+
+ coord_t pillar_size = scale_(PILLAR_SIZE);
+ coord_t pillar_spacing = scale_(PILLAR_SPACING);
+
+ // A regular grid of pillars, filling the 2D bounding box.
+ Polygons grid;
+ {
+ // Rectangle with a side of 2.5x2.5mm.
+ Polygon pillar;
+ pillar.points.push_back(Point(0, 0));
+ pillar.points.push_back(Point(pillar_size, 0));
+ pillar.points.push_back(Point(pillar_size, pillar_size));
+ pillar.points.push_back(Point(0, pillar_size));
+
+ // 2D bounding box of the projection of all contact polygons.
+ BoundingBox bbox;
+ for (LayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it)
+ bbox.merge(get_extents((*it)->polygons));
+ grid.reserve(size_t(ceil(bb.size()(0) / pillar_spacing)) * size_t(ceil(bb.size()(1) / pillar_spacing)));
+ for (coord_t x = bb.min(0); x <= bb.max(0) - pillar_size; x += pillar_spacing) {
+ for (coord_t y = bb.min(1); y <= bb.max(1) - pillar_size; y += pillar_spacing) {
+ grid.push_back(pillar);
+ for (size_t i = 0; i < pillar.points.size(); ++ i)
+ grid.back().points[i].translate(Point(x, y));
+ }
+ }
+ }
+
+ // add pillars to every layer
+ for my $i (0..n_support_z) {
+ $shape->[$i] = [ @$grid ];
+ }
+
+ // build capitals
+ for my $i (0..n_support_z) {
+ my $z = $support_z->[$i];
+
+ my $capitals = intersection(
+ $grid,
+ $contact->{$z} // [],
+ );
+
+ // work on one pillar at time (if any) to prevent the capitals from being merged
+ // but store the contact area supported by the capital because we need to make
+ // sure nothing is left
+ my $contact_supported_by_capitals = [];
+ foreach my $capital (@$capitals) {
+ // enlarge capital tops
+ $capital = offset([$capital], +($pillar_spacing - $pillar_size)/2);
+ push @$contact_supported_by_capitals, @$capital;
+
+ for (my $j = $i-1; $j >= 0; $j--) {
+ my $jz = $support_z->[$j];
+ $capital = offset($capital, -$self->interface_flow->scaled_width/2);
+ last if !@$capitals;
+ push @{ $shape->[$j] }, @$capital;
+ }
+ }
+
+ // Capitals will not generally cover the whole contact area because there will be
+ // remainders. For now we handle this situation by projecting such unsupported
+ // areas to the ground, just like we would do with a normal support.
+ my $contact_not_supported_by_capitals = diff(
+ $contact->{$z} // [],
+ $contact_supported_by_capitals,
+ );
+ if (@$contact_not_supported_by_capitals) {
+ for (my $j = $i-1; $j >= 0; $j--) {
+ push @{ $shape->[$j] }, @$contact_not_supported_by_capitals;
+ }
+ }
+ }
+}
+
+sub clip_with_shape {
+ my ($self, $support, $shape) = @_;
+
+ foreach my $i (keys %$support) {
+ // don't clip bottom layer with shape so that we
+ // can generate a continuous base flange
+ // also don't clip raft layers
+ next if $i == 0;
+ next if $i < $self->object_config->raft_layers;
+ $support->{$i} = intersection(
+ $support->{$i},
+ $shape->[$i],
+ );
+ }
+}
+*/
+
+} // namespace Slic3r
diff --git a/src/libslic3r/SupportMaterial.hpp b/src/libslic3r/SupportMaterial.hpp
new file mode 100644
index 000000000..2e1a05946
--- /dev/null
+++ b/src/libslic3r/SupportMaterial.hpp
@@ -0,0 +1,256 @@
+#ifndef slic3r_SupportMaterial_hpp_
+#define slic3r_SupportMaterial_hpp_
+
+#include "Flow.hpp"
+#include "PrintConfig.hpp"
+#include "Slicing.hpp"
+
+namespace Slic3r {
+
+class PrintObject;
+class PrintConfig;
+class PrintObjectConfig;
+
+// how much we extend support around the actual contact area
+//FIXME this should be dependent on the nozzle diameter!
+#define SUPPORT_MATERIAL_MARGIN 1.5
+
+// This class manages raft and supports for a single PrintObject.
+// Instantiated by Slic3r::Print::Object->_support_material()
+// This class is instantiated before the slicing starts as Object.pm will query
+// the parameters of the raft to determine the 1st layer height and thickness.
+class PrintObjectSupportMaterial
+{
+public:
+ // Support layer type to be used by MyLayer. This type carries a much more detailed information
+ // about the support layer type than the final support layers stored in a PrintObject.
+ enum SupporLayerType {
+ sltUnknown = 0,
+ // Ratft base layer, to be printed with the support material.
+ sltRaftBase,
+ // Raft interface layer, to be printed with the support interface material.
+ sltRaftInterface,
+ // Bottom contact layer placed over a top surface of an object. To be printed with a support interface material.
+ sltBottomContact,
+ // Dense interface layer, to be printed with the support interface material.
+ // This layer is separated from an object by an sltBottomContact layer.
+ sltBottomInterface,
+ // Sparse base support layer, to be printed with a support material.
+ sltBase,
+ // Dense interface layer, to be printed with the support interface material.
+ // This layer is separated from an object with sltTopContact layer.
+ sltTopInterface,
+ // Top contact layer directly supporting an overhang. To be printed with a support interface material.
+ sltTopContact,
+ // Some undecided type yet. It will turn into sltBase first, then it may turn into sltBottomInterface or sltTopInterface.
+ sltIntermediate,
+ };
+
+ // A support layer type used internally by the SupportMaterial class. This class carries a much more detailed
+ // information about the support layer than the layers stored in the PrintObject, mainly
+ // the MyLayer is aware of the bridging flow and the interface gaps between the object and the support.
+ class MyLayer
+ {
+ public:
+ MyLayer() :
+ layer_type(sltUnknown),
+ print_z(0.),
+ bottom_z(0.),
+ height(0.),
+ idx_object_layer_above(size_t(-1)),
+ idx_object_layer_below(size_t(-1)),
+ bridging(false),
+ contact_polygons(nullptr),
+ overhang_polygons(nullptr)
+ {}
+
+ ~MyLayer()
+ {
+ delete contact_polygons;
+ contact_polygons = nullptr;
+ delete overhang_polygons;
+ overhang_polygons = nullptr;
+ }
+
+ void reset() {
+ layer_type = sltUnknown;
+ print_z = 0.;
+ bottom_z = 0.;
+ height = 0.;
+ idx_object_layer_above = size_t(-1);
+ idx_object_layer_below = size_t(-1);
+ bridging = false;
+ polygons.clear();
+ delete contact_polygons;
+ contact_polygons = nullptr;
+ delete overhang_polygons;
+ overhang_polygons = nullptr;
+ }
+
+ bool operator==(const MyLayer &layer2) const {
+ return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging;
+ }
+
+ // Order the layers by lexicographically by an increasing print_z and a decreasing layer height.
+ bool operator<(const MyLayer &layer2) const {
+ if (print_z < layer2.print_z) {
+ return true;
+ } else if (print_z == layer2.print_z) {
+ if (height > layer2.height)
+ return true;
+ else if (height == layer2.height) {
+ // Bridging layers first.
+ return bridging && ! layer2.bridging;
+ } else
+ return false;
+ } else
+ return false;
+ }
+
+ // For the bridging flow, bottom_print_z will be above bottom_z to account for the vertical separation.
+ // For the non-bridging flow, bottom_print_z will be equal to bottom_z.
+ coordf_t bottom_print_z() const { return print_z - height; }
+
+ // To sort the extremes of top / bottom interface layers.
+ coordf_t extreme_z() const { return (this->layer_type == sltTopContact) ? this->bottom_z : this->print_z; }
+
+ SupporLayerType layer_type;
+ // Z used for printing, in unscaled coordinates.
+ coordf_t print_z;
+ // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z,
+ // otherwise bottom_z + gap + height = print_z.
+ coordf_t bottom_z;
+ // Layer height in unscaled coordinates.
+ coordf_t height;
+ // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers.
+ // If this is not a contact layer, it will be set to size_t(-1).
+ size_t idx_object_layer_above;
+ // Index of a PrintObject layer_id, which supports this layer. This will be set for bottom contact layers.
+ // If this is not a contact layer, it will be set to size_t(-1).
+ size_t idx_object_layer_below;
+ // Use a bridging flow when printing this support layer.
+ bool bridging;
+
+ // Polygons to be filled by the support pattern.
+ Polygons polygons;
+ // Currently for the contact layers only.
+ // MyLayer owns the contact_polygons and overhang_polygons, they are freed by the destructor.
+ Polygons *contact_polygons;
+ Polygons *overhang_polygons;
+ };
+
+ // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained
+ // up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future,
+ // which would allocate layers by multiple chunks.
+ typedef std::deque<MyLayer> MyLayerStorage;
+ typedef std::vector<MyLayer*> MyLayersPtr;
+
+public:
+ PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params);
+
+ // Is raft enabled?
+ bool has_raft() const { return m_slicing_params.has_raft(); }
+ // Has any support?
+ bool has_support() const { return m_object_config->support_material.value; }
+ bool build_plate_only() const { return this->has_support() && m_object_config->support_material_buildplate_only.value; }
+
+ bool synchronize_layers() const { return m_slicing_params.soluble_interface && m_object_config->support_material_synchronize_layers.value; }
+ bool has_contact_loops() const { return m_object_config->support_material_interface_contact_loops.value; }
+
+ // Generate support material for the object.
+ // New support layers will be added to the object,
+ // with extrusion paths and islands filled in for each support layer.
+ void generate(PrintObject &object);
+
+private:
+ // Generate top contact layers supporting overhangs.
+ // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined.
+ // If supports over bed surface only are requested, don't generate contact layers over an object.
+ MyLayersPtr top_contact_layers(const PrintObject &object, MyLayerStorage &layer_storage) const;
+
+ // Generate bottom contact layers supporting the top contact layers.
+ // For a soluble interface material synchronize the layer heights with the object,
+ // otherwise set the layer height to a bridging flow of a support interface nozzle.
+ MyLayersPtr bottom_contact_layers_and_layer_support_areas(
+ const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage,
+ std::vector<Polygons> &layer_support_areas) const;
+
+ // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them.
+ void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const;
+
+ // Generate raft layers and the intermediate support layers between the bottom contact and top contact surfaces.
+ MyLayersPtr raft_and_intermediate_support_layers(
+ const PrintObject &object,
+ const MyLayersPtr &bottom_contacts,
+ const MyLayersPtr &top_contacts,
+ MyLayerStorage &layer_storage) const;
+
+ // Fill in the base layers with polygons.
+ void generate_base_layers(
+ const PrintObject &object,
+ const MyLayersPtr &bottom_contacts,
+ const MyLayersPtr &top_contacts,
+ MyLayersPtr &intermediate_layers,
+ const std::vector<Polygons> &layer_support_areas) const;
+
+ // Generate raft layers, also expand the 1st support layer
+ // in case there is no raft layer to improve support adhesion.
+ MyLayersPtr generate_raft_base(
+ const MyLayersPtr &top_contacts,
+ const MyLayersPtr &interface_layers,
+ const MyLayersPtr &base_layers,
+ MyLayerStorage &layer_storage) const;
+
+ // Turn some of the base layers into interface layers.
+ MyLayersPtr generate_interface_layers(
+ const MyLayersPtr &bottom_contacts,
+ const MyLayersPtr &top_contacts,
+ MyLayersPtr &intermediate_layers,
+ MyLayerStorage &layer_storage) const;
+
+ // Trim support layers by an object to leave a defined gap between
+ // the support volume and the object.
+ void trim_support_layers_by_object(
+ const PrintObject &object,
+ MyLayersPtr &support_layers,
+ const coordf_t gap_extra_above,
+ const coordf_t gap_extra_below,
+ const coordf_t gap_xy) const;
+
+/*
+ void generate_pillars_shape();
+ void clip_with_shape();
+*/
+
+ // Produce the actual G-code.
+ void generate_toolpaths(
+ const PrintObject &object,
+ const MyLayersPtr &raft_layers,
+ const MyLayersPtr &bottom_contacts,
+ const MyLayersPtr &top_contacts,
+ const MyLayersPtr &intermediate_layers,
+ const MyLayersPtr &interface_layers) const;
+
+ // Following objects are not owned by SupportMaterial class.
+ const PrintObject *m_object;
+ const PrintConfig *m_print_config;
+ const PrintObjectConfig *m_object_config;
+ // Pre-calculated parameters shared between the object slicer and the support generator,
+ // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc.
+ SlicingParameters m_slicing_params;
+
+ Flow m_first_layer_flow;
+ Flow m_support_material_flow;
+ Flow m_support_material_interface_flow;
+ // Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
+ bool m_can_merge_support_regions;
+
+ coordf_t m_support_layer_height_min;
+ coordf_t m_support_layer_height_max;
+
+ coordf_t m_gap_xy;
+};
+
+} // namespace Slic3r
+
+#endif /* slic3r_SupportMaterial_hpp_ */
diff --git a/src/libslic3r/Surface.cpp b/src/libslic3r/Surface.cpp
new file mode 100644
index 000000000..0e9eca7fd
--- /dev/null
+++ b/src/libslic3r/Surface.cpp
@@ -0,0 +1,147 @@
+#include "BoundingBox.hpp"
+#include "Surface.hpp"
+#include "SVG.hpp"
+
+namespace Slic3r {
+
+Surface::operator Polygons() const
+{
+ return this->expolygon;
+}
+
+double
+Surface::area() const
+{
+ return this->expolygon.area();
+}
+
+bool
+Surface::is_solid() const
+{
+ return this->surface_type == stTop
+ || this->surface_type == stBottom
+ || this->surface_type == stBottomBridge
+ || this->surface_type == stInternalSolid
+ || this->surface_type == stInternalBridge;
+}
+
+bool
+Surface::is_external() const
+{
+ return this->surface_type == stTop
+ || this->surface_type == stBottom
+ || this->surface_type == stBottomBridge;
+}
+
+bool
+Surface::is_internal() const
+{
+ return this->surface_type == stInternal
+ || this->surface_type == stInternalBridge
+ || this->surface_type == stInternalSolid
+ || this->surface_type == stInternalVoid;
+}
+
+bool
+Surface::is_bottom() const
+{
+ return this->surface_type == stBottom
+ || this->surface_type == stBottomBridge;
+}
+
+bool
+Surface::is_bridge() const
+{
+ return this->surface_type == stBottomBridge
+ || this->surface_type == stInternalBridge;
+}
+
+BoundingBox get_extents(const Surface &surface)
+{
+ return get_extents(surface.expolygon.contour);
+}
+
+BoundingBox get_extents(const Surfaces &surfaces)
+{
+ BoundingBox bbox;
+ if (! surfaces.empty()) {
+ bbox = get_extents(surfaces.front());
+ for (size_t i = 1; i < surfaces.size(); ++ i)
+ bbox.merge(get_extents(surfaces[i]));
+ }
+ return bbox;
+}
+
+BoundingBox get_extents(const SurfacesPtr &surfaces)
+{
+ BoundingBox bbox;
+ if (! surfaces.empty()) {
+ bbox = get_extents(*surfaces.front());
+ for (size_t i = 1; i < surfaces.size(); ++ i)
+ bbox.merge(get_extents(*surfaces[i]));
+ }
+ return bbox;
+}
+
+const char* surface_type_to_color_name(const SurfaceType surface_type)
+{
+ switch (surface_type) {
+ case stTop: return "rgb(255,0,0)"; // "red";
+ case stBottom: return "rgb(0,255,0)"; // "green";
+ case stBottomBridge: return "rgb(0,0,255)"; // "blue";
+ case stInternal: return "rgb(255,255,128)"; // yellow
+ case stInternalSolid: return "rgb(255,0,255)"; // magenta
+ case stInternalBridge: return "rgb(0,255,255)";
+ case stInternalVoid: return "rgb(128,128,128)";
+ case stPerimeter: return "rgb(128,0,0)"; // maroon
+ default: return "rgb(64,64,64)";
+ };
+}
+
+Point export_surface_type_legend_to_svg_box_size()
+{
+ return Point(scale_(1.+10.*8.), scale_(3.));
+}
+
+void export_surface_type_legend_to_svg(SVG &svg, const Point &pos)
+{
+ // 1st row
+ coord_t pos_x0 = pos(0) + scale_(1.);
+ coord_t pos_x = pos_x0;
+ coord_t pos_y = pos(1) + scale_(1.5);
+ coord_t step_x = scale_(10.);
+ svg.draw_legend(Point(pos_x, pos_y), "perimeter" , surface_type_to_color_name(stPerimeter));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "top" , surface_type_to_color_name(stTop));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "bottom" , surface_type_to_color_name(stBottom));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "bottom bridge" , surface_type_to_color_name(stBottomBridge));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "invalid" , surface_type_to_color_name(SurfaceType(-1)));
+ // 2nd row
+ pos_x = pos_x0;
+ pos_y = pos(1)+scale_(2.8);
+ svg.draw_legend(Point(pos_x, pos_y), "internal" , surface_type_to_color_name(stInternal));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "internal solid" , surface_type_to_color_name(stInternalSolid));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "internal bridge", surface_type_to_color_name(stInternalBridge));
+ pos_x += step_x;
+ svg.draw_legend(Point(pos_x, pos_y), "internal void" , surface_type_to_color_name(stInternalVoid));
+}
+
+bool export_to_svg(const char *path, const Surfaces &surfaces, const float transparency)
+{
+ BoundingBox bbox;
+ for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface)
+ bbox.merge(get_extents(surface->expolygon));
+
+ SVG svg(path, bbox);
+ for (Surfaces::const_iterator surface = surfaces.begin(); surface != surfaces.end(); ++surface)
+ svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency);
+ svg.Close();
+ return true;
+}
+
+}
diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp
new file mode 100644
index 000000000..c2cec3793
--- /dev/null
+++ b/src/libslic3r/Surface.hpp
@@ -0,0 +1,289 @@
+#ifndef slic3r_Surface_hpp_
+#define slic3r_Surface_hpp_
+
+#include "libslic3r.h"
+#include "ExPolygon.hpp"
+
+namespace Slic3r {
+
+enum SurfaceType {
+ // Top horizontal surface, visible from the top.
+ stTop,
+ // Bottom horizontal surface, visible from the bottom, printed with a normal extrusion flow.
+ stBottom,
+ // Bottom horizontal surface, visible from the bottom, unsupported, printed with a bridging extrusion flow.
+ stBottomBridge,
+ // Normal sparse infill.
+ stInternal,
+ // Full infill, supporting the top surfaces and/or defining the verticall wall thickness.
+ stInternalSolid,
+ // 1st layer of dense infill over sparse infill, printed with a bridging extrusion flow.
+ stInternalBridge,
+ // stInternal turns into void surfaces if the sparse infill is used for supports only,
+ // or if sparse infill layers get combined into a single layer.
+ stInternalVoid,
+ // Inner/outer perimeters.
+ stPerimeter,
+ // Last surface type, if the SurfaceType is used as an index into a vector.
+ stLast,
+ stCount = stLast + 1
+};
+
+class Surface
+{
+public:
+ SurfaceType surface_type;
+ ExPolygon expolygon;
+ double thickness; // in mm
+ unsigned short thickness_layers; // in layers
+ double bridge_angle; // in radians, ccw, 0 = East, only 0+ (negative means undefined)
+ unsigned short extra_perimeters;
+
+ Surface(const Slic3r::Surface &rhs)
+ : surface_type(rhs.surface_type), expolygon(rhs.expolygon),
+ thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
+ bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
+ {};
+
+ Surface(SurfaceType _surface_type, const ExPolygon &_expolygon)
+ : surface_type(_surface_type), expolygon(_expolygon),
+ thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0)
+ {};
+ Surface(const Surface &other, const ExPolygon &_expolygon)
+ : surface_type(other.surface_type), expolygon(_expolygon),
+ thickness(other.thickness), thickness_layers(other.thickness_layers),
+ bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
+ {};
+ Surface(Surface &&rhs)
+ : surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
+ thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
+ bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
+ {};
+ Surface(SurfaceType _surface_type, const ExPolygon &&_expolygon)
+ : surface_type(_surface_type), expolygon(std::move(_expolygon)),
+ thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0)
+ {};
+ Surface(const Surface &other, const ExPolygon &&_expolygon)
+ : surface_type(other.surface_type), expolygon(std::move(_expolygon)),
+ thickness(other.thickness), thickness_layers(other.thickness_layers),
+ bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
+ {};
+
+ Surface& operator=(const Surface &rhs)
+ {
+ surface_type = rhs.surface_type;
+ expolygon = rhs.expolygon;
+ thickness = rhs.thickness;
+ thickness_layers = rhs.thickness_layers;
+ bridge_angle = rhs.bridge_angle;
+ extra_perimeters = rhs.extra_perimeters;
+ return *this;
+ }
+
+ Surface& operator=(Surface &&rhs)
+ {
+ surface_type = rhs.surface_type;
+ expolygon = std::move(rhs.expolygon);
+ thickness = rhs.thickness;
+ thickness_layers = rhs.thickness_layers;
+ bridge_angle = rhs.bridge_angle;
+ extra_perimeters = rhs.extra_perimeters;
+ return *this;
+ }
+
+ operator Polygons() const;
+ double area() const;
+ bool empty() const { return expolygon.empty(); }
+ void clear() { expolygon.clear(); }
+ bool is_solid() const;
+ bool is_external() const;
+ bool is_internal() const;
+ bool is_bottom() const;
+ bool is_bridge() const;
+};
+
+typedef std::vector<Surface> Surfaces;
+typedef std::vector<Surface*> SurfacesPtr;
+
+inline Polygons to_polygons(const Surfaces &src)
+{
+ size_t num = 0;
+ for (Surfaces::const_iterator it = src.begin(); it != src.end(); ++it)
+ num += it->expolygon.holes.size() + 1;
+ Polygons polygons;
+ polygons.reserve(num);
+ for (Surfaces::const_iterator it = src.begin(); it != src.end(); ++it) {
+ polygons.emplace_back(it->expolygon.contour);
+ for (Polygons::const_iterator ith = it->expolygon.holes.begin(); ith != it->expolygon.holes.end(); ++ith)
+ polygons.emplace_back(*ith);
+ }
+ return polygons;
+}
+
+inline Polygons to_polygons(const SurfacesPtr &src)
+{
+ size_t num = 0;
+ for (SurfacesPtr::const_iterator it = src.begin(); it != src.end(); ++it)
+ num += (*it)->expolygon.holes.size() + 1;
+ Polygons polygons;
+ polygons.reserve(num);
+ for (SurfacesPtr::const_iterator it = src.begin(); it != src.end(); ++it) {
+ polygons.emplace_back((*it)->expolygon.contour);
+ for (Polygons::const_iterator ith = (*it)->expolygon.holes.begin(); ith != (*it)->expolygon.holes.end(); ++ith)
+ polygons.emplace_back(*ith);
+ }
+ return polygons;
+}
+
+inline ExPolygons to_expolygons(const Surfaces &src)
+{
+ ExPolygons expolygons;
+ expolygons.reserve(src.size());
+ for (Surfaces::const_iterator it = src.begin(); it != src.end(); ++it)
+ expolygons.emplace_back(it->expolygon);
+ return expolygons;
+}
+
+inline ExPolygons to_expolygons(Surfaces &&src)
+{
+ ExPolygons expolygons;
+ expolygons.reserve(src.size());
+ for (Surfaces::const_iterator it = src.begin(); it != src.end(); ++it)
+ expolygons.emplace_back(ExPolygon(std::move(it->expolygon)));
+ src.clear();
+ return expolygons;
+}
+
+inline ExPolygons to_expolygons(const SurfacesPtr &src)
+{
+ ExPolygons expolygons;
+ expolygons.reserve(src.size());
+ for (SurfacesPtr::const_iterator it = src.begin(); it != src.end(); ++it)
+ expolygons.emplace_back((*it)->expolygon);
+ return expolygons;
+}
+
+// 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 Surfaces &surfaces)
+{
+ size_t n_polygons = 0;
+ for (Surfaces::const_iterator it = surfaces.begin(); it != surfaces.end(); ++ it)
+ n_polygons += it->expolygon.holes.size() + 1;
+ return n_polygons;
+}
+inline size_t number_polygons(const SurfacesPtr &surfaces)
+{
+ size_t n_polygons = 0;
+ for (SurfacesPtr::const_iterator it = surfaces.begin(); it != surfaces.end(); ++ it)
+ n_polygons += (*it)->expolygon.holes.size() + 1;
+ return n_polygons;
+}
+
+// Append a vector of Surfaces at the end of another vector of polygons.
+inline void polygons_append(Polygons &dst, const Surfaces &src)
+{
+ dst.reserve(dst.size() + number_polygons(src));
+ for (Surfaces::const_iterator it = src.begin(); it != src.end(); ++ it) {
+ dst.emplace_back(it->expolygon.contour);
+ dst.insert(dst.end(), it->expolygon.holes.begin(), it->expolygon.holes.end());
+ }
+}
+
+inline void polygons_append(Polygons &dst, Surfaces &&src)
+{
+ dst.reserve(dst.size() + number_polygons(src));
+ for (Surfaces::iterator it = src.begin(); it != src.end(); ++ it) {
+ dst.emplace_back(std::move(it->expolygon.contour));
+ std::move(std::begin(it->expolygon.holes), std::end(it->expolygon.holes), std::back_inserter(dst));
+ it->expolygon.holes.clear();
+ }
+}
+
+// Append a vector of Surfaces at the end of another vector of polygons.
+inline void polygons_append(Polygons &dst, const SurfacesPtr &src)
+{
+ dst.reserve(dst.size() + number_polygons(src));
+ for (SurfacesPtr::const_iterator it = src.begin(); it != src.end(); ++ it) {
+ dst.emplace_back((*it)->expolygon.contour);
+ dst.insert(dst.end(), (*it)->expolygon.holes.begin(), (*it)->expolygon.holes.end());
+ }
+}
+
+inline void polygons_append(Polygons &dst, SurfacesPtr &&src)
+{
+ dst.reserve(dst.size() + number_polygons(src));
+ for (SurfacesPtr::const_iterator it = src.begin(); it != src.end(); ++ it) {
+ dst.emplace_back(std::move((*it)->expolygon.contour));
+ std::move(std::begin((*it)->expolygon.holes), std::end((*it)->expolygon.holes), std::back_inserter(dst));
+ (*it)->expolygon.holes.clear();
+ }
+}
+
+// Append a vector of Surfaces at the end of another vector of polygons.
+inline void surfaces_append(Surfaces &dst, const ExPolygons &src, SurfaceType surfaceType)
+{
+ dst.reserve(dst.size() + src.size());
+ for (const ExPolygon &expoly : src)
+ dst.emplace_back(Surface(surfaceType, expoly));
+}
+inline void surfaces_append(Surfaces &dst, const ExPolygons &src, const Surface &surfaceTempl)
+{
+ dst.reserve(dst.size() + number_polygons(src));
+ for (const ExPolygon &expoly : src)
+ dst.emplace_back(Surface(surfaceTempl, expoly));
+}
+inline void surfaces_append(Surfaces &dst, const Surfaces &src)
+{
+ dst.insert(dst.end(), src.begin(), src.end());
+}
+
+inline void surfaces_append(Surfaces &dst, ExPolygons &&src, SurfaceType surfaceType)
+{
+ dst.reserve(dst.size() + src.size());
+ for (ExPolygon &expoly : src)
+ dst.emplace_back(Surface(surfaceType, std::move(expoly)));
+ src.clear();
+}
+
+inline void surfaces_append(Surfaces &dst, ExPolygons &&src, const Surface &surfaceTempl)
+{
+ dst.reserve(dst.size() + number_polygons(src));
+ for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it)
+ dst.emplace_back(Surface(surfaceTempl, std::move(*it)));
+ src.clear();
+}
+
+inline void surfaces_append(Surfaces &dst, Surfaces &&src)
+{
+ if (dst.empty()) {
+ dst = std::move(src);
+ } else {
+ std::move(std::begin(src), std::end(src), std::back_inserter(dst));
+ src.clear();
+ }
+}
+
+extern BoundingBox get_extents(const Surface &surface);
+extern BoundingBox get_extents(const Surfaces &surfaces);
+extern BoundingBox get_extents(const SurfacesPtr &surfaces);
+
+inline bool surfaces_could_merge(const Surface &s1, const Surface &s2)
+{
+ return
+ s1.surface_type == s2.surface_type &&
+ s1.thickness == s2.thickness &&
+ s1.thickness_layers == s2.thickness_layers &&
+ s1.bridge_angle == s2.bridge_angle;
+}
+
+class SVG;
+
+extern const char* surface_type_to_color_name(const SurfaceType surface_type);
+extern void export_surface_type_legend_to_svg(SVG &svg, const Point &pos);
+extern Point export_surface_type_legend_to_svg_box_size();
+extern bool export_to_svg(const char *path, const Surfaces &surfaces, const float transparency = 1.f);
+
+}
+
+#endif
diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp
new file mode 100644
index 000000000..6db599306
--- /dev/null
+++ b/src/libslic3r/SurfaceCollection.cpp
@@ -0,0 +1,191 @@
+#include "SurfaceCollection.hpp"
+#include "BoundingBox.hpp"
+#include "SVG.hpp"
+
+#include <map>
+
+namespace Slic3r {
+
+SurfaceCollection::operator Polygons() const
+{
+ return to_polygons(surfaces);
+}
+
+SurfaceCollection::operator ExPolygons() const
+{
+ return to_expolygons(surfaces);
+}
+
+void
+SurfaceCollection::simplify(double tolerance)
+{
+ Surfaces ss;
+ for (Surfaces::const_iterator it_s = this->surfaces.begin(); it_s != this->surfaces.end(); ++it_s) {
+ ExPolygons expp;
+ it_s->expolygon.simplify(tolerance, &expp);
+ for (ExPolygons::const_iterator it_e = expp.begin(); it_e != expp.end(); ++it_e) {
+ Surface s = *it_s;
+ s.expolygon = *it_e;
+ ss.push_back(s);
+ }
+ }
+ this->surfaces = ss;
+}
+
+/* group surfaces by common properties */
+void
+SurfaceCollection::group(std::vector<SurfacesPtr> *retval)
+{
+ for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) {
+ // find a group with the same properties
+ SurfacesPtr* group = NULL;
+ for (std::vector<SurfacesPtr>::iterator git = retval->begin(); git != retval->end(); ++git)
+ if (! git->empty() && surfaces_could_merge(*git->front(), *it)) {
+ group = &*git;
+ break;
+ }
+ // if no group with these properties exists, add one
+ if (group == NULL) {
+ retval->resize(retval->size() + 1);
+ group = &retval->back();
+ }
+ // append surface to group
+ group->push_back(&*it);
+ }
+}
+
+SurfacesPtr
+SurfaceCollection::filter_by_type(const SurfaceType type)
+{
+ SurfacesPtr ss;
+ for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
+ if (surface->surface_type == type) ss.push_back(&*surface);
+ }
+ return ss;
+}
+
+SurfacesPtr
+SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes)
+{
+ SurfacesPtr ss;
+ for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
+ for (int i = 0; i < ntypes; ++ i) {
+ if (surface->surface_type == types[i]) {
+ ss.push_back(&*surface);
+ break;
+ }
+ }
+ }
+ return ss;
+}
+
+void
+SurfaceCollection::filter_by_type(SurfaceType type, Polygons* polygons)
+{
+ for (Surfaces::iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
+ if (surface->surface_type == type) {
+ Polygons pp = surface->expolygon;
+ polygons->insert(polygons->end(), pp.begin(), pp.end());
+ }
+ }
+}
+
+void
+SurfaceCollection::keep_type(const SurfaceType type)
+{
+ size_t j = 0;
+ for (size_t i = 0; i < surfaces.size(); ++ i) {
+ if (surfaces[i].surface_type == type) {
+ if (j < i)
+ std::swap(surfaces[i], surfaces[j]);
+ ++ j;
+ }
+ }
+ if (j < surfaces.size())
+ surfaces.erase(surfaces.begin() + j, surfaces.end());
+}
+
+void
+SurfaceCollection::keep_types(const SurfaceType *types, int ntypes)
+{
+ size_t j = 0;
+ for (size_t i = 0; i < surfaces.size(); ++ i) {
+ bool keep = false;
+ for (int k = 0; k < ntypes; ++ k) {
+ if (surfaces[i].surface_type == types[k]) {
+ keep = true;
+ break;
+ }
+ }
+ if (keep) {
+ if (j < i)
+ std::swap(surfaces[i], surfaces[j]);
+ ++ j;
+ }
+ }
+ if (j < surfaces.size())
+ surfaces.erase(surfaces.begin() + j, surfaces.end());
+}
+
+void
+SurfaceCollection::remove_type(const SurfaceType type)
+{
+ size_t j = 0;
+ for (size_t i = 0; i < surfaces.size(); ++ i) {
+ if (surfaces[i].surface_type != type) {
+ if (j < i)
+ std::swap(surfaces[i], surfaces[j]);
+ ++ j;
+ }
+ }
+ if (j < surfaces.size())
+ surfaces.erase(surfaces.begin() + j, surfaces.end());
+}
+
+void
+SurfaceCollection::remove_types(const SurfaceType *types, int ntypes)
+{
+ size_t j = 0;
+ for (size_t i = 0; i < surfaces.size(); ++ i) {
+ bool remove = false;
+ for (int k = 0; k < ntypes; ++ k) {
+ if (surfaces[i].surface_type == types[k]) {
+ remove = true;
+ break;
+ }
+ }
+ if (! remove) {
+ if (j < i)
+ std::swap(surfaces[i], surfaces[j]);
+ ++ j;
+ }
+ }
+ if (j < surfaces.size())
+ surfaces.erase(surfaces.begin() + j, surfaces.end());
+}
+
+void SurfaceCollection::export_to_svg(const char *path, bool show_labels)
+{
+ BoundingBox bbox;
+ for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->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->surfaces.begin(); surface != this->surfaces.end(); ++surface) {
+ svg.draw(surface->expolygon, surface_type_to_color_name(surface->surface_type), transparency);
+ if (show_labels) {
+ int idx = int(surface - this->surfaces.begin());
+ char label[64];
+ sprintf(label, "%d", idx);
+ svg.draw_text(surface->expolygon.contour.points.front(), label, "black");
+ }
+ }
+ export_surface_type_legend_to_svg(svg, legend_pos);
+ svg.Close();
+}
+
+}
diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp
new file mode 100644
index 000000000..9544748e9
--- /dev/null
+++ b/src/libslic3r/SurfaceCollection.hpp
@@ -0,0 +1,70 @@
+#ifndef slic3r_SurfaceCollection_hpp_
+#define slic3r_SurfaceCollection_hpp_
+
+#include "libslic3r.h"
+#include "Surface.hpp"
+#include <vector>
+
+namespace Slic3r {
+
+class SurfaceCollection
+{
+public:
+ Surfaces surfaces;
+
+ SurfaceCollection() {};
+ SurfaceCollection(const Surfaces &surfaces) : surfaces(surfaces) {};
+ SurfaceCollection(Surfaces &&surfaces) : surfaces(std::move(surfaces)) {};
+ operator Polygons() const;
+ operator ExPolygons() const;
+ void simplify(double tolerance);
+ void group(std::vector<SurfacesPtr> *retval);
+ template <class T> bool any_internal_contains(const T &item) const {
+ for (const Surface &surface : this->surfaces) if (surface.is_internal() && surface.expolygon.contains(item)) return true;
+ return false;
+ }
+ template <class T> bool any_bottom_contains(const T &item) const {
+ for (const Surface &surface : this->surfaces) if (surface.is_bottom() && surface.expolygon.contains(item)) return true;
+ return false;
+ }
+ SurfacesPtr filter_by_type(const SurfaceType type);
+ SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes);
+ void keep_type(const SurfaceType type);
+ void keep_types(const SurfaceType *types, int ntypes);
+ void remove_type(const SurfaceType type);
+ void remove_types(const SurfaceType *types, int ntypes);
+ void filter_by_type(SurfaceType type, Polygons* polygons);
+
+ void clear() { surfaces.clear(); }
+ bool empty() const { return surfaces.empty(); }
+ bool has(SurfaceType type) const {
+ for (const Surface &surface : this->surfaces)
+ if (surface.surface_type == type) return true;
+ return false;
+ }
+
+ void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; }
+ void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); }
+ void set(const ExPolygons &src, SurfaceType surfaceType) { clear(); this->append(src, surfaceType); }
+ void set(const ExPolygons &src, const Surface &surfaceTempl) { clear(); this->append(src, surfaceTempl); }
+ void set(const Surfaces &src) { clear(); this->append(src); }
+ void set(ExPolygons &&src, SurfaceType surfaceType) { clear(); this->append(std::move(src), surfaceType); }
+ void set(ExPolygons &&src, const Surface &surfaceTempl) { clear(); this->append(std::move(src), surfaceTempl); }
+ void set(Surfaces &&src) { clear(); this->append(std::move(src)); }
+
+ void append(const SurfaceCollection &coll) { this->append(coll.surfaces); }
+ void append(SurfaceCollection &&coll) { this->append(std::move(coll.surfaces)); }
+ void append(const ExPolygons &src, SurfaceType surfaceType) { surfaces_append(this->surfaces, src, surfaceType); }
+ void append(const ExPolygons &src, const Surface &surfaceTempl) { surfaces_append(this->surfaces, src, surfaceTempl); }
+ void append(const Surfaces &src) { surfaces_append(this->surfaces, src); }
+ void append(ExPolygons &&src, SurfaceType surfaceType) { surfaces_append(this->surfaces, std::move(src), surfaceType); }
+ void append(ExPolygons &&src, const Surface &surfaceTempl) { surfaces_append(this->surfaces, std::move(src), surfaceTempl); }
+ void append(Surfaces &&src) { surfaces_append(this->surfaces, std::move(src)); }
+
+ // For debugging purposes:
+ void export_to_svg(const char *path, bool show_labels);
+};
+
+}
+
+#endif
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
new file mode 100644
index 000000000..5c4f8617d
--- /dev/null
+++ b/src/libslic3r/Technologies.hpp
@@ -0,0 +1,12 @@
+#ifndef _technologies_h_
+#define _technologies_h_
+
+// 1.42.0 techs
+#define ENABLE_1_42_0 1
+
+// Add z coordinate to model instances' offset
+#define ENABLE_MODELINSTANCE_3D_OFFSET (1 && ENABLE_1_42_0)
+
+#endif // _technologies_h_
+
+
diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp
new file mode 100644
index 000000000..4bf13330f
--- /dev/null
+++ b/src/libslic3r/TriangleMesh.cpp
@@ -0,0 +1,1888 @@
+#include "TriangleMesh.hpp"
+#include "ClipperUtils.hpp"
+#include "Geometry.hpp"
+#include "qhull/src/libqhullcpp/Qhull.h"
+#include "qhull/src/libqhullcpp/QhullFacetList.h"
+#include "qhull/src/libqhullcpp/QhullVertexSet.h"
+#include <cmath>
+#include <deque>
+#include <queue>
+#include <set>
+#include <vector>
+#include <map>
+#include <utility>
+#include <algorithm>
+#include <math.h>
+#include <type_traits>
+
+#include <boost/log/trivial.hpp>
+
+#include <tbb/parallel_for.h>
+
+#include <Eigen/Dense>
+
+// for SLIC3R_DEBUG_SLICE_PROCESSING
+#include "libslic3r.h"
+
+#if 0
+ #define DEBUG
+ #define _DEBUG
+ #undef NDEBUG
+ #define SLIC3R_DEBUG
+// #define SLIC3R_TRIANGLEMESH_DEBUG
+#endif
+
+#include <assert.h>
+
+#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)
+#include "SVG.hpp"
+#endif
+
+namespace Slic3r {
+
+TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& facets )
+ : repaired(false)
+{
+ stl_initialize(&this->stl);
+ stl_file &stl = this->stl;
+ stl.error = 0;
+ stl.stats.type = inmemory;
+
+ // count facets and allocate memory
+ stl.stats.number_of_facets = facets.size();
+ stl.stats.original_num_facets = stl.stats.number_of_facets;
+ stl_allocate(&stl);
+
+ for (int i = 0; i < stl.stats.number_of_facets; i++) {
+ stl_facet facet;
+ facet.vertex[0] = points[facets[i](0)].cast<float>();
+ facet.vertex[1] = points[facets[i](1)].cast<float>();
+ facet.vertex[2] = points[facets[i](2)].cast<float>();
+ facet.extra[0] = 0;
+ facet.extra[1] = 0;
+
+ stl_normal normal;
+ stl_calculate_normal(normal, &facet);
+ stl_normalize_vector(normal);
+ facet.normal = normal;
+
+ stl.facet_start[i] = facet;
+ }
+ stl_get_size(&stl);
+}
+
+TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other)
+{
+ stl_close(&this->stl);
+ this->stl = other.stl;
+ this->repaired = other.repaired;
+ this->stl.heads = nullptr;
+ this->stl.tail = nullptr;
+ this->stl.error = other.stl.error;
+ if (other.stl.facet_start != nullptr) {
+ this->stl.facet_start = (stl_facet*)calloc(other.stl.stats.number_of_facets, sizeof(stl_facet));
+ std::copy(other.stl.facet_start, other.stl.facet_start + other.stl.stats.number_of_facets, this->stl.facet_start);
+ }
+ if (other.stl.neighbors_start != nullptr) {
+ this->stl.neighbors_start = (stl_neighbors*)calloc(other.stl.stats.number_of_facets, sizeof(stl_neighbors));
+ std::copy(other.stl.neighbors_start, other.stl.neighbors_start + other.stl.stats.number_of_facets, this->stl.neighbors_start);
+ }
+ if (other.stl.v_indices != nullptr) {
+ this->stl.v_indices = (v_indices_struct*)calloc(other.stl.stats.number_of_facets, sizeof(v_indices_struct));
+ std::copy(other.stl.v_indices, other.stl.v_indices + other.stl.stats.number_of_facets, this->stl.v_indices);
+ }
+ if (other.stl.v_shared != nullptr) {
+ this->stl.v_shared = (stl_vertex*)calloc(other.stl.stats.shared_vertices, sizeof(stl_vertex));
+ std::copy(other.stl.v_shared, other.stl.v_shared + other.stl.stats.shared_vertices, this->stl.v_shared);
+ }
+ return *this;
+}
+
+void TriangleMesh::repair()
+{
+ if (this->repaired) return;
+
+ // admesh fails when repairing empty meshes
+ if (this->stl.stats.number_of_facets == 0) return;
+
+ BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started";
+
+ // checking exact
+ stl_check_facets_exact(&stl);
+ stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge);
+ stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge);
+ stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge);
+
+ // checking nearby
+ //int last_edges_fixed = 0;
+ float tolerance = stl.stats.shortest_edge;
+ float increment = stl.stats.bounding_diameter / 10000.0;
+ int iterations = 2;
+ if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
+ for (int i = 0; i < iterations; i++) {
+ if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
+ //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations);
+ stl_check_facets_nearby(&stl, tolerance);
+ //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed);
+ //last_edges_fixed = stl.stats.edges_fixed;
+ tolerance += increment;
+ } else {
+ break;
+ }
+ }
+ }
+
+ // remove_unconnected
+ if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
+ stl_remove_unconnected_facets(&stl);
+ }
+
+ // fill_holes
+ if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
+ stl_fill_holes(&stl);
+ stl_clear_error(&stl);
+ }
+
+ // normal_directions
+ stl_fix_normal_directions(&stl);
+
+ // normal_values
+ stl_fix_normal_values(&stl);
+
+ // always calculate the volume and reverse all normals if volume is negative
+ stl_calculate_volume(&stl);
+
+ // neighbors
+ stl_verify_neighbors(&stl);
+
+ this->repaired = true;
+
+ BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished";
+}
+
+float TriangleMesh::volume()
+{
+ if (this->stl.stats.volume == -1)
+ stl_calculate_volume(&this->stl);
+ return this->stl.stats.volume;
+}
+
+void TriangleMesh::check_topology()
+{
+ // checking exact
+ stl_check_facets_exact(&stl);
+ stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge);
+ stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge);
+ stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge);
+
+ // checking nearby
+ //int last_edges_fixed = 0;
+ float tolerance = stl.stats.shortest_edge;
+ float increment = stl.stats.bounding_diameter / 10000.0;
+ int iterations = 2;
+ if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
+ for (int i = 0; i < iterations; i++) {
+ if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) {
+ //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations);
+ stl_check_facets_nearby(&stl, tolerance);
+ //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed);
+ //last_edges_fixed = stl.stats.edges_fixed;
+ tolerance += increment;
+ } else {
+ break;
+ }
+ }
+ }
+}
+
+void TriangleMesh::reset_repair_stats() {
+ this->stl.stats.degenerate_facets = 0;
+ this->stl.stats.edges_fixed = 0;
+ this->stl.stats.facets_removed = 0;
+ this->stl.stats.facets_added = 0;
+ this->stl.stats.facets_reversed = 0;
+ this->stl.stats.backwards_edges = 0;
+ this->stl.stats.normals_fixed = 0;
+}
+
+bool TriangleMesh::needed_repair() const
+{
+ return this->stl.stats.degenerate_facets > 0
+ || this->stl.stats.edges_fixed > 0
+ || this->stl.stats.facets_removed > 0
+ || this->stl.stats.facets_added > 0
+ || this->stl.stats.facets_reversed > 0
+ || this->stl.stats.backwards_edges > 0;
+}
+
+void TriangleMesh::WriteOBJFile(char* output_file)
+{
+ stl_generate_shared_vertices(&stl);
+ stl_write_obj(&stl, output_file);
+}
+
+void TriangleMesh::scale(float factor)
+{
+ stl_scale(&(this->stl), factor);
+ stl_invalidate_shared_vertices(&this->stl);
+}
+
+void TriangleMesh::scale(const Vec3d &versor)
+{
+ stl_scale_versor(&this->stl, versor.cast<float>());
+ stl_invalidate_shared_vertices(&this->stl);
+}
+
+void TriangleMesh::translate(float x, float y, float z)
+{
+ if (x == 0.f && y == 0.f && z == 0.f)
+ return;
+ stl_translate_relative(&(this->stl), x, y, z);
+ stl_invalidate_shared_vertices(&this->stl);
+}
+
+void TriangleMesh::rotate(float angle, const Axis &axis)
+{
+ if (angle == 0.f)
+ return;
+
+ // admesh uses degrees
+ angle = Slic3r::Geometry::rad2deg(angle);
+
+ if (axis == X) {
+ stl_rotate_x(&(this->stl), angle);
+ } else if (axis == Y) {
+ stl_rotate_y(&(this->stl), angle);
+ } else if (axis == Z) {
+ stl_rotate_z(&(this->stl), angle);
+ }
+ stl_invalidate_shared_vertices(&this->stl);
+}
+
+void TriangleMesh::rotate(float angle, const Vec3d& axis)
+{
+ if (angle == 0.f)
+ return;
+
+ Vec3f axis_norm = axis.cast<float>().normalized();
+ Transform3f m = Transform3f::Identity();
+ m.rotate(Eigen::AngleAxisf(angle, axis_norm));
+ stl_transform(&stl, m);
+}
+
+void TriangleMesh::mirror(const Axis &axis)
+{
+ if (axis == X) {
+ stl_mirror_yz(&this->stl);
+ } else if (axis == Y) {
+ stl_mirror_xz(&this->stl);
+ } else if (axis == Z) {
+ stl_mirror_xy(&this->stl);
+ }
+ stl_invalidate_shared_vertices(&this->stl);
+}
+
+void TriangleMesh::transform(const Transform3f& t)
+{
+ stl_transform(&stl, t);
+}
+
+void TriangleMesh::align_to_origin()
+{
+ this->translate(
+ - this->stl.stats.min(0),
+ - this->stl.stats.min(1),
+ - this->stl.stats.min(2));
+}
+
+void TriangleMesh::rotate(double angle, Point* center)
+{
+ if (angle == 0.)
+ return;
+ Vec2f c = center->cast<float>();
+ this->translate(-c(0), -c(1), 0);
+ stl_rotate_z(&(this->stl), (float)angle);
+ this->translate(c(0), c(1), 0);
+}
+
+bool TriangleMesh::has_multiple_patches() const
+{
+ // we need neighbors
+ if (!this->repaired)
+ throw std::runtime_error("split() requires repair()");
+
+ if (this->stl.stats.number_of_facets == 0)
+ return false;
+
+ std::vector<int> facet_queue(this->stl.stats.number_of_facets, 0);
+ std::vector<char> facet_visited(this->stl.stats.number_of_facets, false);
+ int facet_queue_cnt = 1;
+ facet_queue[0] = 0;
+ facet_visited[0] = true;
+ while (facet_queue_cnt > 0) {
+ int facet_idx = facet_queue[-- facet_queue_cnt];
+ facet_visited[facet_idx] = true;
+ for (int j = 0; j < 3; ++ j) {
+ int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j];
+ if (neighbor_idx != -1 && ! facet_visited[neighbor_idx])
+ facet_queue[facet_queue_cnt ++] = neighbor_idx;
+ }
+ }
+
+ // If any of the face was not visited at the first time, return "multiple bodies".
+ for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; ++ facet_idx)
+ if (! facet_visited[facet_idx])
+ return true;
+ return false;
+}
+
+size_t TriangleMesh::number_of_patches() const
+{
+ // we need neighbors
+ if (!this->repaired)
+ throw std::runtime_error("split() requires repair()");
+
+ if (this->stl.stats.number_of_facets == 0)
+ return false;
+
+ std::vector<int> facet_queue(this->stl.stats.number_of_facets, 0);
+ std::vector<char> facet_visited(this->stl.stats.number_of_facets, false);
+ int facet_queue_cnt = 0;
+ size_t num_bodies = 0;
+ for (;;) {
+ // Find a seeding triangle for a new body.
+ int facet_idx = 0;
+ for (; facet_idx < this->stl.stats.number_of_facets; ++ facet_idx)
+ if (! facet_visited[facet_idx]) {
+ // A seed triangle was found.
+ facet_queue[facet_queue_cnt ++] = facet_idx;
+ facet_visited[facet_idx] = true;
+ break;
+ }
+ if (facet_idx == this->stl.stats.number_of_facets)
+ // No seed found.
+ break;
+ ++ num_bodies;
+ while (facet_queue_cnt > 0) {
+ int facet_idx = facet_queue[-- facet_queue_cnt];
+ facet_visited[facet_idx] = true;
+ for (int j = 0; j < 3; ++ j) {
+ int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j];
+ if (neighbor_idx != -1 && ! facet_visited[neighbor_idx])
+ facet_queue[facet_queue_cnt ++] = neighbor_idx;
+ }
+ }
+ }
+
+ return num_bodies;
+}
+
+TriangleMeshPtrs TriangleMesh::split() const
+{
+ TriangleMeshPtrs meshes;
+ std::vector<unsigned char> facet_visited(this->stl.stats.number_of_facets, false);
+
+ // we need neighbors
+ if (!this->repaired)
+ throw std::runtime_error("split() requires repair()");
+
+ // loop while we have remaining facets
+ for (;;) {
+ // get the first facet
+ std::queue<int> facet_queue;
+ std::deque<int> facets;
+ for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; facet_idx++) {
+ if (! facet_visited[facet_idx]) {
+ // if facet was not seen put it into queue and start searching
+ facet_queue.push(facet_idx);
+ break;
+ }
+ }
+ if (facet_queue.empty())
+ break;
+
+ while (! facet_queue.empty()) {
+ int facet_idx = facet_queue.front();
+ facet_queue.pop();
+ if (! facet_visited[facet_idx]) {
+ facets.emplace_back(facet_idx);
+ for (int j = 0; j < 3; ++ j)
+ facet_queue.push(this->stl.neighbors_start[facet_idx].neighbor[j]);
+ facet_visited[facet_idx] = true;
+ }
+ }
+
+ TriangleMesh* mesh = new TriangleMesh;
+ meshes.emplace_back(mesh);
+ mesh->stl.stats.type = inmemory;
+ mesh->stl.stats.number_of_facets = facets.size();
+ mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets;
+ stl_clear_error(&mesh->stl);
+ stl_allocate(&mesh->stl);
+
+ bool first = true;
+ for (std::deque<int>::const_iterator facet = facets.begin(); facet != facets.end(); ++ facet) {
+ mesh->stl.facet_start[facet - facets.begin()] = this->stl.facet_start[*facet];
+ stl_facet_stats(&mesh->stl, this->stl.facet_start[*facet], first);
+ }
+ }
+
+ return meshes;
+}
+
+void TriangleMesh::merge(const TriangleMesh &mesh)
+{
+ // reset stats and metadata
+ int number_of_facets = this->stl.stats.number_of_facets;
+ stl_invalidate_shared_vertices(&this->stl);
+ this->repaired = false;
+
+ // update facet count and allocate more memory
+ this->stl.stats.number_of_facets = number_of_facets + mesh.stl.stats.number_of_facets;
+ this->stl.stats.original_num_facets = this->stl.stats.number_of_facets;
+ stl_reallocate(&this->stl);
+
+ // copy facets
+ for (int i = 0; i < mesh.stl.stats.number_of_facets; i++) {
+ this->stl.facet_start[number_of_facets + i] = mesh.stl.facet_start[i];
+ }
+
+ // update size
+ stl_get_size(&this->stl);
+}
+
+// Calculate projection of the mesh into the XY plane, in scaled coordinates.
+//FIXME This could be extremely slow! Use it for tiny meshes only!
+ExPolygons TriangleMesh::horizontal_projection() const
+{
+ Polygons pp;
+ pp.reserve(this->stl.stats.number_of_facets);
+ for (int i = 0; i < this->stl.stats.number_of_facets; i++) {
+ stl_facet* facet = &this->stl.facet_start[i];
+ Polygon p;
+ p.points.resize(3);
+ p.points[0] = Point::new_scale(facet->vertex[0](0), facet->vertex[0](1));
+ p.points[1] = Point::new_scale(facet->vertex[1](0), facet->vertex[1](1));
+ p.points[2] = Point::new_scale(facet->vertex[2](0), facet->vertex[2](1));
+ p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that
+ pp.emplace_back(p);
+ }
+
+ // the offset factor was tuned using groovemount.stl
+ return union_ex(offset(pp, scale_(0.01)), true);
+}
+
+const float* TriangleMesh::first_vertex() const
+{
+ return this->stl.facet_start ? &this->stl.facet_start->vertex[0](0) : nullptr;
+}
+
+Polygon TriangleMesh::convex_hull()
+{
+ this->require_shared_vertices();
+ Points pp;
+ pp.reserve(this->stl.stats.shared_vertices);
+ for (int i = 0; i < this->stl.stats.shared_vertices; ++ i) {
+ const stl_vertex &v = this->stl.v_shared[i];
+ pp.emplace_back(Point::new_scale(v(0), v(1)));
+ }
+ return Slic3r::Geometry::convex_hull(pp);
+}
+
+BoundingBoxf3 TriangleMesh::bounding_box() const
+{
+ BoundingBoxf3 bb;
+ bb.defined = true;
+ bb.min = this->stl.stats.min.cast<double>();
+ bb.max = this->stl.stats.max.cast<double>();
+ return bb;
+}
+
+BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& t) const
+{
+ bool has_shared = (stl.v_shared != nullptr);
+ if (!has_shared)
+ stl_generate_shared_vertices(&stl);
+
+ unsigned int vertices_count = (stl.stats.shared_vertices > 0) ? (unsigned int)stl.stats.shared_vertices : 3 * (unsigned int)stl.stats.number_of_facets;
+
+ if (vertices_count == 0)
+ return BoundingBoxf3();
+
+ Eigen::MatrixXd src_vertices(3, vertices_count);
+
+ if (stl.stats.shared_vertices > 0)
+ {
+ stl_vertex* vertex_ptr = stl.v_shared;
+ for (int i = 0; i < stl.stats.shared_vertices; ++i)
+ {
+ src_vertices(0, i) = (double)(*vertex_ptr)(0);
+ src_vertices(1, i) = (double)(*vertex_ptr)(1);
+ src_vertices(2, i) = (double)(*vertex_ptr)(2);
+ vertex_ptr += 1;
+ }
+ }
+ else
+ {
+ stl_facet* facet_ptr = stl.facet_start;
+ unsigned int v_id = 0;
+ while (facet_ptr < stl.facet_start + stl.stats.number_of_facets)
+ {
+ for (int i = 0; i < 3; ++i)
+ {
+ src_vertices(0, v_id) = (double)facet_ptr->vertex[i](0);
+ src_vertices(1, v_id) = (double)facet_ptr->vertex[i](1);
+ src_vertices(2, v_id) = (double)facet_ptr->vertex[i](2);
+ ++v_id;
+ }
+ facet_ptr += 1;
+ }
+ }
+
+ if (!has_shared && (stl.stats.shared_vertices > 0))
+ stl_invalidate_shared_vertices(&stl);
+
+ Eigen::MatrixXd dst_vertices(3, vertices_count);
+ dst_vertices = t * 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 < vertices_count; ++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);
+}
+
+TriangleMesh TriangleMesh::convex_hull_3d() const
+{
+ // Helper struct for qhull:
+ struct PointForQHull{
+ PointForQHull(float x_p, float y_p, float z_p) : x((realT)x_p), y((realT)y_p), z((realT)z_p) {}
+ realT x, y, z;
+ };
+ std::vector<PointForQHull> src_vertices;
+
+ // We will now fill the vector with input points for computation:
+ stl_facet* facet_ptr = stl.facet_start;
+ while (facet_ptr < stl.facet_start + stl.stats.number_of_facets)
+ {
+ for (int i = 0; i < 3; ++i)
+ {
+ const stl_vertex& v = facet_ptr->vertex[i];
+ src_vertices.emplace_back(v(0), v(1), v(2));
+ }
+
+ facet_ptr += 1;
+ }
+
+ // The qhull call:
+ orgQhull::Qhull qhull;
+ qhull.disableOutputStream(); // we want qhull to be quiet
+ try
+ {
+ qhull.runQhull("", 3, (int)src_vertices.size(), (const realT*)(src_vertices.data()), "Qt");
+ }
+ catch (...)
+ {
+ std::cout << "Unable to create convex hull" << std::endl;
+ return TriangleMesh();
+ }
+
+ // Let's collect results:
+ Pointf3s dst_vertices;
+ std::vector<Vec3crd> facets;
+ auto facet_list = qhull.facetList().toStdVector();
+ for (const orgQhull::QhullFacet& facet : facet_list)
+ { // iterate through facets
+ orgQhull::QhullVertexSet vertices = facet.vertices();
+ for (int i = 0; i < 3; ++i)
+ { // iterate through facet's vertices
+
+ orgQhull::QhullPoint p = vertices[i].point();
+ const float* coords = p.coordinates();
+ dst_vertices.emplace_back(coords[0], coords[1], coords[2]);
+ }
+ unsigned int size = (unsigned int)dst_vertices.size();
+ facets.emplace_back(size - 3, size - 2, size - 1);
+ }
+
+ TriangleMesh output_mesh(dst_vertices, facets);
+ output_mesh.repair();
+ output_mesh.require_shared_vertices();
+ return output_mesh;
+}
+
+void TriangleMesh::require_shared_vertices()
+{
+ BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start";
+ if (!this->repaired)
+ this->repair();
+ if (this->stl.v_shared == NULL) {
+ BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices";
+ stl_generate_shared_vertices(&(this->stl));
+ }
+#ifdef _DEBUG
+ // Verify validity of neighborship data.
+ for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) {
+ const stl_neighbors &nbr = stl.neighbors_start[facet_idx];
+ const int *vertices = stl.v_indices[facet_idx].vertex;
+ for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) {
+ int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx];
+ if (nbr_face != -1) {
+ assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3]);
+ assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]);
+ }
+ }
+ }
+#endif /* _DEBUG */
+ BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
+}
+
+void TriangleMeshSlicer::init(TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel)
+{
+ mesh = _mesh;
+ _mesh->require_shared_vertices();
+ throw_on_cancel();
+ facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1);
+ v_scaled_shared.assign(_mesh->stl.v_shared, _mesh->stl.v_shared + _mesh->stl.stats.shared_vertices);
+ // Scale the copied vertices.
+ for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i)
+ this->v_scaled_shared[i] *= float(1. / SCALING_FACTOR);
+
+ // Create a mapping from triangle edge into face.
+ struct EdgeToFace {
+ // Index of the 1st vertex of the triangle edge. vertex_low <= vertex_high.
+ int vertex_low;
+ // Index of the 2nd vertex of the triangle edge.
+ int vertex_high;
+ // Index of a triangular face.
+ int face;
+ // Index of edge in the face, starting with 1. Negative indices if the edge was stored reverse in (vertex_low, vertex_high).
+ int face_edge;
+ bool operator==(const EdgeToFace &other) const { return vertex_low == other.vertex_low && vertex_high == other.vertex_high; }
+ bool operator<(const EdgeToFace &other) const { return vertex_low < other.vertex_low || (vertex_low == other.vertex_low && vertex_high < other.vertex_high); }
+ };
+ std::vector<EdgeToFace> edges_map;
+ edges_map.assign(this->mesh->stl.stats.number_of_facets * 3, EdgeToFace());
+ for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx)
+ for (int i = 0; i < 3; ++ i) {
+ EdgeToFace &e2f = edges_map[facet_idx*3+i];
+ e2f.vertex_low = this->mesh->stl.v_indices[facet_idx].vertex[i];
+ e2f.vertex_high = this->mesh->stl.v_indices[facet_idx].vertex[(i + 1) % 3];
+ e2f.face = facet_idx;
+ // 1 based indexing, to be always strictly positive.
+ e2f.face_edge = i + 1;
+ if (e2f.vertex_low > e2f.vertex_high) {
+ // Sort the vertices
+ std::swap(e2f.vertex_low, e2f.vertex_high);
+ // and make the face_edge negative to indicate a flipped edge.
+ e2f.face_edge = - e2f.face_edge;
+ }
+ }
+ throw_on_cancel();
+ std::sort(edges_map.begin(), edges_map.end());
+
+ // Assign a unique common edge id to touching triangle edges.
+ int num_edges = 0;
+ for (size_t i = 0; i < edges_map.size(); ++ i) {
+ EdgeToFace &edge_i = edges_map[i];
+ if (edge_i.face == -1)
+ // This edge has been connected to some neighbor already.
+ continue;
+ // Unconnected edge. Find its neighbor with the correct orientation.
+ size_t j;
+ bool found = false;
+ for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++ j)
+ if (edge_i.face_edge * edges_map[j].face_edge < 0 && edges_map[j].face != -1) {
+ // Faces touching with opposite oriented edges and none of the edges is connected yet.
+ found = true;
+ break;
+ }
+ if (! found) {
+ //FIXME Vojtech: Trying to find an edge with equal orientation. This smells.
+ // admesh can assign the same edge ID to more than two facets (which is
+ // still topologically correct), so we have to search for a duplicate of
+ // this edge too in case it was already seen in this orientation
+ for (j = i + 1; j < edges_map.size() && edge_i == edges_map[j]; ++ j)
+ if (edges_map[j].face != -1) {
+ // Faces touching with equally oriented edges and none of the edges is connected yet.
+ found = true;
+ break;
+ }
+ }
+ // Assign an edge index to the 1st face.
+ this->facets_edges[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges;
+ if (found) {
+ EdgeToFace &edge_j = edges_map[j];
+ this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges;
+ // Mark the edge as connected.
+ edge_j.face = -1;
+ }
+ ++ num_edges;
+ if ((i & 0x0ffff) == 0)
+ throw_on_cancel();
+ }
+}
+
+void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const
+{
+ BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice";
+
+ /*
+ This method gets called with a list of unscaled Z coordinates and outputs
+ a vector pointer having the same number of items as the original list.
+ Each item is a vector of polygons created by slicing our mesh at the
+ given heights.
+
+ This method should basically combine the behavior of the existing
+ Perl methods defined in lib/Slic3r/TriangleMesh.pm:
+
+ - analyze(): this creates the 'facets_edges' and the 'edges_facets'
+ tables (we don't need the 'edges' table)
+
+ - slice_facet(): this has to be done for each facet. It generates
+ intersection lines with each plane identified by the Z list.
+ The get_layer_range() binary search used to identify the Z range
+ of the facet is already ported to C++ (see Object.xsp)
+
+ - make_loops(): this has to be done for each layer. It creates polygons
+ from the lines generated by the previous step.
+
+ At the end, we free the tables generated by analyze() as we don't
+ need them anymore.
+
+ NOTE: this method accepts a vector of floats because the mesh coordinate
+ type is float.
+ */
+
+ BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::_slice_do";
+ std::vector<IntersectionLines> lines(z.size());
+ {
+ boost::mutex lines_mutex;
+ tbb::parallel_for(
+ tbb::blocked_range<int>(0,this->mesh->stl.stats.number_of_facets),
+ [&lines, &lines_mutex, &z, throw_on_cancel, this](const tbb::blocked_range<int>& range) {
+ for (int facet_idx = range.begin(); facet_idx < range.end(); ++ facet_idx) {
+ if ((facet_idx & 0x0ffff) == 0)
+ throw_on_cancel();
+ this->_slice_do(facet_idx, &lines, &lines_mutex, z);
+ }
+ }
+ );
+ }
+ throw_on_cancel();
+
+ // v_scaled_shared could be freed here
+
+ // build loops
+ BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::_make_loops_do";
+ layers->resize(z.size());
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, z.size()),
+ [&lines, &layers, throw_on_cancel, this](const tbb::blocked_range<size_t>& range) {
+ for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) {
+ if ((line_idx & 0x0ffff) == 0)
+ throw_on_cancel();
+ this->make_loops(lines[line_idx], &(*layers)[line_idx]);
+ }
+ }
+ );
+ BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice finished";
+
+#ifdef SLIC3R_DEBUG
+ {
+ static int iRun = 0;
+ for (size_t i = 0; i < z.size(); ++ i) {
+ Polygons &polygons = (*layers)[i];
+ ExPolygons expolygons = union_ex(polygons, true);
+ SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), expolygons);
+ {
+ BoundingBox bbox;
+ for (const IntersectionLine &l : lines[i]) {
+ bbox.merge(l.a);
+ bbox.merge(l.b);
+ }
+ SVG svg(debug_out_path("slice_loops_%d_%d.svg", iRun, i).c_str(), bbox);
+ svg.draw(expolygons);
+ for (const IntersectionLine &l : lines[i])
+ svg.draw(l, "red", 0);
+ svg.draw_outline(expolygons, "black", "blue", 0);
+ svg.Close();
+ }
+#if 0
+//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
+ for (Polygon &poly : polygons) {
+ for (size_t i = 1; i < poly.points.size(); ++ i)
+ assert(poly.points[i-1] != poly.points[i]);
+ assert(poly.points.front() != poly.points.back());
+ }
+#endif
+ }
+ ++ iRun;
+ }
+#endif
+}
+
+void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex,
+ const std::vector<float> &z) const
+{
+ const stl_facet &facet = this->mesh->stl.facet_start[facet_idx];
+
+ // find facet extents
+ const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2)));
+ const float max_z = fmaxf(facet.vertex[0](2), fmaxf(facet.vertex[1](2), facet.vertex[2](2)));
+
+ #ifdef SLIC3R_TRIANGLEMESH_DEBUG
+ printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx,
+ facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0](2),
+ facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1](2),
+ facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2](2));
+ printf("z: min = %.2f, max = %.2f\n", min_z, max_z);
+ #endif /* SLIC3R_TRIANGLEMESH_DEBUG */
+
+ // find layer extents
+ std::vector<float>::const_iterator min_layer, max_layer;
+ min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z
+ max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z
+ #ifdef SLIC3R_TRIANGLEMESH_DEBUG
+ printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin()));
+ #endif /* SLIC3R_TRIANGLEMESH_DEBUG */
+
+ for (std::vector<float>::const_iterator it = min_layer; it != max_layer + 1; ++it) {
+ std::vector<float>::size_type layer_idx = it - z.begin();
+ IntersectionLine il;
+ if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) {
+ boost::lock_guard<boost::mutex> l(*lines_mutex);
+ if (il.edge_type == feHorizontal) {
+ // Insert all marked edges of the face. The marked edges do not share an edge with another horizontal face
+ // (they may not have a nighbor, or their neighbor is vertical)
+ const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
+ const bool reverse = this->mesh->stl.facet_start[facet_idx].normal(2) < 0;
+ for (int j = 0; j < 3; ++ j)
+ if (il.flags & ((IntersectionLine::EDGE0_NO_NEIGHBOR | IntersectionLine::EDGE0_FOLD) << j)) {
+ int a_id = vertices[j % 3];
+ int b_id = vertices[(j+1) % 3];
+ if (reverse)
+ std::swap(a_id, b_id);
+ const stl_vertex &a = this->v_scaled_shared[a_id];
+ const stl_vertex &b = this->v_scaled_shared[b_id];
+ il.a(0) = a(0);
+ il.a(1) = a(1);
+ il.b(0) = b(0);
+ il.b(1) = b(1);
+ il.a_id = a_id;
+ il.b_id = b_id;
+ assert(il.a != il.b);
+ // This edge will not be used as a seed for loop extraction if it was added due to a fold of two overlapping horizontal faces.
+ il.set_no_seed((IntersectionLine::EDGE0_FOLD << j) != 0);
+ (*lines)[layer_idx].emplace_back(il);
+ }
+ } else
+ (*lines)[layer_idx].emplace_back(il);
+ }
+ }
+}
+
+void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const
+{
+ std::vector<Polygons> layers_p;
+ this->slice(z, &layers_p, throw_on_cancel);
+
+ BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start";
+ layers->resize(z.size());
+ tbb::parallel_for(
+ tbb::blocked_range<size_t>(0, z.size()),
+ [&layers_p, layers, throw_on_cancel, this](const tbb::blocked_range<size_t>& range) {
+ for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
+#ifdef SLIC3R_TRIANGLEMESH_DEBUG
+ printf("Layer " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]);
+#endif
+ throw_on_cancel();
+ this->make_expolygons(layers_p[layer_id], &(*layers)[layer_id]);
+ }
+ });
+ BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end";
+}
+
+// Return true, if the facet has been sliced and line_out has been filled.
+TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
+ float slice_z, const stl_facet &facet, const int facet_idx,
+ const float min_z, const float max_z,
+ IntersectionLine *line_out) const
+{
+ IntersectionPoint points[3];
+ size_t num_points = 0;
+ size_t point_on_layer = size_t(-1);
+
+ // Reorder vertices so that the first one is the one with lowest Z.
+ // This is needed to get all intersection lines in a consistent order
+ // (external on the right of the line)
+ const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
+ int i = (facet.vertex[1](2) == min_z) ? 1 : ((facet.vertex[2](2) == min_z) ? 2 : 0);
+ for (int j = i; j - i < 3; ++j ) { // loop through facet edges
+ int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)];
+ int a_id = vertices[j % 3];
+ int b_id = vertices[(j+1) % 3];
+ const stl_vertex &a = this->v_scaled_shared[a_id];
+ const stl_vertex &b = this->v_scaled_shared[b_id];
+
+ // Is edge or face aligned with the cutting plane?
+ if (a(2) == slice_z && b(2) == slice_z) {
+ // Edge is horizontal and belongs to the current layer.
+ const stl_vertex &v0 = this->v_scaled_shared[vertices[0]];
+ const stl_vertex &v1 = this->v_scaled_shared[vertices[1]];
+ const stl_vertex &v2 = this->v_scaled_shared[vertices[2]];
+ bool swap = false;
+ const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal;
+ // We may ignore this edge for slicing purposes, but we may still use it for object cutting.
+ FacetSliceType result = Slicing;
+ const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx];
+ if (min_z == max_z) {
+ // All three vertices are aligned with slice_z.
+ line_out->edge_type = feHorizontal;
+ // Mark neighbor edges, which do not have a neighbor.
+ uint32_t edges = 0;
+ for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx) {
+ // If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no
+ // opposite face, add it to the edges to process when slicing.
+ if (nbr.neighbor[nbr_idx] == -1) {
+ // Mark this edge to be added to the slice.
+ edges |= (IntersectionLine::EDGE0_NO_NEIGHBOR << nbr_idx);
+ }
+#if 1
+ else if (normal(2) > 0) {
+ // Produce edges for opposite faced overlapping horizontal faces aka folds.
+ // This method often produces connecting lines (noise) at the cutting plane.
+ // Produce the edges for the top facing face of the pair of top / bottom facing faces.
+
+ // Index of a neighbor face.
+ const int nbr_face = nbr.neighbor[nbr_idx];
+ const int *nbr_vertices = this->mesh->stl.v_indices[nbr_face].vertex;
+ int idx_vertex_opposite = nbr_vertices[nbr.which_vertex_not[nbr_idx]];
+ const stl_vertex &c2 = this->v_scaled_shared[idx_vertex_opposite];
+ if (c2(2) == slice_z) {
+ // Edge shared by facet_idx and nbr_face.
+ int a_id = vertices[nbr_idx];
+ int b_id = vertices[(nbr_idx + 1) % 3];
+ int c1_id = vertices[(nbr_idx + 2) % 3];
+ const stl_vertex &a = this->v_scaled_shared[a_id];
+ const stl_vertex &b = this->v_scaled_shared[b_id];
+ const stl_vertex &c1 = this->v_scaled_shared[c1_id];
+ // Verify that the two neighbor faces share a common edge.
+ assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id);
+ assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id);
+ double n1 = (double(c1(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c1(1)) - double(a(1))) * (double(b(0)) - double(a(0)));
+ double n2 = (double(c2(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c2(1)) - double(a(1))) * (double(b(0)) - double(a(0)));
+ if (n1 * n2 > 0)
+ // The two faces overlap. This indicates an invalid mesh geometry (non-manifold),
+ // but these are the real world objects, and leaving out these edges leads to missing contours.
+ edges |= (IntersectionLine::EDGE0_FOLD << nbr_idx);
+ }
+ }
+#endif
+ }
+ // Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face.
+ result = (edges == 0) ? Cutting : Slicing;
+ line_out->flags |= edges;
+ if (normal(2) < 0) {
+ // If normal points downwards this is a bottom horizontal facet so we reverse its point order.
+ swap = true;
+ }
+ } else {
+ // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane.
+ int nbr_idx = j % 3;
+ int nbr_face = nbr.neighbor[nbr_idx];
+ // Is the third vertex below the cutting plane?
+ bool third_below = v0(2) < slice_z || v1(2) < slice_z || v2(2) < slice_z;
+ // Is this a concave corner?
+ if (nbr_face == -1) {
+#ifdef _DEBUG
+ printf("Face has no neighbor!\n");
+#endif
+ } else {
+ assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id);
+ assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id);
+ int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]];
+ const stl_vertex &c = this->v_scaled_shared[idx_vertex_opposite];
+ if (c(2) == slice_z) {
+ double normal_nbr = (double(c(0)) - double(a(0))) * (double(b(1)) - double(a(1))) - (double(c(1)) - double(a(1))) * (double(b(0)) - double(a(0)));
+#if 0
+ if ((normal_nbr < 0) == third_below) {
+ printf("Flipped normal?\n");
+ }
+#endif
+ result =
+ // A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner.
+ // Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge,
+ // and leth the code downstream hopefully handle it.
+ #if 1
+ // Ignore concave corners for slicing.
+ // This method has the unfortunate property, that folds in a horizontal plane create concave corners,
+ // leading to broken contours, if these concave corners are not replaced by edges of the folds, see above.
+ ((normal_nbr < 0) == third_below) ? Cutting : Slicing;
+ #else
+ // Use concave corners for slicing. This leads to the test 01_trianglemesh.t "slicing a top tangent plane includes its area" failing,
+ // and rightly so.
+ Slicing;
+ #endif
+ } else {
+ // For a pair of faces touching exactly at the cutting plane, ignore one of them. An arbitrary rule is to ignore the face with a higher index.
+ result = (facet_idx < nbr_face) ? Slicing : Cutting;
+ }
+ }
+ if (third_below) {
+ line_out->edge_type = feTop;
+ swap = true;
+ } else
+ line_out->edge_type = feBottom;
+ }
+ line_out->a = to_2d(swap ? b : a).cast<coord_t>();
+ line_out->b = to_2d(swap ? a : b).cast<coord_t>();
+ line_out->a_id = swap ? b_id : a_id;
+ line_out->b_id = swap ? a_id : b_id;
+ assert(line_out->a != line_out->b);
+ return result;
+ }
+
+ if (a(2) == slice_z) {
+ // Only point a alings with the cutting plane.
+ if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) {
+ point_on_layer = num_points;
+ IntersectionPoint &point = points[num_points ++];
+ point(0) = a(0);
+ point(1) = a(1);
+ point.point_id = a_id;
+ }
+ } else if (b(2) == slice_z) {
+ // Only point b alings with the cutting plane.
+ if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) {
+ point_on_layer = num_points;
+ IntersectionPoint &point = points[num_points ++];
+ point(0) = b(0);
+ point(1) = b(1);
+ point.point_id = b_id;
+ }
+ } else if ((a(2) < slice_z && b(2) > slice_z) || (b(2) < slice_z && a(2) > slice_z)) {
+ // A general case. The face edge intersects the cutting plane. Calculate the intersection point.
+ assert(a_id != b_id);
+ // Sort the edge to give a consistent answer.
+ const stl_vertex *pa = &a;
+ const stl_vertex *pb = &b;
+ if (a_id > b_id) {
+ std::swap(a_id, b_id);
+ std::swap(pa, pb);
+ }
+ IntersectionPoint &point = points[num_points];
+ double t = (double(slice_z) - double((*pb)(2))) / (double((*pa)(2)) - double((*pb)(2)));
+ if (t <= 0.) {
+ if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) {
+ point(0) = (*pa)(0);
+ point(1) = (*pa)(1);
+ point_on_layer = num_points ++;
+ point.point_id = a_id;
+ }
+ } else if (t >= 1.) {
+ if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) {
+ point(0) = (*pb)(0);
+ point(1) = (*pb)(1);
+ point_on_layer = num_points ++;
+ point.point_id = b_id;
+ }
+ } else {
+ point(0) = coord_t(floor(double((*pb)(0)) + (double((*pa)(0)) - double((*pb)(0))) * t + 0.5));
+ point(1) = coord_t(floor(double((*pb)(1)) + (double((*pa)(1)) - double((*pb)(1))) * t + 0.5));
+ point.edge_id = edge_id;
+ ++ num_points;
+ }
+ }
+ }
+
+ // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only.
+ assert(num_points < 3);
+ if (num_points == 2) {
+ line_out->edge_type = feGeneral;
+ line_out->a = (Point)points[1];
+ line_out->b = (Point)points[0];
+ line_out->a_id = points[1].point_id;
+ line_out->b_id = points[0].point_id;
+ line_out->edge_a_id = points[1].edge_id;
+ line_out->edge_b_id = points[0].edge_id;
+ // Not a zero lenght edge.
+ //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
+ //assert(line_out->a != line_out->b);
+ // The plane cuts at least one edge in a general position.
+ assert(line_out->a_id == -1 || line_out->b_id == -1);
+ assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1);
+ // General slicing position, use the segment for both slicing and object cutting.
+#if 0
+ if (line_out->a_id != -1 && line_out->b_id != -1) {
+ // Solving a degenerate case, where both the intersections snapped to an edge.
+ // Correctly classify the face as below or above based on the position of the 3rd point.
+ int i = vertices[0];
+ if (i == line_out->a_id || i == line_out->b_id)
+ i = vertices[1];
+ if (i == line_out->a_id || i == line_out->b_id)
+ i = vertices[2];
+ assert(i != line_out->a_id && i != line_out->b_id);
+ line_out->edge_type = (this->v_scaled_shared[i].z < slice_z) ? feTop : feBottom;
+ }
+#endif
+ return Slicing;
+ }
+ return NoSlice;
+}
+
+//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing
+// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces.
+// So the following code makes only sense now to handle degenerate meshes with more than two faces
+// sharing a single edge.
+static inline void remove_tangent_edges(std::vector<IntersectionLine> &lines)
+{
+ std::vector<IntersectionLine*> by_vertex_pair;
+ by_vertex_pair.reserve(lines.size());
+ for (IntersectionLine& line : lines)
+ if (line.edge_type != feGeneral && line.a_id != -1)
+ // This is a face edge. Check whether there is its neighbor stored in lines.
+ by_vertex_pair.emplace_back(&line);
+ auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) {
+ // Sort vertices of l1, l2 lexicographically
+ int l1a = l1->a_id;
+ int l1b = l1->b_id;
+ int l2a = l2->a_id;
+ int l2b = l2->b_id;
+ if (l1a > l1b)
+ std::swap(l1a, l1b);
+ if (l2a > l2b)
+ std::swap(l2a, l2b);
+ // Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored.
+ return l1a < l2a || (l1a == l2a && l1b < l2b);
+ };
+ std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted);
+ for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) {
+ IntersectionLine &l1 = **line;
+ if (! l1.skip()) {
+ // Iterate as long as line and line2 edges share the same end points.
+ for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) {
+ // Lines must share the end points.
+ assert(! edges_lower_sorted(*line, *line2));
+ assert(! edges_lower_sorted(*line2, *line));
+ IntersectionLine &l2 = **line2;
+ if (l2.skip())
+ continue;
+ if (l1.a_id == l2.a_id) {
+ assert(l1.b_id == l2.b_id);
+ l2.set_skip();
+ // If they are both oriented upwards or downwards (like a 'V'),
+ // then we can remove both edges from this layer since it won't
+ // affect the sliced shape.
+ // If one of them is oriented upwards and the other is oriented
+ // downwards, let's only keep one of them (it doesn't matter which
+ // one since all 'top' lines were reversed at slicing).
+ if (l1.edge_type == l2.edge_type) {
+ l1.set_skip();
+ break;
+ }
+ } else {
+ assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id);
+ // If this edge joins two horizontal facets, remove both of them.
+ if (l1.edge_type == feHorizontal && l2.edge_type == feHorizontal) {
+ l1.set_skip();
+ l2.set_skip();
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const
+{
+#if 0
+//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t.
+//#ifdef _DEBUG
+ for (const Line &l : lines)
+ assert(l.a != l.b);
+#endif /* _DEBUG */
+
+ remove_tangent_edges(lines);
+
+ struct OpenPolyline {
+ OpenPolyline() {};
+ OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) :
+ start(start), end(end), points(std::move(points)), consumed(false) {}
+ void reverse() {
+ std::swap(start, end);
+ std::reverse(points.begin(), points.end());
+ }
+ IntersectionReference start;
+ IntersectionReference end;
+ Points points;
+ bool consumed;
+ };
+ std::vector<OpenPolyline> open_polylines;
+ {
+ // Build a map of lines by edge_a_id and a_id.
+ std::vector<IntersectionLine*> by_edge_a_id;
+ std::vector<IntersectionLine*> by_a_id;
+ by_edge_a_id.reserve(lines.size());
+ by_a_id.reserve(lines.size());
+ for (IntersectionLine &line : lines) {
+ if (! line.skip()) {
+ if (line.edge_a_id != -1)
+ by_edge_a_id.emplace_back(&line);
+ if (line.a_id != -1)
+ by_a_id.emplace_back(&line);
+ }
+ }
+ auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; };
+ auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; };
+ std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower);
+ std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower);
+ // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines.
+ IntersectionLines::iterator it_line_seed = lines.begin();
+ for (;;) {
+ // take first spare line and start a new loop
+ IntersectionLine *first_line = nullptr;
+ for (; it_line_seed != lines.end(); ++ it_line_seed)
+ if (it_line_seed->is_seed_candidate()) {
+ //if (! it_line_seed->skip()) {
+ first_line = &(*it_line_seed ++);
+ break;
+ }
+ if (first_line == nullptr)
+ break;
+ first_line->set_skip();
+ Points loop_pts;
+ loop_pts.emplace_back(first_line->a);
+ IntersectionLine *last_line = first_line;
+
+ /*
+ printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
+ first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id,
+ first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y);
+ */
+
+ IntersectionLine key;
+ for (;;) {
+ // find a line starting where last one finishes
+ IntersectionLine* next_line = nullptr;
+ if (last_line->edge_b_id != -1) {
+ key.edge_a_id = last_line->edge_b_id;
+ auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower);
+ if (it_begin != by_edge_a_id.end()) {
+ auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower);
+ for (auto it_line = it_begin; it_line != it_end; ++ it_line)
+ if (! (*it_line)->skip()) {
+ next_line = *it_line;
+ break;
+ }
+ }
+ }
+ if (next_line == nullptr && last_line->b_id != -1) {
+ key.a_id = last_line->b_id;
+ auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower);
+ if (it_begin != by_a_id.end()) {
+ auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower);
+ for (auto it_line = it_begin; it_line != it_end; ++ it_line)
+ if (! (*it_line)->skip()) {
+ next_line = *it_line;
+ break;
+ }
+ }
+ }
+ if (next_line == nullptr) {
+ // Check whether we closed this loop.
+ if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) ||
+ (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
+ // The current loop is complete. Add it to the output.
+ loops->emplace_back(std::move(loop_pts));
+ #ifdef SLIC3R_TRIANGLEMESH_DEBUG
+ printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
+ #endif
+ } else {
+ // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later.
+ loop_pts.emplace_back(last_line->b);
+ open_polylines.emplace_back(OpenPolyline(
+ IntersectionReference(first_line->a_id, first_line->edge_a_id),
+ IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts)));
+ }
+ break;
+ }
+ /*
+ printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n",
+ next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id,
+ next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
+ */
+ loop_pts.emplace_back(next_line->a);
+ last_line = next_line;
+ next_line->set_skip();
+ }
+ }
+ }
+
+ // Now process the open polylines.
+ if (! open_polylines.empty()) {
+ // Store the end points of open_polylines into vectors sorted
+ struct OpenPolylineEnd {
+ OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {}
+ OpenPolyline *polyline;
+ // Is it the start or end point?
+ bool start;
+ const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; }
+ int point_id() const { return ipref().point_id; }
+ int edge_id () const { return ipref().edge_id; }
+ };
+ auto by_edge_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.edge_id() < ope2.edge_id(); };
+ auto by_point_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.point_id() < ope2.point_id(); };
+ std::vector<OpenPolylineEnd> by_edge_id;
+ std::vector<OpenPolylineEnd> by_point_id;
+ by_edge_id.reserve(2 * open_polylines.size());
+ by_point_id.reserve(2 * open_polylines.size());
+ for (OpenPolyline &opl : open_polylines) {
+ if (opl.start.edge_id != -1)
+ by_edge_id .emplace_back(OpenPolylineEnd(&opl, true));
+ if (opl.end.edge_id != -1)
+ by_edge_id .emplace_back(OpenPolylineEnd(&opl, false));
+ if (opl.start.point_id != -1)
+ by_point_id.emplace_back(OpenPolylineEnd(&opl, true));
+ if (opl.end.point_id != -1)
+ by_point_id.emplace_back(OpenPolylineEnd(&opl, false));
+ }
+ std::sort(by_edge_id .begin(), by_edge_id .end(), by_edge_lower);
+ std::sort(by_point_id.begin(), by_point_id.end(), by_point_lower);
+
+ // Try to connect the loops.
+ for (OpenPolyline &opl : open_polylines) {
+ if (opl.consumed)
+ continue;
+ opl.consumed = true;
+ OpenPolylineEnd end(&opl, false);
+ for (;;) {
+ // find a line starting where last one finishes
+ OpenPolylineEnd* next_start = nullptr;
+ if (end.edge_id() != -1) {
+ auto it_begin = std::lower_bound(by_edge_id.begin(), by_edge_id.end(), end, by_edge_lower);
+ if (it_begin != by_edge_id.end()) {
+ auto it_end = std::upper_bound(it_begin, by_edge_id.end(), end, by_edge_lower);
+ for (auto it_edge = it_begin; it_edge != it_end; ++ it_edge)
+ if (! it_edge->polyline->consumed) {
+ next_start = &(*it_edge);
+ break;
+ }
+ }
+ }
+ if (next_start == nullptr && end.point_id() != -1) {
+ auto it_begin = std::lower_bound(by_point_id.begin(), by_point_id.end(), end, by_point_lower);
+ if (it_begin != by_point_id.end()) {
+ auto it_end = std::upper_bound(it_begin, by_point_id.end(), end, by_point_lower);
+ for (auto it_point = it_begin; it_point != it_end; ++ it_point)
+ if (! it_point->polyline->consumed) {
+ next_start = &(*it_point);
+ break;
+ }
+ }
+ }
+ if (next_start == nullptr) {
+ // The current loop could not be closed. Unmark the segment.
+ opl.consumed = false;
+ break;
+ }
+ // Attach this polyline to the end of the initial polyline.
+ if (next_start->start) {
+ auto it = next_start->polyline->points.begin();
+ std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points));
+ //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.end());
+ } else {
+ auto it = next_start->polyline->points.rbegin();
+ std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points));
+ //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.rend());
+ }
+ end = *next_start;
+ end.start = !end.start;
+ next_start->polyline->points.clear();
+ next_start->polyline->consumed = true;
+ // Check whether we closed this loop.
+ const IntersectionReference &ip1 = opl.start;
+ const IntersectionReference &ip2 = end.ipref();
+ if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) ||
+ (ip1.point_id != -1 && ip1.point_id == ip2.point_id)) {
+ // The current loop is complete. Add it to the output.
+ //assert(opl.points.front().point_id == opl.points.back().point_id);
+ //assert(opl.points.front().edge_id == opl.points.back().edge_id);
+ // Remove the duplicate last point.
+ opl.points.pop_back();
+ if (opl.points.size() >= 3) {
+ // The closed polygon is patched from pieces with messed up orientation, therefore
+ // the orientation of the patched up polygon is not known.
+ // Orient the patched up polygons CCW. This heuristic may close some holes and cavities.
+ double area = 0.;
+ for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++)
+ area += double(opl.points[j](0) + opl.points[i](0)) * double(opl.points[i](1) - opl.points[j](1));
+ if (area < 0)
+ std::reverse(opl.points.begin(), opl.points.end());
+ loops->emplace_back(std::move(opl.points));
+ }
+ opl.points.clear();
+ break;
+ }
+ // Continue with the current loop.
+ }
+ }
+ }
+}
+
+// Only used to cut the mesh into two halves.
+void TriangleMeshSlicer::make_expolygons_simple(std::vector<IntersectionLine> &lines, ExPolygons* slices) const
+{
+ assert(slices->empty());
+
+ Polygons loops;
+ this->make_loops(lines, &loops);
+
+ Polygons holes;
+ for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) {
+ if (loop->area() >= 0.) {
+ ExPolygon ex;
+ ex.contour = *loop;
+ slices->emplace_back(ex);
+ } else {
+ holes.emplace_back(*loop);
+ }
+ }
+
+ // If there are holes, then there should also be outer contours.
+ assert(holes.empty() || ! slices->empty());
+ if (slices->empty())
+ return;
+
+ // Assign holes to outer contours.
+ for (Polygons::const_iterator hole = holes.begin(); hole != holes.end(); ++ hole) {
+ // Find an outer contour to a hole.
+ int slice_idx = -1;
+ double current_contour_area = std::numeric_limits<double>::max();
+ for (ExPolygons::iterator slice = slices->begin(); slice != slices->end(); ++ slice) {
+ if (slice->contour.contains(hole->points.front())) {
+ double area = slice->contour.area();
+ if (area < current_contour_area) {
+ slice_idx = slice - slices->begin();
+ current_contour_area = area;
+ }
+ }
+ }
+ // assert(slice_idx != -1);
+ if (slice_idx == -1)
+ // Ignore this hole.
+ continue;
+ assert(current_contour_area < std::numeric_limits<double>::max() && current_contour_area >= -hole->area());
+ (*slices)[slice_idx].holes.emplace_back(std::move(*hole));
+ }
+
+#if 0
+ // If the input mesh is not valid, the holes may intersect with the external contour.
+ // Rather subtract them from the outer contour.
+ Polygons poly;
+ for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) {
+ if (it_slice->holes.empty()) {
+ poly.emplace_back(std::move(it_slice->contour));
+ } else {
+ Polygons contours;
+ contours.emplace_back(std::move(it_slice->contour));
+ for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it)
+ it->reverse();
+ polygons_append(poly, diff(contours, it_slice->holes));
+ }
+ }
+ // If the input mesh is not valid, the input contours may intersect.
+ *slices = union_ex(poly);
+#endif
+
+#if 0
+ // If the input mesh is not valid, the holes may intersect with the external contour.
+ // Rather subtract them from the outer contour.
+ ExPolygons poly;
+ for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) {
+ Polygons contours;
+ contours.emplace_back(std::move(it_slice->contour));
+ for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it)
+ it->reverse();
+ expolygons_append(poly, diff_ex(contours, it_slice->holes));
+ }
+ // If the input mesh is not valid, the input contours may intersect.
+ *slices = std::move(poly);
+#endif
+}
+
+void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) const
+{
+ /*
+ Input loops are not suitable for evenodd nor nonzero fill types, as we might get
+ two consecutive concentric loops having the same winding order - and we have to
+ respect such order. In that case, evenodd would create wrong inversions, and nonzero
+ would ignore holes inside two concentric contours.
+ So we're ordering loops and collapse consecutive concentric loops having the same
+ winding order.
+ TODO: find a faster algorithm for this, maybe with some sort of binary search.
+ If we computed a "nesting tree" we could also just remove the consecutive loops
+ having the same winding order, and remove the extra one(s) so that we could just
+ supply everything to offset() instead of performing several union/diff calls.
+
+ we sort by area assuming that the outermost loops have larger area;
+ the previous sorting method, based on $b->contains($a->[0]), failed to nest
+ loops correctly in some edge cases when original model had overlapping facets
+ */
+
+ /* The following lines are commented out because they can generate wrong polygons,
+ see for example issue #661 */
+
+ //std::vector<double> area;
+ //std::vector<size_t> sorted_area; // vector of indices
+ //for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) {
+ // area.emplace_back(loop->area());
+ // sorted_area.emplace_back(loop - loops.begin());
+ //}
+ //
+ //// outer first
+ //std::sort(sorted_area.begin(), sorted_area.end(),
+ // [&area](size_t a, size_t b) { return std::abs(area[a]) > std::abs(area[b]); });
+
+ //// we don't perform a safety offset now because it might reverse cw loops
+ //Polygons p_slices;
+ //for (std::vector<size_t>::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) {
+ // /* we rely on the already computed area to determine the winding order
+ // of the loops, since the Orientation() function provided by Clipper
+ // would do the same, thus repeating the calculation */
+ // Polygons::const_iterator loop = loops.begin() + *loop_idx;
+ // if (area[*loop_idx] > +EPSILON)
+ // p_slices.emplace_back(*loop);
+ // else if (area[*loop_idx] < -EPSILON)
+ // //FIXME This is arbitrary and possibly very slow.
+ // // If the hole is inside a polygon, then there is no need to diff.
+ // // If the hole intersects a polygon boundary, then diff it, but then
+ // // there is no guarantee of an ordering of the loops.
+ // // Maybe we can test for the intersection before running the expensive diff algorithm?
+ // p_slices = diff(p_slices, *loop);
+ //}
+
+ // perform a safety offset to merge very close facets (TODO: find test case for this)
+ double safety_offset = scale_(0.0499);
+//FIXME see https://github.com/prusa3d/Slic3r/issues/520
+// double safety_offset = scale_(0.0001);
+
+ /* The following line is commented out because it can generate wrong polygons,
+ see for example issue #661 */
+ //ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset);
+
+ #ifdef SLIC3R_TRIANGLEMESH_DEBUG
+ size_t holes_count = 0;
+ for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++ e)
+ holes_count += e->holes.size();
+ printf(PRINTF_ZU " surface(s) having " PRINTF_ZU " holes detected from " PRINTF_ZU " polylines\n",
+ ex_slices.size(), holes_count, loops.size());
+ #endif
+
+ // append to the supplied collection
+ /* Fix for issue #661 { */
+ expolygons_append(*slices, offset2_ex(union_(loops, false), +safety_offset, -safety_offset));
+ //expolygons_append(*slices, ex_slices);
+ /* } */
+}
+
+void TriangleMeshSlicer::make_expolygons(std::vector<IntersectionLine> &lines, ExPolygons* slices) const
+{
+ Polygons pp;
+ this->make_loops(lines, &pp);
+ this->make_expolygons(pp, slices);
+}
+
+void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) const
+{
+ IntersectionLines upper_lines, lower_lines;
+
+ float scaled_z = scale_(z);
+ for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) {
+ stl_facet* facet = &this->mesh->stl.facet_start[facet_idx];
+
+ // find facet extents
+ float min_z = std::min(facet->vertex[0](2), std::min(facet->vertex[1](2), facet->vertex[2](2)));
+ float max_z = std::max(facet->vertex[0](2), std::max(facet->vertex[1](2), facet->vertex[2](2)));
+
+ // intersect facet with cutting plane
+ IntersectionLine line;
+ if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line) != TriangleMeshSlicer::NoSlice) {
+ // Save intersection lines for generating correct triangulations.
+ if (line.edge_type == feTop) {
+ lower_lines.emplace_back(line);
+ } else if (line.edge_type == feBottom) {
+ upper_lines.emplace_back(line);
+ } else if (line.edge_type != feHorizontal) {
+ lower_lines.emplace_back(line);
+ upper_lines.emplace_back(line);
+ }
+ }
+
+ if (min_z > z || (min_z == z && max_z > z)) {
+ // facet is above the cut plane and does not belong to it
+ if (upper != NULL) stl_add_facet(&upper->stl, facet);
+ } else if (max_z < z || (max_z == z && min_z < z)) {
+ // facet is below the cut plane and does not belong to it
+ if (lower != NULL) stl_add_facet(&lower->stl, facet);
+ } else if (min_z < z && max_z > z) {
+ // Facet is cut by the slicing plane.
+
+ // look for the vertex on whose side of the slicing plane there are no other vertices
+ int isolated_vertex;
+ if ( (facet->vertex[0](2) > z) == (facet->vertex[1](2) > z) ) {
+ isolated_vertex = 2;
+ } else if ( (facet->vertex[1](2) > z) == (facet->vertex[2](2) > z) ) {
+ isolated_vertex = 0;
+ } else {
+ isolated_vertex = 1;
+ }
+
+ // get vertices starting from the isolated one
+ const stl_vertex &v0 = facet->vertex[isolated_vertex];
+ const stl_vertex &v1 = facet->vertex[(isolated_vertex+1) % 3];
+ const stl_vertex &v2 = facet->vertex[(isolated_vertex+2) % 3];
+
+ // intersect v0-v1 and v2-v0 with cutting plane and make new vertices
+ stl_vertex v0v1, v2v0;
+ v0v1(0) = v1(0) + (v0(0) - v1(0)) * (z - v1(2)) / (v0(2) - v1(2));
+ v0v1(1) = v1(1) + (v0(1) - v1(1)) * (z - v1(2)) / (v0(2) - v1(2));
+ v0v1(2) = z;
+ v2v0(0) = v2(0) + (v0(0) - v2(0)) * (z - v2(2)) / (v0(2) - v2(2));
+ v2v0(1) = v2(1) + (v0(1) - v2(1)) * (z - v2(2)) / (v0(2) - v2(2));
+ v2v0(2) = z;
+
+ // build the triangular facet
+ stl_facet triangle;
+ triangle.normal = facet->normal;
+ triangle.vertex[0] = v0;
+ triangle.vertex[1] = v0v1;
+ triangle.vertex[2] = v2v0;
+
+ // build the facets forming a quadrilateral on the other side
+ stl_facet quadrilateral[2];
+ quadrilateral[0].normal = facet->normal;
+ quadrilateral[0].vertex[0] = v1;
+ quadrilateral[0].vertex[1] = v2;
+ quadrilateral[0].vertex[2] = v0v1;
+ quadrilateral[1].normal = facet->normal;
+ quadrilateral[1].vertex[0] = v2;
+ quadrilateral[1].vertex[1] = v2v0;
+ quadrilateral[1].vertex[2] = v0v1;
+
+ if (v0(2) > z) {
+ if (upper != NULL) stl_add_facet(&upper->stl, &triangle);
+ if (lower != NULL) {
+ stl_add_facet(&lower->stl, &quadrilateral[0]);
+ stl_add_facet(&lower->stl, &quadrilateral[1]);
+ }
+ } else {
+ if (upper != NULL) {
+ stl_add_facet(&upper->stl, &quadrilateral[0]);
+ stl_add_facet(&upper->stl, &quadrilateral[1]);
+ }
+ if (lower != NULL) stl_add_facet(&lower->stl, &triangle);
+ }
+ }
+ }
+
+ // triangulate holes of upper mesh
+ if (upper != NULL) {
+ // compute shape of section
+ ExPolygons section;
+ this->make_expolygons_simple(upper_lines, &section);
+
+ // triangulate section
+ Polygons triangles;
+ for (ExPolygons::const_iterator expolygon = section.begin(); expolygon != section.end(); ++expolygon)
+ expolygon->triangulate_p2t(&triangles);
+
+ // convert triangles to facets and append them to mesh
+ for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) {
+ Polygon p = *polygon;
+ p.reverse();
+ stl_facet facet;
+ facet.normal = stl_normal(0, 0, -1.f);
+ for (size_t i = 0; i <= 2; ++i) {
+ facet.vertex[i](0) = unscale<float>(p.points[i](0));
+ facet.vertex[i](1) = unscale<float>(p.points[i](1));
+ facet.vertex[i](2) = z;
+ }
+ stl_add_facet(&upper->stl, &facet);
+ }
+ }
+
+ // triangulate holes of lower mesh
+ if (lower != NULL) {
+ // compute shape of section
+ ExPolygons section;
+ this->make_expolygons_simple(lower_lines, &section);
+
+ // triangulate section
+ Polygons triangles;
+ for (ExPolygons::const_iterator expolygon = section.begin(); expolygon != section.end(); ++expolygon)
+ expolygon->triangulate_p2t(&triangles);
+
+ // convert triangles to facets and append them to mesh
+ for (Polygons::const_iterator polygon = triangles.begin(); polygon != triangles.end(); ++polygon) {
+ stl_facet facet;
+ facet.normal = stl_normal(0, 0, 1.f);
+ for (size_t i = 0; i <= 2; ++i) {
+ facet.vertex[i](0) = unscale<float>(polygon->points[i](0));
+ facet.vertex[i](1) = unscale<float>(polygon->points[i](1));
+ facet.vertex[i](2) = z;
+ }
+ stl_add_facet(&lower->stl, &facet);
+ }
+ }
+
+ // Update the bounding box / sphere of the new meshes.
+ stl_get_size(&upper->stl);
+ stl_get_size(&lower->stl);
+}
+
+// Generate the vertex list for a cube solid of arbitrary size in X/Y/Z.
+TriangleMesh make_cube(double x, double y, double z) {
+ Vec3d pv[8] = {
+ Vec3d(x, y, 0), Vec3d(x, 0, 0), Vec3d(0, 0, 0),
+ Vec3d(0, y, 0), Vec3d(x, y, z), Vec3d(0, y, z),
+ Vec3d(0, 0, z), Vec3d(x, 0, z)
+ };
+ Vec3crd fv[12] = {
+ Vec3crd(0, 1, 2), Vec3crd(0, 2, 3), Vec3crd(4, 5, 6),
+ Vec3crd(4, 6, 7), Vec3crd(0, 4, 7), Vec3crd(0, 7, 1),
+ Vec3crd(1, 7, 6), Vec3crd(1, 6, 2), Vec3crd(2, 6, 5),
+ Vec3crd(2, 5, 3), Vec3crd(4, 0, 3), Vec3crd(4, 3, 5)
+ };
+
+ std::vector<Vec3crd> facets(&fv[0], &fv[0]+12);
+ Pointf3s vertices(&pv[0], &pv[0]+8);
+
+ TriangleMesh mesh(vertices ,facets);
+ return mesh;
+}
+
+// Generate the mesh for a cylinder and return it, using
+// the generated angle to calculate the top mesh triangles.
+// Default is 360 sides, angle fa is in radians.
+TriangleMesh make_cylinder(double r, double h, double fa) {
+ Pointf3s vertices;
+ std::vector<Vec3crd> facets;
+
+ // 2 special vertices, top and bottom center, rest are relative to this
+ vertices.emplace_back(Vec3d(0.0, 0.0, 0.0));
+ vertices.emplace_back(Vec3d(0.0, 0.0, h));
+
+ // adjust via rounding to get an even multiple for any provided angle.
+ double angle = (2*PI / floor(2*PI / fa));
+
+ // for each line along the polygon approximating the top/bottom of the
+ // circle, generate four points and four facets (2 for the wall, 2 for the
+ // top and bottom.
+ // Special case: Last line shares 2 vertices with the first line.
+ unsigned id = vertices.size() - 1;
+ vertices.emplace_back(Vec3d(sin(0) * r , cos(0) * r, 0));
+ vertices.emplace_back(Vec3d(sin(0) * r , cos(0) * r, h));
+ for (double i = 0; i < 2*PI; i+=angle) {
+ Vec2d p = Eigen::Rotation2Dd(i) * Eigen::Vector2d(0, r);
+ vertices.emplace_back(Vec3d(p(0), p(1), 0.));
+ vertices.emplace_back(Vec3d(p(0), p(1), h));
+ id = vertices.size() - 1;
+ facets.emplace_back(Vec3crd( 0, id - 1, id - 3)); // top
+ facets.emplace_back(Vec3crd(id, 1, id - 2)); // bottom
+ facets.emplace_back(Vec3crd(id, id - 2, id - 3)); // upper-right of side
+ facets.emplace_back(Vec3crd(id, id - 3, id - 1)); // bottom-left of side
+ }
+ // Connect the last set of vertices with the first.
+ facets.emplace_back(Vec3crd( 2, 0, id - 1));
+ facets.emplace_back(Vec3crd( 1, 3, id));
+ facets.emplace_back(Vec3crd(id, 3, 2));
+ facets.emplace_back(Vec3crd(id, 2, id - 1));
+
+ TriangleMesh mesh(vertices, facets);
+ return mesh;
+}
+
+// Generates mesh for a sphere centered about the origin, using the generated angle
+// to determine the granularity.
+// Default angle is 1 degree.
+TriangleMesh make_sphere(double rho, double fa) {
+ Pointf3s vertices;
+ std::vector<Vec3crd> facets;
+
+ // Algorithm:
+ // Add points one-by-one to the sphere grid and form facets using relative coordinates.
+ // Sphere is composed effectively of a mesh of stacked circles.
+
+ // adjust via rounding to get an even multiple for any provided angle.
+ double angle = (2*PI / floor(2*PI / fa));
+
+ // Ring to be scaled to generate the steps of the sphere
+ std::vector<double> ring;
+ for (double i = 0; i < 2*PI; i+=angle) {
+ ring.emplace_back(i);
+ }
+ const size_t steps = ring.size();
+ const double increment = (double)(1.0 / (double)steps);
+
+ // special case: first ring connects to 0,0,0
+ // insert and form facets.
+ vertices.emplace_back(Vec3d(0.0, 0.0, -rho));
+ size_t id = vertices.size();
+ for (size_t i = 0; i < ring.size(); i++) {
+ // Fixed scaling
+ const double z = -rho + increment*rho*2.0;
+ // radius of the circle for this step.
+ const double r = sqrt(abs(rho*rho - z*z));
+ Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
+ vertices.emplace_back(Vec3d(b(0), b(1), z));
+ facets.emplace_back((i == 0) ? Vec3crd(1, 0, ring.size()) : Vec3crd(id, 0, id - 1));
+ ++ id;
+ }
+
+ // General case: insert and form facets for each step, joining it to the ring below it.
+ for (size_t s = 2; s < steps - 1; s++) {
+ const double z = -rho + increment*(double)s*2.0*rho;
+ const double r = sqrt(abs(rho*rho - z*z));
+
+ for (size_t i = 0; i < ring.size(); i++) {
+ Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
+ vertices.emplace_back(Vec3d(b(0), b(1), z));
+ if (i == 0) {
+ // wrap around
+ facets.emplace_back(Vec3crd(id + ring.size() - 1 , id, id - 1));
+ facets.emplace_back(Vec3crd(id, id - ring.size(), id - 1));
+ } else {
+ facets.emplace_back(Vec3crd(id , id - ring.size(), (id - 1) - ring.size()));
+ facets.emplace_back(Vec3crd(id, id - 1 - ring.size() , id - 1));
+ }
+ id++;
+ }
+ }
+
+
+ // special case: last ring connects to 0,0,rho*2.0
+ // only form facets.
+ vertices.emplace_back(Vec3d(0.0, 0.0, rho));
+ for (size_t i = 0; i < ring.size(); i++) {
+ if (i == 0) {
+ // third vertex is on the other side of the ring.
+ facets.emplace_back(Vec3crd(id, id - ring.size(), id - 1));
+ } else {
+ facets.emplace_back(Vec3crd(id, id - ring.size() + i, id - ring.size() + (i - 1)));
+ }
+ }
+ id++;
+ TriangleMesh mesh(vertices, facets);
+ return mesh;
+}
+}
diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp
new file mode 100644
index 000000000..f6e0baea9
--- /dev/null
+++ b/src/libslic3r/TriangleMesh.hpp
@@ -0,0 +1,199 @@
+#ifndef slic3r_TriangleMesh_hpp_
+#define slic3r_TriangleMesh_hpp_
+
+#include "libslic3r.h"
+#include <admesh/stl.h>
+#include <functional>
+#include <vector>
+#include <boost/thread.hpp>
+#include "BoundingBox.hpp"
+#include "Line.hpp"
+#include "Point.hpp"
+#include "Polygon.hpp"
+#include "ExPolygon.hpp"
+
+namespace Slic3r {
+
+class TriangleMesh;
+class TriangleMeshSlicer;
+typedef std::vector<TriangleMesh*> TriangleMeshPtrs;
+
+class TriangleMesh
+{
+public:
+ TriangleMesh() : repaired(false) { stl_initialize(&this->stl); }
+ TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets);
+ TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; }
+ TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); }
+ ~TriangleMesh() { stl_close(&this->stl); }
+ TriangleMesh& operator=(const TriangleMesh &other);
+ TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; }
+ void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); }
+ void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); }
+ void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); }
+ void write_binary(const char* output_file) { stl_write_binary(&this->stl, output_file, ""); }
+ void repair();
+ float volume();
+ void check_topology();
+ bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == this->stl.stats.number_of_facets; }
+ void WriteOBJFile(char* output_file);
+ void scale(float factor);
+ void scale(const Vec3d &versor);
+ void translate(float x, float y, float z);
+ void rotate(float angle, const Axis &axis);
+ void rotate(float angle, const Vec3d& axis);
+ void rotate_x(float angle) { this->rotate(angle, X); }
+ void rotate_y(float angle) { this->rotate(angle, Y); }
+ void rotate_z(float angle) { this->rotate(angle, Z); }
+ void mirror(const Axis &axis);
+ void mirror_x() { this->mirror(X); }
+ void mirror_y() { this->mirror(Y); }
+ void mirror_z() { this->mirror(Z); }
+ void transform(const Transform3f& t);
+ void align_to_origin();
+ void rotate(double angle, Point* center);
+ TriangleMeshPtrs split() const;
+ void merge(const TriangleMesh &mesh);
+ ExPolygons horizontal_projection() const;
+ const float* first_vertex() const;
+ Polygon convex_hull();
+ BoundingBoxf3 bounding_box() const;
+ // Returns the bbox of this TriangleMesh transformed by the given transformation
+ BoundingBoxf3 transformed_bounding_box(const Transform3d& t) const;
+ // Returns the convex hull of this TriangleMesh
+ TriangleMesh convex_hull_3d() const;
+ void reset_repair_stats();
+ bool needed_repair() const;
+ size_t facets_count() const { return this->stl.stats.number_of_facets; }
+
+ // Returns true, if there are two and more connected patches in the mesh.
+ // Returns false, if one or zero connected patch is in the mesh.
+ bool has_multiple_patches() const;
+
+ // Count disconnected triangle patches.
+ size_t number_of_patches() const;
+
+ mutable stl_file stl;
+ bool repaired;
+
+private:
+ void require_shared_vertices();
+ friend class TriangleMeshSlicer;
+};
+
+enum FacetEdgeType {
+ // A general case, the cutting plane intersect a face at two different edges.
+ feGeneral,
+ // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane.
+ feTop,
+ // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
+ feBottom,
+ // All three vertices of a face are aligned with the cutting plane.
+ feHorizontal
+};
+
+class IntersectionReference
+{
+public:
+ IntersectionReference() : point_id(-1), edge_id(-1) {};
+ IntersectionReference(int point_id, int edge_id) : point_id(point_id), edge_id(edge_id) {}
+ // Where is this intersection point located? On mesh vertex or mesh edge?
+ // Only one of the following will be set, the other will remain set to -1.
+ // Index of the mesh vertex.
+ int point_id;
+ // Index of the mesh edge.
+ int edge_id;
+};
+
+class IntersectionPoint : public Point, public IntersectionReference
+{
+public:
+ IntersectionPoint() {};
+ IntersectionPoint(int point_id, int edge_id, const Point &pt) : IntersectionReference(point_id, edge_id), Point(pt) {}
+ IntersectionPoint(const IntersectionReference &ir, const Point &pt) : IntersectionReference(ir), Point(pt) {}
+ // Inherits coord_t x, y
+};
+
+class IntersectionLine : public Line
+{
+public:
+ IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feGeneral), flags(0) {}
+
+ bool skip() const { return (this->flags & SKIP) != 0; }
+ void set_skip() { this->flags |= SKIP; }
+
+ bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); }
+ void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; }
+
+ // Inherits Point a, b
+ // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1.
+ // Vertex indices of the line end points.
+ int a_id;
+ int b_id;
+ // Source mesh edges of the line end points.
+ int edge_a_id;
+ int edge_b_id;
+ // feGeneral, feTop, feBottom, feHorizontal
+ FacetEdgeType edge_type;
+ // Used by TriangleMeshSlicer::slice() to skip duplicate edges.
+ enum {
+ // Triangle edge added, because it has no neighbor.
+ EDGE0_NO_NEIGHBOR = 0x001,
+ EDGE1_NO_NEIGHBOR = 0x002,
+ EDGE2_NO_NEIGHBOR = 0x004,
+ // Triangle edge added, because it makes a fold with another horizontal edge.
+ EDGE0_FOLD = 0x010,
+ EDGE1_FOLD = 0x020,
+ EDGE2_FOLD = 0x040,
+ // The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds).
+ NO_SEED = 0x100,
+ SKIP = 0x200,
+ };
+ uint32_t flags;
+};
+typedef std::vector<IntersectionLine> IntersectionLines;
+typedef std::vector<IntersectionLine*> IntersectionLinePtrs;
+
+class TriangleMeshSlicer
+{
+public:
+ typedef std::function<void()> throw_on_cancel_callback_type;
+ TriangleMeshSlicer() : mesh(nullptr) {}
+ // Not quite nice, but the constructor and init() methods require non-const mesh pointer to be able to call mesh->require_shared_vertices()
+ TriangleMeshSlicer(TriangleMesh* mesh) { this->init(mesh, [](){}); }
+ void init(TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel);
+ void slice(const std::vector<float> &z, std::vector<Polygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const;
+ void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const;
+ enum FacetSliceType {
+ NoSlice = 0,
+ Slicing = 1,
+ Cutting = 2
+ };
+ FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx,
+ const float min_z, const float max_z, IntersectionLine *line_out) const;
+ void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const;
+
+private:
+ const TriangleMesh *mesh;
+ // Map from a facet to an edge index.
+ std::vector<int> facets_edges;
+ // Scaled copy of this->mesh->stl.v_shared
+ std::vector<stl_vertex> v_scaled_shared;
+
+ void _slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex, const std::vector<float> &z) const;
+ void make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const;
+ void make_expolygons(const Polygons &loops, ExPolygons* slices) const;
+ void make_expolygons_simple(std::vector<IntersectionLine> &lines, ExPolygons* slices) const;
+ void make_expolygons(std::vector<IntersectionLine> &lines, ExPolygons* slices) const;
+};
+
+TriangleMesh make_cube(double x, double y, double z);
+
+// Generate a TriangleMesh of a cylinder
+TriangleMesh make_cylinder(double r, double h, double fa=(2*PI/360));
+
+TriangleMesh make_sphere(double rho, double fa=(2*PI/360));
+
+}
+
+#endif
diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp
new file mode 100644
index 000000000..c90ad7650
--- /dev/null
+++ b/src/libslic3r/Utils.hpp
@@ -0,0 +1,100 @@
+#ifndef slic3r_Utils_hpp_
+#define slic3r_Utils_hpp_
+
+#include <locale>
+
+#include "libslic3r.h"
+
+namespace Slic3r {
+
+extern void set_logging_level(unsigned int level);
+extern void trace(unsigned int level, const char *message);
+extern void disable_multi_threading();
+
+// Set a path with GUI resource files.
+void set_var_dir(const std::string &path);
+// Return a full path to the GUI resource files.
+const std::string& var_dir();
+// Return a full resource path for a file_name.
+std::string var(const std::string &file_name);
+
+// Set a path with various static definition data (for example the initial config bundles).
+void set_resources_dir(const std::string &path);
+// Return a full path to the resources directory.
+const std::string& resources_dir();
+
+// Set a path with GUI localization files.
+void set_local_dir(const std::string &path);
+// Return a full path to the localization directory.
+const std::string& localization_dir();
+
+// Set a path with preset files.
+void set_data_dir(const std::string &path);
+// Return a full path to the GUI resource files.
+const std::string& data_dir();
+
+// A special type for strings encoded in the local Windows 8-bit code page.
+// This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded.
+typedef std::string local_encoded_string;
+
+// Convert an UTF-8 encoded string into local coding.
+// On Windows, the UTF-8 string is converted to a local 8-bit code page.
+// On OSX and Linux, this function does no conversion and returns a copy of the source string.
+extern local_encoded_string encode_path(const char *src);
+extern std::string decode_path(const char *src);
+extern std::string normalize_utf8_nfc(const char *src);
+
+// Safely rename a file even if the target exists.
+// On Windows, the file explorer (or anti-virus or whatever else) often locks the file
+// for a short while, so the file may not be movable. Retry while we see recoverable errors.
+extern int rename_file(const std::string &from, const std::string &to);
+
+// Copy a file, adjust the access attributes, so that the target is writable.
+extern int copy_file(const std::string &from, const std::string &to);
+
+// File path / name / extension splitting utilities, working with UTF-8,
+// to be published to Perl.
+namespace PerlUtils {
+ // Get a file name including the extension.
+ extern std::string path_to_filename(const char *src);
+ // Get a file name without the extension.
+ extern std::string path_to_stem(const char *src);
+ // Get just the extension.
+ extern std::string path_to_extension(const char *src);
+ // Get a directory without the trailing slash.
+ extern std::string path_to_parent_path(const char *src);
+};
+
+// Timestamp formatted for header_slic3r_generated().
+extern std::string timestamp_str();
+// Standard "generated by Slic3r version xxx timestamp xxx" header string,
+// to be placed at the top of Slic3r generated files.
+inline std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_FORK_NAME " " SLIC3R_VERSION " " ) + timestamp_str(); }
+
+// getpid platform wrapper
+extern unsigned get_current_pid();
+
+// Compute the next highest power of 2 of 32-bit v
+// http://graphics.stanford.edu/~seander/bithacks.html
+template<typename T>
+inline T next_highest_power_of_2(T v)
+{
+ if (v != 0)
+ -- v;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ if (sizeof(T) >= sizeof(uint16_t))
+ v |= v >> 8;
+ if (sizeof(T) >= sizeof(uint32_t))
+ v |= v >> 16;
+ if (sizeof(T) >= sizeof(uint64_t))
+ v |= v >> 32;
+ return ++ v;
+}
+
+extern std::string xml_escape(std::string text);
+
+} // namespace Slic3r
+
+#endif // slic3r_Utils_hpp_
diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h
new file mode 100644
index 000000000..5f2f3fba1
--- /dev/null
+++ b/src/libslic3r/libslic3r.h
@@ -0,0 +1,182 @@
+#ifndef _libslic3r_h_
+#define _libslic3r_h_
+
+// this needs to be included early for MSVC (listing it in Build.PL is not enough)
+#include <ostream>
+#include <iostream>
+#include <math.h>
+#include <queue>
+#include <sstream>
+#include <cstdio>
+#include <stdint.h>
+#include <stdarg.h>
+#include <vector>
+#include <boost/thread.hpp>
+
+#include "Technologies.hpp"
+
+#define SLIC3R_FORK_NAME "Slic3r Prusa Edition"
+#define SLIC3R_VERSION "1.41.0"
+#define SLIC3R_BUILD "UNKNOWN"
+
+typedef int32_t coord_t;
+typedef double coordf_t;
+
+//FIXME This epsilon value is used for many non-related purposes:
+// For a threshold of a squared Euclidean distance,
+// for a trheshold in a difference of radians,
+// for a threshold of a cross product of two non-normalized vectors etc.
+#define EPSILON 1e-4
+// Scaling factor for a conversion from coord_t to coordf_t: 10e-6
+// This scaling generates a following fixed point representation with for a 32bit integer:
+// 0..4294mm with 1nm resolution
+// int32_t fits an interval of (-2147.48mm, +2147.48mm)
+#define SCALING_FACTOR 0.000001
+// RESOLUTION, SCALED_RESOLUTION: Used as an error threshold for a Douglas-Peucker polyline simplification algorithm.
+#define RESOLUTION 0.0125
+#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR)
+#define PI 3.141592653589793238
+// When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam.
+#define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15
+// Maximum perimeter length for the loop to apply the small perimeter speed.
+#define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI
+#define INSET_OVERLAP_TOLERANCE 0.4
+// 3mm ring around the top / bottom / bridging areas.
+//FIXME This is quite a lot.
+#define EXTERNAL_INFILL_MARGIN 3.
+//FIXME Better to use an inline function with an explicit return type.
+//inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); }
+#define scale_(val) ((val) / SCALING_FACTOR)
+#define SCALED_EPSILON scale_(EPSILON)
+
+// Which C++ version is supported?
+// For example, could optimized functions with move semantics be used?
+#if __cplusplus==201402L
+ #define SLIC3R_CPPVER 14
+ #define STDMOVE(WHAT) std::move(WHAT)
+#elif __cplusplus==201103L
+ #define SLIC3R_CPPVER 11
+ #define STDMOVE(WHAT) std::move(WHAT)
+#else
+ #define SLIC3R_CPPVER 0
+ #define STDMOVE(WHAT) (WHAT)
+#endif
+
+#define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/"
+
+inline std::string debug_out_path(const char *name, ...)
+{
+ char buffer[2048];
+ va_list args;
+ va_start(args, name);
+ std::vsprintf(buffer, name, args);
+ va_end(args);
+ return std::string(SLIC3R_DEBUG_OUT_PATH_PREFIX) + std::string(buffer);
+}
+
+#ifdef _MSC_VER
+ // Visual Studio older than 2015 does not support the prinf type specifier %zu. Use %Iu instead.
+ #define PRINTF_ZU "%Iu"
+#else
+ #define PRINTF_ZU "%zu"
+#endif
+
+#ifndef UNUSED
+#define UNUSED(x) (void)(x)
+#endif /* UNUSED */
+
+// Detect whether the compiler supports C++11 noexcept exception specifications.
+#if defined(_MSC_VER) && _MSC_VER < 1900
+ #define noexcept throw()
+#endif
+
+// Write slices as SVG images into out directory during the 2D processing of the slices.
+// #define SLIC3R_DEBUG_SLICE_PROCESSING
+
+namespace Slic3r {
+
+template<typename T, typename Q>
+inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); }
+
+enum Axis { X=0, Y, Z, E, F, NUM_AXES };
+
+template <class T>
+inline void append_to(std::vector<T> &dst, const std::vector<T> &src)
+{
+ dst.insert(dst.end(), src.begin(), src.end());
+}
+
+template <typename T>
+inline void append(std::vector<T>& dest, const std::vector<T>& src)
+{
+ if (dest.empty())
+ dest = src;
+ else
+ dest.insert(dest.end(), src.begin(), src.end());
+}
+
+template <typename T>
+inline void append(std::vector<T>& dest, std::vector<T>&& src)
+{
+ if (dest.empty())
+ dest = std::move(src);
+ else
+ std::move(std::begin(src), std::end(src), std::back_inserter(dest));
+ src.clear();
+ src.shrink_to_fit();
+}
+
+// Casting an std::vector<> from one type to another type without warnings about a loss of accuracy.
+template<typename T_TO, typename T_FROM>
+std::vector<T_TO> cast(const std::vector<T_FROM> &src)
+{
+ std::vector<T_TO> dst;
+ dst.reserve(src.size());
+ for (const T_FROM &a : src)
+ dst.emplace_back((T_TO)a);
+ return dst;
+}
+
+template <typename T>
+inline void remove_nulls(std::vector<T*> &vec)
+{
+ vec.erase(
+ std::remove_if(vec.begin(), vec.end(), [](const T *ptr) { return ptr == nullptr; }),
+ vec.end());
+}
+
+template <typename T>
+inline void sort_remove_duplicates(std::vector<T> &vec)
+{
+ std::sort(vec.begin(), vec.end());
+ vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
+}
+
+// Older compilers do not provide a std::make_unique template. Provide a simple one.
+template<typename T, typename... Args>
+inline std::unique_ptr<T> make_unique(Args&&... args) {
+ return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+template<typename T>
+static inline T sqr(T x)
+{
+ return x * x;
+}
+
+template <typename T>
+static inline T clamp(const T low, const T high, const T value)
+{
+ return std::max(low, std::min(high, value));
+}
+
+template <typename T, typename Number>
+static inline T lerp(const T& a, const T& b, Number t)
+{
+ assert((t >= Number(-EPSILON)) && (t <= Number(1) + Number(EPSILON)));
+ return (Number(1) - t) * a + t * b;
+}
+
+} // namespace Slic3r
+
+#endif
diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp
new file mode 100644
index 000000000..4ff15175b
--- /dev/null
+++ b/src/libslic3r/utils.cpp
@@ -0,0 +1,345 @@
+#include "Utils.hpp"
+#include "I18N.hpp"
+
+#include <locale>
+#include <ctime>
+
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include <boost/log/core.hpp>
+#include <boost/log/trivial.hpp>
+#include <boost/log/expressions.hpp>
+
+#include <boost/locale.hpp>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/date_time/local_time/local_time.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/path.hpp>
+#include <boost/nowide/fstream.hpp>
+#include <boost/nowide/integration/filesystem.hpp>
+#include <boost/nowide/convert.hpp>
+#include <boost/nowide/cstdio.hpp>
+
+#include <tbb/task_scheduler_init.h>
+
+namespace Slic3r {
+
+static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error;
+
+void set_logging_level(unsigned int level)
+{
+ switch (level) {
+ // Report fatal errors only.
+ case 0: logSeverity = boost::log::trivial::fatal; break;
+ // Report fatal errors and errors.
+ case 1: logSeverity = boost::log::trivial::error; break;
+ // Report fatal errors, errors and warnings.
+ case 2: logSeverity = boost::log::trivial::warning; break;
+ // Report all errors, warnings and infos.
+ case 3: logSeverity = boost::log::trivial::info; break;
+ // Report all errors, warnings, infos and debugging.
+ case 4: logSeverity = boost::log::trivial::debug; break;
+ // Report everyting including fine level tracing information.
+ default: logSeverity = boost::log::trivial::trace; break;
+ }
+
+ boost::log::core::get()->set_filter
+ (
+ boost::log::trivial::severity >= logSeverity
+ );
+}
+
+// Force set_logging_level(<=error) after loading of the DLL.
+// Switch boost::filesystem to utf8.
+static struct RunOnInit {
+ RunOnInit() {
+ boost::nowide::nowide_filesystem();
+ set_logging_level(1);
+ }
+} g_RunOnInit;
+
+void trace(unsigned int level, const char *message)
+{
+ boost::log::trivial::severity_level severity = boost::log::trivial::trace;
+ switch (level) {
+ // Report fatal errors only.
+ case 0: severity = boost::log::trivial::fatal; break;
+ // Report fatal errors and errors.
+ case 1: severity = boost::log::trivial::error; break;
+ // Report fatal errors, errors and warnings.
+ case 2: severity = boost::log::trivial::warning; break;
+ // Report all errors, warnings and infos.
+ case 3: severity = boost::log::trivial::info; break;
+ // Report all errors, warnings, infos and debugging.
+ case 4: severity = boost::log::trivial::debug; break;
+ // Report everyting including fine level tracing information.
+ default: severity = boost::log::trivial::trace; break;
+ }
+
+ BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(),\
+ (::boost::log::keywords::severity = severity)) << message;
+}
+
+void disable_multi_threading()
+{
+ // Disable parallelization so the Shiny profiler works
+ static tbb::task_scheduler_init *tbb_init = nullptr;
+ if (tbb_init == nullptr)
+ tbb_init = new tbb::task_scheduler_init(1);
+}
+
+static std::string g_var_dir;
+
+void set_var_dir(const std::string &dir)
+{
+ g_var_dir = dir;
+}
+
+const std::string& var_dir()
+{
+ return g_var_dir;
+}
+
+std::string var(const std::string &file_name)
+{
+ auto file = (boost::filesystem::path(g_var_dir) / file_name).make_preferred();
+ return file.string();
+}
+
+static std::string g_resources_dir;
+
+void set_resources_dir(const std::string &dir)
+{
+ g_resources_dir = dir;
+}
+
+const std::string& resources_dir()
+{
+ return g_resources_dir;
+}
+
+static std::string g_local_dir;
+
+void set_local_dir(const std::string &dir)
+{
+ g_local_dir = dir;
+}
+
+const std::string& localization_dir()
+{
+ return g_local_dir;
+}
+
+// Translate function callback, to call wxWidgets translate function to convert non-localized UTF8 string to a localized one.
+Slic3r::I18N::translate_fn_type Slic3r::I18N::translate_fn = nullptr;
+
+static std::string g_data_dir;
+
+void set_data_dir(const std::string &dir)
+{
+ g_data_dir = dir;
+}
+
+const std::string& data_dir()
+{
+ return g_data_dir;
+}
+
+
+// borrowed from LVVM lib/Support/Windows/Path.inc
+int rename_file(const std::string &from, const std::string &to)
+{
+ int ec = 0;
+
+#ifdef _WIN32
+
+ // Convert to utf-16.
+ std::wstring wide_from = boost::nowide::widen(from);
+ std::wstring wide_to = boost::nowide::widen(to);
+
+ // Retry while we see recoverable errors.
+ // System scanners (eg. indexer) might open the source file when it is written
+ // and closed.
+ bool TryReplace = true;
+
+ // This loop may take more than 2000 x 1ms to finish.
+ for (int i = 0; i < 2000; ++ i) {
+ if (i > 0)
+ // Sleep 1ms
+ ::Sleep(1);
+ if (TryReplace) {
+ // Try ReplaceFile first, as it is able to associate a new data stream
+ // with the destination even if the destination file is currently open.
+ if (::ReplaceFileW(wide_to.data(), wide_from.data(), NULL, 0, NULL, NULL))
+ return 0;
+ DWORD ReplaceError = ::GetLastError();
+ ec = -1; // ReplaceError
+ // If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or
+ // ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW().
+ if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT ||
+ ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) {
+ TryReplace = false;
+ continue;
+ }
+ // If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry
+ // using ReplaceFileW().
+ if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED)
+ continue;
+ // We get ERROR_FILE_NOT_FOUND if the destination file is missing.
+ // MoveFileEx can handle this case.
+ if (ReplaceError != ERROR_ACCESS_DENIED && ReplaceError != ERROR_FILE_NOT_FOUND && ReplaceError != ERROR_SHARING_VIOLATION)
+ break;
+ }
+ if (::MoveFileExW(wide_from.c_str(), wide_to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING))
+ return 0;
+ DWORD MoveError = ::GetLastError();
+ ec = -1; // MoveError
+ if (MoveError != ERROR_ACCESS_DENIED && MoveError != ERROR_SHARING_VIOLATION)
+ break;
+ }
+
+#else
+
+ boost::nowide::remove(to.c_str());
+ ec = boost::nowide::rename(from.c_str(), to.c_str());
+
+#endif
+
+ return ec;
+}
+
+int copy_file(const std::string &from, const std::string &to)
+{
+ const boost::filesystem::path source(from);
+ const boost::filesystem::path target(to);
+ static const auto perms = boost::filesystem::owner_read | boost::filesystem::owner_write | boost::filesystem::group_read | boost::filesystem::others_read; // aka 644
+
+ // Make sure the file has correct permission both before and after we copy over it.
+ try {
+ if (boost::filesystem::exists(target))
+ boost::filesystem::permissions(target, perms);
+ boost::filesystem::copy_file(source, target, boost::filesystem::copy_option::overwrite_if_exists);
+ boost::filesystem::permissions(target, perms);
+ } catch (std::exception & /* ex */) {
+ return -1;
+ }
+ return 0;
+}
+
+} // namespace Slic3r
+
+#ifdef WIN32
+ #ifndef NOMINMAX
+ # define NOMINMAX
+ #endif
+ #include <windows.h>
+#endif /* WIN32 */
+
+namespace Slic3r {
+
+// Encode an UTF-8 string to the local code page.
+std::string encode_path(const char *src)
+{
+#ifdef WIN32
+ // Convert the source utf8 encoded string to a wide string.
+ std::wstring wstr_src = boost::nowide::widen(src);
+ if (wstr_src.length() == 0)
+ return std::string();
+ // Convert a wide string to a local code page.
+ int size_needed = ::WideCharToMultiByte(0, 0, wstr_src.data(), (int)wstr_src.size(), nullptr, 0, nullptr, nullptr);
+ std::string str_dst(size_needed, 0);
+ ::WideCharToMultiByte(0, 0, wstr_src.data(), (int)wstr_src.size(), const_cast<char*>(str_dst.data()), size_needed, nullptr, nullptr);
+ return str_dst;
+#else /* WIN32 */
+ return src;
+#endif /* WIN32 */
+}
+
+// Encode an 8-bit string from a local code page to UTF-8.
+std::string decode_path(const char *src)
+{
+#ifdef WIN32
+ int len = int(strlen(src));
+ if (len == 0)
+ return std::string();
+ // Convert the string encoded using the local code page to a wide string.
+ int size_needed = ::MultiByteToWideChar(0, 0, src, len, nullptr, 0);
+ std::wstring wstr_dst(size_needed, 0);
+ ::MultiByteToWideChar(0, 0, src, len, const_cast<wchar_t*>(wstr_dst.data()), size_needed);
+ // Convert a wide string to utf8.
+ return boost::nowide::narrow(wstr_dst.c_str());
+#else /* WIN32 */
+ return src;
+#endif /* WIN32 */
+}
+
+std::string normalize_utf8_nfc(const char *src)
+{
+ static std::locale locale_utf8(boost::locale::generator().generate(""));
+ return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8);
+}
+
+namespace PerlUtils {
+ // Get a file name including the extension.
+ std::string path_to_filename(const char *src) { return boost::filesystem::path(src).filename().string(); }
+ // Get a file name without the extension.
+ std::string path_to_stem(const char *src) { return boost::filesystem::path(src).stem().string(); }
+ // Get just the extension.
+ std::string path_to_extension(const char *src) { return boost::filesystem::path(src).extension().string(); }
+ // Get a directory without the trailing slash.
+ std::string path_to_parent_path(const char *src) { return boost::filesystem::path(src).parent_path().string(); }
+};
+
+std::string timestamp_str()
+{
+ const auto now = boost::posix_time::second_clock::local_time();
+ char buf[2048];
+ sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d",
+ // Local date in an ANSII format.
+ int(now.date().year()), int(now.date().month()), int(now.date().day()),
+ int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds()));
+ return buf;
+}
+
+unsigned get_current_pid()
+{
+#ifdef WIN32
+ return GetCurrentProcessId();
+#else
+ return ::getpid();
+#endif
+}
+
+std::string xml_escape(std::string text)
+{
+ std::string::size_type pos = 0;
+ for (;;)
+ {
+ pos = text.find_first_of("\"\'&<>", pos);
+ if (pos == std::string::npos)
+ break;
+
+ std::string replacement;
+ switch (text[pos])
+ {
+ case '\"': replacement = "&quot;"; break;
+ case '\'': replacement = "&apos;"; break;
+ case '&': replacement = "&amp;"; break;
+ case '<': replacement = "&lt;"; break;
+ case '>': replacement = "&gt;"; break;
+ default: break;
+ }
+
+ text.replace(pos, 1, replacement);
+ pos += replacement.size();
+ }
+
+ return text;
+}
+
+}; // namespace Slic3r