diff options
author | bubnikv <bubnikv@gmail.com> | 2018-09-12 12:59:02 +0300 |
---|---|---|
committer | bubnikv <bubnikv@gmail.com> | 2018-09-12 12:59:02 +0300 |
commit | 0235f1a8211cfecf75ac0a585295bc1378747972 (patch) | |
tree | 9c1d06a43a691d9691c7eff6f21646b0dcc8cc74 /xs/src/libslic3r | |
parent | 41ce69f327a2fe3324dc47756b64a6fdb001cfa3 (diff) | |
parent | a97df55592f524eb73bd7d04ef8b7c1b3525d27d (diff) |
Merged with dev
Diffstat (limited to 'xs/src/libslic3r')
99 files changed, 9844 insertions, 4892 deletions
diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp index 91ba88d84..d3cca7ff2 100644 --- a/xs/src/libslic3r/BoundingBox.cpp +++ b/xs/src/libslic3r/BoundingBox.cpp @@ -2,12 +2,14 @@ #include <algorithm> #include <assert.h> +#include <Eigen/Dense> + namespace Slic3r { template BoundingBoxBase<Point>::BoundingBoxBase(const std::vector<Point> &points); -template BoundingBoxBase<Pointf>::BoundingBoxBase(const std::vector<Pointf> &points); +template BoundingBoxBase<Vec2d>::BoundingBoxBase(const std::vector<Vec2d> &points); -template BoundingBox3Base<Pointf3>::BoundingBox3Base(const std::vector<Pointf3> &points); +template BoundingBox3Base<Vec3d>::BoundingBox3Base(const std::vector<Vec3d> &points); BoundingBox::BoundingBox(const Lines &lines) { @@ -20,23 +22,21 @@ BoundingBox::BoundingBox(const Lines &lines) *this = BoundingBox(points); } -void -BoundingBox::polygon(Polygon* polygon) const +void BoundingBox::polygon(Polygon* polygon) const { polygon->points.clear(); polygon->points.resize(4); - polygon->points[0].x = this->min.x; - polygon->points[0].y = this->min.y; - polygon->points[1].x = this->max.x; - polygon->points[1].y = this->min.y; - polygon->points[2].x = this->max.x; - polygon->points[2].y = this->max.y; - polygon->points[3].x = this->min.x; - polygon->points[3].y = this->max.y; + 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 BoundingBox::polygon() const { Polygon p; this->polygon(&p); @@ -48,8 +48,8 @@ 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.x, this->max.y).rotated(angle)); - out.merge(Point(this->max.x, this->min.y).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; } @@ -58,36 +58,35 @@ BoundingBox BoundingBox::rotated(double angle, const Point ¢er) const BoundingBox out; out.merge(this->min.rotated(angle, center)); out.merge(this->max.rotated(angle, center)); - out.merge(Point(this->min.x, this->max.y).rotated(angle, center)); - out.merge(Point(this->max.x, this->min.y).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.scale(factor); - this->max.scale(factor); + this->min *= factor; + this->max *= factor; } template void BoundingBoxBase<Point>::scale(double factor); -template void BoundingBoxBase<Pointf>::scale(double factor); -template void BoundingBoxBase<Pointf3>::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.x = std::min(point.x, this->min.x); - this->min.y = std::min(point.y, this->min.y); - this->max.x = std::max(point.x, this->max.x); - this->max.y = std::max(point.y, this->max.y); + this->min = this->min.cwiseMin(point); + this->max = this->max.cwiseMax(point); } else { - this->min = this->max = point; + this->min = point; + this->max = point; this->defined = true; } } template void BoundingBoxBase<Point>::merge(const Point &point); -template void BoundingBoxBase<Pointf>::merge(const Pointf &point); +template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point); template <class PointClass> void BoundingBoxBase<PointClass>::merge(const std::vector<PointClass> &points) @@ -95,18 +94,16 @@ BoundingBoxBase<PointClass>::merge(const std::vector<PointClass> &points) this->merge(BoundingBoxBase(points)); } template void BoundingBoxBase<Point>::merge(const Points &points); -template void BoundingBoxBase<Pointf>::merge(const Pointfs &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.x >= bb.max.x || bb.min.y >= bb.max.y); + assert(bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1)); if (bb.defined) { if (this->defined) { - this->min.x = std::min(bb.min.x, this->min.x); - this->min.y = std::min(bb.min.y, this->min.y); - this->max.x = std::max(bb.max.x, this->max.x); - this->max.y = std::max(bb.max.y, this->max.y); + this->min = this->min.cwiseMin(bb.min); + this->max = this->max.cwiseMax(bb.max); } else { this->min = bb.min; this->max = bb.max; @@ -115,112 +112,121 @@ BoundingBoxBase<PointClass>::merge(const BoundingBoxBase<PointClass> &bb) } } template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb); -template void BoundingBoxBase<Pointf>::merge(const BoundingBoxBase<Pointf> &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.z = std::min(point.z, this->min.z); - this->max.z = std::max(point.z, this->max.z); + this->min = this->min.cwiseMin(point); + this->max = this->max.cwiseMax(point); + } else { + this->min = point; + this->max = point; + this->defined = true; } - BoundingBoxBase<PointClass>::merge(point); } -template void BoundingBox3Base<Pointf3>::merge(const Pointf3 &point); +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<Pointf3>::merge(const Pointf3s &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.x >= bb.max.x || bb.min.y >= bb.max.y || bb.min.z >= bb.max.z); + 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.z = std::min(bb.min.z, this->min.z); - this->max.z = std::max(bb.max.z, this->max.z); + 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; } - BoundingBoxBase<PointClass>::merge(bb); } } -template void BoundingBox3Base<Pointf3>::merge(const BoundingBox3Base<Pointf3> &bb); +template void BoundingBox3Base<Vec3d>::merge(const BoundingBox3Base<Vec3d> &bb); template <class PointClass> PointClass BoundingBoxBase<PointClass>::size() const { - return PointClass(this->max.x - this->min.x, this->max.y - this->min.y); + return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1)); } template Point BoundingBoxBase<Point>::size() const; -template Pointf BoundingBoxBase<Pointf>::size() const; +template Vec2d BoundingBoxBase<Vec2d>::size() const; template <class PointClass> PointClass BoundingBox3Base<PointClass>::size() const { - return PointClass(this->max.x - this->min.x, this->max.y - this->min.y, this->max.z - this->min.z); + return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1), this->max(2) - this->min(2)); } -template Pointf3 BoundingBox3Base<Pointf3>::size() const; +template Vec3d BoundingBox3Base<Vec3d>::size() const; template <class PointClass> double BoundingBoxBase<PointClass>::radius() const { assert(this->defined); - double x = this->max.x - this->min.x; - double y = this->max.y - this->min.y; + 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<Pointf>::radius() const; +template double BoundingBoxBase<Vec2d>::radius() const; template <class PointClass> double BoundingBox3Base<PointClass>::radius() const { - double x = this->max.x - this->min.x; - double y = this->max.y - this->min.y; - double z = this->max.z - this->min.z; + 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<Pointf3>::radius() const; +template double BoundingBox3Base<Vec3d>::radius() const; template <class PointClass> void BoundingBoxBase<PointClass>::offset(coordf_t delta) { - this->min.translate(-delta, -delta); - this->max.translate(delta, delta); + PointClass v(delta, delta); + this->min -= v; + this->max += v; } template void BoundingBoxBase<Point>::offset(coordf_t delta); -template void BoundingBoxBase<Pointf>::offset(coordf_t delta); +template void BoundingBoxBase<Vec2d>::offset(coordf_t delta); template <class PointClass> void BoundingBox3Base<PointClass>::offset(coordf_t delta) { - this->min.translate(-delta, -delta, -delta); - this->max.translate(delta, delta, delta); + PointClass v(delta, delta, delta); + this->min -= v; + this->max += v; } -template void BoundingBox3Base<Pointf3>::offset(coordf_t delta); +template void BoundingBox3Base<Vec3d>::offset(coordf_t delta); template <class PointClass> PointClass BoundingBoxBase<PointClass>::center() const { - return PointClass( - (this->max.x + this->min.x)/2, - (this->max.y + this->min.y)/2 - ); + return (this->min + this->max) / 2; } template Point BoundingBoxBase<Point>::center() const; -template Pointf BoundingBoxBase<Pointf>::center() const; +template Vec2d BoundingBoxBase<Vec2d>::center() const; template <class PointClass> PointClass BoundingBox3Base<PointClass>::center() const { - return PointClass( - (this->max.x + this->min.x)/2, - (this->max.y + this->min.y)/2, - (this->max.z + this->min.z)/2 - ); + 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 Pointf3 BoundingBox3Base<Pointf3>::center() const; +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. @@ -238,9 +244,40 @@ static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) void BoundingBox::align_to_grid(const coord_t cell_size) { if (this->defined) { - min.x = _align_to_grid(min.x, cell_size); - min.y = _align_to_grid(min.y, cell_size); + 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/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp index 92a2bd451..db56c765c 100644 --- a/xs/src/libslic3r/BoundingBox.hpp +++ b/xs/src/libslic3r/BoundingBox.hpp @@ -7,11 +7,6 @@ namespace Slic3r { -typedef Point Size; -typedef Point3 Size3; -typedef Pointf Sizef; -typedef Pointf3 Sizef3; - template <class PointClass> class BoundingBoxBase { @@ -20,25 +15,22 @@ public: PointClass max; bool defined; - BoundingBoxBase() : defined(false) {}; + BoundingBoxBase() : defined(false), min(PointClass::Zero()), max(PointClass::Zero()) {} BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : - min(pmin), max(pmax), defined(pmin.x < pmax.x && pmin.y < pmax.y) {} - BoundingBoxBase(const std::vector<PointClass>& points) + 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()) CONFESS("Empty point set supplied to BoundingBoxBase constructor"); typename std::vector<PointClass>::const_iterator it = points.begin(); - this->min.x = this->max.x = it->x; - this->min.y = this->max.y = it->y; - for (++it; it != points.end(); ++it) - { - this->min.x = std::min(it->x, this->min.x); - this->min.y = std::min(it->y, this->min.y); - this->max.x = std::max(it->x, this->max.x); - this->max.y = std::max(it->y, this->max.y); + 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.x < this->max.x) && (this->min.y < this->max.y); + 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); @@ -46,17 +38,17 @@ public: void scale(double factor); PointClass size() const; double radius() const; - void translate(coordf_t x, coordf_t y) { assert(this->defined); this->min.translate(x, y); this->max.translate(x, y); } - void translate(const Pointf &pos) { this->translate(pos.x, pos.y); } + 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.x >= this->min.x && point.x <= this->max.x - && point.y >= this->min.y && point.y <= this->max.y; + 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.x < other.min.x || this->min.x > other.max.x || - this->max.y < other.min.y || this->min.y > other.max.y); + 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); } @@ -69,39 +61,42 @@ public: BoundingBox3Base() : BoundingBoxBase<PointClass>() {}; BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : BoundingBoxBase<PointClass>(pmin, pmax) - { if (pmin.z >= pmax.z) BoundingBoxBase<PointClass>::defined = false; } + { if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; } BoundingBox3Base(const std::vector<PointClass>& points) - : BoundingBoxBase<PointClass>(points) { if (points.empty()) CONFESS("Empty point set supplied to BoundingBox3Base constructor"); - typename std::vector<PointClass>::const_iterator it = points.begin(); - this->min.z = this->max.z = it->z; - for (++it; it != points.end(); ++it) - { - this->min.z = std::min(it->z, this->min.z); - this->max.z = std::max(it->z, this->max.z); + 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.z < this->max.z); + 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) { this->min.translate(x, y, z); this->max.translate(x, y, z); } - void translate(const Pointf3 &pos) { this->translate(pos.x, pos.y, pos.z); } + 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.z >= this->min.z && point.z <= this->max.z; + 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> @@ -125,40 +120,42 @@ public: friend BoundingBox get_extents_rotated(const Points &points, double angle); }; -class BoundingBox3 : public BoundingBox3Base<Point3> +class BoundingBox3 : public BoundingBox3Base<Vec3crd> { public: - BoundingBox3() : BoundingBox3Base<Point3>() {}; - BoundingBox3(const Point3 &pmin, const Point3 &pmax) : BoundingBox3Base<Point3>(pmin, pmax) {}; - BoundingBox3(const Points3& points) : BoundingBox3Base<Point3>(points) {}; + 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<Pointf> +class BoundingBoxf : public BoundingBoxBase<Vec2d> { public: - BoundingBoxf() : BoundingBoxBase<Pointf>() {}; - BoundingBoxf(const Pointf &pmin, const Pointf &pmax) : BoundingBoxBase<Pointf>(pmin, pmax) {}; - BoundingBoxf(const std::vector<Pointf> &points) : BoundingBoxBase<Pointf>(points) {}; + 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<Pointf3> +class BoundingBoxf3 : public BoundingBox3Base<Vec3d> { public: - BoundingBoxf3() : BoundingBox3Base<Pointf3>() {}; - BoundingBoxf3(const Pointf3 &pmin, const Pointf3 &pmax) : BoundingBox3Base<Pointf3>(pmin, pmax) {}; - BoundingBoxf3(const std::vector<Pointf3> &points) : BoundingBox3Base<Pointf3>(points) {}; + 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.x >= bb.max.x || bb.min.y >= bb.max.y; + 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.x >= bb.max.x || bb.min.y >= bb.max.y || bb.min.z >= bb.max.z; + return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2); } } // namespace Slic3r diff --git a/xs/src/libslic3r/BridgeDetector.cpp b/xs/src/libslic3r/BridgeDetector.cpp index a5272683f..ccc3505ce 100644 --- a/xs/src/libslic3r/BridgeDetector.cpp +++ b/xs/src/libslic3r/BridgeDetector.cpp @@ -102,16 +102,16 @@ bool BridgeDetector::detect_angle(double bridge_direction_override) // 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.y - bbox.min.y + this->spacing) / this->spacing); + 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.y + this->spacing / 2; y <= bbox.max.y; y += this->spacing) - for (coord_t y = bbox.min.y; y <= bbox.max.y; y += this->spacing) +// 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.x - s * y), (coord_t)round(c * y + s * bbox.min.x)), - Point((coord_t)round(c * bbox.max.x - s * y), (coord_t)round(c * y + s * bbox.max.x)))); + 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; @@ -182,9 +182,9 @@ std::vector<double> BridgeDetector::bridge_direction_candidates() const /* we also test angles of each open supporting edge (this finds the optimal angle for C-shaped supports) */ - for (Polylines::const_iterator edge = this->_edges.begin(); edge != this->_edges.end(); ++edge) - if (! edge->first_point().coincides_with(edge->last_point())) - angles.push_back(Line(edge->first_point(), edge->last_point()).direction()); + 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 @@ -282,10 +282,12 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const 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 (Lines::const_iterator line = unsupported_lines.begin(); line != unsupported_lines.end(); ++line) { - if (!Slic3r::Geometry::directions_parallel(line->direction(), angle)) - unsupported->push_back(*line); - } + 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); + } } /* diff --git a/xs/src/libslic3r/ClipperUtils.cpp b/xs/src/libslic3r/ClipperUtils.cpp index 86123b23c..f00e908ce 100644 --- a/xs/src/libslic3r/ClipperUtils.cpp +++ b/xs/src/libslic3r/ClipperUtils.cpp @@ -171,7 +171,7 @@ 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).x, (*pit).y )); + retval.push_back(ClipperLib::IntPoint( (*pit)(0), (*pit)(1) )); return retval; } @@ -181,7 +181,7 @@ 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).x, (*pit).y )); + output.push_back(ClipperLib::IntPoint( (*pit)(0), (*pit)(1) )); return output; } @@ -458,6 +458,19 @@ offset2_ex(const Polygons &polygons, const float delta1, const float delta2, 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, @@ -582,26 +595,26 @@ Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, co 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().coincides_with(retval[j].points.front())) { + 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().coincides_with(retval[j].points.back())) { + } 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().coincides_with(retval[j].points.front())) { + } 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().coincides_with(retval[j].points.back())) { + } 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(); @@ -621,8 +634,8 @@ _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons // convert Lines to Polylines Polylines polylines; polylines.reserve(subject.size()); - for (Lines::const_iterator line = subject.begin(); line != subject.end(); ++line) - polylines.push_back(*line); + for (const Line &line : subject) + polylines.emplace_back(Polyline(line.a, line.b)); // perform operation polylines = _clipper_pl(clipType, polylines, clip, safety_offset_); @@ -650,8 +663,7 @@ union_pt_chained(const Polygons &subject, bool safety_offset_) return retval; } -void -traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* 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? */ @@ -677,8 +689,7 @@ traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval) } } -Polygons -simplify_polygons(const Polygons &subject, bool preserve_collinear) +Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear) { // convert into Clipper polygons ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); @@ -698,13 +709,11 @@ simplify_polygons(const Polygons &subject, bool preserve_collinear) return ClipperPaths_to_Slic3rPolygons(output); } -ExPolygons -simplify_polygons_ex(const Polygons &subject, bool preserve_collinear) +ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear) { - if (!preserve_collinear) { - return union_ex(simplify_polygons(subject, preserve_collinear)); - } - + if (! preserve_collinear) + return union_ex(simplify_polygons(subject, false)); + // convert into Clipper polygons ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); diff --git a/xs/src/libslic3r/ClipperUtils.hpp b/xs/src/libslic3r/ClipperUtils.hpp index eb83c31a3..b065cfe07 100644 --- a/xs/src/libslic3r/ClipperUtils.hpp +++ b/xs/src/libslic3r/ClipperUtils.hpp @@ -80,6 +80,9 @@ Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, 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); diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index 1a0b8fb4a..e7442d773 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -20,6 +20,7 @@ namespace Slic3r { +// Escape \n, \r and backslash std::string escape_string_cstyle(const std::string &str) { // Allocate a buffer twice the input string length, @@ -28,9 +29,15 @@ std::string escape_string_cstyle(const std::string &str) char *outptr = out.data(); for (size_t i = 0; i < str.size(); ++ i) { char c = str[i]; - if (c == '\n' || c == '\r') { + if (c == '\r') { + (*outptr ++) = '\\'; + (*outptr ++) = 'r'; + } else if (c == '\n') { (*outptr ++) = '\\'; (*outptr ++) = 'n'; + } else if (c == '\\') { + (*outptr ++) = '\\'; + (*outptr ++) = '\\'; } else (*outptr ++) = c; } @@ -69,7 +76,10 @@ std::string escape_strings_cstyle(const std::vector<std::string> &strs) if (c == '\\' || c == '"') { (*outptr ++) = '\\'; (*outptr ++) = c; - } else if (c == '\n' || c == '\r') { + } else if (c == '\r') { + (*outptr ++) = '\\'; + (*outptr ++) = 'r'; + } else if (c == '\n') { (*outptr ++) = '\\'; (*outptr ++) = 'n'; } else @@ -84,6 +94,7 @@ std::string escape_strings_cstyle(const std::vector<std::string> &strs) 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); @@ -94,8 +105,12 @@ bool unescape_string_cstyle(const std::string &str, std::string &str_out) if (++ i == str.size()) return false; c = str[i]; - if (c == 'n') + if (c == 'r') + (*outptr ++) = '\r'; + else if (c == 'n') (*outptr ++) = '\n'; + else + (*outptr ++) = c; } else (*outptr ++) = c; } @@ -134,7 +149,9 @@ bool unescape_strings_cstyle(const std::string &str, std::vector<std::string> &o if (++ i == str.size()) return false; c = str[i]; - if (c == 'n') + if (c == 'r') + c = '\r'; + else if (c == 'n') c = '\n'; } buf.push_back(c); @@ -185,10 +202,13 @@ void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys // This is only possible if other is of DynamicConfig type. if (ignore_nonexistent) continue; - throw UnknownOptionException(); + throw UnknownOptionException(opt_key); } const ConfigOption *other_opt = other.option(opt_key); - if (other_opt != nullptr) + 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); } } @@ -206,6 +226,56 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const 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); @@ -232,7 +302,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con // Try to deserialize the option by its name. const ConfigDef *def = this->def(); if (def == nullptr) - throw NoDefinitionException(); + 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. @@ -248,7 +318,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con break; } if (optdef == nullptr) - throw UnknownOptionException(); + throw UnknownOptionException(opt_key); } if (! optdef->shortcut.empty()) { @@ -278,7 +348,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const // Get option definition. const ConfigDef *def = this->def(); if (def == nullptr) - throw NoDefinitionException(); + 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. @@ -468,7 +538,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // Try to create a new ConfigOption. const ConfigDef *def = this->def(); if (def == nullptr) - throw NoDefinitionException(); + 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); @@ -522,7 +592,7 @@ void StaticConfig::set_defaults() t_config_option_keys StaticConfig::keys() const { t_config_option_keys keys; - assert(this->def != nullptr); + 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); diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 3dccedbf0..e3cd14335 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -49,9 +49,9 @@ enum ConfigOptionType { coPercents = coPercent + coVectorType, // a fraction or an absolute value coFloatOrPercent = 5, - // single 2d point. Currently not used. + // single 2d point (Point2f). Currently not used. coPoint = 6, - // vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets. + // 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, @@ -291,6 +291,8 @@ 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(); } @@ -620,11 +622,11 @@ public: } }; -class ConfigOptionPoint : public ConfigOptionSingle<Pointf> +class ConfigOptionPoint : public ConfigOptionSingle<Vec2d> { public: - ConfigOptionPoint() : ConfigOptionSingle<Pointf>(Pointf(0,0)) {} - explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle<Pointf>(value) {} + 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(); } @@ -635,30 +637,28 @@ public: std::string serialize() const override { std::ostringstream ss; - ss << this->value.x; + ss << this->value(0); ss << ","; - ss << this->value.y; + ss << this->value(1); return ss.str(); } bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); - std::istringstream iss(str); - iss >> this->value.x; - iss.ignore(std::numeric_limits<std::streamsize>::max(), ','); - iss.ignore(std::numeric_limits<std::streamsize>::max(), 'x'); - iss >> this->value.y; - return true; + 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<Pointf> +class ConfigOptionPoints : public ConfigOptionVector<Vec2d> { public: - ConfigOptionPoints() : ConfigOptionVector<Pointf>() {} - explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector<Pointf>(n, value) {} - explicit ConfigOptionPoints(std::initializer_list<Pointf> il) : ConfigOptionVector<Pointf>(std::move(il)) {} + 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(); } @@ -671,9 +671,9 @@ public: 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->x; + ss << (*it)(0); ss << "x"; - ss << it->y; + ss << (*it)(1); } return ss.str(); } @@ -696,13 +696,13 @@ public: std::istringstream is(str); std::string point_str; while (std::getline(is, point_str, ',')) { - Pointf point; + 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.x; + std::istringstream(coord_str) >> point(0); if (std::getline(iss, coord_str, 'x')) { - std::istringstream(coord_str) >> point.y; + std::istringstream(coord_str) >> point(1); } } this->values.push_back(point); @@ -821,12 +821,7 @@ public: bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); - 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; - this->value = static_cast<T>(it->second); - return true; + return from_string(str, this->value); } static bool has(T value) @@ -838,7 +833,7 @@ public: } // Map from an enum name to an enum integer value. - static t_config_enum_names& get_enum_names() + static const t_config_enum_names& get_enum_names() { static t_config_enum_names names; if (names.empty()) { @@ -855,7 +850,17 @@ public: return names; } // Map from an enum name to an enum integer value. - static t_config_enum_values& get_enum_values(); + 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. @@ -900,7 +905,7 @@ 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. - ConfigOption *default_value = nullptr; + const ConfigOption *default_value = nullptr; // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, @@ -958,7 +963,7 @@ public: std::vector<std::string> enum_labels; // For enums (when type == coEnum). Maps enum_values to enums. // Initialized by ConfigOptionEnum<xxx>::get_enum_values() - t_config_enum_values *enum_keys_map = nullptr; + 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) @@ -1030,7 +1035,7 @@ public: 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()); +// assert(opt == nullptr || opt->type() == TYPE::static_type()); return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast<TYPE*>(opt); } template<typename TYPE> @@ -1046,6 +1051,10 @@ public: 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. @@ -1232,17 +1241,22 @@ protected: }; /// Specialization of std::exception to indicate that an unknown config option has been encountered. -class UnknownOptionException : public std::exception -{ +class UnknownOptionException : public std::runtime_error { public: - const char* what() const noexcept override { return "Unknown config option"; } + 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::exception +class NoDefinitionException : public std::runtime_error { public: - const char* what() const noexcept override { return "No config definition"; } + NoDefinitionException() : + std::runtime_error("No definition exception") {} + NoDefinitionException(const std::string &opt_key) : + std::runtime_error(std::string("No definition exception: ") + opt_key) {} }; } diff --git a/xs/src/libslic3r/EdgeGrid.cpp b/xs/src/libslic3r/EdgeGrid.cpp index 752243407..50f424e6d 100644 --- a/xs/src/libslic3r/EdgeGrid.cpp +++ b/xs/src/libslic3r/EdgeGrid.cpp @@ -117,15 +117,15 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) m_bbox.merge(pts[j]); } coord_t eps = 16; - m_bbox.min.x -= eps; - m_bbox.min.y -= eps; - m_bbox.max.x += eps; - m_bbox.max.y += eps; + 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.x - m_bbox.min.x + m_resolution - 1) / m_resolution; - m_rows = (m_bbox.max.y - m_bbox.min.y + m_resolution - 1) / m_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. @@ -135,15 +135,15 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) // End points of the line segment. Slic3r::Point p1(pts[j]); Slic3r::Point p2 = pts[(j + 1 == pts.size()) ? 0 : j + 1]; - p1.x -= m_bbox.min.x; - p1.y -= m_bbox.min.y; - p2.x -= m_bbox.min.x; - p2.y -= m_bbox.min.y; + 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.x / m_resolution; - coord_t iy = p1.y / m_resolution; - coord_t ixb = p2.x / m_resolution; - coord_t iyb = p2.y / m_resolution; + 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); @@ -154,13 +154,13 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) // Both ends fall into the same cell. continue; // Raster the centeral part of the line. - coord_t dx = std::abs(p2.x - p1.x); - coord_t dy = std::abs(p2.y - p1.y); - if (p1.x < p2.x) { - int64_t ex = int64_t((ix + 1)*m_resolution - p1.x) * int64_t(dy); - if (p1.y < p2.y) { + 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.y) * int64_t(dx); + int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); do { assert(ix <= ixb && iy <= iyb); if (ex < ey) { @@ -185,7 +185,7 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) } else { // x positive, y non positive - int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); + int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); do { assert(ix <= ixb && iy >= iyb); if (ex <= ey) { @@ -203,10 +203,10 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) } } else { - int64_t ex = int64_t(p1.x - ix*m_resolution) * int64_t(dy); - if (p1.y < p2.y) { + 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.y) * int64_t(dx); + int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); do { assert(ix >= ixb && iy <= iyb); if (ex < ey) { @@ -225,7 +225,7 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) } else { // x non positive, y non positive - int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); + int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); do { assert(ix >= ixb && iy >= iyb); if (ex < ey) { @@ -279,15 +279,15 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) // End points of the line segment. Slic3r::Point p1(pts[j]); Slic3r::Point p2 = pts[(j + 1 == pts.size()) ? 0 : j + 1]; - p1.x -= m_bbox.min.x; - p1.y -= m_bbox.min.y; - p2.x -= m_bbox.min.x; - p2.y -= m_bbox.min.y; + 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.x / m_resolution; - coord_t iy = p1.y / m_resolution; - coord_t ixb = p2.x / m_resolution; - coord_t iyb = p2.y / m_resolution; + 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); @@ -298,13 +298,13 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) // Both ends fall into the same cell. continue; // Raster the centeral part of the line. - coord_t dx = std::abs(p2.x - p1.x); - coord_t dy = std::abs(p2.y - p1.y); - if (p1.x < p2.x) { - int64_t ex = int64_t((ix + 1)*m_resolution - p1.x) * int64_t(dy); - if (p1.y < p2.y) { + 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.y) * int64_t(dx); + int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); do { assert(ix <= ixb && iy <= iyb); if (ex < ey) { @@ -329,7 +329,7 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) } else { // x positive, y non positive - int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); + int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); do { assert(ix <= ixb && iy >= iyb); if (ex <= ey) { @@ -347,10 +347,10 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) } } else { - int64_t ex = int64_t(p1.x - ix*m_resolution) * int64_t(dy); - if (p1.y < p2.y) { + 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.y) * int64_t(dx); + int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); do { assert(ix >= ixb && iy <= iyb); if (ex < ey) { @@ -369,7 +369,7 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) } else { // x non positive, y non positive - int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); + int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); do { assert(ix >= ixb && iy >= iyb); if (ex < ey) { @@ -429,15 +429,15 @@ bool EdgeGrid::Grid::intersect(const MultiPoint &polyline, bool closed) Point p1 = p1src; Point p2 = p2src; // Discretize the line segment p1, p2. - p1.x -= m_bbox.min.x; - p1.y -= m_bbox.min.y; - p2.x -= m_bbox.min.x; - p2.y -= m_bbox.min.y; + 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.x, m_resolution); - coord_t iy = div_floor(p1.y, m_resolution); - coord_t ixb = div_floor(p2.x, m_resolution); - coord_t iyb = div_floor(p2.y, m_resolution); + 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); @@ -449,12 +449,12 @@ bool EdgeGrid::Grid::intersect(const MultiPoint &polyline, bool closed) // Both ends fall into the same cell. continue; // Raster the centeral part of the line. - coord_t dx = std::abs(p2.x - p1.x); - coord_t dy = std::abs(p2.y - p1.y); - if (p1.x < p2.x) { - int64_t ex = int64_t((ix + 1)*m_resolution - p1.x) * int64_t(dy); - if (p1.y < p2.y) { - int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); + 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) { @@ -479,7 +479,7 @@ bool EdgeGrid::Grid::intersect(const MultiPoint &polyline, bool closed) } while (ix != ixb || iy != iyb); } else { - int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); + int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); do { assert(ix <= ixb && iy >= iyb); if (ex <= ey) { @@ -498,9 +498,9 @@ bool EdgeGrid::Grid::intersect(const MultiPoint &polyline, bool closed) } } else { - int64_t ex = int64_t(p1.x - ix*m_resolution) * int64_t(dy); - if (p1.y < p2.y) { - int64_t ey = int64_t((iy + 1)*m_resolution - p1.y) * int64_t(dx); + 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) { @@ -519,7 +519,7 @@ bool EdgeGrid::Grid::intersect(const MultiPoint &polyline, bool closed) } while (ix != ixb || iy != iyb); } else { - int64_t ey = int64_t(p1.y - iy*m_resolution) * int64_t(dx); + int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); do { assert(ix >= ixb && iy >= iyb); if (ex < ey) { @@ -556,8 +556,8 @@ bool EdgeGrid::Grid::line_cell_intersect(const Point &p1a, const Point &p2a, con { BoundingBox bbox(p1a, p1a); bbox.merge(p2a); - int64_t va_x = p2a.x - p1a.x; - int64_t va_y = p2a.y - p1a.y; + 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. @@ -576,21 +576,21 @@ bool EdgeGrid::Grid::line_cell_intersect(const Point &p1a, const Point &p2a, con if (! bbox.overlap(bbox2)) continue; // Now intersect the two line segments using exact arithmetics. - int64_t w1_x = p1b.x - p1a.x; - int64_t w1_y = p1b.y - p1a.y; - int64_t w2_x = p2b.x - p1a.x; - int64_t w2_y = p2b.y - p1a.y; + 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.x - p1b.x; - w1_y = p1a.y - p1b.y; - w2_x = p2a.x - p1b.x; - w2_y = p2a.y - p1b.y; - int64_t vb_x = p2b.x - p1b.x; - int64_t vb_y = p2b.y - p1b.y; + 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) @@ -607,14 +607,14 @@ bool EdgeGrid::Grid::line_cell_intersect(const Point &p1a, const Point &p2a, con bool EdgeGrid::Grid::inside(const Point &pt_src) { Point p = pt_src; - p.x -= m_bbox.min.x; - p.y -= m_bbox.min.y; + p(0) -= m_bbox.min(0); + p(1) -= m_bbox.min(1); // Get the cell of the point. - if (p.x < 0 || p.y < 0) + if (p(0) < 0 || p(1) < 0) return false; - coord_t ix = p.x / m_resolution; - coord_t iy = p.y / m_resolution; - if (ix >= m_cols || iy >= m_rows) + 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; @@ -634,21 +634,21 @@ bool EdgeGrid::Grid::inside(const Point &pt_src) idx2 = 0; const Point &p1 = contour[idx1]; const Point &p2 = contour[idx2]; - if (p1.y < p2.y) { - if (p.y < p1.y || p.y > p2.y) + 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.y != p2.y) { - assert(p1.y > p2.y); - if (p.y < p2.y || p.y > p1.y) + } else if (p1(1) != p2(1)) { + assert(p1(1) > p2(1)); + if (p(1) < p2(1) || p(1) > p1(1)) continue; } else { - assert(p1.y == p2.y); - if (p1.y == p.y) { - if (p.x >= p1.x && p.x <= p2.x) + 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. @@ -767,9 +767,9 @@ void EdgeGrid::Grid::calculate_sdf() 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 = p1.vector_to(p2); + const Slic3r::Point v_seg = p2 - p1; // l2 of v_seg - const int64_t l2_seg = int64_t(v_seg.x) * int64_t(v_seg.x) + int64_t(v_seg.y) * int64_t(v_seg.y); + 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; @@ -780,28 +780,28 @@ void EdgeGrid::Grid::calculate_sdf() 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.x + corner_c * m_resolution, m_bbox.min.y + corner_r * m_resolution); - Slic3r::Point v_pt = p1.vector_to(pt); + 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.x) * int64_t(v_pt.x) + int64_t(v_seg.y) * int64_t(v_pt.y); + 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.x) * int64_t(v_pt.x) + int64_t(v_pt.y) * int64_t(v_pt.y)); + 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 = p0.vector_to(p1); - int64_t t2_pt = int64_t(v_seg_prev.x) * int64_t(v_pt.x) + int64_t(v_seg_prev.y) * int64_t(v_pt.y); + 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.x) * int64_t(v_seg.y) - int64_t(v_seg_prev.y) * int64_t(v_seg.x); + 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.x); - l[1] = std::abs(v_pt.y); + 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)); @@ -816,7 +816,7 @@ void EdgeGrid::Grid::calculate_sdf() } else { // Closest to the segment. assert(t_pt >= 0 && t_pt <= l2_seg); - int64_t d_seg = int64_t(v_seg.y) * int64_t(v_pt.x) - int64_t(v_seg.x) * int64_t(v_pt.y); + 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) { @@ -824,8 +824,8 @@ void EdgeGrid::Grid::calculate_sdf() // 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.y) * linv); - l[1] = std::abs(float(v_seg.x) * linv); + 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)); @@ -1059,8 +1059,8 @@ void EdgeGrid::Grid::calculate_sdf() float EdgeGrid::Grid::signed_distance_bilinear(const Point &pt) const { - coord_t x = pt.x - m_bbox.min.x; - coord_t y = pt.y - m_bbox.min.y; + 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; @@ -1124,39 +1124,39 @@ float EdgeGrid::Grid::signed_distance_bilinear(const Point &pt) const 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.x - m_bbox.min.x, pt.y - m_bbox.min.y); + 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.x += search_radius; - bbox.max.y += search_radius; - if (bbox.max.x < 0 || bbox.max.y < 0) + bbox.max(0) += search_radius; + bbox.max(1) += search_radius; + if (bbox.max(0) < 0 || bbox.max(1) < 0) return false; - bbox.max.x /= m_resolution; - bbox.max.y /= m_resolution; - if (bbox.max.x >= m_cols) - bbox.max.x = m_cols - 1; - if (bbox.max.y >= m_rows) - bbox.max.y = m_rows - 1; + 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.x -= search_radius; - bbox.min.y -= search_radius; - if (bbox.min.x < 0) - bbox.min.x = 0; - if (bbox.min.y < 0) - bbox.min.y = 0; - bbox.min.x /= m_resolution; - bbox.min.y /= m_resolution; + 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.x > bbox.max.x || - bbox.min.y > bbox.max.y) + 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.y; r <= bbox.max.y; ++ r) { - for (int c = bbox.min.x; c <= bbox.max.x; ++ c) { + 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]; @@ -1164,25 +1164,25 @@ bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radiu // 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 = p1.vector_to(p2); - Slic3r::Point v_pt = p1.vector_to(pt); + 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.x) * int64_t(v_pt.x) + int64_t(v_seg.y) * int64_t(v_pt.y); + 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.x) * int64_t(v_seg.x) + int64_t(v_seg.y) * int64_t(v_seg.y); + 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.x) * int64_t(v_pt.x) + int64_t(v_pt.y) * int64_t(v_pt.y)); + 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 = p0.vector_to(p1); - int64_t t2_pt = int64_t(v_seg_prev.x) * int64_t(v_pt.x) + int64_t(v_seg_prev.y) * int64_t(v_pt.y); + 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.x) * int64_t(v_seg.y) - int64_t(v_seg_prev.y) * int64_t(v_seg.x); + 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; @@ -1195,7 +1195,7 @@ bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radiu } else { // Closest to the segment. assert(t_pt >= 0 && t_pt <= l2_seg); - int64_t d_seg = int64_t(v_seg.y) * int64_t(v_pt.x) - int64_t(v_seg.x) * int64_t(v_pt.y); + 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) { @@ -1307,7 +1307,7 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const 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.x) * int64_t(v2.y) - int64_t(v2.x) * int64_t(v1.y); + 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; @@ -1328,10 +1328,10 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const Polygon &poly = out[i]; for (size_t j = 0; j < poly.points.size(); ++ j) { Point &p = poly.points[j]; - p.x *= m_resolution; - p.y *= m_resolution; - p.x += m_bbox.min.x; - p.y += m_bbox.min.y; + 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. @@ -1341,11 +1341,11 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const 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.x != 0 && v.y != 0) { + if (v(0) != 0 && v(1) != 0) { // This is a corner point. Copy it to the output contour. Point p = poly.points[j]; - p.y += (v.x < 0) ? - offset : offset; - p.x += (v.y > 0) ? - offset : offset; + p(1) += (v(0) < 0) ? - offset : offset; + p(0) += (v(1) > 0) ? - offset : offset; pts.push_back(p); } } @@ -1357,8 +1357,8 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const #if 0 void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path) { - unsigned int w = (bbox.max.x - bbox.min.x + resolution - 1) / resolution; - unsigned int h = (bbox.max.y - bbox.min.y + resolution - 1) / resolution; + 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); @@ -1371,7 +1371,7 @@ void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coo 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.x, r * resolution + bbox.min.y); + Point pt(c * resolution + bbox.min(0), r * resolution + bbox.min(1)); coordf_t min_dist; bool on_segment = true; #if 0 @@ -1409,8 +1409,8 @@ void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coo pxl[2] = 0; } - float gridx = float(pt.x - grid.bbox().min.x) / float(grid.resolution()); - float gridy = float(pt.y - grid.bbox().min.y) / float(grid.resolution()); + 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)); diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index 1d4bac50b..4294fe543 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -34,54 +34,43 @@ ExPolygon::operator Polylines() const return to_polylines(*this); } -void -ExPolygon::scale(double factor) +void ExPolygon::scale(double factor) { contour.scale(factor); - for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { - (*it).scale(factor); - } + for (Polygon &hole : holes) + hole.scale(factor); } -void -ExPolygon::translate(double x, double y) +void ExPolygon::translate(double x, double y) { contour.translate(x, y); - for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { - (*it).translate(x, y); - } + for (Polygon &hole : holes) + hole.translate(x, y); } -void -ExPolygon::rotate(double angle) +void ExPolygon::rotate(double angle) { contour.rotate(angle); - for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { - (*it).rotate(angle); - } + for (Polygon &hole : holes) + hole.rotate(angle); } -void -ExPolygon::rotate(double angle, const Point ¢er) +void ExPolygon::rotate(double angle, const Point ¢er) { contour.rotate(angle, center); - for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) { - (*it).rotate(angle, center); - } + for (Polygon &hole : holes) + hole.rotate(angle, center); } -double -ExPolygon::area() const +double ExPolygon::area() const { double a = this->contour.area(); - for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { - a -= -(*it).area(); // holes have negative area - } + for (const Polygon &hole : holes) + a -= - hole.area(); // holes have negative area return a; } -bool -ExPolygon::is_valid() const +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) { @@ -90,20 +79,17 @@ ExPolygon::is_valid() const return true; } -bool -ExPolygon::contains(const Line &line) const +bool ExPolygon::contains(const Line &line) const { - return this->contains((Polyline)line); + return this->contains(Polyline(line.a, line.b)); } -bool -ExPolygon::contains(const Polyline &polyline) const +bool ExPolygon::contains(const Polyline &polyline) const { return diff_pl((Polylines)polyline, *this).empty(); } -bool -ExPolygon::contains(const Polylines &polylines) const +bool ExPolygon::contains(const Polylines &polylines) const { #if 0 BoundingBox bbox = get_extents(polylines); @@ -120,8 +106,7 @@ ExPolygon::contains(const Polylines &polylines) const return pl_out.empty(); } -bool -ExPolygon::contains(const Point &point) const +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) { @@ -131,8 +116,7 @@ ExPolygon::contains(const Point &point) const } // inclusive version of contains() that also checks whether point is on boundaries -bool -ExPolygon::contains_b(const Point &point) const +bool ExPolygon::contains_b(const Point &point) const { return this->contains(point) || this->has_boundary_point(point); } @@ -168,52 +152,42 @@ ExPolygon::overlaps(const ExPolygon &other) const return ! other.contour.points.empty() && this->contains_b(other.contour.points.front()); } -void -ExPolygon::simplify_p(double tolerance, Polygons* polygons) const +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 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.push_back(p); + pp.emplace_back(std::move(p)); } - // holes - for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { - Polygon p = *it; + 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.push_back(p); + pp.emplace_back(std::move(p)); } - pp = simplify_polygons(pp); - return pp; + return simplify_polygons(pp); } -ExPolygons -ExPolygon::simplify(double tolerance) const +ExPolygons ExPolygon::simplify(double tolerance) const { - Polygons pp = this->simplify_p(tolerance); - return union_ex(pp); + return union_ex(this->simplify_p(tolerance)); } -void -ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const +void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const { - ExPolygons ep = this->simplify(tolerance); - expolygons->insert(expolygons->end(), ep.begin(), ep.end()); + append(*expolygons, this->simplify(tolerance)); } void @@ -253,25 +227,24 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl Point new_front = polyline.points.front(); Point new_back = polyline.points.back(); if (polyline.endpoints.first && !this->has_boundary_point(new_front)) { - Line line(polyline.points.front(), polyline.points[1]); - + 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) line.b = line.midpoint(); - - line.extend_start(max_width); - (void)this->contour.intersection(line, &new_front); + 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)) { - Line line( - *(polyline.points.end() - 2), - polyline.points.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) line.a = line.midpoint(); - line.extend_end(max_width); - - (void)this->contour.intersection(line, &new_back); + 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; @@ -304,14 +277,14 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl // find another polyline starting here for (size_t j = i+1; j < pp.size(); ++j) { ThickPolyline& other = pp[j]; - if (polyline.last_point().coincides_with(other.last_point())) { + if (polyline.last_point() == other.last_point()) { other.reverse(); - } else if (polyline.first_point().coincides_with(other.last_point())) { + } else if (polyline.first_point() == other.last_point()) { polyline.reverse(); other.reverse(); - } else if (polyline.first_point().coincides_with(other.first_point())) { + } else if (polyline.first_point() == other.first_point()) { polyline.reverse(); - } else if (!polyline.last_point().coincides_with(other.first_point())) { + } else if (polyline.last_point() != other.first_point()) { continue; } @@ -371,7 +344,7 @@ ExPolygon::get_trapezoids2(Polygons* polygons) const 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); + xx.push_back(p->x()); std::sort(xx.begin(), xx.end()); // find trapezoids by looping from first to next-to-last coordinate @@ -382,14 +355,14 @@ ExPolygon::get_trapezoids2(Polygons* polygons) const // build rectangle Polygon poly; poly.points.resize(4); - poly[0].x = *x; - poly[0].y = bb.min.y; - poly[1].x = next_x; - poly[1].y = bb.min.y; - poly[2].x = next_x; - poly[2].y = bb.max.y; - poly[3].x = *x; - poly[3].y = bb.max.y; + 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 @@ -435,10 +408,11 @@ ExPolygon::triangulate_pp(Polygons* polygons) const TPPLPoly p; p.Init(int(ex->contour.points.size())); //printf(PRINTF_ZU "\n0\n", ex->contour.points.size()); - for (Points::const_iterator point = ex->contour.points.begin(); point != ex->contour.points.end(); ++point) { - p[ point-ex->contour.points.begin() ].x = point->x; - p[ point-ex->contour.points.begin() ].y = point->y; - //printf("%ld %ld\n", point->x, point->y); + 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); @@ -449,10 +423,11 @@ ExPolygon::triangulate_pp(Polygons* polygons) const TPPLPoly p; p.Init(hole->points.size()); //printf(PRINTF_ZU "\n1\n", hole->points.size()); - for (Points::const_iterator point = hole->points.begin(); point != hole->points.end(); ++point) { - p[ point-hole->points.begin() ].x = point->x; - p[ point-hole->points.begin() ].y = point->y; - //printf("%ld %ld\n", point->x, point->y); + 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); @@ -470,8 +445,8 @@ ExPolygon::triangulate_pp(Polygons* polygons) const Polygon p; p.points.resize(num_points); for (long i = 0; i < num_points; ++i) { - p.points[i].x = coord_t((*poly)[i].x); - p.points[i].y = coord_t((*poly)[i].y); + p.points[i](0) = coord_t((*poly)[i].x); + p.points[i](1) = coord_t((*poly)[i].y); } polygons->push_back(p); } @@ -487,19 +462,17 @@ ExPolygon::triangulate_p2t(Polygons* polygons) const // contour std::vector<p2t::Point*> ContourPoints; - for (Points::const_iterator point = ex->contour.points.begin(); point != ex->contour.points.end(); ++point) { + for (const Point &pt : ex->contour.points) // We should delete each p2t::Point object - ContourPoints.push_back(new p2t::Point(point->x, point->y)); - } + 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 (Points::const_iterator point = hole->points.begin(); point != hole->points.end(); ++point) { + for (const Point &pt : hole->points) // will be destructed in SweepContext::~SweepContext - points.push_back(new p2t::Point(point->x, point->y)); - } + points.push_back(new p2t::Point(pt(0), pt(1))); cdt.AddHole(points); } @@ -516,9 +489,8 @@ ExPolygon::triangulate_p2t(Polygons* polygons) const polygons->push_back(p); } - for(std::vector<p2t::Point*>::iterator it = ContourPoints.begin(); it != ContourPoints.end(); ++it) { - delete *it; - } + for (p2t::Point *ptr : ContourPoints) + delete ptr; } } @@ -533,17 +505,6 @@ ExPolygon::lines() const return lines; } -std::string -ExPolygon::dump_perl() const -{ - std::ostringstream ret; - ret << "[" << this->contour.dump_perl(); - for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) - ret << "," << h->dump_perl(); - ret << "]"; - return ret.str(); -} - BoundingBox get_extents(const ExPolygon &expolygon) { return get_extents(expolygon.contour); diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index f4782ba55..4833ee49e 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -63,7 +63,6 @@ public: void triangulate_pp(Polygons* polygons) const; void triangulate_p2t(Polygons* polygons) const; Lines lines() const; - std::string dump_perl() const; }; // Count a nuber of polygons stored inside the vector of expolygons. diff --git a/xs/src/libslic3r/ExtrusionEntity.cpp b/xs/src/libslic3r/ExtrusionEntity.cpp index c6f67b169..92f0d3669 100644 --- a/xs/src/libslic3r/ExtrusionEntity.cpp +++ b/xs/src/libslic3r/ExtrusionEntity.cpp @@ -220,7 +220,7 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang) 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 = point.distance_to(p_tmp); + double dist = (p_tmp - point).cast<double>().norm(); if (dist < min) { p = p_tmp; min = dist; diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 9d34aced8..2bd03600c 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -92,6 +92,7 @@ public: virtual double min_mm3_per_mm() const = 0; virtual Polyline as_polyline() const = 0; virtual double length() const = 0; + virtual double total_volume() const = 0; }; typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr; @@ -148,6 +149,7 @@ public: // 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; } + virtual double total_volume() const { return mm3_per_mm * unscale<double>(length()); } private: void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; @@ -194,6 +196,7 @@ public: // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const; Polyline as_polyline() const; + virtual double total_volume() const { 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. @@ -241,6 +244,7 @@ public: // 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(); } + virtual double total_volume() const { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; } private: ExtrusionLoopRole m_loop_role; diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.cpp b/xs/src/libslic3r/ExtrusionEntityCollection.cpp index 4513139e2..7a086bcbf 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.cpp @@ -125,6 +125,7 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt continue; } } + ExtrusionEntity* entity = (*it)->clone(); my_paths.push_back(entity); if (orig_indices != NULL) indices_map[entity] = it - this->entities.begin(); diff --git a/xs/src/libslic3r/ExtrusionEntityCollection.hpp b/xs/src/libslic3r/ExtrusionEntityCollection.hpp index 03bd2ba97..3b34145f8 100644 --- a/xs/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/xs/src/libslic3r/ExtrusionEntityCollection.hpp @@ -50,10 +50,15 @@ public: src.clear(); } } - void append(const ExtrusionPaths &paths) { + void append(const ExtrusionPaths &paths) { this->entities.reserve(this->entities.size() + paths.size()); - for (ExtrusionPaths::const_iterator path = paths.begin(); path != paths.end(); ++path) - this->entities.push_back(path->clone()); + 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); @@ -79,6 +84,7 @@ public: void flatten(ExtrusionEntityCollection* retval) const; ExtrusionEntityCollection flatten() const; double min_mm3_per_mm() const; + virtual double total_volume() const {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 { diff --git a/xs/src/libslic3r/ExtrusionSimulator.cpp b/xs/src/libslic3r/ExtrusionSimulator.cpp index daecbc0d1..fcb2fe825 100644 --- a/xs/src/libslic3r/ExtrusionSimulator.cpp +++ b/xs/src/libslic3r/ExtrusionSimulator.cpp @@ -893,24 +893,24 @@ ExtrusionSimulator::~ExtrusionSimulator() 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) + 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); + 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) { + 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; + 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; + pimpl->image_data[r * image_size.x() * 4 + c * 4 + 3] = 255; } } // printf("Allocating image data, set\n"); @@ -922,8 +922,8 @@ void ExtrusionSimulator::set_viewport(const BoundingBox &viewport) 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]); + 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); } } @@ -943,8 +943,8 @@ 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); + 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"); } @@ -955,17 +955,17 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const // 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 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); + // 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); + // printf("point %d,%d\n", it->x+shift.x(), it->y+shift.y); ExtrusionPoint ept; - ept.center = V2f(float(it->x+shift.x-bbox.min.x) * scalex, float(it->y+shift.y-bbox.min.y) * scaley); + 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); @@ -989,9 +989,9 @@ void ExtrusionSimulator::evaluate_accumulator(ExtrusionSimulationType simulation 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) { + 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) { @@ -1009,9 +1009,9 @@ void ExtrusionSimulator::evaluate_accumulator(ExtrusionSimulationType simulation } // 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) { + 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 diff --git a/xs/src/libslic3r/FileParserError.hpp b/xs/src/libslic3r/FileParserError.hpp new file mode 100644 index 000000000..3f560fa4f --- /dev/null +++ b/xs/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/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp index aa9774784..6a37e4369 100644 --- a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -54,9 +54,9 @@ static std::vector<coordf_t> perpendPoints(const coordf_t offset, const size_t b // components that are outside these limits are set to the limits. static inline void trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY) { - for (Pointfs::iterator it = pts.begin(); it != pts.end(); ++ it) { - it->x = clamp(minX, maxX, it->x); - it->y = clamp(minY, maxY, it->y); + for (Vec2d &pt : pts) { + pt(0) = clamp(minX, maxX, pt(0)); + pt(1) = clamp(minY, maxY, pt(1)); } } @@ -66,7 +66,7 @@ static inline Pointfs zip(const std::vector<coordf_t> &x, const std::vector<coor Pointfs out; out.reserve(x.size()); for (size_t i = 0; i < x.size(); ++ i) - out.push_back(Pointf(x[i], y[i])); + out.push_back(Vec2d(x[i], y[i])); return out; } @@ -128,7 +128,7 @@ static Polylines makeGrid(coord_t z, coord_t gridSize, size_t gridWidth, size_t result.push_back(Polyline()); Polyline &polyline = result.back(); for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it) - polyline.points.push_back(Point(coord_t(it->x * scaleFactor), coord_t(it->y * scaleFactor))); + polyline.points.push_back(Point(coord_t((*it)(0) * scaleFactor), coord_t((*it)(1) * scaleFactor))); } return result; } @@ -153,13 +153,13 @@ void Fill3DHoneycomb::_fill_surface_single( Polylines polylines = makeGrid( scale_(this->z), distance, - ceil(bb.size().x / distance) + 1, - ceil(bb.size().y / distance) + 1, + 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.x, bb.min.y); + it->translate(bb.min(0), bb.min(1)); // clip pattern to boundaries polylines = intersection_pl(polylines, (Polygons)expolygon); @@ -187,7 +187,7 @@ void Fill3DHoneycomb::_fill_surface_single( const Point &last_point = pts_end.back(); // TODO: we should also check that both points are on a fill_boundary to avoid // connecting paths on the boundaries of internal regions - if (first_point.distance_to(last_point) <= 1.5 * distance && + 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()); diff --git a/xs/src/libslic3r/Fill/FillBase.hpp b/xs/src/libslic3r/Fill/FillBase.hpp index 62d18e518..b67d14339 100644 --- a/xs/src/libslic3r/Fill/FillBase.hpp +++ b/xs/src/libslic3r/Fill/FillBase.hpp @@ -121,11 +121,11 @@ public: return aligned; } static Point _align_to_grid(Point coord, Point spacing) - { return Point(_align_to_grid(coord.x, spacing.x), _align_to_grid(coord.y, spacing.y)); } + { 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.x, spacing.x, base.x), _align_to_grid(coord.y, spacing.y, base.y)); } + { return Point(_align_to_grid(coord(0), spacing(0), base(0)), _align_to_grid(coord(1), spacing(1), base(1))); } }; } // namespace Slic3r diff --git a/xs/src/libslic3r/Fill/FillConcentric.cpp b/xs/src/libslic3r/Fill/FillConcentric.cpp index b21ad2799..8a3a7ea89 100644 --- a/xs/src/libslic3r/Fill/FillConcentric.cpp +++ b/xs/src/libslic3r/Fill/FillConcentric.cpp @@ -20,8 +20,8 @@ void FillConcentric::_fill_surface_single( coord_t distance = coord_t(min_spacing / params.density); if (params.density > 0.9999f && !params.dont_adjust) { - distance = this->_adjust_solid_spacing(bounding_box.size().x, distance); - this->spacing = unscale(distance); + distance = this->_adjust_solid_spacing(bounding_box.size()(0), distance); + this->spacing = unscale<double>(distance); } Polygons loops = (Polygons)expolygon; diff --git a/xs/src/libslic3r/Fill/FillGyroid.cpp b/xs/src/libslic3r/Fill/FillGyroid.cpp index e63ce0bfd..d6bf03ce6 100644 --- a/xs/src/libslic3r/Fill/FillGyroid.cpp +++ b/xs/src/libslic3r/Fill/FillGyroid.cpp @@ -9,74 +9,117 @@ namespace Slic3r { -static inline Polyline make_wave_vertical( - double width, double height, double x0, - double segmentSize, double scaleFactor, - double z_cos, double z_sin, bool flip) +static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip) { - Polyline polyline; - polyline.points.emplace_back(Point(coord_t(clamp(0., width, x0) * scaleFactor), 0)); - double phase_offset_sin = (z_cos < 0 ? M_PI : 0) + M_PI; - double phase_offset_cos = (z_cos < 0 ? M_PI : 0) + M_PI + (flip ? M_PI : 0.); - for (double y = 0.; y < height + segmentSize; y += segmentSize) { - y = std::min(y, height); - double a = sin(y + phase_offset_sin); + 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(y + phase_offset_cos); + double res = z_sin * cos(x + phase_offset + (flip ? M_PI : 0.)); double r = sqrt(sqr(a) + sqr(b)); - double x = clamp(0., width, asin(a/r) + asin(res/r) + M_PI + x0); - polyline.points.emplace_back(convert_to<Point>(Pointf(x, y) * scaleFactor)); + 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); } - if (flip) - std::reverse(polyline.points.begin(), polyline.points.end()); - return polyline; } -static inline Polyline make_wave_horizontal( - double width, double height, double y0, - double segmentSize, double scaleFactor, - double z_cos, double z_sin, bool flip) +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; - polyline.points.emplace_back(Point(0, coord_t(clamp(0., height, y0) * scaleFactor))); - double phase_offset_sin = (z_sin < 0 ? M_PI : 0) + (flip ? 0 : M_PI); - double phase_offset_cos = z_sin < 0 ? M_PI : 0.; - for (double x=0.; x < width + segmentSize; x += segmentSize) { - x = std::min(x, width); - double a = cos(x + phase_offset_cos); - double b = - z_sin; - double res = z_cos * sin(x + phase_offset_sin); - double r = sqrt(sqr(a) + sqr(b)); - double y = clamp(0., height, asin(a/r) + asin(res/r) + 0.5 * M_PI + y0); - polyline.points.emplace_back(convert_to<Point>(Pointf(x, y) * scaleFactor)); + 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>()); } - if (flip) - std::reverse(polyline.points.begin(), polyline.points.end()); + return polyline; } -static Polylines make_gyroid_waves(double gridZ, double density, double layer_width, double width, double height) +static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip) { - double scaleFactor = scale_(layer_width) / density; - double segmentSize = 0.5 * density; + 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 ? - double z = gridZ / scaleFactor; - double z_sin = sin(z); - double z_cos = cos(z); - Polylines result; - if (abs(z_sin) <= abs(z_cos)) { - // Vertical wave - double x0 = M_PI * (int)((- 0.5 * M_PI) / M_PI - 1.); - bool flip = ((int)(x0 / M_PI + 1.) & 1) != 0; - for (; x0 < width - 0.5 * M_PI; x0 += M_PI, flip = ! flip) - result.emplace_back(make_wave_vertical(width, height, x0, segmentSize, scaleFactor, z_cos, z_sin, flip)); - } else { - // Horizontal wave - bool flip = true; - for (double y0 = 0.; y0 < width; y0 += M_PI, flip = !flip) - result.emplace_back(make_wave_horizontal(width, height, y0, segmentSize, scaleFactor, z_cos, z_sin, flip)); + 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; } @@ -87,24 +130,27 @@ void FillGyroid::_fill_surface_single( ExPolygon &expolygon, Polylines &polylines_out) { - // no rotation is supported for this infill pattern + // no rotation is supported for this infill pattern (yet) BoundingBox bb = expolygon.contour.bounding_box(); - coord_t distance = coord_t(scale_(this->spacing) / (params.density*this->scaling)); + // 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))); - + 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), - params.density*this->scaling, + density_adjusted, this->spacing, - ceil(bb.size().x / distance) + 1., - ceil(bb.size().y / distance) + 1.); + ceil(bb.size()(0) / distance) + 1., + ceil(bb.size()(1) / distance) + 1.); // move pattern in place for (Polyline &polyline : polylines) - polyline.translate(bb.min.x, bb.min.y); + polyline.translate(bb.min(0), bb.min(1)); // clip pattern to boundaries polylines = intersection_pl(polylines, (Polygons)expolygon); @@ -133,7 +179,7 @@ void FillGyroid::_fill_surface_single( // 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 (first_point.distance_to(last_point) <= 5 * distance && + 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()); diff --git a/xs/src/libslic3r/Fill/FillGyroid.hpp b/xs/src/libslic3r/Fill/FillGyroid.hpp index 758652a5c..17924b5ab 100644 --- a/xs/src/libslic3r/Fill/FillGyroid.hpp +++ b/xs/src/libslic3r/Fill/FillGyroid.hpp @@ -17,10 +17,6 @@ public: virtual bool use_bridge_flow() const { return true; } protected: - - // mult of density, to have a good %of weight for each density parameter - float scaling = 1.75; - virtual void _fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp index aa0e0f6b0..6f26167a2 100644 --- a/xs/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp @@ -50,13 +50,13 @@ void FillHoneycomb::_fill_surface_single( bounding_box.merge(_align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height))); } - coord_t x = bounding_box.min.x; - while (x <= bounding_box.max.x) { + 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.y; y <= bounding_box.max.y; y += m.y_short + m.hex_side + m.y_short + m.hex_side) { + 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)); @@ -101,7 +101,7 @@ void FillHoneycomb::_fill_surface_single( for (Polylines::iterator it_path = chained.begin(); it_path != chained.end(); ++ it_path) { if (! paths.empty()) { // distance between first point of this path and last point of last path - double distance = paths.back().last_point().distance_to(it_path->first_point()); + 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; diff --git a/xs/src/libslic3r/Fill/FillPlanePath.cpp b/xs/src/libslic3r/Fill/FillPlanePath.cpp index f71ef95a1..615cc6efe 100644 --- a/xs/src/libslic3r/Fill/FillPlanePath.cpp +++ b/xs/src/libslic3r/Fill/FillPlanePath.cpp @@ -24,14 +24,14 @@ void FillPlanePath::_fill_surface_single( Point shift = this->_centered() ? bounding_box.center() : bounding_box.min; - expolygon.translate(-shift.x, -shift.y); - bounding_box.translate(-shift.x, -shift.y); + expolygon.translate(-shift(0), -shift(1)); + bounding_box.translate(-shift(0), -shift(1)); Pointfs pts = _generate( - coord_t(ceil(coordf_t(bounding_box.min.x) / distance_between_lines)), - coord_t(ceil(coordf_t(bounding_box.min.y) / distance_between_lines)), - coord_t(ceil(coordf_t(bounding_box.max.x) / distance_between_lines)), - coord_t(ceil(coordf_t(bounding_box.max.y) / distance_between_lines))); + 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) { @@ -41,8 +41,8 @@ void FillPlanePath::_fill_surface_single( polyline.points.reserve(pts.size()); for (Pointfs::iterator it = pts.begin(); it != pts.end(); ++ it) polyline.points.push_back(Point( - coord_t(floor(it->x * distance_between_lines + 0.5)), - coord_t(floor(it->y * distance_between_lines + 0.5)))); + 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)); @@ -62,7 +62,7 @@ void FillPlanePath::_fill_surface_single( // paths must be repositioned and rotated back for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) { - it->translate(shift.x, shift.y); + it->translate(shift(0), shift(1)); it->rotate(direction.first); } } @@ -86,12 +86,12 @@ Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t m coordf_t r = 1; Pointfs out; //FIXME Vojtech: If used as a solid infill, there is a gap left at the center. - out.push_back(Pointf(0, 0)); - out.push_back(Pointf(1, 0)); + 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(Pointf(r * cos(theta), r * sin(theta))); + out.push_back(Vec2d(r * cos(theta), r * sin(theta))); } return out; } @@ -162,7 +162,7 @@ Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, line.reserve(sz2); for (size_t i = 0; i < sz2; ++ i) { Point p = hilbert_n_to_xy(i); - line.push_back(Pointf(p.x + min_x, p.y + min_y)); + line.push_back(Vec2d(p(0) + min_x, p(1) + min_y)); } return line; } @@ -175,27 +175,27 @@ Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_ coordf_t r = 0; coordf_t r_inc = sqrt(2.); Pointfs out; - out.push_back(Pointf(0, 0)); + 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(Pointf( r, 0.)); - out.push_back(Pointf( r2, rx)); - out.push_back(Pointf( rx, rx)); - out.push_back(Pointf( rx, r2)); - out.push_back(Pointf(0., r)); - out.push_back(Pointf(-rx, r2)); - out.push_back(Pointf(-rx, rx)); - out.push_back(Pointf(-r2, rx)); - out.push_back(Pointf(-r, 0.)); - out.push_back(Pointf(-r2, -rx)); - out.push_back(Pointf(-rx, -rx)); - out.push_back(Pointf(-rx, -r2)); - out.push_back(Pointf(0., -r)); - out.push_back(Pointf( rx, -r2)); - out.push_back(Pointf( rx, -rx)); - out.push_back(Pointf( r2+r_inc, -rx)); + 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; } diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp index 5ba30ba51..205eb1b66 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp @@ -26,8 +26,8 @@ void FillRectilinear::_fill_surface_single( // define flow spacing according to requested density if (params.density > 0.9999f && !params.dont_adjust) { - this->_line_spacing = this->_adjust_solid_spacing(bounding_box.size().x, this->_line_spacing); - this->spacing = unscale(this->_line_spacing); + 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. @@ -38,14 +38,14 @@ void FillRectilinear::_fill_surface_single( } // generate the basic pattern - coord_t x_max = bounding_box.max.x + SCALED_EPSILON; + coord_t x_max = bounding_box.max(0) + SCALED_EPSILON; Lines lines; - for (coord_t x = bounding_box.min.x; x <= x_max; x += this->_line_spacing) - lines.push_back(this->_line(lines.size(), x, bounding_box.min.y, bounding_box.max.y)); + 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.y + SCALED_EPSILON; - for (coord_t y = bounding_box.min.y; y <= y_max; y += this->_line_spacing) - lines.push_back(Line(Point(bounding_box.min.x, y), Point(bounding_box.max.x, y))); + 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 @@ -72,10 +72,10 @@ void FillRectilinear::_fill_surface_single( 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) + if (first_point->y() > last_point->y()) std::swap(first_point, last_point); - first_point->y -= extra; - last_point->y += extra; + first_point->y() -= extra; + last_point->y() += extra; } size_t n_polylines_out_old = polylines_out.size(); @@ -103,10 +103,10 @@ void FillRectilinear::_fill_surface_single( const Point &first_point = it_polyline->points.front(); const Point &last_point = pts_end.back(); // Distance in X, Y. - const Vector distance = first_point.vector_to(last_point); + 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.x), std::abs(distance.y)) && + 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()); @@ -122,7 +122,7 @@ void FillRectilinear::_fill_surface_single( // 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.x, - direction.second.y); + // it->translate(- direction.second(0), - direction.second(1)); it->rotate(direction.first); } } diff --git a/xs/src/libslic3r/Fill/FillRectilinear2.cpp b/xs/src/libslic3r/Fill/FillRectilinear2.cpp index ddd785101..65440d0ef 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear2.cpp @@ -42,12 +42,12 @@ static inline coordf_t segment_length(const Polygon &poly, size_t seg1, const Po Point px = (i == 0) ? p1 : p2; Point pa = poly.points[((seg == 0) ? poly.points.size() : seg) - 1]; Point pb = poly.points[seg]; - if (pa.x > pb.x) - std::swap(pa.x, pb.x); - if (pa.y > pb.y) - std::swap(pa.y, pb.y); - assert(px.x >= pa.x && px.x <= pb.x); - assert(px.y >= pa.y && px.y <= pb.y); + 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; @@ -55,14 +55,14 @@ static inline coordf_t segment_length(const Polygon &poly, size_t seg1, const Po coordf_t len = 0; if (seg1 <= seg2) { for (size_t i = seg1; i < seg2; ++ i, pPrev = pThis) - len += pPrev->distance_to(*(pThis = &poly.points[i])); + len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm(); } else { for (size_t i = seg1; i < poly.points.size(); ++ i, pPrev = pThis) - len += pPrev->distance_to(*(pThis = &poly.points[i])); + len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm(); for (size_t i = 0; i < seg2; ++ i, pPrev = pThis) - len += pPrev->distance_to(*(pThis = &poly.points[i])); + len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm(); } - len += pPrev->distance_to(p2); + len += (*pPrev - p2).cast<double>().norm(); return len; } @@ -791,15 +791,15 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP // define flow spacing according to requested density if (params.full_infill() && !params.dont_adjust) { - line_spacing = this->_adjust_solid_spacing(bounding_box.size().x, line_spacing); - this->spacing = unscale(line_spacing); + 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.x -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); + 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), @@ -808,8 +808,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP // Intersect a set of euqally spaced vertical lines wiht expolygon. // n_vlines = ceil(bbox_width / line_spacing) - size_t n_vlines = (bounding_box.max.x - bounding_box.min.x + line_spacing - 1) / line_spacing; - coord_t x0 = bounding_box.min.x; + 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; @@ -842,8 +842,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP 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.x; - coord_t r = p2.x; + 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 @@ -869,33 +869,33 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP assert(l <= this_x); assert(r >= this_x); // Calculate the intersection position in y axis. x is known. - if (p1.x == this_x) { - if (p2.x == this_x) { + if (p1(0) == this_x) { + if (p2(0) == this_x) { // Ignore strictly vertical segments. continue; } - is.pos_p = p1.y; + is.pos_p = p1(1); is.pos_q = 1; - } else if (p2.x == this_x) { - is.pos_p = p2.y; + } 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.x > p1.x) { - is.pos_p = this_x - p1.x; - is.pos_q = p2.x - p1.x; + if (p2(0) > p1(0)) { + is.pos_p = this_x - p1(0); + is.pos_q = p2(0) - p1(0); } else { - is.pos_p = p1.x - this_x; - is.pos_q = p1.x - p2.x; + 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.y - p1.y); - is.pos_p += p1.y * int64_t(is.pos_q); + 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.y, p2.y)); - assert(is.pos() <= std::max(p1.y, p2.y) + 1); + 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); } } @@ -919,7 +919,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP 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].x - contour[iPrev].x; + 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) : @@ -1066,7 +1066,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP intrsctn.consumed_vertical_up : seg.intersections[i-1].consumed_vertical_up; if (! consumed) { - coordf_t dist2 = sqr(coordf_t(pointLast.x - seg.pos)) + sqr(coordf_t(pointLast.y - intrsctn.pos())); + 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; @@ -1356,8 +1356,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP // Handle nearly zero length edges. if (polyline_current->points.size() <= 1 || (polyline_current->points.size() == 2 && - std::abs(polyline_current->points.front().x - polyline_current->points.back().x) < SCALED_EPSILON && - std::abs(polyline_current->points.front().y - polyline_current->points.back().y) < SCALED_EPSILON)) + 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; @@ -1383,7 +1383,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP // 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.x, - rotate_vector.second.y); + // 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? diff --git a/xs/src/libslic3r/Fill/FillRectilinear3.cpp b/xs/src/libslic3r/Fill/FillRectilinear3.cpp index 1e7e3a1cd..8fc129eac 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear3.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear3.cpp @@ -217,30 +217,30 @@ Point SegmentIntersection::pos() const 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 Pointf p1 = convert_to<Pointf>(seg_start); - const Pointf v1 = convert_to<Pointf>(seg_end - seg_start); + 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 Pointf p2 = convert_to<Pointf>(line->pos); - const Pointf v2 = convert_to<Pointf>(line->dir); + const Vec2d p2(line->pos.cast<coordf_t>()); + const Vec2d v2(line->dir.cast<coordf_t>()); // Intersect the two rays. - double denom = v1.x * v2.y - v2.x * v1.y; + 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.x >>= 1; - out.y >>= 1; + out(0) >>= 1; + out(1) >>= 1; } else { // Find the intersection point. - double t = (v2.x * (p1.y - p2.y) - v2.y * (p1.x - p2.x)) / denom; + 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.x = coord_t(floor(p1.x + t * v1.x + 0.5)); - out.y = coord_t(floor(p1.y + t * v1.y + 0.5)); + 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; @@ -276,13 +276,13 @@ int SegmentIntersection::ordering_along_line(const SegmentIntersection &other) c // 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 (cross(this->line->dir, seg_end_b - this->line->pos) == 0) + 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 (cross(this->line->dir, seg_start_a - this->line->pos) == 0) + if (cross2(Vec2i64(this->line->dir.cast<int64_t>()), (seg_start_a - this->line->pos).cast<int64_t>()) == 0) return 0; } else { // General case. @@ -290,35 +290,35 @@ int SegmentIntersection::ordering_along_line(const SegmentIntersection &other) c } // First test, whether both points of one segment are completely in one half-plane of the other line. - const Point vec_b = seg_end_b - seg_start_b; - int side_start = signum(cross(vec_b, seg_start_a - seg_start_b)); - int side_end = signum(cross(vec_b, seg_end_a - seg_start_b)); + 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(cross(vec_b, this->line->dir)) * side_start; + return signum(cross2(vec_b, this->line->dir.cast<int64_t>())) * side_start; - const Point vec_a = seg_end_a - seg_start_a; - int side_start2 = signum(cross(vec_a, seg_start_b - seg_start_a)); - int side_end2 = signum(cross(vec_a, seg_end_b - seg_start_a)); + 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(cross(this->line->dir, vec_a)) * side_start2; + 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 = cross(this->line->dir, vec_a); - int64_t denom2 = cross(this->line->dir, vec_b); - Point vx_a = seg_start_a - this->line->pos; - Point vx_b = seg_start_b - this->line->pos; - int64_t t1_times_denom1 = int64_t(vx_a.x) * int64_t(vec_a.y) - int64_t(vx_a.y) * int64_t(vec_a.x); - int64_t t2_times_denom2 = int64_t(vx_b.x) * int64_t(vec_b.y) - int64_t(vx_b.y) * int64_t(vec_b.x); + 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); @@ -330,7 +330,7 @@ bool SegmentIntersection::operator<(const SegmentIntersection &other) const #ifdef _DEBUG Point p1 = this->pos(); Point p2 = other.pos(); - int64_t d = dot(this->line->dir, p2 - p1); + 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 @@ -389,16 +389,16 @@ static bool prepare_infill_hatching_segments( // 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().x, line_spacing); + out.line_spacing = Fill::_adjust_solid_spacing(bounding_box.size()(0), line_spacing); // Report back the adjusted line spacing. - fill_dir_params.spacing = float(unscale(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.x -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); + 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), @@ -407,13 +407,13 @@ static bool prepare_infill_hatching_segments( // Intersect a set of euqally spaced vertical lines wiht expolygon. // n_vlines = ceil(bbox_width / line_spacing) - size_t n_vlines = (bounding_box.max.x - bounding_box.min.x + line_spacing - 1) / line_spacing; - coord_t x0 = bounding_box.min.x; + 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.y); + out.start_point = Point(x0, bounding_box.min(1)); out.start_point.rotate(out.angle); #ifdef SLIC3R_DEBUG @@ -436,10 +436,10 @@ static bool prepare_infill_hatching_segments( for (size_t i = 0; i < n_vlines; ++ i) { auto &seg = out.segs[i]; seg.idx = i; - // seg.x = x0 + coord_t(i) * line_spacing; + // seg(0) = x0 + coord_t(i) * line_spacing; coord_t x = x0 + coord_t(i) * line_spacing; - seg.pos.x = coord_t(floor(cos_a * x - sin_a * bounding_box.min.y + 0.5)); - seg.pos.y = coord_t(floor(cos_a * bounding_box.min.y + sin_a * x + 0.5)); + 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; } @@ -454,7 +454,7 @@ static bool prepare_infill_hatching_segments( 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.x, v.y, out.direction.x, out.direction.y); + 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; @@ -462,26 +462,26 @@ static bool prepare_infill_hatching_segments( // 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->x + sin_a * pl->y - SCALED_EPSILON); - coord_t r = (coord_t)ceil (cos_a * pr->x + sin_a * pr->y + SCALED_EPSILON); + 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) + 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) + 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].x == i * line_spacing + x0); - // assert(l <= out.segs[i].x); - // assert(r >= out.segs[i].x); + // 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; @@ -489,12 +489,12 @@ static bool prepare_infill_hatching_segments( 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().x + 1 >= std::min(pl->x, pr->x)); - assert(is.pos().y + 1 >= std::min(pl->y, pr->y)); - assert(is.pos().x <= std::max(pl->x, pr->x) + 1); - assert(is.pos().y <= std::max(pl->y, pr->y) + 1); + 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); } } @@ -510,7 +510,7 @@ static bool prepare_infill_hatching_segments( 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 = dot(sil.dir, p2 - p1); + int64_t d = sil.dir.cast<int64_t>().dot((p2 - p1).cast<int64_t>()); assert(d >= - int64_t(SCALED_EPSILON)); } #endif /* _DEBUG */ @@ -527,7 +527,7 @@ static bool prepare_infill_hatching_segments( 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); + 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) : @@ -659,12 +659,12 @@ static inline coordf_t segment_length(const Polygon &poly, size_t seg1, const Po Point px = (i == 0) ? p1 : p2; Point pa = poly.points[((seg == 0) ? poly.points.size() : seg) - 1]; Point pb = poly.points[seg]; - if (pa.x > pb.x) - std::swap(pa.x, pb.x); - if (pa.y > pb.y) - std::swap(pa.y, pb.y); - assert(px.x >= pa.x && px.x <= pb.x); - assert(px.y >= pa.y && px.y <= pb.y); + 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; @@ -672,14 +672,14 @@ static inline coordf_t segment_length(const Polygon &poly, size_t seg1, const Po coordf_t len = 0; if (seg1 <= seg2) { for (size_t i = seg1; i < seg2; ++ i, pPrev = pThis) - len += pPrev->distance_to(*(pThis = &poly.points[i])); + len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm(); } else { for (size_t i = seg1; i < poly.points.size(); ++ i, pPrev = pThis) - len += pPrev->distance_to(*(pThis = &poly.points[i])); + len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm(); for (size_t i = 0; i < seg2; ++ i, pPrev = pThis) - len += pPrev->distance_to(*(pThis = &poly.points[i])); + len += (*pPrev - *(pThis = &poly.points[i])).cast<double>().norm(); } - len += pPrev->distance_to(p2); + len += (*pPrev - p2).cast<double>().norm(); return len; } @@ -1191,7 +1191,7 @@ static bool fill_hatching_segments_legacy( intrsctn.consumed_vertical_up : seg.intersections[i-1].consumed_vertical_up; if (! consumed) { - coordf_t dist2 = pointLast.distance_to(intrsctn.pos()); + coordf_t dist2 = (intrsctn.pos() - pointLast).cast<double>().norm(); if (dist2 < dist2min) { dist2min = dist2; i_vline = i_vline2; @@ -1481,8 +1481,8 @@ static bool fill_hatching_segments_legacy( // Handle nearly zero length edges. if (polyline_current->points.size() <= 1 || (polyline_current->points.size() == 2 && - std::abs(polyline_current->points.front().x - polyline_current->points.back().x) < SCALED_EPSILON && - std::abs(polyline_current->points.front().y - polyline_current->points.back().y) < SCALED_EPSILON)) + 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; @@ -1510,7 +1510,7 @@ static bool fill_hatching_segments_legacy( // 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.x, - rotate_vector.second.y); + // 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? diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp index b34b8989e..464cbb8e6 100644 --- a/xs/src/libslic3r/Format/3mf.cpp +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -16,6 +16,12 @@ #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 @@ -23,6 +29,7 @@ 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"; @@ -36,9 +43,9 @@ 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* METADATA_TAG = "metadata"; const char* VOLUME_TAG = "volume"; const char* UNIT_ATTR = "unit"; @@ -80,8 +87,6 @@ const char* INVALID_OBJECT_TYPES[] = "other" }; -typedef Eigen::Matrix<float, 4, 4, Eigen::RowMajor> Matrix4x4; - 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)) @@ -114,11 +119,11 @@ int get_attribute_value_int(const char** attributes, unsigned int attributes_siz return (text != nullptr) ? ::atoi(text) : 0; } -Matrix4x4 get_matrix_from_string(const std::string& mat_str) +Slic3r::Transform3d get_transform_from_string(const std::string& mat_str) { if (mat_str.empty()) // empty string means default identity matrix - return Matrix4x4::Identity(); + 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); @@ -126,9 +131,9 @@ Matrix4x4 get_matrix_from_string(const std::string& mat_str) unsigned int size = (unsigned int)mat_elements_str.size(); if (size != 12) // invalid data, return identity matrix - return Matrix4x4::Identity(); + return Slic3r::Transform3d::Identity(); - Matrix4x4 ret = Matrix4x4::Identity(); + Slic3r::Transform3d ret = Slic3r::Transform3d::Identity(); unsigned int i = 0; // matrices are stored into 3mf files as 4x3 // we need to transpose them @@ -136,7 +141,7 @@ Matrix4x4 get_matrix_from_string(const std::string& mat_str) { for (unsigned int r = 0; r < 3; ++r) { - ret(r, c) = (float)::atof(mat_elements_str[i++].c_str()); + ret(r, c) = ::atof(mat_elements_str[i++].c_str()); } } return ret; @@ -202,17 +207,17 @@ namespace Slic3r { struct Component { int object_id; - Matrix4x4 matrix; + Transform3d transform; explicit Component(int object_id) : object_id(object_id) - , matrix(Matrix4x4::Identity()) + , transform(Transform3d::Identity()) { } - Component(int object_id, const Matrix4x4& matrix) + Component(int object_id, const Transform3d& transform) : object_id(object_id) - , matrix(matrix) + , transform(transform) { } }; @@ -266,11 +271,11 @@ namespace Slic3r { struct Instance { ModelInstance* instance; - Matrix4x4 matrix; + Transform3d transform; - Instance(ModelInstance* instance, const Matrix4x4& matrix) + Instance(ModelInstance* instance, const Transform3d& transform) : instance(instance) - , matrix(matrix) + , transform(transform) { } }; @@ -315,6 +320,10 @@ namespace Slic3r { 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; @@ -326,6 +335,9 @@ namespace Slic3r { 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(); @@ -339,12 +351,14 @@ namespace Slic3r { 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); - bool _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename); + 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); @@ -386,10 +400,12 @@ namespace Slic3r { bool _handle_start_item(const char** attributes, unsigned int num_attributes); bool _handle_end_item(); - bool _create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter); + bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); + bool _handle_end_metadata(); - void _apply_transform(ModelObject& object, const Matrix4x4& matrix); - void _apply_transform(ModelInstance& instance, const Matrix4x4& matrix); + 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(); @@ -408,6 +424,7 @@ namespace Slic3r { // 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); @@ -415,9 +432,12 @@ namespace Slic3r { }; _3MF_Importer::_3MF_Importer() - : m_xml_parser(nullptr) + : m_version(0) + , m_xml_parser(nullptr) , m_model(nullptr) , m_unit_factor(1.0f) + , m_curr_metadata_name("") + , m_curr_characters("") { } @@ -428,6 +448,7 @@ namespace Slic3r { 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(); @@ -438,6 +459,9 @@ namespace Slic3r { 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); @@ -473,6 +497,8 @@ namespace Slic3r { 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)) @@ -490,15 +516,26 @@ namespace Slic3r { 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 - if (!_extract_print_config_from_archive(archive, stat, bundle, filename)) - { - mz_zip_reader_end(&archive); - add_error("Archive does not contain a valid print config"); - return false; - } + _extract_print_config_from_archive(archive, stat, bundle, filename); } else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { @@ -527,6 +564,13 @@ namespace Slic3r { 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()) { @@ -557,19 +601,6 @@ namespace Slic3r { if (!_generate_volumes(*object.second, obj_geometry->second, *volumes_ptr)) return false; - - // apply transformation if the object contains a single instance - if (object.second->instances.size() == 1) - { - for (const Instance& instance : m_instances) - { - if (object.second->instances[0] == instance.instance) - { - _apply_transform(*object.second, instance.matrix); - break; - } - } - } } // fixes the min z of the model if negative @@ -597,6 +628,7 @@ namespace Slic3r { 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) @@ -623,23 +655,90 @@ namespace Slic3r { return true; } - bool _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) + 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::vector<char> buffer((size_t)stat.m_uncomp_size + 1, 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 false; + return; } - - buffer.back() = '\0'; bundle.load_config_string(buffer.data(), archive_filename.c_str()); } + } - return true; + 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) @@ -719,6 +818,8 @@ namespace Slic3r { 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(); @@ -755,11 +856,18 @@ namespace Slic3r { 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) @@ -822,11 +930,10 @@ namespace Slic3r { if (instance.instance != nullptr) { ModelObject* object = instance.instance->get_object(); - if ((object != nullptr) && (object->instances.size() > 1)) + if (object != nullptr) { - // multiple instances -> apply the matrix to the instance - // (for single instance the transformation can be applied only after the volumes have been generated) - _apply_transform(*instance.instance, instance.matrix); + // apply the transform to the instance + _apply_transform(*instance.instance, instance.transform); } } } @@ -1010,7 +1117,7 @@ namespace Slic3r { 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); - Matrix4x4 matrix = get_matrix_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_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()) @@ -1023,7 +1130,7 @@ namespace Slic3r { } } - m_curr_object.components.emplace_back(object_id, matrix); + m_curr_object.components.emplace_back(object_id, transform); return true; } @@ -1056,9 +1163,9 @@ namespace Slic3r { // see specifications int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); - Matrix4x4 matrix = get_matrix_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); - return _create_object_instance(object_id, matrix, 1); + return _create_object_instance(object_id, transform, 1); } bool _3MF_Importer::_handle_end_item() @@ -1067,7 +1174,26 @@ namespace Slic3r { return true; } - bool _3MF_Importer::_create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter) + 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; @@ -1104,7 +1230,7 @@ namespace Slic3r { return false; } - m_instances.emplace_back(instance, matrix); + m_instances.emplace_back(instance, transform); } } else @@ -1112,7 +1238,7 @@ namespace Slic3r { // recursively process nested components for (const Component& component : it->second) { - if (!_create_object_instance(component.object_id, matrix * component.matrix, recur_counter + 1)) + if (!_create_object_instance(component.object_id, transform * component.transform, recur_counter + 1)) return false; } } @@ -1120,29 +1246,20 @@ namespace Slic3r { return true; } - void _3MF_Importer::_apply_transform(ModelObject& object, const Matrix4x4& matrix) - { - float matrix3x4[12] = { matrix(0, 0), matrix(0, 1), matrix(0, 2), matrix(0, 3), - matrix(1, 0), matrix(1, 1), matrix(1, 2), matrix(1, 3), - matrix(2, 0), matrix(2, 1), matrix(2, 2), matrix(2, 3) }; - - object.transform(matrix3x4); - } - - void _3MF_Importer::_apply_transform(ModelInstance& instance, const Matrix4x4& matrix) + 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 - double offset_x = (double)matrix(0, 3); - double offset_y = (double)matrix(1, 3); - double offset_z = (double)matrix(2, 3); + double offset_x = transform(0, 3); + double offset_y = transform(1, 3); + double offset_z = transform(2, 3); // scale - double sx = ::sqrt(sqr((double)matrix(0, 0)) + sqr((double)matrix(1, 0)) + sqr((double)matrix(2, 0))); - double sy = ::sqrt(sqr((double)matrix(0, 1)) + sqr((double)matrix(1, 1)) + sqr((double)matrix(2, 1))); - double sz = ::sqrt(sqr((double)matrix(0, 2)) + sqr((double)matrix(1, 2)) + sqr((double)matrix(2, 2))); + 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)) @@ -1152,69 +1269,26 @@ namespace Slic3r { if ((std::abs(sx - sy) > 0.00001) || (std::abs(sx - sz) > 0.00001)) return; - // rotations (extracted using quaternion) double inv_sx = 1.0 / sx; double inv_sy = 1.0 / sy; double inv_sz = 1.0 / sz; - - Eigen::Matrix<double, 3, 3, Eigen::RowMajor> m3x3; - m3x3 << (double)matrix(0, 0) * inv_sx, (double)matrix(0, 1) * inv_sy, (double)matrix(0, 2) * inv_sz, - (double)matrix(1, 0) * inv_sx, (double)matrix(1, 1) * inv_sy, (double)matrix(1, 2) * inv_sz, - (double)matrix(2, 0) * inv_sx, (double)matrix(2, 1) * inv_sy, (double)matrix(2, 2) * inv_sz; - double qw = 0.5 * ::sqrt(std::max(0.0, 1.0 + m3x3(0, 0) + m3x3(1, 1) + m3x3(2, 2))); - double qx = 0.5 * ::sqrt(std::max(0.0, 1.0 + m3x3(0, 0) - m3x3(1, 1) - m3x3(2, 2))); - double qy = 0.5 * ::sqrt(std::max(0.0, 1.0 - m3x3(0, 0) + m3x3(1, 1) - m3x3(2, 2))); - double qz = 0.5 * ::sqrt(std::max(0.0, 1.0 - m3x3(0, 0) - m3x3(1, 1) + m3x3(2, 2))); + 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; - double q_magnitude = ::sqrt(sqr(qw) + sqr(qx) + sqr(qy) + sqr(qz)); + Eigen::AngleAxisd rotation; + rotation.fromRotationMatrix(m3x3); - // invalid length, return - if (q_magnitude == 0.0) + // 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 inv_q_magnitude = 1.0 / q_magnitude; + double angle_z = (rotation.axis() == Vec3d::UnitZ()) ? rotation.angle() : -rotation.angle(); - qw *= inv_q_magnitude; - qx *= inv_q_magnitude; - qy *= inv_q_magnitude; - qz *= inv_q_magnitude; - - double test = qx * qy + qz * qw; - double angle_x, angle_y, angle_z; - - if (test > 0.499) - { - // singularity at north pole - angle_x = 0.0; - angle_y = 2.0 * ::atan2(qx, qw); - angle_z = 0.5 * PI; - } - else if (test < -0.499) - { - // singularity at south pole - angle_x = 0.0; - angle_y = -2.0 * ::atan2(qx, qw); - angle_z = -0.5 * PI; - } - else - { - angle_x = ::atan2(2.0 * qx * qw - 2.0 * qy * qz, 1.0 - 2.0 * sqr(qx) - 2.0 * sqr(qz)); - angle_y = ::atan2(2.0 * qy * qw - 2.0 * qx * qz, 1.0 - 2.0 * sqr(qy) - 2.0 * sqr(qz)); - angle_z = ::asin(2.0 * qx * qy + 2.0 * qz * qw); - - if (angle_x < 0.0) - angle_x += 2.0 * PI; - - if (angle_y < 0.0) - angle_y += 2.0 * PI; - - if (angle_z < 0.0) - angle_z += 2.0 * PI; - } - - instance.offset.x = offset_x; - instance.offset.y = offset_y; + instance.offset(0) = offset_x; + instance.offset(1) = offset_y; instance.scaling_factor = sx; instance.rotation = angle_z; } @@ -1346,12 +1420,13 @@ namespace Slic3r { stl_facet& facet = stl.facet_start[i]; for (unsigned int v = 0; v < 3; ++v) { - ::memcpy((void*)&facet.vertex[v].x, (const void*)&geometry.vertices[geometry.triangles[src_start_id + ii + v] * 3], 3 * sizeof(float)); + ::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) @@ -1382,6 +1457,13 @@ namespace Slic3r { 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; @@ -1401,11 +1483,11 @@ namespace Slic3r { struct BuildItem { unsigned int id; - Matrix4x4 matrix; + Transform3d transform; - BuildItem(unsigned int id, const Matrix4x4& matrix) + BuildItem(unsigned int id, const Transform3d& transform) : id(id) - , matrix(matrix) + , transform(transform) { } }; @@ -1443,27 +1525,28 @@ namespace Slic3r { IdToObjectDataMap m_objects_data; public: - bool save_model_to_file(const std::string& filename, Model& model, const Print& print); + 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 _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 _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); + 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 _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); @@ -1501,14 +1584,25 @@ namespace Slic3r { return false; } - // adds slic3r print config file - if (!_add_print_config_file_to_archive(archive, print)) + // 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)) { @@ -1573,7 +1667,8 @@ namespace Slic3r { { 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\">\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; @@ -1641,11 +1736,8 @@ namespace Slic3r { stream << " </" << COMPONENTS_TAG << ">\n"; } - Eigen::Affine3f transform; - transform = Eigen::Translation3f((float)(instance->offset.x + object.origin_translation.x), (float)(instance->offset.y + object.origin_translation.y), (float)object.origin_translation.z) - * Eigen::AngleAxisf((float)instance->rotation, Eigen::Vector3f::UnitZ()) - * Eigen::Scaling((float)instance->scaling_factor); - build_items.emplace_back(instance_id, transform.matrix()); + Transform3d t = instance->world_matrix(); + build_items.emplace_back(instance_id, t); stream << " </" << OBJECT_TAG << ">\n"; @@ -1687,10 +1779,9 @@ namespace Slic3r { for (int i = 0; i < stl.stats.shared_vertices; ++i) { stream << " <" << VERTEX_TAG << " "; - // Subtract origin_translation in order to restore the original local coordinates - stream << "x=\"" << (stl.v_shared[i].x - object.origin_translation.x) << "\" "; - stream << "y=\"" << (stl.v_shared[i].y - object.origin_translation.y) << "\" "; - stream << "z=\"" << (stl.v_shared[i].z - object.origin_translation.z) << "\" />\n"; + stream << "x=\"" << stl.v_shared[i](0) << "\" "; + stream << "y=\"" << stl.v_shared[i](1) << "\" "; + stream << "z=\"" << stl.v_shared[i](2) << "\" />\n"; } } @@ -1747,7 +1838,7 @@ namespace Slic3r { { for (unsigned r = 0; r < 3; ++r) { - stream << item.matrix(r, c); + stream << item.transform(r, c); if ((r != 2) || (c != 3)) stream << " "; } @@ -1760,6 +1851,44 @@ namespace Slic3r { 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]; @@ -1768,10 +1897,13 @@ namespace Slic3r { GCode::append_full_config(print, out); - if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + if (!out.empty()) { - add_error("Unable to add print config file to archive"); - return false; + 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; @@ -1792,7 +1924,7 @@ namespace Slic3r { // stores object's name if (!obj->name.empty()) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << obj->name << "\"/>\n"; + 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()) @@ -1815,7 +1947,7 @@ namespace Slic3r { // stores volume's name if (!volume->name.empty()) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << volume->name << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; // stores volume's modifier field if (volume->modifier) @@ -1856,20 +1988,17 @@ namespace Slic3r { _3MF_Importer importer; bool res = importer.load_model_from_file(path, *model, *bundle); - - if (!res) - importer.log_errors(); - + importer.log_errors(); return res; } - bool store_3mf(const char* path, Model* model, Print* print) + 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); + bool res = exporter.save_model_to_file(path, *model, *print, export_print_config); if (!res) exporter.log_errors(); diff --git a/xs/src/libslic3r/Format/3mf.hpp b/xs/src/libslic3r/Format/3mf.hpp index 9b48c860b..85bc812e3 100644 --- a/xs/src/libslic3r/Format/3mf.hpp +++ b/xs/src/libslic3r/Format/3mf.hpp @@ -12,7 +12,7 @@ namespace Slic3r { // 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); + extern bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config); }; // namespace Slic3r diff --git a/xs/src/libslic3r/Format/AMF.cpp b/xs/src/libslic3r/Format/AMF.cpp index a52dd532a..81617ad72 100644 --- a/xs/src/libslic3r/Format/AMF.cpp +++ b/xs/src/libslic3r/Format/AMF.cpp @@ -8,11 +8,13 @@ #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 @@ -24,6 +26,12 @@ #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. +const unsigned int VERSION_AMF = 1; +const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version"; + const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config"; namespace Slic3r @@ -32,6 +40,7 @@ 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), @@ -137,6 +146,8 @@ struct AMFParserContext 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. @@ -360,9 +371,9 @@ void AMFParserContext::endElement(const char * /* name */) case NODE_TYPE_VERTEX: assert(m_object); // Parse the vertex data - m_object_vertices.emplace_back(atof(m_value[0].c_str())); - m_object_vertices.emplace_back(atof(m_value[1].c_str())); - m_object_vertices.emplace_back(atof(m_value[2].c_str())); + 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(); @@ -391,10 +402,11 @@ void AMFParserContext::endElement(const char * /* name */) 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].x, &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float)); + 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; @@ -462,6 +474,10 @@ void AMFParserContext::endElement(const char * /* name */) 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; @@ -482,8 +498,8 @@ void AMFParserContext::endDocument() for (const Instance &instance : object.second.instances) if (instance.deltax_set && instance.deltay_set) { ModelInstance *mi = m_model.objects[object.second.idx]->add_instance(); - mi->offset.x = instance.deltax; - mi->offset.y = instance.deltay; + mi->offset(0) = instance.deltax; + mi->offset(1) = instance.deltay; mi->rotation = instance.rz_set ? instance.rz : 0.f; mi->scaling_factor = instance.scale_set ? instance.scale : 1.f; } @@ -543,47 +559,8 @@ bool load_amf_file(const char *path, PresetBundle* bundle, Model *model) return result; } -// Load an AMF archive into a provided model. -bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) +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 ((path == nullptr) || (model == nullptr)) - return false; - - 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); - if (num_entries != 1) - { - printf("Found invalid number of entries\n"); - mz_zip_reader_end(&archive); - return false; - } - - mz_zip_archive_file_stat stat; - res = mz_zip_reader_file_stat(&archive, 0, &stat); - if (res == 0) - { - printf("Unable to extract entry statistics\n"); - mz_zip_reader_end(&archive); - return false; - } - - std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(path).filename().string(), ".zip.amf", ".amf"); - if (internal_amf_filename != stat.m_filename) - { - printf("Found invalid internal filename\n"); - mz_zip_reader_end(&archive); - return false; - } - if (stat.m_uncomp_size == 0) { printf("Found invalid size\n"); @@ -611,7 +588,7 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) return false; } - res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0); + 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"); @@ -628,6 +605,62 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model) 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; } @@ -636,23 +669,39 @@ bool load_amf_archive(const char *path, PresetBundle* bundle, Model *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, ".zip.amf")) - return load_amf_archive(path, bundle, model); - else if (boost::iends_with(path, ".amf") || boost::iends_with(path, ".amf.xml")) + 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 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, path, 0); + mz_bool res = mz_zip_writer_init_file(&archive, export_path.c_str(), 0); if (res == 0) return false; @@ -660,10 +709,14 @@ bool store_amf(const char *path, Model *model, Print* print) 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"; - std::string config = "\n"; - GCode::append_full_config(*print, config); - stream << "<metadata type=\"" << SLIC3R_CONFIG_TYPE << "\">" << config << "</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()) @@ -683,7 +736,7 @@ bool store_amf(const char *path, Model *model, Print* print) 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\">" << object->name << "</metadata>\n"; + 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. @@ -708,9 +761,9 @@ bool store_amf(const char *path, Model *model, Print* print) for (size_t i = 0; i < stl.stats.shared_vertices; ++ i) { stream << " <vertex>\n"; stream << " <coordinates>\n"; - stream << " <x>" << stl.v_shared[i].x << "</x>\n"; - stream << " <y>" << stl.v_shared[i].y << "</y>\n"; - stream << " <z>" << stl.v_shared[i].z << "</z>\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"; } @@ -727,7 +780,7 @@ bool store_amf(const char *path, Model *model, Print* print) 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\">" << volume->name << "</metadata>\n"; + stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n"; if (volume->modifier) stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n"; for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) { @@ -751,8 +804,8 @@ bool store_amf(const char *path, Model *model, Print* print) " <scale>%lf</scale>\n" " </instance>\n", object_id, - instance->offset.x, - instance->offset.y, + instance->offset(0), + instance->offset(1), instance->rotation, instance->scaling_factor); //FIXME missing instance->scaling_factor @@ -767,20 +820,20 @@ bool store_amf(const char *path, Model *model, Print* print) } stream << "</amf>\n"; - std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(path).filename().string(), ".zip.amf", ".amf"); + 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(path); + boost::filesystem::remove(export_path); return false; } if (!mz_zip_writer_finalize_archive(&archive)) { mz_zip_writer_end(&archive); - boost::filesystem::remove(path); + boost::filesystem::remove(export_path); return false; } diff --git a/xs/src/libslic3r/Format/AMF.hpp b/xs/src/libslic3r/Format/AMF.hpp index 027ebdab3..4779e9a51 100644 --- a/xs/src/libslic3r/Format/AMF.hpp +++ b/xs/src/libslic3r/Format/AMF.hpp @@ -12,7 +12,7 @@ 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); +extern bool store_amf(const char *path, Model *model, Print* print, bool export_print_config); }; // namespace Slic3r diff --git a/xs/src/libslic3r/Format/OBJ.cpp b/xs/src/libslic3r/Format/OBJ.cpp index ea6b5604c..ee5756083 100644 --- a/xs/src/libslic3r/Format/OBJ.cpp +++ b/xs/src/libslic3r/Format/OBJ.cpp @@ -57,14 +57,14 @@ bool load_obj(const char *path, Model *model, const char *object_name_in) continue; stl_facet &facet = stl.facet_start[i_face ++]; size_t num_normals = 0; - stl_normal normal = { 0.f }; + 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].x, &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float)); + memcpy(facet.vertex[v].data(), &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float)); if (vertex.normalIdx != -1) { - normal.x += data.normals[vertex.normalIdx*3]; - normal.y += data.normals[vertex.normalIdx*3+1]; - normal.z += data.normals[vertex.normalIdx*3+2]; + 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; } } @@ -74,33 +74,27 @@ bool load_obj(const char *path, Model *model, const char *object_name_in) facet2.vertex[0] = facet.vertex[0]; facet2.vertex[1] = facet.vertex[2]; const ObjParser::ObjVertex &vertex = data.vertices[i++]; - memcpy(&facet2.vertex[2].x, &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float)); + memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float)); if (vertex.normalIdx != -1) { - normal.x += data.normals[vertex.normalIdx*3]; - normal.y += data.normals[vertex.normalIdx*3+1]; - normal.z += data.normals[vertex.normalIdx*3+2]; + 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 = sqrt(facet.normal.x*facet.normal.x + facet.normal.y*facet.normal.y + facet.normal.z*facet.normal.z); + float len = facet.normal.norm(); if (len > EPSILON) { - normal.x /= len; - normal.y /= len; - normal.z /= len; + normal /= len; facet.normal = normal; facet2.normal = normal; } } } else if (num_normals == 3) { // Normalize an average normal of a triangle. - float len = sqrt(facet.normal.x*facet.normal.x + facet.normal.y*facet.normal.y + facet.normal.z*facet.normal.z); - if (len > EPSILON) { - normal.x /= len; - normal.y /= len; - normal.z /= len; - facet.normal = normal; - } + float len = facet.normal.norm(); + if (len > EPSILON) + facet.normal = normal / len; } } stl_get_size(&stl); diff --git a/xs/src/libslic3r/Format/PRUS.cpp b/xs/src/libslic3r/Format/PRUS.cpp index 1809eaead..3cf3fc075 100644 --- a/xs/src/libslic3r/Format/PRUS.cpp +++ b/xs/src/libslic3r/Format/PRUS.cpp @@ -166,7 +166,7 @@ bool load_prus(const char *path, Model *model) float trafo[3][4] = { 0 }; double instance_rotation = 0.; double instance_scaling_factor = 1.f; - Pointf instance_offset(0., 0.); + Vec2d instance_offset(0., 0.); bool trafo_set = false; unsigned int group_id = (unsigned int)-1; unsigned int extruder_id = (unsigned int)-1; @@ -207,8 +207,8 @@ bool load_prus(const char *path, Model *model) for (size_t c = 0; c < 3; ++ c) trafo[r][c] += mat_trafo(r, c); } - instance_offset.x = position[0] - zero[0]; - instance_offset.y = position[1] - zero[1]; + instance_offset(0) = position[0] - zero[0]; + instance_offset(1) = position[1] - zero[1]; trafo[2][3] = position[2] / instance_scaling_factor; trafo_set = true; } @@ -260,8 +260,8 @@ bool load_prus(const char *path, Model *model) mesh.repair(); // Transform the model. stl_transform(&stl, &trafo[0][0]); - if (std::abs(stl.stats.min.z) < EPSILON) - stl.stats.min.z = 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; @@ -309,11 +309,11 @@ bool load_prus(const char *path, Model *model) 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].x, &facet.vertex[0].y, &facet.vertex[0].z); + 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].x, &facet.vertex[1].y, &facet.vertex[1].z); + 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].x, &facet.vertex[2].y, &facet.vertex[2].z); + 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); @@ -324,9 +324,9 @@ bool load_prus(const char *path, Model *model) 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.x) != 1 || - sscanf(normal_buf[1], "%f", &facet.normal.y) != 1 || - sscanf(normal_buf[2], "%f", &facet.normal.z) != 1) { + 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)); diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index e55404845..0ade0de61 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -49,11 +49,11 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const 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().x, gcodegen.origin().y) : Point(0, 0); + 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.negative()); + result.translate(- scaled_origin); return result; } @@ -64,8 +64,8 @@ std::string OozePrevention::pre_toolchange(GCode &gcodegen) // move to the nearest standby point if (!this->standby_points.empty()) { // get current position in print coordinates - Pointf3 writer_pos = gcodegen.writer().get_position(); - Point pos = Point::new_scale(writer_pos.x, writer_pos.y); + Vec3d writer_pos = gcodegen.writer().get_position(); + Point pos = Point::new_scale(writer_pos(0), writer_pos(1)); // find standby point Point standby_point; @@ -74,7 +74,7 @@ std::string OozePrevention::pre_toolchange(GCode &gcodegen) /* 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(Pointf::new_unscale(standby_point), + gcode += gcodegen.writer().travel_to_xy(unscale(standby_point), "move to standby position"); } @@ -160,13 +160,25 @@ Wipe::wipe(GCode &gcodegen, bool toolchange) 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().x), scale_(wipe_tower_pt.y - gcodegen.origin().y)); + 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. @@ -174,14 +186,14 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T 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, tcr.start_pos), + 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.gcode; + 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); @@ -195,18 +207,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T check_add_eol(gcode); } // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); + 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, tcr.end_pos)); + 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 - tcr.end_pos.x) < std::abs(m_right - tcr.end_pos.x)) ? m_right : m_left, - tcr.end_pos.y))); + 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. @@ -214,6 +226,57 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T 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); @@ -230,7 +293,7 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) 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(Pointf(m_priming.end_pos.x, m_priming.end_pos.y)); + 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(); @@ -262,7 +325,7 @@ std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, std::string WipeTowerIntegration::finalize(GCode &gcodegen) { std::string gcode; - if (std::abs(gcodegen.writer().get_position().z - m_final_purge.print_z) > EPSILON) + 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; @@ -309,10 +372,12 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec size_t object_idx; size_t layer_idx; }; - std::vector<std::vector<LayerToPrint>> per_object(print.objects().size(), std::vector<LayerToPrint>()); + + 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 < print.objects().size(); ++ i) { - per_object[i] = collect_layers_to_print(*print.objects()[i]); + 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()); @@ -337,8 +402,8 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec 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(print.objects().size(), LayerToPrint()); - for (; i < j; ++ i) { + 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]); @@ -382,6 +447,13 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ 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"; @@ -413,9 +485,69 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) print.set_started(psGCodeExport); - // resets time estimator - m_time_estimator.reset(); - m_time_estimator.set_dialect(print.config().gcode_flavor); + // 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(); @@ -429,9 +561,10 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // 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 : print.objects()) { + for (auto object : printable_objects) { std::vector<coordf_t> zs; zs.reserve(object->layers().size() + object->support_layers().size()); for (auto layer : object->layers()) @@ -444,7 +577,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) } else { // Print all objects with the same print_z together. std::vector<coordf_t> zs; - for (auto object : print.objects()) { + 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); @@ -464,8 +597,8 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) { // get the minimum cross-section used in the print std::vector<double> mm3_per_mm; - for (auto object : print.objects()) { - for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) { + 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]; @@ -529,7 +662,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) print.throw_if_canceled(); // Write some terse information on the slicing parameters. - const PrintObject *first_object = print.objects().front(); + 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) { @@ -547,6 +680,14 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) } 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(); @@ -559,20 +700,24 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) 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 < print.objects().size(); ++initial_print_object_id) { - tool_ordering = ToolOrdering(*print.objects()[initial_print_object_id], initial_extruder_id); - if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1) - break; - } - } else { + // 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; - initial_extruder_id = tool_ordering.first_extruder(); 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! @@ -586,6 +731,9 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) 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)); @@ -598,8 +746,8 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) 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. @@ -638,12 +786,12 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // 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 : print.objects()) + for (const PrintObject *object : printable_objects) for (const Layer *layer : object->layers()) for (const ExPolygon &expoly : layer->slices.expolygons) - for (const Point © : object->_shifted_copies) { + for (const Point © : object->copies()) { islands.emplace_back(expoly.contour); - islands.back().translate(copy); + islands.back().translate(- copy); } //FIXME Mege the islands in parallel. m_avoid_crossing_perimeters.init_external_mp(union_ex(islands)); @@ -660,9 +808,9 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points); Polygons skirts; for (unsigned int extruder_id : print.extruders()) { - const Pointf &extruder_offset = print.config().extruder_offset.get_at(extruder_id); + const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id); Polygon s(outer_skirt); - s.translate(-scale_(extruder_offset.x), -scale_(extruder_offset.y)); + s.translate(Point::new_scale(- extruder_offset(0), - extruder_offset(1))); skirts.emplace_back(std::move(s)); } m_ooze_prevention.enable = true; @@ -681,21 +829,24 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) print.throw_if_canceled(); } - // Set initial extruder only after custom start G-code. - _write(file, this->set_extruder(initial_extruder_id)); + 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(print.objects()); - std::sort(objects.begin(), objects.end(), [](const PrintObject* po1, const PrintObject* po2) { return po1->size.z < po2->size.z; }); + 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 © : object._shifted_copies) { + for (const Point © : object.copies()) { // Get optimal tool ordering to minimize tool switches of a multi-exruder print. - if (object_id != initial_print_object_id || © != object._shifted_copies.data()) { + if (object_id != initial_print_object_id || © != 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(); @@ -707,7 +858,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) assert(final_extruder_id != (unsigned int)-1); } print.throw_if_canceled(); - this->set_origin(unscale(copy.x), unscale(copy.y)); + 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. @@ -736,7 +887,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) for (const LayerToPrint <p : 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()), © - object._shifted_copies.data()); + this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), © - object.copies().data()); print.throw_if_canceled(); } if (m_pressure_equalizer) @@ -751,8 +902,9 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) // Order objects using a nearest neighbor search. std::vector<size_t> object_indices; Points object_reference_points; - for (PrintObject *object : print.objects()) - object_reference_points.push_back(object->_shifted_copies.front()); + 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. @@ -761,33 +913,35 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) 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")); - _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 : print.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"); + 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 ToolOrdering::LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first); + 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)); @@ -813,30 +967,38 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) } // Process filament-specific gcode in extruder order. - 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())); - } 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()))); + { + 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)); } - _writeln(file, this->placeholder_parser_process("end_gcode", print.config().end_gcode, m_writer.extruder()->id())); _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_time_estimator.calculate_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_print_time = m_time_estimator.get_time_hms(); + 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(); double extruded_volume = extruder.extruded_volume(); 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(), used_filament)); + 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; @@ -850,7 +1012,9 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data) print.m_print_statistics.total_extruded_volume = print.m_print_statistics.total_extruded_volume + extruded_volume; } _write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost); - _write_format(file, "; estimated printing time = %s\n", m_time_estimator.get_time_hms().c_str()); + _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"); @@ -938,6 +1102,36 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc 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 @@ -1028,7 +1222,7 @@ void GCode::process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector<LayerToPrint> &layers, - const ToolOrdering::LayerTools &layer_tools, + 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) @@ -1166,7 +1360,6 @@ void GCode::process_layer( // 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; @@ -1233,8 +1426,8 @@ void GCode::process_layer( 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.x >= bbox.min.x && point.x < bbox.max.x && - point.y >= bbox.min.y && point.y < bbox.max.y && + 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); }; @@ -1243,74 +1436,77 @@ void GCode::process_layer( if (layerm == nullptr) continue; const PrintRegion ®ion = *print.regions()[region_id]; - - // process perimeters - for (const ExtrusionEntity *ee : layerm->perimeters.entities) { - // perimeter_coll represents perimeter extrusions of a single island. - const auto *perimeter_coll = dynamic_cast<const ExtrusionEntityCollection*>(ee); - if (perimeter_coll->entities.empty()) - // This shouldn't happen but first_point() would fail. - continue; - // Init by_extruder item only if we actually use the extruder. - std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder( - by_extruder, - std::max<int>(region.config().perimeter_extruder.value - 1, 0), - &layer_to_print - layers.data(), - layers.size(), n_slices+1); - for (size_t i = 0; i <= n_slices; ++ i) - if (// perimeter_coll->first_point does not fit inside any slice - i == n_slices || - // perimeter_coll->first_point fits inside ith slice - point_inside_surface(i, perimeter_coll->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].perimeters.append(perimeter_coll->entities); - break; - } - } - - // process infill - // layerm->fills is a collection of Slic3r::ExtrusionPath::Collection objects (C++ class ExtrusionEntityCollection), - // each one containing the ExtrusionPath objects of a certain infill "group" (also called "surface" - // throughout the code). We can redefine the order of such Collections but we have to - // do each one completely at once. - for (const ExtrusionEntity *ee : layerm->fills.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; - // init by_extruder item only if we actually use the extruder - int extruder_id = std::max<int>(0, (is_solid_infill(fill->entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) - 1); - // Init by_extruder item only if we actually use the extruder. - std::vector<ObjectByExtruder::Island> &islands = object_islands_by_extruder( - by_extruder, - extruder_id, - &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].infills.append(fill->entities); - break; + + + // 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()) { @@ -1327,7 +1523,7 @@ void GCode::process_layer( 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; @@ -1336,7 +1532,7 @@ void GCode::process_layer( 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.); @@ -1349,49 +1545,60 @@ void GCode::process_layer( 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; - for (const 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->_shifted_copies; - else - copies.push_back(print_object->_shifted_copies[single_object_idx]); - // Sort the copies by the closest point starting with the current print position. - - for (const Point © : 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.x), unscale(copy.y)); - if (object_by_extruder.support != nullptr) { - 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 (const ObjectByExtruder::Island &island : object_by_extruder.islands) { - if (print.config().infill_first) { - gcode += this->extrude_infill(print, island.by_region); - gcode += this->extrude_perimeters(print, island.by_region, lower_layer_edge_grids[layer_id]); - } else { - gcode += this->extrude_perimeters(print, island.by_region, lower_layer_edge_grids[layer_id]); - gcode += this->extrude_infill(print, island.by_region); + // 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 © : 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; } } } @@ -1414,7 +1621,7 @@ void GCode::process_layer( 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); } @@ -1426,15 +1633,22 @@ void GCode::apply_print_config(const PrintConfig &print_config) void GCode::append_full_config(const Print& print, std::string& str) { - const StaticPrintConfig *configs[] = { &print.config(), &print.default_object_config(), &print.default_region_config() }; + 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) @@ -1450,14 +1664,14 @@ void GCode::set_extruders(const std::vector<unsigned int> &extruder_ids) } } -void GCode::set_origin(const Pointf &pointf) +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.x - pointf.x), - scale_(m_origin.y - pointf.y) + scale_(m_origin(0) - pointf(0)), + scale_(m_origin(1) - pointf(1)) ); - m_last_pos.translate(translate); + m_last_pos += translate; m_wipe.path.translate(translate); m_origin = pointf; } @@ -1588,13 +1802,13 @@ static Points::iterator project_point_to_polygon_and_insert(Polygon &polygon, co j = 0; const Point &p1 = polygon.points[i]; const Point &p2 = polygon.points[j]; - const Slic3r::Point v_seg = p1.vector_to(p2); - const Slic3r::Point v_pt = p1.vector_to(pt); - const int64_t l2_seg = int64_t(v_seg.x) * int64_t(v_seg.x) + int64_t(v_seg.y) * int64_t(v_seg.y); - int64_t t_pt = int64_t(v_seg.x) * int64_t(v_pt.x) + int64_t(v_seg.y) * int64_t(v_pt.y); + 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.x) * int64_t(v_pt.x) + int64_t(v_pt.y) * int64_t(v_pt.y)); + 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; @@ -1607,7 +1821,7 @@ static Points::iterator project_point_to_polygon_and_insert(Polygon &polygon, co } else { // Closest to the segment. assert(t_pt >= 0 && t_pt <= l2_seg); - int64_t d_seg = int64_t(v_seg.y) * int64_t(v_pt.x) - int64_t(v_seg.x) * int64_t(v_pt.y); + 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) { @@ -1616,15 +1830,15 @@ static Points::iterator project_point_to_polygon_and_insert(Polygon &polygon, co // Evaluate the foot point. pt_min = p1; double linv = double(d_seg) / double(l2_seg); - pt_min.x = pt.x - coord_t(floor(double(v_seg.y) * linv + 0.5)); - pt_min.y = pt.y + coord_t(floor(double(v_seg.x) * linv + 0.5)); + 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.distance_to(polygon.points[i_min]) > eps) { + 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); } @@ -1636,8 +1850,8 @@ 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] + float(polygon.points[i].distance_to(polygon.points[i-1])); - lengths.back() = lengths[lengths.size()-2] + float(polygon.points.front().distance_to(polygon.points.back())); + 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; } @@ -1685,10 +1899,10 @@ std::vector<float> polygon_angles_at_vertices(const Polygon &polygon, const std: const Point &p0 = polygon.points[idx_prev]; const Point &p1 = polygon.points[idx_curr]; const Point &p2 = polygon.points[idx_next]; - const Point v1 = p0.vector_to(p1); - const Point v2 = p1.vector_to(p2); - int64_t dot = int64_t(v1.x)*int64_t(v2.x) + int64_t(v1.y)*int64_t(v2.y); - int64_t cross = int64_t(v1.x)*int64_t(v2.y) - int64_t(v1.y)*int64_t(v2.x); + 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; } @@ -1712,10 +1926,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou { static int iRun = 0; BoundingBox bbox = (*lower_layer_edge_grid)->bbox(); - bbox.min.x -= scale_(5.f); - bbox.min.y -= scale_(5.f); - bbox.max.x += scale_(5.f); - bbox.max.y += scale_(5.f); + 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 @@ -1751,7 +1965,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou break; case spRear: last_pos = m_layer->object()->bounding_box().center(); - last_pos.y += coord_t(3. * m_layer->object()->bounding_box().radius()); + last_pos(1) += coord_t(3. * m_layer->object()->bounding_box().radius()); last_pos_weight = 5.f; break; } @@ -1884,7 +2098,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou //FIXME Better parametrize the loop by its length. Polygon polygon = loop.polygon(); Point centroid = polygon.centroid(); - last_pos = Point(polygon.bounding_box().max.x, centroid.y); + 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. @@ -1941,19 +2155,17 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // 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 - Line first_segment( - paths.front().polyline.points[0], - paths.front().polyline.points[1] - ); - double distance = std::min<double>( - scale_(EXTRUDER_CONFIG(nozzle_diameter)), - first_segment.length() - ); - Point point = first_segment.point_at(distance); - point.rotate(angle, first_segment.a); - + 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(point), "move inwards before travel"); + gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel"); } return gcode; @@ -2074,7 +2286,9 @@ void GCode::_write(FILE* file, const char *what) // writes string to file fwrite(gcode, 1, ::strlen(gcode), file); // updates time estimator and gcode lines vector - m_time_estimator.add_gcode_block(gcode); + m_normal_time_estimator.add_gcode_block(gcode); + if (m_silent_time_estimator_enabled) + m_silent_time_estimator.add_gcode_block(gcode); } } @@ -2121,7 +2335,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, std::string gcode; // go to first point of extrusion path - if (!m_last_pos_defined || !m_last_pos.coincides_with(path.first_point())) { + if (!m_last_pos_defined || m_last_pos != path.first_point()) { gcode += this->travel_to( path.first_point(), path.role(), @@ -2194,7 +2408,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line - if (m_enable_extrusion_role_markers || m_enable_analyzer) + if (m_enable_extrusion_role_markers) { if (path.role() != m_last_extrusion_role) { @@ -2205,18 +2419,20 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(m_last_extrusion_role)); gcode += buf; } - if (m_enable_analyzer) - { - char buf[32]; - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), 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; @@ -2254,6 +2470,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, if (path.role() == erExternalPerimeter) comment += ";_EXTERNAL_PERIMETER"; } + // F is mm per minute. gcode += m_writer.set_speed(F, "", comment); double path_length = 0.; @@ -2433,21 +2650,76 @@ std::string GCode::set_extruder(unsigned int extruder_id) } // convert a model-space scaled point into G-code coordinates -Pointf GCode::point_to_gcode(const Point &point) const +Vec2d GCode::point_to_gcode(const Point &point) const { - Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset); - return Pointf( - unscale(point.x) + m_origin.x - extruder_offset.x, - unscale(point.y) + m_origin.y - extruder_offset.y); + 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 Pointf &point) const +Point GCode::gcode_to_point(const Vec2d &point) const { - Pointf extruder_offset = EXTRUDER_CONFIG(extruder_offset); + Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); return Point( - scale_(point.x - m_origin.x + extruder_offset.x), - scale_(point.y - m_origin.y + extruder_offset.y)); + 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") { + CONFESS("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/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index 0e5305cb5..34183012a 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -83,8 +83,10 @@ public: 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)), - m_right(float(print_config.wipe_tower_x.value + print_config.wipe_tower_width.value)), + 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), @@ -101,9 +103,14 @@ 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; @@ -112,15 +119,18 @@ private: 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), @@ -132,6 +142,9 @@ public: 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() {} @@ -141,12 +154,12 @@ public: 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 Pointf& origin() const { return m_origin; } - void set_origin(const Pointf &pointf); - void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Pointf(x, y)); } + 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; } - Pointf point_to_gcode(const Point &point) const; - Point gcode_to_point(const Pointf &point) const; + 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; } @@ -186,7 +199,7 @@ protected: const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector<LayerToPrint> &layers, - const ToolOrdering::LayerTools &layer_tools, + 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)); @@ -201,6 +214,7 @@ protected: 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. @@ -216,11 +230,24 @@ protected: 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; + + 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); @@ -234,7 +261,7 @@ protected: /* Origin of print coordinates expressed in unscaled G-code coordinates. This affects the input arguments supplied to the extrude*() and travel_to() methods. */ - Pointf m_origin; + Vec2d m_origin; FullPrintConfig m_config; GCodeWriter m_writer; PlaceholderParser m_placeholder_parser; @@ -255,6 +282,7 @@ protected: // 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; @@ -289,8 +317,10 @@ protected: // Index of a last object copy extruded. std::pair<const PrintObject*, Point> m_last_obj_copy; - // Time estimator - GCodeTimeEstimator m_time_estimator; + // Time estimators + GCodeTimeEstimator m_normal_time_estimator; + GCodeTimeEstimator m_silent_time_estimator; + bool m_silent_time_estimator_enabled; // Analyzer GCodeAnalyzer m_analyzer; @@ -308,6 +338,7 @@ protected: 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 diff --git a/xs/src/libslic3r/GCode/Analyzer.cpp b/xs/src/libslic3r/GCode/Analyzer.cpp index 6530806c4..51d5b1a06 100644 --- a/xs/src/libslic3r/GCode/Analyzer.cpp +++ b/xs/src/libslic3r/GCode/Analyzer.cpp @@ -14,7 +14,7 @@ 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::Pointf3 DEFAULT_START_POSITION = Slic3r::Pointf3(0.0f, 0.0f, 0.0f); +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 { @@ -71,7 +71,7 @@ bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other) 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 Pointf3& start_position, const Pointf3& end_position, float delta_extruder) +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) @@ -80,7 +80,7 @@ GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusi { } -GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, const GCodeAnalyzer::Metadata& data, const Pointf3& start_position, const Pointf3& end_position, float 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) @@ -97,8 +97,8 @@ GCodeAnalyzer::GCodeAnalyzer() void GCodeAnalyzer::reset() { _set_units(Millimeters); - _set_positioning_xyz_type(Absolute); - _set_positioning_e_type(Relative); + _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); @@ -177,6 +177,16 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi _processG1(line); break; } + case 10: // Retract + { + _processG10(line); + break; + } + case 11: // Unretract + { + _processG11(line); + break; + } case 22: // Firmware controlled Retract { _processG22(line); @@ -237,13 +247,13 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi } // 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, GCodeAnalyzer::EPositioningType type, float current_absolute_position) +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 (type == GCodeAnalyzer::Absolute) ? ret : current_absolute_position + ret; + return is_relative ? current_absolute_position + ret : ret; } else return current_absolute_position; @@ -256,7 +266,11 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line) float new_pos[Num_Axis]; for (unsigned char a = X; a < Num_Axis; ++a) { - new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, (a == E) ? _get_positioning_e_type() : _get_positioning_xyz_type(), _get_axis_position((EAxis)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 @@ -305,6 +319,18 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line) _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 @@ -319,12 +345,12 @@ void GCodeAnalyzer::_processG23(const GCodeReader::GCodeLine& line) void GCodeAnalyzer::_processG90(const GCodeReader::GCodeLine& line) { - _set_positioning_xyz_type(Absolute); + _set_global_positioning_type(Absolute); } void GCodeAnalyzer::_processG91(const GCodeReader::GCodeLine& line) { - _set_positioning_xyz_type(Relative); + _set_global_positioning_type(Relative); } void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line) @@ -367,12 +393,12 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line) void GCodeAnalyzer::_processM82(const GCodeReader::GCodeLine& line) { - _set_positioning_e_type(Absolute); + _set_e_local_positioning_type(Absolute); } void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line) { - _set_positioning_e_type(Relative); + _set_e_local_positioning_type(Relative); } void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) @@ -466,24 +492,24 @@ GCodeAnalyzer::EUnits GCodeAnalyzer::_get_units() const return m_state.units; } -void GCodeAnalyzer::_set_positioning_xyz_type(GCodeAnalyzer::EPositioningType type) +void GCodeAnalyzer::_set_global_positioning_type(GCodeAnalyzer::EPositioningType type) { - m_state.positioning_xyz_type = type; + m_state.global_positioning_type = type; } -GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_positioning_xyz_type() const +GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_global_positioning_type() const { - return m_state.positioning_xyz_type; + return m_state.global_positioning_type; } -void GCodeAnalyzer::_set_positioning_e_type(GCodeAnalyzer::EPositioningType type) +void GCodeAnalyzer::_set_e_local_positioning_type(GCodeAnalyzer::EPositioningType type) { - m_state.positioning_e_type = type; + m_state.e_local_positioning_type = type; } -GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_positioning_e_type() const +GCodeAnalyzer::EPositioningType GCodeAnalyzer::_get_e_local_positioning_type() const { - return m_state.positioning_e_type; + return m_state.e_local_positioning_type; } void GCodeAnalyzer::_set_extrusion_role(ExtrusionRole extrusion_role) @@ -561,12 +587,12 @@ void GCodeAnalyzer::_reset_axes_position() ::memset((void*)m_state.position, 0, Num_Axis * sizeof(float)); } -void GCodeAnalyzer::_set_start_position(const Pointf3& position) +void GCodeAnalyzer::_set_start_position(const Vec3d& position) { m_state.start_position = position; } -const Pointf3& GCodeAnalyzer::_get_start_position() const +const Vec3d& GCodeAnalyzer::_get_start_position() const { return m_state.start_position; } @@ -586,9 +612,9 @@ float GCodeAnalyzer::_get_delta_extrusion() const return _get_axis_position(E) - m_state.start_extrusion; } -Pointf3 GCodeAnalyzer::_get_end_position() const +Vec3d GCodeAnalyzer::_get_end_position() const { - return Pointf3(m_state.position[X], m_state.position[Y], m_state.position[Z]); + return Vec3d(m_state.position[X], m_state.position[Y], m_state.position[Z]); } void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type) @@ -647,15 +673,17 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ Metadata data; float z = FLT_MAX; Polyline polyline; - Pointf3 position(FLT_MAX, FLT_MAX, FLT_MAX); + 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) || (data.feedrate != move.data.feedrate) || (z != move.start_position.z) || (position != move.start_position)) + 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(); @@ -665,19 +693,21 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ 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))); + 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; + 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))); + polyline.append(Point(scale_(move.end_position.x()), scale_(move.end_position.y()))); // update current values position = move.end_position; @@ -688,9 +718,10 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ Helper::store_polyline(polyline, data, z, preview_data); // updates preview ranges data - preview_data.extrusion.ranges.height.set_from(height_range); - preview_data.extrusion.ranges.width.set_from(width_range); - preview_data.extrusion.ranges.feedrate.set_from(feedrate_range); + 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) @@ -711,17 +742,21 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) return; Polyline3 polyline; - Pointf3 position(FLT_MAX, FLT_MAX, FLT_MAX); + 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; + 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)) { @@ -733,23 +768,31 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) polyline = Polyline3(); // add both vertices of the move - polyline.append(Point3(scale_(move.start_position.x), scale_(move.start_position.y), scale_(move.start_position.z))); - polyline.append(Point3(scale_(move.end_position.x), scale_(move.end_position.y), scale_(move.end_position.z))); + 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(Point3(scale_(move.end_position.x), scale_(move.end_position.y), scale_(move.end_position.z))); + 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) @@ -761,7 +804,7 @@ void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_da for (const GCodeMove& move : retraction_moves->second) { // store position - Point3 position(scale_(move.start_position.x), scale_(move.start_position.y), scale_(move.start_position.z)); + 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); } } @@ -775,7 +818,7 @@ void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_ for (const GCodeMove& move : unretraction_moves->second) { // store position - Point3 position(scale_(move.start_position.x), scale_(move.start_position.y), scale_(move.start_position.z)); + 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); } } diff --git a/xs/src/libslic3r/GCode/Analyzer.hpp b/xs/src/libslic3r/GCode/Analyzer.hpp index 7939d432d..27a49b869 100644 --- a/xs/src/libslic3r/GCode/Analyzer.hpp +++ b/xs/src/libslic3r/GCode/Analyzer.hpp @@ -75,12 +75,12 @@ public: EType type; Metadata data; - Pointf3 start_position; - Pointf3 end_position; + 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 Pointf3& start_position, const Pointf3& end_position, float delta_extruder); - GCodeMove(EType type, const Metadata& data, const Pointf3& start_position, const Pointf3& 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; @@ -90,10 +90,10 @@ private: struct State { EUnits units; - EPositioningType positioning_xyz_type; - EPositioningType positioning_e_type; + EPositioningType global_positioning_type; + EPositioningType e_local_positioning_type; Metadata data; - Pointf3 start_position; + Vec3d start_position = Vec3d::Zero(); float start_extrusion; float position[Num_Axis]; }; @@ -127,6 +127,12 @@ private: // 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); @@ -170,11 +176,11 @@ private: void _set_units(EUnits units); EUnits _get_units() const; - void _set_positioning_xyz_type(EPositioningType type); - EPositioningType _get_positioning_xyz_type() const; + void _set_global_positioning_type(EPositioningType type); + EPositioningType _get_global_positioning_type() const; - void _set_positioning_e_type(EPositioningType type); - EPositioningType _get_positioning_e_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; @@ -200,15 +206,15 @@ private: // Sets axes position to zero void _reset_axes_position(); - void _set_start_position(const Pointf3& position); - const Pointf3& _get_start_position() const; + 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[]) - Pointf3 _get_end_position() const; + Vec3d _get_end_position() const; // Adds a new move with the given data void _store_move(GCodeMove::EType type); diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp index cd2baeffb..40ccc7b09 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp @@ -23,366 +23,586 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen), m_current_ void CoolingBuffer::reset() { m_current_pos.assign(5, 0.f); - Pointf3 pos = m_gcodegen.writer().get_position(); - m_current_pos[0] = float(pos.x); - m_current_pos[1] = float(pos.y); - m_current_pos[2] = float(pos.z); + 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); } -#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) - -std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id) +struct CoolingLine { - const FullPrintConfig &config = m_gcodegen.config(); - const std::vector<Extruder> &extruders = m_gcodegen.writer().extruders(); - const size_t num_extruders = extruders.size(); + 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; + } - // Calculate the required per extruder time stretches. - struct Adjustment { - Adjustment(unsigned int extruder_id = 0) : extruder_id(extruder_id) {} - // Calculate the total elapsed time per this extruder, adjusted for the slowdown. - float elapsed_time_total() { - float time_total = 0.f; - for (const Line &line : lines) + 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 maximum time when slowing down. - float maximum_time(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (const Line &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 non-adjustable part of the total time. - float non_adjustable_time(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (const Line &line : lines) - if (! line.adjustable(slowdown_external_perimeters)) - time_total += line.time; - return time_total; - } - float slow_down_maximum(bool slowdown_external_perimeters) { - float time_total = 0.f; - for (Line &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; - } + 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; } - return time_total; + time_total += line.time; } - float slow_down_proportional(float factor, bool slowdown_external_perimeters) { - assert(factor >= 1.f); - float time_total = 0.f; - for (Line &line : lines) { - if (line.adjustable(slowdown_external_perimeters)) { - line.slowdown = true; - line.time = std::min(line.time_max, line.time * factor); - } - 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; } - return time_total; + time_total += line.time; } + return time_total; + } - bool operator<(const Adjustment &rhs) const { return this->extruder_id < rhs.extruder_id; } - - struct Line - { - 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, - }; + // 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; + } - Line(unsigned int type, size_t line_start, size_t line_end) : - type(type), line_start(line_start), line_end(line_end), - length(0.f), time(0.f), time_max(0.f), slowdown(false) {} + // 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; + } - 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; + // 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; } + } + } - 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 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; - }; + // 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; - // Extruder, for which the G-code will be adjusted. - unsigned int extruder_id; - // Parsed lines. - std::vector<Line> lines; - }; - std::vector<Adjustment> adjustments(num_extruders, Adjustment()); - for (size_t i = 0; i < num_extruders; ++ i) - adjustments[i].extruder_id = extruders[i].id(); - const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); - // Parse the layer G-code for the moves, which could be adjusted. + // 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> ¤t_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) { - float min_print_speed = float(EXTRUDER_CONFIG(min_print_speed)); - auto adjustment = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder)); - unsigned int initial_extruder = m_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 Adjustment::Line 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); - // Adjustment::Line will contain the trailing '\n'. - if (*line_end == '\n') - ++ line_end; - Adjustment::Line line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); - if (boost::starts_with(sline, "G0 ")) - line.type = Adjustment::Line::TYPE_G0; - else if (boost::starts_with(sline, "G1 ")) - line.type = Adjustment::Line::TYPE_G1; - else if (boost::starts_with(sline, "G92 ")) - line.type = Adjustment::Line::TYPE_G92; - if (line.type) { - // G0, G1 or G92 - // Parse the G-code line. - std::vector<float> new_pos(m_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 & Adjustment::Line::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 |= Adjustment::Line::TYPE_HAS_F; - } - } - // Skip this word. - for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); + 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; + } } - bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); - bool wipe = boost::contains(sline, ";_WIPE"); - if (external_perimeter) - line.type |= Adjustment::Line::TYPE_EXTERNAL_PERIMETER; - if (wipe) - line.type |= Adjustment::Line::TYPE_WIPE; - if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) { - line.type |= Adjustment::Line::TYPE_ADJUSTABLE; - active_speed_modifier = adjustment->lines.size(); + // 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]); } - if ((line.type & Adjustment::Line::TYPE_G92) == 0) { - // G0 or G1. Calculate the duration. - if (config.use_relative_e_distances.value) - // Reset extruder accumulator. - m_current_pos[3] = 0.f; - float dif[4]; - for (size_t i = 0; i < 4; ++ i) - dif[i] = new_pos[i] - m_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; } - if (line.length > 0) - line.time = line.length / new_pos[4]; // current F - line.time_max = line.time; - if ((line.type & Adjustment::Line::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) - line.time_max = (min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / min_print_speed); - if (active_speed_modifier < adjustment->lines.size() && (line.type & Adjustment::Line::TYPE_G1)) { - // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. - assert((line.type & Adjustment::Line::TYPE_HAS_F) == 0); - Adjustment::Line &sm = adjustment->lines[active_speed_modifier]; - 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; - } - } - m_current_pos = std::move(new_pos); - } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { - line.type = Adjustment::Line::TYPE_EXTRUDE_END; - active_speed_modifier = size_t(-1); - } else if (boost::starts_with(sline, toolchange_prefix)) { - // Switch the tool. - line.type = Adjustment::Line::TYPE_SET_TOOL; - unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size()); - if (new_extruder != m_current_extruder) { - m_current_extruder = new_extruder; - min_print_speed = float(EXTRUDER_CONFIG(min_print_speed)); - adjustment = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder)); + // Don't store this line. + line.type = 0; } - } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) { - line.type = Adjustment::Line::TYPE_BRIDGE_FAN_START; - } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) { - line.type = Adjustment::Line::TYPE_BRIDGE_FAN_END; - } else if (boost::starts_with(sline, "G4 ")) { - // Parse the wait time. - line.type = Adjustment::Line::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)); - } - m_current_extruder = initial_extruder; + 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)); } - // Sort the extruders by the increasing slowdown_below_layer_time. - std::vector<size_t> by_slowdown_layer_time; - by_slowdown_layer_time.reserve(num_extruders); + 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_total_non_adjustable = 0.f; - for (size_t i = 0; i < num_extruders; ++ i) { - if (config.cooling.get_at(extruders[i].id())) - by_slowdown_layer_time.emplace_back(i); - else - elapsed_time_total_non_adjustable += adjustments[i].elapsed_time_total(); + 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_layer_time.begin(), by_slowdown_layer_time.end(), - [&config, &extruders](const size_t idx1, const size_t idx2){ - return config.slowdown_below_layer_time.get_at(extruders[idx1].id()) < - config.slowdown_below_layer_time.get_at(extruders[idx2].id()); - }); + 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; }); - // Elapsed time after adjustment. - float elapsed_time_total = 0.f; - { - // Elapsed time for the already adjusted extruders. - float elapsed_time_total0 = elapsed_time_total_non_adjustable; - for (size_t i_by_slowdown_layer_time = 0; i_by_slowdown_layer_time < by_slowdown_layer_time.size(); ++ i_by_slowdown_layer_time) { - // Idx in adjustments. - size_t idx = by_slowdown_layer_time[i_by_slowdown_layer_time]; - // Macro to sum or adjust all sections starting with i_by_slowdown_layer_time. - #define FORALL_UNPROCESSED(ACCUMULATOR, ACTION) \ - ACCUMULATOR = elapsed_time_total0;\ - for (size_t j = i_by_slowdown_layer_time; j < by_slowdown_layer_time.size(); ++ j) \ - ACCUMULATOR += adjustments[by_slowdown_layer_time[j]].ACTION - // Calculate the current adjusted elapsed_time_total over the non-finalized extruders. - float total; - FORALL_UNPROCESSED(total, elapsed_time_total()); - float slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(adjustments[idx].extruder_id)) * 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. + 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 { - // 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; - FORALL_UNPROCESSED(max_time, maximum_time(true)); - if (max_time > slowdown_below_layer_time) { - // By slowing every possible movement, the layer time could be reached. Now decide - // whether the external perimeters shall be slowed down as well. - float max_time_nep; - FORALL_UNPROCESSED(max_time_nep, maximum_time(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; - FORALL_UNPROCESSED(non_adjustable_time, 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 - non_adjustable_time); - assert(factor > 1.f); - FORALL_UNPROCESSED(total, slow_down_proportional(factor, false)); - if (total > 0.95f * slowdown_below_layer_time) - break; - } - } else { - // Slow down everything. First slow down the non-external perimeters to maximum. - FORALL_UNPROCESSED(total, slow_down_maximum(false)); - // Slow down the external perimeters proportionally. - float non_adjustable_time; - FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(true)); - for (size_t iter = 0; iter < 5; ++ iter) { - float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time); - assert(factor > 1.f); - FORALL_UNPROCESSED(total, slow_down_proportional(factor, true)); - if (total > 0.95f * slowdown_below_layer_time) - break; - } - } - } else { - // Slow down to maximum possible. - FORALL_UNPROCESSED(total, slow_down_maximum(true)); - } + // Slow down to maximum possible. + for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) + (*it)->slowdown_to_minimum_feedrate(true); } - #undef FORALL_UNPROCESSED - // Sum the final elapsed time for all extruders up to i_by_slowdown_layer_time. - if (i_by_slowdown_layer_time + 1 == by_slowdown_layer_time.size()) - // Optimization for single extruder prints. - elapsed_time_total0 = total; - else - elapsed_time_total0 += adjustments[idx].elapsed_time_total(); } - elapsed_time_total = elapsed_time_total0; + elapsed_time_total0 += adj.elapsed_time_total(); } - // Transform the G-code. - // First sort the adjustment lines by their position in the source G-code. - std::vector<const Adjustment::Line*> lines; + 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 Adjustment &adj : adjustments) + for (const PerExtruderAdjustments &adj : per_extruder_adjustments) n_lines += adj.lines.size(); lines.reserve(n_lines); - for (const Adjustment &adj : adjustments) - for (const Adjustment::Line &line : adj.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 Adjustment::Line *ln1, const Adjustment::Line *ln2) { return ln1->line_start < ln2->line_start; } ); + 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; @@ -390,8 +610,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ int fan_speed = -1; bool bridge_fan_control = false; int bridge_fan_speed = 0; - auto change_extruder_set_fan = [ this, layer_id, elapsed_time_total, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { + 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)) { @@ -399,17 +620,18 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ 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 (elapsed_time_total < slowdown_below_layer_time) { + 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 (elapsed_time_total < fan_below_layer_time) { + } else if (layer_time < fan_below_layer_time) { // Layer time quite short. Enable the fan proportionally according to the current layer time. - assert(elapsed_time_total >= slowdown_below_layer_time); - double t = (elapsed_time_total - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_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; @@ -421,49 +643,50 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ new_gcode += m_gcodegen.writer().set_fan(fan_speed); } }; - change_extruder_set_fan(); - const char *pos = gcode.c_str(); - int current_feedrate = 0; - for (const Adjustment::Line *line : lines) { + 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 & Adjustment::Line::TYPE_SET_TOOL) { + 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 & Adjustment::Line::TYPE_BRIDGE_FAN_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 & Adjustment::Line::TYPE_BRIDGE_FAN_END) { + } 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 & Adjustment::Line::TYPE_EXTRUDE_END) { + } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { // Just remove this comment. - } else if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE | Adjustment::Line::TYPE_HAS_F)) { + } 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 *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->length / line->time) + 0.5)); + 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 & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) || line->length == 0.) { + } 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 { @@ -488,7 +711,7 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ 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); + 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); @@ -497,22 +720,22 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_ } // Process the rest of the line. if (end < line_end) { - if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::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 & Adjustment::Line::TYPE_EXTERNAL_PERIMETER) + 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 & Adjustment::Line::TYPE_WIPE) + 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); - } + 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); + new_gcode.append(line_start, line_end - line_start); } pos = line_end; } diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.hpp b/xs/src/libslic3r/GCode/CoolingBuffer.hpp index f85c470b3..bf4b082e2 100644 --- a/xs/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/xs/src/libslic3r/GCode/CoolingBuffer.hpp @@ -9,13 +9,17 @@ namespace Slic3r { class GCode; class Layer; - -/* -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. -*/ - +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); @@ -25,7 +29,12 @@ public: GCode* gcodegen() { return &m_gcodegen; } private: - CoolingBuffer& operator=(const CoolingBuffer&); + CoolingBuffer& operator=(const CoolingBuffer&) = delete; + std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_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; @@ -34,6 +43,9 @@ private: 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; }; } diff --git a/xs/src/libslic3r/GCode/PreviewData.cpp b/xs/src/libslic3r/GCode/PreviewData.cpp index 1923505e4..9cf9716e0 100644 --- a/xs/src/libslic3r/GCode/PreviewData.cpp +++ b/xs/src/libslic3r/GCode/PreviewData.cpp @@ -2,7 +2,12 @@ #include "PreviewData.hpp" #include <float.h> #include <wx/intl.h> -#include "slic3r/GUI/GUI.hpp" +#include <I18N.hpp> + +#include <boost/format.hpp> + +//! macro used to mark string used at localization, +#define L(s) (s) namespace Slic3r { @@ -85,6 +90,12 @@ void GCodePreviewData::Range::update_from(float 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; @@ -93,17 +104,31 @@ void GCodePreviewData::Range::set_from(const Range& other) float GCodePreviewData::Range::step_size() const { - return (max - min) / (float)Colors_Count; + return (max - min) / (float)(Colors_Count - 1); } -const GCodePreviewData::Color& GCodePreviewData::Range::get_color_at_max() const +GCodePreviewData::Color GCodePreviewData::Range::get_color_at(float value) const { - return colors[Colors_Count - 1]; -} + if (empty()) + return Color::Dummy; -const GCodePreviewData::Color& GCodePreviewData::Range::get_color_at(float value) const -{ - return empty() ? get_color_at_max() : colors[clamp((unsigned int)0, Colors_Count - 1, (unsigned int)((value - min) / step_size()))]; + 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) @@ -158,9 +183,6 @@ 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)); - ::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)); for (unsigned int i = 0; i < Num_Extrusion_Roles; ++i) { @@ -198,12 +220,13 @@ 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 Point3& position, float width, float height) +GCodePreviewData::Retraction::Position::Position(const Vec3crd& position, float width, float height) : position(position) , width(width) , height(height) @@ -228,6 +251,11 @@ GCodePreviewData::GCodePreviewData() 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(); @@ -237,6 +265,10 @@ void GCodePreviewData::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(); @@ -248,24 +280,29 @@ bool GCodePreviewData::empty() const return extrusion.layers.empty() && travel.polylines.empty() && retraction.positions.empty() && unretraction.positions.empty(); } -const GCodePreviewData::Color& GCodePreviewData::get_extrusion_role_color(ExtrusionRole role) const +GCodePreviewData::Color GCodePreviewData::get_extrusion_role_color(ExtrusionRole role) const { return extrusion.role_colors[role]; } -const GCodePreviewData::Color& GCodePreviewData::get_extrusion_height_color(float height) const +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 extrusion.ranges.height.get_color_at(height); + return ranges.width.get_color_at(width); } -const GCodePreviewData::Color& GCodePreviewData::get_extrusion_width_color(float width) const +GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) const { - return extrusion.ranges.width.get_color_at(width); + return ranges.feedrate.get_color_at(feedrate); } -const GCodePreviewData::Color& GCodePreviewData::get_extrusion_feedrate_color(float feedrate) const +GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const { - return extrusion.ranges.feedrate.get_color_at(feedrate); + 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) @@ -334,6 +371,8 @@ std::string GCodePreviewData::get_legend_title() const 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"); } @@ -348,11 +387,12 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: 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 (unsigned int i = 0; i < Range::Colors_Count; ++i) + for (int i = Range::Colors_Count - 1; i >= 0; --i) { - char buf[32]; - sprintf(buf, "%.*f/%.*f", decimals, scale_factor * (range.min + (float)i * step), decimals, scale_factor * (range.min + (float)(i + 1) * step)); + char buf[1024]; + sprintf(buf, "%.*f", decimals, scale_factor * (range.min + (float)i * step)); list.emplace_back(buf, range.colors[i]); } } @@ -370,24 +410,29 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: items.reserve(last_valid - first_valid + 1); for (unsigned int i = (unsigned int)first_valid; i <= (unsigned int)last_valid; ++i) { - items.emplace_back(_CHB(extrusion.role_names[i].c_str()).data(), extrusion.role_colors[i]); + items.emplace_back(Slic3r::I18N::translate(extrusion.role_names[i]), extrusion.role_colors[i]); } break; } case Extrusion::Height: { - Helper::FillListFromRange(items, extrusion.ranges.height, 3, 1.0f); + Helper::FillListFromRange(items, ranges.height, 3, 1.0f); break; } case Extrusion::Width: { - Helper::FillListFromRange(items, extrusion.ranges.width, 3, 1.0f); + Helper::FillListFromRange(items, ranges.width, 3, 1.0f); break; } case Extrusion::Feedrate: { - Helper::FillListFromRange(items, extrusion.ranges.feedrate, 0, 1.0f); + 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: @@ -396,13 +441,9 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: items.reserve(tools_colors_count); for (unsigned int i = 0; i < tools_colors_count; ++i) { - char buf[MIN_BUF_LENGTH_FOR_L]; - sprintf(buf, _CHB(L("Extruder %d")), i + 1); - GCodePreviewData::Color color; ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float)); - - items.emplace_back(buf, color); + items.emplace_back((boost::format(Slic3r::I18N::translate(L("Extruder %d"))) % (i + 1)).str(), color); } break; diff --git a/xs/src/libslic3r/GCode/PreviewData.hpp b/xs/src/libslic3r/GCode/PreviewData.hpp index 9fb2dc464..ab74993f5 100644 --- a/xs/src/libslic3r/GCode/PreviewData.hpp +++ b/xs/src/libslic3r/GCode/PreviewData.hpp @@ -37,11 +37,19 @@ public: 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; - const Color& get_color_at(float value) const; - const Color& get_color_at_max() const; + Color get_color_at(float value) const; + }; + + struct Ranges + { + Range height; + Range width; + Range feedrate; + Range volumetric_rate; }; struct LegendItem @@ -62,6 +70,7 @@ public: Height, Width, Feedrate, + VolumetricRate, Tool, Num_View_Types }; @@ -71,13 +80,6 @@ public: static const std::string Default_Extrusion_Role_Names[Num_Extrusion_Roles]; static const EViewType Default_View_Type; - struct Ranges - { - Range height; - Range width; - Range feedrate; - }; - struct Layer { float z; @@ -91,7 +93,6 @@ public: EViewType view_type; Color role_colors[Num_Extrusion_Roles]; std::string role_names[Num_Extrusion_Roles]; - Ranges ranges; LayersList layers; unsigned int role_flags; @@ -150,11 +151,11 @@ public: struct Position { - Point3 position; + Vec3crd position; float width; float height; - Position(const Point3& position, float width, float height); + Position(const Vec3crd& position, float width, float height); }; typedef std::vector<Position> PositionsList; @@ -178,6 +179,7 @@ public: Retraction retraction; Retraction unretraction; Shell shell; + Ranges ranges; GCodePreviewData(); @@ -185,10 +187,11 @@ public: void reset(); bool empty() const; - const Color& get_extrusion_role_color(ExtrusionRole role) const; - const Color& get_extrusion_height_color(float height) const; - const Color& get_extrusion_width_color(float width) const; - const Color& get_extrusion_feedrate_color(float feedrate) 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); diff --git a/xs/src/libslic3r/GCode/PrintExtents.cpp b/xs/src/libslic3r/GCode/PrintExtents.cpp index 43352ee82..00b8838b6 100644 --- a/xs/src/libslic3r/GCode/PrintExtents.cpp +++ b/xs/src/libslic3r/GCode/PrintExtents.cpp @@ -19,10 +19,10 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c if (! polyline.points.empty()) bbox.merge(polyline.points.front()); for (const Point &pt : polyline.points) { - bbox.min.x = std::min(bbox.min.x, pt.x - radius); - bbox.min.y = std::min(bbox.min.y, pt.y - radius); - bbox.max.x = std::max(bbox.max.x, pt.x + radius); - bbox.max.y = std::max(bbox.max.y, pt.y + radius); + 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; } @@ -32,8 +32,8 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusio BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)); BoundingBoxf bboxf; if (! empty(bbox)) { - bboxf.min = Pointf::new_unscale(bbox.min); - bboxf.max = Pointf::new_unscale(bbox.max); + bboxf.min = unscale(bbox.min); + bboxf.max = unscale(bbox.max); bboxf.defined = true; } return bboxf; @@ -46,8 +46,8 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width))); BoundingBoxf bboxf; if (! empty(bbox)) { - bboxf.min = Pointf::new_unscale(bbox.min); - bboxf.max = Pointf::new_unscale(bbox.max); + bboxf.min = unscale(bbox.min); + bboxf.max = unscale(bbox.max); bboxf.defined = true; } return bboxf; @@ -60,8 +60,8 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width))); BoundingBoxf bboxf; if (! empty(bbox)) { - bboxf.min = Pointf::new_unscale(bbox.min); - bboxf.max = Pointf::new_unscale(bbox.max); + bboxf.min = unscale(bbox.min); + bboxf.max = unscale(bbox.max); bboxf.defined = true; } return bboxf; @@ -121,9 +121,9 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object 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._shifted_copies) { + for (const Point &offset : print_object.copies()) { BoundingBoxf bbox_translated(bbox_this); - bbox_translated.translate(Pointf::new_unscale(offset)); + bbox_translated.translate(unscale(offset)); bbox.merge(bbox_translated); } } @@ -134,6 +134,12 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object // 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) @@ -142,14 +148,11 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { const WipeTower::Extrusion &e = tcr.extrusions[i]; if (e.width > 0) { - Pointf p1((&e - 1)->pos.x, (&e - 1)->pos.y); - Pointf p2(e.pos.x, e.pos.y); - bbox.merge(p1); - coordf_t radius = 0.5 * e.width; - bbox.min.x = std::min(bbox.min.x, std::min(p1.x, p2.x) - radius); - bbox.min.y = std::min(bbox.min.y, std::min(p1.y, p2.y) - radius); - bbox.max.x = std::max(bbox.max.x, std::max(p1.x, p2.x) + radius); - bbox.max.y = std::max(bbox.max.y, std::max(p1.y, p2.y) + radius); + 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); } } } @@ -166,14 +169,14 @@ BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print) for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { const WipeTower::Extrusion &e = tcr.extrusions[i]; if (e.width > 0) { - Pointf p1((&e - 1)->pos.x, (&e - 1)->pos.y); - Pointf p2(e.pos.x, e.pos.y); + 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.x = std::min(bbox.min.x, std::min(p1.x, p2.x) - radius); - bbox.min.y = std::min(bbox.min.y, std::min(p1.y, p2.y) - radius); - bbox.max.x = std::max(bbox.max.x, std::max(p1.x, p2.x) + radius); - bbox.max.y = std::max(bbox.max.y, std::max(p1.y, p2.y) + radius); + 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); } } } diff --git a/xs/src/libslic3r/GCode/ToolOrdering.cpp b/xs/src/libslic3r/GCode/ToolOrdering.cpp index 6e03d89c9..175b69447 100644 --- a/xs/src/libslic3r/GCode/ToolOrdering.cpp +++ b/xs/src/libslic3r/GCode/ToolOrdering.cpp @@ -15,6 +15,24 @@ 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) @@ -48,11 +66,14 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude // (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 : print.objects()) { + 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); @@ -65,7 +86,7 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool } // Collect extruders reuqired to print the layers. - for (auto object : print.objects()) + for (auto object : objects) this->collect_extruders(*object); // Reorder the extruders to minimize tool switches. @@ -76,9 +97,10 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool this->collect_extruder_statistics(prime_multi_material); } -ToolOrdering::LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) + +LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) { - auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), ToolOrdering::LayerTools(print_z - EPSILON)); + 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) { @@ -102,7 +124,7 @@ void ToolOrdering::initialize_layers(std::vector<coordf_t> &zs) 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_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1]), m_print_config_ptr)); i = j; } } @@ -134,12 +156,29 @@ void ToolOrdering::collect_extruders(const PrintObject &object) if (layerm == nullptr) continue; const PrintRegion ®ion = *object.print()->regions()[region_id]; + if (! layerm->perimeters.entities.empty()) { - layer_tools.extruders.push_back(region.config().perimeter_extruder.value); + 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); @@ -148,19 +187,33 @@ void ToolOrdering::collect_extruders(const PrintObject &object) 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) - 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; } } - // Sort and remove duplicates - for (LayerTools < : m_layer_tools) - sort_remove_duplicates(lt.extruders); + 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. @@ -217,6 +270,8 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id) } } + + void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z) { if (m_layer_tools.empty()) @@ -327,4 +382,250 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material) } } + + +// 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(), [<](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(), [<](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/xs/src/libslic3r/GCode/ToolOrdering.hpp b/xs/src/libslic3r/GCode/ToolOrdering.hpp index c92806b19..4dcf6516a 100644 --- a/xs/src/libslic3r/GCode/ToolOrdering.hpp +++ b/xs/src/libslic3r/GCode/ToolOrdering.hpp @@ -9,38 +9,99 @@ namespace Slic3r { class Print; class PrintObject; +class LayerTools; -class ToolOrdering + + +// 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: - struct LayerTools - { - LayerTools(const coordf_t z) : - print_z(z), - has_object(false), - has_support(false), - has_wipe_tower(false), - wipe_tower_partitions(0), - wipe_tower_layer_height(0.) {} - - bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; } - bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; } - - 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; - }; + 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 @@ -72,7 +133,7 @@ public: 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(); } - const std::vector<LayerTools>& layer_tools() const { return m_layer_tools; } + 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: @@ -80,17 +141,22 @@ private: 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; + 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/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index 2c92e16e6..c35a0feb3 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -17,12 +17,37 @@ public: 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; }; @@ -90,6 +115,9 @@ public: // 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; @@ -112,17 +140,15 @@ public: 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, - // May be used by a stand alone post processor. - Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + 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, Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + 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(Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) = 0; + 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, diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 069f975dd..2c96cce7b 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -1,10 +1,25 @@ +/* + +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 <fstream> #include <iostream> #include <vector> +#include <numeric> #include "Analyzer.hpp" @@ -16,6 +31,7 @@ #define strcasecmp _stricmp #endif + namespace Slic3r { @@ -24,17 +40,38 @@ namespace PrusaMultiMaterial { class Writer { public: - Writer() : + 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_layer_height(0.f), m_preview_suppressed(false), - m_elapsed_time(0.f) {} - - Writer& set_initial_position(const WipeTower::xy &pos) { - m_start_pos = pos; + 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; } @@ -44,17 +81,20 @@ public: Writer& set_z(float z) { m_current_z = z; return *this; } - Writer& set_layer_height(float layer_height) - { m_layer_height = layer_height; 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() { m_preview_suppressed = true; return *this; } - Writer& resume_preview() { m_preview_suppressed = false; return *this; } + 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) { @@ -67,8 +107,9 @@ public: 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& start_pos() const { return m_start_pos; } 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; } // Extrude with an explicitely provided amount of extrusion. @@ -81,22 +122,30 @@ public: float dx = x - m_current_pos.x; float dy = y - m_current_pos.y; double len = sqrt(dx*dx+dy*dy); + + + // 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) * m_filament_area / (len * m_layer_height)); + float width = float(double(e) * /*Filament_Area*/2.40528 / (len * m_layer_height)); // Correct for the roundings of a squished extrusion. - width += float(m_layer_height * (1. - M_PI / 4.)); - if (m_extrusions.empty() || m_extrusions.back().pos != m_current_pos) - m_extrusions.emplace_back(WipeTower::Extrusion(m_current_pos, 0, m_current_tool)); - m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(x, y), width, m_current_tool)); + 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 (x != m_current_pos.x) - m_gcode += set_format_X(x); - if (y != m_current_pos.y) - m_gcode += set_format_Y(y); + 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); @@ -104,6 +153,9 @@ public: 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"; @@ -130,6 +182,31 @@ public: 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) { @@ -143,7 +220,7 @@ public: 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. @@ -153,6 +230,17 @@ public: 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) { @@ -198,8 +286,19 @@ public: // 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, "M%d S%d\n", wait ? 109 : 104, temperature); + sprintf(buf, "G4 S%.3f\n", time); m_gcode += buf; return *this; }; @@ -243,6 +342,25 @@ public: 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 : "; @@ -279,9 +397,16 @@ private: std::string m_gcode; std::vector<WipeTower::Extrusion> m_extrusions; float m_elapsed_time; - const double m_filament_area = 0.25*M_PI*1.75*1.75; - - std::string set_format_X(float x) { + 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; + + std::string set_format_X(float x) + { char buf[64]; sprintf(buf, " X%.3f", x); m_current_pos.x = x; @@ -315,32 +440,11 @@ private: } Writer& operator=(const Writer &rhs); -}; - -/* -class Material -{ -public: - std::string name; - std::string type; - - struct RammingStep { -// float length; - float extrusion_multiplier; // sirka linky - float extrusion; - float speed; - }; - std::vector<RammingStep> ramming_sequence; +}; // class Writer - // Number and speed of the cooling moves. - std::vector<float> cooling_moves; +}; // namespace PrusaMultiMaterial - // Percentage of the speed overide, in pairs of <z, percentage> - std::vector<std::pair<float, int>> speed_override; -}; -*/ -} // namespace PrusaMultiMaterial WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *name) { @@ -365,6 +469,7 @@ WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *nam 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. @@ -373,90 +478,52 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( 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, - // May be used by a stand alone post processor. - Purpose purpose) + bool last_wipe_inside_wipe_tower) { this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false); - - float wipe_area = m_wipe_area; - // Calculate the amount of wipe over the wipe tower brim following the prime, decrease wipe_area - // with the amount of material extruded over the brim. - { - // Simulate the brim extrusions, summ the length of the extrusion. - float e_length = this->tool_change(0, false, PURPOSE_EXTRUDE).total_extrusion_length_in_plane(); - // Shrink wipe_area by the amount of extrusion extruded by the finish_layer(). - // Y stepping of the wipe extrusions. - float dy = m_perimeter_width * 0.8f; - // Number of whole wipe lines, that would be extruded to wipe as much material as the finish_layer(). - // Minimum wipe area is 5mm wide. - //FIXME calculate the purge_lines_width precisely. - float purge_lines_width = 1.3f; - wipe_area = std::max(5.f, m_wipe_area - float(floor(e_length / m_wipe_tower_width)) * dy - purge_lines_width); - } - - this->set_layer(first_layer_height, first_layer_height, tools.size(), true, false); - m_num_layer_changes = 0; - m_current_tool = tools.front(); - + 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); - box_coordinates cleaning_box(xy(5.f, 0.f), m_wipe_tower_width, wipe_area); - PrusaMultiMaterial::Writer writer; + 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_layer_height(m_layer_height) .set_initial_tool(m_current_tool) .append(";--------------------\n" "; CP PRIMING START\n") .append(";--------------------\n") .speed_override(100); - // Always move to the starting position. - writer.set_initial_position(xy(0.f, 0.f)) - .travel(cleaning_box.ld, 7200) - // Increase the extruder driver current to allow fast ramming. - .set_extruder_trimpot(750); - - // adds tag for analyzer - char buf[32]; - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); - writer.append(buf); - - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - float y_end = 0.f; - for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { - unsigned int tool = tools[idx_tool]; - // Select the tool, set a speed override for soluble and flex materials. - toolchange_Change(writer, tool, m_material[tool]); - // Prime the tool. - toolchange_Load(writer, cleaning_box); - if (idx_tool + 1 == tools.size()) { - // Last tool should not be unloaded, but it should be wiped enough to become of a pure color. - if (last_wipe_inside_wipe_tower) { - // Shrink the last wipe area to the area of the other purge areas, - // remember the last initial wipe width to be purged into the 1st layer of the wipe tower. - m_initial_extra_wipe = std::max(0.f, wipe_area - (y_end + 0.5f * 0.85f * m_perimeter_width - cleaning_box.ld.y)); - cleaning_box.lu.y -= m_initial_extra_wipe; - cleaning_box.ru.y -= m_initial_extra_wipe; - } - toolchange_Wipe(writer, cleaning_box, false); - } 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); - // Change the extruder temperature to the temperature of the next filament before starting the cooling moves. - toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], m_first_layer_temperature[tools[idx_tool+1]]); - // Save the y end of the non-last priming area. - y_end = writer.y(); - cleaning_box.translate(m_wipe_tower_width, 0.f); - writer.travel(cleaning_box.ld, 7200); - } - ++ m_num_tool_changes; - } - } + 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; + } // Reset the extruder current to a normal value. writer.set_extruder_trimpot(550) @@ -467,267 +534,169 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( ";------------------\n" "\n\n"); - // Force m_idx_tool_change_in_layer to -1, so that tool_change() will know to extrude the wipe tower brim. - m_idx_tool_change_in_layer = (unsigned int)(-1); + // so that tool_change() will know to extrude the wipe tower brim: + m_print_brim = true; ToolChangeResult result; - result.print_z = m_z_pos; - result.layer_height = m_layer_height; + 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(); - result.end_pos = writer.pos(); + 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, Purpose purpose) +WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer) { - // Either it is the last tool unload, - // or there must be a nonzero wipe tower partitions available. -// assert(tool < 0 || it_layer_tools->wipe_tower_partitions > 0); - - if (m_idx_tool_change_in_layer == (unsigned int)(-1)) { - // First layer, prime the extruder. - return toolchange_Brim(purpose); - } - - float wipe_area = m_wipe_area; - if (++ m_idx_tool_change_in_layer < (unsigned int)m_max_color_changes && last_in_layer) { - // This tool_change() call will be followed by a finish_layer() call. - // Try to shrink the wipe_area to save material, as less than usual wipe is required - // if this step is foolowed by finish_layer() extrusions wiping the same extruder. - for (size_t iter = 0; iter < 3; ++ iter) { - // Simulate the finish_layer() extrusions, summ the length of the extrusion. - float e_length = 0.f; - { - unsigned int old_idx_tool_change = m_idx_tool_change_in_layer; - float old_wipe_start_y = m_current_wipe_start_y; - m_current_wipe_start_y += wipe_area; - e_length = this->finish_layer(PURPOSE_EXTRUDE).total_extrusion_length_in_plane(); - m_idx_tool_change_in_layer = old_idx_tool_change; - m_current_wipe_start_y = old_wipe_start_y; - } - // Shrink wipe_area by the amount of extrusion extruded by the finish_layer(). - // Y stepping of the wipe extrusions. - float dy = m_perimeter_width * 0.8f; - // Number of whole wipe lines, that would be extruded to wipe as much material as the finish_layer(). - float num_lines_extruded = floor(e_length / m_wipe_tower_width); - // Minimum wipe area is 5mm wide. - wipe_area = m_wipe_area - num_lines_extruded * dy; - if (wipe_area < 5.) { - wipe_area = 5.; + 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( - m_wipe_tower_pos + xy(0.f, m_current_wipe_start_y + 0.5f * m_perimeter_width), - m_wipe_tower_width, - wipe_area - m_perimeter_width); + 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; + PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width); writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_layer_height(m_layer_height) - .set_initial_tool(m_current_tool) - .append(";--------------------\n" - "; CP TOOLCHANGE START\n") - .comment_with_value(" toolchange #", m_num_tool_changes) - .comment_material(m_material[m_current_tool]) - .append(";--------------------\n") - .speed_override(100); - - xy initial_position = ((m_current_shape == SHAPE_NORMAL) ? cleaning_box.ld : cleaning_box.lu) + - xy(m_perimeter_width, ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width); - - if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Scaffold leaks terribly, reduce leaking by a full retract when going to the wipe tower. - float initial_retract = ((m_material[m_current_tool] == SCAFF) ? 1.f : 0.5f) * m_retract; - writer // Lift for a Z hop. - .z_hop(m_zhop, 7200) - // Additional retract on move to tower. - .retract(initial_retract, 3600) - // Move to a starting position, one perimeter width inside the cleaning box. - .travel(initial_position, 7200) - // Unlift for a Z hop. - .z_hop_reset(7200) - // Additional retract on move to tower. - .load(initial_retract, 3600) - .load(m_retract, 1500); - } else { - // Already at the initial position. - writer.set_initial_position(initial_position); - } - - // adds tag for analyzer - char buf[32]; - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); - writer.append(buf); - - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // 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) { - toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], - m_is_first_layer ? m_first_layer_temperature[tool] : m_temperature[tool]); - // This is not the last change. - // Change the tool, set a speed override for soluble and flex materials. - toolchange_Change(writer, tool, m_material[tool]); - toolchange_Load(writer, cleaning_box); - // Wipe the newly loaded filament until the end of the assigned wipe area. - toolchange_Wipe(writer, cleaning_box, false); - // Draw a perimeter around cleaning_box and wipe. - box_coordinates box = cleaning_box; - if (m_current_shape == SHAPE_REVERSED) { - std::swap(box.lu, box.ld); - std::swap(box.ru, box.rd); - } - // Draw a perimeter around cleaning_box. - writer.travel(box.lu, 7000) - .extrude(box.ld, 3200).extrude(box.rd) - .extrude(box.ru).extrude(box.lu); - // Wipe the nozzle. - //if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - writer.travel(box.ru, 7200) - .travel(box.lu); - } else - toolchange_Unload(writer, cleaning_box, m_material[m_current_tool], m_temperature[m_current_tool]); - - // Reset the extruder current to a normal value. - writer.set_extruder_trimpot(550) - .feedrate(6000) - .flush_planner_queue() - .reset_extruder() - .append("; CP TOOLCHANGE END\n" - ";------------------\n" - "\n\n"); - - ++ m_num_tool_changes; - m_current_wipe_start_y += wipe_area; - } + .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. + } else + toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material, m_filpar[m_current_tool].temperature); + + ++ m_num_tool_changes; + 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"); ToolChangeResult result; - result.print_z = m_z_pos; - result.layer_height = m_layer_height; + 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(); - result.end_pos = writer.pos(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); return result; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(Purpose purpose, bool sideOnly, float y_offset) +WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset) { const box_coordinates wipeTower_box( - m_wipe_tower_pos, + WipeTower::xy(0.f, 0.f), m_wipe_tower_width, - m_wipe_area * float(m_max_color_changes) - m_perimeter_width / 2); + m_wipe_tower_depth); - PrusaMultiMaterial::Writer writer; + PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width); writer.set_extrusion_flow(m_extrusion_flow * 1.1f) - // Let the writer know the current Z position as a base for Z-hop. - .set_z(m_z_pos) - .set_layer_height(m_layer_height) + .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"); + .append(";-------------------------------------\n" + "; CP WIPE TOWER FIRST LAYER BRIM START\n"); xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0); - - if (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - // Move with Z hop. - writer.z_hop(m_zhop, 7200) - .travel(initial_position, 6000) - .z_hop_reset(7200); - else - writer.set_initial_position(initial_position); - - // adds tag for analyzer - char buf[32]; - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erWipeTower); - writer.append(buf); - - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Prime the extruder 10*m_perimeter_width left along the vertical edge of the wipe tower. - writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), - 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. - // toolchange_Change(writer, int(tool), m_material[tool]); - - if (sideOnly) { - float x_offset = m_perimeter_width; - for (size_t i = 0; i < 4; ++ i, x_offset += m_perimeter_width) - writer.travel (wipeTower_box.ld + xy(- x_offset, y_offset), 7000) - .extrude(wipeTower_box.lu + xy(- x_offset, - y_offset), 2100); - writer.travel(wipeTower_box.rd + xy(x_offset, y_offset), 7000); - x_offset = m_perimeter_width; - for (size_t i = 0; i < 4; ++ i, x_offset += m_perimeter_width) - writer.travel (wipeTower_box.rd + xy(x_offset, y_offset), 7000) - .extrude(wipeTower_box.ru + xy(x_offset, - y_offset), 2100); - } else { - // Extrude 4 rounds of a brim around the future wipe tower. - box_coordinates box(wipeTower_box); - //FIXME why is the box shifted in +Y by 0.5f * m_perimeter_width? - box.translate(0.f, 0.5f * m_perimeter_width); - box.expand(0.5f * 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); - } - } - - if (m_initial_extra_wipe > m_perimeter_width * 1.9f) { - box_coordinates cleaning_box( - m_wipe_tower_pos + xy(0.f, 0.5f * m_perimeter_width), - m_wipe_tower_width, - m_initial_extra_wipe - m_perimeter_width); - writer.travel(cleaning_box.ld + xy(m_perimeter_width, 0.5f * m_perimeter_width), 6000); - // Wipe the newly loaded filament until the end of the assigned wipe area. - toolchange_Wipe(writer, cleaning_box, true); - // Draw a perimeter around cleaning_box. - writer.travel(cleaning_box.lu, 7000) - .extrude(cleaning_box.ld, 3200).extrude(cleaning_box.rd) - .extrude(cleaning_box.ru).extrude(cleaning_box.lu); - m_current_wipe_start_y = m_initial_extra_wipe; - } - - // Move to the front left corner. - writer.travel(wipeTower_box.ld, 7000); - - //if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - // Wipe along the front edge. - // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - writer.travel(wipeTower_box.rd) - .travel(wipeTower_box.ld); - - writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" - ";-----------------------------------\n"); - - // Mark the brim as extruded. - m_idx_tool_change_in_layer = 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 ToolChangeResult result; - result.print_z = m_z_pos; - result.layer_height = m_layer_height; + 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(); - result.end_pos = writer.pos(); + 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, @@ -735,87 +704,146 @@ void WipeTowerPrusaMM::toolchange_Unload( const material_type current_material, const int new_temperature) { - float xl = cleaning_box.ld.x + 0.5f * m_perimeter_width; - float xr = cleaning_box.rd.x - 0.5f * m_perimeter_width; - float y_step = ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width; - - writer.append("; CP TOOLCHANGE UNLOAD\n"); - - // Ram the hot material out of the extruder melt zone. - // Current extruder position is on the left, one perimeter inside the cleaning box in both X and Y. - float e0 = m_perimeter_width * m_extrusion_flow; - float e = (xr - xl) * m_extrusion_flow; - switch (current_material) - { - case ABS: - // ramming start end y increment amount feedrate - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.2f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.2f, e0, 1.6f * e, 4600) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.2f, e0, 1.8f * e, 5000) - .ram(xr - m_perimeter_width * 2, xl + m_perimeter_width * 2, y_step * 1.2f, e0, 1.8f * e, 5000); - break; - case PVA: - // Used for the PrimaSelect PVA - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.75f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.5f, 0, 1.75f * e, 4500) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.5f, 0, 1.75f * e, 4800) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.5f, 0, 1.75f * e, 5000); - break; - case SCAFF: - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 2.f, 0, 1.75f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 3.f, 0, 2.34f * e, 4600) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 3.f, 0, 2.63f * e, 5200); - break; - default: - // PLA, PLA/PHA and others - // Used for the Verbatim BVOH, PET, NGEN, co-polyesters - writer.ram(xl + m_perimeter_width * 2, xr - m_perimeter_width, y_step * 0.2f, 0, 1.60f * e, 4000) - .ram(xr - m_perimeter_width, xl + m_perimeter_width, y_step * 1.2f, e0, 1.65f * e, 4600) - .ram(xl + m_perimeter_width * 2, xr - m_perimeter_width * 2, y_step * 1.2f, e0, 1.74f * e, 5200); + 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; + } } - - // Pull the filament end into a cooling tube. - writer.retract(15, 5000).retract(50, 5400).retract(15, 3000).retract(12, 2000); - - if (new_temperature != 0) - // Set the extruder temperature, but don't wait. + 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 ) { // Set the extruder temperature, but don't wait. writer.set_extruder_temp(new_temperature, false); - - // In case the current print head position is closer to the left edge, reverse the direction. - if (std::abs(writer.x() - xl) < std::abs(writer.x() - xr)) - std::swap(xl, xr); - // Horizontal cooling moves will be performed at the following Y coordinate: - writer.travel(xr, writer.y() + y_step * 0.8f, 7200) - .suppress_preview(); - switch (current_material) - { - case PVA: - writer.cool(xl, xr, 3, -5, 1600) - .cool(xl, xr, 5, -5, 2000) - .cool(xl, xr, 5, -5, 2200) - .cool(xl, xr, 5, -5, 2400) - .cool(xl, xr, 5, -5, 2400) - .cool(xl, xr, 5, -3, 2400); - break; - case SCAFF: - writer.cool(xl, xr, 3, -5, 1600) - .cool(xl, xr, 5, -5, 2000) - .cool(xl, xr, 5, -5, 2200) - .cool(xl, xr, 5, -5, 2200) - .cool(xl, xr, 5, -3, 2400); - break; - default: - writer.cool(xl, xr, 3, -5, 1600) - .cool(xl, xr, 5, -5, 2000) - .cool(xl, xr, 5, -5, 2400) - .cool(xl, xr, 5, -3, 2400); - } + 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 solube and flex materials. +// Change the tool, set a speed override for soluble and flex materials. void WipeTowerPrusaMM::toolchange_Change( PrusaMultiMaterial::Writer &writer, const unsigned int new_tool, @@ -835,219 +863,373 @@ void WipeTowerPrusaMM::toolchange_Change( 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; - float xr = cleaning_box.rd.x - m_perimeter_width; - //FIXME flipping left / right side, so that the following toolchange_Wipe will start - // where toolchange_Load ends. - std::swap(xl, xr); - - writer.append("; CP TOOLCHANGE LOAD\n") - // Load the filament while moving left / right, - // so the excess material will not create a blob at a single position. +{ + 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() - // Accelerate the filament loading - .load_move_x(xr, 20, 1400) - // Fast loading phase - .load_move_x(xl, 40, 3000) - // Slowing down - .load_move_x(xr, 20, 1600) - .load_move_x(xl, 10, 1000) - .resume_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*/ - // Extrude first five lines (just three lines if colorInit is set). - writer.extrude(xr, writer.y(), 1600); - bool colorInit = false; - size_t pass = colorInit ? 1 : 2; - float dy = ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width * 0.85f; - for (int i = 0; i < pass; ++ i) { - writer.travel (xr, writer.y() + dy, 7200); - writer.extrude(xl, writer.y(), 2200); - writer.travel (xl, writer.y() + dy, 7200); - writer.extrude(xr, writer.y(), 2200); - } + .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, - bool skip_initial_y_move) + 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; - float xl = cleaning_box.ld.x + 2.f * m_perimeter_width; - float xr = cleaning_box.rd.x - 2.f * m_perimeter_width; - // Wipe speed will increase up to 4800. - float wipe_speed = 4200.f; - float wipe_speed_inc = 50.f; - float wipe_speed_max = 4800.f; - // Y increment per wipe line. - float dy = ((m_current_shape == SHAPE_NORMAL) ? 1.f : -1.f) * m_perimeter_width * 0.8f; - for (bool p = true; - // Next wipe line fits the cleaning box. - ((m_current_shape == SHAPE_NORMAL) ? - (writer.y() <= cleaning_box.lu.y - m_perimeter_width) : - (writer.y() >= cleaning_box.ld.y + m_perimeter_width)); - p = ! p) - { - wipe_speed = std::min(wipe_speed_max, wipe_speed + wipe_speed_inc); - if (skip_initial_y_move) - skip_initial_y_move = false; + 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 - (p ? m_perimeter_width / 2 : m_perimeter_width), writer.y() + dy, wipe_speed * wipe_coeff); - writer.extrude(xr + (p ? m_perimeter_width : m_perimeter_width * 2), writer.y(), wipe_speed * wipe_coeff); - // Next wipe line fits the cleaning box. - if ((m_current_shape == SHAPE_NORMAL) ? - (writer.y() > cleaning_box.lu.y - m_perimeter_width) : - (writer.y() < cleaning_box.ld.y + m_perimeter_width)) + 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; - wipe_speed = std::min(wipe_speed_max, wipe_speed + wipe_speed_inc); - writer.extrude(xr + m_perimeter_width, writer.y() + dy, wipe_speed * wipe_coeff); - writer.extrude(xl - m_perimeter_width, writer.y()); + } + // 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; } - // Reset the extrusion flow. - writer.set_extrusion_flow(m_extrusion_flow); + + // 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(Purpose purpose) + + + +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; + PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width); writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_layer_height(m_layer_height) - .set_initial_tool(m_current_tool) - .append(";--------------------\n" - "; CP EMPTY GRID START\n") - // m_num_layer_changes is incremented by set_z, so it is 1 based. - .comment_with_value(" layer #", m_num_layer_changes - 1); + .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; - box_coordinates fill_box(m_wipe_tower_pos + xy(0.f, m_current_wipe_start_y), - m_wipe_tower_width, float(m_max_color_changes) * m_wipe_area - m_current_wipe_start_y); - fill_box.expand(0.f, - 0.5f * m_perimeter_width); - { - float firstLayerOffset = 0.f; - fill_box.ld.y += firstLayerOffset; - fill_box.rd.y += firstLayerOffset; + 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 (purpose == PURPOSE_MOVE_TO_TOWER || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - if (m_idx_tool_change_in_layer == 0) { - // There were no tool changes at all in this layer. - writer.retract(m_retract * 1.5f, 3600) - // Jump with retract to fill_box.ld + a random shift in +x. - .z_hop(m_zhop, 7200) - .travel(fill_box.ld + xy(5.f + 15.f * float(rand()) / RAND_MAX, 0.f), 7000) - .z_hop_reset(7200) - // Prime the extruder. - .load_move_x(fill_box.ld.x, m_retract * 1.5f, 3600); - } else { - // Otherwise the extruder is already over the wipe tower. + 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; } - } else { - // The print head is inside the wipe tower. Rather move to the start of the following extrusion. - // writer.set_initial_position(fill_box.ld); - writer.set_initial_position(fill_box.ld); } +} - if (purpose == PURPOSE_EXTRUDE || purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) { - // Extrude the first perimeter. - box_coordinates box = fill_box; - writer.extrude(box.lu, 2400 * speed_factor) - .extrude(box.ru) - .extrude(box.rd) - .extrude(box.ld + xy(m_perimeter_width / 2, 0)); - - // Extrude second perimeter. - box.expand(- m_perimeter_width / 2); - writer.extrude(box.lu, 3200 * speed_factor) - .extrude(box.ru) - .extrude(box.rd) - .extrude(box.ld + xy(m_perimeter_width / 2, 0)); - - if (m_is_first_layer) { - // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. - box.expand(- m_perimeter_width / 2); - box.ld.y -= 0.5f * m_perimeter_width; - box.rd.y = box.ld.y; - int nsteps = int(floor((box.lu.y - box.ld.y) / (2. * (1.0 * m_perimeter_width)))); - float step = (box.lu.y - box.ld.y) / nsteps; - for (size_t i = 0; i < nsteps; ++ i) { - writer.extrude(box.ld.x, writer.y() + 0.5f * step); - writer.extrude(box.rd.x, writer.y()); - writer.extrude(box.rd.x, writer.y() + 0.5f * step); - writer.extrude(box.ld.x, writer.y()); - } - } else { - // Extrude a sparse infill to support the material to be printed above. - - // Extrude an inverse U at the left of the region. - writer.extrude(box.ld + xy(m_perimeter_width / 2, m_perimeter_width / 2)) - .extrude(fill_box.ld + xy(m_perimeter_width * 3, m_perimeter_width), 2900 * speed_factor) - .extrude(fill_box.lu + xy(m_perimeter_width * 3, - m_perimeter_width)) - .extrude(fill_box.lu + xy(m_perimeter_width * 6, - m_perimeter_width)) - .extrude(fill_box.ld + xy(m_perimeter_width * 6, m_perimeter_width)); - - if (fill_box.lu.y - fill_box.ld.y > 4.f) { - // Extrude three zig-zags. - float step = (m_wipe_tower_width - m_perimeter_width * 12.f) / 12.f; - for (size_t i = 0; i < 3; ++ i) { - writer.extrude(writer.x() + step, fill_box.ld.y + m_perimeter_width * 8, 3200 * speed_factor); - writer.extrude(writer.x() , fill_box.lu.y - m_perimeter_width * 8); - writer.extrude(writer.x() + step, fill_box.lu.y - m_perimeter_width ); - writer.extrude(writer.x() + step, fill_box.lu.y - m_perimeter_width * 8); - writer.extrude(writer.x() , fill_box.ld.y + m_perimeter_width * 8); - writer.extrude(writer.x() + step, fill_box.ld.y + m_perimeter_width ); - } - } +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; - // Extrude an inverse U at the left of the region. - writer.extrude(fill_box.ru + xy(- m_perimeter_width * 6, - m_perimeter_width), 2900 * speed_factor) - .extrude(fill_box.ru + xy(- m_perimeter_width * 3, - m_perimeter_width)) - .extrude(fill_box.rd + xy(- m_perimeter_width * 3, m_perimeter_width)) - .extrude(fill_box.rd + xy(- m_perimeter_width, m_perimeter_width)); - } + 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; - // if (purpose == PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE) - if (true) - // Wipe along the front side of the current wiping box. - // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - writer.travel(fill_box.ld + xy( m_perimeter_width, m_perimeter_width / 2), 7200) - .travel(fill_box.rd + xy(- m_perimeter_width, m_perimeter_width / 2)); + //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 + + 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 - writer.feedrate(7200); + 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; + } +} - writer.append("; CP EMPTY GRID END\n" - ";------------------\n\n\n\n\n\n\n"); +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; - // Indicate that this wipe tower layer is fully covered. - m_idx_tool_change_in_layer = (unsigned int)m_max_color_changes; + //lay.extra_spacing = width_ratio; + for (auto &tch : lay.tool_changes) + tch.required_depth *= width_ratio; } - ToolChangeResult result; - result.print_z = m_z_pos; - result.layer_height = m_layer_height; - result.gcode = writer.gcode(); - result.elapsed_time = writer.elapsed_time(); - result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos(); - result.end_pos = writer.pos(); - return result; + 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/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index b8c7ab31f..305dbc40a 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -1,13 +1,15 @@ #ifndef WipeTowerPrusaMM_hpp_ #define WipeTowerPrusaMM_hpp_ -#include <algorithm> #include <cmath> #include <string> +#include <sstream> #include <utility> +#include <algorithm> #include "WipeTower.hpp" + namespace Slic3r { @@ -15,6 +17,8 @@ namespace PrusaMultiMaterial { class Writer; }; + + class WipeTowerPrusaMM : public WipeTower { public: @@ -39,65 +43,103 @@ public: // 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 wipe_area, unsigned int initial_tool) : + 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_area(wipe_area), + m_wipe_tower_rotation_angle(rotation_angle), + m_y_shift(0.f), m_z_pos(0.f), - m_current_tool(initial_tool) - { - for (size_t i = 0; i < 4; ++ i) { - // Extruder specific parameters. - m_material[i] = PLA; - m_temperature[i] = 0; - m_first_layer_temperature[i] = 0; - } - } + 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() {} - // _retract - retract value in mm - void set_retract(float retract) { m_retract = retract; } - - // _zHop - z hop value in mm - void set_zhop(float zhop) { m_zhop = zhop; } // Set the extruder properties. - void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp) + 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) { - m_material[idx] = material; - m_temperature[idx] = temp; - m_first_layer_temperature[idx] = first_layer_temp; + //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); } + + // 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, + 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, + size_t max_tool_changes, // Is this the first layer of the print? In that case print the brim first. - bool is_first_layer, + bool is_first_layer, // Is this the last layer of the waste tower? - bool is_last_layer) + bool is_last_layer) { m_z_pos = print_z; m_layer_height = layer_height; - m_max_color_changes = max_tool_changes; m_is_first_layer = is_first_layer; - m_is_last_layer = is_last_layer; - // Start counting the color changes from zero. Special case: -1 - extrude a brim first. - m_idx_tool_change_in_layer = is_first_layer ? (unsigned int)(-1) : 0; - m_current_wipe_start_y = 0.f; + 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; - ++ m_num_layer_changes; - // Extrusion rate for an extrusion aka perimeter width 0.35mm. - // Clamp the extrusion height to a 0.2mm layer height, independent of the nozzle diameter. -// m_extrusion_flow = std::min(0.2f, layer_height) * 0.145f; - // Use a strictly - m_extrusion_flow = layer_height * 0.145f; + 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. @@ -115,79 +157,120 @@ public: 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, - // May be used by a stand alone post processor. - Purpose purpose = PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE); + 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, Purpose purpose); + virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); - // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. + // Fill the unfilled space with a sparse infill. // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer(Purpose purpose); + 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); + } - // 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 - { return m_idx_tool_change_in_layer == m_max_color_changes; } private: WipeTowerPrusaMM(); - // A fill-in direction (positive Y, negative Y) alternates with each layer. - enum wipe_shape + enum wipe_shape // A fill-in direction { - SHAPE_NORMAL = 1, + SHAPE_NORMAL = 1, SHAPE_REVERSED = -1 }; - // Left front corner of the wipe tower in mm. - xy m_wipe_tower_pos; - // Width of the wipe tower. - float m_wipe_tower_width; - // Per color Y span. - float m_wipe_area; - // Current Z position. - float m_z_pos = 0.f; - // Current layer height. - float m_layer_height = 0.f; - // Maximum number of color changes per layer. - size_t m_max_color_changes = 0; - // Is this the 1st layer of the print? If so, print the brim around the waste tower. - bool m_is_first_layer = false; - // Is this the last layer of this waste tower? - bool m_is_last_layer = false; + + 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_zhop = 0.5f; - float m_retract = 4.f; - // Width of an extrusion line, also a perimeter spacing for 100% infill. - float m_perimeter_width = 0.5f; - // Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. - float m_extrusion_flow = 0.029f; + 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. - material_type m_material[4]; - int m_temperature[4]; - int m_first_layer_temperature[4]; - - // State of the wiper tower generator. - // Layer change counter for the output statistics. - unsigned int m_num_layer_changes = 0; - // Tool change change counter for the output statistics. - unsigned int m_num_tool_changes = 0; - // Layer change counter in this layer. Counting up to m_max_color_changes. - unsigned int m_idx_tool_change_in_layer = 0; + 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; - // Current y position at the wipe tower. - float m_current_wipe_start_y = 0.f; - // How much to wipe the 1st extruder over the wipe tower at the 1st layer - // after the wipe tower brim has been extruded? - float m_initial_extra_wipe = 0.f; + 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 { @@ -216,14 +299,43 @@ private: } xy ld; // left down xy lu; // left upper - xy ru; // right 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(); + + // Returns gcode for wipe tower brim - // sideOnly -- set to false -- experimental, draw brim on sides of wipe tower + // 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(Purpose purpose, bool sideOnly = false, float y_offset = 0.f); + ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); void toolchange_Unload( PrusaMultiMaterial::Writer &writer, @@ -243,11 +355,12 @@ private: void toolchange_Wipe( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box, - bool skip_initial_y_move); - - void toolchange_Perimeter(); + float wipe_volume); }; + + + }; // namespace Slic3r #endif /* WipeTowerPrusaMM_hpp_ */ diff --git a/xs/src/libslic3r/GCodeReader.cpp b/xs/src/libslic3r/GCodeReader.cpp index c1e1e98be..51853e9fa 100644 --- a/xs/src/libslic3r/GCodeReader.cpp +++ b/xs/src/libslic3r/GCodeReader.cpp @@ -114,6 +114,28 @@ void GCodeReader::parse_file(const std::string &file, callback_t callback) 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(); diff --git a/xs/src/libslic3r/GCodeReader.hpp b/xs/src/libslic3r/GCodeReader.hpp index 102cbd27a..84ed89a7c 100644 --- a/xs/src/libslic3r/GCodeReader.hpp +++ b/xs/src/libslic3r/GCodeReader.hpp @@ -27,6 +27,7 @@ public: 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(); } diff --git a/xs/src/libslic3r/GCodeSender.cpp b/xs/src/libslic3r/GCodeSender.cpp index bbeaf836d..0988091ce 100644 --- a/xs/src/libslic3r/GCodeSender.cpp +++ b/xs/src/libslic3r/GCodeSender.cpp @@ -2,6 +2,7 @@ #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> @@ -41,6 +42,7 @@ struct termios2 { //#define DEBUG_SERIAL #ifdef DEBUG_SERIAL +#include <cstdlib> #include <fstream> std::fstream fs; #endif @@ -52,7 +54,11 @@ 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() { @@ -358,15 +364,23 @@ GCodeSender::on_read(const boost::system::error_code& error, // 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"))); - ++ toresend; // N is 0-based - if (toresend >= this->sent - this->last_sent.size() && toresend < this->last_sent.size()) { + +#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() + toresend - (this->sent - this->last_sent.size()) - 1, + this->last_sent.begin() + this->last_sent.size() - lines_to_resend, this->last_sent.end() ); @@ -477,8 +491,14 @@ GCodeSender::do_send() if (line.empty()) return; // compute full line - std::string full_line = "N" + boost::lexical_cast<std::string>(this->sent) + " " + 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; @@ -497,8 +517,9 @@ GCodeSender::do_send() this->last_sent.push_back(line); this->can_send = false; - if (this->last_sent.size() > KEEP_SENT) - this->last_sent.erase(this->last_sent.begin(), this->last_sent.end() - KEEP_SENT); + 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 @@ -548,16 +569,12 @@ GCodeSender::set_DTR(bool on) void GCodeSender::reset() { - this->set_DTR(false); - boost::this_thread::sleep(boost::posix_time::milliseconds(200)); - this->set_DTR(true); - boost::this_thread::sleep(boost::posix_time::milliseconds(200)); - this->set_DTR(false); - boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); - { - boost::lock_guard<boost::mutex> l(this->queue_mutex); - this->can_send = true; - } + 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/xs/src/libslic3r/GCodeSender.hpp b/xs/src/libslic3r/GCodeSender.hpp index 3022993cb..d7663ca55 100644 --- a/xs/src/libslic3r/GCodeSender.hpp +++ b/xs/src/libslic3r/GCodeSender.hpp @@ -51,7 +51,7 @@ class GCodeSender : private boost::noncopyable { bool can_send; bool queue_paused; size_t sent; - std::vector<std::string> last_sent; + std::deque<std::string> last_sent; // this mutex guards log, T, B mutable boost::mutex log_mutex; diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp index 912799ca9..7471367fe 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.cpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -4,15 +4,20 @@ #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.2f, 2.5f }; // from Prusa Firmware (Configuration.h) +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 @@ -73,6 +78,10 @@ namespace Slic3r { 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])); @@ -159,12 +168,51 @@ namespace Slic3r { } #endif // ENABLE_MOVE_STATS - GCodeTimeEstimator::GCodeTimeEstimator() + 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(); @@ -178,9 +226,6 @@ namespace Slic3r { #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS - - _reset_blocks(); - _reset(); } void GCodeTimeEstimator::calculate_time_from_file(const std::string& file) @@ -193,9 +238,6 @@ namespace Slic3r { #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS - - _reset_blocks(); - _reset(); } void GCodeTimeEstimator::calculate_time_from_lines(const std::vector<std::string>& gcode_lines) @@ -211,42 +253,126 @@ namespace Slic3r { #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS - - _reset_blocks(); - _reset(); } - void GCodeTimeEstimator::add_gcode_line(const std::string& gcode_line) + bool GCodeTimeEstimator::post_process_remaining_times(const std::string& filename, float interval) { - PROFILE_FUNC(); - _parser.parse_line(gcode_line, - [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) - { this->_process_gcode_line(reader, line); }); - } + boost::nowide::ifstream in(filename); + if (!in.good()) + throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for reading.\n")); - 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); + 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; + } } - } - void GCodeTimeEstimator::calculate_time() - { - PROFILE_FUNC(); - _calculate_time(); + 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")); + } -#if ENABLE_MOVE_STATS - _log_moves_stats(); -#endif // ENABLE_MOVE_STATS + // 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"; - _reset_blocks(); - _reset(); + // 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(); + + boost::nowide::remove(filename.c_str()); + if (boost::nowide::rename(path_tmp.c_str(), filename.c_str()) != 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) @@ -301,7 +427,10 @@ namespace Slic3r { void GCodeTimeEstimator::set_acceleration(float acceleration_mm_sec2) { - _state.acceleration = 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 @@ -309,6 +438,18 @@ namespace Slic3r { 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; @@ -339,6 +480,40 @@ namespace Slic3r { 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; @@ -356,6 +531,7 @@ namespace Slic3r { GCodeFlavor GCodeTimeEstimator::get_dialect() const { + PROFILE_FUNC(); return _state.dialect; } @@ -369,28 +545,61 @@ namespace Slic3r { return _state.units; } - void GCodeTimeEstimator::set_positioning_xyz_type(GCodeTimeEstimator::EPositioningType type) + 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 { - _state.positioning_xyz_type = type; + return _state.g1_line_id; } - GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_positioning_xyz_type() const + void GCodeTimeEstimator::increment_g1_line_id() { - return _state.positioning_xyz_type; + ++_state.g1_line_id; } - void GCodeTimeEstimator::set_positioning_e_type(GCodeTimeEstimator::EPositioningType type) + void GCodeTimeEstimator::reset_g1_line_id() { - _state.positioning_e_type = type; + _state.g1_line_id = 0; } - GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_positioning_e_type() const + void GCodeTimeEstimator::set_extruder_id(unsigned int id) { - return _state.positioning_e_type; + _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; } @@ -408,16 +617,19 @@ namespace Slic3r { { set_units(Millimeters); set_dialect(gcfRepRap); - set_positioning_xyz_type(Absolute); - set_positioning_e_type(Relative); + 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; @@ -425,11 +637,14 @@ namespace Slic3r { 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() { - _time = 0.0f; + _reset_time(); #if ENABLE_MOVE_STATS _moves_stats.clear(); #endif // ENABLE_MOVE_STATS @@ -442,23 +657,14 @@ namespace Slic3r { return _time; } - std::string GCodeTimeEstimator::get_time_hms() const + std::string GCodeTimeEstimator::get_time_dhms() const { - float timeinsecs = get_time(); - int hours = (int)(timeinsecs / 3600.0f); - timeinsecs -= (float)hours * 3600.0f; - int minutes = (int)(timeinsecs / 60.0f); - timeinsecs -= (float)minutes * 60.0f; - - char buffer[64]; - if (hours > 0) - ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)timeinsecs); - else if (minutes > 0) - ::sprintf(buffer, "%dm %ds", minutes, (int)timeinsecs); - else - ::sprintf(buffer, "%ds", (int)timeinsecs); + return _get_time_dhms(get_time()); + } - return buffer; + std::string GCodeTimeEstimator::get_time_minutes() const + { + return _get_time_minutes(get_time()); } void GCodeTimeEstimator::_reset() @@ -471,6 +677,17 @@ namespace Slic3r { 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() @@ -478,22 +695,27 @@ namespace Slic3r { _blocks.clear(); } + void GCodeTimeEstimator::_calculate_time() { + PROFILE_FUNC(); _forward_pass(); _reverse_pass(); _recalculate_trapezoids(); _time += get_additional_time(); - for (const Block& block : _blocks) + 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()) @@ -505,8 +727,13 @@ namespace Slic3r { _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) @@ -619,22 +846,32 @@ namespace Slic3r { _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, GCodeTimeEstimator::EPositioningType type, float current_absolute_position) + 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 (type == GCodeTimeEstimator::Absolute) ? ret : current_absolute_position + ret; + return is_relative ? current_absolute_position + ret : ret; } else return current_absolute_position; @@ -642,12 +879,19 @@ namespace Slic3r { 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) { - new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, (a == E) ? get_positioning_e_type() : get_positioning_xyz_type(), get_axis_position((EAxis)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 @@ -686,13 +930,16 @@ namespace Slic3r { 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; - for (unsigned char a = X; a < Num_Axis; ++a) + if (min_feedrate_factor < 1.0f) { - _curr.axis_feedrate[a] *= min_feedrate_factor; - _curr.abs_axis_feedrate[a] *= min_feedrate_factor; + 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 @@ -825,10 +1072,12 @@ namespace Slic3r { // 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; @@ -850,33 +1099,37 @@ namespace Slic3r { 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) { - set_positioning_xyz_type(Absolute); + PROFILE_FUNC(); + set_global_positioning_type(Absolute); } void GCodeTimeEstimator::_processG91(const GCodeReader::GCodeLine& line) { - // TODO: THERE ARE DIALECT VARIANTS - - set_positioning_xyz_type(Relative); + 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; @@ -917,26 +1170,31 @@ namespace Slic3r { void GCodeTimeEstimator::_processM1(const GCodeReader::GCodeLine& line) { + PROFILE_FUNC(); _simulate_st_synchronize(); } void GCodeTimeEstimator::_processM82(const GCodeReader::GCodeLine& line) { - set_positioning_e_type(Absolute); + PROFILE_FUNC(); + set_e_local_positioning_type(Absolute); } void GCodeTimeEstimator::_processM83(const GCodeReader::GCodeLine& line) { - set_positioning_e_type(Relative); + 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 @@ -957,6 +1215,7 @@ namespace Slic3r { void GCodeTimeEstimator::_processM203(const GCodeReader::GCodeLine& line) { + PROFILE_FUNC(); GCodeFlavor dialect = get_dialect(); // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate @@ -981,16 +1240,32 @@ namespace Slic3r { void GCodeTimeEstimator::_processM204(const GCodeReader::GCodeLine& line) { + PROFILE_FUNC(); float value; - if (line.has_value('S', 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); + 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(); @@ -1017,6 +1292,7 @@ namespace Slic3r { 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)) @@ -1025,6 +1301,7 @@ namespace Slic3r { void GCodeTimeEstimator::_processM566(const GCodeReader::GCodeLine& line) { + PROFILE_FUNC(); if (line.has_x()) set_axis_max_jerk(X, line.x() * MMMIN_TO_MMSEC); @@ -1038,17 +1315,49 @@ namespace Slic3r { 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(); - _reset_blocks(); } void GCodeTimeEstimator::_forward_pass() { + PROFILE_FUNC(); if (_blocks.size() > 1) { - for (unsigned int i = 0; i < (unsigned int)_blocks.size() - 1; ++i) + for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size() - 1; ++i) { _planner_forward_pass_kernel(_blocks[i], _blocks[i + 1]); } @@ -1057,9 +1366,10 @@ namespace Slic3r { void GCodeTimeEstimator::_reverse_pass() { + PROFILE_FUNC(); if (_blocks.size() > 1) { - for (int i = (int)_blocks.size() - 1; i >= 1; --i) + for (int i = (int)_blocks.size() - 1; i >= _last_st_synchronized_block_id + 2; --i) { _planner_reverse_pass_kernel(_blocks[i - 1], _blocks[i]); } @@ -1068,6 +1378,7 @@ namespace Slic3r { 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. @@ -1108,11 +1419,14 @@ namespace Slic3r { void GCodeTimeEstimator::_recalculate_trapezoids() { + PROFILE_FUNC(); Block* curr = nullptr; Block* next = nullptr; - for (Block& b : _blocks) + for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) { + Block& b = _blocks[i]; + curr = next; next = &b; @@ -1142,6 +1456,33 @@ namespace Slic3r { } } + 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 { diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp index 5ad5b8d0c..e9da584c3 100644 --- a/xs/src/libslic3r/GCodeTimeEstimator.hpp +++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp @@ -17,6 +17,15 @@ namespace Slic3r { 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, @@ -61,16 +70,27 @@ namespace Slic3r { { GCodeFlavor dialect; EUnits units; - EPositioningType positioning_xyz_type; - EPositioningType positioning_e_type; + 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; + 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: @@ -121,7 +141,6 @@ namespace Slic3r { bool nominal_length; }; - #if ENABLE_MOVE_STATS EMoveType move_type; #endif // ENABLE_MOVE_STATS @@ -134,6 +153,9 @@ namespace Slic3r { FeedrateProfile feedrate; Trapezoid trapezoid; + float elapsed_time; + + Block(); // Returns the length of the move covered by this block, in mm float move_length() const; @@ -187,19 +209,39 @@ namespace Slic3r { 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: - GCodeTimeEstimator(); + 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); @@ -210,14 +252,12 @@ namespace Slic3r { // 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); - // 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() - void calculate_time(); + // 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); @@ -239,6 +279,10 @@ namespace Slic3r { 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; @@ -248,6 +292,11 @@ namespace Slic3r { 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; @@ -257,11 +306,19 @@ namespace Slic3r { void set_units(EUnits units); EUnits get_units() const; - void set_positioning_xyz_type(EPositioningType type); - EPositioningType get_positioning_xyz_type() const; + void set_global_positioning_type(EPositioningType type); + EPositioningType get_global_positioning_type() const; - void set_positioning_e_type(EPositioningType type); - EPositioningType get_positioning_e_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); @@ -275,11 +332,15 @@ namespace Slic3r { // Returns the estimated time, in seconds float get_time() const; - // Returns the estimated time, in format HHh MMm SSs - std::string get_time_hms() 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 @@ -342,6 +403,12 @@ namespace Slic3r { // 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(); @@ -353,6 +420,12 @@ namespace Slic3r { 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 diff --git a/xs/src/libslic3r/GCodeWriter.cpp b/xs/src/libslic3r/GCodeWriter.cpp index 9876a3fac..6ef17f4f4 100644 --- a/xs/src/libslic3r/GCodeWriter.cpp +++ b/xs/src/libslic3r/GCodeWriter.cpp @@ -19,6 +19,8 @@ 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) @@ -170,6 +172,10 @@ std::string GCodeWriter::set_fan(unsigned int speed, bool dont_save) 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(); @@ -270,30 +276,30 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s return gcode.str(); } -std::string GCodeWriter::travel_to_xy(const Pointf &point, const std::string &comment) +std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment) { - m_pos.x = point.x; - m_pos.y = point.y; + m_pos(0) = point(0); + m_pos(1) = point(1); std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point.x) - << " Y" << XYZF_NUM(point.y) + 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 Pointf3 &point, const std::string &comment) +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.z)) { - double nominal_z = m_pos.z - m_lifted; - m_lifted = m_lifted - (point.z - nominal_z); - return this->travel_to_xy(point); + 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 @@ -302,9 +308,9 @@ std::string GCodeWriter::travel_to_xyz(const Pointf3 &point, const std::string & m_pos = point; std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point.x) - << " Y" << XYZF_NUM(point.y) - << " Z" << XYZF_NUM(point.z) + 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"; @@ -317,7 +323,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment) 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.z - m_lifted; + double nominal_z = m_pos(2) - m_lifted; m_lifted = m_lifted - (z - nominal_z); return ""; } @@ -330,7 +336,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment) std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) { - m_pos.z = z; + m_pos(2) = z; std::ostringstream gcode; gcode << "G1 Z" << XYZF_NUM(z) @@ -345,38 +351,38 @@ 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.z - m_lifted; - if (z >= nominal_z && z <= m_pos.z) + 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 Pointf &point, double dE, const std::string &comment) +std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment) { - m_pos.x = point.x; - m_pos.y = point.y; + m_pos(0) = point(0); + m_pos(1) = point(1); m_extruder->extrude(dE); std::ostringstream gcode; - gcode << "G1 X" << XYZF_NUM(point.x) - << " Y" << XYZF_NUM(point.y) + 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 Pointf3 &point, double dE, const std::string &comment) +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.x) - << " Y" << XYZF_NUM(point.y) - << " Z" << XYZF_NUM(point.z) + 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"; @@ -480,12 +486,12 @@ std::string GCodeWriter::lift() { 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.z >= above && (below == 0 || m_pos.z <= below)) + 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.z + target_lift, "lift Z"); + return this->_travel_to_z(m_pos(2) + target_lift, "lift Z"); } return ""; } @@ -494,7 +500,7 @@ std::string GCodeWriter::unlift() { std::string gcode; if (m_lifted > 0) { - gcode += this->_travel_to_z(m_pos.z - m_lifted, "restore layer Z"); + gcode += this->_travel_to_z(m_pos(2) - m_lifted, "restore layer Z"); m_lifted = 0; } return gcode; diff --git a/xs/src/libslic3r/GCodeWriter.hpp b/xs/src/libslic3r/GCodeWriter.hpp index 78e9c9323..664b0e3a1 100644 --- a/xs/src/libslic3r/GCodeWriter.hpp +++ b/xs/src/libslic3r/GCodeWriter.hpp @@ -18,7 +18,7 @@ public: GCodeWriter() : multiple_extruders(false), m_extrusion_axis("E"), m_extruder(nullptr), m_single_extruder_multi_material(false), - m_last_acceleration(0), m_last_fan_speed(0), + 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) {} @@ -55,18 +55,18 @@ public: 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 Pointf &point, const std::string &comment = std::string()); - std::string travel_to_xyz(const Pointf3 &point, const std::string &comment = std::string()); + 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 Pointf &point, double dE, const std::string &comment = std::string()); - std::string extrude_to_xyz(const Pointf3 &point, double dE, const std::string &comment = std::string()); + 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(); - Pointf3 get_position() const { return m_pos; } + Vec3d get_position() const { return m_pos; } private: std::vector<Extruder> m_extruders; @@ -74,11 +74,14 @@ private: 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; - Pointf3 m_pos; + 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); diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index 39b626ee3..b0ded2d04 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -195,47 +195,110 @@ using namespace boost::polygon; // provides also high() and low() namespace Slic3r { namespace Geometry { -static bool -sort_points (Point a, Point b) +static bool sort_points(const Point& a, const Point& b) { - return (a.x < b.x) || (a.x == b.x && a.y < b.y); + 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 */ +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); + hull.points.resize(2 * n); // Build lower hull for (int i = 0; i < n; i++) { - while (k >= 2 && points[i].ccw(hull.points[k-2], hull.points[k-1]) <= 0) k--; - hull.points[k++] = points[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.points[k-2], hull.points[k-1]) <= 0) k--; - hull.points[k++] = points[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().coincides_with(hull.points.back()) ); + + 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) { @@ -243,7 +306,7 @@ convex_hull(const Polygons &polygons) for (Polygons::const_iterator p = polygons.begin(); p != polygons.end(); ++p) { pp.insert(pp.end(), p->points.begin(), p->points.end()); } - return convex_hull(pp); + return convex_hull(std::move(pp)); } /* accepts an arrayref of points and returns a list of indices @@ -345,54 +408,54 @@ linint(double value, double oldmin, double oldmax, double newmin, double newmax) // If the points have the same weight, sort them lexicographically by their positions. struct ArrangeItem { ArrangeItem() {} - Pointf pos; + Vec2d pos; coordf_t weight; bool operator<(const ArrangeItem &other) const { return weight < other.weight || - ((weight == other.weight) && (pos.y < other.pos.y || (pos.y == other.pos.y && pos.x < other.pos.x))); + ((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 Pointf &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box) +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 Pointf cell_size(part_size.x + gap, part_size.y + gap); + 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( - Pointf(0, 0), - Pointf(cell_size.x * num_parts, cell_size.y * num_parts)); + 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().x + gap) / cell_size.x)); - size_t cellh = size_t(floor((bed_bbox.size().y + gap) / cell_size.y)); + 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) CONFESS(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. - Pointf cells_size(cellw * cell_size.x - gap, cellh * cell_size.y - gap); - Pointf cells_offset(bed_bbox.center() - 0.5 * cells_size); + 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.y, cells_bb.max.y); + 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().y - cy; + 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.x, cells_bb.max.x); + 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().x - cx; + 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.x = cx; - ci.pos.y = cy; + ci.pos(0) = cx; + ci.pos(1) = cy; // Square distance of the cell center to the bed center. ci.weight = xd * xd + yd * yd; } @@ -405,61 +468,61 @@ Pointfs arrange(size_t num_parts, const Pointf &part_size, coordf_t gap, const B Pointfs positions; positions.reserve(num_parts); for (std::vector<ArrangeItem>::const_iterator it = cellsorder.begin(); it != cellsorder.end(); ++ it) - positions.push_back(Pointf(it->pos.x - 0.5 * part_size.x, it->pos.y - 0.5 * part_size.y)); + 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: - Pointf pos; +public: + Vec2d pos = Vec2d::Zero(); size_t index_x, index_y; coordf_t dist; }; class ArrangeItemIndex { - public: +public: coordf_t index; ArrangeItem item; ArrangeItemIndex(coordf_t _index, ArrangeItem _item) : index(_index), item(_item) {}; }; bool -arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions) +arrange(size_t total_parts, const Vec2d &part_size, coordf_t dist, const BoundingBoxf* bb, Pointfs &positions) { positions.clear(); - Pointf part = part_size; + Vec2d part = part_size; // use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm - part.x += dist; - part.y += dist; + part(0) += dist; + part(1) += dist; - Pointf area; + 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.x = part.x * total_parts; - area.y = part.y * total_parts; + 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.x + dist) / part.x); - size_t cellh = floor((area.y + dist) / part.y); + 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 - Pointf cells(cellw * part.x, cellh * part.y); + Vec2d cells(cellw * part(0), cellh * part(1)); // bounding box of total space used by cells BoundingBoxf cells_bb; - cells_bb.merge(Pointf(0,0)); // min + cells_bb.merge(Vec2d(0,0)); // min cells_bb.merge(cells); // max // center bounding box to area cells_bb.translate( - (area.x - cells.x) / 2, - (area.y - cells.y) / 2 + (area(0) - cells(0)) / 2, + (area(1) - cells(1)) / 2 ); // list of cells, sorted by distance from center @@ -468,15 +531,15 @@ arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const Boundi // 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.x, cells_bb.max.x); - coordf_t cy = linint(j + 0.5, 0, cellh, cells_bb.min.y, cells_bb.max.y); + 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.x / 2) - cx); - coordf_t yd = fabs((area.y / 2) - cy); + coordf_t xd = fabs((area(0) / 2) - cx); + coordf_t yd = fabs((area(1) / 2) - cy); ArrangeItem c; - c.pos.x = cx; - c.pos.y = cy; + 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)); @@ -533,13 +596,13 @@ arrange(size_t total_parts, const Pointf &part_size, coordf_t dist, const Boundi coordf_t cx = c.item.index_x - lx; coordf_t cy = c.item.index_y - ty; - positions.push_back(Pointf(cx * part.x, cy * part.y)); + 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.x; - p->y += bb->min.y; + p->x() += bb->min(0); + p->y() += bb->min(1); } } @@ -608,15 +671,15 @@ namespace Voronoi { namespace Internal { if (cell1.contains_point() && cell2.contains_point()) { point_type p1 = retrieve_point(segments, cell1); point_type p2 = retrieve_point(segments, cell2); - origin.x((p1.x() + p2.x()) * 0.5); - origin.y((p1.y() + p2.y()) * 0.5); - direction.x(p1.y() - p2.y()); - direction.y(p2.x() - p1.x()); + 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).x() - low(segment).x(); - coordinate_type dy = high(segment).y() - low(segment).y(); + 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); @@ -625,19 +688,19 @@ namespace Voronoi { namespace Internal { direction.y(dx); } } - coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y())); + 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.x() - direction.x() * koef, - origin.y() - direction.y() * koef)); + 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.x() + direction.x() * koef, - origin.y() + direction.y() * koef)); + origin(0) + direction(0) * koef, + origin(1) + direction(1) * koef)); } else { clipped_edge->push_back( point_type(edge.vertex1()->x(), edge.vertex1()->y())); @@ -676,10 +739,10 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d const bool primaryEdgesOnly = false; BoundingBox bbox = BoundingBox(lines); - bbox.min.x -= coord_t(1. / SCALING_FACTOR); - bbox.min.y -= coord_t(1. / SCALING_FACTOR); - bbox.max.x += coord_t(1. / SCALING_FACTOR); - bbox.max.y += coord_t(1. / SCALING_FACTOR); + 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); @@ -689,7 +752,7 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d // 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.x - bbox.min.x) + double(bbox.max.y - bbox.min.y); + 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; @@ -697,8 +760,8 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d 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.x), double(it->a.y)), - Voronoi::Internal::point_type(double(it->b.x), double(it->b.y)))); + 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) @@ -712,13 +775,13 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d } // Draw the input polygon. for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) - svg.draw(Line(Point(coord_t(it->a.x), coord_t(it->a.y)), Point(coord_t(it->b.x), coord_t(it->b.y))), inputSegmentColor, inputSegmentLineWidth); + 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->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius); + 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()) @@ -743,7 +806,7 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d color = voronoiLineColorSecondary; } for (std::size_t i = 0; i + 1 < samples.size(); ++i) - svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth); + 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 @@ -758,8 +821,8 @@ static inline void dump_voronoi_to_svg(const Lines &lines, /* const */ voronoi_d template<typename T> T dist(const boost::polygon::point_data<T> &p1,const boost::polygon::point_data<T> &p2) { - T dx = p2.x() - p1.x(); - T dy = p2.y() - p1.y(); + T dx = p2(0) - p1(0); + T dy = p2(1) - p1(1); return sqrt(dx*dx+dy*dy); } @@ -770,11 +833,11 @@ 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.x()-p0.x(), p1.y()-p0.y()); - const point_type dproj(px.x()-p0.x(), px.y()-p0.y()); - const T t = (dir.x()*dproj.x() + dir.y()*dproj.y()) / (dir.x()*dir.x() + dir.y()*dir.y()); + 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.x() + t*dir.x(), p0.y() + t*dir.y()); + return point_type(p0(0) + t*dir(0), p0(1) + t*dir(1)); } template<typename VD, typename SEGMENTS> @@ -828,8 +891,8 @@ 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.x), typename VD::coord_type(lines[idx].a.y)), - typename VD::point_type(typename VD::coord_type(lines[idx].b.x), typename VD::coord_type(lines[idx].b.y))); + 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; @@ -910,7 +973,7 @@ MedialAxis::build(ThickPolylines* polylines) assert(polyline.width.size() == polyline.points.size()*2 - 2); // prevent loop endpoints from being extended - if (polyline.first_point().coincides_with(polyline.last_point())) { + if (polyline.first_point() == polyline.last_point()) { polyline.endpoints.first = false; polyline.endpoints.second = false; } @@ -1003,7 +1066,7 @@ MedialAxis::validate_edge(const VD::edge_type* edge) // 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.coincides_with(line.b)) { + if (line.a == line.b) { // in this case, contains(line) returns a false positive if (!this->expolygon->contains(line.a)) return false; } else { @@ -1042,12 +1105,12 @@ MedialAxis::validate_edge(const VD::edge_type* edge) calculate the distance to that endpoint instead. */ coordf_t w0 = cell_r->contains_segment() - ? line.a.distance_to(segment_r)*2 - : line.a.distance_to(this->retrieve_endpoint(cell_r))*2; + ? 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() - ? line.b.distance_to(segment_l)*2 - : line.b.distance_to(this->retrieve_endpoint(cell_l))*2; + ? 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 diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index c2c3dc8b7..3698b996f 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -30,9 +30,9 @@ enum Orientation 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.x) * int64_t(c.y) - int64_t(b.y) * int64_t(c.x); - int64_t v = int64_t(a.x) * int64_t(c.y) - int64_t(a.y) * int64_t(c.x); - int64_t w = int64_t(a.x) * int64_t(b.y) - int64_t(a.y) * int64_t(b.x); + 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); } @@ -52,7 +52,7 @@ static inline bool is_ccw(const Polygon &poly) for (unsigned int i = 1; i < poly.points.size(); ++ i) { const Point &pmin = poly.points[imin]; const Point &p = poly.points[i]; - if (p.x < pmin.x || (p.x == pmin.x && p.y < pmin.y)) + if (p(0) < pmin(0) || (p(0) == pmin(0) && p(1) < pmin(1))) imin = i; } @@ -66,26 +66,26 @@ static inline bool is_ccw(const Polygon &poly) return o == ORIENTATION_CCW; } -inline bool ray_ray_intersection(const Pointf &p1, const Vectorf &v1, const Pointf &p2, const Vectorf &v2, Pointf &res) +inline bool ray_ray_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res) { - double denom = v1.x * v2.y - v2.x * v1.y; + double denom = v1(0) * v2(1) - v2(0) * v1(1); if (std::abs(denom) < EPSILON) return false; - double t = (v2.x * (p1.y - p2.y) - v2.y * (p1.x - p2.x)) / denom; - res.x = p1.x + t * v1.x; - res.y = p1.y + t * v1.y; + 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 Pointf &p1, const Vectorf &v1, const Pointf &p2, const Vectorf &v2, Pointf &res) +inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res) { - double denom = v1.x * v2.y - v2.x * v1.y; + double denom = v1(0) * v2(1) - v2(0) * v1(1); if (std::abs(denom) < EPSILON) // Lines are collinear. return false; - double s12_x = p1.x - p2.x; - double s12_y = p1.y - p2.y; - double s_numer = v1.x * s12_y - v1.y * s12_x; + 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; @@ -95,7 +95,7 @@ inline bool segment_segment_intersection(const Pointf &p1, const Vectorf &v1, co if (s_numer < 0.) // Intersection outside of the 1st segment. return false; - double t_numer = v2.x * s12_y - v2.y * s12_x; + 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) @@ -103,13 +103,15 @@ inline bool segment_segment_intersection(const Pointf &p1, const Vectorf &v1, co return false; // Intersection inside both of the segments. double t = t_numer / denom; - res.x = p1.x + t * v1.x; - res.y = p1.y + t * v1.y; + 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); @@ -123,7 +125,7 @@ void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* ret double linint(double value, double oldmin, double oldmax, double newmin, double newmax); bool arrange( // input - size_t num_parts, const Pointf &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, + size_t num_parts, const Vec2d &part_size, coordf_t gap, const BoundingBoxf* bed_bounding_box, // output Pointfs &positions); diff --git a/xs/src/libslic3r/I18N.hpp b/xs/src/libslic3r/I18N.hpp new file mode 100644 index 000000000..db4fd22df --- /dev/null +++ b/xs/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/xs/src/libslic3r/Int128.hpp b/xs/src/libslic3r/Int128.hpp index 7bf0c87b4..d54b75342 100644 --- a/xs/src/libslic3r/Int128.hpp +++ b/xs/src/libslic3r/Int128.hpp @@ -48,7 +48,6 @@ #endif #include <cassert> -#include "Point.hpp" #if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__) #define HAS_INTRINSIC_128_TYPE @@ -288,20 +287,4 @@ public: } return sign_determinant_2x2(p1, q1, p2, q2) * invert; } - - // Exact orientation predicate, - // returns +1: CCW, 0: collinear, -1: CW. - static int orient(const Slic3r::Point &p1, const Slic3r::Point &p2, const Slic3r::Point &p3) - { - Slic3r::Vector v1(p2 - p1); - Slic3r::Vector v2(p3 - p1); - return sign_determinant_2x2_filtered(v1.x, v1.y, v2.x, v2.y); - } - - // Exact orientation predicate, - // returns +1: CCW, 0: collinear, -1: CW. - static int cross(const Slic3r::Point &v1, const Slic3r::Point &v2) - { - return sign_determinant_2x2_filtered(v1.x, v1.y, v2.x, v2.y); - } }; diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index 72ac371a2..6c2bd0da9 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -167,8 +167,8 @@ void Layer::export_region_slices_to_svg(const char *path) const 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.x, bbox.max.y); - bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); + 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; @@ -193,8 +193,8 @@ void Layer::export_region_fill_surfaces_to_svg(const char *path) const 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.x, bbox.max.y); - bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); + 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; diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index c576d7eec..65184c9d1 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -392,8 +392,8 @@ void LayerRegion::export_region_slices_to_svg(const char *path) const 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.x, bbox.max.y); - bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); + 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; @@ -419,8 +419,8 @@ void LayerRegion::export_region_fill_surfaces_to_svg(const char *path) const 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.x, bbox.max.y); - bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); + 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; diff --git a/xs/src/libslic3r/Line.cpp b/xs/src/libslic3r/Line.cpp index e9d5d7742..35cfa2b76 100644 --- a/xs/src/libslic3r/Line.cpp +++ b/xs/src/libslic3r/Line.cpp @@ -7,133 +7,70 @@ namespace Slic3r { -std::string -Line::wkt() const +Linef3 transform(const Linef3& line, const Transform3d& t) { - std::ostringstream ss; - ss << "LINESTRING(" << this->a.x << " " << this->a.y << "," - << this->b.x << " " << this->b.y << ")"; - return ss.str(); -} - -Line::operator Lines() const -{ - Lines lines; - lines.push_back(*this); - return lines; -} - -Line::operator Polyline() const -{ - Polyline pl; - pl.points.push_back(this->a); - pl.points.push_back(this->b); - return pl; -} - -void -Line::scale(double factor) -{ - this->a.scale(factor); - this->b.scale(factor); -} - -void -Line::translate(double x, double y) -{ - this->a.translate(x, y); - this->b.translate(x, y); -} - -void -Line::rotate(double angle, const Point ¢er) -{ - this->a.rotate(angle, center); - this->b.rotate(angle, center); -} - -void -Line::reverse() -{ - std::swap(this->a, this->b); -} + typedef Eigen::Matrix<double, 3, 2> LineInMatrixForm; -double -Line::length() const -{ - return this->a.distance_to(this->b); -} + 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)); -Point -Line::midpoint() const -{ - return Point((this->a.x + this->b.x) / 2.0, (this->a.y + this->b.y) / 2.0); + 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))); } -void -Line::point_at(double distance, Point* point) const +bool Line::intersection_infinite(const Line &other, Point* point) const { - double len = this->length(); - *point = this->a; - if (this->a.x != this->b.x) - point->x = this->a.x + (this->b.x - this->a.x) * distance / len; - if (this->a.y != this->b.y) - point->y = this->a.y + (this->b.y - this->a.y) * distance / len; -} - -Point -Line::point_at(double distance) const -{ - Point p; - this->point_at(distance, &p); - return p; -} - -bool -Line::intersection_infinite(const Line &other, Point* point) const -{ - Vector x = this->a.vector_to(other.a); - Vector d1 = this->vector(); - Vector d2 = other.vector(); - - double cross = d1.x * d2.y - d1.y * d2.x; - if (std::fabs(cross) < EPSILON) + 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 = (x.x * d2.y - x.y * d2.x)/cross; - point->x = this->a.x + d1.x * t1; - point->y = this->a.y + d1.y * t1; + double t1 = cross2(v12, v2) / denom; + *point = (a1 + t1 * v1).cast<coord_t>(); return true; } -bool -Line::coincides_with(const Line &line) const +/* distance to the closest point of line */ +double Line::distance_to(const Point &point) const { - return this->a.coincides_with(line.a) && this->b.coincides_with(line.b); + 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::distance_to(const Point &point) const +double Line::perp_distance_to(const Point &point) const { - return point.distance_to(*this); + 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::atan2_() const -{ - return atan2(this->b.y - this->a.y, this->b.x - this->a.x); -} - -double -Line::orientation() const +double Line::orientation() const { double angle = this->atan2_(); if (angle < 0) angle = 2*PI + angle; return angle; } -double -Line::direction() const +double Line::direction() const { double atan2 = this->atan2_(); return (fabs(atan2 - PI) < EPSILON) ? 0 @@ -141,108 +78,42 @@ Line::direction() const : atan2; } -bool -Line::parallel_to(double angle) const { - return Slic3r::Geometry::directions_parallel(this->direction(), angle); -} - -bool -Line::parallel_to(const Line &line) const { - return this->parallel_to(line.direction()); -} - -Vector -Line::vector() const +bool Line::parallel_to(double angle) const { - return Vector(this->b.x - this->a.x, this->b.y - this->a.y); -} - -Vector -Line::normal() const -{ - return Vector((this->b.y - this->a.y), -(this->b.x - this->a.x)); -} - -void -Line::extend_end(double distance) -{ - // relocate last point by extending the segment by the specified length - Line line = *this; - line.reverse(); - this->b = line.point_at(-distance); -} - -void -Line::extend_start(double distance) -{ - // relocate first point by extending the first segment by the specified length - this->a = this->point_at(-distance); + return Slic3r::Geometry::directions_parallel(this->direction(), angle); } -bool -Line::intersection(const Line& line, Point* intersection) const -{ - double denom = ((double)(line.b.y - line.a.y)*(this->b.x - this->a.x)) - - ((double)(line.b.x - line.a.x)*(this->b.y - this->a.y)); - - double nume_a = ((double)(line.b.x - line.a.x)*(this->a.y - line.a.y)) - - ((double)(line.b.y - line.a.y)*(this->a.x - line.a.x)); - - double nume_b = ((double)(this->b.x - this->a.x)*(this->a.y - line.a.y)) - - ((double)(this->b.y - this->a.y)*(this->a.x - line.a.x)); - - if (fabs(denom) < EPSILON) { - if (fabs(nume_a) < EPSILON && fabs(nume_b) < EPSILON) { - return false; // coincident - } - return false; // parallel - } - - double ua = nume_a / denom; - double ub = nume_b / denom; - - if (ua >= 0 && ua <= 1.0f && ub >= 0 && ub <= 1.0f) - { +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->x = this->a.x + ua*(this->b.x - this->a.x); - intersection->y = this->a.y + ua*(this->b.y - this->a.y); + (*intersection) = (l1.a.cast<double>() + t1 * v1).cast<coord_t>(); return true; } - return false; // not intersecting } -double -Line::ccw(const Point& point) const -{ - return point.ccw(*this); -} - -double Line3::length() const -{ - return a.distance_to(b); -} - -Vector3 Line3::vector() const -{ - return Vector3(b.x - a.x, b.y - a.y, b.z - a.z); -} - -Pointf3 -Linef3::intersect_plane(double z) const -{ - return Pointf3( - this->a.x + (this->b.x - this->a.x) * (z - this->a.z) / (this->b.z - this->a.z), - this->a.y + (this->b.y - this->a.y) * (z - this->a.z) / (this->b.z - this->a.z), - z - ); -} - -void -Linef3::scale(double factor) +Vec3d Linef3::intersect_plane(double z) const { - this->a.scale(factor); - this->b.scale(factor); + 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/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp index 4826017ab..36e02247c 100644 --- a/xs/src/libslic3r/Line.hpp +++ b/xs/src/libslic3r/Line.hpp @@ -15,80 +15,85 @@ 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: - Point a; - Point b; - Line() {}; - explicit Line(Point _a, Point _b): a(_a), b(_b) {}; - std::string wkt() const; - operator Lines() const; - operator Polyline() const; - void scale(double factor); - void translate(double x, double y); - void rotate(double angle, const Point ¢er); - void reverse(); - double length() const; - Point midpoint() const; - void point_at(double distance, Point* point) const; - Point point_at(double distance) const; - bool intersection_infinite(const Line &other, Point* point) const; - bool coincides_with(const Line &line) const; + 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 ¢er) { 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; - bool parallel_to(double angle) const; - bool parallel_to(const Line &line) const; - double atan2_() 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; - Vector normal() const; - void extend_end(double distance); - void extend_start(double distance); - bool intersection(const Line& line, Point* intersection) const; - double ccw(const Point& point) 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: - coordf_t a_width, b_width; - - ThickLine() : a_width(0), b_width(0) {}; - ThickLine(Point _a, Point _b) : Line(_a, _b), a_width(0), b_width(0) {}; +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: - Point3 a; - Point3 b; + Line3() : a(Vec3crd::Zero()), b(Vec3crd::Zero()) {} + Line3(const Vec3crd& _a, const Vec3crd& _b) : a(_a), b(_b) {} - Line3() {} - Line3(const Point3& _a, const Point3& _b) : a(_a), b(_b) {} + double length() const { return (this->a - this->b).cast<double>().norm(); } + Vec3crd vector() const { return this->b - this->a; } - double length() const; - Vector3 vector() const; + Vec3crd a; + Vec3crd b; }; class Linef { - public: - Pointf a; - Pointf b; - Linef() {}; - explicit Linef(Pointf _a, Pointf _b): a(_a), b(_b) {}; +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: - Pointf3 a; - Pointf3 b; - Linef3() {}; - explicit Linef3(Pointf3 _a, Pointf3 _b): a(_a), b(_b) {}; - Pointf3 intersect_plane(double z) const; - void scale(double factor); +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 diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 6f1ce1ce5..19c474cad 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -14,8 +14,13 @@ #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 @@ -230,28 +235,17 @@ BoundingBoxf3 Model::bounding_box() const return bb; } -BoundingBoxf3 Model::transformed_bounding_box() const -{ - BoundingBoxf3 bb; - for (const ModelObject* obj : this->objects) - bb.merge(obj->tight_bounding_box(false)); - return bb; -} - -void Model::center_instances_around_point(const Pointf &point) +void Model::center_instances_around_point(const Vec2d &point) { -// BoundingBoxf3 bb = this->bounding_box(); 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)); - Sizef3 size = bb.size(); - coordf_t shift_x = -bb.min.x + point.x - size.x/2; - coordf_t shift_y = -bb.min.y + point.y - size.y/2; + Vec2d shift = point - 0.5 * to_2d(bb.size()) - to_2d(bb.min); for (ModelObject *o : this->objects) { for (ModelInstance *i : o->instances) - i->offset.translate(shift_x, shift_y); + i->offset += shift; o->invalidate_bounding_box(); } } @@ -267,6 +261,10 @@ TriangleMesh Model::mesh() const 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 @@ -302,29 +300,30 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) 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.push_back(bbox.size()); - instance_centers.push_back(bbox.center()); + 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) { + + size_t idx = 0; + for (ModelObject *o : this->objects) { for (ModelInstance *i : o->instances) { i->offset = positions[idx] - instance_centers[idx]; ++ 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, this->bounding_box().size()); + Pointfs model_sizes(copies_num-1, to_2d(this->bounding_box().size())); Pointfs positions; if (! _arrange(model_sizes, dist, bb, positions)) CONFESS("Cannot duplicate part as the resulting objects would not fit on the print bed.\n"); @@ -335,9 +334,9 @@ void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) // 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 Pointf &pos : positions) { + for (const Vec2d &pos : positions) { ModelInstance *instance = o->add_instance(*i); - instance->offset.translate(pos); + instance->offset += pos; } } o->invalidate_bounding_box(); @@ -367,13 +366,13 @@ void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist) ModelObject* object = this->objects.front(); object->clear_instances(); - Sizef3 size = object->bounding_box().size(); + Vec3d size = object->bounding_box().size(); 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(); - instance->offset.x = (size.x + dist) * (x_copy-1); - instance->offset.y = (size.y + dist) * (y_copy-1); + instance->offset(0) = (size(0) + dist) * (x_copy-1); + instance->offset(1) = (size(1) + dist) * (y_copy-1); } } } @@ -387,7 +386,7 @@ bool Model::looks_like_multipart_object() const 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.z; + 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) @@ -398,17 +397,26 @@ bool Model::looks_like_multipart_object() const return false; } -void Model::convert_multipart_object() +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) - object->add_volume(*v)->name = o->name; + { + 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); @@ -422,13 +430,13 @@ void Model::adjust_min_z() if (objects.empty()) return; - if (bounding_box().min.z < 0.0) + if (bounding_box().min(2) < 0.0) { for (ModelObject* obj : objects) { if (obj != nullptr) { - coordf_t obj_min_z = obj->bounding_box().min.z; + 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); } @@ -436,30 +444,26 @@ void Model::adjust_min_z() } } -bool Model::fits_print_volume(const DynamicPrintConfig* config) const +unsigned int Model::get_auto_extruder_id(unsigned int max_extruders) { - if (config == nullptr) - return false; + unsigned int id = s_auto_extruder_id; - if (objects.empty()) - return true; + if (++s_auto_extruder_id > max_extruders) + reset_auto_extruder_id(); - const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(config->option("bed_shape")); - if (opt == nullptr) - return false; + return id; +} - BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config->opt_float("max_print_height"))); - return print_volume.contains(transformed_bounding_box()); +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; } -bool Model::fits_print_volume(const FullPrintConfig &config) const +void Model::reset_auto_extruder_id() { - if (objects.empty()) - return true; - BoundingBox bed_box_2D = get_extents(Polygon::new_scale(config.bed_shape.values)); - BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), config.max_print_height)); - return print_volume.contains(transformed_bounding_box()); + s_auto_extruder_id = 1; } ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volumes) : @@ -592,7 +596,7 @@ void ModelObject::clear_instances() // Returns the bounding box of the transformed instances. // This bounding box is approximate and not snug. -const BoundingBoxf3& ModelObject::bounding_box() +const BoundingBoxf3& ModelObject::bounding_box() const { if (! m_bounding_box_valid) { BoundingBoxf3 raw_bbox; @@ -608,54 +612,6 @@ const BoundingBoxf3& ModelObject::bounding_box() return m_bounding_box; } -BoundingBoxf3 ModelObject::tight_bounding_box(bool include_modifiers) const -{ - BoundingBoxf3 bb; - - for (const ModelVolume* vol : this->volumes) - { - if (include_modifiers || !vol->modifier) - { - for (const ModelInstance* inst : this->instances) - { - double c = cos(inst->rotation); - double s = sin(inst->rotation); - - for (int f = 0; f < vol->mesh.stl.stats.number_of_facets; ++f) - { - const stl_facet& facet = vol->mesh.stl.facet_start[f]; - - for (int i = 0; i < 3; ++i) - { - // original point - const stl_vertex& v = facet.vertex[i]; - Pointf3 p((double)v.x, (double)v.y, (double)v.z); - - // scale - p.x *= inst->scaling_factor; - p.y *= inst->scaling_factor; - p.z *= inst->scaling_factor; - - // rotate Z - double x = p.x; - double y = p.y; - p.x = c * x - s * y; - p.y = s * x + c * y; - - // translate - p.x += inst->offset.x; - p.y += inst->offset.y; - - bb.merge(p); - } - } - } - } - } - - return bb; -} - // A mesh containing all transformed instances of this object. TriangleMesh ModelObject::mesh() const { @@ -713,25 +669,19 @@ void ModelObject::center_around_origin() if (! v->modifier) bb.merge(v->mesh.bounding_box()); - // first align to origin on XYZ - Vectorf3 vector(-bb.min.x, -bb.min.y, -bb.min.z); - - // then center it on XY - Sizef3 size = bb.size(); - vector.x -= size.x/2; - vector.y -= size.y/2; - - this->translate(vector); - this->origin_translation.translate(vector); - + // 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 (!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 - Vectorf3 v = vector.negative(); - v.rotate(i->rotation); - v.scale(i->scaling_factor); - i->offset.translate(v.x, v.y); + Vec3d i_shift = i->world_matrix(true) * shift; + i->offset -= to_2d(i_shift); } this->invalidate_bounding_box(); } @@ -740,47 +690,64 @@ void ModelObject::center_around_origin() 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)); - if (m_bounding_box_valid) + 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 Pointf3 &versor) +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 = Pointf3(0,0,0); + this->origin_translation = Vec3d::Zero(); this->invalidate_bounding_box(); } -void ModelObject::rotate(float angle, const Axis &axis) +void ModelObject::rotate(float angle, const Axis& axis) { for (ModelVolume *v : this->volumes) + { v->mesh.rotate(angle, axis); - this->origin_translation = Pointf3(0,0,0); + v->m_convex_hull.rotate(angle, axis); + } + + center_around_origin(); + + this->origin_translation = Vec3d::Zero(); this->invalidate_bounding_box(); } -void ModelObject::transform(const float* matrix3x4) +void ModelObject::rotate(float angle, const Vec3d& axis) { - if (matrix3x4 == nullptr) - return; - - for (ModelVolume* v : volumes) + for (ModelVolume *v : this->volumes) { - v->mesh.transform(matrix3x4); + v->mesh.rotate(angle, axis); + v->m_convex_hull.rotate(angle, axis); } - origin_translation = Pointf3(0.0, 0.0, 0.0); - invalidate_bounding_box(); + 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); - this->origin_translation = Pointf3(0,0,0); + v->m_convex_hull.mirror(axis); + } + + this->origin_translation = Vec3d::Zero(); this->invalidate_bounding_box(); } @@ -826,33 +793,8 @@ void ModelObject::cut(coordf_t z, Model* model) const lower->add_volume(*volume); } else { TriangleMesh upper_mesh, lower_mesh; - // TODO: shouldn't we use object bounding box instead of per-volume bb? - coordf_t cut_z = z + volume->mesh.bounding_box().min.z; - if (false) { -// if (volume->mesh.has_multiple_patches()) { - // Cutting algorithm does not work on intersecting meshes. - // As we are not sure whether the meshes don't intersect, - // we rather split the mesh into multiple non-intersecting pieces. - TriangleMeshPtrs meshptrs = volume->mesh.split(); - for (TriangleMeshPtrs::iterator mesh = meshptrs.begin(); mesh != meshptrs.end(); ++mesh) { - printf("Cutting mesh patch %d of %d\n", int(mesh - meshptrs.begin()), int(meshptrs.size())); - (*mesh)->repair(); - TriangleMeshSlicer tms(*mesh); - if (mesh == meshptrs.begin()) { - tms.cut(cut_z, &upper_mesh, &lower_mesh); - } else { - TriangleMesh upper_mesh_this, lower_mesh_this; - tms.cut(cut_z, &upper_mesh_this, &lower_mesh_this); - upper_mesh.merge(upper_mesh_this); - lower_mesh.merge(lower_mesh_this); - } - delete *mesh; - } - } else { - printf("Cutting mesh patch\n"); - TriangleMeshSlicer tms(&volume->mesh); - tms.cut(cut_z, &upper_mesh, &lower_mesh); - } + TriangleMeshSlicer tms(&volume->mesh); + tms.cut(z, &upper_mesh, &lower_mesh); upper_mesh.repair(); lower_mesh.repair(); @@ -907,6 +849,27 @@ void ModelObject::split(ModelObjectPtrs* new_objects) return; } +void ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume) +{ + for (const ModelVolume* vol : this->volumes) + { + if (!vol->modifier) + { + 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; @@ -916,16 +879,16 @@ void ModelObject::print_info() const TriangleMesh mesh = this->raw_mesh(); mesh.check_topology(); BoundingBoxf3 bb = mesh.bounding_box(); - Sizef3 size = bb.size(); - cout << "size_x = " << size.x << endl; - cout << "size_y = " << size.y << endl; - cout << "size_z = " << size.z << endl; - cout << "min_x = " << bb.min.x << endl; - cout << "min_y = " << bb.min.y << endl; - cout << "min_z = " << bb.min.z << endl; - cout << "max_x = " << bb.max.x << endl; - cout << "max_y = " << bb.max.y << endl; - cout << "max_z = " << bb.max.z << endl; + 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; @@ -977,10 +940,20 @@ ModelMaterial* ModelVolume::assign_unique_material() 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; +} + // 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() +size_t ModelVolume::split(unsigned int max_extruders) { TriangleMeshPtrs meshptrs = this->mesh.split(); if (meshptrs.size() <= 1) { @@ -991,6 +964,9 @@ size_t ModelVolume::split() 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) @@ -1000,6 +976,7 @@ size_t ModelVolume::split() 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; } @@ -1009,45 +986,26 @@ size_t ModelVolume::split() void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { - mesh->rotate_z(this->rotation); // rotate around mesh origin - mesh->scale(this->scaling_factor); // scale around mesh origin - if (!dont_translate) - mesh->translate(this->offset.x, this->offset.y, 0); + 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. - double c = cos(this->rotation); - double s = sin(this->rotation); - BoundingBoxf3 bbox; - for (int i = 0; i < mesh->stl.stats.number_of_facets; ++ i) { - const stl_facet &facet = mesh->stl.facet_start[i]; - for (int j = 0; j < 3; ++ j) { - stl_vertex v = facet.vertex[j]; - double xold = v.x; - double yold = v.y; - v.x = float(c * xold - s * yold); - v.y = float(s * xold + c * yold); - bbox.merge(Pointf3(v.x, v.y, v.z)); - } - } - if (! empty(bbox)) { + 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.x *= float(this->scaling_factor); - bbox.min.y *= float(this->scaling_factor); - bbox.min.z *= float(this->scaling_factor); - bbox.max.x *= float(this->scaling_factor); - bbox.max.y *= float(this->scaling_factor); - bbox.max.z *= float(this->scaling_factor); + bbox.min *= this->scaling_factor; + bbox.max *= this->scaling_factor; } // Translate the bounding box. if (! dont_translate) { - bbox.min.x += float(this->offset.x); - bbox.min.y += float(this->offset.y); - bbox.max.x += float(this->offset.x); - bbox.max.y += float(this->offset.y); + Eigen::Map<Vec2d>(bbox.min.data()) += this->offset; + Eigen::Map<Vec2d>(bbox.max.data()) += this->offset; } } return bbox; @@ -1055,32 +1013,12 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh* mes BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const { - // rotate around mesh origin - double c = cos(this->rotation); - double s = sin(this->rotation); - Pointf3 pts[4] = { - bbox.min, - bbox.max, - Pointf3(bbox.min.x, bbox.max.y, bbox.min.z), - Pointf3(bbox.max.x, bbox.min.y, bbox.max.z) - }; - BoundingBoxf3 out; - for (int i = 0; i < 4; ++ i) { - Pointf3 &v = pts[i]; - double xold = v.x; - double yold = v.y; - v.x = float(c * xold - s * yold); - v.y = float(s * xold + c * yold); - v.x *= this->scaling_factor; - v.y *= this->scaling_factor; - v.z *= this->scaling_factor; - if (! dont_translate) { - v.x += this->offset.x; - v.y += this->offset.y; - } - out.merge(v); - } - return out; + 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 @@ -1089,4 +1027,20 @@ void ModelInstance::transform_polygon(Polygon* polygon) const 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) + m.translate(Vec3d(offset(0), offset(1), 0.0)); + + if (!dont_rotate) + m.rotate(Eigen::AngleAxisd(rotation, Vec3d::UnitZ())); + + if (!dont_scale) + m.scale(scaling_factor); + + return m; +} + } diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index bf7f60e36..8a8af481c 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -84,7 +84,7 @@ public: 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. */ - Pointf3 origin_translation; + Vec3d origin_translation; Model* get_model() const { return m_model; }; @@ -103,11 +103,8 @@ public: // 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 BoundingBoxf3& bounding_box() const; void invalidate_bounding_box() { m_bounding_box_valid = false; } - // Returns a snug bounding box of the transformed instances. - // This bounding box is not being cached. - BoundingBoxf3 tight_bounding_box(bool include_modifiers) const; // A mesh containing all transformed instances of this object. TriangleMesh mesh() const; @@ -120,11 +117,11 @@ public: // 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 Vectorf3 &vector) { this->translate(vector.x, vector.y, vector.z); } + 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 Pointf3 &versor); + void scale(const Vec3d &versor); void rotate(float angle, const Axis &axis); - void transform(const float* matrix3x4); + void rotate(float angle, const Vec3d& axis); void mirror(const Axis &axis); size_t materials_count() const; size_t facets_count() const; @@ -132,11 +129,13 @@ public: void cut(coordf_t z, Model* model) const; void split(ModelObjectPtrs* new_objects); + 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), m_bounding_box_valid(false) {} + 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); @@ -145,8 +144,9 @@ private: // Parent object, owning this ModelObject. Model *m_model; // Bounding box, cached. - BoundingBoxf3 m_bounding_box; - bool m_bounding_box_valid; + + 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. @@ -154,6 +154,10 @@ private: class ModelVolume { friend class ModelObject; + + // The convex hull of this model's mesh. + TriangleMesh m_convex_hull; + public: std::string name; // The triangular model. @@ -173,36 +177,61 @@ public: // 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(); - + size_t split(unsigned int max_extruders); + ModelMaterial* assign_unique_material(); + void calculate_convex_hull(); + const TriangleMesh& get_convex_hull() const; + private: // Parent object owning this ModelVolume. ModelObject* object; t_model_material_id _material_id; - ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), object(object) {} - ModelVolume(ModelObject *object, TriangleMesh &&mesh) : mesh(std::move(mesh)), modifier(false), object(object) {} - ModelVolume(ModelObject *object, const ModelVolume &other) : - name(other.name), mesh(other.mesh), config(other.config), modifier(other.modifier), object(object) - { this->material_id(other.material_id()); } - ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : + ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), modifier(false), 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)), modifier(false), object(object) {} + ModelVolume(ModelObject *object, const ModelVolume &other) : + name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), modifier(other.modifier), 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), modifier(other.modifier), object(object) - { this->material_id(other.material_id()); } + { + 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 { - friend class ModelObject; public: + enum EPrintVolumeState : unsigned char + { + PVS_Inside, + PVS_Partly_Outside, + PVS_Fully_Outside, + Num_BedStates + }; + + friend class ModelObject; + double rotation; // Rotation around the Z axis, in radians around mesh center point double scaling_factor; - Pointf offset; // in unscaled coordinates + Vec2d offset; // in unscaled coordinates - ModelObject* get_object() const { return this->object; }; + // 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; } // To be called on an external mesh void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; @@ -210,16 +239,22 @@ public: 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; - ModelInstance(ModelObject *object) : rotation(0), scaling_factor(1), object(object) {} + 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) {} + rotation(other.rotation), scaling_factor(other.scaling_factor), offset(other.offset), object(object), print_volume_state(PVS_Inside) {} }; @@ -230,6 +265,8 @@ private: // 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. @@ -266,9 +303,7 @@ public: bool add_default_instances(); // Returns approximate axis aligned bounding box of this model BoundingBoxf3 bounding_box() const; - // Returns tight axis aligned bounding box of this model - BoundingBoxf3 transformed_bounding_box() const; - void center_instances_around_point(const Pointf &point); + 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); @@ -278,16 +313,16 @@ public: void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); bool looks_like_multipart_object() const; - void convert_multipart_object(); + void convert_multipart_object(unsigned int max_extruders); // Ensures that the min z of the model is not negative void adjust_min_z(); - // Returs true if this model is contained into the print volume defined inside the given config - bool fits_print_volume(const DynamicPrintConfig* config) const; - bool fits_print_volume(const FullPrintConfig &config) const; - 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(); }; } diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp new file mode 100644 index 000000000..36bd8ed97 --- /dev/null +++ b/xs/src/libslic3r/ModelArrange.hpp @@ -0,0 +1,788 @@ +#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 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())) { + // 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; + + // 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 = (fullbb.width()*fullbb.height()) / (norm*norm); + auto querybb = item.boundingBox(); + + // Query the spatial index for the neighbors + std::vector<SpatElement> result; + result.reserve(spatindex.size()); + 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.45 * dist + 0.45 * density + 0.1 * alignment_score; + } + } else if( !isBig(item.area()) && spatindex.empty()) { + auto bindist = pl::distance(ibb.center(), bincenter) / norm; + // Bindist is surprisingly enough... + score = bindist; + } 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_; + double norm_; + Pile merged_pile_; + Box pilebb_; + ItemGroup remaining_; + ItemGroup items_; +public: + + _ArrBase(const TBin& bin, Distance dist, + std::function<void(unsigned)> progressind): + 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(); + + // 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}); + } + }; + + pck_.progressIndicator(progressind); + } + + 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): + _ArrBase<Box>(bin, dist, progressind) + { + + pconf_.object_function = [this, bin] (const Item &item) { + + auto result = objfunc(bin.center(), + merged_pile_, + pilebb_, + items_, + item, + bin_area_, + norm_, + rtree_, + 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): + _ArrBase<lnCircle>(bin, dist, progressind) { + + pconf_.object_function = [this, &bin] (const Item &item) { + + auto result = objfunc(bin.center(), + merged_pile_, + pilebb_, + items_, + item, + bin_area_, + norm_, + rtree_, + 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): + _ArrBase<PolygonImpl>(bin, dist, progressind) + { + 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_, + 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): + _ArrBase<Box>(Box(0, 0), dist, progressind) + { + this->pconf_.object_function = [this] (const Item &item) { + + auto result = objfunc({0, 0}, + merged_pile_, + pilebb_, + items_, + item, + 0, + norm_, + rtree_, + 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( { + ClipperLib::cInt(objinst->offset(0)/SCALING_FACTOR), + ClipperLib::cInt(objinst->offset(1)/SCALING_FACTOR) + }); + 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(); + Vec2d foff(off.X*SCALING_FACTOR + batch_offset, off.Y*SCALING_FACTOR); + + // write the tranformation data into the model instance + inst_ptr->rotation = rot; + inst_ptr->offset = foff; + } +} + + +/** + * \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). + */ +bool arrange(Model &model, coordf_t min_obj_distance, + const Slic3r::Polyline& bed, + BedShapeHint bedhint, + bool first_bin_only, + std::function<void(unsigned)> progressind) +{ + 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 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); + + // 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); + 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); + + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; + } + }; + + if(result.empty()) 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/xs/src/libslic3r/MotionPlanner.cpp b/xs/src/libslic3r/MotionPlanner.cpp index e8605d68c..ff3475ed8 100644 --- a/xs/src/libslic3r/MotionPlanner.cpp +++ b/xs/src/libslic3r/MotionPlanner.cpp @@ -58,7 +58,7 @@ 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 Line(from, to); + return Polyline(from, to); // Are both points in the same island? int island_idx_from = -1; @@ -74,7 +74,7 @@ Polyline MotionPlanner::shortest_path(const Point &from, const Point &to) // 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 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; @@ -90,7 +90,7 @@ Polyline MotionPlanner::shortest_path(const Point &from, const Point &to) 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 Line(from, to); + return Polyline(from, to); } // Now check whether points are inside the environment. @@ -224,7 +224,7 @@ const MotionPlannerGraph& MotionPlanner::init_graph(int island_idx) else v1_idx = i_v1->second; // Euclidean distance is used as weight for the graph edge - graph->add_edge(v0_idx, v1_idx, p0.distance_to(p1)); + graph->add_edge(v0_idx, v1_idx, (p1 - p0).cast<double>().norm()); } } } @@ -238,7 +238,7 @@ static inline size_t nearest_waypoint_index(const Point &start_point, const Poin size_t idx = size_t(-1); double dmin = std::numeric_limits<double>::infinity(); for (const Point &p : middle_points) { - double d = start_point.distance_to(p) + p.distance_to(end_point); + double d = (p - start_point).cast<double>().norm() + (end_point - p).cast<double>().norm(); if (d < dmin) { idx = &p - middle_points.data(); dmin = d; diff --git a/xs/src/libslic3r/MultiPoint.cpp b/xs/src/libslic3r/MultiPoint.cpp index 2e65492cd..f44897a04 100644 --- a/xs/src/libslic3r/MultiPoint.cpp +++ b/xs/src/libslic3r/MultiPoint.cpp @@ -8,59 +8,52 @@ MultiPoint::operator Points() const return this->points; } -void -MultiPoint::scale(double factor) +void MultiPoint::scale(double factor) { - for (Points::iterator it = points.begin(); it != points.end(); ++it) { - (*it).scale(factor); - } + for (Point &pt : points) + pt *= factor; } -void -MultiPoint::translate(double x, double y) +void MultiPoint::translate(double x, double y) { - for (Points::iterator it = points.begin(); it != points.end(); ++it) { - (*it).translate(x, y); - } + Vector v(x, y); + for (Point &pt : points) + pt += v; } -void -MultiPoint::translate(const Point &vector) +void MultiPoint::translate(const Point &v) { - this->translate(vector.x, vector.y); + 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.x); - double cur_y = double(pt.y); - pt.x = coord_t(round(cos_angle * cur_x - sin_angle * cur_y)); - pt.y = coord_t(round(cos_angle * cur_y + sin_angle * cur_x)); + 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 ¢er) +void MultiPoint::rotate(double angle, const Point ¢er) { double s = sin(angle); double c = cos(angle); - for (Points::iterator it = points.begin(); it != points.end(); ++it) { - double dx = double(it->x - center.x); - double dy = double(it->y - center.y); - it->x = (coord_t)round(double(center.x) + c * dx - s * dy); - it->y = (coord_t)round(double(center.y) + c * dy + s * dx); + 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() +void MultiPoint::reverse() { std::reverse(this->points.begin(), this->points.end()); } -Point -MultiPoint::first_point() const +Point MultiPoint::first_point() const { return this->points.front(); } @@ -79,16 +72,16 @@ MultiPoint::length() const int MultiPoint::find_point(const Point &point) const { - for (Points::const_iterator it = this->points.begin(); it != this->points.end(); ++it) { - if (it->coincides_with(point)) return it - this->points.begin(); - } + 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.distance_to(point.projection_onto(*this)); + double dist = (point.projection_onto(*this) - point).cast<double>().norm(); return dist < SCALED_EPSILON; } @@ -102,7 +95,7 @@ bool MultiPoint::has_duplicate_points() const { for (size_t i = 1; i < points.size(); ++i) - if (points[i-1].coincides_with(points[i])) + if (points[i-1] == points[i]) return true; return false; } @@ -112,7 +105,7 @@ MultiPoint::remove_duplicate_points() { size_t j = 0; for (size_t i = 1; i < points.size(); ++i) { - if (points[j].coincides_with(points[i])) { + if (points[j] == points[i]) { // Just increase index i. } else { ++ j; @@ -146,10 +139,10 @@ bool MultiPoint::first_intersection(const Line& line, Point* intersection) const if (l.intersection(line, &ip)) { if (! found) { found = true; - dmin = ip.distance_to(line.a); + dmin = (line.a - ip).cast<double>().norm(); *intersection = ip; } else { - double d = ip.distance_to(line.a); + double d = (line.a - ip).cast<double>().norm(); if (d < dmin) { dmin = d; *intersection = ip; @@ -160,19 +153,6 @@ bool MultiPoint::first_intersection(const Line& line, Point* intersection) const return found; } -std::string -MultiPoint::dump_perl() const -{ - std::ostringstream ret; - ret << "["; - for (Points::const_iterator p = this->points.begin(); p != this->points.end(); ++p) { - ret << p->dump_perl(); - if (p != this->points.end()-1) ret << ","; - } - ret << "]"; - return ret.str(); -} - //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 @@ -185,7 +165,7 @@ MultiPoint::_douglas_peucker(const Points &points, const double tolerance) 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 = it->distance_to(full); + double d = full.distance_to(*it); if (d > dmax) { index = it - points.begin(); dmax = d; @@ -216,25 +196,22 @@ MultiPoint::_douglas_peucker(const Points &points, const double tolerance) void MultiPoint3::translate(double x, double y) { - for (Point3& p : points) - { - p.translate(x, y); + for (Vec3crd &p : points) { + p(0) += x; + p(1) += y; } } void MultiPoint3::translate(const Point& vector) { - translate(vector.x, vector.y); + this->translate(vector(0), vector(1)); } double MultiPoint3::length() const { - Lines3 lines = this->lines(); double len = 0.0; - for (const Line3& line : lines) - { + for (const Line3& line : this->lines()) len += line.length(); - } return len; } @@ -246,15 +223,11 @@ BoundingBox3 MultiPoint3::bounding_box() const bool MultiPoint3::remove_duplicate_points() { size_t j = 0; - for (size_t i = 1; i < points.size(); ++i) - { - if (points[j].coincides_with(points[i])) - { + for (size_t i = 1; i < points.size(); ++i) { + if (points[j] == points[i]) { // Just increase index i. - } - else - { - ++j; + } else { + ++ j; if (j < i) points[j] = points[i]; } @@ -281,19 +254,19 @@ BoundingBox get_extents_rotated(const Points &points, double angle) double s = sin(angle); double c = cos(angle); Points::const_iterator it = points.begin(); - double cur_x = (double)it->x; - double cur_y = (double)it->y; - bbox.min.x = bbox.max.x = (coord_t)round(c * cur_x - s * cur_y); - bbox.min.y = bbox.max.y = (coord_t)round(c * cur_y + s * cur_x); + 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->x; - double cur_y = (double)it->y; + 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.x = std::min(x, bbox.min.x); - bbox.min.y = std::min(y, bbox.min.y); - bbox.max.x = std::max(x, bbox.max.x); - bbox.max.y = std::max(y, bbox.max.y); + 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; } diff --git a/xs/src/libslic3r/MultiPoint.hpp b/xs/src/libslic3r/MultiPoint.hpp index 0970e9a67..1fef4083b 100644 --- a/xs/src/libslic3r/MultiPoint.hpp +++ b/xs/src/libslic3r/MultiPoint.hpp @@ -18,10 +18,11 @@ public: Points points; operator Points() const; - MultiPoint() {}; + MultiPoint() {} MultiPoint(const MultiPoint &other) : points(other.points) {} MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} - explicit MultiPoint(const Points &_points): points(_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); @@ -43,9 +44,9 @@ public: int idx = -1; if (! this->points.empty()) { idx = 0; - double dist_min = this->points.front().distance_to(point); + 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].distance_to(point); + double d = (this->points[i] - point).cast<double>().norm(); if (d < dist_min) { dist_min = d; idx = i; @@ -75,7 +76,6 @@ public: bool intersection(const Line& line, Point* intersection) const; bool first_intersection(const Line& line, Point* intersection) const; - std::string dump_perl() const; static Points _douglas_peucker(const Points &points, const double tolerance); }; @@ -85,7 +85,7 @@ class MultiPoint3 public: Points3 points; - void append(const Point3& point) { this->points.push_back(point); } + void append(const Vec3crd& point) { this->points.push_back(point); } void translate(double x, double y); void translate(const Point& vector); diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 176f28926..de8aeeb2a 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -44,7 +44,6 @@ void PerimeterGenerator::process() // 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))); } @@ -52,149 +51,115 @@ void PerimeterGenerator::process() // extra perimeters for each one for (const Surface &surface : this->slices->surfaces) { // detect how many perimeters must be generated for this island - const int loop_number = this->config->perimeters + surface.extra_perimeters -1; // 0-indexed loops - - Polygons gaps; - - Polygons last = surface.expolygon.simplify_p(SCALED_RESOLUTION); - if (loop_number >= 0) { // no loops = -1 - + 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 <= loop_number+1; ++i) { // outer loop is 0 - Polygons offsets; + 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 - if (this->config->thin_walls) { - offsets = offset2( + offsets = this->config->thin_walls ? + offset2_ex( last, -(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1), - +(ext_min_spacing/2 - 1) - ); - } else { - offsets = offset(last, - ext_perimeter_width / 2); - } - + +(ext_min_spacing / 2 - 1)) : + offset_ex(last, - ext_perimeter_width / 2); // look for thin walls if (this->config->thin_walls) { - Polygons diffpp = diff( - last, - offset(offsets, ext_perimeter_width / 2), - true // medial axis requires non-overlapping geometry - ); - // 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(diffpp, -min_width/2, +min_width/2); - + 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 (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) - ex->medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); - - #ifdef DEBUG - printf(" " PRINTF_ZU " thin walls detected\n", thin_walls.size()); - #endif - - /* - if (false) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "medial_axis.svg", - no_arrows => 1, - #expolygons => \@expp, - polylines => \@thin_walls, - ); - } - */ + 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; - - if (this->config->thin_walls) { + 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. - offsets = offset2( - last, - -(distance + min_spacing/2 - 1), - +(min_spacing/2 - 1) - ); - } else { + 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 - offsets = offset( - last, - -distance - ); - } - + offset_ex(last, - distance); // look for gaps - if (this->config->gap_fill_speed.value > 0 && this->config->fill_density.value > 0) { + 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 - Polygons diff_pp = diff( - offset(last, -0.5*distance), - offset(offsets, +0.5*distance + 10) // safety offset - ); - gaps.insert(gaps.end(), diff_pp.begin(), diff_pp.end()); - } + append(gaps, diff_ex( + offset(last, -0.5 * distance), + offset(offsets, 0.5 * distance + 10))); // safety offset } - - if (offsets.empty()) break; - if (i > loop_number) break; // we were only looking for gaps this time - - last = offsets; - for (Polygons::const_iterator polygon = offsets.begin(); polygon != offsets.end(); ++polygon) { - PerimeterGeneratorLoop loop(*polygon, i); - loop.is_contour = polygon->is_counter_clockwise(); - if (loop.is_contour) { - contours[i].push_back(loop); - } else { - holes[i].push_back(loop); + 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) { + 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) { + 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) { + 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; + -- 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) { + 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; + -- i; goto NEXT_LOOP; } } @@ -202,75 +167,57 @@ void PerimeterGenerator::process() NEXT_LOOP: ; } } - // nest contour loops - for (int d = loop_number; d >= 1; --d) { + 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) { + 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) { + 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; + -- 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(); - + 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()) + if (! entities.empty()) this->loops->append(entities); } // for each loop of an island // fill gaps - if (!gaps.empty()) { - /* - SVG svg("gaps.svg"); - svg.draw(union_ex(gaps)); - svg.Close(); - */ - + if (! gaps.empty()) { // collapse double min = 0.2 * perimeter_width * (1 - INSET_OVERLAP_TOLERANCE); double max = 2. * perimeter_spacing; ExPolygons gaps_ex = diff_ex( - offset2(gaps, -min/2, +min/2), - offset2(gaps, -max/2, +max/2), - true - ); - + //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 (ExPolygons::const_iterator ex = gaps_ex.begin(); ex != gaps_ex.end(); ++ex) - ex->medial_axis(max, min, &polylines); - - if (!polylines.empty()) { + 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 @@ -279,7 +226,7 @@ void PerimeterGenerator::process() 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(last, gap_fill.polygons_covered_by_width(10.f)); + last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f)); } } @@ -287,36 +234,34 @@ void PerimeterGenerator::process() // 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 = 0; - if (loop_number == 0) { - // one loop - inset += ext_perimeter_spacing / 2; - } else if (loop_number > 0) { - // two or more loops - inset += perimeter_spacing / 2; - } + 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 -= this->config->get_abs_value("infill_overlap", inset + solid_infill_spacing / 2); + 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 : union_ex(last)) + 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); + 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( - pp, - -inset -min_perimeter_infill_spacing/2, - +min_perimeter_infill_spacing/2), + 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 +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 @@ -421,120 +366,115 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, return entities; } -ExtrusionEntityCollection -PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const +static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance) { - // 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 - const double tolerance = scale_(0.05); + ExtrusionPaths paths; + ExtrusionPath path(role); + ThickLines lines = thick_polyline.thicklines(); - ExtrusionEntityCollection coll; - for (ThickPolylines::const_iterator p = polylines.begin(); p != polylines.end(); ++p) { - ExtrusionPaths paths; - ExtrusionPath path(role); - ThickLines lines = p->thicklines(); + for (int i = 0; i < (int)lines.size(); ++i) { + const ThickLine& line = lines[i]; - 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.point_at(j*seg_len)); - - 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); + 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>()); - 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); + 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); - --i; - continue; + assert(pp.size() == segments + 1); + assert(width.size() == segments*2); } - const double w = fmax(line.a_width, line.b_width); + // 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); + } - if (path.polyline.points.empty()) { - path.polyline.append(line.a); + -- 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); - // Convert from spacing to extrusion width based on the extrusion model - // of a square extrusion ended with semi circles. - flow.width = unscale(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.push_back(path); - path = ExtrusionPath(role); - --i; - } + // we need to initialize a new line + paths.emplace_back(std::move(path)); + path = ExtrusionPath(role); + -- i; } } - if (path.polyline.is_valid()) - paths.push_back(path); - - // append paths to collection - if (!paths.empty()) { - if (paths.front().first_point().coincides_with(paths.back().last_point())) - coll.append(ExtrusionLoop(paths)); + } + 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(paths); + coll.append(std::move(paths)); } } - return coll; } -bool -PerimeterGeneratorLoop::is_internal_contour() const +bool PerimeterGeneratorLoop::is_internal_contour() const { - if (this->is_contour) { - // an internal contour is a contour containing no other contours - for (std::vector<PerimeterGeneratorLoop>::const_iterator loop = this->children.begin(); - loop != this->children.end(); ++loop) { - if (loop->is_contour) { - return false; - } - } - return true; - } - return false; + // 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/xs/src/libslic3r/PerimeterGenerator.hpp b/xs/src/libslic3r/PerimeterGenerator.hpp index dd1f9b0ec..44af8c8be 100644 --- a/xs/src/libslic3r/PerimeterGenerator.hpp +++ b/xs/src/libslic3r/PerimeterGenerator.hpp @@ -24,9 +24,8 @@ public: // Children contour, may be both CCW and CW oriented (outer contours or holes). std::vector<PerimeterGeneratorLoop> children; - PerimeterGeneratorLoop(Polygon polygon, unsigned short depth) - : polygon(polygon), is_contour(false), depth(depth) - {}; + 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. diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index 848e7fd2a..cc6f1c75e 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -414,6 +414,7 @@ namespace client 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); } @@ -421,6 +422,40 @@ namespace client 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 ¶m1, expr ¶m2, 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 ¶m1, expr ¶m2) { function_2params(param1, param2, FUNCTION_MIN); } + static void max(expr ¶m1, expr ¶m2) { function_2params(param1, param2, FUNCTION_MAX); } + static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op) { const std::string *subject = nullptr; @@ -658,7 +693,7 @@ namespace client 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(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx].dump_perl()); 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); @@ -1019,6 +1054,10 @@ namespace client | (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) ] @@ -1051,6 +1090,8 @@ namespace client ("elsif") ("endif") ("false") + ("min") + ("max") ("not") ("or") ("true"); diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index 7c1dc91f5..c2417d0dc 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -1,85 +1,72 @@ #include "Point.hpp" #include "Line.hpp" #include "MultiPoint.hpp" +#include "Int128.hpp" #include <algorithm> -#include <cmath> namespace Slic3r { -Point::Point(double x, double y) +std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t) { - this->x = lrint(x); - this->y = lrint(y); -} + unsigned int vertices_count = (unsigned int)points.size(); + if (vertices_count == 0) + return std::vector<Vec3f>(); -std::string -Point::wkt() const -{ - std::ostringstream ss; - ss << "POINT(" << this->x << " " << this->y << ")"; - return ss.str(); -} + unsigned int data_size = 3 * vertices_count * sizeof(float); -std::string -Point::dump_perl() const -{ - std::ostringstream ss; - ss << "[" << this->x << "," << this->y << "]"; - return ss.str(); -} + Eigen::MatrixXf src(3, vertices_count); + ::memcpy((void*)src.data(), (const void*)points.data(), data_size); -void -Point::scale(double factor) -{ - this->x *= factor; - this->y *= factor; -} + Eigen::MatrixXf dst(3, vertices_count); + dst = t * src.colwise().homogeneous(); -void -Point::translate(double x, double y) -{ - this->x += x; - this->y += y; + std::vector<Vec3f> ret_points(vertices_count, Vec3f::Zero()); + ::memcpy((void*)ret_points.data(), (const void*)dst.data(), data_size); + return ret_points; } -void -Point::translate(const Vector &vector) +Pointf3s transform(const Pointf3s& points, const Transform3d& t) { - this->translate(vector.x, vector.y); -} + unsigned int vertices_count = (unsigned int)points.size(); + if (vertices_count == 0) + return Pointf3s(); -void -Point::rotate(double angle) -{ - double cur_x = (double)this->x; - double cur_y = (double)this->y; - double s = sin(angle); - double c = cos(angle); - this->x = (coord_t)round(c * cur_x - s * cur_y); - this->y = (coord_t)round(c * cur_y + s * cur_x); + 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, const Point ¢er) +void Point::rotate(double angle) { - double cur_x = (double)this->x; - double cur_y = (double)this->y; - double s = sin(angle); - double c = cos(angle); - double dx = cur_x - (double)center.x; - double dy = cur_y - (double)center.y; - this->x = (coord_t)round( (double)center.x + c * dx - s * dy ); - this->y = (coord_t)round( (double)center.y + c * dy + s * dx ); + 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); } -bool -Point::coincides_with_epsilon(const Point &point) const +void Point::rotate(double angle, const Point ¢er) { - return std::abs(this->x - point.x) < SCALED_EPSILON && std::abs(this->y - point.y) < SCALED_EPSILON; + 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 +int Point::nearest_point_index(const Points &points) const { PointConstPtrs p; p.reserve(points.size()); @@ -96,12 +83,12 @@ int Point::nearest_point_index(const PointConstPtrs &points) const 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->x - (*it)->x); + 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->y - (*it)->y); + d += sqr<double>((*this)(1) - (*it)->y()); if (distance != -1 && d > distance) continue; idx = it - points.begin(); @@ -113,8 +100,7 @@ int Point::nearest_point_index(const PointConstPtrs &points) const return idx; } -int -Point::nearest_point_index(const PointPtrs &points) const +int Point::nearest_point_index(const PointPtrs &points) const { PointConstPtrs p; p.reserve(points.size()); @@ -123,8 +109,7 @@ Point::nearest_point_index(const PointPtrs &points) const return this->nearest_point_index(p); } -bool -Point::nearest_point(const Points &points, Point* point) const +bool Point::nearest_point(const Points &points, Point* point) const { int idx = this->nearest_point_index(points); if (idx == -1) return false; @@ -132,40 +117,6 @@ Point::nearest_point(const Points &points, Point* point) const return true; } -/* distance to the closest point of line */ -double -Point::distance_to(const Line &line) const -{ - const double dx = line.b.x - line.a.x; - const double dy = line.b.y - line.a.y; - - const double l2 = dx*dx + dy*dy; // avoid a sqrt - if (l2 == 0.0) return this->distance_to(line.a); // line.a == line.b case - - // 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 = ((this->x - line.a.x) * dx + (this->y - line.a.y) * dy) / l2; - if (t < 0.0) return this->distance_to(line.a); // beyond the 'a' end of the segment - else if (t > 1.0) return this->distance_to(line.b); // beyond the 'b' end of the segment - Point projection( - line.a.x + t * dx, - line.a.y + t * dy - ); - return this->distance_to(projection); -} - -double -Point::perp_distance_to(const Line &line) const -{ - if (line.a.coincides_with(line.b)) return this->distance_to(line.a); - - double n = (double)(line.b.x - line.a.x) * (double)(line.a.y - this->y) - - (double)(line.a.x - this->x) * (double)(line.b.y - line.a.y); - - return std::abs(n) / line.length(); -} - /* 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. @@ -173,51 +124,46 @@ Point::perp_distance_to(const Line &line) const * 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 +double Point::ccw(const Point &p1, const Point &p2) const { - return (double)(p2.x - p1.x)*(double)(this->y - p1.y) - (double)(p2.y - p1.y)*(double)(this->x - p1.x); + 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 +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 Point::ccw_angle(const Point &p1, const Point &p2) const { - double angle = atan2(p1.x - this->x, p1.y - this->y) - - atan2(p2.x - this->x, p2.y - this->y); + 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 Point::projection_onto(const MultiPoint &poly) const { Point running_projection = poly.first_point(); - double running_min = this->distance_to(running_projection); + 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 (this->distance_to(point_temp) < running_min) { + if ((point_temp - *this).cast<double>().norm() < running_min) { running_projection = point_temp; - running_min = this->distance_to(running_projection); + running_min = (running_projection - *this).cast<double>().norm(); } } return running_projection; } -Point -Point::projection_onto(const Line &line) const +Point Point::projection_onto(const Line &line) const { - if (line.a.coincides_with(line.b)) return line.a; + if (line.a == line.b) return line.a; /* (Ported from VisiLibity by Karl J. Obermeyer) @@ -228,151 +174,37 @@ Point::projection_onto(const Line &line) const 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.x - line.a.x); - double ly = (double)(line.b.y - line.a.y); - double theta = ( (double)(line.b.x - this->x)*lx + (double)(line.b.y- this->y)*ly ) + 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 + (1.0-theta) * line.b; + return (theta * line.a.cast<coordf_t>() + (1.0-theta) * line.b.cast<coordf_t>()).cast<coord_t>(); // Else pick closest endpoint. - if (this->distance_to(line.a) < this->distance_to(line.b)) { - return line.a; - } else { - return line.b; - } -} - -Point -Point::negative() const -{ - return Point(-this->x, -this->y); -} - -Vector -Point::vector_to(const Point &point) const -{ - return Vector(point.x - this->x, point.y - this->y); -} - -std::ostream& -operator<<(std::ostream &stm, const Pointf &pointf) -{ - return stm << pointf.x << "," << pointf.y; + return ((line.a - *this).cast<double>().squaredNorm() < (line.b - *this).cast<double>().squaredNorm()) ? line.a : line.b; } -std::string -Pointf::wkt() const +std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf) { - std::ostringstream ss; - ss << "POINT(" << this->x << " " << this->y << ")"; - return ss.str(); + return stm << pointf(0) << "," << pointf(1); } -std::string -Pointf::dump_perl() const -{ - std::ostringstream ss; - ss << "[" << this->x << "," << this->y << "]"; - return ss.str(); -} - -void -Pointf::scale(double factor) -{ - this->x *= factor; - this->y *= factor; -} - -void -Pointf::translate(double x, double y) -{ - this->x += x; - this->y += y; -} - -void -Pointf::translate(const Vectorf &vector) -{ - this->translate(vector.x, vector.y); -} - -void -Pointf::rotate(double angle) -{ - double cur_x = this->x; - double cur_y = this->y; - double s = sin(angle); - double c = cos(angle); - this->x = c * cur_x - s * cur_y; - this->y = c * cur_y + s * cur_x; -} - -void -Pointf::rotate(double angle, const Pointf ¢er) -{ - double cur_x = this->x; - double cur_y = this->y; - double s = sin(angle); - double c = cos(angle); - double dx = cur_x - center.x; - double dy = cur_y - center.y; - this->x = center.x + c * dx - s * dy; - this->y = center.y + c * dy + s * dx; -} +namespace int128 { -Pointf -Pointf::negative() const +int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3) { - return Pointf(-this->x, -this->y); + Slic3r::Vector v1(p2 - p1); + Slic3r::Vector v2(p3 - p1); + return Int128::sign_determinant_2x2_filtered(v1(0), v1(1), v2(0), v2(1)); } -Vectorf -Pointf::vector_to(const Pointf &point) const +int cross(const Vec2crd &v1, const Vec2crd &v2) { - return Vectorf(point.x - this->x, point.y - this->y); + return Int128::sign_determinant_2x2_filtered(v1(0), v1(1), v2(0), v2(1)); } -void -Pointf3::scale(double factor) -{ - Pointf::scale(factor); - this->z *= factor; -} - -void -Pointf3::translate(const Vectorf3 &vector) -{ - this->translate(vector.x, vector.y, vector.z); -} - -void -Pointf3::translate(double x, double y, double z) -{ - Pointf::translate(x, y); - this->z += z; -} - -double -Pointf3::distance_to(const Pointf3 &point) const -{ - double dx = ((double)point.x - this->x); - double dy = ((double)point.y - this->y); - double dz = ((double)point.z - this->z); - return sqrt(dx*dx + dy*dy + dz*dz); -} - -Pointf3 -Pointf3::negative() const -{ - return Pointf3(-this->x, -this->y, -this->z); -} - -Vectorf3 -Pointf3::vector_to(const Pointf3 &point) const -{ - return Vectorf3(point.x - this->x, point.y - this->y, point.z - this->z); } } diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 6c9096a3d..c1aff561f 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -2,89 +2,131 @@ #define slic3r_Point_hpp_ #include "libslic3r.h" +#include <cstddef> #include <vector> -#include <math.h> +#include <cmath> #include <string> #include <sstream> #include <unordered_map> +#include <Eigen/Geometry> + namespace Slic3r { class Line; -class Linef; class MultiPoint; class Point; -class Point3; -class Pointf; -class Pointf3; typedef Point Vector; -typedef Point3 Vector3; -typedef Pointf Vectorf; -typedef Pointf3 Vectorf3; -typedef std::vector<Point> Points; -typedef std::vector<Point*> PointPtrs; -typedef std::vector<const Point*> PointConstPtrs; -typedef std::vector<Point3> Points3; -typedef std::vector<Pointf> Pointfs; -typedef std::vector<Pointf3> Pointf3s; -class Point +// 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<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; - coord_t x; - coord_t y; - Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {}; - Point(int64_t _x, int64_t _y): x(coord_t(_x)), y(coord_t(_y)) {}; // for Clipper - Point(double x, double y); + + 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))); } - bool operator==(const Point& rhs) const { return this->x == rhs.x && this->y == rhs.y; } - bool operator!=(const Point& rhs) const { return ! (*this == rhs); } - bool operator<(const Point& rhs) const { return this->x < rhs.x || (this->x == rhs.x && this->y < rhs.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; + } - Point& operator+=(const Point& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } - Point& operator-=(const Point& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } - Point& operator*=(const coord_t& rhs) { this->x *= rhs; this->y *= rhs; return *this; } + bool operator< (const Point& rhs) const { return (*this)(0) < rhs(0) || ((*this)(0) == rhs(0) && (*this)(1) < rhs(1)); } - std::string wkt() const; - std::string dump_perl() const; - void scale(double factor); - void translate(double x, double y); - void translate(const Vector &vector); - void rotate(double angle); - void rotate(double angle, const Point ¢er); - Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } - Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } - bool coincides_with(const Point &point) const { return this->x == point.x && this->y == point.y; } - bool coincides_with_epsilon(const Point &point) const; - 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 distance_to(const Point &point) const { return sqrt(distance_to_sq(point)); } - double distance_to_sq(const Point &point) const { double dx = double(point.x - this->x); double dy = double(point.y - this->y); return dx*dx + dy*dy; } - double distance_to(const Line &line) const; - double perp_distance_to(const Line &line) const; + 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 ¢er); + Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } + Point rotated(double angle, const Point ¢er) 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; - Point negative() const; - Vector vector_to(const Point &point) const; + Point projection_onto(const MultiPoint &poly) const; + Point projection_onto(const Line &line) const; }; -inline Point operator+(const Point& point1, const Point& point2) { return Point(point1.x + point2.x, point1.y + point2.y); } -inline Point operator-(const Point& point1, const Point& point2) { return Point(point1.x - point2.x, point1.y - point2.y); } -inline Point operator*(double scalar, const Point& point2) { return Point(scalar * point2.x, scalar * point2.y); } -inline int64_t cross(const Point &v1, const Point &v2) { return int64_t(v1.x) * int64_t(v2.y) - int64_t(v1.y) * int64_t(v2.x); } -inline int64_t dot(const Point &v1, const Point &v2) { return int64_t(v1.x) * int64_t(v2.x) + int64_t(v1.y) * int64_t(v2.y); } +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 Point &pt) const { - return std::hash<coord_t>()(pt.x) ^ std::hash<coord_t>()(pt.y); + size_t operator()(const Vec2crd &pt) const { + return std::hash<coord_t>()(pt(0)) ^ std::hash<coord_t>()(pt(1)); } }; @@ -128,36 +170,36 @@ public: } void insert(const ValueType &value) { - const Point *pt = m_point_accessor(value); + const Vec2crd *pt = m_point_accessor(value); if (pt != nullptr) - m_map.emplace(std::make_pair(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2), value)); + m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), value)); } void insert(ValueType &&value) { - const Point *pt = m_point_accessor(value); + const Vec2crd *pt = m_point_accessor(value); if (pt != nullptr) - m_map.emplace(std::make_pair(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2), std::move(value))); + 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 Point &pt) { + 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. - Point grid_corner((pt.x+(m_grid_resolution>>1))>>m_grid_log2, (pt.y+(m_grid_resolution>>1))>>m_grid_log2); + 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(Point(grid_corner.x + neighbor_x, grid_corner.y + neighbor_y)); + 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 Point *pt2 = m_point_accessor(value); + const Vec2crd *pt2 = m_point_accessor(value); if (pt2 != nullptr) { - const double d2 = pt.distance_to_sq(*pt2); + const double d2 = (pt - *pt2).squaredNorm(); if (d2 < dist_min) { dist_min = d2; value_min = &value; @@ -172,7 +214,7 @@ public: } private: - typedef typename std::unordered_multimap<Point, ValueType, PointHash> map_type; + typedef typename std::unordered_multimap<Vec2crd, ValueType, PointHash> map_type; PointAccessor m_point_accessor; map_type m_map; coord_t m_search_radius; @@ -180,107 +222,7 @@ private: coord_t m_grid_log2; }; -class Point3 : public Point -{ -public: - coord_t z; - explicit Point3(coord_t _x = 0, coord_t _y = 0, coord_t _z = 0): Point(_x, _y), z(_z) {}; - static Point3 new_scale(coordf_t x, coordf_t y, coordf_t z) { return Point3(coord_t(scale_(x)), coord_t(scale_(y)), coord_t(scale_(z))); } - bool operator==(const Point3 &rhs) const { return this->x == rhs.x && this->y == rhs.y && this->z == rhs.z; } - bool operator!=(const Point3 &rhs) const { return ! (*this == rhs); } - bool coincides_with(const Point3& rhs) const { return this->x == rhs.x && this->y == rhs.y && this->z == rhs.z; } -private: - // Hide the following inherited methods: - bool operator==(const Point &rhs) const; - bool operator!=(const Point &rhs) const; -}; - -std::ostream& operator<<(std::ostream &stm, const Pointf &pointf); - -class Pointf -{ -public: - typedef coordf_t coord_type; - coordf_t x; - coordf_t y; - explicit Pointf(coordf_t _x = 0, coordf_t _y = 0): x(_x), y(_y) {}; - static Pointf new_unscale(coord_t x, coord_t y) { - return Pointf(unscale(x), unscale(y)); - }; - static Pointf new_unscale(const Point &p) { - return Pointf(unscale(p.x), unscale(p.y)); - }; - std::string wkt() const; - std::string dump_perl() const; - void scale(double factor); - void translate(double x, double y); - void translate(const Vectorf &vector); - void rotate(double angle); - void rotate(double angle, const Pointf ¢er); - Pointf negative() const; - Vectorf vector_to(const Pointf &point) const; - - Pointf& operator+=(const Pointf& rhs) { this->x += rhs.x; this->y += rhs.y; return *this; } - Pointf& operator-=(const Pointf& rhs) { this->x -= rhs.x; this->y -= rhs.y; return *this; } - Pointf& operator*=(const coordf_t& rhs) { this->x *= rhs; this->y *= rhs; return *this; } - - bool operator==(const Pointf &rhs) const { return this->x == rhs.x && this->y == rhs.y; } - bool operator!=(const Pointf &rhs) const { return ! (*this == rhs); } - bool operator< (const Pointf& rhs) const { return this->x < rhs.x || (this->x == rhs.x && this->y < rhs.y); } -}; - -inline Pointf operator+(const Pointf& point1, const Pointf& point2) { return Pointf(point1.x + point2.x, point1.y + point2.y); } -inline Pointf operator-(const Pointf& point1, const Pointf& point2) { return Pointf(point1.x - point2.x, point1.y - point2.y); } -inline Pointf operator*(double scalar, const Pointf& point2) { return Pointf(scalar * point2.x, scalar * point2.y); } -inline Pointf operator*(const Pointf& point2, double scalar) { return Pointf(scalar * point2.x, scalar * point2.y); } -inline coordf_t cross(const Pointf &v1, const Pointf &v2) { return v1.x * v2.y - v1.y * v2.x; } -inline coordf_t dot(const Pointf &v1, const Pointf &v2) { return v1.x * v2.x + v1.y * v2.y; } -inline coordf_t dot(const Pointf &v) { return v.x * v.x + v.y * v.y; } -inline double length(const Vectorf &v) { return sqrt(dot(v)); } -inline double l2(const Vectorf &v) { return dot(v); } - -class Pointf3 : public Pointf -{ -public: - coordf_t z; - explicit Pointf3(coordf_t _x = 0, coordf_t _y = 0, coordf_t _z = 0): Pointf(_x, _y), z(_z) {}; - static Pointf3 new_unscale(coord_t x, coord_t y, coord_t z) { - return Pointf3(unscale(x), unscale(y), unscale(z)); - }; - static Pointf3 new_unscale(const Point3& p) { return Pointf3(unscale(p.x), unscale(p.y), unscale(p.z)); } - void scale(double factor); - void translate(const Vectorf3 &vector); - void translate(double x, double y, double z); - double distance_to(const Pointf3 &point) const; - Pointf3 negative() const; - Vectorf3 vector_to(const Pointf3 &point) const; - - bool operator==(const Pointf3 &rhs) const { return this->x == rhs.x && this->y == rhs.y && this->z == rhs.z; } - bool operator!=(const Pointf3 &rhs) const { return ! (*this == rhs); } - -private: - // Hide the following inherited methods: - bool operator==(const Pointf &rhs) const; - bool operator!=(const Pointf &rhs) const; -}; - -inline Pointf3 operator+(const Pointf3& p1, const Pointf3& p2) { return Pointf3(p1.x + p2.x, p1.y + p2.y, p1.z + p2.z); } -inline Pointf3 operator-(const Pointf3& p1, const Pointf3& p2) { return Pointf3(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z); } -inline Pointf3 operator-(const Pointf3& p) { return Pointf3(-p.x, -p.y, -p.z); } -inline Pointf3 operator*(double scalar, const Pointf3& p) { return Pointf3(scalar * p.x, scalar * p.y, scalar * p.z); } -inline Pointf3 operator*(const Pointf3& p, double scalar) { return Pointf3(scalar * p.x, scalar * p.y, scalar * p.z); } -inline Pointf3 cross(const Pointf3& v1, const Pointf3& v2) { return Pointf3(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x); } -inline coordf_t dot(const Pointf3& v1, const Pointf3& v2) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; } -inline Pointf3 normalize(const Pointf3& v) -{ - coordf_t len = ::sqrt(sqr(v.x) + sqr(v.y) + sqr(v.z)); - return (len != 0.0) ? 1.0 / len * v : Pointf3(0.0, 0.0, 0.0); -} - -template<typename TO> inline TO convert_to(const Point &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y)); } -template<typename TO> inline TO convert_to(const Pointf &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y)); } -template<typename TO> inline TO convert_to(const Point3 &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y), typename TO::coord_type(src.z)); } -template<typename TO> inline TO convert_to(const Pointf3 &src) { return TO(typename TO::coord_type(src.x), typename TO::coord_type(src.y), typename TO::coord_type(src.z)); } +std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf); } // namespace Slic3r @@ -296,7 +238,7 @@ namespace boost { namespace polygon { typedef coord_t coordinate_type; static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { - return (orient == HORIZONTAL) ? point.x : point.y; + return (orient == HORIZONTAL) ? (coordinate_type)point(0) : (coordinate_type)point(1); } }; @@ -305,14 +247,14 @@ namespace boost { namespace polygon { typedef coord_t coordinate_type; static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { if (orient == HORIZONTAL) - point.x = value; + point(0) = value; else - point.y = value; + point(1) = value; } static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { Slic3r::Point retval; - retval.x = x_value; - retval.y = y_value; + retval(0) = x_value; + retval(1) = y_value; return retval; } }; diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index 27f9a2ca1..14248d84f 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -44,11 +44,9 @@ Polyline Polygon::split_at_vertex(const Point &point) const { // find index of point - for (Points::const_iterator it = this->points.begin(); it != this->points.end(); ++it) { - if (it->coincides_with(point)) { - return this->split_at_index(it - this->points.begin()); - } - } + for (const Point &pt : this->points) + if (pt == point) + return this->split_at_index(&pt - &this->points.front()); CONFESS("Point not found"); return Polyline(); } @@ -88,7 +86,7 @@ int64_t Polygon::area2x() const int64_t a = 0; for (size_t i = 0, j = n - 1; i < n; ++i) - a += int64_t(poly[j].x + poly[i].x) * int64_t(poly[j].y - poly[i].y); + a += int64_t(poly[j](0) + poly[i](0)) * int64_t(poly[j](1) - poly[i](1)); j = i; } return -a * 0.5; @@ -103,7 +101,7 @@ double Polygon::area() const double a = 0.; for (size_t i = 0, j = n - 1; i < n; ++i) { - a += double(points[j].x + points[i].x) * double(points[i].y - points[j].y); + a += ((double)points[j](0) + (double)points[i](0)) * ((double)points[i](1) - (double)points[j](1)); j = i; } return 0.5 * a; @@ -157,17 +155,17 @@ Polygon::contains(const Point &point) const 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.y well. - // Does the ray with y == point.y intersect this line segment? + //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->y > point.y) != (j->y > point.y)) - && ((double)point.x < (double)(j->x - i->x) * (double)(point.y - i->y) / (double)(j->y - i->y) + (double)i->x) ) + 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->y > point.y) != (j->y > point.y)) { + if (((*i)(1) > point(1)) != ((*j)(1) > point(1))) { // Orientation predicated relative to i-th point. - double orient = (double)(point.x - i->x) * (double)(j->y - i->y) - (double)(point.y - i->y) * (double)(j->x - i->x); - if ((i->y > j->y) ? (orient > 0.) : (orient < 0.)) + 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 @@ -225,26 +223,13 @@ Polygon::centroid() const 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 ); + 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)); } -std::string -Polygon::wkt() const -{ - std::ostringstream wkt; - wkt << "POLYGON(("; - for (Points::const_iterator p = this->points.begin(); p != this->points.end(); ++p) { - wkt << p->x << " " << p->y; - if (p != this->points.end()-1) wkt << ","; - } - wkt << "))"; - return wkt.str(); -} - // find all concave vertices (i.e. having an internal angle greater than the supplied angle) // (external = right side, thus we consider ccw orientation) Points @@ -302,24 +287,24 @@ Point Polygon::point_projection(const Point &point) const 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 = pt0.distance_to(point); + double d = (point - pt0).cast<double>().norm(); if (d < dmin) { dmin = d; proj = pt0; } - d = pt1.distance_to(point); + d = (point - pt1).cast<double>().norm(); if (d < dmin) { dmin = d; proj = pt1; } - Pointf v1(coordf_t(pt1.x - pt0.x), coordf_t(pt1.y - pt0.y)); - coordf_t div = dot(v1); + Vec2d v1(coordf_t(pt1(0) - pt0(0)), coordf_t(pt1(1) - pt0(1))); + coordf_t div = v1.squaredNorm(); if (div > 0.) { - Pointf v2(coordf_t(point.x - pt0.x), coordf_t(point.y - pt0.y)); - coordf_t t = dot(v1, v2) / div; + 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.x) + t * v1.x + 0.5)), coord_t(floor(coordf_t(pt0.y) + t * v1.y + 0.5))); - d = foot.distance_to(point); + 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; @@ -376,12 +361,12 @@ 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.x) * int64_t(v2.x) + int64_t(v1.y) * int64_t(v2.y); + 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.x) * double(v1.x) + double(v1.y) * double(v1.y); - double l2_2 = double(v2.x) * double(v2.x) + double(v2.y) * double(v2.y); + 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. @@ -389,7 +374,7 @@ static inline bool is_stick(const Point &p1, const Point &p2, const Point &p3) // 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.x) * double(v2.y) - double(v2.x) * double(v1.y); + 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; } diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index 1a02d78b7..54909352c 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -24,11 +24,12 @@ public: 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(std::vector<Pointf> points) { - Points int_points; - for (auto pt : points) - int_points.push_back(Point::new_scale(pt.x, pt.y)); - return Polygon(int_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; } @@ -54,7 +55,6 @@ public: void simplify(double tolerance, Polygons &polygons) const; void triangulate_convex(Polygons* polygons) const; Point centroid() const; - std::string wkt() const; Points concave_points(double angle = PI) const; Points convex_points(double angle = PI) const; // Projection of a point onto the polygon. diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index 3432506c6..b2e50bca3 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -33,7 +33,7 @@ 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->x < p.x) p = *it; + if ((*it)(0) < p(0)) p = *it; } return p; } @@ -52,92 +52,82 @@ Polyline::lines() const } // removes the given distance from the end of the polyline -void -Polyline::clip_end(double distance) +void Polyline::clip_end(double distance) { while (distance > 0) { - Point last_point = this->last_point(); + Vec2d last_point = this->last_point().cast<double>(); this->points.pop_back(); - if (this->points.empty()) break; - - double last_segment_length = last_point.distance_to(this->last_point()); - if (last_segment_length <= distance) { - distance -= last_segment_length; - continue; + 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; } - - Line segment(last_point, this->last_point()); - this->points.push_back(segment.point_at(distance)); - distance = 0; + distance -= sqrt(lsqr); } } // removes the given distance from the start of the polyline -void -Polyline::clip_start(double distance) +void Polyline::clip_start(double distance) { this->reverse(); this->clip_end(distance); - if (this->points.size() >= 2) this->reverse(); + if (this->points.size() >= 2) + this->reverse(); } -void -Polyline::extend_end(double distance) +void Polyline::extend_end(double distance) { // relocate last point by extending the last segment by the specified length - Line line( - this->points.back(), - *(this->points.end() - 2) - ); - this->points.back() = line.point_at(-distance); + 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) +void Polyline::extend_start(double distance) { // relocate first point by extending the first segment by the specified length - this->points.front() = Line(this->points.front(), this->points[1]).point_at(-distance); + 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 Polyline::equally_spaced_points(double distance) const { Points points; - points.push_back(this->first_point()); + points.emplace_back(this->first_point()); double len = 0; for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) { - double segment_length = it->distance_to(*(it-1)); + 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) + continue; if (len == distance) { - points.push_back(*it); + points.emplace_back(*it); len = 0; continue; } - double take = segment_length - (len - distance); // how much we take of this segment - Line segment(*(it-1), *it); - points.push_back(segment.point_at(take)); - --it; - len = -take; + points.emplace_back((p1 + v * (take / v.norm())).cast<coord_t>()); + -- it; + len = - take; } return points; } -void -Polyline::simplify(double tolerance) +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) +void Polyline::simplify_by_visibility(const T &area) { Points &pp = this->points; @@ -157,30 +147,29 @@ Polyline::simplify_by_visibility(const T &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 +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 = point.distance_to(p); + 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 (point.distance_to(p_tmp) < min) { + if ((p_tmp - point).cast<double>().norm() < min) { p = p_tmp; - min = point.distance_to(p); + 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.coincides_with(p)) p1->points.push_back(line->a); - } + 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); @@ -193,8 +182,7 @@ Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const } } -bool -Polyline::is_straight() const +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 @@ -208,19 +196,6 @@ Polyline::is_straight() const return true; } -std::string -Polyline::wkt() const -{ - std::ostringstream wkt; - wkt << "LINESTRING(("; - for (Points::const_iterator p = this->points.begin(); p != this->points.end(); ++p) { - wkt << p->x << " " << p->y; - if (p != this->points.end()-1) wkt << ","; - } - wkt << "))"; - return wkt.str(); -} - BoundingBox get_extents(const Polyline &polyline) { return polyline.bounding_box(); @@ -254,30 +229,17 @@ bool remove_degenerate(Polylines &polylines) return modified; } -ThickLines -ThickPolyline::thicklines() const +ThickLines ThickPolyline::thicklines() const { ThickLines lines; if (this->points.size() >= 2) { lines.reserve(this->points.size() - 1); - for (size_t i = 0; i < this->points.size()-1; ++i) { - ThickLine line(this->points[i], this->points[i+1]); - line.a_width = this->width[2*i]; - line.b_width = this->width[2*i+1]; - lines.push_back(line); - } + 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; } -void -ThickPolyline::reverse() -{ - Polyline::reverse(); - std::reverse(this->width.begin(), this->width.end()); - std::swap(this->endpoints.first, this->endpoints.second); -} - Lines3 Polyline3::lines() const { Lines3 lines; diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index b64743d84..0c934e074 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -19,14 +19,15 @@ 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); } 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(std::vector<Pointf> points) { + static Polyline new_scale(const std::vector<Vec2d> &points) { Polyline pl; - Points int_points; - for (auto pt : points) - int_points.push_back(Point::new_scale(pt.x, pt.y)); - pl.append(int_points); + pl.points.reserve(points.size()); + for (const Vec2d &pt : points) + pl.points.emplace_back(Point::new_scale(pt(0), pt(1))); return pl; } @@ -71,7 +72,6 @@ public: 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; - std::string wkt() const; }; extern BoundingBox get_extents(const Polyline &polyline); @@ -129,12 +129,17 @@ inline void polylines_append(Polylines &dst, Polylines &&src) bool remove_degenerate(Polylines &polylines); class ThickPolyline : public Polyline { - public: - std::vector<coordf_t> width; - std::pair<bool,bool> endpoints; - ThickPolyline() : endpoints(std::make_pair(false, false)) {}; +public: + ThickPolyline() : endpoints(std::make_pair(false, false)) {} ThickLines thicklines() const; - void reverse(); + 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 diff --git a/xs/src/libslic3r/PolylineCollection.cpp b/xs/src/libslic3r/PolylineCollection.cpp index ca9c64d23..3f65ea699 100644 --- a/xs/src/libslic3r/PolylineCollection.cpp +++ b/xs/src/libslic3r/PolylineCollection.cpp @@ -15,9 +15,9 @@ inline int nearest_point_index(const std::vector<Chaining> &pairs, const Point & 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.x - it->first.x)); + T d = sqr(T(start_near(0) - it->first(0))); if (d <= dmin) { - d += sqr(T(start_near.y - it->first.y)); + d += sqr(T(start_near(1) - it->first(1))); if (d < dmin) { idx = (it - pairs.begin()) * 2; dmin = d; @@ -26,9 +26,9 @@ inline int nearest_point_index(const std::vector<Chaining> &pairs, const Point & } } if (! no_reverse) { - d = sqr(T(start_near.x - it->last.x)); + d = sqr(T(start_near(0) - it->last(0))); if (d <= dmin) { - d += sqr(T(start_near.y - it->last.y)); + d += sqr(T(start_near(1) - it->last(1))); if (d < dmin) { idx = (it - pairs.begin()) * 2 + 1; dmin = d; @@ -82,7 +82,7 @@ Point PolylineCollection::leftmost_point(const Polylines &polylines) Point p = it->leftmost_point(); for (++ it; it != polylines.end(); ++it) { Point p2 = it->leftmost_point(); - if (p2.x < p.x) + if (p2(0) < p(0)) p = p2; } return p; diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index e8a010a79..ae02216df 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -4,6 +4,7 @@ #include "Extruder.hpp" #include "Flow.hpp" #include "Geometry.hpp" +#include "I18N.hpp" #include "SupportMaterial.hpp" #include "GCode.hpp" #include "GCode/WipeTowerPrusaMM.hpp" @@ -13,6 +14,13 @@ #include <boost/lexical_cast.hpp> #include <boost/log/trivial.hpp> +//#include "slic3r/ProgressIndicator.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>; @@ -71,6 +79,13 @@ bool Print::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)); @@ -127,7 +142,6 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option "gcode_comments", "gcode_flavor", "infill_acceleration", - "infill_first", "layer_gcode", "min_fan_speed", "max_fan_speed", @@ -154,6 +168,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option "retract_restart_extra", "retract_restart_extra_toolchange", "retract_speed", + "single_extruder_multi_material_priming", "slowdown_below_layer_time", "standby_temperature_delta", "start_gcode", @@ -165,7 +180,10 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option "use_relative_e_distances", "use_volumetric_e", "variable_layer_height", - "wipe" + "wipe", + "wipe_tower_x", + "wipe_tower_y", + "wipe_tower_rotation_angle" }; static std::unordered_set<std::string> steps_ignore; @@ -173,6 +191,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option 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, @@ -199,15 +218,29 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option || 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_x" - || opt_key == "wipe_tower_y" || opt_key == "wipe_tower_width" - || opt_key == "wipe_tower_per_color_wipe" + || 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 ( @@ -219,7 +252,6 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option osteps.emplace_back(posSupportMaterial); steps.emplace_back(psSkirt); steps.emplace_back(psBrim); - steps.emplace_back(psWipeTower); } else { // for legacy, if we can't handle this option let's invalidate all steps //FIXME invalidate all steps of all objects as well? @@ -361,7 +393,7 @@ void Print::add_model_object(ModelObject* model_object, int idx) } // If no region exists with the same config, create a new one. if (region_id == size_t(-1)) { - region_id = this->regions().size(); + region_id = m_regions.size(); this->add_region(config); } // Assign volume to a region. @@ -456,7 +488,7 @@ bool Print::apply_config(DynamicPrintConfig config) 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 + // 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; @@ -530,14 +562,20 @@ bool Print::has_skirt() const std::string Print::validate() const { BoundingBox bed_box_2D = get_extents(Polygon::new_scale(m_config.bed_shape.values)); - BoundingBoxf3 print_volume(Pointf3(unscale(bed_box_2D.min.x), unscale(bed_box_2D.min.y), 0.0), Pointf3(unscale(bed_box_2D.max.x), unscale(bed_box_2D.max.y), m_config.max_print_height)); + 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.z = -1e10; + print_volume.min(2) = -1e10; + unsigned int printable_count = 0; for (PrintObject *po : m_objects) { - if (! print_volume.contains(po->model_object()->tight_bounding_box(false))) - return "Some objects are outside of the print volume."; + 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. { @@ -558,11 +596,11 @@ std::string Print::validate() const // 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 © : object->_shifted_copies) { + for (const Point © : object->m_copies) { Polygon p = convex_hull; p.translate(copy); if (! intersection(convex_hulls_other, p).empty()) - return "Some objects are too close; your extruder will collide with them."; + return L("Some objects are too close; your extruder will collide with them."); polygons_append(convex_hulls_other, p); } } @@ -571,13 +609,13 @@ std::string Print::validate() const { std::vector<coord_t> object_height; for (const PrintObject *object : m_objects) - object_height.insert(object_height.end(), object->copies().size(), object->size.z); + 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 "Some objects are too tall and cannot be printed without extruder collisions."; + return L("Some objects are too tall and cannot be printed without extruder collisions."); } } // end if (m_config.complete_objects) @@ -587,40 +625,65 @@ std::string Print::validate() const total_copies_count += object->copies().size(); // #4043 if (total_copies_count > 1 && ! m_config.complete_objects.value) - return "The Spiral Vase option can only be used when printing a single object."; + return L("The Spiral Vase option can only be used when printing a single object."); if (m_regions.size() > 1) - return "The Spiral Vase option can only be used when printing single material objects."; + 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 0 - for (auto dmr : m_config.nozzle_diameter.values) - if (std::abs(dmr - 0.4) > EPSILON) - return "The Wipe Tower is currently only supported for the 0.4mm nozzle diameter."; - #endif if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfMarlin) - return "The Wipe Tower is currently only supported for the Marlin and RepRap/Sprinter G-code flavors."; + 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 "The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."; + 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 "The Wipe Tower is only supported for multiple objects if they have equal layer heigths"; + 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 "The Wipe Tower is only supported for multiple objects if they are printed over an equal number of 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 "The Wipe Tower is only supported for multiple objects if they are printed with the same 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 "The Wipe Tower is only supported for multiple objects if they are sliced equally."; + 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; - for (size_t i = 5; i < object->layer_height_profile.size(); i += 2) - if (object->layer_height_profile[i-1] > slicing_params.object_print_z_min + EPSILON && - std::abs(object->layer_height_profile[i] - object->config().layer_height) > EPSILON) - return "The Wipe Tower is currently only supported with constant Z layer spacing. Layer editing is not allowed."; + + 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"); + } } } @@ -628,13 +691,17 @@ std::string Print::validate() const // find the smallest nozzle diameter std::vector<unsigned int> extruders = this->extruders(); if (extruders.empty()) - return "The supplied settings will cause an empty print."; + 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)) { @@ -642,13 +709,13 @@ std::string Print::validate() const // 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 "Printing with multiple extruders of differing nozzle diameters. " + 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."; + "all nozzles have to be of the same diameter."); } // validate first_layer_height - double first_layer_height = object->config().get_abs_value("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 @@ -663,11 +730,11 @@ std::string Print::validate() const first_layer_min_nozzle_diameter = min_nozzle_diameter; } if (first_layer_height > first_layer_min_nozzle_diameter) - return "First layer height can't be greater than 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 "Layer height can't be greater than nozzle diameter"; + return L("Layer height can't be greater than nozzle diameter"); } } @@ -680,9 +747,9 @@ BoundingBox Print::bounding_box() const { BoundingBox bb; for (const PrintObject *object : m_objects) - for (Point copy : object->_shifted_copies) { + for (Point copy : object->m_copies) { bb.merge(copy); - copy.translate(object->size); + copy += to_2d(object->size); bb.merge(copy); } return bb; @@ -897,8 +964,9 @@ void Print::_make_skirt() // 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.; - for (const PrintObject *object : m_objects) { - size_t skirt_layers = this->has_infinite_skirt() ? + 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); @@ -906,8 +974,7 @@ void Print::_make_skirt() // Collect points from all layers contained in skirt height. Points points; - for (const PrintObject *object : m_objects) { - this->throw_if_canceled(); + for (const PrintObject *object : printable_objects) { Points object_points; // Get object layers up to skirt_height_z. for (const Layer *layer : object->m_layers) { @@ -925,10 +992,10 @@ void Print::_make_skirt() append(object_points, extrusion_entity->as_polyline().points); } // Repeat points for each object copy. - for (const Point &shift : object->_shifted_copies) { + for (const Point &shift : object->m_copies) { Points copy_points = object_points; for (Point &pt : copy_points) - pt.translate(shift); + pt += shift; append(points, copy_points); } } @@ -967,7 +1034,9 @@ void Print::_make_skirt() // 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. - coord_t distance = scale_(std::max(m_config.skirt_distance.value, m_config.brim_width.value)); + 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.); @@ -995,7 +1064,7 @@ void Print::_make_skirt() 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(loop.length()) * extruders_e_per_mm[extruder_idx]; + 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) @@ -1020,22 +1089,22 @@ void Print::_make_brim() // Brim is only printed on first layer and uses perimeter extruder. Flow flow = this->brim_flow(); Polygons islands; - for (PrintObject *object : m_objects) { - this->throw_if_canceled(); + 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->_shifted_copies.size()); - for (const Point &pt : object->_shifted_copies) + 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.width)); + 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); @@ -1066,6 +1135,18 @@ bool Print::has_wipe_tower() const 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()) @@ -1081,7 +1162,7 @@ void Print::_make_wipe_tower() 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 ToolOrdering::LayerTools < = m_wipe_tower_data.tool_ordering.layer_tools()[i]; + const LayerTools < = m_wipe_tower_data.tool_ordering.layer_tools()[i]; if (lt.has_wipe_tower && ! lt.has_object && ! lt.has_support) { idx_begin = i; break; @@ -1095,7 +1176,7 @@ void Print::_make_wipe_tower() 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) { - ToolOrdering::LayerTools < = const_cast<ToolOrdering::LayerTools&>(m_wipe_tower_data.tool_ordering.layer_tools()[i]); + LayerTools < = 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; @@ -1112,78 +1193,73 @@ void Print::_make_wipe_tower() // 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_per_color_wipe.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 < 4; ++ i) + 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)); - - // When printing the first layer's wipe tower, the first extruder is expected to be active and primed. - // Therefore the number of wipe sections at the wipe tower will be (m_wipe_tower_data.tool_ordering.front().extruders-1) at the 1st layer. - // The following variable is true if the last priming section cannot be squeezed inside the wipe tower. - bool last_priming_wipe_full = m_wipe_tower_data.tool_ordering.front().extruders.size() > m_wipe_tower_data.tool_ordering.front().wipe_tower_partitions; + 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(), ! last_priming_wipe_full, WipeTower::PURPOSE_EXTRUDE)); + wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); - // Generate the wipe tower layers. - m_wipe_tower_data.tool_changes.reserve(m_wipe_tower_data.tool_ordering.layer_tools().size()); - // Set current_extruder_id to the last extruder primed. - unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.all_extruders().back(); - for (const ToolOrdering::LayerTools &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { - this->throw_if_canceled(); - if (! layer_tools.has_wipe_tower) - // This is a support only layer, or the wipe tower does not reach to this height. - continue; - bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); - bool last_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.back() || (&layer_tools + 1)->wipe_tower_partitions == 0; - wipe_tower.set_layer( - float(layer_tools.print_z), - float(layer_tools.wipe_tower_layer_height), - layer_tools.wipe_tower_partitions, - first_layer, - last_layer); - std::vector<WipeTower::ToolChangeResult> tool_changes; - for (unsigned int extruder_id : layer_tools.extruders) - // Call the wipe_tower.tool_change() at the first layer for the initial extruder - // to extrude the wipe tower brim, - if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || - // or when an extruder shall be switched. - extruder_id != current_extruder_id) { - tool_changes.emplace_back(wipe_tower.tool_change(extruder_id, extruder_id == layer_tools.extruders.back(), WipeTower::PURPOSE_EXTRUDE)); - current_extruder_id = extruder_id; - } - if (! wipe_tower.layer_finished()) { - tool_changes.emplace_back(wipe_tower.finish_layer(WipeTower::PURPOSE_EXTRUDE)); - if (tool_changes.size() > 1) { - // Merge the two last tool changes into one. - WipeTower::ToolChangeResult &tc1 = tool_changes[tool_changes.size() - 2]; - WipeTower::ToolChangeResult &tc2 = tool_changes.back(); - if (tc1.end_pos != tc2.start_pos) { - // Add a travel move from tc1.end_pos to tc2.start_pos. - char buf[2048]; - sprintf(buf, "G1 X%.3f Y%.3f F7200\n", tc2.start_pos.x, tc2.start_pos.y); - tc1.gcode += buf; + // 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; } - tc1.gcode += tc2.gcode; - append(tc1.extrusions, tc2.extrusions); - tc1.end_pos = tc2.end_pos; - tool_changes.pop_back(); } + 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; } - m_wipe_tower_data.tool_changes.emplace_back(std::move(tool_changes)); - if (last_layer) - 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) { @@ -1201,7 +1277,7 @@ void Print::_make_wipe_tower() 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, WipeTower::PURPOSE_EXTRUDE)); + wipe_tower.tool_change((unsigned int)-1, false)); } std::string Print::output_filename() const @@ -1211,7 +1287,7 @@ std::string Print::output_filename() const try { return this->placeholder_parser().process(m_config.output_filename_format.value, 0, &cfg_timestamp); } catch (std::runtime_error &err) { - throw std::runtime_error(std::string("Failed processing of the output_filename_format template.\n") + err.what()); + throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what()); } } @@ -1238,4 +1314,24 @@ std::string Print::output_filepath(const std::string &path) const return path; } +void Print::print_to_png(const std::string &dirpath) +{ + 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)); +} + +// Returns extruder this eec should be printed with, according to PrintRegion config +int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion) +{ + 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/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 71711b31e..d72c1fef5 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -158,31 +158,23 @@ class PrintObject 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; + 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; + 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; + 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; + bool typed_slices; - Point3 size; // XYZ in scaled coordinates - - // 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 _copies_shift; - - // Slic3r::Point objects in scaled G-code coordinates in our coordinates - Points _shifted_copies; + Vec3crd size; // XYZ in scaled coordinates Print* print() { return m_print; } const Print* print() const { return m_print; } @@ -195,13 +187,13 @@ public: const SupportLayerPtrs& support_layers() const { return m_support_layers; } const Points& copies() const { return m_copies; } - bool add_copy(const Pointf &point); + 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), this->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) { @@ -244,6 +236,8 @@ public: 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 @@ -262,6 +256,7 @@ private: 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(); @@ -272,11 +267,17 @@ private: 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; @@ -308,18 +309,23 @@ struct WipeTowerData std::vector<std::vector<WipeTower::ToolChangeResult>> tool_changes; std::unique_ptr<WipeTower::ToolChangeResult> final_purge; + // 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); + depth = 0.f; } }; struct PrintStatistics { PrintStatistics() { clear(); } - std::string estimated_print_time; + std::string estimated_normal_print_time; + std::string estimated_silent_print_time; double total_used_filament; double total_extruded_volume; double total_cost; @@ -327,11 +333,12 @@ struct PrintStatistics std::map<size_t, float> filament_stats; void clear() { - estimated_print_time.clear(); + estimated_normal_print_time.clear(); + estimated_silent_print_time.clear(); total_used_filament = 0.; total_extruded_volume = 0.; - total_weight = 0.; total_cost = 0.; + total_weight = 0.; filament_stats.clear(); } }; @@ -345,7 +352,7 @@ 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. @@ -359,6 +366,8 @@ public: bool apply_config(DynamicPrintConfig config); void process(); void export_gcode(const std::string &path_template, GCodePreviewData *preview_data); + // SLA export, temporary. + void print_to_png(const std::string &dirpath); // methods for handling state bool is_step_done(PrintStep step) const { return m_state.is_done(step); } @@ -366,6 +375,9 @@ public: 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; @@ -386,9 +398,13 @@ public: 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 ®ion); + const ExtrusionEntityCollection& skirt() const { return m_skirt; } const ExtrusionEntityCollection& brim() const { return m_brim; } @@ -458,6 +474,7 @@ private: // The mutex will be used to guard the worker thread against entering a stage // while the data influencing the stage is modified. 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. @@ -489,6 +506,7 @@ private: 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) diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 298fe9207..a3e84a356 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1,7 +1,10 @@ #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> @@ -11,12 +14,54 @@ namespace Slic3r { //! macro used to mark string used at localization, //! return same string -#define L(s) s +#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. @@ -30,10 +75,6 @@ PrintConfigDef::PrintConfigDef() def->cli = "avoid-crossing-perimeters!"; def->default_value = new ConfigOptionBool(false); - def = this->add("bed_shape", coPoints); - def->label = L("Bed shape"); - def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) }; - def = this->add("bed_temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Bed temperature for layers after the first one. " @@ -111,8 +152,8 @@ PrintConfigDef::PrintConfigDef() "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->max = 2; + def->default_value = new ConfigOptionFloat(1); def = this->add("bridge_speed", coFloat); def->label = L("Bridges"); @@ -151,6 +192,11 @@ PrintConfigDef::PrintConfigDef() "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 " @@ -167,6 +213,22 @@ PrintConfigDef::PrintConfigDef() 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 " @@ -177,6 +239,18 @@ PrintConfigDef::PrintConfigDef() 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 " @@ -255,11 +329,11 @@ PrintConfigDef::PrintConfigDef() def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); - def->enum_labels.push_back("Rectilinear"); - def->enum_labels.push_back("Concentric"); - def->enum_labels.push_back("Hilbert Curve"); - def->enum_labels.push_back("Archimedean Chords"); - def->enum_labels.push_back("Octagram Spiral"); + 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.push_back("solid_fill_pattern"); def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear); @@ -316,6 +390,7 @@ PrintConfigDef::PrintConfigDef() 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"); @@ -355,7 +430,7 @@ PrintConfigDef::PrintConfigDef() "from the XY coordinate)."); def->sidetext = L("mm"); def->cli = "extruder-offset=s@"; - def->default_value = new ConfigOptionPoints { Pointf(0,0) }; + def->default_value = new ConfigOptionPoints { Vec2d(0,0) }; def = this->add("extrusion_axis", coString); def->label = L("Extrusion axis"); @@ -407,7 +482,7 @@ PrintConfigDef::PrintConfigDef() 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->default_value = new ConfigOptionStrings { "#29B2B2" }; def = this->add("filament_notes", coStrings); def->label = L("Filament notes"); @@ -428,6 +503,108 @@ PrintConfigDef::PrintConfigDef() 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 " @@ -449,10 +626,7 @@ PrintConfigDef::PrintConfigDef() def = this->add("filament_type", coStrings); def->label = L("Filament type"); - 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->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"; @@ -555,19 +729,19 @@ PrintConfigDef::PrintConfigDef() def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); - def->enum_labels.push_back("Rectilinear"); - def->enum_labels.push_back("Grid"); - def->enum_labels.push_back("Triangles"); - def->enum_labels.push_back("Stars"); - def->enum_labels.push_back("Cubic"); - def->enum_labels.push_back("Line"); - def->enum_labels.push_back("Concentric"); - def->enum_labels.push_back("Honeycomb"); - def->enum_labels.push_back("3D Honeycomb"); - def->enum_labels.push_back("Gyroid"); - def->enum_labels.push_back("Hilbert Curve"); - def->enum_labels.push_back("Archimedean Chords"); - def->enum_labels.push_back("Octagram Spiral"); + 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); @@ -675,8 +849,8 @@ PrintConfigDef::PrintConfigDef() def->enum_labels.push_back("Mach3/LinuxCNC"); def->enum_labels.push_back("Machinekit"); def->enum_labels.push_back("Smoothie"); - def->enum_labels.push_back("No extrusion"); - def->default_value = new ConfigOptionEnum<GCodeFlavor>(gcfMarlin); + 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"); @@ -754,6 +928,18 @@ PrintConfigDef::PrintConfigDef() 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. " @@ -774,15 +960,105 @@ PrintConfigDef::PrintConfigDef() def->height = 50; def->default_value = new ConfigOptionString(""); - 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 = 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->default_value = new ConfigOptionFloat(0.3); + 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"); @@ -804,13 +1080,6 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloats { 0. }; - 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); - 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 " @@ -879,7 +1148,7 @@ PrintConfigDef::PrintConfigDef() def->default_value = new ConfigOptionFloats { 10. }; def = this->add("min_skirt_length", coFloat); - def->label = L("Minimum extrusion length"); + 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."); @@ -905,25 +1174,37 @@ PrintConfigDef::PrintConfigDef() def->cli = "nozzle-diameter=f@"; def->default_value = new ConfigOptionFloats { 0.5 }; - def = this->add("octoprint_apikey", coString); - def->label = L("API Key"); - def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain " - "the API Key required for authentication."); - def->cli = "octoprint-apikey=s"; + 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("octoprint_cafile", coString); + 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 = "octoprint-cafile=s"; + def->cli = "printhost-cafile=s"; def->default_value = new ConfigOptionString(""); - def = this->add("octoprint_host", coString); + def = this->add("print_host", coString); def->label = L("Hostname, IP or URL"); - def->tooltip = L("Slic3r can upload G-code files to OctoPrint. This field should contain " - "the hostname, IP address or URL of the OctoPrint instance."); - def->cli = "octoprint-host=s"; + 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); @@ -959,6 +1240,24 @@ PrintConfigDef::PrintConfigDef() 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. " @@ -1023,7 +1322,12 @@ PrintConfigDef::PrintConfigDef() def->multiline = true; def->full_width = true; def->height = 60; - def->default_value = new ConfigOptionStrings{ "" }; + 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"); @@ -1034,6 +1338,16 @@ PrintConfigDef::PrintConfigDef() 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(""); @@ -1172,10 +1486,10 @@ PrintConfigDef::PrintConfigDef() def->enum_values.push_back("nearest"); def->enum_values.push_back("aligned"); def->enum_values.push_back("rear"); - def->enum_labels.push_back("Random"); - def->enum_labels.push_back("Nearest"); - def->enum_labels.push_back("Aligned"); - def->enum_labels.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 @@ -1388,7 +1702,13 @@ PrintConfigDef::PrintConfigDef() 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->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"); @@ -1438,8 +1758,8 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->enum_values.push_back("0"); def->enum_values.push_back("0.2"); - def->enum_labels.push_back("0 (soluble)"); - def->enum_labels.push_back("0.2 (detachable)"); + 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); @@ -1528,9 +1848,9 @@ PrintConfigDef::PrintConfigDef() def->enum_values.push_back("rectilinear"); def->enum_values.push_back("rectilinear-grid"); def->enum_values.push_back("honeycomb"); - def->enum_labels.push_back("rectilinear"); - def->enum_labels.push_back("rectilinear grid"); - def->enum_labels.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); @@ -1587,7 +1907,7 @@ PrintConfigDef::PrintConfigDef() "temperature control commands in the output."); def->cli = "temperature=i@"; def->full_label = L("Temperature"); - def->max = 0; + def->min = 0; def->max = max_temp; def->default_value = new ConfigOptionInts { 200 }; @@ -1711,6 +2031,25 @@ PrintConfigDef::PrintConfigDef() 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"); @@ -1732,14 +2071,37 @@ PrintConfigDef::PrintConfigDef() def->cli = "wipe-tower-width=f"; def->default_value = new ConfigOptionFloat(60.); - def = this->add("wipe_tower_per_color_wipe", coFloat); - def->label = L("Per color change depth"); - def->tooltip = L("Depth of a wipe color per color change. For N colors, there will be " - "maximum (N-1) tool switches performed, therefore the total depth " - "of the wipe tower will be (N-1) times this value."); + 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-per-color-wipe=f"; - def->default_value = new ConfigOptionFloat(15.); + 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"); @@ -1760,6 +2122,149 @@ PrintConfigDef::PrintConfigDef() 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) @@ -1792,10 +2297,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va ConfigOptionPoint p; p.deserialize(value); std::ostringstream oss; - oss << "0x0," << p.value.x << "x0," << p.value.x << "x" << p.value.y << ",0x" << p.value.y; + oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1); value = oss.str(); - } else if (opt_key == "octoprint_host" && !value.empty()) { - opt_key = "print_host"; } 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; @@ -1806,10 +2309,12 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } 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 == "support_material_threshold" && value == "0") { - // 0 used to be automatic threshold, but we introduced percent values so let's - // transform it into the default value - value = "60%"; + } 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: @@ -1818,16 +2323,16 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va "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", "octoprint_host", - "print_center", "g0", "threads", "pressure_advance" + "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)) { - //printf("Unknown option %s\n", opt_key.c_str()); opt_key = ""; return; } @@ -2079,9 +2584,14 @@ std::string FullPrintConfig::validate() // 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/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index cb94a7921..5bc99a51a 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -22,11 +22,23 @@ 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, @@ -44,7 +56,16 @@ enum FilamentType { ftPLA, ftABS, ftPET, ftHIPS, ftFLEX, ftSCAFF, ftEDGE, ftNGEN, ftPVA }; -template<> inline t_config_enum_values& ConfigOptionEnum<GCodeFlavor>::get_enum_values() { +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; @@ -61,7 +82,16 @@ template<> inline t_config_enum_values& ConfigOptionEnum<GCodeFlavor>::get_enum_ return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum<InfillPattern>::get_enum_values() { +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; @@ -81,7 +111,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum<InfillPattern>::get_enu return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum<SupportMaterialPattern>::get_enum_values() { +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; @@ -91,7 +121,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum<SupportMaterialPattern> return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum<SeamPosition>::get_enum_values() { +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; @@ -102,7 +132,7 @@ template<> inline t_config_enum_values& ConfigOptionEnum<SeamPosition>::get_enum return keys_map; } -template<> inline t_config_enum_values& ConfigOptionEnum<FilamentType>::get_enum_values() { +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; @@ -126,6 +156,11 @@ 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. @@ -154,6 +189,13 @@ public: // 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> @@ -329,7 +371,8 @@ public: 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) { @@ -365,6 +408,7 @@ protected: OPT_PTR(support_material_threshold); OPT_PTR(support_material_with_sheath); OPT_PTR(xy_size_compensation); + OPT_PTR(wipe_into_objects); } }; @@ -407,7 +451,8 @@ public: 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) { @@ -445,6 +490,57 @@ protected: 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); } }; @@ -466,6 +562,18 @@ public: 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; @@ -485,13 +593,20 @@ public: 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 @@ -515,6 +630,18 @@ protected: 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); @@ -532,6 +659,7 @@ protected: 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); @@ -540,11 +668,17 @@ protected: 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 GCodeConfig +class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig { STATIC_PRINT_CONFIG_CACHE_DERIVED(PrintConfig) PrintConfig() : GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } @@ -592,6 +726,7 @@ public: ConfigOptionString output_filename_format; ConfigOptionFloat perimeter_acceleration; ConfigOptionStrings post_process; + ConfigOptionString printer_model; ConfigOptionString printer_notes; ConfigOptionFloat resolution; ConfigOptionFloats retract_before_travel; @@ -610,12 +745,23 @@ public: 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); @@ -657,6 +803,7 @@ protected: 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); @@ -675,7 +822,17 @@ protected: 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); } }; @@ -683,18 +840,20 @@ class HostConfig : public StaticPrintConfig { STATIC_PRINT_CONFIG_CACHE(HostConfig) public: - ConfigOptionString octoprint_host; - ConfigOptionString octoprint_apikey; - ConfigOptionString octoprint_cafile; + 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(octoprint_host); - OPT_PTR(octoprint_apikey); - OPT_PTR(octoprint_cafile); + OPT_PTR(host_type); + OPT_PTR(print_host); + OPT_PTR(printhost_apikey); + OPT_PTR(printhost_cafile); OPT_PTR(serial_port); OPT_PTR(serial_speed); } @@ -713,6 +872,7 @@ class FullPrintConfig : 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) {} @@ -725,6 +885,73 @@ protected: } }; +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 diff --git a/xs/src/libslic3r/PrintExport.hpp b/xs/src/libslic3r/PrintExport.hpp new file mode 100644 index 000000000..7c3871251 --- /dev/null +++ b/xs/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/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index ba2521cd7..96c831157 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -4,6 +4,7 @@ #include "Geometry.hpp" #include "SupportMaterial.hpp" #include "Surface.hpp" +#include "Slicing.hpp" #include <utility> #include <boost/log/trivial.hpp> @@ -37,6 +38,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding 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 @@ -47,10 +49,9 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding // 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). - this->_copies_shift = Point::new_scale(modobj_bbox.min.x, modobj_bbox.min.y); + m_copies_shift = Point::new_scale(modobj_bbox.min(0), modobj_bbox.min(1)); // Scale the object size and store it - Pointf3 size = modobj_bbox.size(); - this->size = Point3::new_scale(size.x, size.y, size.z); + this->size = (modobj_bbox.size() * (1. / SCALING_FACTOR)).cast<coord_t>(); } this->reload_model_instances(); @@ -58,10 +59,10 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, const Bounding this->layer_height_profile = model_object->layer_height_profile; } -bool PrintObject::add_copy(const Pointf &point) +bool PrintObject::add_copy(const Vec2d &point) { Points points = m_copies; - points.push_back(Point::new_scale(point.x, point.y)); + points.push_back(Point::new_scale(point(0), point(1))); return this->set_copies(points); } @@ -74,24 +75,23 @@ bool PrintObject::delete_last_copy() bool PrintObject::set_copies(const Points &points) { - m_copies = points; + bool copies_num_changed = m_copies.size() != points.size(); // order copies with a nearest neighbor search and translate them by _copies_shift - this->_shifted_copies.clear(); - this->_shifted_copies.reserve(points.size()); + 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) { - Point copy = points[point_idx]; - copy.translate(this->_copies_shift); - this->_shifted_copies.push_back(copy); - } + 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; } @@ -100,7 +100,8 @@ bool PrintObject::reload_model_instances() Points copies; copies.reserve(m_model_object->instances.size()); for (const ModelInstance *mi : m_model_object->instances) - copies.emplace_back(Point::new_scale(mi->offset.x, mi->offset.y)); + if (mi->is_printable()) + copies.emplace_back(Point::new_scale(mi->offset(0), mi->offset(1))); return this->set_copies(copies); } @@ -115,7 +116,7 @@ bool PrintObject::reload_model_instances() // this should be idempotent void PrintObject::slice() { - if (m_state.is_done(posSlice)) + if (this->is_step_done(posSlice)) return; this->set_started(posSlice); m_print->set_status(10, "Processing triangulated mesh"); @@ -143,7 +144,7 @@ void PrintObject::make_perimeters() // prerequisites this->slice(); - if (m_state.is_done(posPerimeters)) + if (this->is_step_done(posPerimeters)) return; this->set_started(posPerimeters); @@ -168,12 +169,8 @@ void PrintObject::make_perimeters() // 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 ®ion = *m_print->regions()[region_id]; - - if (!region.config().extra_perimeters - || region.config().perimeters == 0 - || region.config().fill_density == 0 - || this->layer_count() < 2) + const PrintRegion ®ion = *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"; @@ -259,7 +256,7 @@ void PrintObject::make_perimeters() void PrintObject::prepare_infill() { - if (m_state.is_done(posPrepareInfill)) + if (this->is_step_done(posPrepareInfill)) return; this->set_started(posPrepareInfill); @@ -375,10 +372,13 @@ void PrintObject::prepare_infill() void PrintObject::infill() { + if (! this->is_printable()) + return; + // prerequisites this->prepare_infill(); - if (! m_state.is_done(posInfill)) { + if (! this->is_step_done(posInfill)) { this->set_started(posInfill); BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( @@ -401,7 +401,7 @@ void PrintObject::infill() void PrintObject::generate_support_material() { - if (! m_state.is_done(posSupportMaterial)) { + 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) { @@ -545,7 +545,10 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_ || opt_key == "perimeter_speed" || opt_key == "small_perimeter_speed" || opt_key == "solid_infill_speed" - || opt_key == "top_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 @@ -585,6 +588,8 @@ bool PrintObject::invalidate_step(PrintObjectStep step) } // 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; } @@ -1341,7 +1346,7 @@ SlicingParameters PrintObject::slicing_parameters() const { return SlicingParameters::create_from_config( this->print()->config(), m_config, - unscale(this->size.z), this->print()->object_extruders()); + unscale<double>(this->size(2)), this->print()->object_extruders()); } bool PrintObject::update_layer_height_profile(std::vector<coordf_t> &layer_height_profile) const @@ -1402,8 +1407,8 @@ void PrintObject::_slice() this->typed_slices = false; -#if 0 - // Disable parallelization for debugging purposes. +#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 @@ -1461,7 +1466,7 @@ void PrintObject::_slice() if (region_id == other_region_id) continue; for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) { - Layer *layer = this->layers()[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) @@ -1563,7 +1568,7 @@ std::vector<ExPolygons> PrintObject::_slice_region(size_t region_id, const std:: // 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(- float(unscale(this->_copies_shift.x)), - float(unscale(this->_copies_shift.y)), -float(this->model_object()->bounding_box().min.z)); + 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; Print *print = this->print(); @@ -1680,6 +1685,113 @@ void PrintObject::_simplify_slices(double 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 ®ion = *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, ®ion, 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. @@ -2069,6 +2181,9 @@ void PrintObject::combine_infill() void PrintObject::_generate_support_material() { + if (!this->is_printable()) + return; + PrintObjectSupportMaterial support_material(this, PrintObject::slicing_parameters()); support_material.generate(*this); } @@ -2083,4 +2198,12 @@ void PrintObject::reset_layer_height_profile() 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/xs/src/libslic3r/Rasterizer/Rasterizer.cpp b/xs/src/libslic3r/Rasterizer/Rasterizer.cpp new file mode 100644 index 000000000..b0bf04343 --- /dev/null +++ b/xs/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/xs/src/libslic3r/Rasterizer/Rasterizer.hpp b/xs/src/libslic3r/Rasterizer/Rasterizer.hpp new file mode 100644 index 000000000..cbb39bc6b --- /dev/null +++ b/xs/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/xs/src/libslic3r/SLABasePool.cpp b/xs/src/libslic3r/SLABasePool.cpp new file mode 100644 index 000000000..f3683865c --- /dev/null +++ b/xs/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/xs/src/libslic3r/SLABasePool.hpp b/xs/src/libslic3r/SLABasePool.hpp new file mode 100644 index 000000000..55c94df07 --- /dev/null +++ b/xs/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/xs/src/libslic3r/SVG.cpp b/xs/src/libslic3r/SVG.cpp index c94db8e74..03f55802e 100644 --- a/xs/src/libslic3r/SVG.cpp +++ b/xs/src/libslic3r/SVG.cpp @@ -3,7 +3,7 @@ #include <boost/nowide/cstdio.hpp> -#define COORD(x) ((float)unscale((x))*10) +#define COORD(x) (unscale<float>((x))*10) namespace Slic3r { @@ -32,8 +32,8 @@ bool SVG::open(const char* afilename, const BoundingBox &bbox, const coord_t bbo this->f = boost::nowide::fopen(afilename, "w"); if (f == NULL) return false; - float w = COORD(bbox.max.x - bbox.min.x + 2 * bbox_offset); - float h = COORD(bbox.max.y - bbox.min.y + 2 * bbox_offset); + 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" @@ -50,7 +50,7 @@ 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.x - origin.x), COORD(line.a.y - origin.y), COORD(line.b.x - origin.x), COORD(line.b.y - origin.y), stroke.c_str(), (stroke_width == 0) ? 1.f : COORD(stroke_width)); + 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"); @@ -58,21 +58,21 @@ SVG::draw(const Line &line, std::string stroke, coordf_t stroke_width) void SVG::draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coordf_t stroke_width) { - Pointf dir(line.b.x-line.a.x, line.b.y-line.a.y); - Pointf perp(-dir.y, dir.x); - coordf_t len = sqrt(perp.x*perp.x + perp.y*perp.y); + 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.x-da*perp.x-origin.x), - COORD(line.a.y-da*perp.y-origin.y), - COORD(line.b.x-db*perp.x-origin.x), - COORD(line.b.y-db*perp.y-origin.y), - COORD(line.b.x+db*perp.x-origin.x), - COORD(line.b.y+db*perp.y-origin.y), - COORD(line.a.x+da*perp.x-origin.x), - COORD(line.a.y+da*perp.y-origin.y), + 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)); } @@ -220,7 +220,7 @@ 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.x - origin.x) << "\" cy=\"" << COORD(point.y - origin.y) + svg << " <circle cx=\"" << COORD(point(0) - origin(0)) << "\" cy=\"" << COORD(point(1) - origin(1)) << "\" r=\"" << radius << "\" " << "style=\"stroke: none; fill: " << fill << "\" />"; @@ -287,8 +287,8 @@ 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->x - origin.x) << " "; - d << COORD(p->y - origin.y) << " "; + d << COORD((*p)(0) - origin(0)) << " "; + d << COORD((*p)(1) - origin(1)) << " "; } if (closed) d << "z"; return d.str(); @@ -300,8 +300,8 @@ 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.x) << " "; - d << COORD(scale * p->Y - origin.y) << " "; + d << COORD(scale * p->X - origin(0)) << " "; + d << COORD(scale * p->Y - origin(1)) << " "; } if (closed) d << "z"; return d.str(); @@ -311,8 +311,8 @@ 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.x-origin.x), - COORD(pt.y-origin.y), + COORD(pt(0)-origin(0)), + COORD(pt(1)-origin(1)), color, text); } @@ -320,13 +320,13 @@ 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.x-origin.x), - COORD(pt.y-origin.y), + 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.x-origin.x) + 20.f, - COORD(pt.y-origin.y), + COORD(pt(0)-origin(0)) + 20.f, + COORD(pt(1)-origin(1)), "black", text); } diff --git a/xs/src/libslic3r/Slicing.cpp b/xs/src/libslic3r/Slicing.cpp index e9295d1e3..1bc38502b 100644 --- a/xs/src/libslic3r/Slicing.cpp +++ b/xs/src/libslic3r/Slicing.cpp @@ -561,15 +561,15 @@ int generate_layer_height_texture( void *data, int rows, int cols, bool level_of_detail_2nd_level) { // https://github.com/aschn/gnuplot-colorbrewer - std::vector<Point3> palette_raw; - palette_raw.push_back(Point3(0x01A, 0x098, 0x050)); - palette_raw.push_back(Point3(0x066, 0x0BD, 0x063)); - palette_raw.push_back(Point3(0x0A6, 0x0D9, 0x06A)); - palette_raw.push_back(Point3(0x0D9, 0x0F1, 0x0EB)); - palette_raw.push_back(Point3(0x0FE, 0x0E6, 0x0EB)); - palette_raw.push_back(Point3(0x0FD, 0x0AE, 0x061)); - palette_raw.push_back(Point3(0x0F4, 0x06D, 0x043)); - palette_raw.push_back(Point3(0x0D7, 0x030, 0x027)); + 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)); @@ -600,25 +600,25 @@ int generate_layer_height_texture( 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 Point3 &color1 = palette_raw[idx1]; - const Point3 &color2 = palette_raw[idx2]; + 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. - Pointf3 color( - intensity * lerp(coordf_t(color1.x), coordf_t(color2.x), t), - intensity * lerp(coordf_t(color1.y), coordf_t(color2.y), t), - intensity * lerp(coordf_t(color1.z), coordf_t(color2.z), t)); + 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.x + 0.5))); - ptr[1] = (unsigned char)clamp<int>(0, 255, int(floor(color.y + 0.5))); - ptr[2] = (unsigned char)clamp<int>(0, 255, int(floor(color.z + 0.5))); + 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. @@ -636,21 +636,21 @@ int generate_layer_height_texture( 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 Point3 &color1 = palette_raw[idx1]; - const Point3 &color2 = palette_raw[idx2]; + const Vec3crd &color1 = palette_raw[idx1]; + const Vec3crd &color2 = palette_raw[idx2]; // Color mapping from layer height to RGB. - Pointf3 color( - lerp(coordf_t(color1.x), coordf_t(color2.x), t), - lerp(coordf_t(color1.y), coordf_t(color2.y), t), - lerp(coordf_t(color1.z), coordf_t(color2.z), t)); + 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.x + 0.5))); - ptr[1] = (unsigned char)clamp<int>(0, 255, int(floor(color.y + 0.5))); - ptr[2] = (unsigned char)clamp<int>(0, 255, int(floor(color.z + 0.5))); + 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. diff --git a/xs/src/libslic3r/SlicingAdaptive.cpp b/xs/src/libslic3r/SlicingAdaptive.cpp index ff0da7636..2ef4aec8c 100644 --- a/xs/src/libslic3r/SlicingAdaptive.cpp +++ b/xs/src/libslic3r/SlicingAdaptive.cpp @@ -15,8 +15,8 @@ void SlicingAdaptive::clear() std::pair<float, float> face_z_span(const stl_facet *f) { return std::pair<float, float>( - std::min(std::min(f->vertex[0].z, f->vertex[1].z), f->vertex[2].z), - std::max(std::max(f->vertex[0].z, f->vertex[1].z), f->vertex[2].z)); + 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() @@ -40,7 +40,7 @@ void SlicingAdaptive::prepare() // 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.z; + m_face_normal_z[iface] = m_faces[iface]->normal(2); } float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet) diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index d18f9d710..a6b6c1bb8 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -67,9 +67,9 @@ Point export_support_surface_type_legend_to_svg_box_size() void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos) { // 1st row - coord_t pos_x0 = pos.x + scale_(1.); + coord_t pos_x0 = pos(0) + scale_(1.); coord_t pos_x = pos_x0; - coord_t pos_y = pos.y + scale_(1.5); + 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; @@ -82,7 +82,7 @@ void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos) 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.y+scale_(2.8); + 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)); @@ -98,8 +98,8 @@ void export_print_z_polygons_to_svg(const char *path, PrintObjectSupportMaterial 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.x, bbox.max.y); - bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); + 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) @@ -120,8 +120,8 @@ void export_print_z_polygons_and_extrusions_to_svg( 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.x, bbox.max.y); - bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); + 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) @@ -506,8 +506,8 @@ public: for (ExPolygon &island : islands) { BoundingBox bbox = get_extents(island.contour); - auto it_lower = std::lower_bound(m_island_samples.begin(), m_island_samples.end(), bbox.min - Point(1, 1)); - auto it_upper = std::upper_bound(m_island_samples.begin(), m_island_samples.end(), bbox.max + Point(1, 1)); + 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))); samples_inside.clear(); for (auto it = it_lower; it != it_upper; ++ it) if (bbox.contains(*it)) @@ -519,12 +519,12 @@ public: 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.y well. - // Does the ray with y == point.y intersect this line segment? + //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->y > sample_inside.first.y) != (j->y > sample_inside.first.y)) { - double x1 = (double)sample_inside.first.x; - double x2 = (double)i->x + (double)(j->x - i->x) * (double)(sample_inside.first.y - i->y) / (double)(j->y - i->y); + 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; } @@ -585,11 +585,11 @@ private: 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.x)*double(v.x)+double(v.y)*double(v.y); + 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.x + coef * v.x, p2.y + coef * v.y); + return Point(p2(0) + coef * v(0), p2(1) + coef * v(1)); } static Points island_samples(const ExPolygons &expolygons) @@ -789,7 +789,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() for (Polyline &polyline : overhang_perimeters) - polyline.points[0].x += 1; + polyline.points[0](0) += 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); @@ -2057,11 +2057,11 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const 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 Pointf v_seg(coordf_t(p2.x) - coordf_t(p1.x), coordf_t(p2.y) - coordf_t(p1.y)); - const Pointf v_cntr(coordf_t(p1.x - center_last.x), coordf_t(p1.y - center_last.y)); - coordf_t a = dot(v_seg); - coordf_t b = 2. * dot(v_seg, v_cntr); - coordf_t c = dot(v_cntr) - circle_distance * 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. @@ -2081,7 +2081,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const } seg_current_pt = &p1; seg_current_t = t; - center_last = Point(p1.x + coord_t(v_seg.x * t), p1.y + coord_t(v_seg.y * 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); @@ -2100,9 +2100,9 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const circle_centers.push_back(center_last); } external_loops.push_back(std::move(contour)); - for (Points::const_iterator it_center = circle_centers.begin(); it_center != circle_centers.end(); ++ it_center) { + for (const Point ¢er : circle_centers) { circles.push_back(circle); - circles.back().translate(*it_center); + circles.back().translate(center); } } } @@ -2359,7 +2359,7 @@ void modulate_extrusion_by_overlapping_layers( (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); } private: - ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&); + ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) = delete; const std::vector<ExtrusionPathFragment> &m_path_fragments; }; const coord_t search_radius = 7; @@ -2392,7 +2392,7 @@ void modulate_extrusion_by_overlapping_layers( 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.distance_to_sq(pt_current); + //const double d2 = (pt_end - pt_current).squaredNorm(); //assert(d2 < coordf_t(search_radius * search_radius)); // End of the path. break; @@ -2887,9 +2887,9 @@ void PrintObjectSupportMaterial::clip_by_pillars( 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().x / pillar_spacing)) * size_t(ceil(bb.size().y / pillar_spacing))); - for (coord_t x = bb.min.x; x <= bb.max.x - pillar_size; x += pillar_spacing) { - for (coord_t y = bb.min.y; y <= bb.max.y - pillar_size; y += pillar_spacing) { + 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)); diff --git a/xs/src/libslic3r/Surface.cpp b/xs/src/libslic3r/Surface.cpp index 384540d87..0e9eca7fd 100644 --- a/xs/src/libslic3r/Surface.cpp +++ b/xs/src/libslic3r/Surface.cpp @@ -106,9 +106,9 @@ Point export_surface_type_legend_to_svg_box_size() void export_surface_type_legend_to_svg(SVG &svg, const Point &pos) { // 1st row - coord_t pos_x0 = pos.x + scale_(1.); + coord_t pos_x0 = pos(0) + scale_(1.); coord_t pos_x = pos_x0; - coord_t pos_y = pos.y + scale_(1.5); + 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; @@ -121,7 +121,7 @@ void export_surface_type_legend_to_svg(SVG &svg, const Point &pos) 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.y+scale_(2.8); + 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)); diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index 42ddf9574..6db599306 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -170,8 +170,8 @@ void SurfaceCollection::export_to_svg(const char *path, bool show_labels) 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.x, bbox.max.y); - bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); + 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; diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 17b3d98f9..7b8f85b6b 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -1,6 +1,9 @@ #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> @@ -10,11 +13,14 @@ #include <utility> #include <algorithm> #include <math.h> +#include <type_traits> #include <boost/log/trivial.hpp> #include <tbb/parallel_for.h> +#include <Eigen/Dense> + #if 0 #define DEBUG #define _DEBUG @@ -30,13 +36,7 @@ namespace Slic3r { -TriangleMesh::TriangleMesh() - : repaired(false) -{ - stl_initialize(&this->stl); -} - -TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Point3>& facets ) +TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& facets ) : repaired(false) { stl_initialize(&this->stl); @@ -51,47 +51,22 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Point3>& fa for (int i = 0; i < stl.stats.number_of_facets; i++) { stl_facet facet; - facet.normal.x = 0; - facet.normal.y = 0; - facet.normal.z = 0; - - const Pointf3& ref_f1 = points[facets[i].x]; - facet.vertex[0].x = ref_f1.x; - facet.vertex[0].y = ref_f1.y; - facet.vertex[0].z = ref_f1.z; - - const Pointf3& ref_f2 = points[facets[i].y]; - facet.vertex[1].x = ref_f2.x; - facet.vertex[1].y = ref_f2.y; - facet.vertex[1].z = ref_f2.z; - - const Pointf3& ref_f3 = points[facets[i].z]; - facet.vertex[2].x = ref_f3.x; - facet.vertex[2].y = ref_f3.y; - facet.vertex[2].z = ref_f3.z; - + 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(const TriangleMesh &other) : - repaired(false) -{ - stl_initialize(&this->stl); - *this = other; -} - -TriangleMesh::TriangleMesh(TriangleMesh &&other) : - repaired(false) -{ - stl_initialize(&this->stl); - this->swap(other); -} - TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) { stl_close(&this->stl); @@ -119,42 +94,8 @@ TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) return *this; } -TriangleMesh& TriangleMesh::operator=(TriangleMesh &&other) -{ - this->swap(other); - return *this; -} - -void -TriangleMesh::swap(TriangleMesh &other) -{ - std::swap(this->stl, other.stl); - std::swap(this->repaired, other.repaired); -} - -TriangleMesh::~TriangleMesh() { - stl_close(&this->stl); -} - -void -TriangleMesh::ReadSTLFile(const char* input_file) { - stl_open(&stl, input_file); -} - -void -TriangleMesh::write_ascii(const char* output_file) +void TriangleMesh::repair() { - stl_write_ascii(&this->stl, output_file, ""); -} - -void -TriangleMesh::write_binary(const char* output_file) -{ - stl_write_binary(&this->stl, output_file, ""); -} - -void -TriangleMesh::repair() { if (this->repaired) return; // admesh fails when repairing empty meshes @@ -251,13 +192,7 @@ void TriangleMesh::check_topology() } } -bool TriangleMesh::is_manifold() const -{ - return this->stl.stats.connected_facets_3_edge == this->stl.stats.number_of_facets; -} - -void -TriangleMesh::reset_repair_stats() { +void TriangleMesh::reset_repair_stats() { this->stl.stats.degenerate_facets = 0; this->stl.stats.edges_fixed = 0; this->stl.stats.facets_removed = 0; @@ -267,8 +202,7 @@ TriangleMesh::reset_repair_stats() { this->stl.stats.normals_fixed = 0; } -bool -TriangleMesh::needed_repair() const +bool TriangleMesh::needed_repair() const { return this->stl.stats.degenerate_facets > 0 || this->stl.stats.edges_fixed > 0 @@ -278,14 +212,8 @@ TriangleMesh::needed_repair() const || this->stl.stats.backwards_edges > 0; } -size_t -TriangleMesh::facets_count() const +void TriangleMesh::WriteOBJFile(char* output_file) { - return this->stl.stats.number_of_facets; -} - -void -TriangleMesh::WriteOBJFile(char* output_file) { stl_generate_shared_vertices(&stl); stl_write_obj(&stl, output_file); } @@ -296,13 +224,9 @@ void TriangleMesh::scale(float factor) stl_invalidate_shared_vertices(&this->stl); } -void TriangleMesh::scale(const Pointf3 &versor) +void TriangleMesh::scale(const Vec3d &versor) { - float fversor[3]; - fversor[0] = versor.x; - fversor[1] = versor.y; - fversor[2] = versor.z; - stl_scale_versor(&this->stl, fversor); + stl_scale_versor(&this->stl, versor.cast<float>()); stl_invalidate_shared_vertices(&this->stl); } @@ -332,19 +256,15 @@ void TriangleMesh::rotate(float angle, const Axis &axis) stl_invalidate_shared_vertices(&this->stl); } -void TriangleMesh::rotate_x(float angle) +void TriangleMesh::rotate(float angle, const Vec3d& axis) { - this->rotate(angle, X); -} - -void TriangleMesh::rotate_y(float angle) -{ - this->rotate(angle, Y); -} + if (angle == 0.f) + return; -void TriangleMesh::rotate_z(float angle) -{ - this->rotate(angle, Z); + 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) @@ -359,46 +279,27 @@ void TriangleMesh::mirror(const Axis &axis) stl_invalidate_shared_vertices(&this->stl); } -void TriangleMesh::mirror_x() -{ - this->mirror(X); -} - -void TriangleMesh::mirror_y() -{ - this->mirror(Y); -} - -void TriangleMesh::mirror_z() +void TriangleMesh::transform(const Transform3f& t) { - this->mirror(Z); -} - -void TriangleMesh::transform(const float* matrix3x4) -{ - if (matrix3x4 == nullptr) - return; - - stl_transform(&stl, const_cast<float*>(matrix3x4)); - stl_invalidate_shared_vertices(&stl); + stl_transform(&stl, t); } void TriangleMesh::align_to_origin() { this->translate( - -(this->stl.stats.min.x), - -(this->stl.stats.min.y), - -(this->stl.stats.min.z) - ); + - 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; - this->translate(float(-center->x), float(-center->y), 0); + Vec2f c = center->cast<float>(); + this->translate(-c(0), -c(1), 0); stl_rotate_z(&(this->stl), (float)angle); - this->translate(float(+center->x), float(+center->y), 0); + this->translate(c(0), c(1), 0); } bool TriangleMesh::has_multiple_patches() const @@ -471,14 +372,14 @@ size_t TriangleMesh::number_of_patches() const return num_bodies; } -TriangleMeshPtrs -TriangleMesh::split() const +TriangleMeshPtrs TriangleMesh::split() const { - TriangleMeshPtrs meshes; - std::set<int> seen_facets; + TriangleMeshPtrs meshes; + std::vector<unsigned char> facet_visited(this->stl.stats.number_of_facets, false); // we need neighbors - if (!this->repaired) CONFESS("split() requires repair()"); + if (!this->repaired) + CONFESS("split() requires repair()"); // loop while we have remaining facets for (;;) { @@ -486,46 +387,45 @@ TriangleMesh::split() const 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 (seen_facets.find(facet_idx) == seen_facets.end()) { + 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()) { + if (facet_queue.empty()) + break; + + while (! facet_queue.empty()) { int facet_idx = facet_queue.front(); facet_queue.pop(); - if (seen_facets.find(facet_idx) != seen_facets.end()) continue; - facets.push_back(facet_idx); - for (int j = 0; j <= 2; j++) { - facet_queue.push(this->stl.neighbors_start[facet_idx].neighbor[j]); + 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; } - seen_facets.insert(facet_idx); } - + TriangleMesh* mesh = new TriangleMesh; - meshes.push_back(mesh); + 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); - int first = 1; - for (std::deque<int>::const_iterator facet = facets.begin(); facet != facets.end(); ++facet) { + 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); - first = 0; } } return meshes; } -void -TriangleMesh::merge(const TriangleMesh &mesh) +void TriangleMesh::merge(const TriangleMesh &mesh) { // reset stats and metadata int number_of_facets = this->stl.stats.number_of_facets; @@ -556,45 +456,165 @@ ExPolygons TriangleMesh::horizontal_projection() const stl_facet* facet = &this->stl.facet_start[i]; Polygon p; p.points.resize(3); - p.points[0] = Point::new_scale(facet->vertex[0].x, facet->vertex[0].y); - p.points[1] = Point::new_scale(facet->vertex[1].x, facet->vertex[1].y); - p.points[2] = Point::new_scale(facet->vertex[2].x, facet->vertex[2].y); + 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.push_back(p); + 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) { - stl_vertex* v = &this->stl.v_shared[i]; - pp.emplace_back(Point::new_scale(v->x, v->y)); + 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 TriangleMesh::bounding_box() const { BoundingBoxf3 bb; bb.defined = true; - bb.min.x = this->stl.stats.min.x; - bb.min.y = this->stl.stats.min.y; - bb.min.z = this->stl.stats.min.z; - bb.max.x = this->stl.stats.max.x; - bb.max.y = this->stl.stats.max.y; - bb.max.z = this->stl.stats.max.z; + bb.min = this->stl.stats.min.cast<double>(); + bb.max = this->stl.stats.max.cast<double>(); return bb; } -void -TriangleMesh::require_shared_vertices() +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) @@ -614,11 +634,8 @@ void TriangleMeshSlicer::init(TriangleMesh *_mesh, throw_on_cancel_callback_type 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].x /= float(SCALING_FACTOR); - this->v_scaled_shared[i].y /= float(SCALING_FACTOR); - this->v_scaled_shared[i].z /= float(SCALING_FACTOR); - } + 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 { @@ -783,14 +800,14 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLin const stl_facet &facet = this->mesh->stl.facet_start[facet_idx]; // find facet extents - const float min_z = fminf(facet.vertex[0].z, fminf(facet.vertex[1].z, facet.vertex[2].z)); - const float max_z = fmaxf(facet.vertex[0].z, fmaxf(facet.vertex[1].z, facet.vertex[2].z)); + 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_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].z, - facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z, - facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z); + 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 @@ -810,24 +827,24 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLin if (il.edge_type == feHorizontal) { // Insert all three edges of the face. const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; - const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; + const bool reverse = this->mesh->stl.facet_start[facet_idx].normal(2) < 0; for (int j = 0; j < 3; ++ 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.x = a->x; - il.a.y = a->y; - il.b.x = b->x; - il.b.y = b->y; + 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; - (*lines)[layer_idx].push_back(il); + (*lines)[layer_idx].emplace_back(il); } } else - (*lines)[layer_idx].push_back(il); + (*lines)[layer_idx].emplace_back(il); } } } @@ -867,66 +884,63 @@ bool TriangleMeshSlicer::slice_facet( // 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) - int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); + 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)]; const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; 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]; + 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->z == slice_z && b->z == slice_z) { + 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; if (min_z == max_z) { // All three vertices are aligned with slice_z. line_out->edge_type = feHorizontal; - if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) { + if (this->mesh->stl.facet_start[facet_idx].normal(2) < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. - std::swap(a, b); - std::swap(a_id, b_id); + swap = true; } - } else if (v0.z < slice_z || v1.z < slice_z || v2.z < slice_z) { + } else if (v0(2) < slice_z || v1(2) < slice_z || v2(2) < slice_z) { // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. line_out->edge_type = feTop; - std::swap(a, b); - std::swap(a_id, b_id); + swap = true; } else { // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. line_out->edge_type = feBottom; } - line_out->a.x = a->x; - line_out->a.y = a->y; - line_out->b.x = b->x; - line_out->b.y = b->y; - line_out->a_id = a_id; - line_out->b_id = b_id; + 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; return true; } - if (a->z == slice_z) { + if (a(2) == slice_z) { // Only point a alings with the cutting plane. points_on_layer[num_points_on_layer ++] = num_points; IntersectionPoint &point = points[num_points ++]; - point.x = a->x; - point.y = a->y; + point(0) = a(0); + point(1) = a(1); point.point_id = a_id; - } else if (b->z == slice_z) { + } else if (b(2) == slice_z) { // Only point b alings with the cutting plane. points_on_layer[num_points_on_layer ++] = num_points; IntersectionPoint &point = points[num_points ++]; - point.x = b->x; - point.y = b->y; + point(0) = b(0); + point(1) = b(1); point.point_id = b_id; - } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { + } 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. IntersectionPoint &point = points[num_points ++]; - point.x = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z); - point.y = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z); + point(0) = b(0) + (a(0) - b(0)) * (slice_z - b(2)) / (a(2) - b(2)); + point(1) = b(1) + (a(1) - b(1)) * (slice_z - b(2)) / (a(2) - b(2)); point.edge_id = edge_id; } } @@ -1197,8 +1211,8 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo 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); + /*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) { @@ -1207,7 +1221,7 @@ void TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygo // 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].x + opl.points[i].x) * double(opl.points[i].y - opl.points[j].y); + 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)); @@ -1234,9 +1248,9 @@ void TriangleMeshSlicer::make_expolygons_simple(std::vector<IntersectionLine> &l if (loop->area() >= 0.) { ExPolygon ex; ex.contour = *loop; - slices->push_back(ex); + slices->emplace_back(ex); } else { - holes.push_back(*loop); + holes.emplace_back(*loop); } } @@ -1327,8 +1341,8 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic //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.push_back(loop->area()); - // sorted_area.push_back(loop - loops.begin()); + // area.emplace_back(loop->area()); + // sorted_area.emplace_back(loop - loops.begin()); //} // //// outer first @@ -1343,7 +1357,7 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic // would do the same, thus repeating the calculation */ // Polygons::const_iterator loop = loops.begin() + *loop_idx; // if (area[*loop_idx] > +EPSILON) - // p_slices.push_back(*loop); + // 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. @@ -1393,20 +1407,20 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; // find facet extents - float min_z = std::min(facet->vertex[0].z, std::min(facet->vertex[1].z, facet->vertex[2].z)); - float max_z = std::max(facet->vertex[0].z, std::max(facet->vertex[1].z, facet->vertex[2].z)); + 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)) { // Save intersection lines for generating correct triangulations. if (line.edge_type == feTop) { - lower_lines.push_back(line); + lower_lines.emplace_back(line); } else if (line.edge_type == feBottom) { - upper_lines.push_back(line); + upper_lines.emplace_back(line); } else if (line.edge_type != feHorizontal) { - lower_lines.push_back(line); - upper_lines.push_back(line); + lower_lines.emplace_back(line); + upper_lines.emplace_back(line); } } @@ -1421,47 +1435,47 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // look for the vertex on whose side of the slicing plane there are no other vertices int isolated_vertex; - if ( (facet->vertex[0].z > z) == (facet->vertex[1].z > z) ) { + if ( (facet->vertex[0](2) > z) == (facet->vertex[1](2) > z) ) { isolated_vertex = 2; - } else if ( (facet->vertex[1].z > z) == (facet->vertex[2].z > z) ) { + } 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 - stl_vertex* v0 = &facet->vertex[isolated_vertex]; - stl_vertex* v1 = &facet->vertex[(isolated_vertex+1) % 3]; - stl_vertex* v2 = &facet->vertex[(isolated_vertex+2) % 3]; + 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.x = v1->x + (v0->x - v1->x) * (z - v1->z) / (v0->z - v1->z); - v0v1.y = v1->y + (v0->y - v1->y) * (z - v1->z) / (v0->z - v1->z); - v0v1.z = z; - v2v0.x = v2->x + (v0->x - v2->x) * (z - v2->z) / (v0->z - v2->z); - v2v0.y = v2->y + (v0->y - v2->y) * (z - v2->z) / (v0->z - v2->z); - v2v0.z = z; + 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[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[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[0] = v2; quadrilateral[1].vertex[1] = v2v0; quadrilateral[1].vertex[2] = v0v1; - if (v0->z > z) { + if (v0(2) > z) { if (upper != NULL) stl_add_facet(&upper->stl, &triangle); if (lower != NULL) { stl_add_facet(&lower->stl, &quadrilateral[0]); @@ -1493,13 +1507,11 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) Polygon p = *polygon; p.reverse(); stl_facet facet; - facet.normal.x = 0; - facet.normal.y = 0; - facet.normal.z = -1; + facet.normal = stl_normal(0, 0, -1.f); for (size_t i = 0; i <= 2; ++i) { - facet.vertex[i].x = unscale(p.points[i].x); - facet.vertex[i].y = unscale(p.points[i].y); - facet.vertex[i].z = z; + 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); } @@ -1519,13 +1531,11 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // 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.x = 0; - facet.normal.y = 0; - facet.normal.z = 1; + facet.normal = stl_normal(0, 0, 1.f); for (size_t i = 0; i <= 2; ++i) { - facet.vertex[i].x = unscale(polygon->points[i].x); - facet.vertex[i].y = unscale(polygon->points[i].y); - facet.vertex[i].z = z; + 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); } @@ -1538,19 +1548,19 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // Generate the vertex list for a cube solid of arbitrary size in X/Y/Z. TriangleMesh make_cube(double x, double y, double z) { - Pointf3 pv[8] = { - Pointf3(x, y, 0), Pointf3(x, 0, 0), Pointf3(0, 0, 0), - Pointf3(0, y, 0), Pointf3(x, y, z), Pointf3(0, y, z), - Pointf3(0, 0, z), Pointf3(x, 0, 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) }; - Point3 fv[12] = { - Point3(0, 1, 2), Point3(0, 2, 3), Point3(4, 5, 6), - Point3(4, 6, 7), Point3(0, 4, 7), Point3(0, 7, 1), - Point3(1, 7, 6), Point3(1, 6, 2), Point3(2, 6, 5), - Point3(2, 5, 3), Point3(4, 0, 3), Point3(4, 3, 5) + 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<Point3> facets(&fv[0], &fv[0]+12); + std::vector<Vec3crd> facets(&fv[0], &fv[0]+12); Pointf3s vertices(&pv[0], &pv[0]+8); TriangleMesh mesh(vertices ,facets); @@ -1562,11 +1572,11 @@ TriangleMesh make_cube(double x, double y, double z) { // Default is 360 sides, angle fa is in radians. TriangleMesh make_cylinder(double r, double h, double fa) { Pointf3s vertices; - std::vector<Point3> facets; + std::vector<Vec3crd> facets; // 2 special vertices, top and bottom center, rest are relative to this - vertices.push_back(Pointf3(0.0, 0.0, 0.0)); - vertices.push_back(Pointf3(0.0, 0.0, h)); + 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)); @@ -1576,26 +1586,23 @@ TriangleMesh make_cylinder(double r, double h, double fa) { // top and bottom. // Special case: Last line shares 2 vertices with the first line. unsigned id = vertices.size() - 1; - vertices.push_back(Pointf3(sin(0) * r , cos(0) * r, 0)); - vertices.push_back(Pointf3(sin(0) * r , cos(0) * r, h)); + 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) { - Pointf3 b(0, r, 0); - Pointf3 t(0, r, h); - b.rotate(i, Pointf3(0,0,0)); - t.rotate(i, Pointf3(0,0,h)); - vertices.push_back(b); - vertices.push_back(t); + 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.push_back(Point3( 0, id - 1, id - 3)); // top - facets.push_back(Point3(id, 1, id - 2)); // bottom - facets.push_back(Point3(id, id - 2, id - 3)); // upper-right of side - facets.push_back(Point3(id, id - 3, id - 1)); // bottom-left of side + 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.push_back(Point3( 2, 0, id - 1)); - facets.push_back(Point3( 1, 3, id)); - facets.push_back(Point3(id, 3, 2)); - facets.push_back(Point3(id, 2, id - 1)); + 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; @@ -1606,7 +1613,7 @@ TriangleMesh make_cylinder(double r, double h, double fa) { // Default angle is 1 degree. TriangleMesh make_sphere(double rho, double fa) { Pointf3s vertices; - std::vector<Point3> facets; + std::vector<Vec3crd> facets; // Algorithm: // Add points one-by-one to the sphere grid and form facets using relative coordinates. @@ -1618,29 +1625,24 @@ TriangleMesh make_sphere(double rho, double 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.push_back(i); + 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.push_back(Pointf3(0.0, 0.0, -rho)); + 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)); - Pointf3 b(0, r, z); - b.rotate(ring[i], Pointf3(0,0,z)); - vertices.push_back(b); - if (i == 0) { - facets.push_back(Point3(1, 0, ring.size())); - } else { - facets.push_back(Point3(id, 0, id - 1)); - } - id++; + 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. @@ -1649,16 +1651,15 @@ TriangleMesh make_sphere(double rho, double fa) { const double r = sqrt(abs(rho*rho - z*z)); for (size_t i = 0; i < ring.size(); i++) { - Pointf3 b(0, r, z); - b.rotate(ring[i], Pointf3(0,0,z)); - vertices.push_back(b); + 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.push_back(Point3(id + ring.size() - 1 , id, id - 1)); - facets.push_back(Point3(id, id - ring.size(), id - 1)); + facets.emplace_back(Vec3crd(id + ring.size() - 1 , id, id - 1)); + facets.emplace_back(Vec3crd(id, id - ring.size(), id - 1)); } else { - facets.push_back(Point3(id , id - ring.size(), (id - 1) - ring.size())); - facets.push_back(Point3(id, id - 1 - ring.size() , id - 1)); + facets.emplace_back(Vec3crd(id , id - ring.size(), (id - 1) - ring.size())); + facets.emplace_back(Vec3crd(id, id - 1 - ring.size() , id - 1)); } id++; } @@ -1667,13 +1668,13 @@ TriangleMesh make_sphere(double rho, double fa) { // special case: last ring connects to 0,0,rho*2.0 // only form facets. - vertices.push_back(Pointf3(0.0, 0.0, rho)); + 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.push_back(Point3(id, id - ring.size(), id - 1)); + facets.emplace_back(Vec3crd(id, id - ring.size(), id - 1)); } else { - facets.push_back(Point3(id, id - ring.size() + i, id - ring.size() + (i - 1))); + facets.emplace_back(Vec3crd(id, id - ring.size() + i, id - ring.size() + (i - 1))); } } id++; diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index 9a09975cd..ed3e6022d 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -21,44 +21,50 @@ typedef std::vector<TriangleMesh*> TriangleMeshPtrs; class TriangleMesh { public: - TriangleMesh(); - TriangleMesh(const Pointf3s &points, const std::vector<Point3> &facets); - TriangleMesh(const TriangleMesh &other); - TriangleMesh(TriangleMesh &&other); + 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); - void swap(TriangleMesh &other); - ~TriangleMesh(); - void ReadSTLFile(const char* input_file); - void write_ascii(const char* output_file); - void write_binary(const char* output_file); + 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; + 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 Pointf3 &versor); + void scale(const Vec3d &versor); void translate(float x, float y, float z); void rotate(float angle, const Axis &axis); - void rotate_x(float angle); - void rotate_y(float angle); - void rotate_z(float angle); + 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(); - void mirror_y(); - void mirror_z(); - void transform(const float* matrix3x4); + 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; + 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. @@ -67,7 +73,7 @@ public: // Count disconnected triangle patches. size_t number_of_patches() const; - stl_file stl; + mutable stl_file stl; bool repaired; private: diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 27e7fad6b..f1390b8a2 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -3,6 +3,8 @@ #include <locale> +#include "libslic3r.h" + namespace Slic3r { extern void set_logging_level(unsigned int level); @@ -60,8 +62,8 @@ extern std::string timestamp_str(); // 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(); } -// Encode a file into a multi-part HTTP response with a given boundary. -std::string octoprint_encode_file_send_request_content(const char *path, bool select, bool print, const char *boundary); +// 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 @@ -82,6 +84,28 @@ inline T next_highest_power_of_2(T v) return ++ v; } +extern std::string xml_escape(std::string text); + +class PerlCallback { +public: + PerlCallback(void *sv) : m_callback(nullptr) { this->register_callback(sv); } + PerlCallback() : m_callback(nullptr) {} + ~PerlCallback() { this->deregister_callback(); } + void register_callback(void *sv); + void deregister_callback(); + void call() const; + void call(int i) const; + void call(int i, int j) const; + void call(const std::vector<int>& ints) const; + void call(double a) const; + void call(double a, double b) const; + void call(double a, double b, double c) const; + void call(double a, double b, double c, double d) const; + void call(bool b) const; +private: + void *m_callback; +}; + } // namespace Slic3r #endif // slic3r_Utils_hpp_ diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 0f192c37c..925e93031 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include <boost/thread.hpp> #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.39.0" +#define SLIC3R_VERSION "1.41.0" #define SLIC3R_BUILD "UNKNOWN" typedef int32_t coord_t; @@ -45,7 +45,6 @@ typedef double coordf_t; //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 unscale(val) ((val) * SCALING_FACTOR) #define SCALED_EPSILON scale_(EPSILON) /* Implementation of CONFESS("foo"): */ #ifdef _MSC_VER @@ -102,6 +101,9 @@ inline std::string debug_out_path(const char *name, ...) 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> @@ -130,6 +132,17 @@ inline void append(std::vector<T>& dest, std::vector<T>&& src) 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) { diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 34b9eaa9f..95aaf5453 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -1,6 +1,15 @@ +#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> @@ -87,7 +96,7 @@ const std::string& var_dir() std::string var(const std::string &file_name) { - auto file = boost::filesystem::canonical(boost::filesystem::path(g_var_dir) / file_name).make_preferred(); + auto file = (boost::filesystem::path(g_var_dir) / file_name).make_preferred(); return file.string(); } @@ -115,6 +124,9 @@ 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) @@ -129,44 +141,6 @@ const std::string& data_dir() } // namespace Slic3r -#ifdef SLIC3R_HAS_BROKEN_CROAK - -// Some Strawberry Perl builds (mainly the latest 64bit builds) have a broken mechanism -// for emiting Perl exception after handling a C++ exception. Perl interpreter -// simply hangs. Better to show a message box in that case and stop the application. - -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#ifdef WIN32 -#include <Windows.h> -#endif - -void confess_at(const char *file, int line, const char *func, const char *format, ...) -{ - char dest[1024*8]; - va_list argptr; - va_start(argptr, format); - vsprintf(dest, format, argptr); - va_end(argptr); - - char filelinefunc[1024*8]; - sprintf(filelinefunc, "\r\nin function: %s\r\nfile: %s\r\nline: %d\r\n", func, file, line); - strcat(dest, filelinefunc); - strcat(dest, "\r\n Closing the application.\r\n"); - #ifdef WIN32 - ::MessageBoxA(NULL, dest, "Slic3r Prusa Edition", MB_OK | MB_ICONERROR); - #endif - - // Give up. - printf(dest); - exit(-1); -} - -#else - #include <xsinit.h> void @@ -196,7 +170,157 @@ confess_at(const char *file, int line, const char *func, #endif } -#endif +void PerlCallback::register_callback(void *sv) +{ + if (! SvROK((SV*)sv) || SvTYPE(SvRV((SV*)sv)) != SVt_PVCV) + croak("Not a Callback %_ for PerlFunction", (SV*)sv); + if (m_callback) + SvSetSV((SV*)m_callback, (SV*)sv); + else + m_callback = newSVsv((SV*)sv); +} + +void PerlCallback::deregister_callback() +{ + if (m_callback) { + sv_2mortal((SV*)m_callback); + m_callback = nullptr; + } +} + +void PerlCallback::call() const +{ + if (! m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(int i) const +{ + if (! m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSViv(i))); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(int i, int j) const +{ + if (! m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSViv(i))); + XPUSHs(sv_2mortal(newSViv(j))); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(const std::vector<int>& ints) const +{ + if (! m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + for (int i : ints) + { + XPUSHs(sv_2mortal(newSViv(i))); + } + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(double a) const +{ + if (!m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVnv(a))); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(double a, double b) const +{ + if (!m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVnv(a))); + XPUSHs(sv_2mortal(newSVnv(b))); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(double a, double b, double c) const +{ + if (!m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVnv(a))); + XPUSHs(sv_2mortal(newSVnv(b))); + XPUSHs(sv_2mortal(newSVnv(c))); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(double a, double b, double c, double d) const +{ + if (!m_callback) + return; + dSP; + ENTER; + SAVETMPS; + PUSHMARK(SP); + XPUSHs(sv_2mortal(newSVnv(a))); + XPUSHs(sv_2mortal(newSVnv(b))); + XPUSHs(sv_2mortal(newSVnv(c))); + XPUSHs(sv_2mortal(newSVnv(d))); + PUTBACK; + perl_call_sv(SvRV((SV*)m_callback), G_DISCARD); + FREETMPS; + LEAVE; +} + +void PerlCallback::call(bool b) const +{ + call(b ? 1 : 0); +} #ifdef WIN32 #ifndef NOMINMAX @@ -263,7 +387,6 @@ namespace PerlUtils { std::string timestamp_str() { const auto now = boost::posix_time::second_clock::local_time(); - const auto date = now.date(); char buf[2048]; sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d", // Local date in an ANSII format. @@ -272,31 +395,40 @@ std::string timestamp_str() return buf; } -std::string octoprint_encode_file_send_request_content(const char *cpath, bool select, bool print, const char *boundary) +unsigned get_current_pid() +{ +#ifdef WIN32 + return GetCurrentProcessId(); +#else + return ::getpid(); +#endif +} + +std::string xml_escape(std::string text) { - // Read the complete G-code string into a string buffer. - // It will throw if the file cannot be open or read. - std::stringstream str_stream; + std::string::size_type pos = 0; + for (;;) { - boost::nowide::ifstream ifs(cpath); - str_stream << ifs.rdbuf(); + pos = text.find_first_of("\"\'&<>", pos); + if (pos == std::string::npos) + break; + + std::string replacement; + switch (text[pos]) + { + case '\"': replacement = """; break; + case '\'': replacement = "'"; break; + case '&': replacement = "&"; break; + case '<': replacement = "<"; break; + case '>': replacement = ">"; break; + default: break; + } + + text.replace(pos, 1, replacement); + pos += replacement.size(); } - boost::filesystem::path path(cpath); - std::string request = boundary + '\n'; - request += "Content-Disposition: form-data; name=\""; - request += path.stem().string() + "\"; filename=\"" + path.filename().string() + "\"\n"; - request += "Content-Type: application/octet-stream\n\n"; - request += str_stream.str(); - request += boundary + '\n'; - request += "Content-Disposition: form-data; name=\"select\"\n\n"; - request += select ? "true\n" : "false\n"; - request += boundary + '\n'; - request += "Content-Disposition: form-data; name=\"print\"\n\n"; - request += print ? "true\n" : "false\n"; - request += boundary + '\n'; - - return request; + return text; } }; // namespace Slic3r |