diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 6a2161d288..ae0dc85a20 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -15,7 +15,6 @@ our @EXPORT_OK = qw( X Y Z convex_hull - chained_path_from deg2rad rad2deg ); diff --git a/src/libslic3r/AABBTreeLines.hpp b/src/libslic3r/AABBTreeLines.hpp index 990b3197fd..fd5e7cd967 100644 --- a/src/libslic3r/AABBTreeLines.hpp +++ b/src/libslic3r/AABBTreeLines.hpp @@ -345,7 +345,7 @@ public: return dist; } - std::vector all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius) + std::vector all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius) const { return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast(), radius * radius); } diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index bdc1a19c27..672bb80726 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -490,8 +490,10 @@ static void make_inner_brim(const Print &print, loops = union_pt_chained_outside_in(loops); std::reverse(loops.begin(), loops.end()); - extrusion_entities_append_loops(brim.entities, std::move(loops), ExtrusionRole::Skirt, float(flow.mm3_per_mm()), - float(flow.width()), float(print.skirt_first_layer_height())); + extrusion_entities_append_loops(brim.entities, std::move(loops), + ExtrusionAttributes{ + ExtrusionRole::Skirt, + ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } }); } // Produce brim lines around those objects, that have the brim enabled. @@ -672,7 +674,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance if (i + 1 == j && first_path.size() > 3 && first_path.front().x() == first_path.back().x() && first_path.front().y() == first_path.back().y()) { auto *loop = new ExtrusionLoop(); brim.entities.emplace_back(loop); - loop->paths.emplace_back(ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); + loop->paths.emplace_back(ExtrusionAttributes{ + ExtrusionRole::Skirt, + ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } }); Points &points = loop->paths.front().polyline.points; points.reserve(first_path.size()); for (const ClipperLib_Z::IntPoint &pt : first_path) @@ -683,7 +687,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance ExtrusionEntityCollection this_loop_trimmed; this_loop_trimmed.entities.reserve(j - i); for (; i < j; ++ i) { - this_loop_trimmed.entities.emplace_back(new ExtrusionPath(ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()))); + this_loop_trimmed.entities.emplace_back(new ExtrusionPath({ + ExtrusionRole::Skirt, + ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } })); const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first; Points &points = dynamic_cast(this_loop_trimmed.entities.back())->polyline.points; points.reserve(path.size()); @@ -699,7 +705,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance } } } else { - extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())); + extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), + ExtrusionAttributes{ ExtrusionRole::Skirt, + ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } }); } make_inner_brim(print, top_level_objects_with_brim, bottom_layers_expolygons, brim); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6f8a759f3f..41ef6482aa 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -142,8 +142,12 @@ set(SLIC3R_SOURCES GCode/ConflictChecker.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp + GCode/ExtrusionProcessor.cpp + GCode/ExtrusionProcessor.hpp GCode/FindReplace.cpp GCode/FindReplace.hpp + GCode/GCodeWriter.cpp + GCode/GCodeWriter.hpp GCode/PostProcessor.cpp GCode/PostProcessor.hpp GCode/PressureEqualizer.cpp @@ -156,10 +160,16 @@ set(SLIC3R_SOURCES GCode/SpiralVase.hpp GCode/SeamPlacer.cpp GCode/SeamPlacer.hpp + GCode/SmoothPath.cpp + GCode/SmoothPath.hpp GCode/ToolOrdering.cpp GCode/ToolOrdering.hpp + GCode/Wipe.cpp + GCode/Wipe.hpp GCode/WipeTower.cpp GCode/WipeTower.hpp + GCode/WipeTowerIntegration.cpp + GCode/WipeTowerIntegration.hpp GCode/GCodeProcessor.cpp GCode/GCodeProcessor.hpp GCode/AvoidCrossingPerimeters.cpp @@ -170,10 +180,10 @@ set(SLIC3R_SOURCES GCodeReader.hpp # GCodeSender.cpp # GCodeSender.hpp - GCodeWriter.cpp - GCodeWriter.hpp Geometry.cpp Geometry.hpp + Geometry/ArcWelder.cpp + Geometry/ArcWelder.hpp Geometry/Bicubic.hpp Geometry/Circle.cpp Geometry/Circle.hpp diff --git a/src/libslic3r/CustomGCode.cpp b/src/libslic3r/CustomGCode.cpp index 213437782f..5aebf8ef8e 100644 --- a/src/libslic3r/CustomGCode.cpp +++ b/src/libslic3r/CustomGCode.cpp @@ -1,7 +1,7 @@ #include "CustomGCode.hpp" #include "Config.hpp" #include "GCode.hpp" -#include "GCodeWriter.hpp" +#include "GCode/GCodeWriter.hpp" namespace Slic3r { diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index b1a089d085..ea5a3b803b 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -1,5 +1,5 @@ #include "Extruder.hpp" -#include "GCodeWriter.hpp" +#include "GCode/GCodeWriter.hpp" #include "PrintConfig.hpp" namespace Slic3r { diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 8a1a88b4b1..f66e8a0e43 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -2,6 +2,7 @@ #include "ExtrusionEntityCollection.hpp" #include "ExPolygon.hpp" #include "ClipperUtils.hpp" +#include "Exception.hpp" #include "Extruder.hpp" #include "Flow.hpp" #include @@ -9,7 +10,7 @@ #include namespace Slic3r { - + void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const { this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection), retval); @@ -38,12 +39,12 @@ double ExtrusionPath::length() const void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const { for (const Polyline &polyline : polylines) - collection->entities.emplace_back(new ExtrusionPath(polyline, *this)); + collection->entities.emplace_back(new ExtrusionPath(polyline, this->attributes())); } void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { - polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon)); + polygons_append(out, offset(this->polyline, float(scale_(m_attributes.width/2)) + scaled_epsilon)); } void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const @@ -51,8 +52,8 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale // Instantiating the Flow class to get the line spacing. // Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler. bool bridge = this->role().is_bridge(); - assert(! bridge || this->width == this->height); - auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f); + assert(! bridge || m_attributes.width == m_attributes.height); + auto flow = bridge ? Flow::bridging_flow(m_attributes.width, 0.f) : Flow(m_attributes.width, m_attributes.height, 0.f); polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon)); } @@ -87,7 +88,7 @@ double ExtrusionMultiPath::min_mm3_per_mm() const { double min_mm3_per_mm = std::numeric_limits::max(); for (const ExtrusionPath &path : this->paths) - min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm); + min_mm3_per_mm = std::min(min_mm3_per_mm, path.min_mm3_per_mm()); return min_mm3_per_mm; } @@ -112,21 +113,34 @@ Polyline ExtrusionMultiPath::as_polyline() const return out; } -bool ExtrusionLoop::make_clockwise() +double ExtrusionLoop::area() const { - bool was_ccw = this->polygon().is_counter_clockwise(); - if (was_ccw) this->reverse(); - return was_ccw; -} - -bool ExtrusionLoop::make_counter_clockwise() -{ - bool was_cw = this->polygon().is_clockwise(); - if (was_cw) this->reverse(); - return was_cw; + double a = 0; + for (const ExtrusionPath &path : this->paths) { + assert(path.size() >= 2); + if (path.size() >= 2) { + // Assumming that the last point of one path segment is repeated at the start of the following path segment. + auto it = path.polyline.points.begin(); + Point prev = *it ++; + for (; it != path.polyline.points.end(); ++ it) { + a += cross2(prev.cast(), it->cast()); + prev = *it; + } + } + } + return a * 0.5; } void ExtrusionLoop::reverse() +{ +#if 0 + this->reverse_loop(); +#else + throw Slic3r::LogicError("ExtrusionLoop::reverse() must NOT be called"); +#endif +} + +void ExtrusionLoop::reverse_loop() { for (ExtrusionPath &path : this->paths) path.reverse(); @@ -248,8 +262,8 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang, const // now split path_idx in two parts const ExtrusionPath &path = this->paths[path_idx]; - ExtrusionPath p1(path.role(), path.mm3_per_mm, path.width, path.height); - ExtrusionPath p2(path.role(), path.mm3_per_mm, path.width, path.height); + ExtrusionPath p1(path.attributes()); + ExtrusionPath p2(path.attributes()); path.polyline.split_at(p, &p1.polyline, &p2.polyline); if (this->paths.size() == 1) { @@ -316,7 +330,7 @@ double ExtrusionLoop::min_mm3_per_mm() const { double min_mm3_per_mm = std::numeric_limits::max(); for (const ExtrusionPath &path : this->paths) - min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm); + min_mm3_per_mm = std::min(min_mm3_per_mm, path.min_mm3_per_mm()); return min_mm3_per_mm; } diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 277ac78242..3930d8f65b 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -3,10 +3,12 @@ #include "libslic3r.h" #include "ExtrusionRole.hpp" +#include "Flow.hpp" #include "Polygon.hpp" #include "Polyline.hpp" #include +#include #include #include @@ -55,28 +57,91 @@ public: virtual double total_volume() const = 0; }; -typedef std::vector ExtrusionEntitiesPtr; +using ExtrusionEntitiesPtr = std::vector; + +// Const reference for ordering extrusion entities without having to modify them. +class ExtrusionEntityReference final +{ +public: + ExtrusionEntityReference() = delete; + ExtrusionEntityReference(const ExtrusionEntity &extrusion_entity, bool flipped) : + m_extrusion_entity(&extrusion_entity), m_flipped(flipped) {} + ExtrusionEntityReference operator=(const ExtrusionEntityReference &rhs) + { m_extrusion_entity = rhs.m_extrusion_entity; m_flipped = rhs.m_flipped; return *this; } + + const ExtrusionEntity& extrusion_entity() const { return *m_extrusion_entity; } + template + const Type* cast() const { return dynamic_cast(m_extrusion_entity); } + bool flipped() const { return m_flipped; } + +private: + const ExtrusionEntity *m_extrusion_entity; + bool m_flipped; +}; + +using ExtrusionEntityReferences = std::vector; + +struct ExtrusionFlow +{ + ExtrusionFlow() = default; + ExtrusionFlow(double mm3_per_mm, float width, float height) : + mm3_per_mm{ mm3_per_mm }, width{ width }, height{ height } {} + ExtrusionFlow(const Flow &flow) : + mm3_per_mm(flow.mm3_per_mm()), width(flow.width()), height(flow.height()) {} + + // Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator. + double mm3_per_mm{ -1. }; + // Width of the extrusion, used for visualization purposes. + float width{ -1.f }; + // Height of the extrusion, used for visualization purposes. + float height{ -1.f }; +}; + +inline bool operator==(const ExtrusionFlow &lhs, const ExtrusionFlow &rhs) +{ + return lhs.mm3_per_mm == rhs.mm3_per_mm && lhs.width == rhs.width && lhs.height == rhs.height; +} + +struct OverhangAttributes { + float start_distance_from_prev_layer; + float end_distance_from_prev_layer; + float proximity_to_curled_lines; //value between 0 and 1 +}; + +struct ExtrusionAttributes : ExtrusionFlow +{ + ExtrusionAttributes() = default; + ExtrusionAttributes(ExtrusionRole role) : role{ role } {} + ExtrusionAttributes(ExtrusionRole role, const Flow &flow) : role{ role }, ExtrusionFlow{ flow } {} + ExtrusionAttributes(ExtrusionRole role, const ExtrusionFlow &flow) : role{ role }, ExtrusionFlow{ flow } {} + + // What is the role / purpose of this extrusion? + ExtrusionRole role{ ExtrusionRole::None }; + // OVerhangAttributes are currently computed for perimeters if dynamic overhangs are enabled. + // They are used to control fan and print speed in export. + std::optional overhang_attributes; +}; + +inline bool operator==(const ExtrusionAttributes &lhs, const ExtrusionAttributes &rhs) +{ + return static_cast(lhs) == static_cast(rhs) && + lhs.role == rhs.role; +} class ExtrusionPath : public ExtrusionEntity { public: Polyline polyline; - // Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator. - double mm3_per_mm; - // Width of the extrusion, used for visualization purposes. - float width; - // Height of the extrusion, used for visualization purposes. - float height; - ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {} - ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {} - ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} - ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} - ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} - ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {} + ExtrusionPath(ExtrusionRole role) : m_attributes{ role } {} + ExtrusionPath(const ExtrusionAttributes &attributes) : m_attributes(attributes) {} + ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), m_attributes(rhs.m_attributes) {} + ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), m_attributes(rhs.m_attributes) {} + ExtrusionPath(const Polyline &polyline, const ExtrusionAttributes &attribs) : polyline(polyline), m_attributes(attribs) {} + ExtrusionPath(Polyline &&polyline, const ExtrusionAttributes &attribs) : polyline(std::move(polyline)), m_attributes(attribs) {} - ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = rhs.polyline; return *this; } - ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = std::move(rhs.polyline); return *this; } + ExtrusionPath& operator=(const ExtrusionPath &rhs) { this->polyline = rhs.polyline; m_attributes = rhs.m_attributes; return *this; } + ExtrusionPath& operator=(ExtrusionPath &&rhs) { this->polyline = std::move(rhs.polyline); m_attributes = rhs.m_attributes; return *this; } ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); } // Create a new object, initialize it with this object using the move semantics. @@ -97,35 +162,46 @@ public: void clip_end(double distance); void simplify(double tolerance); double length() const override; - ExtrusionRole role() const override { return m_role; } + + const ExtrusionAttributes& attributes() const { return m_attributes; } + ExtrusionRole role() const override { return m_attributes.role; } + float width() const { return m_attributes.width; } + float height() const { return m_attributes.height; } + double mm3_per_mm() const { return m_attributes.mm3_per_mm; } + // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. + double min_mm3_per_mm() const override { return m_attributes.mm3_per_mm; } + std::optional& overhang_attributes_mutable() { return m_attributes.overhang_attributes; } + // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. - void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; + void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. - void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override; - Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const + void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override; + Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } - Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const + Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } - // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. - double min_mm3_per_mm() const override { return this->mm3_per_mm; } - Polyline as_polyline() const override { return this->polyline; } - void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); } - void collect_points(Points &dst) const override { append(dst, this->polyline.points); } - double total_volume() const override { return mm3_per_mm * unscale(length()); } + + Polyline as_polyline() const override { return this->polyline; } + void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); } + void collect_points(Points &dst) const override { append(dst, this->polyline.points); } + double total_volume() const override { return m_attributes.mm3_per_mm * unscale(length()); } private: - void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; + void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; - ExtrusionRole m_role; + ExtrusionAttributes m_attributes; }; class ExtrusionPathOriented : public ExtrusionPath { public: - ExtrusionPathOriented(ExtrusionRole role, double mm3_per_mm, float width, float height) : ExtrusionPath(role, mm3_per_mm, width, height) {} + ExtrusionPathOriented(const ExtrusionAttributes &attribs) : ExtrusionPath(attribs) {} + ExtrusionPathOriented(const Polyline &polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(polyline, attribs) {} + ExtrusionPathOriented(Polyline &&polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(std::move(polyline), attribs) {} + ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); } // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); } @@ -192,7 +268,8 @@ class ExtrusionLoop : public ExtrusionEntity public: ExtrusionPaths paths; - ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {} + ExtrusionLoop() = default; + ExtrusionLoop(ExtrusionLoopRole role) : m_loop_role(role) {} ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {} ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {} ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role) @@ -204,16 +281,21 @@ public: ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); } // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionLoop(std::move(*this)); } - bool make_clockwise(); - bool make_counter_clockwise(); - void reverse() override; - const Point& first_point() const override { return this->paths.front().polyline.points.front(); } - const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); } - const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; } - Polygon polygon() const; - double length() const override; - bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled(0.001)); - void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled(0.001)); + double area() const; + bool is_counter_clockwise() const { return this->area() > 0; } + bool is_clockwise() const { return this->area() < 0; } + // Reverse shall never be called on ExtrusionLoop using a virtual function call, it is most likely never what one wants, + // as this->can_reverse() returns false for an ExtrusionLoop. + void reverse() override; + // Used by PerimeterGenerator to reorient extrusion loops. + void reverse_loop(); + const Point& first_point() const override { return this->paths.front().polyline.points.front(); } + const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); } + const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; } + Polygon polygon() const; + double length() const override; + bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled(0.001)); + void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled(0.001)); struct ClosestPathPoint { size_t path_idx; size_t segment_idx; @@ -259,59 +341,51 @@ public: #endif /* NDEBUG */ private: - ExtrusionLoopRole m_loop_role; + ExtrusionLoopRole m_loop_role{ elrDefault }; }; -inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, const ExtrusionAttributes &attributes) { dst.reserve(dst.size() + polylines.size()); for (Polyline &polyline : polylines) - if (polyline.is_valid()) { - dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height)); - dst.back().polyline = polyline; - } + if (polyline.is_valid()) + dst.emplace_back(polyline, attributes); } -inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, const ExtrusionAttributes &attributes) { dst.reserve(dst.size() + polylines.size()); for (Polyline &polyline : polylines) - if (polyline.is_valid()) { - dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height)); - dst.back().polyline = std::move(polyline); - } + if (polyline.is_valid()) + dst.emplace_back(std::move(polyline), attributes); polylines.clear(); } -inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true) +inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, const ExtrusionAttributes &attributes, bool can_reverse = true) { dst.reserve(dst.size() + polylines.size()); for (const Polyline &polyline : polylines) - if (polyline.is_valid()) { - ExtrusionPath* extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height); - dst.push_back(extrusion_path); - extrusion_path->polyline = polyline; - } + if (polyline.is_valid()) + dst.emplace_back(can_reverse ? new ExtrusionPath(polyline, attributes) : new ExtrusionPathOriented(polyline, attributes)); } -inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true) +inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, const ExtrusionAttributes &attributes, bool can_reverse = true) { dst.reserve(dst.size() + polylines.size()); for (Polyline &polyline : polylines) - if (polyline.is_valid()) { - ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height); - dst.push_back(extrusion_path); - extrusion_path->polyline = std::move(polyline); - } + if (polyline.is_valid()) + dst.emplace_back(can_reverse ? + new ExtrusionPath(std::move(polyline), attributes) : + new ExtrusionPathOriented(std::move(polyline), attributes)); polylines.clear(); } -inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, ExtrusionRole role, double mm3_per_mm, float width, float height) +inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, const ExtrusionAttributes &attributes) { dst.reserve(dst.size() + loops.size()); for (Polygon &poly : loops) { if (poly.is_valid()) { - ExtrusionPath path(role, mm3_per_mm, width, height); + ExtrusionPath path(attributes); path.polyline.points = std::move(poly.points); path.polyline.points.push_back(path.polyline.points.front()); dst.emplace_back(new ExtrusionLoop(std::move(path))); @@ -320,22 +394,14 @@ inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons loops.clear(); } -inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, const ExtrusionAttributes &attributes) { dst.reserve(dst.size() + polylines.size()); - for (Polyline &polyline : polylines) { - if (polyline.is_valid()) { - if (polyline.is_closed()) { - ExtrusionPath extrusion_path(role, mm3_per_mm, width, height); - extrusion_path.polyline = std::move(polyline); - dst.emplace_back(new ExtrusionLoop(std::move(extrusion_path))); - } else { - ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height); - extrusion_path->polyline = std::move(polyline); - dst.emplace_back(extrusion_path); - } - } - } + for (Polyline &polyline : polylines) + if (polyline.is_valid()) + dst.emplace_back(polyline.is_closed() ? + static_cast(new ExtrusionLoop(ExtrusionPath{ std::move(polyline), attributes })) : + static_cast(new ExtrusionPath(std::move(polyline), attributes))); polylines.clear(); } diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 55167861c3..3f49eaa032 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -6,6 +6,7 @@ namespace Slic3r { +#if 0 void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role) { if (role != ExtrusionRole::Mixed) { @@ -17,6 +18,7 @@ void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, last); } } +#endif ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths) : no_sort(false) @@ -83,18 +85,6 @@ void ExtrusionEntityCollection::remove(size_t i) this->entities.erase(this->entities.begin() + i); } -ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const ExtrusionEntitiesPtr& extrusion_entities, const Point &start_near, ExtrusionRole role) -{ - // Return a filtered copy of the collection. - ExtrusionEntityCollection out; - out.entities = filter_by_extrusion_role(extrusion_entities, role); - // Clone the extrusion entities. - for (auto &ptr : out.entities) - ptr = ptr->clone(); - chain_and_reorder_extrusion_entities(out.entities, &start_near); - return out; -} - void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { for (const ExtrusionEntity *entity : this->entities) diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 676bdd891a..3d6ffba3fd 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -7,6 +7,7 @@ namespace Slic3r { +#if 0 // Remove those items from extrusion_entities, that do not match role. // Do nothing if role is mixed. // Removed elements are NOT being deleted. @@ -21,6 +22,7 @@ inline ExtrusionEntitiesPtr filter_by_extrusion_role(const ExtrusionEntitiesPtr filter_by_extrusion_role_in_place(out, role); return out; } +#endif class ExtrusionEntityCollection : public ExtrusionEntity { @@ -96,9 +98,6 @@ public: } void replace(size_t i, const ExtrusionEntity &entity); void remove(size_t i); - static ExtrusionEntityCollection chained_path_from(const ExtrusionEntitiesPtr &extrusion_entities, const Point &start_near, ExtrusionRole role = ExtrusionRole::Mixed); - ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = ExtrusionRole::Mixed) const - { return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); } void reverse() override; const Point& first_point() const override { return this->entities.front()->first_point(); } const Point& last_point() const override { return this->entities.back()->last_point(); } diff --git a/src/libslic3r/ExtrusionRole.hpp b/src/libslic3r/ExtrusionRole.hpp index 986c139a24..140468f05a 100644 --- a/src/libslic3r/ExtrusionRole.hpp +++ b/src/libslic3r/ExtrusionRole.hpp @@ -82,6 +82,7 @@ struct ExtrusionRole : public ExtrusionRoleModifiers bool is_external_perimeter() const { return this->is_perimeter() && this->is_external(); } bool is_infill() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Infill); } bool is_solid_infill() const { return this->is_infill() && this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); } + bool is_sparse_infill() const { return this->is_infill() && ! this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); } bool is_external() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::External); } bool is_bridge() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Bridge); } @@ -89,6 +90,9 @@ struct ExtrusionRole : public ExtrusionRoleModifiers bool is_support_base() const { return this->is_support() && ! this->is_external(); } bool is_support_interface() const { return this->is_support() && this->is_external(); } bool is_mixed() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Mixed); } + + // Brim is currently marked as skirt. + bool is_skirt() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Skirt); } }; // Special flags describing loop diff --git a/src/libslic3r/ExtrusionSimulator.cpp b/src/libslic3r/ExtrusionSimulator.cpp index 6b1f76abea..a08b4d8b9a 100644 --- a/src/libslic3r/ExtrusionSimulator.cpp +++ b/src/libslic3r/ExtrusionSimulator.cpp @@ -957,9 +957,9 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const polyline.reserve(path.polyline.points.size()); float scalex = float(viewport.size().x()) / float(bbox.size().x()); float scaley = float(viewport.size().y()) / float(bbox.size().y()); - float w = scale_(path.width) * scalex; + float w = scale_(path.width()) * scalex; //float h = scale_(path.height) * scalex; - w = scale_(path.mm3_per_mm / path.height) * scalex; + w = scale_(path.mm3_per_mm() / path.height()) * scalex; // printf("scalex: %f, scaley: %f\n", scalex, scaley); // printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y); for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) { diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 499e7b85af..7fa4c992f9 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -558,8 +558,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } else { extrusion_entities_append_paths( eec->entities, std::move(polylines), - surface_fill.params.extrusion_role, - flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); + ExtrusionAttributes{ surface_fill.params.extrusion_role, + ExtrusionFlow{ flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height() } + }); } insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size())); } @@ -904,8 +905,9 @@ void Layer::make_ironing() eec->no_sort = true; extrusion_entities_append_paths( eec->entities, std::move(polylines), - ExtrusionRole::Ironing, - flow_mm3_per_mm, extrusion_width, float(extrusion_height)); + ExtrusionAttributes{ ExtrusionRole::Ironing, + ExtrusionFlow{ flow_mm3_per_mm, extrusion_width, float(extrusion_height) } + }); insert_fills_into_islands(*this, ironing_params.region_id, fill_begin, uint32_t(ironing_params.layerm->fills().size())); } } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 444fe95e25..35d1386ac1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -9,6 +9,7 @@ #include "GCode/PrintExtents.hpp" #include "GCode/Thumbnails.hpp" #include "GCode/WipeTower.hpp" +#include "GCode/WipeTowerIntegration.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "PrintConfig.hpp" @@ -25,6 +26,7 @@ #include #include #include +#include #include #include @@ -106,7 +108,7 @@ namespace Slic3r { return ok; } - std::string OozePrevention::pre_toolchange(GCode& gcodegen) + std::string OozePrevention::pre_toolchange(GCodeGenerator &gcodegen) { std::string gcode; @@ -132,314 +134,25 @@ namespace Slic3r { return gcode; } - std::string OozePrevention::post_toolchange(GCode& gcodegen) + std::string OozePrevention::post_toolchange(GCodeGenerator &gcodegen) { return (gcodegen.config().standby_temperature_delta.value != 0) ? gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) : std::string(); } - int OozePrevention::_get_temp(const GCode& gcodegen) const + int OozePrevention::_get_temp(const GCodeGenerator &gcodegen) const { return (gcodegen.layer() == nullptr || gcodegen.layer()->id() == 0) ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id()) : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id()); } - std::string Wipe::wipe(GCode& gcodegen, bool toolchange) - { - std::string gcode; - const Extruder &extruder = *gcodegen.writer().extruder(); - - // Remaining quantized retraction length. - if (double retract_length = extruder.retract_to_go(toolchange ? extruder.retract_length_toolchange() : extruder.retract_length()); - retract_length > 0 && this->path.size() >= 2) { - // Reduce feedrate a bit; travel speed is often too high to move on existing material. - // Too fast = ripping of existing material; too slow = short wipe path, thus more blob. - const double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8; - // Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one - // due to rounding (TODO: test and/or better math for this). - const double xy_to_e = 0.95 * extruder.retract_speed() / wipe_speed; - // Start with the current position, which may be different from the wipe path start in case of loop clipping. - Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos()); - auto it = this->path.points.begin(); - Vec2d p = gcodegen.point_to_gcode_quantized(*(++ it)); - if (p != prev) { - gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n"; - auto end = this->path.points.end(); - bool done = false; - for (; it != end && ! done; ++ it) { - p = gcodegen.point_to_gcode_quantized(*it); - double segment_length = (p - prev).norm(); - double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length); - if (dE > retract_length - EPSILON) { - if (dE > retract_length + EPSILON) - // Shorten the segment. - p = prev + (p - prev) * (retract_length / dE); - dE = retract_length; - done = true; - } - //FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle. - // Is it here for the cooling markers? Or should it be outside of the cycle? - gcode += gcodegen.writer().set_speed(wipe_speed * 60, {}, gcodegen.enable_cooling_markers() ? ";_WIPE" : ""); - gcode += gcodegen.writer().extrude_to_xy(p, -dE, "wipe and retract"); - prev = p; - retract_length -= dE; - } - // add tag for processor - gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; - gcodegen.set_last_pos(gcodegen.gcode_to_point(prev)); - } - } - - // Prevent wiping again on the same path. - this->reset_path(); - return gcode; - } - - static inline Point wipe_tower_point_to_object_point(GCode& gcodegen, const Vec2f& wipe_tower_pt) - { - return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); - } - - std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const - { - if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) - throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); - - std::string gcode; - - // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) - // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position - float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); - - auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { - Vec2f out = Eigen::Rotation2Df(alpha) * pt; - out += m_wipe_tower_pos; - return out; - }; - - Vec2f start_pos = tcr.start_pos; - Vec2f end_pos = tcr.end_pos; - if (! tcr.priming) { - start_pos = transform_wt_pt(start_pos); - end_pos = transform_wt_pt(end_pos); - } - - Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; - float wipe_tower_rotation = tcr.priming ? 0.f : alpha; - - std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); - - gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't). - - double current_z = gcodegen.writer().get_position().z(); - if (z == -1.) // in case no specific z was provided, print at current_z pos - z = current_z; - - const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id); - const bool will_go_down = ! is_approx(z, current_z); - const bool is_ramming = (gcodegen.config().single_extruder_multi_material && ! tcr.priming) - || (! gcodegen.config().single_extruder_multi_material && gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool)); - const bool should_travel_to_tower = tcr.force_travel // wipe tower says so - || ! needs_toolchange // this is just finishing the tower with no toolchange - || is_ramming; - if (should_travel_to_tower) { - // FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges, - // then we could simplify the condition and make it more readable. - gcode += gcodegen.retract(); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); - gcode += gcodegen.travel_to( - wipe_tower_point_to_object_point(gcodegen, start_pos), - ExtrusionRole::Mixed, - "Travel to a Wipe Tower"); - gcode += gcodegen.unretract(); - } else { - // When this is multiextruder printer without any ramming, we can just change - // the tool without travelling to the tower. - } - - if (will_go_down) { - gcode += gcodegen.writer().retract(); - gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); - gcode += gcodegen.writer().unretract(); - } - - std::string toolchange_gcode_str; - std::string deretraction_str; - if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) { - if (is_ramming) - gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. - toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z - if (gcodegen.config().wipe_tower) - deretraction_str = gcodegen.unretract(); - } - - - - - // Insert the toolchange and deretraction gcode into the generated gcode. - DynamicConfig config; - config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str)); - config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str)); - std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); - unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); - gcode += tcr_gcode; - check_add_eol(toolchange_gcode_str); - - // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(end_pos.cast()); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); - if (!is_approx(z, current_z)) { - gcode += gcodegen.writer().retract(); - gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); - gcode += gcodegen.writer().unretract(); - } - - else { - // Prepare a future wipe. - gcodegen.m_wipe.reset_path(); - for (const Vec2f& wipe_pt : tcr.wipe_path) - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt))); - } - - // Let the planner know we are traveling between objects. - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); - 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::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const - { - Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast(); - - std::istringstream gcode_str(tcr.gcode); - std::string gcode_out; - std::string line; - Vec2f pos = tcr.start_pos; - Vec2f transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; - Vec2f old_pos(-1000.1f, -1000.1f); - - while (gcode_str) { - std::getline(gcode_str, line); // we read the gcode line by line - - // All G1 commands should be translated and rotated. X and Y coords are - // only pushed to the output when they differ from last time. - // WT generator can override this by appending the never_skip_tag - if (boost::starts_with(line, "G1 ")) { - bool never_skip = false; - auto it = line.find(WipeTower::never_skip_tag()); - if (it != std::string::npos) { - // remove the tag and remember we saw it - never_skip = true; - line.erase(it, it + WipeTower::never_skip_tag().size()); - } - std::ostringstream line_out; - std::istringstream line_str(line); - line_str >> std::noskipws; // don't skip whitespace - char ch = 0; - line_str >> ch >> ch; // read the "G1" - while (line_str >> ch) { - if (ch == 'X' || ch == 'Y') - line_str >> (ch == 'X' ? pos.x() : pos.y()); - else - line_out << ch; - } - - transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; - - if (transformed_pos != old_pos || never_skip) { - line = line_out.str(); - boost::trim_left(line); // Remove leading spaces - std::ostringstream oss; - oss << std::fixed << std::setprecision(3) << "G1"; - if (transformed_pos.x() != old_pos.x() || never_skip) - oss << " X" << transformed_pos.x() - extruder_offset.x(); - if (transformed_pos.y() != old_pos.y() || never_skip) - oss << " Y" << transformed_pos.y() - extruder_offset.y(); - if (! line.empty()) - oss << " "; - line = oss.str() + line; - old_pos = transformed_pos; - } - } - - gcode_out += line + "\n"; - - // If this was a toolchange command, we should change current extruder offset - if (line == "[toolchange_gcode]") { - extruder_offset = m_extruder_offsets[tcr.new_tool].cast(); - - // If the extruder offset changed, add an extra move so everything is continuous - if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) { - std::ostringstream oss; - oss << std::fixed << std::setprecision(3) - << "G1 X" << transformed_pos.x() - extruder_offset.x() - << " Y" << transformed_pos.y() - extruder_offset.y() - << "\n"; - gcode_out += oss.str(); - } - } - } - return gcode_out; - } - - - std::string WipeTowerIntegration::prime(GCode& gcodegen) - { - std::string gcode; - for (const WipeTower::ToolChangeResult& tcr : m_priming) { - if (! tcr.extrusions.empty()) - gcode += append_tcr(gcodegen, tcr, tcr.new_tool); - } - return gcode; - } - - std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id, bool finish_layer) - { - std::string gcode; - assert(m_layer_idx >= 0); - if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { - if (m_layer_idx < (int)m_tool_changes.size()) { - if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) - throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); - - // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, - // resulting in a wipe tower with sparse layers. - double wipe_tower_z = -1; - bool ignore_sparse = false; - if (gcodegen.config().wipe_tower_no_sparse_layers.value) { - wipe_tower_z = m_last_wipe_tower_print_z; - ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool && m_layer_idx != 0); - if (m_tool_change_idx == 0 && !ignore_sparse) - wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; - } - - if (!ignore_sparse) { - gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); - m_last_wipe_tower_print_z = wipe_tower_z; - } - } - } - return gcode; - } - - // Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower. - std::string WipeTowerIntegration::finalize(GCode& gcodegen) - { - std::string gcode; - if (std::abs(gcodegen.writer().get_position().z() - 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; - } - const std::vector ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" }; #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id()) -void GCode::PlaceholderParserIntegration::reset() +void GCodeGenerator::PlaceholderParserIntegration::reset() { this->failed_templates.clear(); this->output_config.clear(); @@ -459,7 +172,7 @@ void GCode::PlaceholderParserIntegration::reset() this->e_restart_extra.clear(); } -void GCode::PlaceholderParserIntegration::init(const GCodeWriter &writer) +void GCodeGenerator::PlaceholderParserIntegration::init(const GCodeWriter &writer) { this->reset(); const std::vector &extruders = writer.extruders(); @@ -495,7 +208,7 @@ void GCode::PlaceholderParserIntegration::init(const GCodeWriter &writer) this->parser.set("zhop", this->opt_zhop); } -void GCode::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWriter &writer) +void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWriter &writer) { memcpy(this->position.data(), writer.get_position().data(), sizeof(double) * 3); this->opt_position->values = this->position; @@ -534,7 +247,7 @@ void GCode::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWri } // Throw if any of the output vector variables were resized by the script. -void GCode::PlaceholderParserIntegration::validate_output_vector_variables() +void GCodeGenerator::PlaceholderParserIntegration::validate_output_vector_variables() { if (this->opt_position->values.size() != 3) throw Slic3r::RuntimeError("\"position\" output variable must not be resized by the script."); @@ -550,9 +263,9 @@ void GCode::PlaceholderParserIntegration::validate_output_vector_variables() // Collect pairs of object_layer + support_layer sorted by print_z. // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON. -GCode::ObjectsLayerToPrint GCode::collect_layers_to_print(const PrintObject& object) +GCodeGenerator::ObjectsLayerToPrint GCodeGenerator::collect_layers_to_print(const PrintObject& object) { - GCode::ObjectsLayerToPrint layers_to_print; + GCodeGenerator::ObjectsLayerToPrint layers_to_print; layers_to_print.reserve(object.layers().size() + object.support_layers().size()); /* @@ -651,7 +364,7 @@ GCode::ObjectsLayerToPrint GCode::collect_layers_to_print(const PrintObject& obj // Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z // will be printed for all objects at once. // Return a list of items. -std::vector> GCode::collect_layers_to_print(const Print& print) +std::vector> GCodeGenerator::collect_layers_to_print(const Print& print) { struct OrderingItem { coordf_t print_z; @@ -700,7 +413,7 @@ std::vector> GCode::collect_laye return layers_to_print; } -// free functions called by GCode::do_export() +// free functions called by GCodeGenerator::do_export() namespace DoExport { // static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) // { @@ -805,7 +518,7 @@ namespace DoExport { } } // namespace DoExport -void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) +void GCodeGenerator::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb) { CNumericLocalesSetter locales_setter; @@ -900,7 +613,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessorResult* resu print->set_done(psGCodeExport); } -// free functions called by GCode::_do_export() +// free functions called by GCodeGenerator::_do_export() namespace DoExport { static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled) { @@ -1086,7 +799,36 @@ std::vector sort_object_instances_by_model_order(const Pri return instances; } -void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb) +static inline bool arc_welder_enabled(const PrintConfig& print_config) +{ + return + // Enabled + print_config.arc_fitting != ArcFittingType::Disabled && + // Not a spiral vase print + !print_config.spiral_vase && + // Presure equalizer not used + print_config.max_volumetric_extrusion_rate_slope_negative == 0. && + print_config.max_volumetric_extrusion_rate_slope_positive == 0.; +} + +static inline GCode::SmoothPathCache::InterpolationParameters interpolation_parameters(const PrintConfig& print_config) +{ + return { + scaled(print_config.gcode_resolution.value), + arc_welder_enabled(print_config) ? Geometry::ArcWelder::default_arc_length_percent_tolerance : 0 + }; +} + +static inline GCode::SmoothPathCache smooth_path_interpolate_global(const Print& print) +{ + const GCode::SmoothPathCache::InterpolationParameters interpolation_params = interpolation_parameters(print.config()); + GCode::SmoothPathCache out; + out.interpolate_add(print.skirt(), interpolation_params); + out.interpolate_add(print.brim(), interpolation_params); + return out; +} + +void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb) { // modifies m_silent_time_estimator_enabled DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled); @@ -1346,6 +1088,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato file.write(this->set_extruder(initial_extruder_id, 0.)); } + GCode::SmoothPathCache smooth_path_cache_global = smooth_path_interpolate_global(print); + // Do all objects for each layer. if (print.config().complete_objects.value) { size_t finished_objects = 0; @@ -1390,7 +1134,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. - this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file); + this->process_layers(print, tool_ordering, collect_layers_to_print(object), + *print_object_instance_sequential_active - object.instances().data(), + smooth_path_cache_global, file); ++ finished_objects; // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. // Reset it when starting another object from 1st layer. @@ -1403,7 +1149,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato std::vector> layers_to_print = collect_layers_to_print(print); // Prusa Multi-Material wipe tower. if (has_wipe_tower && ! layers_to_print.empty()) { - m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); + m_wipe_tower = std::make_unique(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()); file.write(m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); if (print.config().single_extruder_multi_material_priming) { file.write(m_wipe_tower->prime(*this)); @@ -1446,7 +1192,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. - this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file); + this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, + smooth_path_cache_global, file); if (m_wipe_tower) // Purge the extruder, pull out the active filament. file.write(m_wipe_tower->finalize(*this)); @@ -1521,20 +1268,38 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); } +// Fill in cache of smooth paths for perimeters, fills and supports of the given object layers. +// Based on params, the paths are either decimated to sparser polylines, or interpolated with circular arches. +void GCodeGenerator::smooth_path_interpolate( + const ObjectLayerToPrint &object_layer_to_print, + const GCode::SmoothPathCache::InterpolationParameters ¶ms, + GCode::SmoothPathCache &out) +{ + if (const Layer *layer = object_layer_to_print.object_layer; layer) { + for (const LayerRegion *layerm : layer->regions()) { + out.interpolate_add(layerm->perimeters(), params); + out.interpolate_add(layerm->fills(), params); + } + } + if (const SupportLayer *layer = object_layer_to_print.support_layer; layer) + out.interpolate_add(layer->support_fills, params); +} + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. -void GCode::process_layers( +void GCodeGenerator::process_layers( const Print &print, const ToolOrdering &tool_ordering, const std::vector &print_object_instances_ordering, const std::vector> &layers_to_print, + const GCode::SmoothPathCache &smooth_path_cache_global, GCodeOutputStream &output_stream) { - // The pipeline is variable: The vase mode filter is optional. size_t layer_to_print_idx = 0; - const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> LayerResult { + const GCode::SmoothPathCache::InterpolationParameters interpolation_params = interpolation_parameters(print.config()); + const auto smooth_path_interpolator = tbb::make_filter>(slic3r_tbb_filtermode::serial_in_order, + [this, &print, &layers_to_print, &layer_to_print_idx, &interpolation_params](tbb::flow_control &fc) -> std::pair { if (layer_to_print_idx >= layers_to_print.size()) { if ((!m_pressure_equalizer && layer_to_print_idx == layers_to_print.size()) || (m_pressure_equalizer && layer_to_print_idx == (layers_to_print.size() + 1))) { fc.stop(); @@ -1542,23 +1307,41 @@ void GCode::process_layers( } else { // Pressure equalizer need insert empty input. Because it returns one layer back. // Insert NOP (no operation) layer; - ++layer_to_print_idx; - return LayerResult::make_nop_layer_result(); + return { ++ layer_to_print_idx, {} }; } } else { - const std::pair &layer = layers_to_print[layer_to_print_idx++]; + print.throw_if_canceled(); + size_t idx = layer_to_print_idx ++; + GCode::SmoothPathCache smooth_path_cache; + for (const ObjectLayerToPrint &l : layers_to_print[idx].second) + GCodeGenerator::smooth_path_interpolate(l, interpolation_params, smooth_path_cache); + return { idx, std::move(smooth_path_cache) }; + } + }); + const auto generator = tbb::make_filter, LayerResult>(slic3r_tbb_filtermode::serial_in_order, + [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &smooth_path_cache_global]( + std::pair in) -> LayerResult { + size_t layer_to_print_idx = in.first; + if (layer_to_print_idx == layers_to_print.size()) { + // Pressure equalizer need insert empty input. Because it returns one layer back. + // Insert NOP (no operation) layer; + return LayerResult::make_nop_layer_result(); + } else { + const std::pair &layer = layers_to_print[layer_to_print_idx]; 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(); print.throw_if_canceled(); - return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); + return this->process_layer(print, layer.second, layer_tools, + GCode::SmoothPathCaches{ smooth_path_cache_global, in.second }, + &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); } }); + // The pipeline is variable: The vase mode filter is optional. const auto spiral_vase = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [spiral_vase = this->m_spiral_vase.get()](LayerResult in) -> LayerResult { if (in.nop_layer_result) return in; - spiral_vase->enable(in.spiral_vase_enable); return { spiral_vase->process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; }); @@ -1581,45 +1364,40 @@ void GCode::process_layers( [&output_stream](std::string s) { output_stream.write(s); } ); + tbb::filter pipeline_to_layerresult = smooth_path_interpolator & generator; + if (m_spiral_vase) + pipeline_to_layerresult = pipeline_to_layerresult & spiral_vase; + if (m_pressure_equalizer) + pipeline_to_layerresult = pipeline_to_layerresult & pressure_equalizer; + + tbb::filter pipeline_to_string = cooling; + if (m_find_replace) + pipeline_to_string = pipeline_to_string & find_replace; + // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline. // Handler is unregistered when the destructor is called. TBBLocalesSetter locales_setter; - // The pipeline elements are joined using const references, thus no copying is performed. output_stream.find_replace_supress(); - if (m_spiral_vase && m_find_replace && m_pressure_equalizer) - tbb::parallel_pipeline(12, generator & spiral_vase & pressure_equalizer & cooling & find_replace & output); - else if (m_spiral_vase && m_find_replace) - tbb::parallel_pipeline(12, generator & spiral_vase & cooling & find_replace & output); - else if (m_spiral_vase && m_pressure_equalizer) - tbb::parallel_pipeline(12, generator & spiral_vase & pressure_equalizer & cooling & output); - else if (m_find_replace && m_pressure_equalizer) - tbb::parallel_pipeline(12, generator & pressure_equalizer & cooling & find_replace & output); - else if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_vase & cooling & output); - else if (m_find_replace) - tbb::parallel_pipeline(12, generator & cooling & find_replace & output); - else if (m_pressure_equalizer) - tbb::parallel_pipeline(12, generator & pressure_equalizer & cooling & output); - else - tbb::parallel_pipeline(12, generator & cooling & output); + tbb::parallel_pipeline(12, pipeline_to_layerresult & pipeline_to_string & output); output_stream.find_replace_enable(); } // Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. -void GCode::process_layers( +void GCodeGenerator::process_layers( const Print &print, const ToolOrdering &tool_ordering, ObjectsLayerToPrint layers_to_print, const size_t single_object_idx, + const GCode::SmoothPathCache &smooth_path_cache_global, GCodeOutputStream &output_stream) { - // The pipeline is variable: The vase mode filter is optional. size_t layer_to_print_idx = 0; - const auto generator = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx](tbb::flow_control& fc) -> LayerResult { + const GCode::SmoothPathCache::InterpolationParameters interpolation_params = interpolation_parameters(print.config()); + const auto smooth_path_interpolator = tbb::make_filter> (slic3r_tbb_filtermode::serial_in_order, + [this, &print, &layers_to_print, &layer_to_print_idx, interpolation_params](tbb::flow_control &fc) -> std::pair { if (layer_to_print_idx >= layers_to_print.size()) { if ((!m_pressure_equalizer && layer_to_print_idx == layers_to_print.size()) || (m_pressure_equalizer && layer_to_print_idx == (layers_to_print.size() + 1))) { fc.stop(); @@ -1627,15 +1405,32 @@ void GCode::process_layers( } else { // Pressure equalizer need insert empty input. Because it returns one layer back. // Insert NOP (no operation) layer; - ++layer_to_print_idx; - return LayerResult::make_nop_layer_result(); + return { ++ layer_to_print_idx, {} }; } } else { - ObjectLayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; print.throw_if_canceled(); - return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx); + size_t idx = layer_to_print_idx ++; + GCode::SmoothPathCache smooth_path_cache; + GCodeGenerator::smooth_path_interpolate(layers_to_print[idx], interpolation_params, smooth_path_cache); + return { idx, std::move(smooth_path_cache) }; } }); + const auto generator = tbb::make_filter, LayerResult>(slic3r_tbb_filtermode::serial_in_order, + [this, &print, &tool_ordering, &layers_to_print, &smooth_path_cache_global, single_object_idx](std::pair in) -> LayerResult { + size_t layer_to_print_idx = in.first; + if (layer_to_print_idx == layers_to_print.size()) { + // Pressure equalizer need insert empty input. Because it returns one layer back. + // Insert NOP (no operation) layer; + return LayerResult::make_nop_layer_result(); + } else { + ObjectLayerToPrint &layer = layers_to_print[layer_to_print_idx]; + print.throw_if_canceled(); + return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), + GCode::SmoothPathCaches{ smooth_path_cache_global, in.second }, + &layer == &layers_to_print.back(), nullptr, single_object_idx); + } + }); + // The pipeline is variable: The vase mode filter is optional. const auto spiral_vase = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [spiral_vase = this->m_spiral_vase.get()](LayerResult in)->LayerResult { if (in.nop_layer_result) @@ -1661,32 +1456,26 @@ void GCode::process_layers( [&output_stream](std::string s) { output_stream.write(s); } ); + tbb::filter pipeline_to_layerresult = smooth_path_interpolator & generator; + if (m_spiral_vase) + pipeline_to_layerresult = pipeline_to_layerresult & spiral_vase; + if (m_pressure_equalizer) + pipeline_to_layerresult = pipeline_to_layerresult & pressure_equalizer; + + tbb::filter pipeline_to_string = cooling; + if (m_find_replace) + pipeline_to_string = pipeline_to_string & find_replace; + // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline. // Handler is unregistered when the destructor is called. TBBLocalesSetter locales_setter; - // The pipeline elements are joined using const references, thus no copying is performed. output_stream.find_replace_supress(); - if (m_spiral_vase && m_find_replace && m_pressure_equalizer) - tbb::parallel_pipeline(12, generator & spiral_vase & pressure_equalizer & cooling & find_replace & output); - else if (m_spiral_vase && m_find_replace) - tbb::parallel_pipeline(12, generator & spiral_vase & cooling & find_replace & output); - else if (m_spiral_vase && m_pressure_equalizer) - tbb::parallel_pipeline(12, generator & spiral_vase & pressure_equalizer & cooling & output); - else if (m_find_replace && m_pressure_equalizer) - tbb::parallel_pipeline(12, generator & pressure_equalizer & cooling & find_replace & output); - else if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_vase & cooling & output); - else if (m_find_replace) - tbb::parallel_pipeline(12, generator & cooling & find_replace & output); - else if (m_pressure_equalizer) - tbb::parallel_pipeline(12, generator & pressure_equalizer & cooling & output); - else - tbb::parallel_pipeline(12, generator & cooling & output); + tbb::parallel_pipeline(12, pipeline_to_layerresult & pipeline_to_string & output); output_stream.find_replace_enable(); } -std::string GCode::placeholder_parser_process( +std::string GCodeGenerator::placeholder_parser_process( const std::string &name, const std::string &templ, unsigned int current_extruder_id, @@ -1800,7 +1589,7 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // 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(GCodeOutputStream &file, Print &print) +void GCodeGenerator::print_machine_envelope(GCodeOutputStream &file, Print &print) { const GCodeFlavor flavor = print.config().gcode_flavor.value; if ( (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware) @@ -1861,7 +1650,7 @@ void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print) // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. // M140 - Set Extruder Temperature // M190 - Set Extruder Temperature and Wait -void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +void GCodeGenerator::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { bool autoemit = print.config().autoemit_temperature_commands; // Initial bed temperature based on the first extruder. @@ -1883,7 +1672,7 @@ void GCode::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &p // M104 - Set Extruder Temperature // M109 - Set Extruder Temperature and Wait // RepRapFirmware: G10 Sxx -void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +void GCodeGenerator::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { bool autoemit = print.config().autoemit_temperature_commands; // Is the bed temperature set by the provided custom G-code? @@ -1921,7 +1710,7 @@ void GCode::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Pr } } -std::vector GCode::sort_print_object_instances( +std::vector GCodeGenerator::sort_print_object_instances( const std::vector &object_layers, // Ordering must be defined for normal (non-sequential print). const std::vector *ordering, @@ -1962,7 +1751,7 @@ namespace ProcessLayer { static std::string emit_custom_gcode_per_print_z( - GCode &gcodegen, + GCodeGenerator &gcodegen, const CustomGCode::Item *custom_gcode, unsigned int current_extruder_id, // ID of the first extruder printing this layer. @@ -2115,11 +1904,12 @@ namespace Skirt { // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths // and performing the extruder specific extrusions together. -LayerResult GCode::process_layer( +LayerResult GCodeGenerator::process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. const ObjectsLayerToPrint &layers, const LayerTools &layer_tools, + const GCode::SmoothPathCaches &smooth_path_caches, const bool last_layer, // Pairs of PrintObject index and its instance index. const std::vector *ordering, @@ -2263,10 +2053,6 @@ LayerResult GCode::process_layer( } } - for (const ObjectLayerToPrint &layer_to_print : layers) { - m_extrusion_quality_estimator.prepare_for_new_layer(layer_to_print.object_layer); - } - // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. for (unsigned int extruder_id : layer_tools.extruders) { @@ -2286,13 +2072,11 @@ LayerResult GCode::process_layer( double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); for (size_t i = loops.first; i < loops.second; ++i) { // Adjust flow according to this layer's layer height. - ExtrusionLoop loop = *dynamic_cast(print.skirt().entities[i]); - for (ExtrusionPath &path : loop.paths) { - path.height = layer_skirt_flow.height(); - path.mm3_per_mm = mm3_per_mm; - } //FIXME using the support_material_speed of the 1st object printed. - gcode += this->extrude_loop(loop, "skirt"sv, m_config.support_material_speed.value); + gcode += this->extrude_skirt(dynamic_cast(*print.skirt().entities[i]), + // Override of skirt extrusion parameters. extrude_skirt() will fill in the extrusion width. + ExtrusionFlow{ mm3_per_mm, 0., layer_skirt_flow.height() }, + smooth_path_caches.global(), "skirt"sv, m_config.support_material_speed.value); } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). @@ -2304,9 +2088,8 @@ LayerResult GCode::process_layer( if (! m_brim_done) { this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); - for (const ExtrusionEntity *ee : print.brim().entities) { - gcode += this->extrude_entity(*ee, "brim"sv, m_config.support_material_speed.value); - } + for (const ExtrusionEntity *ee : print.brim().entities) + gcode += this->extrude_entity({ *ee, false }, smooth_path_caches.global(), "brim"sv, m_config.support_material_speed.value); m_brim_done = true; m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. @@ -2323,7 +2106,7 @@ LayerResult GCode::process_layer( for (const InstanceToPrint &instance : instances_to_print) this->process_layer_single_object( gcode, extruder_id, instance, - layers[instance.object_layer_to_print_id], layer_tools, + layers[instance.object_layer_to_print_id], layer_tools, smooth_path_caches.layer_local(), is_anything_overridden, true /* print_wipe_extrusions */); if (gcode_size_old < gcode.size()) gcode+="; PURGING FINISHED\n"; @@ -2332,7 +2115,7 @@ LayerResult GCode::process_layer( for (const InstanceToPrint &instance : instances_to_print) this->process_layer_single_object( gcode, extruder_id, instance, - layers[instance.object_layer_to_print_id], layer_tools, + layers[instance.object_layer_to_print_id], layer_tools, smooth_path_caches.layer_local(), is_anything_overridden, false /* print_wipe_extrusions */); } @@ -2350,7 +2133,7 @@ static inline bool comment_is_perimeter(const std::string_view comment) { return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size(); } -void GCode::process_layer_single_object( +void GCodeGenerator::process_layer_single_object( // output std::string &gcode, // Index of the extruder currently active. @@ -2361,6 +2144,8 @@ void GCode::process_layer_single_object( const ObjectLayerToPrint &layer_to_print, // Container for extruder overrides (when wiping into object or infill). const LayerTools &layer_tools, + // Optional smooth path interpolating extrusion polylines. + const GCode::SmoothPathCache &smooth_path_cache, // Is any extrusion possibly marked as wiping extrusion? const bool is_anything_overridden, // Round 1 (wiping into object or infill) or round 2 (normal extrusions). @@ -2399,8 +2184,6 @@ void GCode::process_layer_single_object( const PrintObject &print_object = print_instance.print_object; const Print &print = *print_object.print(); - m_extrusion_quality_estimator.set_current_object(&print_object); - if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr) if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) { ExtrusionRole role = support_layer.support_fills.role(); @@ -2433,9 +2216,16 @@ void GCode::process_layer_single_object( init_layer_delayed(); m_layer = layer_to_print.support_layer; m_object_layer_over_raft = false; - gcode += this->extrude_support( - // support_extrusion_role is ExtrusionRole::SupportMaterial, ExtrusionRole::SupportMaterialInterface or ExtrusionRole::Mixed for all extrusion paths. - support_layer.support_fills.chained_path_from(m_last_pos, extrude_support ? (extrude_interface ? ExtrusionRole::Mixed : ExtrusionRole::SupportMaterial) : ExtrusionRole::SupportMaterialInterface)); + ExtrusionEntitiesPtr entities_cache; + const ExtrusionEntitiesPtr &entities = extrude_support && extrude_interface ? support_layer.support_fills.entities : entities_cache; + if (! extrude_support || ! extrude_interface) { + auto role = extrude_support ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface; + entities_cache.reserve(support_layer.support_fills.entities.size()); + for (ExtrusionEntity *ee : support_layer.support_fills.entities) + if (ee->role() == role) + entities_cache.emplace_back(ee); + } + gcode += this->extrude_support(chain_extrusion_references(entities), smooth_path_cache); } } @@ -2493,16 +2283,13 @@ void GCode::process_layer_single_object( if (! temp_fill_extrusions.empty()) { init_layer_delayed(); m_config.apply(region.config()); - //FIXME The source extrusions may be reversed, thus modifying the extrusions! Is it a problem? How about the initial G-code preview? - // Will parallel access of initial G-code preview to these extrusions while reordering them at backend cause issues? - chain_and_reorder_extrusion_entities(temp_fill_extrusions, &m_last_pos); const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; - for (const ExtrusionEntity *fill : temp_fill_extrusions) - if (auto *eec = dynamic_cast(fill); eec) { - for (const ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) - gcode += this->extrude_entity(*ee, extrusion_name); + for (const ExtrusionEntityReference &fill : chain_extrusion_references(temp_fill_extrusions, &m_last_pos)) + if (auto *eec = dynamic_cast(&fill.extrusion_entity()); eec) { + for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, &m_last_pos, fill.flipped())) + gcode += this->extrude_entity(ee, smooth_path_cache, extrusion_name); } else - gcode += this->extrude_entity(*fill, extrusion_name); + gcode += this->extrude_entity(fill, smooth_path_cache, extrusion_name); } }; @@ -2516,6 +2303,8 @@ void GCode::process_layer_single_object( const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); bool first = true; for (uint32_t perimeter_id : island.perimeters) { + // Extrusions inside islands are expected to be ordered already. + // Don't reorder them. assert(dynamic_cast(layerm.perimeters().entities[perimeter_id])); if (const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); shall_print_this_extrusion_collection(eec, region)) { @@ -2527,7 +2316,8 @@ void GCode::process_layer_single_object( m_config.apply(region.config()); } for (const ExtrusionEntity *ee : *eec) - gcode += this->extrude_entity(*ee, comment_perimeter, -1.); + // Don't reorder, don't flip. + gcode += this->extrude_entity({ *ee, false }, smooth_path_cache, comment_perimeter, -1.); } } }; @@ -2568,14 +2358,14 @@ void GCode::process_layer_single_object( gcode += std::string("; stop printing object ") + print_object.model_object()->name + " id:" + std::to_string(object_id) + " copy " + std::to_string(print_instance.instance_id) + "\n"; } -void GCode::apply_print_config(const PrintConfig &print_config) +void GCodeGenerator::apply_print_config(const PrintConfig &print_config) { m_writer.apply_print_config(print_config); m_config.apply(print_config); m_scaled_resolution = scaled(print_config.gcode_resolution.value); } -void GCode::append_full_config(const Print &print, std::string &str) +void GCodeGenerator::append_full_config(const Print &print, std::string &str) { const DynamicPrintConfig &cfg = print.full_print_config(); // Sorted list of config keys, which shall not be stored into the G-code. Initializer list. @@ -2596,32 +2386,22 @@ void GCode::append_full_config(const Print &print, std::string &str) str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; } -void GCode::set_extruders(const std::vector &extruder_ids) +void GCodeGenerator::set_extruders(const std::vector &extruder_ids) { m_writer.set_extruders(extruder_ids); - - // enable wipe path generation if any extruder has wipe enabled - m_wipe.enable = false; - for (auto id : extruder_ids) - if (m_config.wipe.get_at(id)) { - m_wipe.enable = true; - break; - } + m_wipe.init(this->config(), extruder_ids); } -void GCode::set_origin(const Vec2d &pointf) +void GCodeGenerator::set_origin(const Vec2d &pointf) { // if origin increases (goes towards right), last_pos decreases because it goes towards left - const Point translate( - scale_(m_origin(0) - pointf(0)), - scale_(m_origin(1) - pointf(1)) - ); - m_last_pos += translate; - m_wipe.path.translate(translate); + const auto offset = Point::new_scale(m_origin - pointf); + m_last_pos += offset; + m_wipe.offset_path(offset); m_origin = pointf; } -std::string GCode::preamble() +std::string GCodeGenerator::preamble() { std::string gcode = m_writer.preamble(); @@ -2634,8 +2414,8 @@ std::string GCode::preamble() return gcode; } -// called by GCode::process_layer() -std::string GCode::change_layer(coordf_t print_z) +// called by GCodeGenerator::process_layer() +std::string GCodeGenerator::change_layer(coordf_t print_z) { std::string gcode; if (m_layer_count > 0) @@ -2657,198 +2437,180 @@ std::string GCode::change_layer(coordf_t print_z) return gcode; } -std::string GCode::extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed) +#ifndef NDEBUG +static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bool loop) { - // get a copy; don't modify the orientation of the original loop object otherwise - // next copies (if any) would not detect the correct orientation - - // extrude all loops ccw - bool was_clockwise = loop.make_counter_clockwise(); - - // find the point of the loop that is closest to the current extruder position - // or randomize if requested - Point last_pos = this->last_pos(); + for (auto it = std::next(smooth_path.begin()); it != smooth_path.end(); ++ it) { + assert(it->path.size() >= 2); + assert(std::prev(it)->path.back().point == it->path.front().point); + } + assert(! loop || smooth_path.front().path.front().point == smooth_path.back().path.back().point); + return true; +} +#endif //NDEBUG +std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) +{ + // Extrude all loops CCW. + bool is_hole = loop_src.is_clockwise(); + Point seam_point = this->last_pos(); if (! m_config.spiral_vase && comment_is_perimeter(description)) { assert(m_layer != nullptr); - m_seam_placer.place_seam(m_layer, loop, m_config.external_perimeters_first, this->last_pos()); - } else - // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns, - // thus empty path segments will not be produced by G-code export. - loop.split_at(last_pos, false, scaled(0.0015)); - - for (auto it = std::next(loop.paths.begin()); it != loop.paths.end(); ++it) { - assert(it->polyline.points.size() >= 2); - assert(std::prev(it)->polyline.last_point() == it->polyline.first_point()); + seam_point = m_seam_placer.place_seam(m_layer, loop_src, m_config.external_perimeters_first, this->last_pos()); } - assert(loop.paths.front().first_point() == loop.paths.back().last_point()); + // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns, + // thus empty path segments will not be produced by G-code export. + GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam( + loop_src, is_hole, m_scaled_resolution, seam_point, scaled(0.0015)); - // clip the path to avoid the extruder to get exactly on the first point of the loop; + // Clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so - // we discard it in that case - double clip_length = m_enable_loop_clipping ? - scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER : - 0; + // we discard it in that case. + if (m_enable_loop_clipping) + clip_end(smooth_path, scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); - // get paths - ExtrusionPaths paths; - loop.clip_end(clip_length, &paths); - if (paths.empty()) return ""; + if (smooth_path.empty()) + return {}; - // apply the small perimeter speed - if (paths.front().role().is_perimeter() && loop.length() <= SMALL_PERIMETER_LENGTH && speed == -1) + assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping)); + + // Apply the small perimeter speed. + if (loop_src.paths.front().role().is_perimeter() && loop_src.length() <= SMALL_PERIMETER_LENGTH && speed == -1) speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); - // extrude along the path + // Extrude along the smooth path. std::string gcode; - for (ExtrusionPath &path : paths) { - path.simplify(m_scaled_resolution); - gcode += this->_extrude(path, description, speed); - } + for (const GCode::SmoothPathElement &el : smooth_path) + gcode += this->_extrude(el.path_attributes, el.path, description, speed); // reset acceleration - gcode += m_writer.set_print_acceleration((unsigned int)(m_config.default_acceleration.value + 0.5)); + gcode += m_writer.set_print_acceleration(fast_round_up(m_config.default_acceleration.value)); - if (m_wipe.enable) { - m_wipe.path = paths.front().polyline; - - for (auto it = std::next(paths.begin()); it != paths.end(); ++it) { - if (it->role().is_bridge()) - break; // Don't perform a wipe on bridges. - - assert(it->polyline.points.size() >= 2); - assert(m_wipe.path.points.back() == it->polyline.first_point()); - if (m_wipe.path.points.back() != it->polyline.first_point()) - break; // ExtrusionLoop is interrupted in some place. - - m_wipe.path.points.insert(m_wipe.path.points.end(), it->polyline.points.begin() + 1, it->polyline.points.end()); - } - } - - // make a little move inwards before leaving loop - if (paths.back().role().is_external_perimeter() && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { - // detect angle between last and first segment - // the side depends on the original winding order of the polygon (left for contours, right for holes) - //FIXME improve the algorithm in case the loop is tiny. - //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). - // Angle from the 2nd point to the last point. - double angle_inside = angle(paths.front().polyline.points[1] - paths.front().first_point(), - *(paths.back().polyline.points.end()-3) - paths.front().first_point()); - assert(angle_inside >= -M_PI && angle_inside <= M_PI); - // 3rd of this angle will be taken, thus make the angle monotonic before interpolation. - if (was_clockwise) { - if (angle_inside > 0) - angle_inside -= 2.0 * M_PI; - } else { - if (angle_inside < 0) - angle_inside += 2.0 * M_PI; - } - - // create the destination point along the first segment and rotate it - // we make sure we don't exceed the segment length because we don't know - // the rotation of the second segment so we might cross the object boundary - Vec2d p1 = paths.front().polyline.points.front().cast(); - Vec2d p2 = paths.front().polyline.points[1].cast(); - 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(); - // Rotate pt inside around the seam point. - pt.rotate(angle_inside / 3., paths.front().polyline.points.front()); - // generate the travel move - gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel"); + if (m_wipe.enabled()) { + // Wipe will hide the seam. + m_wipe.set_path(std::move(smooth_path), false); + } else if (loop_src.paths.back().role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) { + // Only wipe inside if the wipe along the perimeter is disabled. + // Make a little move inwards before leaving loop. + if (std::optional pt = wipe_hide_seam(smooth_path, is_hole, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) + // Generate the seam hiding travel move. + gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel"); } return gcode; } -std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed) +std::string GCodeGenerator::extrude_skirt( + const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override, + const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) { - for (auto it = std::next(multipath.paths.begin()); it != multipath.paths.end(); ++it) { + assert(loop_src.is_counter_clockwise()); + GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam( + loop_src, false, m_scaled_resolution, this->last_pos(), scaled(0.0015)); + + // Clip the path to avoid the extruder to get exactly on the first point of the loop; + // if polyline was shorter than the clipping distance we'd get a null polyline, so + // we discard it in that case. + if (m_enable_loop_clipping) + clip_end(smooth_path, scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); + + if (smooth_path.empty()) + return {}; + + assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping)); + + // Extrude along the smooth path. + std::string gcode; + for (GCode::SmoothPathElement &el : smooth_path) { + // Override extrusion parameters. + el.path_attributes.mm3_per_mm = extrusion_flow_override.mm3_per_mm; + el.path_attributes.height = extrusion_flow_override.height; + gcode += this->_extrude(el.path_attributes, el.path, description, speed); + } + + // reset acceleration + gcode += m_writer.set_print_acceleration(fast_round_up(m_config.default_acceleration.value)); + + if (m_wipe.enabled()) + // Wipe will hide the seam. + m_wipe.set_path(std::move(smooth_path), false); + + return gcode; +} + +std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) +{ +#ifndef NDEBUG + for (auto it = std::next(multipath.paths.begin()); it != multipath.paths.end(); ++ it) { assert(it->polyline.points.size() >= 2); assert(std::prev(it)->polyline.last_point() == it->polyline.first_point()); } +#endif // NDEBUG + GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit(multipath, reverse, m_scaled_resolution); + // extrude along the path std::string gcode; - for (ExtrusionPath path : multipath.paths) { - path.simplify(m_scaled_resolution); - gcode += this->_extrude(path, description, speed); - } - if (m_wipe.enable) { - m_wipe.path = std::move(multipath.paths.back().polyline); - m_wipe.path.reverse(); - - for (auto it = std::next(multipath.paths.rbegin()); it != multipath.paths.rend(); ++it) { - if (it->role().is_bridge()) - break; // Do not perform a wipe on bridges. - - assert(it->polyline.points.size() >= 2); - assert(m_wipe.path.points.back() == it->polyline.last_point()); - if (m_wipe.path.points.back() != it->polyline.last_point()) - break; // ExtrusionMultiPath is interrupted in some place. - - m_wipe.path.points.insert(m_wipe.path.points.end(), it->polyline.points.rbegin() + 1, it->polyline.points.rend()); - } - } + for (GCode::SmoothPathElement &el : smooth_path) + gcode += this->_extrude(el.path_attributes, el.path, description, speed); + m_wipe.set_path(std::move(smooth_path), true); // reset acceleration gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5)); return gcode; } -std::string GCode::extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed) +std::string GCodeGenerator::extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) { - if (const ExtrusionPath* path = dynamic_cast(&entity)) - return this->extrude_path(*path, description, speed); - else if (const ExtrusionMultiPath* multipath = dynamic_cast(&entity)) - return this->extrude_multi_path(*multipath, description, speed); - else if (const ExtrusionLoop* loop = dynamic_cast(&entity)) - return this->extrude_loop(*loop, description, speed); + if (const ExtrusionPath *path = dynamic_cast(&entity.extrusion_entity())) + return this->extrude_path(*path, entity.flipped(), smooth_path_cache, description, speed); + else if (const ExtrusionMultiPath *multipath = dynamic_cast(&entity.extrusion_entity())) + return this->extrude_multi_path(*multipath, entity.flipped(), smooth_path_cache, description, speed); + else if (const ExtrusionLoop *loop = dynamic_cast(&entity.extrusion_entity())) + return this->extrude_loop(*loop, smooth_path_cache, description, speed); else throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()"); - return ""; + return {}; } -std::string GCode::extrude_path(ExtrusionPath path, std::string_view description, double speed) +std::string GCodeGenerator::extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, std::string_view description, double speed) { - path.simplify(m_scaled_resolution); - std::string gcode = this->_extrude(path, description, speed); - if (m_wipe.enable) { - m_wipe.path = std::move(path.polyline); - m_wipe.path.reverse(); - } + Geometry::ArcWelder::Path smooth_path = smooth_path_cache.resolve_or_fit(path, reverse, m_scaled_resolution); + std::string gcode = this->_extrude(path.attributes(), smooth_path, description, speed); + Geometry::ArcWelder::reverse(smooth_path); + m_wipe.set_path(std::move(smooth_path)); // reset acceleration gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5)); return gcode; } -std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fills) +std::string GCodeGenerator::extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache) { static constexpr const auto support_label = "support material"sv; static constexpr const auto support_interface_label = "support material interface"sv; std::string gcode; - if (! support_fills.entities.empty()) { + if (! support_fills.empty()) { const double support_speed = m_config.support_material_speed.value; const double support_interface_speed = m_config.support_material_interface_speed.get_abs_value(support_speed); - for (const ExtrusionEntity *ee : support_fills.entities) { - ExtrusionRole role = ee->role(); + for (const ExtrusionEntityReference &eref : support_fills) { + ExtrusionRole role = eref.extrusion_entity().role(); assert(role == ExtrusionRole::SupportMaterial || role == ExtrusionRole::SupportMaterialInterface); const auto label = (role == ExtrusionRole::SupportMaterial) ? support_label : support_interface_label; const double speed = (role == ExtrusionRole::SupportMaterial) ? support_speed : support_interface_speed; - const ExtrusionPath *path = dynamic_cast(ee); + const ExtrusionPath *path = dynamic_cast(&eref.extrusion_entity()); if (path) - gcode += this->extrude_path(*path, label, speed); + gcode += this->extrude_path(*path, eref.flipped(), smooth_path_cache, label, speed); + else if (const ExtrusionMultiPath *multipath = dynamic_cast(&eref.extrusion_entity()); multipath) + gcode += this->extrude_multi_path(*multipath, eref.flipped(), smooth_path_cache, label, speed); else { - const ExtrusionMultiPath *multipath = dynamic_cast(ee); - if (multipath) - gcode += this->extrude_multi_path(*multipath, label, speed); - else { - const ExtrusionEntityCollection *eec = dynamic_cast(ee); - assert(eec); - if (eec) - gcode += this->extrude_support(*eec); + const ExtrusionEntityCollection *eec = dynamic_cast(&eref.extrusion_entity()); + assert(eec); + if (eec) { + //FIXME maybe order the support here? + ExtrusionEntityReferences refs; + refs.reserve(eec->entities.size()); + std::transform(eec->entities.begin(), eec->entities.end(), std::back_inserter(refs), + [flipped = eref.flipped()](const ExtrusionEntity *ee) { return ExtrusionEntityReference{ *ee, flipped }; }); + gcode += this->extrude_support(refs, smooth_path_cache); } } } @@ -2856,17 +2618,17 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill return gcode; } -bool GCode::GCodeOutputStream::is_error() const +bool GCodeGenerator::GCodeOutputStream::is_error() const { return ::ferror(this->f); } -void GCode::GCodeOutputStream::flush() +void GCodeGenerator::GCodeOutputStream::flush() { ::fflush(this->f); } -void GCode::GCodeOutputStream::close() +void GCodeGenerator::GCodeOutputStream::close() { if (this->f) { ::fclose(this->f); @@ -2874,7 +2636,7 @@ void GCode::GCodeOutputStream::close() } } -void GCode::GCodeOutputStream::write(const char *what) +void GCodeGenerator::GCodeOutputStream::write(const char *what) { if (what != nullptr) { //FIXME don't allocate a string, maybe process a batch of lines? @@ -2885,13 +2647,13 @@ void GCode::GCodeOutputStream::write(const char *what) } } -void GCode::GCodeOutputStream::writeln(const std::string &what) +void GCodeGenerator::GCodeOutputStream::writeln(const std::string &what) { if (! what.empty()) this->write(what.back() == '\n' ? what : what + '\n'); } -void GCode::GCodeOutputStream::write_format(const char* format, ...) +void GCodeGenerator::GCodeOutputStream::write_format(const char* format, ...) { va_list args; va_start(args, format); @@ -2923,18 +2685,22 @@ void GCode::GCodeOutputStream::write_format(const char* format, ...) va_end(args); } -std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view description, double speed) +std::string GCodeGenerator::_extrude( + const ExtrusionAttributes &path_attr, + const Geometry::ArcWelder::Path &path, + const std::string_view description, + double speed) { std::string gcode; - const std::string_view description_bridge = path.role().is_bridge() ? " (bridge)"sv : ""sv; + const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; // go to first point of extrusion path - if (!m_last_pos_defined || m_last_pos != path.first_point()) { + if (!m_last_pos_defined || m_last_pos != path.front().point) { std::string comment = "move to first "; comment += description; comment += description_bridge; comment += " point"; - gcode += this->travel_to(path.first_point(), path.role(), comment); + gcode += this->travel_to(path.front().point, path_attr.role, comment); } // compensate retraction @@ -2947,17 +2713,17 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de acceleration = m_config.first_layer_acceleration.value; } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) { acceleration = m_config.first_layer_acceleration_over_raft.value; - } else if (m_config.bridge_acceleration.value > 0 && path.role().is_bridge()) { + } else if (m_config.bridge_acceleration.value > 0 && path_attr.role.is_bridge()) { acceleration = m_config.bridge_acceleration.value; - } else if (m_config.top_solid_infill_acceleration > 0 && path.role() == ExtrusionRole::TopSolidInfill) { + } else if (m_config.top_solid_infill_acceleration > 0 && path_attr.role == ExtrusionRole::TopSolidInfill) { acceleration = m_config.top_solid_infill_acceleration.value; - } else if (m_config.solid_infill_acceleration > 0 && path.role().is_solid_infill()) { + } else if (m_config.solid_infill_acceleration > 0 && path_attr.role.is_solid_infill()) { acceleration = m_config.solid_infill_acceleration.value; - } else if (m_config.infill_acceleration.value > 0 && path.role().is_infill()) { + } else if (m_config.infill_acceleration.value > 0 && path_attr.role.is_infill()) { acceleration = m_config.infill_acceleration.value; - } else if (m_config.external_perimeter_acceleration > 0 && path.role().is_external_perimeter()) { + } else if (m_config.external_perimeter_acceleration > 0 && path_attr.role.is_external_perimeter()) { acceleration = m_config.external_perimeter_acceleration.value; - } else if (m_config.perimeter_acceleration.value > 0 && path.role().is_perimeter()) { + } else if (m_config.perimeter_acceleration.value > 0 && path_attr.role.is_perimeter()) { acceleration = m_config.perimeter_acceleration.value; } else { acceleration = m_config.default_acceleration.value; @@ -2966,36 +2732,36 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de } // calculate extrusion length per distance unit - double e_per_mm = m_writer.extruder()->e_per_mm3() * path.mm3_per_mm; + double e_per_mm = m_writer.extruder()->e_per_mm3() * path_attr.mm3_per_mm; if (m_writer.extrusion_axis().empty()) // gcfNoExtrusion e_per_mm = 0; // set speed if (speed == -1) { - if (path.role() == ExtrusionRole::Perimeter) { + if (path_attr.role == ExtrusionRole::Perimeter) { speed = m_config.get_abs_value("perimeter_speed"); - } else if (path.role() == ExtrusionRole::ExternalPerimeter) { + } else if (path_attr.role == ExtrusionRole::ExternalPerimeter) { speed = m_config.get_abs_value("external_perimeter_speed"); - } else if (path.role().is_bridge()) { - assert(path.role().is_perimeter() || path.role() == ExtrusionRole::BridgeInfill); + } else if (path_attr.role.is_bridge()) { + assert(path_attr.role.is_perimeter() || path_attr.role == ExtrusionRole::BridgeInfill); speed = m_config.get_abs_value("bridge_speed"); - } else if (path.role() == ExtrusionRole::InternalInfill) { + } else if (path_attr.role == ExtrusionRole::InternalInfill) { speed = m_config.get_abs_value("infill_speed"); - } else if (path.role() == ExtrusionRole::SolidInfill) { + } else if (path_attr.role == ExtrusionRole::SolidInfill) { speed = m_config.get_abs_value("solid_infill_speed"); - } else if (path.role() == ExtrusionRole::TopSolidInfill) { + } else if (path_attr.role == ExtrusionRole::TopSolidInfill) { speed = m_config.get_abs_value("top_solid_infill_speed"); - } else if (path.role() == ExtrusionRole::Ironing) { + } else if (path_attr.role == ExtrusionRole::Ironing) { speed = m_config.get_abs_value("ironing_speed"); - } else if (path.role() == ExtrusionRole::GapFill) { + } else if (path_attr.role == ExtrusionRole::GapFill) { speed = m_config.get_abs_value("gap_fill_speed"); } else { throw Slic3r::InvalidArgument("Invalid speed"); } } if (m_volumetric_speed != 0. && speed == 0) - speed = m_volumetric_speed / path.mm3_per_mm; + speed = m_volumetric_speed / path_attr.mm3_per_mm; if (this->on_first_layer()) speed = m_config.get_abs_value("first_layer_speed", speed); else if (this->object_layer_over_raft()) @@ -3004,54 +2770,36 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) speed = std::min( speed, - m_config.max_volumetric_speed.value / path.mm3_per_mm + m_config.max_volumetric_speed.value / path_attr.mm3_per_mm ); } if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) speed = std::min( speed, - EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm + EXTRUDER_CONFIG(filament_max_volumetric_speed) / path_attr.mm3_per_mm ); } - bool variable_speed_or_fan_speed = false; - std::vector new_points{}; - if ((this->m_config.enable_dynamic_overhang_speeds || this->config().enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())) && - !this->on_first_layer() && path.role().is_perimeter()) { - std::vector> overhangs_with_speeds = {{100, ConfigOptionFloatOrPercent{speed, false}}}; - if (this->m_config.enable_dynamic_overhang_speeds) { - overhangs_with_speeds = {{0, m_config.overhang_speed_0}, - {25, m_config.overhang_speed_1}, - {50, m_config.overhang_speed_2}, - {75, m_config.overhang_speed_3}, - {100, ConfigOptionFloatOrPercent{speed, false}}}; - } - - std::vector> overhang_w_fan_speeds = {{100, ConfigOptionInts{0}}}; - if (this->m_config.enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())) { - overhang_w_fan_speeds = {{0, m_config.overhang_fan_speed_0}, - {25, m_config.overhang_fan_speed_1}, - {50, m_config.overhang_fan_speed_2}, - {75, m_config.overhang_fan_speed_3}, - {100, ConfigOptionInts{0}}}; - } - + std::pair dynamic_speed_and_fan_speed{-1, -1}; + if (path_attr.overhang_attributes.has_value()) { double external_perim_reference_speed = m_config.get_abs_value("external_perimeter_speed"); if (external_perim_reference_speed == 0) - external_perim_reference_speed = m_volumetric_speed / path.mm3_per_mm; + external_perim_reference_speed = m_volumetric_speed / path_attr.mm3_per_mm; if (m_config.max_volumetric_speed.value > 0) - external_perim_reference_speed = std::min(external_perim_reference_speed, m_config.max_volumetric_speed.value / path.mm3_per_mm); + external_perim_reference_speed = std::min(external_perim_reference_speed, + m_config.max_volumetric_speed.value / path_attr.mm3_per_mm); if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { external_perim_reference_speed = std::min(external_perim_reference_speed, - EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm); + EXTRUDER_CONFIG(filament_max_volumetric_speed) / path_attr.mm3_per_mm); } - new_points = m_extrusion_quality_estimator.estimate_speed_from_extrusion_quality(path, overhangs_with_speeds, overhang_w_fan_speeds, - m_writer.extruder()->id(), external_perim_reference_speed, - speed); - variable_speed_or_fan_speed = std::any_of(new_points.begin(), new_points.end(), - [speed](const ProcessedPoint &p) { return p.speed != speed || p.fan_speed != 0; }); + dynamic_speed_and_fan_speed = ExtrusionProcessor::calculate_overhang_speed(path_attr, this->m_config, m_writer.extruder()->id(), + external_perim_reference_speed, speed); + } + + if (dynamic_speed_and_fan_speed.first > -1) { + speed = dynamic_speed_and_fan_speed.first; } double F = speed * 60; // convert mm/sec to mm/min @@ -3059,7 +2807,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de // extrude arc or line if (m_enable_extrusion_role_markers) { - if (GCodeExtrusionRole role = extrusion_role_to_gcode_extrusion_role(path.role()); role != m_last_extrusion_role) + if (GCodeExtrusionRole role = extrusion_role_to_gcode_extrusion_role(path_attr.role); role != m_last_extrusion_role) { m_last_extrusion_role = role; if (m_enable_extrusion_role_markers) @@ -3077,29 +2825,29 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de bool last_was_wipe_tower = (m_last_processor_extrusion_role == GCodeExtrusionRole::WipeTower); assert(is_decimal_separator_point()); - if (GCodeExtrusionRole role = extrusion_role_to_gcode_extrusion_role(path.role()); role != m_last_processor_extrusion_role) { + if (GCodeExtrusionRole role = extrusion_role_to_gcode_extrusion_role(path_attr.role); role != m_last_processor_extrusion_role) { m_last_processor_extrusion_role = role; char buf[64]; sprintf(buf, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), gcode_extrusion_role_to_string(m_last_processor_extrusion_role).c_str()); gcode += buf; } - if (last_was_wipe_tower || m_last_width != path.width) { - m_last_width = path.width; + if (last_was_wipe_tower || m_last_width != path_attr.width) { + m_last_width = path_attr.width; gcode += std::string(";") + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width) + float_to_string_decimal_point(m_last_width) + "\n"; } #if ENABLE_GCODE_VIEWER_DATA_CHECKING - if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { - m_last_mm3_per_mm = path.mm3_per_mm; + if (last_was_wipe_tower || (m_last_mm3_per_mm != path_attr.mm3_per_mm)) { + m_last_mm3_per_mm = path_attr.mm3_per_mm; gcode += std::string(";") + GCodeProcessor::Mm3_Per_Mm_Tag + float_to_string_decimal_point(m_last_mm3_per_mm) + "\n"; } #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - if (last_was_wipe_tower || std::abs(m_last_height - path.height) > EPSILON) { - m_last_height = path.height; + if (last_was_wipe_tower || std::abs(m_last_height - path_attr.height) > EPSILON) { + m_last_height = path_attr.height; gcode += std::string(";") + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) + float_to_string_decimal_point(m_last_height) + "\n"; @@ -3107,72 +2855,77 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de std::string cooling_marker_setspeed_comments; if (m_enable_cooling_markers) { - if (path.role().is_bridge()) + if (path_attr.role.is_bridge()) gcode += ";_BRIDGE_FAN_START\n"; else cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED"; - if (path.role() == ExtrusionRole::ExternalPerimeter) + if (path_attr.role == ExtrusionRole::ExternalPerimeter) cooling_marker_setspeed_comments += ";_EXTERNAL_PERIMETER"; } - if (!variable_speed_or_fan_speed) { - // F is mm per minute. - gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments); - double path_length = 0.; - std::string comment; - if (m_config.gcode_comments) { - comment = description; - comment += description_bridge; - } - Vec2d prev = this->point_to_gcode_quantized(path.polyline.points.front()); - auto it = path.polyline.points.begin(); - auto end = path.polyline.points.end(); - for (++ it; it != end; ++ it) { - Vec2d p = this->point_to_gcode_quantized(*it); - const double line_length = (p - prev).norm(); + // F is mm per minute. + gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments); + if (dynamic_speed_and_fan_speed.second >= 0) + gcode += ";_SET_FAN_SPEED" + std::to_string(int(dynamic_speed_and_fan_speed.second)) + "\n"; + double path_length = 0.; + std::string comment; + if (m_config.gcode_comments) { + comment = description; + comment += description_bridge; + } + Vec2d prev = this->point_to_gcode_quantized(path.front().point); + auto it = path.begin(); + auto end = path.end(); + const bool emit_radius = m_config.arc_fitting == ArcFittingType::EmitRadius; + for (++ it; it != end; ++ it) { + Vec2d p = this->point_to_gcode_quantized(it->point); + if (it->radius == 0) { + // Extrude line segment. + if (const double line_length = (p - prev).norm(); line_length > 0) { + path_length += line_length; + gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment); + } + } else { + // Extrude an arc. + assert(m_config.arc_fitting == ArcFittingType::EmitCenter || + m_config.arc_fitting == ArcFittingType::EmitRadius); + double radius = unscaled(it->radius); + if (emit_radius) + // Only quantize radius if emitting it directly into G-code. Otherwise use the exact radius for calculating the IJ values. + radius = GCodeFormatter::quantize_xyzf(radius); + Vec2d ij; + if (! emit_radius) { + // Calculate quantized IJ circle center offset. + Vec2d center_raw = Geometry::ArcWelder::arc_center(prev.cast(), p.cast(), double(radius), it->ccw()) - prev; + ij = GCodeFormatter::quantize(center_raw); + } + double angle = Geometry::ArcWelder::arc_angle(prev.cast(), p.cast(), double(radius)); + assert(angle > 0); + const double line_length = angle * std::abs(radius); path_length += line_length; - gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment); - prev = p; + const double dE = e_per_mm * line_length; + assert(dE > 0); + gcode += emit_radius ? + m_writer.extrude_to_xy_G2G3R(p, radius, it->ccw(), dE, comment) : + m_writer.extrude_to_xy_G2G3IJ(p, ij, it->ccw(), dE, comment); } - } else { - std::string marked_comment; - if (m_config.gcode_comments) { - marked_comment = description; - marked_comment += description_bridge; - } - double last_set_speed = new_points[0].speed * 60.0; - double last_set_fan_speed = new_points[0].fan_speed; - gcode += m_writer.set_speed(last_set_speed, "", cooling_marker_setspeed_comments); - gcode += "\n;_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n"; - Vec2d prev = this->point_to_gcode_quantized(new_points[0].p); - for (size_t i = 1; i < new_points.size(); i++) { - const ProcessedPoint &processed_point = new_points[i]; - Vec2d p = this->point_to_gcode_quantized(processed_point.p); - const double line_length = (p - prev).norm(); - gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, marked_comment); - prev = p; - double new_speed = processed_point.speed * 60.0; - if (last_set_speed != new_speed) { - gcode += m_writer.set_speed(new_speed, "", cooling_marker_setspeed_comments); - last_set_speed = new_speed; - } - if (last_set_fan_speed != processed_point.fan_speed) { - last_set_fan_speed = processed_point.fan_speed; - gcode += "\n;_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n"; - } - } - gcode += "\n;_RESET_FAN_SPEED\n"; + prev = p; } if (m_enable_cooling_markers) - gcode += path.role().is_bridge() ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n"; + gcode += path_attr.role.is_bridge() ? ";_BRIDGE_FAN_END" : ";_EXTRUDE_END"; - this->set_last_pos(path.last_point()); + if (dynamic_speed_and_fan_speed.second >= 0) + gcode += ";_RESET_FAN_SPEED"; + + gcode += "\n"; + + this->set_last_pos(path.back().point); return gcode; } // This method accepts &point in print coordinates. -std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment) +std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, std::string comment) { /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by @@ -3254,7 +3007,7 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string return gcode; } -bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) +bool GCodeGenerator::needs_retraction(const Polyline &travel, ExtrusionRole role) { if (travel.length() < scale_(EXTRUDER_CONFIG(retract_before_travel))) { // skip retraction if the move is shorter than the configured threshold @@ -3293,7 +3046,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) return true; } -std::string GCode::retract(bool toolchange) +std::string GCodeGenerator::retract(bool toolchange) { std::string gcode; @@ -3319,7 +3072,7 @@ std::string GCode::retract(bool toolchange) return gcode; } -std::string GCode::set_extruder(unsigned int extruder_id, double print_z) +std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_z) { if (!m_writer.need_toolchange(extruder_id)) return ""; @@ -3421,20 +3174,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) } // convert a model-space scaled point into G-code coordinates -Vec2d GCode::point_to_gcode(const Point &point) const -{ - Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset); - return unscaled(point) + m_origin - extruder_offset; -} - -Vec2d GCode::point_to_gcode_quantized(const Point &point) const -{ - Vec2d p = this->point_to_gcode(point); - return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) }; -} - -// convert a model-space scaled point into G-code coordinates -Point GCode::gcode_to_point(const Vec2d &point) const +Point GCodeGenerator::gcode_to_point(const Vec2d &point) const { Vec2d pt = point - m_origin; if (const Extruder *extruder = m_writer.extruder(); extruder) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 346ececba7..1c92874922 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -5,18 +5,22 @@ #include "JumpPointSearch.hpp" #include "libslic3r.h" #include "ExPolygon.hpp" -#include "GCodeWriter.hpp" #include "Layer.hpp" #include "Point.hpp" #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" +#include "Geometry/ArcWelder.hpp" #include "GCode/AvoidCrossingPerimeters.hpp" #include "GCode/CoolingBuffer.hpp" #include "GCode/FindReplace.hpp" +#include "GCode/GCodeWriter.hpp" +#include "GCode/PressureEqualizer.hpp" #include "GCode/RetractWhenCrossingPerimeters.hpp" +#include "GCode/SmoothPath.hpp" #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" -#include "GCode/WipeTower.hpp" +#include "GCode/Wipe.hpp" +#include "GCode/WipeTowerIntegration.hpp" #include "GCode/SeamPlacer.hpp" #include "GCode/GCodeProcessor.hpp" #include "EdgeGrid.hpp" @@ -26,12 +30,10 @@ #include #include -#include "GCode/PressureEqualizer.hpp" - namespace Slic3r { // Forward declarations. -class GCode; +class GCodeGenerator; namespace { struct Item; } struct PrintInstance; @@ -41,71 +43,11 @@ public: bool enable; OozePrevention() : enable(false) {} - std::string pre_toolchange(GCode &gcodegen); - std::string post_toolchange(GCode &gcodegen); + std::string pre_toolchange(GCodeGenerator &gcodegen); + std::string post_toolchange(GCodeGenerator &gcodegen); private: - int _get_temp(const GCode &gcodegen) const; -}; - -class Wipe { -public: - bool enable; - Polyline path; - - Wipe() : enable(false) {} - bool has_path() const { return ! this->path.empty(); } - void reset_path() { this->path.clear(); } - std::string wipe(GCode &gcodegen, bool toolchange); -}; - -class WipeTowerIntegration { -public: - WipeTowerIntegration( - const PrintConfig &print_config, - const std::vector &priming, - const std::vector> &tool_changes, - const WipeTower::ToolChangeResult &final_purge) : - m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f), - m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)), - m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)), - m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)), - m_extruder_offsets(print_config.extruder_offset.values), - m_priming(priming), - m_tool_changes(tool_changes), - m_final_purge(final_purge), - m_layer_idx(-1), - m_tool_change_idx(0) - {} - - std::string prime(GCode &gcodegen); - void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } - std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); - std::string finalize(GCode &gcodegen); - std::vector used_filament_length() const; - -private: - WipeTowerIntegration& operator=(const WipeTowerIntegration&); - std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const; - - // Postprocesses gcode: rotates and moves G1 extrusions and returns result - std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& 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 Vec2f m_wipe_tower_pos; - const float m_wipe_tower_rotation; - const std::vector m_extruder_offsets; - - // Reference to cached values at the Printer class. - const std::vector &m_priming; - const std::vector> &m_tool_changes; - const WipeTower::ToolChangeResult &m_final_purge; - // Current layer index. - int m_layer_idx; - int m_tool_change_idx; - double m_last_wipe_tower_print_z = 0.f; + int _get_temp(const GCodeGenerator &gcodegen) const; }; class ColorPrintColors @@ -129,9 +71,9 @@ struct LayerResult { static LayerResult make_nop_layer_result() { return {"", std::numeric_limits::max(), false, false, true}; } }; -class GCode { +class GCodeGenerator { public: - GCode() : + GCodeGenerator() : m_origin(Vec2d::Zero()), m_enable_loop_clipping(true), m_enable_cooling_markers(false), @@ -153,7 +95,7 @@ public: m_silent_time_estimator_enabled(false), m_last_obj_copy(nullptr, Point(std::numeric_limits::max(), std::numeric_limits::max())) {} - ~GCode() = default; + ~GCodeGenerator() = default; // throws std::runtime_exception on error, // throws CanceledException through print->throw_if_canceled(). @@ -165,9 +107,19 @@ public: 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; } // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset. - Vec2d point_to_gcode(const Point &point) const; + template + Vec2d point_to_gcode(const Eigen::MatrixBase &point) const { + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode(): first parameter is not a 2D vector"); + return Vec2d(unscaled(point.x()), unscaled(point.y())) + m_origin + - m_config.extruder_offset.get_at(m_writer.extruder()->id()); + } // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution. - Vec2d point_to_gcode_quantized(const Point &point) const; + template + Vec2d point_to_gcode_quantized(const Eigen::MatrixBase &point) const { + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode_quantized(): first parameter is not a 2D vector"); + Vec2d p = this->point_to_gcode(point); + return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) }; + } Point gcode_to_point(const Vec2d &point) const; const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } @@ -250,6 +202,7 @@ private: // Set of object & print layers of the same PrintObject and with the same print_z. const ObjectsLayerToPrint &layers, const LayerTools &layer_tools, + const GCode::SmoothPathCaches &smooth_path_caches, const bool last_layer, // Pairs of PrintObject index and its instance index. const std::vector *ordering, @@ -264,6 +217,7 @@ private: const ToolOrdering &tool_ordering, const std::vector &print_object_instances_ordering, const std::vector> &layers_to_print, + const GCode::SmoothPathCache &smooth_path_cache_global, GCodeOutputStream &output_stream); // Process all layers of a single object instance (sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser @@ -273,6 +227,7 @@ private: const ToolOrdering &tool_ordering, ObjectsLayerToPrint layers_to_print, const size_t single_object_idx, + const GCode::SmoothPathCache &smooth_path_cache_global, GCodeOutputStream &output_stream); void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } @@ -280,10 +235,13 @@ private: void set_extruders(const std::vector &extruder_ids); std::string preamble(); std::string change_layer(coordf_t print_z); - std::string extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed = -1.); - std::string extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed = -1.); - std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.); - std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.); + std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); + std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); + std::string extrude_skirt(const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override, + const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed); + + std::string extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); + std::string extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); struct InstanceToPrint { @@ -317,12 +275,14 @@ private: const ObjectLayerToPrint &layer_to_print, // Container for extruder overrides (when wiping into object or infill). const LayerTools &layer_tools, + // Optional smooth path interpolating extrusion polylines. + const GCode::SmoothPathCache &smooth_path_cache, // Is any extrusion possibly marked as wiping extrusion? const bool is_anything_overridden, // Round 1 (wiping into object or infill) or round 2 (normal extrusions). const bool print_wipe_extrusions); - std::string extrude_support(const ExtrusionEntityCollection &support_fills); + std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); @@ -333,8 +293,6 @@ private: // Cache for custom seam enforcers/blockers for each layer. SeamPlacer m_seam_placer; - ExtrusionQualityEstimator m_extrusion_quality_estimator; - /* Origin of print coordinates expressed in unscaled G-code coordinates. This affects the input arguments supplied to the extrude*() and travel_to() methods. */ @@ -375,7 +333,7 @@ private: } m_placeholder_parser_integration; OozePrevention m_ooze_prevention; - Wipe m_wipe; + GCode::Wipe m_wipe; AvoidCrossingPerimeters m_avoid_crossing_perimeters; JPSPathFinder m_avoid_crossing_curled_overhangs; RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters; @@ -418,7 +376,7 @@ private: std::unique_ptr m_spiral_vase; std::unique_ptr m_find_replace; std::unique_ptr m_pressure_equalizer; - std::unique_ptr m_wipe_tower; + std::unique_ptr m_wipe_tower; // Heights (print_z) at which the skirt has already been extruded. std::vector m_skirt_done; @@ -434,7 +392,8 @@ private: // Processor GCodeProcessor m_processor; - std::string _extrude(const ExtrusionPath &path, const std::string_view description, double speed = -1); + std::string _extrude( + const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, const std::string_view description, double speed = -1); void print_machine_envelope(GCodeOutputStream &file, Print &print); void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); @@ -443,8 +402,12 @@ private: // To control print speed of 1st object layer over raft interface. bool object_layer_over_raft() const { return m_object_layer_over_raft; } - friend class Wipe; - friend class WipeTowerIntegration; + // Fill in cache of smooth paths for perimeters, fills and supports of the given object layers. + // Based on params, the paths are either decimated to sparser polylines, or interpolated with circular arches. + static void smooth_path_interpolate(const ObjectLayerToPrint &layers, const GCode::SmoothPathCache::InterpolationParameters ¶ms, GCode::SmoothPathCache &out); + + friend class GCode::Wipe; + friend class GCode::WipeTowerIntegration; friend class PressureEqualizer; }; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index c866e13e4f..c7dd09b785 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -730,7 +730,7 @@ static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vec return false; } -static bool need_wipe(const GCode &gcodegen, +static bool need_wipe(const GCodeGenerator &gcodegen, const ExPolygons &lslices_offset, const std::vector &lslices_offset_bboxes, const EdgeGrid::Grid &grid_lslices_offset, @@ -1167,7 +1167,7 @@ static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons } // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. -Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) +Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled) { // 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. @@ -1470,7 +1470,7 @@ static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary } // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. -Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) +Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled) { // 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. diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index eb81c7972e..5e6d83f570 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -8,7 +8,7 @@ namespace Slic3r { // Forward declarations. -class GCode; +class GCodeGenerator; class Layer; class Point; @@ -25,13 +25,13 @@ public: void init_layer(const Layer &layer); - Polyline travel_to(const GCode& gcodegen, const Point& point) + Polyline travel_to(const GCodeGenerator &gcodegen, const Point& point) { bool could_be_wipe_disabled; return this->travel_to(gcodegen, point, &could_be_wipe_disabled); } - Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled); + Polyline travel_to(const GCodeGenerator &gcodegen, const Point& point, bool* could_be_wipe_disabled); struct Boundary { // Collection of boundaries used for detection of crossing perimeters for travels diff --git a/src/libslic3r/GCode/ConflictChecker.hpp b/src/libslic3r/GCode/ConflictChecker.hpp index 344018f3d3..49ec3a4b13 100644 --- a/src/libslic3r/GCode/ConflictChecker.hpp +++ b/src/libslic3r/GCode/ConflictChecker.hpp @@ -43,7 +43,7 @@ public: void raise() { if (valid()) { - if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height; } + if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height(); } _curPileIdx++; } } diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 41655a754d..1c3be55ca5 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -19,7 +19,7 @@ namespace Slic3r { -CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) +CoolingBuffer::CoolingBuffer(GCodeGenerator &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) { this->reset(gcodegen.writer().get_position()); @@ -33,36 +33,45 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_t void CoolingBuffer::reset(const Vec3d &position) { - m_current_pos.assign(5, 0.f); - m_current_pos[0] = float(position.x()); - m_current_pos[1] = float(position.y()); - m_current_pos[2] = float(position.z()); - m_current_pos[4] = float(m_config.travel_speed.value); + assert(m_current_pos.size() == 5); + m_current_pos[AxisIdx::X] = float(position.x()); + m_current_pos[AxisIdx::Y] = float(position.y()); + m_current_pos[AxisIdx::Z] = float(position.z()); + m_current_pos[AxisIdx::E] = 0.f; + m_current_pos[AxisIdx::F] = float(m_config.travel_speed.value); m_fan_speed = -1; } struct CoolingLine { - enum Type { + enum Type : uint32_t { 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, + // G2 or G3: Arc interpolation + TYPE_G2G3 = 1 << 6, + TYPE_ADJUSTABLE = 1 << 7, + TYPE_EXTERNAL_PERIMETER = 1 << 8, + // Arc interpolation, counter-clockwise. + TYPE_G2G3_CCW = 1 << 9, + // Arc interpolation, arc defined by IJ (offset of arc center from its start position). + TYPE_G2G3_IJ = 1 << 10, + // Arc interpolation, arc defined by R (arc radius, positive - smaller, negative - larger). + TYPE_G2G3_R = 1 << 11, // The line sets a feedrate. - TYPE_HAS_F = 1 << 8, - TYPE_WIPE = 1 << 9, - TYPE_G4 = 1 << 10, - TYPE_G92 = 1 << 11, + TYPE_HAS_F = 1 << 12, + TYPE_WIPE = 1 << 13, + TYPE_G4 = 1 << 14, + TYPE_G92 = 1 << 15, // Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block // cannot have its speed adjusted. This should not happen (sic!). - TYPE_ADJUSTABLE_EMPTY = 1 << 12, + TYPE_ADJUSTABLE_EMPTY = 1 << 16, // Custom fan speed (introduced for overhang fan speed) - TYPE_SET_FAN_SPEED = 1 << 13, - TYPE_RESET_FAN_SPEED = 1 << 14, + TYPE_SET_FAN_SPEED = 1 << 17, + TYPE_RESET_FAN_SPEED = 1 << 18, }; CoolingLine(unsigned int type, size_t line_start, size_t line_end) : @@ -324,7 +333,7 @@ std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, b // Parse the layer G-code for the moves, which could be adjusted. // Return the list of parsed lines, bucketed by an extruder. -std::vector CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const +std::vector CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::array ¤t_pos) const { std::vector per_extruder_adjustments(m_extruder_ids.size()); std::vector map_extruder_to_per_extruder_adjustment(m_num_extruders, 0); @@ -347,7 +356,7 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: // for a sequence of extrusion moves. size_t active_speed_modifier = size_t(-1); - std::vector new_pos; + std::array new_pos; for (; *line_start != 0; line_start = line_end) { while (*line_end != '\n' && *line_end != 0) @@ -362,12 +371,20 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: line.type = CoolingLine::TYPE_G0; else if (boost::starts_with(sline, "G1 ")) line.type = CoolingLine::TYPE_G1; + else if (boost::starts_with(sline, "G2 ")) + // Arc, clockwise. + line.type = CoolingLine::TYPE_G2G3; + else if (boost::starts_with(sline, "G3 ")) + // Arc, counter-clockwise. + line.type = CoolingLine::TYPE_G2G3 | CoolingLine::TYPE_G2G3_CCW; else if (boost::starts_with(sline, "G92 ")) line.type = CoolingLine::TYPE_G92; if (line.type) { - // G0, G1 or G92 + // G0, G1, G2, G3 or G92 + // Initialize current_pos from new_pos, set IJKR to zero. + std::fill(std::copy(std::begin(current_pos), std::end(current_pos), std::begin(new_pos)), + std::end(new_pos), 0.f); // Parse the G-code line. - new_pos = current_pos; for (auto c = sline.begin() + 3;;) { // Skip whitespaces. for (; c != sline.end() && (*c == ' ' || *c == '\t'); ++ c); @@ -376,21 +393,31 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: // Parse the axis. size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : - (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); + (*c == extrusion_axis) ? AxisIdx::E : (*c == 'F') ? AxisIdx::F : + (*c >= 'I' && *c <= 'K') ? int(AxisIdx::I) + (*c - 'I') : + (*c == 'R') ? AxisIdx::R : size_t(-1); if (axis != size_t(-1)) { //auto [pend, ec] = fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]); - if (axis == 4) { + if (axis == AxisIdx::F) { // Convert mm/min to mm/sec. - new_pos[4] /= 60.f; + new_pos[AxisIdx::F] /= 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; - } + } else if (axis >= AxisIdx::I && axis <= AxisIdx::J) + line.type |= CoolingLine::TYPE_G2G3_IJ; + else if (axis == AxisIdx::R) + line.type |= CoolingLine::TYPE_G2G3_R; } // Skip this word. for (; c != sline.end() && *c != ' ' && *c != '\t'; ++ c); } + // If G2 or G3, then either center of the arc or radius has to be defined. + assert(! (line.type & CoolingLine::TYPE_G2G3) || + (line.type & (CoolingLine::TYPE_G2G3_IJ | CoolingLine::TYPE_G2G3_R))); + // Arc is defined either by IJ or by R, not by both. + assert(! ((line.type & CoolingLine::TYPE_G2G3_IJ) && (line.type & CoolingLine::TYPE_G2G3_R))); bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); bool wipe = boost::contains(sline, ";_WIPE"); if (external_perimeter) @@ -402,23 +429,41 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: active_speed_modifier = adjustment->lines.size(); } if ((line.type & CoolingLine::TYPE_G92) == 0) { - // G0 or G1. Calculate the duration. + // G0, G1, G2, G3. Calculate the duration. + assert((line.type & CoolingLine::TYPE_G0) != 0 + (line.type & CoolingLine::TYPE_G1) != 0 + (line.type & CoolingLine::TYPE_G2G3) != 0 == 1); if (m_config.use_relative_e_distances.value) // Reset extruder accumulator. - current_pos[3] = 0.f; + current_pos[AxisIdx::E] = 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]; + float dxy2; + if (line.type & CoolingLine::TYPE_G2G3) { + // Measure arc length. + if (line.type & CoolingLine::TYPE_G2G3_IJ) { + dxy2 = sqr(Geometry::ArcWelder::arc_length( + Vec2d(current_pos[AxisIdx::X], current_pos[AxisIdx::Y]), + Vec2d(new_pos[AxisIdx::X], new_pos[AxisIdx::Y]), + Vec2d(current_pos[AxisIdx::X] + new_pos[AxisIdx::I], current_pos[AxisIdx::Y] + new_pos[AxisIdx::J]), + line.type & CoolingLine::TYPE_G2G3_CCW)); + } else if (line.type & CoolingLine::TYPE_G2G3_R) { + dxy2 = sqr(Geometry::ArcWelder::arc_length( + Vec2d(current_pos[AxisIdx::X], current_pos[AxisIdx::Y]), + Vec2d(new_pos[AxisIdx::X], new_pos[AxisIdx::Y]), + double(new_pos[AxisIdx::R]))); + } else + dxy2 = 0; + } else + dxy2 = sqr(dif[AxisIdx::X]) + sqr(dif[AxisIdx::Y]); + float dxyz2 = dxy2 + sqr(dif[AxisIdx::Z]); 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) { + } else if (std::abs(dif[AxisIdx::E]) > 0.f) { // Movement in the extruder axis. - line.length = std::abs(dif[3]); + line.length = std::abs(dif[AxisIdx::E]); } - line.feedrate = new_pos[4]; + line.feedrate = new_pos[AxisIdx::F]; assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f); if (line.length > 0) { assert(line.feedrate > 0); @@ -430,7 +475,7 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: assert(adjustment->min_print_speed >= 0); 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)) { + if (active_speed_modifier < adjustment->lines.size() && (line.type & (CoolingLine::TYPE_G1 | CoolingLine::TYPE_G2G3))) { // 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]; @@ -447,7 +492,7 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: line.type = 0; } } - current_pos = std::move(new_pos); + std::copy(std::begin(new_pos), std::begin(new_pos) + 5, std::begin(current_pos)); } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { // Closing a block of non-zero length extrusion moves. line.type = CoolingLine::TYPE_EXTRUDE_END; diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 91a81c7f31..9950a5f7ef 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -7,7 +7,7 @@ namespace Slic3r { -class GCode; +class GCodeGenerator; class Layer; struct PerExtruderAdjustments; @@ -22,7 +22,7 @@ struct PerExtruderAdjustments; // class CoolingBuffer { public: - CoolingBuffer(GCode &gcodegen); + CoolingBuffer(GCodeGenerator &gcodegen); void reset(const Vec3d &position); void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } std::string process_layer(std::string &&gcode, size_t layer_id, bool flush); @@ -31,7 +31,7 @@ public: private: CoolingBuffer& operator=(const CoolingBuffer&) = delete; - std::vector parse_layer_gcode(const std::string &gcode, std::vector ¤t_pos) const; + std::vector parse_layer_gcode(const std::string &gcode, std::array ¤t_pos) const; float calculate_layer_slowdown(std::vector &per_extruder_adjustments); // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. // Returns the adjusted G-code. @@ -40,9 +40,11 @@ private: // G-code snippet cached for the support layers preceding an object layer. std::string m_gcode; // Internal data. - // X,Y,Z,E,F std::vector m_axis; - std::vector m_current_pos; + enum AxisIdx : int { + X = 0, Y, Z, E, F, I, J, K, R, Count + }; + std::array m_current_pos; // Current known fan speed or -1 if not known yet. int m_fan_speed; // Cached from GCodeWriter. @@ -51,7 +53,7 @@ private: // Highest of m_extruder_ids plus 1. unsigned int m_num_extruders { 0 }; const std::string m_toolchange_prefix; - // Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified, + // Referencs GCodeGenerator::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified, // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required. const PrintConfig &m_config; unsigned int m_current_extruder; diff --git a/src/libslic3r/GCode/ExtrusionProcessor.cpp b/src/libslic3r/GCode/ExtrusionProcessor.cpp new file mode 100644 index 0000000000..8ebc5d3df1 --- /dev/null +++ b/src/libslic3r/GCode/ExtrusionProcessor.cpp @@ -0,0 +1,216 @@ +#include "ExtrusionProcessor.hpp" +#include + +namespace Slic3r { namespace ExtrusionProcessor { + +ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path, + const AABBTreeLines::LinesDistancer &unscaled_prev_layer, + const AABBTreeLines::LinesDistancer &prev_layer_curled_lines) +{ + std::vector extended_points = estimate_points_properties(path.polyline.points, + unscaled_prev_layer, path.width()); + std::vector> calculated_distances(extended_points.size()); + + for (size_t i = 0; i < extended_points.size(); i++) { + const ExtendedPoint &curr = extended_points[i]; + const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i]; + + // The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines + float proximity_to_curled_lines = 0.0; + const double dist_limit = 10.0 * path.width(); + { + Vec2d middle = 0.5 * (curr.position + next.position); + auto line_indices = prev_layer_curled_lines.all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit)); + if (!line_indices.empty()) { + double len = (next.position - curr.position).norm(); + // For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle + // of this long line + // The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down. + // NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point + // TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge + if (len > 8) { + Vec2d dir = Vec2d(next.position - curr.position) / len; + Vec2d right = Vec2d(-dir.y(), dir.x()); + + Polygon box_of_influence = { + scaled(Vec2d(curr.position + right * dist_limit)), + scaled(Vec2d(next.position + right * dist_limit)), + scaled(Vec2d(next.position - right * dist_limit)), + scaled(Vec2d(curr.position - right * dist_limit)), + }; + + double projected_lengths_sum = 0; + for (size_t idx : line_indices) { + const CurledLine &line = prev_layer_curled_lines.get_line(idx); + Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence}); + if (inside.empty()) + continue; + double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast())))); + projected_lengths_sum += projected_length; + } + if (projected_lengths_sum < 0.4 * len) { + line_indices.clear(); + } + } + + for (size_t idx : line_indices) { + const CurledLine &line = prev_layer_curled_lines.get_line(idx); + float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle))); + float proximity = (1.0 - (distance_from_curled / dist_limit)) * (1.0 - (distance_from_curled / dist_limit)) * + (line.curled_height / (path.height() * 10.0f)); // max_curled_height_factor from SupportSpotGenerator + proximity_to_curled_lines = std::max(proximity_to_curled_lines, proximity); + } + } + } + calculated_distances[i].first = std::max(curr.distance, next.distance); + calculated_distances[i].second = proximity_to_curled_lines; + } + + ExtrusionPaths result; + ExtrusionAttributes new_attrs = path.attributes(); + new_attrs.overhang_attributes = std::optional( + {calculated_distances[0].first, calculated_distances[0].first, calculated_distances[0].second}); + result.emplace_back(new_attrs); + result.back().polyline.append(Point::new_scale(extended_points[0].position)); + size_t sequence_start_index = 0; + for (size_t i = 1; i < extended_points.size(); i++) { + result.back().polyline.append(Point::new_scale(extended_points[i].position)); + result.back().overhang_attributes_mutable()->end_distance_from_prev_layer = extended_points[i].distance; + + if (std::abs(calculated_distances[sequence_start_index].first - calculated_distances[i].first) < 0.001 * path.attributes().width && + std::abs(calculated_distances[sequence_start_index].second - calculated_distances[i].second) < 0.001) { + // do not start new path, the attributes are similar enough + // NOTE: a larger tolerance may be applied here. However, it makes the gcode preview much less smooth + // (But it has very likely zero impact on the print quality.) + } else if (i + 1 < extended_points.size()) { // do not start new path if this is last point! + // start new path, parameters differ + new_attrs.overhang_attributes->start_distance_from_prev_layer = calculated_distances[i].first; + new_attrs.overhang_attributes->end_distance_from_prev_layer = calculated_distances[i].first; + new_attrs.overhang_attributes->proximity_to_curled_lines = calculated_distances[i].second; + sequence_start_index = i; + result.emplace_back(new_attrs); + result.back().polyline.append(Point::new_scale(extended_points[i].position)); + } + } + + return result; +}; + +ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(const ExtrusionEntityCollection *ecc, + const AABBTreeLines::LinesDistancer &unscaled_prev_layer, + const AABBTreeLines::LinesDistancer &prev_layer_curled_lines) +{ + ExtrusionEntityCollection result{}; + result.no_sort = ecc->no_sort; + for (const auto *e : ecc->entities) { + if (auto *col = dynamic_cast(e)) { + result.append(calculate_and_split_overhanging_extrusions(col, unscaled_prev_layer, prev_layer_curled_lines)); + } else if (auto *loop = dynamic_cast(e)) { + ExtrusionLoop new_loop = *loop; + new_loop.paths.clear(); + for (const ExtrusionPath &p : loop->paths) { + auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines); + new_loop.paths.insert(new_loop.paths.end(), paths.begin(), paths.end()); + } + result.append(new_loop); + } else if (auto *mp = dynamic_cast(e)) { + ExtrusionMultiPath new_mp = *mp; + new_mp.paths.clear(); + for (const ExtrusionPath &p : mp->paths) { + auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines); + new_mp.paths.insert(new_mp.paths.end(), paths.begin(), paths.end()); + } + result.append(new_mp); + } else if (auto *op = dynamic_cast(e)) { + auto paths = calculate_and_split_overhanging_extrusions(*op, unscaled_prev_layer, prev_layer_curled_lines); + for (const ExtrusionPath &p : paths) { + result.append(ExtrusionPathOriented(p.polyline, p.attributes())); + } + } else if (auto *p = dynamic_cast(e)) { + auto paths = calculate_and_split_overhanging_extrusions(*p, unscaled_prev_layer, prev_layer_curled_lines); + result.append(paths); + } else { + throw Slic3r::InvalidArgument("Unknown extrusion entity type"); + } + } + return result; +}; + + +std::pair calculate_overhang_speed(const ExtrusionAttributes &attributes, + const FullPrintConfig &config, + size_t extruder_id, + float external_perim_reference_speed, + float default_speed) +{ + assert(attributes.overhang_attributes.has_value()); + std::vector> overhangs_with_speeds = { + {100, ConfigOptionFloatOrPercent{default_speed, false}}}; + if (config.enable_dynamic_overhang_speeds) { + overhangs_with_speeds = {{0, config.overhang_speed_0}, + {25, config.overhang_speed_1}, + {50, config.overhang_speed_2}, + {75, config.overhang_speed_3}, + {100, ConfigOptionFloatOrPercent{default_speed, false}}}; + } + + std::vector> overhang_with_fan_speeds = {{100, ConfigOptionInts{0}}}; + if (config.enable_dynamic_fan_speeds.get_at(extruder_id)) { + overhang_with_fan_speeds = {{0, config.overhang_fan_speed_0}, + {25, config.overhang_fan_speed_1}, + {50, config.overhang_fan_speed_2}, + {75, config.overhang_fan_speed_3}, + {100, ConfigOptionInts{0}}}; + } + + float speed_base = external_perim_reference_speed > 0 ? external_perim_reference_speed : default_speed; + std::map speed_sections; + for (size_t i = 0; i < overhangs_with_speeds.size(); i++) { + float distance = attributes.width * (1.0 - (overhangs_with_speeds[i].first / 100.0)); + float speed = overhangs_with_speeds[i].second.percent ? (speed_base * overhangs_with_speeds[i].second.value / 100.0) : + overhangs_with_speeds[i].second.value; + if (speed < EPSILON) + speed = speed_base; + speed_sections[distance] = speed; + } + + std::map fan_speed_sections; + for (size_t i = 0; i < overhang_with_fan_speeds.size(); i++) { + float distance = attributes.width * (1.0 - (overhang_with_fan_speeds[i].first / 100.0)); + float fan_speed = overhang_with_fan_speeds[i].second.get_at(extruder_id); + fan_speed_sections[distance] = fan_speed; + } + + auto interpolate_speed = [](const std::map &values, float distance) { + auto upper_dist = values.lower_bound(distance); + if (upper_dist == values.end()) { + return values.rbegin()->second; + } + if (upper_dist == values.begin()) { + return upper_dist->second; + } + + auto lower_dist = std::prev(upper_dist); + float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first); + return (1.0f - t) * lower_dist->second + t * upper_dist->second; + }; + + float extrusion_speed = std::min(interpolate_speed(speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer), + interpolate_speed(speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer)); + float curled_base_speed = interpolate_speed(speed_sections, + attributes.width * attributes.overhang_attributes->proximity_to_curled_lines); + float final_speed = std::min(curled_base_speed, extrusion_speed); + float fan_speed = std::min(interpolate_speed(fan_speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer), + interpolate_speed(fan_speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer)); + + if (!config.enable_dynamic_overhang_speeds) { + final_speed = -1; + } + if (!config.enable_dynamic_fan_speeds.get_at(extruder_id)) { + fan_speed = -1; + } + + return {final_speed, fan_speed}; +} + +}} // namespace Slic3r::ExtrusionProcessor \ No newline at end of file diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index 5314e9afe4..727868b1c5 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -14,25 +14,29 @@ #include "../Flow.hpp" #include "../Config.hpp" #include "../Line.hpp" +#include "../Exception.hpp" +#include "../PrintConfig.hpp" #include +#include #include #include #include #include #include +#include #include #include #include #include -namespace Slic3r { +namespace Slic3r { namespace ExtrusionProcessor { struct ExtendedPoint { - Vec2d position; - float distance; - float curvature; + Vec2d position; + float distance; + float curvature; }; template @@ -46,27 +50,30 @@ std::vector estimate_points_properties(const POINTS using AABBScalar = typename AABBTreeLines::LinesDistancer::Scalar; if (input_points.empty()) return {}; - float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f; - auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast(); }; + float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f; + auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast(); }; std::vector points; points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1)); { ExtendedPoint start_point{maybe_unscale(input_points.front())}; - auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(start_point.position.cast()); - start_point.distance = distance + boundary_offset; + auto [distance, nearest_line, + x] = unscaled_prev_layer.template distance_from_lines_extra(start_point.position.cast()); + start_point.distance = distance + boundary_offset; points.push_back(start_point); } for (size_t i = 1; i < input_points.size(); i++) { ExtendedPoint next_point{maybe_unscale(input_points[i])}; - auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(next_point.position.cast()); - next_point.distance = distance + boundary_offset; + auto [distance, nearest_line, + x] = unscaled_prev_layer.template distance_from_lines_extra(next_point.position.cast()); + next_point.distance = distance + boundary_offset; if (ADD_INTERSECTIONS && ((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) { - const ExtendedPoint &prev_point = points.back(); - auto intersections = unscaled_prev_layer.template intersections_with_line(L{prev_point.position.cast(), next_point.position.cast()}); + const ExtendedPoint &prev_point = points.back(); + auto intersections = unscaled_prev_layer.template intersections_with_line( + L{prev_point.position.cast(), next_point.position.cast()}); for (const auto &intersection : intersections) { ExtendedPoint p{}; p.position = intersection.first.template cast(); @@ -79,47 +86,49 @@ std::vector estimate_points_properties(const POINTS if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) { std::vector new_points; - new_points.reserve(points.size()*2); + new_points.reserve(points.size() * 2); new_points.push_back(points.front()); for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) { const ExtendedPoint &curr = points[point_idx]; const ExtendedPoint &next = points[point_idx + 1]; - if ((curr.distance > 0 && curr.distance < boundary_offset + 2.0f) || - (next.distance > 0 && next.distance < boundary_offset + 2.0f)) { + if ((curr.distance > -boundary_offset && curr.distance < boundary_offset + 2.0f) || + (next.distance > -boundary_offset && next.distance < boundary_offset + 2.0f)) { double line_len = (next.position - curr.position).norm(); if (line_len > 4.0f) { - double a0 = std::clamp((curr.distance + 2 * boundary_offset) / line_len, 0.0, 1.0); - double a1 = std::clamp(1.0f - (next.distance + 2 * boundary_offset) / line_len, 0.0, 1.0); + double a0 = std::clamp((curr.distance + 3 * boundary_offset) / line_len, 0.0, 1.0); + double a1 = std::clamp(1.0f - (next.distance + 3 * boundary_offset) / line_len, 0.0, 1.0); double t0 = std::min(a0, a1); double t1 = std::max(a0, a1); if (t0 < 1.0) { - auto p0 = curr.position + t0 * (next.position - curr.position); - auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra(p0.cast()); + auto p0 = curr.position + t0 * (next.position - curr.position); + auto [p0_dist, p0_near_l, + p0_x] = unscaled_prev_layer.template distance_from_lines_extra(p0.cast()); ExtendedPoint new_p{}; - new_p.position = p0; - new_p.distance = float(p0_dist + boundary_offset); + new_p.position = p0; + new_p.distance = float(p0_dist + boundary_offset); new_points.push_back(new_p); } if (t1 > 0.0) { - auto p1 = curr.position + t1 * (next.position - curr.position); - auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra(p1.cast()); + auto p1 = curr.position + t1 * (next.position - curr.position); + auto [p1_dist, p1_near_l, + p1_x] = unscaled_prev_layer.template distance_from_lines_extra(p1.cast()); ExtendedPoint new_p{}; - new_p.position = p1; - new_p.distance = float(p1_dist + boundary_offset); + new_p.position = p1; + new_p.distance = float(p1_dist + boundary_offset); new_points.push_back(new_p); } } } new_points.push_back(next); } - points = new_points; + points = std::move(new_points); } if (max_line_length > 0) { std::vector new_points; - new_points.reserve(points.size()*2); + new_points.reserve(points.size() * 2); { for (size_t i = 0; i + 1 < points.size(); i++) { const ExtendedPoint &curr = points[i]; @@ -133,14 +142,14 @@ std::vector estimate_points_properties(const POINTS auto [p_dist, p_near_l, p_x] = unscaled_prev_layer.template distance_from_lines_extra(pos.cast()); ExtendedPoint new_p{}; - new_p.position = pos; - new_p.distance = float(p_dist + boundary_offset); + new_p.position = pos; + new_p.distance = float(p_dist + boundary_offset); new_points.push_back(new_p); } } new_points.push_back(points.back()); } - points = new_points; + points = std::move(new_points); } std::vector angles_for_curvature(points.size()); @@ -212,144 +221,21 @@ std::vector estimate_points_properties(const POINTS return points; } -struct ProcessedPoint -{ - Point p; - float speed = 1.0f; - int fan_speed = 0; -}; +ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path, + const AABBTreeLines::LinesDistancer &unscaled_prev_layer, + const AABBTreeLines::LinesDistancer &prev_layer_curled_lines); -class ExtrusionQualityEstimator -{ - std::unordered_map> prev_layer_boundaries; - std::unordered_map> next_layer_boundaries; - std::unordered_map> prev_curled_extrusions; - std::unordered_map> next_curled_extrusions; - const PrintObject *current_object; +ExtrusionEntityCollection calculate_and_split_overhanging_extrusions( + const ExtrusionEntityCollection *ecc, + const AABBTreeLines::LinesDistancer &unscaled_prev_layer, + const AABBTreeLines::LinesDistancer &prev_layer_curled_lines); -public: - void set_current_object(const PrintObject *object) { current_object = object; } +std::pair calculate_overhang_speed(const ExtrusionAttributes &attributes, + const FullPrintConfig &config, + size_t extruder_id, + float external_perim_reference_speed, + float default_speed); - void prepare_for_new_layer(const Layer *layer) - { - if (layer == nullptr) - return; - const PrintObject *object = layer->object(); - prev_layer_boundaries[object] = next_layer_boundaries[object]; - next_layer_boundaries[object] = AABBTreeLines::LinesDistancer{to_unscaled_linesf(layer->lslices)}; - prev_curled_extrusions[object] = next_curled_extrusions[object]; - next_curled_extrusions[object] = AABBTreeLines::LinesDistancer{layer->curled_lines}; - } - - std::vector estimate_speed_from_extrusion_quality( - const ExtrusionPath &path, - const std::vector> overhangs_w_speeds, - const std::vector> overhangs_w_fan_speeds, - size_t extruder_id, - float ext_perimeter_speed, - float original_speed) - { - float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed; - std::map speed_sections; - for (size_t i = 0; i < overhangs_w_speeds.size(); i++) { - float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0)); - float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) : - overhangs_w_speeds[i].second.value; - if (speed < EPSILON) speed = speed_base; - speed_sections[distance] = speed; - } - - std::map fan_speed_sections; - for (size_t i = 0; i < overhangs_w_fan_speeds.size(); i++) { - float distance = path.width * (1.0 - (overhangs_w_fan_speeds[i].first / 100.0)); - float fan_speed = overhangs_w_fan_speeds[i].second.get_at(extruder_id); - fan_speed_sections[distance] = fan_speed; - } - - std::vector extended_points = - estimate_points_properties(path.polyline.points, prev_layer_boundaries[current_object], path.width); - - std::vector processed_points; - processed_points.reserve(extended_points.size()); - for (size_t i = 0; i < extended_points.size(); i++) { - const ExtendedPoint &curr = extended_points[i]; - const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i]; - - // The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines - float artificial_distance_to_curled_lines = 0.0; - const double dist_limit = 10.0 * path.width; - { - Vec2d middle = 0.5 * (curr.position + next.position); - auto line_indices = prev_curled_extrusions[current_object].all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit)); - if (!line_indices.empty()) { - double len = (next.position - curr.position).norm(); - // For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle of this long line - // The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down. - // NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point - // TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge - if (len > 8) { - Vec2d dir = Vec2d(next.position - curr.position) / len; - Vec2d right = Vec2d(-dir.y(), dir.x()); - - Polygon box_of_influence = { - scaled(Vec2d(curr.position + right * dist_limit)), - scaled(Vec2d(next.position + right * dist_limit)), - scaled(Vec2d(next.position - right * dist_limit)), - scaled(Vec2d(curr.position - right * dist_limit)), - }; - - double projected_lengths_sum = 0; - for (size_t idx : line_indices) { - const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx); - Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence}); - if (inside.empty()) - continue; - double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast())))); - projected_lengths_sum += projected_length; - } - if (projected_lengths_sum < 0.4 * len) { - line_indices.clear(); - } - } - - for (size_t idx : line_indices) { - const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx); - float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle))); - float dist = path.width * (1.0 - (distance_from_curled / dist_limit)) * - (1.0 - (distance_from_curled / dist_limit)) * - (line.curled_height / (path.height * 10.0f)); // max_curled_height_factor from SupportSpotGenerator - artificial_distance_to_curled_lines = std::max(artificial_distance_to_curled_lines, dist); - } - } - } - - auto interpolate_speed = [](const std::map &values, float distance) { - auto upper_dist = values.lower_bound(distance); - if (upper_dist == values.end()) { - return values.rbegin()->second; - } - if (upper_dist == values.begin()) { - return upper_dist->second; - } - - auto lower_dist = std::prev(upper_dist); - float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first); - return (1.0f - t) * lower_dist->second + t * upper_dist->second; - }; - - float extrusion_speed = std::min(interpolate_speed(speed_sections, curr.distance), - interpolate_speed(speed_sections, next.distance)); - float curled_base_speed = interpolate_speed(speed_sections, artificial_distance_to_curled_lines); - float final_speed = std::min(curled_base_speed, extrusion_speed); - float fan_speed = std::min(interpolate_speed(fan_speed_sections, curr.distance), - interpolate_speed(fan_speed_sections, next.distance)); - - processed_points.push_back({scaled(curr.position), final_speed, int(fan_speed)}); - } - return processed_points; - } -}; - -} // namespace Slic3r +}} // namespace Slic3r::ExtrusionProcessor #endif // slic3r_ExtrusionProcessor_hpp_ diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index f7fbb52a69..40ea2e6903 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -4,8 +4,9 @@ #include "libslic3r/LocalesUtils.hpp" #include "libslic3r/format.hpp" #include "libslic3r/I18N.hpp" -#include "libslic3r/GCodeWriter.hpp" +#include "libslic3r/GCode/GCodeWriter.hpp" #include "libslic3r/I18N.hpp" +#include "libslic3r/Geometry/ArcWelder.hpp" #include "GCodeProcessor.hpp" #include @@ -43,8 +44,6 @@ static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero(); // taken from PrusaResearch.ini - [printer:Original Prusa i3 MK2.5 MMU2] static const std::vector DEFAULT_EXTRUDER_COLORS = { "#FF8000", "#DB5182", "#3EC0FF", "#FF4F4F", "#FBEB7D" }; -static const std::string INTERNAL_G2G3_TAG = "!#!#! internal only - from G2/G3 expansion !#!#!"; - namespace Slic3r { const std::vector GCodeProcessor::Reserved_Tags = { @@ -2363,13 +2362,10 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) if (line.has_e()) g1_axes[E] = (double)line.e(); std::optional g1_feedrate = std::nullopt; if (line.has_f()) g1_feedrate = (double)line.f(); - std::optional g1_cmt = std::nullopt; - if (!line.comment().empty()) g1_cmt = line.comment(); - - process_G1(g1_axes, g1_feedrate, g1_cmt); + process_G1(g1_axes, g1_feedrate); } -void GCodeProcessor::process_G1(const std::array, 4>& axes, std::optional feedrate, std::optional cmt) +void GCodeProcessor::process_G1(const std::array, 4>& axes, std::optional feedrate, G1DiscretizationOrigin origin) { const float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); const float filament_radius = 0.5f * filament_diameter; @@ -2452,7 +2448,7 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes m_height = m_forced_height; else if (m_layer_id == 0) m_height = m_first_layer_height + m_z_offset; - else if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG) { + else if (origin == G1DiscretizationOrigin::G1) { if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0) m_height = m_end_position[Z] - m_extruded_last_z; } @@ -2463,7 +2459,7 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes if (m_end_position[Z] == 0.0f || (m_extrusion_role == GCodeExtrusionRole::Custom && m_layer_id == 0)) m_end_position[Z] = m_height; - if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG) + if (origin == G1DiscretizationOrigin::G1) m_extruded_last_z = m_end_position[Z]; m_options_z_corrector.update(m_height); @@ -2693,18 +2689,48 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes } // store move - store_move_vertex(type, cmt.has_value() && *cmt == INTERNAL_G2G3_TAG); + store_move_vertex(type, origin == G1DiscretizationOrigin::G2G3); } void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise) { - if (!line.has('I') || !line.has('J')) + enum class EFitting { None, IJ, R }; + const EFitting fitting = line.has('R') ? EFitting::R : (line.has('I') && line.has('J')) ? EFitting::IJ : EFitting::None; + + if (fitting == EFitting::None) return; + const float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); + const float filament_radius = 0.5f * filament_diameter; + const float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); + + AxisCoords end_position = m_start_position; + for (unsigned char a = X; a <= E; ++a) { + end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section)); + } + // relative center Vec3f rel_center = Vec3f::Zero(); - if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y())) - return; +#ifndef _NDEBUG + double radius = 0.0; +#endif // _NDEBUG + if (fitting == EFitting::R) { + float r; + if (!line.has_value('R', r) || r == 0.0f) + return; +#ifndef _NDEBUG + radius = (double)std::abs(r); +#endif // _NDEBUG + const Vec2f start_pos((float)m_start_position[X], (float)m_start_position[Y]); + const Vec2f end_pos((float)end_position[X], (float)end_position[Y]); + const Vec2f c = Geometry::ArcWelder::arc_center(start_pos, end_pos, r, !clockwise); + rel_center.x() = c.x() - m_start_position[X]; + rel_center.y() = c.y() - m_start_position[Y]; + } + else { + if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y())) + return; + } // scale center, if needed if (m_units == EUnits::Inches) @@ -2740,15 +2766,6 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc // arc center arc.center = arc.start + rel_center.cast(); - const float filament_diameter = (static_cast(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); - const float filament_radius = 0.5f * filament_diameter; - const float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); - - AxisCoords end_position = m_start_position; - for (unsigned char a = X; a <= E; ++a) { - end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section)); - } - // arc end endpoint arc.end = Vec3d(end_position[X], end_position[Y], end_position[Z]); @@ -2757,6 +2774,8 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc // what to do ??? } + assert(fitting != EFitting::R || std::abs(radius - arc.start_radius()) < EPSILON); + // updates feedrate from line std::optional feedrate; if (line.has_f()) @@ -2816,9 +2835,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc g1_feedrate = (double)*feedrate; if (extrusion.has_value()) g1_axes[E] = target[E]; - std::optional g1_cmt = INTERNAL_G2G3_TAG; - - process_G1(g1_axes, g1_feedrate, g1_cmt); + process_G1(g1_axes, g1_feedrate, G1DiscretizationOrigin::G2G3); }; // calculate arc segments @@ -2827,8 +2844,13 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc // https://github.com/prusa3d/Prusa-Firmware/blob/MK3/Firmware/motion_control.cpp // segments count +#if 0 static const double MM_PER_ARC_SEGMENT = 1.0; const size_t segments = std::max(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1); +#else + static const double gcode_arc_tolerance = 0.0125; + const size_t segments = Geometry::ArcWelder::arc_discretization_steps(arc.start_radius(), std::abs(arc.angle), gcode_arc_tolerance); +#endif const double inv_segment = 1.0 / double(segments); const double theta_per_segment = arc.angle * inv_segment; diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index fcba4321ae..a383f251c2 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -56,9 +56,13 @@ namespace Slic3r { time = 0.0f; travel_time = 0.0f; custom_gcode_times.clear(); + custom_gcode_times.shrink_to_fit(); moves_times.clear(); + moves_times.shrink_to_fit(); roles_times.clear(); + roles_times.shrink_to_fit(); layers_times.clear(); + layers_times.shrink_to_fit(); } }; @@ -76,6 +80,7 @@ namespace Slic3r { m.reset(); } volumes_per_color_change.clear(); + volumes_per_color_change.shrink_to_fit(); volumes_per_extruder.clear(); used_filaments_per_role.clear(); cost_per_extruder.clear(); @@ -680,8 +685,12 @@ namespace Slic3r { // Move void process_G0(const GCodeReader::GCodeLine& line); void process_G1(const GCodeReader::GCodeLine& line); + enum class G1DiscretizationOrigin { + G1, + G2G3, + }; void process_G1(const std::array, 4>& axes = { std::nullopt, std::nullopt, std::nullopt, std::nullopt }, - std::optional feedrate = std::nullopt, std::optional cmt = std::nullopt); + std::optional feedrate = std::nullopt, G1DiscretizationOrigin origin = G1DiscretizationOrigin::G1); // Arc Move void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise); diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCode/GCodeWriter.cpp similarity index 85% rename from src/libslic3r/GCodeWriter.cpp rename to src/libslic3r/GCode/GCodeWriter.cpp index 9c330c38e9..241daa3250 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCode/GCodeWriter.cpp @@ -1,10 +1,12 @@ #include "GCodeWriter.hpp" -#include "CustomGCode.hpp" +#include "../CustomGCode.hpp" + #include #include #include #include #include +#include #ifdef __APPLE__ #include @@ -13,6 +15,8 @@ #define FLAVOR_IS(val) this->config.gcode_flavor == val #define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val +using namespace std::string_view_literals; + namespace Slic3r { // static @@ -90,17 +94,17 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish))) return {}; - std::string code, comment; + std::string_view code, comment; if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) { - code = "M109"; - comment = "set temperature and wait for it to be reached"; + code = "M109"sv; + comment = "set temperature and wait for it to be reached"sv; } else { if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware - code = "G10"; + code = "G10"sv; } else { - code = "M104"; + code = "M104"sv; } - comment = "set temperature"; + comment = "set temperature"sv; } std::ostringstream gcode; @@ -130,22 +134,22 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait) { if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached)) - return std::string(); + return {}; m_last_bed_temperature = temperature; m_last_bed_temperature_reached = wait; - std::string code, comment; + std::string_view code, comment; if (wait && FLAVOR_IS_NOT(gcfTeacup)) { if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { - code = "M109"; + code = "M109"sv; } else { - code = "M190"; + code = "M190"sv; } - comment = "set bed temperature and wait for it to be reached"; + comment = "set bed temperature and wait for it to be reached"sv; } else { - code = "M140"; - comment = "set bed temperature"; + code = "M140"sv; + comment = "set bed temperature"sv; } std::ostringstream gcode; @@ -176,7 +180,7 @@ std::string GCodeWriter::set_acceleration_internal(Acceleration type, unsigned i auto& last_value = separate_travel ? m_last_travel_acceleration : m_last_acceleration ; if (acceleration == 0 || acceleration == last_value) - return std::string(); + return {}; last_value = acceleration; @@ -245,7 +249,7 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id) return gcode.str(); } -std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const +std::string GCodeWriter::set_speed(double F, const std::string_view comment, const std::string_view cooling_marker) const { assert(F > 0.); assert(F < 100000.); @@ -257,10 +261,9 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s return w.string(); } -std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment) +std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment) { - m_pos.x() = point.x(); - m_pos.y() = point.y(); + m_pos.head<2>() = point.head<2>(); GCodeG1Formatter w; w.emit_xy(point); @@ -269,7 +272,40 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com return w.string(); } -std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment) +std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment) +{ + assert(std::abs(point.x()) < 1200.); + assert(std::abs(point.y()) < 1200.); + assert(std::abs(ij.x()) < 1200.); + assert(std::abs(ij.y()) < 1200.); + assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001); + + m_pos.head<2>() = point.head<2>(); + + GCodeG2G3Formatter w(ccw); + w.emit_xy(point); + w.emit_ij(ij); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); +} + +std::string GCodeWriter::travel_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, const std::string_view comment) +{ + assert(std::abs(point.x()) < 1200.); + assert(std::abs(point.y()) < 1200.); + assert(std::abs(radius) >= 0.001); + assert(std::abs(radius) < 1800.); + + m_pos.head<2>() = point.head<2>(); + + GCodeG2G3Formatter w(ccw); + w.emit_xy(point); + w.emit_radius(radius); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); +} + +std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment) { // FIXME: This function was not being used when travel_speed_z was separated (bd6badf). // Calculation of feedrate was not updated accordingly. If you want to use @@ -302,7 +338,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co return w.string(); } -std::string GCodeWriter::travel_to_z(double z, const std::string &comment) +std::string GCodeWriter::travel_to_z(double z, const std::string_view comment) { /* If target Z is lower than current Z but higher than nominal Z we don't perform the move but we only adjust the nominal Z by @@ -321,7 +357,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment) return this->_travel_to_z(z, comment); } -std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) +std::string GCodeWriter::_travel_to_z(double z, const std::string_view comment) { m_pos.z() = z; @@ -348,10 +384,12 @@ bool GCodeWriter::will_move_z(double z) const return true; } -std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment) +std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment) { - m_pos.x() = point.x(); - m_pos.y() = point.y(); + assert(dE != 0); + assert(std::abs(dE) < 1000.0); + + m_pos.head<2>() = point.head<2>(); GCodeG1Formatter w; w.emit_xy(point); @@ -360,8 +398,47 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: return w.string(); } +std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment) +{ + assert(std::abs(dE) < 1000.0); + assert(dE != 0); + assert(std::abs(point.x()) < 1200.); + assert(std::abs(point.y()) < 1200.); + assert(std::abs(ij.x()) < 1200.); + assert(std::abs(ij.y()) < 1200.); + assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001); + + m_pos.head<2>() = point.head<2>(); + + GCodeG2G3Formatter w(ccw); + w.emit_xy(point); + w.emit_ij(ij); + w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); +} + +std::string GCodeWriter::extrude_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, double dE, const std::string_view comment) +{ + assert(dE != 0); + assert(std::abs(dE) < 1000.0); + assert(std::abs(point.x()) < 1200.); + assert(std::abs(point.y()) < 1200.); + assert(std::abs(radius) >= 0.001); + assert(std::abs(radius) < 1800.); + + m_pos.head<2>() = point.head<2>(); + + GCodeG2G3Formatter w(ccw); + w.emit_xy(point); + w.emit_radius(radius); + w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second); + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); +} + #if 0 -std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment) +std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment) { m_pos = point; m_lifted = 0; @@ -397,8 +474,11 @@ std::string GCodeWriter::retract_for_toolchange(bool before_wipe) ); } -std::string GCodeWriter::_retract(double length, double restart_extra, const std::string &comment) +std::string GCodeWriter::_retract(double length, double restart_extra, const std::string_view comment) { + assert(std::abs(length) < 1000.0); + assert(std::abs(restart_extra) < 1000.0); + /* If firmware retraction is enabled, we use a fake value of 1 since we ignore the actual configured retract_length which might be 0, in which case the retraction logic gets skipped. */ diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCode/GCodeWriter.hpp similarity index 78% rename from src/libslic3r/GCodeWriter.hpp rename to src/libslic3r/GCode/GCodeWriter.hpp index 0d376cb159..127fb61c03 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCode/GCodeWriter.hpp @@ -1,13 +1,15 @@ #ifndef slic3r_GCodeWriter_hpp_ #define slic3r_GCodeWriter_hpp_ -#include "libslic3r.h" +#include "../libslic3r.h" +#include "../Extruder.hpp" +#include "../Point.hpp" +#include "../PrintConfig.hpp" +#include "CoolingBuffer.hpp" + #include +#include #include -#include "Extruder.hpp" -#include "Point.hpp" -#include "PrintConfig.hpp" -#include "GCode/CoolingBuffer.hpp" namespace Slic3r { @@ -56,13 +58,17 @@ public: // printed with the same extruder. std::string toolchange_prefix() const; std::string toolchange(unsigned int extruder_id); - std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const; - std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string()); - std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string()); - std::string travel_to_z(double z, const std::string &comment = std::string()); + std::string set_speed(double F, const std::string_view comment = {}, const std::string_view cooling_marker = {}) const; + std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {}); + std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {}); + std::string travel_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, const std::string_view comment = {}); + std::string travel_to_xyz(const Vec3d &point, const std::string_view comment = {}); + std::string travel_to_z(double z, const std::string_view comment = {}); bool will_move_z(double z) const; - std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string()); -// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string()); + std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {}); + std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment); + std::string extrude_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, double dE, const std::string_view comment); +// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {}); std::string retract(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false); std::string unretract(); @@ -113,8 +119,8 @@ private: Print }; - std::string _travel_to_z(double z, const std::string &comment); - std::string _retract(double length, double restart_extra, const std::string &comment); + std::string _travel_to_z(double z, const std::string_view comment); + std::string _retract(double length, double restart_extra, const std::string_view comment); std::string set_acceleration_internal(Acceleration type, unsigned int acceleration); }; @@ -152,6 +158,10 @@ public: static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; } static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); } static double quantize_e(double v) { return quantize(v, E_EXPORT_DIGITS); } + static Vec2d quantize(const Vec2d &pt) + { return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; } + static Vec3d quantize(const Vec3d &pt) + { return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS), quantize(pt.z(), XYZF_EXPORT_DIGITS) }; } void emit_axis(const char axis, const double v, size_t digits); @@ -170,7 +180,20 @@ public: this->emit_axis('Z', z, XYZF_EXPORT_DIGITS); } - void emit_e(const std::string &axis, double v) { + void emit_ij(const Vec2d &point) { + if (point.x() != 0) + this->emit_axis('I', point.x(), XYZF_EXPORT_DIGITS); + if (point.y() != 0) + this->emit_axis('J', point.y(), XYZF_EXPORT_DIGITS); + } + + // Positive radius means a smaller arc, + // negative radius means a larger arc. + void emit_radius(const double radius) { + this->emit_axis('R', radius, XYZF_EXPORT_DIGITS); + } + + void emit_e(const std::string_view axis, double v) { if (! axis.empty()) { // not gcfNoExtrusion this->emit_axis(axis[0], v, E_EXPORT_DIGITS); @@ -181,12 +204,12 @@ public: this->emit_axis('F', speed, XYZF_EXPORT_DIGITS); } - void emit_string(const std::string &s) { - strncpy(ptr_err.ptr, s.c_str(), s.size()); + void emit_string(const std::string_view s) { + strncpy(ptr_err.ptr, s.data(), s.size()); ptr_err.ptr += s.size(); } - void emit_comment(bool allow_comments, const std::string &comment) { + void emit_comment(bool allow_comments, const std::string_view comment) { if (allow_comments && ! comment.empty()) { *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' '; this->emit_string(comment); @@ -210,14 +233,25 @@ public: GCodeG1Formatter() { this->buf[0] = 'G'; this->buf[1] = '1'; - this->buf_end = buf + buflen; - this->ptr_err.ptr = this->buf + 2; + this->ptr_err.ptr += 2; } GCodeG1Formatter(const GCodeG1Formatter&) = delete; GCodeG1Formatter& operator=(const GCodeG1Formatter&) = delete; }; +class GCodeG2G3Formatter : public GCodeFormatter { +public: + GCodeG2G3Formatter(bool ccw) { + this->buf[0] = 'G'; + this->buf[1] = ccw ? '3' : '2'; + this->ptr_err.ptr += 2; + } + + GCodeG2G3Formatter(const GCodeG2G3Formatter&) = delete; + GCodeG2G3Formatter& operator=(const GCodeG2G3Formatter&) = delete; +}; + } /* namespace Slic3r */ #endif /* slic3r_GCodeWriter_hpp_ */ diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 9a01dc8a6c..9bcaa27244 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -30,7 +30,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path) { - BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))); + BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width()))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = unscale(bbox.min); @@ -44,7 +44,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio { BoundingBox bbox; for (const ExtrusionPath &extrusion_path : extrusion_loop.paths) - bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)))); + bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width())))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = unscale(bbox.min); @@ -58,7 +58,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext { BoundingBox bbox; for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths) - bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)))); + bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width())))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = unscale(bbox.min); diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 3bf1edf39e..db328997e7 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1484,7 +1484,7 @@ void SeamPlacer::init(const Print &print, std::function throw_if_can } } -void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, +Point SeamPlacer::place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first, const Point &last_pos) const { using namespace SeamPlacerImpl; const PrintObject *po = layer->object(); @@ -1587,7 +1587,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern //lastly, for internal perimeters, do the staggering if requested if (po->config().staggered_inner_seams && loop.length() > 0.0) { //fix depth, it is sometimes strongly underestimated - depth = std::max(loop.paths[projected_point.path_idx].width, depth); + depth = std::max(loop.paths[projected_point.path_idx].width(), depth); while (depth > 0.0f) { auto next_point = get_next_loop_point(projected_point); @@ -1605,14 +1605,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern } } - // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns, - // thus empty path segments will not be produced by G-code export. - if (!loop.split_at_vertex(seam_point, scaled(0.0015))) { - // The point is not in the original loop. - // Insert it. - loop.split_at(seam_point, true); - } - + return seam_point; } } // namespace Slic3r diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 671f6bcce8..5603e99624 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -141,7 +141,7 @@ public: void init(const Print &print, std::function throw_if_canceled_func); - void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const; + Point place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first, const Point &last_pos) const; private: void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); diff --git a/src/libslic3r/GCode/SmoothPath.cpp b/src/libslic3r/GCode/SmoothPath.cpp new file mode 100644 index 0000000000..ba793b465e --- /dev/null +++ b/src/libslic3r/GCode/SmoothPath.cpp @@ -0,0 +1,257 @@ +#include "SmoothPath.hpp" + +#include "../ExtrusionEntity.hpp" +#include "../ExtrusionEntityCollection.hpp" + +namespace Slic3r::GCode { + +// Length of a smooth path. +double length(const SmoothPath &path) +{ + double l = 0; + for (const SmoothPathElement &el : path) + l += Geometry::ArcWelder::path_length(el.path); + return l; +} + +// Returns true if the smooth path is longer than a threshold. +bool longer_than(const SmoothPath &path, double length) +{ + for (const SmoothPathElement &el : path) { + for (auto it = std::next(el.path.begin()); it != el.path.end(); ++ it) { + length -= Geometry::ArcWelder::segment_length(*std::prev(it), *it); + if (length < 0) + return true; + } + } + return length < 0; +} + +std::optional sample_path_point_at_distance_from_start(const SmoothPath &path, double distance) +{ + if (distance >= 0) { + for (const SmoothPathElement &el : path) { + auto it = el.path.begin(); + auto end = el.path.end(); + Point prev_point = it->point; + for (++ it; it != end; ++ it) { + Point point = it->point; + if (it->linear()) { + // Linear segment + Vec2d v = (point - prev_point).cast(); + double lsqr = v.squaredNorm(); + if (lsqr > sqr(distance)) + return std::make_optional(prev_point + (v * (distance / sqrt(lsqr))).cast()); + distance -= sqrt(lsqr); + } else { + // Circular segment + float angle = Geometry::ArcWelder::arc_angle(prev_point.cast(), point.cast(), it->radius); + double len = std::abs(it->radius) * angle; + if (len > distance) { + // Rotate the segment end point in reverse towards the start point. + return std::make_optional(prev_point.rotated(- angle * (distance / len), + Geometry::ArcWelder::arc_center(prev_point.cast(), point.cast(), it->radius, it->ccw()).cast())); + } + distance -= len; + } + if (distance < 0) + return std::make_optional(point); + prev_point = point; + } + } + } + // Failed. + return {}; +} + +std::optional sample_path_point_at_distance_from_end(const SmoothPath &path, double distance) +{ + if (distance >= 0) { + for (const SmoothPathElement& el : path) { + auto it = el.path.begin(); + auto end = el.path.end(); + Point prev_point = it->point; + for (++it; it != end; ++it) { + Point point = it->point; + if (it->linear()) { + // Linear segment + Vec2d v = (point - prev_point).cast(); + double lsqr = v.squaredNorm(); + if (lsqr > sqr(distance)) + return std::make_optional(prev_point + (v * (distance / sqrt(lsqr))).cast()); + distance -= sqrt(lsqr); + } + else { + // Circular segment + float angle = Geometry::ArcWelder::arc_angle(prev_point.cast(), point.cast(), it->radius); + double len = std::abs(it->radius) * angle; + if (len > distance) { + // Rotate the segment end point in reverse towards the start point. + return std::make_optional(prev_point.rotated(-angle * (distance / len), + Geometry::ArcWelder::arc_center(prev_point.cast(), point.cast(), it->radius, it->ccw()).cast())); + } + distance -= len; + } + if (distance < 0) + return std::make_optional(point); + prev_point = point; + } + } + } + // Failed. + return {}; +} + +double clip_end(SmoothPath &path, double distance) +{ + while (! path.empty() && distance > 0) { + Geometry::ArcWelder::Path &p = path.back().path; + distance = clip_end(p, distance); + if (p.empty()) { + path.pop_back(); + } else { + // Trailing path was trimmed and it is valid. + assert(path.back().path.size() > 1); + assert(distance == 0); + // Distance to go is zero. + return 0; + } + } + // Return distance to go after the whole smooth path was trimmed to zero. + return distance; +} + +void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters ¶ms) +{ + double tolerance = params.tolerance; + if (path.role().is_sparse_infill()) + // Use 3x lower resolution than the object fine detail for sparse infill. + tolerance *= 3.; + else if (path.role().is_support()) + // Use 4x lower resolution than the object fine detail for support. + tolerance *= 4.; + else if (path.role().is_skirt()) + // Brim is currently marked as skirt. + // Use 4x lower resolution than the object fine detail for skirt & brim. + tolerance *= 4.; + m_cache[&path.polyline] = Slic3r::Geometry::ArcWelder::fit_path(path.polyline.points, tolerance, params.fit_circle_tolerance); +} + +void SmoothPathCache::interpolate_add(const ExtrusionMultiPath &multi_path, const InterpolationParameters ¶ms) +{ + for (const ExtrusionPath &path : multi_path.paths) + this->interpolate_add(path, params); +} + +void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters ¶ms) +{ + for (const ExtrusionPath &path : loop.paths) + this->interpolate_add(path, params); +} + +void SmoothPathCache::interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters ¶ms) +{ + for (const ExtrusionEntity *ee : eec) { + if (ee->is_collection()) + this->interpolate_add(*static_cast(ee), params); + else if (const ExtrusionPath *path = dynamic_cast(ee); path) + this->interpolate_add(*path, params); + else if (const ExtrusionMultiPath *multi_path = dynamic_cast(ee); multi_path) + this->interpolate_add(*multi_path, params); + else if (const ExtrusionLoop *loop = dynamic_cast(ee); loop) + this->interpolate_add(*loop, params); + else + assert(false); + } +} + +const Geometry::ArcWelder::Path* SmoothPathCache::resolve(const Polyline *pl) const +{ + auto it = m_cache.find(pl); + return it == m_cache.end() ? nullptr : &it->second; +} + +const Geometry::ArcWelder::Path* SmoothPathCache::resolve(const ExtrusionPath &path) const +{ + return this->resolve(&path.polyline); +} + +Geometry::ArcWelder::Path SmoothPathCache::resolve_or_fit(const ExtrusionPath &path, bool reverse, double tolerance) const +{ + Geometry::ArcWelder::Path out; + if (const Geometry::ArcWelder::Path *cached = this->resolve(path); cached) + out = *cached; + else + out = Geometry::ArcWelder::fit_polyline(path.polyline.points, tolerance); + if (reverse) + Geometry::ArcWelder::reverse(out); + return out; +} + +SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const +{ + SmoothPath out; + out.reserve(paths.size()); + if (reverse) { + for (auto it = paths.crbegin(); it != paths.crend(); ++ it) + out.push_back({ it->attributes(), this->resolve_or_fit(*it, true, resolution) }); + } else { + for (auto it = paths.cbegin(); it != paths.cend(); ++ it) + out.push_back({ it->attributes(), this->resolve_or_fit(*it, false, resolution) }); + } + return out; +} + +SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionMultiPath &multipath, bool reverse, double resolution) const +{ + return this->resolve_or_fit(multipath.paths, reverse, resolution); +} + +SmoothPath SmoothPathCache::resolve_or_fit_split_with_seam( + const ExtrusionLoop &loop, const bool reverse, const double resolution, + const Point &seam_point, const double seam_point_merge_distance_threshold) const +{ + SmoothPath out = this->resolve_or_fit(loop.paths, reverse, resolution); + assert(! out.empty()); + if (! out.empty()) { + // Find a closest point on a vector of smooth paths. + Geometry::ArcWelder::PathSegmentProjection proj; + int proj_path = -1; + for (const SmoothPathElement &el : out) + if (Geometry::ArcWelder::PathSegmentProjection this_proj = Geometry::ArcWelder::point_to_path_projection(el.path, seam_point, proj.distance2); + this_proj.valid()) { + // Found a better (closer) projection. + assert(this_proj.distance2 < proj.distance2); + assert(this_proj.segment_id >= 0 && this_proj.segment_id < el.path.size()); + proj = this_proj; + proj_path = &el - out.data(); + if (proj.distance2 == 0) + // There will be no better split point found than one with zero distance. + break; + } + assert(proj_path >= 0); + // Split the path at the closest point. + Geometry::ArcWelder::Path &path = out[proj_path].path; + std::pair split = Geometry::ArcWelder::split_at( + path, proj, seam_point_merge_distance_threshold); + if (split.second.empty()) { + std::rotate(out.begin(), out.begin() + proj_path + 1, out.end()); + assert(out.back().path == split.first); + } else { + ExtrusionAttributes attr = out[proj_path].path_attributes; + std::rotate(out.begin(), out.begin() + proj_path, out.end()); + out.front().path = std::move(split.second); + if (! split.first.empty()) { + if (out.back().path_attributes == attr) { + // Merge with the last segment. + out.back().path.insert(out.back().path.end(), std::next(split.first.begin()), split.first.end()); + } else + out.push_back({ attr, std::move(split.first) }); + } + } + } + + return out; +} + +} // namespace Slic3r::GCode diff --git a/src/libslic3r/GCode/SmoothPath.hpp b/src/libslic3r/GCode/SmoothPath.hpp new file mode 100644 index 0000000000..b2eb335a32 --- /dev/null +++ b/src/libslic3r/GCode/SmoothPath.hpp @@ -0,0 +1,87 @@ +#ifndef slic3r_GCode_SmoothPath_hpp_ +#define slic3r_GCode_SmoothPath_hpp_ + +#include + +#include "../ExtrusionEntity.hpp" +#include "../Geometry/ArcWelder.hpp" + +namespace Slic3r { + +class ExtrusionEntityCollection; + +namespace GCode { + +struct SmoothPathElement +{ + ExtrusionAttributes path_attributes; + Geometry::ArcWelder::Path path; +}; + +using SmoothPath = std::vector; + +// Length of a smooth path. +double length(const SmoothPath &path); +// Returns true if the smooth path is longer than a threshold. +bool longer_than(const SmoothPath &path, const double length); + +std::optional sample_path_point_at_distance_from_start(const SmoothPath &path, double distance); +std::optional sample_path_point_at_distance_from_end(const SmoothPath &path, double distance); + +// Clip end of a smooth path, for seam hiding. +double clip_end(SmoothPath &path, double distance); + +class SmoothPathCache +{ +public: + struct InterpolationParameters { + double tolerance; + double fit_circle_tolerance; + }; + + void interpolate_add(const ExtrusionPath &ee, const InterpolationParameters ¶ms); + void interpolate_add(const ExtrusionMultiPath &ee, const InterpolationParameters ¶ms); + void interpolate_add(const ExtrusionLoop &ee, const InterpolationParameters ¶ms); + void interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters ¶ms); + + const Geometry::ArcWelder::Path* resolve(const Polyline *pl) const; + const Geometry::ArcWelder::Path* resolve(const ExtrusionPath &path) const; + + // Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline. + Geometry::ArcWelder::Path resolve_or_fit(const ExtrusionPath &path, bool reverse, double resolution) const; + + // Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline. + SmoothPath resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const; + SmoothPath resolve_or_fit(const ExtrusionMultiPath &path, bool reverse, double resolution) const; + + // Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline. + SmoothPath resolve_or_fit_split_with_seam( + const ExtrusionLoop &path, const bool reverse, const double resolution, + const Point &seam_point, const double seam_point_merge_distance_threshold) const; + +private: + ankerl::unordered_dense::map m_cache; +}; + +// Encapsulates references to global and layer local caches of smooth extrusion paths. +class SmoothPathCaches final +{ +public: + SmoothPathCaches() = delete; + SmoothPathCaches(const SmoothPathCache &global, const SmoothPathCache &layer_local) : + m_global(&global), m_layer_local(&layer_local) {} + SmoothPathCaches operator=(const SmoothPathCaches &rhs) + { m_global = rhs.m_global; m_layer_local = rhs.m_layer_local; return *this; } + + const SmoothPathCache& global() const { return *m_global; } + const SmoothPathCache& layer_local() const { return *m_layer_local; } + +private: + const SmoothPathCache *m_global; + const SmoothPathCache *m_layer_local; +}; + +} // namespace GCode +} // namespace Slic3r + +#endif // slic3r_GCode_SmoothPath_hpp_ diff --git a/src/libslic3r/GCode/Wipe.cpp b/src/libslic3r/GCode/Wipe.cpp new file mode 100644 index 0000000000..0fa887810f --- /dev/null +++ b/src/libslic3r/GCode/Wipe.cpp @@ -0,0 +1,252 @@ +#include "Wipe.hpp" +#include "../GCode.hpp" + +#include + +#include + +using namespace std::string_view_literals; + +namespace Slic3r::GCode { + +void Wipe::init(const PrintConfig &config, const std::vector &extruders) +{ + this->reset_path(); + + // Calculate maximum wipe length to accumulate by the wipe cache. + // Paths longer than wipe_xy should never be needed for the wipe move. + double wipe_xy = 0; + const bool multimaterial = extruders.size() > 1; + for (auto id : extruders) + if (config.wipe.get_at(id)) { + // Wipe length to extrusion ratio. + const double xy_to_e = this->calc_xy_to_e_ratio(config, id); + wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length.get_at(id)); + if (multimaterial) + wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length_toolchange.get_at(id)); + } + + if (wipe_xy == 0) + this->disable(); + else + this->enable(wipe_xy); +} + +void Wipe::set_path(SmoothPath &&path, bool reversed) +{ + this->reset_path(); + + if (this->enabled() && ! path.empty()) { + if (reversed) { + m_path = std::move(path.back().path); + Geometry::ArcWelder::reverse(m_path); + int64_t len = Geometry::ArcWelder::estimate_path_length(m_path); + for (auto it = std::next(path.rbegin()); len < m_wipe_len_max && it != path.rend(); ++ it) { + if (it->path_attributes.role.is_bridge()) + break; // Do not perform a wipe on bridges. + assert(it->path.size() >= 2); + assert(m_path.back().point == it->path.back().point); + if (m_path.back().point != it->path.back().point) + // ExtrusionMultiPath is interrupted in some place. This should not really happen. + break; + len += Geometry::ArcWelder::estimate_path_length(it->path); + m_path.insert(m_path.end(), it->path.rbegin() + 1, it->path.rend()); + } + } else { + m_path = std::move(path.front().path); + int64_t len = Geometry::ArcWelder::estimate_path_length(m_path); + for (auto it = std::next(path.begin()); len < m_wipe_len_max && it != path.end(); ++ it) { + if (it->path_attributes.role.is_bridge()) + break; // Do not perform a wipe on bridges. + assert(it->path.size() >= 2); + assert(m_path.back().point == it->path.front().point); + if (m_path.back().point != it->path.front().point) + // ExtrusionMultiPath is interrupted in some place. This should not really happen. + break; + len += Geometry::ArcWelder::estimate_path_length(it->path); + m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end()); + } + } + } + + assert(m_path.empty() || m_path.size() > 1); +} + +std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) +{ + std::string gcode; + const Extruder &extruder = *gcodegen.writer().extruder(); + static constexpr const std::string_view wipe_retract_comment = "wipe and retract"sv; + + // Remaining quantized retraction length. + if (double retract_length = extruder.retract_to_go(toolchange ? extruder.retract_length_toolchange() : extruder.retract_length()); + retract_length > 0 && this->has_path()) { + // Delayed emitting of a wipe start tag. + bool wiped = false; + const double wipe_speed = this->calc_wipe_speed(gcodegen.writer().config); + auto start_wipe = [&wiped, &gcode, &gcodegen, wipe_speed](){ + if (! wiped) { + wiped = true; + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n"; + gcode += gcodegen.writer().set_speed(wipe_speed * 60, {}, gcodegen.enable_cooling_markers() ? ";_WIPE"sv : ""sv); + } + }; + const double xy_to_e = this->calc_xy_to_e_ratio(gcodegen.writer().config, extruder.id()); + auto wipe_linear = [&gcode, &gcodegen, &retract_length, xy_to_e](const Vec2d &prev_quantized, Vec2d &p) { + Vec2d p_quantized = GCodeFormatter::quantize(p); + if (p_quantized == prev_quantized) { + p = p_quantized; + return false; + } + double segment_length = (p_quantized - prev_quantized).norm(); + // Quantize E axis as it is to be extruded as a whole segment. + double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length); + bool done = false; + if (dE > retract_length - EPSILON) { + if (dE > retract_length + EPSILON) + // Shorten the segment. + p = GCodeFormatter::quantize(Vec2d(prev_quantized + (p - prev_quantized) * (retract_length / dE))); + else + p = p_quantized; + dE = retract_length; + done = true; + } else + p = p_quantized; + gcode += gcodegen.writer().extrude_to_xy(p, -dE, wipe_retract_comment); + retract_length -= dE; + return done; + }; + const bool emit_radius = gcodegen.config().arc_fitting == ArcFittingType::EmitRadius; + auto wipe_arc = [&gcode, &gcodegen, &retract_length, xy_to_e, emit_radius]( + const Vec2d &prev_quantized, Vec2d &p, double radius_in, const bool ccw) { + Vec2d p_quantized = GCodeFormatter::quantize(p); + if (p_quantized == prev_quantized) { + p = p_quantized; + return false; + } + // Only quantize radius if emitting it directly into G-code. Otherwise use the exact radius for calculating the IJ values. + double radius = emit_radius ? GCodeFormatter::quantize_xyzf(radius_in) : radius_in; + Vec2d center = Geometry::ArcWelder::arc_center(prev_quantized.cast(), p_quantized.cast(), double(radius), ccw); + float angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast(), p_quantized.cast(), double(radius)); + assert(angle > 0); + double segment_length = angle * std::abs(radius); + double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length); + bool done = false; + if (dE > retract_length - EPSILON) { + if (dE > retract_length + EPSILON) { + // Shorten the segment. Recalculate the arc from the unquantized end coordinate. + center = Geometry::ArcWelder::arc_center(prev_quantized.cast(), p.cast(), double(radius), ccw); + angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast(), p.cast(), double(radius)); + segment_length = angle * std::abs(radius); + dE = xy_to_e * segment_length; + p = GCodeFormatter::quantize( + Vec2d(center + Eigen::Rotation2D((ccw ? angle : -angle) * (retract_length / dE)) * (prev_quantized - center))); + } else + p = p_quantized; + dE = retract_length; + done = true; + } else + p = p_quantized; + assert(dE > 0); + if (emit_radius) { + gcode += gcodegen.writer().extrude_to_xy_G2G3R(p, radius, ccw, -dE, wipe_retract_comment); + } else { + // Calculate quantized IJ circle center offset. + gcode += gcodegen.writer().extrude_to_xy_G2G3IJ( + p, GCodeFormatter::quantize(Vec2d(center - prev_quantized)), ccw, -dE, wipe_retract_comment); + } + retract_length -= dE; + return done; + }; + // Start with the current position, which may be different from the wipe path start in case of loop clipping. + Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos()); + auto it = this->path().begin(); + Vec2d p = gcodegen.point_to_gcode(it->point + m_offset); + ++ it; + bool done = false; + if (p != prev) { + start_wipe(); + done = wipe_linear(prev, p); + } + if (! done) { + prev = p; + auto end = this->path().end(); + for (; it != end && ! done; ++ it) { + p = gcodegen.point_to_gcode(it->point + m_offset); + if (p != prev) { + start_wipe(); + if (it->linear() ? + wipe_linear(prev, p) : + wipe_arc(prev, p, unscaled(it->radius), it->ccw())) + break; + prev = p; + } + } + } + if (wiped) { + // add tag for processor + assert(p == GCodeFormatter::quantize(p)); + gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; + gcodegen.set_last_pos(gcodegen.gcode_to_point(p)); + } + } + + // Prevent wiping again on the same path. + this->reset_path(); + return gcode; +} + +// Make a little move inwards before leaving loop after path was extruded, +// thus the current extruder position is at the end of a path and the path +// may not be closed in case the loop was clipped to hide a seam. +std::optional wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length) +{ + assert(! path.empty()); + assert(path.front().path.size() >= 2); + assert(path.back().path.size() >= 2); + + // Heuristics for estimating whether there is a chance that the wipe move will fit inside a small perimeter + // or that the wipe move direction could be calculated with reasonable accuracy. + if (longer_than(path, 2.5 * wipe_length)) { + // The print head will be moved away from path end inside the island. + Point p_current = path.back().path.back().point; + Point p_next = path.front().path.front().point; + Point p_prev; + { + // Is the seam hiding gap large enough already? + double l = wipe_length - (p_next - p_current).cast().norm(); + if (l > 0) { + // Not yet. + std::optional n = sample_path_point_at_distance_from_start(path, l); + assert(n); + if (! n) + // Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above. + return {}; + } + if (std::optional p = sample_path_point_at_distance_from_end(path, wipe_length); p) + p_prev = *p; + else + // Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above. + return {}; + } + // Detect angle between last and first segment. + // The side depends on the original winding order of the polygon (left for contours, right for holes). + double angle_inside = angle(p_next - p_current, p_prev - p_current); + assert(angle_inside >= -M_PI && angle_inside <= M_PI); + // 3rd of this angle will be taken, thus make the angle monotonic before interpolation. + if (is_hole) { + if (angle_inside > 0) + angle_inside -= 2.0 * M_PI; + } else { + if (angle_inside < 0) + angle_inside += 2.0 * M_PI; + } + // Rotate the forward segment inside by 1/3 of the wedge angle. + auto v_rotated = Eigen::Rotation2D(angle_inside) * (p_next - p_current).cast().normalized(); + return std::make_optional(p_current + (v_rotated * wipe_length).cast()); + } + + return {}; +} + +} // namespace Slic3r::GCode diff --git a/src/libslic3r/GCode/Wipe.hpp b/src/libslic3r/GCode/Wipe.hpp new file mode 100644 index 0000000000..f02a77fa7f --- /dev/null +++ b/src/libslic3r/GCode/Wipe.hpp @@ -0,0 +1,73 @@ +#ifndef slic3r_GCode_Wipe_hpp_ +#define slic3r_GCode_Wipe_hpp_ + +#include "SmoothPath.hpp" + +#include "../Geometry/ArcWelder.hpp" +#include "../Point.hpp" +#include "../PrintConfig.hpp" + +#include +#include + +namespace Slic3r { + +class GCodeGenerator; + +namespace GCode { + +class Wipe { +public: + using Path = Slic3r::Geometry::ArcWelder::Path; + + Wipe() = default; + + void init(const PrintConfig &config, const std::vector &extruders); + void enable(double wipe_len_max) { m_enabled = true; m_wipe_len_max = wipe_len_max; } + void disable() { m_enabled = false; } + bool enabled() const { return m_enabled; } + + const Path& path() const { return m_path; } + bool has_path() const { assert(m_path.empty() || m_path.size() > 1); return ! m_path.empty(); } + void reset_path() { m_path.clear(); m_offset = Point::Zero(); } + void set_path(const Path &path) { + assert(path.empty() || path.size() > 1); + this->reset_path(); + if (this->enabled() && path.size() > 1) + m_path = path; + } + void set_path(Path &&path) { + assert(path.empty() || path.size() > 1); + this->reset_path(); + if (this->enabled() && path.size() > 1) + m_path = std::move(path); + } + void set_path(SmoothPath &&path, bool reversed); + void offset_path(const Point &v) { m_offset += v; } + + std::string wipe(GCodeGenerator &gcodegen, bool toolchange); + + // Reduce feedrate a bit; travel speed is often too high to move on existing material. + // Too fast = ripping of existing material; too slow = short wipe path, thus more blob. + static double calc_wipe_speed(const GCodeConfig &config) { return config.travel_speed.value * 0.8; } + // Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one + // due to rounding (TODO: test and/or better math for this). + static double calc_xy_to_e_ratio(const GCodeConfig &config, unsigned int extruder_id) + { return 0.95 * floor(config.retract_speed.get_at(extruder_id) + 0.5) / calc_wipe_speed(config); } + +private: + bool m_enabled{ false }; + // Maximum length of a path to accumulate. Only wipes shorter than this threshold will be requested. + double m_wipe_len_max{ 0. }; + Path m_path; + // Offset from m_path to the current PrintObject active. + Point m_offset{ Point::Zero() }; +}; + +// Make a little move inwards before leaving loop. +std::optional wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length); + +} // namespace GCode +} // namespace Slic3r + +#endif // slic3r_GCode_Wipe_hpp_ diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 38242125fa..55ef567a57 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -1,5 +1,5 @@ -#ifndef WipeTower_ -#define WipeTower_ +#ifndef slic3r_GCode_WipeTower_hpp_ +#define slic3r_GCode_WipeTower_hpp_ #include #include @@ -411,4 +411,4 @@ private: } // namespace Slic3r -#endif // WipeTowerPrusaMM_hpp_ +#endif // slic3r_GCode_WipeTower_hpp_ diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp new file mode 100644 index 0000000000..77f497faad --- /dev/null +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -0,0 +1,251 @@ +#include "WipeTowerIntegration.hpp" + +#include "../GCode.hpp" +#include "../libslic3r.h" + +namespace Slic3r::GCode { + +static inline Point wipe_tower_point_to_object_point(GCodeGenerator &gcodegen, const Vec2f& wipe_tower_pt) +{ + return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); +} + +std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const +{ + if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) + throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + + std::string gcode; + + // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) + // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position + float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); + + auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { + Vec2f out = Eigen::Rotation2Df(alpha) * pt; + out += m_wipe_tower_pos; + return out; + }; + + Vec2f start_pos = tcr.start_pos; + Vec2f end_pos = tcr.end_pos; + if (! tcr.priming) { + start_pos = transform_wt_pt(start_pos); + end_pos = transform_wt_pt(end_pos); + } + + Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; + float wipe_tower_rotation = tcr.priming ? 0.f : alpha; + + std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); + + gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't). + + double current_z = gcodegen.writer().get_position().z(); + if (z == -1.) // in case no specific z was provided, print at current_z pos + z = current_z; + + const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id); + const bool will_go_down = ! is_approx(z, current_z); + const bool is_ramming = (gcodegen.config().single_extruder_multi_material && ! tcr.priming) + || (! gcodegen.config().single_extruder_multi_material && gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool)); + const bool should_travel_to_tower = tcr.force_travel // wipe tower says so + || ! needs_toolchange // this is just finishing the tower with no toolchange + || is_ramming; + if (should_travel_to_tower) { + gcode += gcodegen.retract(); + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + gcode += gcodegen.travel_to( + wipe_tower_point_to_object_point(gcodegen, start_pos), + ExtrusionRole::Mixed, + "Travel to a Wipe Tower"); + gcode += gcodegen.unretract(); + } else { + // When this is multiextruder printer without any ramming, we can just change + // the tool without travelling to the tower. + } + + if (will_go_down) { + gcode += gcodegen.writer().retract(); + gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); + gcode += gcodegen.writer().unretract(); + } + + std::string toolchange_gcode_str; + std::string deretraction_str; + if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) { + if (is_ramming) + gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. + toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z + if (gcodegen.config().wipe_tower) + deretraction_str = gcodegen.unretract(); + } + + + + + // Insert the toolchange and deretraction gcode into the generated gcode. + DynamicConfig config; + config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str)); + config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str)); + std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); + unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); + gcode += tcr_gcode; + if (! toolchange_gcode_str.empty() && toolchange_gcode_str.back() != '\n') + toolchange_gcode_str += '\n'; + + // A phony move to the end position at the wipe tower. + gcodegen.writer().travel_to_xy(end_pos.cast()); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); + if (!is_approx(z, current_z)) { + gcode += gcodegen.writer().retract(); + gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); + gcode += gcodegen.writer().unretract(); + } + + else { + // Prepare a future wipe. + // Convert to a smooth path. + Geometry::ArcWelder::Path path; + path.reserve(tcr.wipe_path.size()); + std::transform(tcr.wipe_path.begin(), tcr.wipe_path.end(), std::back_inserter(path), + [&gcodegen, &transform_wt_pt](const Vec2f &wipe_pt) { + return Geometry::ArcWelder::Segment{ wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt)) }; + }); + // Pass to the wipe cache. + gcodegen.m_wipe.set_path(std::move(path)); + } + + // Let the planner know we are traveling between objects. + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + 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::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const +{ + Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast(); + + std::istringstream gcode_str(tcr.gcode); + std::string gcode_out; + std::string line; + Vec2f pos = tcr.start_pos; + Vec2f transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; + Vec2f old_pos(-1000.1f, -1000.1f); + + while (gcode_str) { + std::getline(gcode_str, line); // we read the gcode line by line + + // All G1 commands should be translated and rotated. X and Y coords are + // only pushed to the output when they differ from last time. + // WT generator can override this by appending the never_skip_tag + if (boost::starts_with(line, "G1 ")) { + bool never_skip = false; + auto it = line.find(WipeTower::never_skip_tag()); + if (it != std::string::npos) { + // remove the tag and remember we saw it + never_skip = true; + line.erase(it, it + WipeTower::never_skip_tag().size()); + } + std::ostringstream line_out; + std::istringstream line_str(line); + line_str >> std::noskipws; // don't skip whitespace + char ch = 0; + line_str >> ch >> ch; // read the "G1" + while (line_str >> ch) { + if (ch == 'X' || ch == 'Y') + line_str >> (ch == 'X' ? pos.x() : pos.y()); + else + line_out << ch; + } + + transformed_pos = Eigen::Rotation2Df(angle) * pos + translation; + + if (transformed_pos != old_pos || never_skip) { + line = line_out.str(); + boost::trim_left(line); // Remove leading spaces + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) << "G1"; + if (transformed_pos.x() != old_pos.x() || never_skip) + oss << " X" << transformed_pos.x() - extruder_offset.x(); + if (transformed_pos.y() != old_pos.y() || never_skip) + oss << " Y" << transformed_pos.y() - extruder_offset.y(); + if (! line.empty()) + oss << " "; + line = oss.str() + line; + old_pos = transformed_pos; + } + } + + gcode_out += line + "\n"; + + // If this was a toolchange command, we should change current extruder offset + if (line == "[toolchange_gcode]") { + extruder_offset = m_extruder_offsets[tcr.new_tool].cast(); + + // If the extruder offset changed, add an extra move so everything is continuous + if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) { + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) + << "G1 X" << transformed_pos.x() - extruder_offset.x() + << " Y" << transformed_pos.y() - extruder_offset.y() + << "\n"; + gcode_out += oss.str(); + } + } + } + return gcode_out; +} + + +std::string WipeTowerIntegration::prime(GCodeGenerator &gcodegen) +{ + std::string gcode; + for (const WipeTower::ToolChangeResult& tcr : m_priming) { + if (! tcr.extrusions.empty()) + gcode += append_tcr(gcodegen, tcr, tcr.new_tool); + } + return gcode; +} + +std::string WipeTowerIntegration::tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer) +{ + std::string gcode; + assert(m_layer_idx >= 0); + if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { + if (m_layer_idx < (int)m_tool_changes.size()) { + if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) + throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); + + // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, + // resulting in a wipe tower with sparse layers. + double wipe_tower_z = -1; + bool ignore_sparse = false; + if (gcodegen.config().wipe_tower_no_sparse_layers.value) { + wipe_tower_z = m_last_wipe_tower_print_z; + ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool && m_layer_idx != 0); + if (m_tool_change_idx == 0 && !ignore_sparse) + wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height; + } + + if (!ignore_sparse) { + gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z); + m_last_wipe_tower_print_z = wipe_tower_z; + } + } + } + return gcode; +} + +// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower. +std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen) +{ + std::string gcode; + if (std::abs(gcodegen.writer().get_position().z() - 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; +} + +} // namespace Slic3r::GCode diff --git a/src/libslic3r/GCode/WipeTowerIntegration.hpp b/src/libslic3r/GCode/WipeTowerIntegration.hpp new file mode 100644 index 0000000000..52b27625bb --- /dev/null +++ b/src/libslic3r/GCode/WipeTowerIntegration.hpp @@ -0,0 +1,65 @@ +#ifndef slic3r_GCode_WipeTowerIntegration_hpp_ +#define slic3r_GCode_WipeTowerIntegration_hpp_ + +#include "WipeTower.hpp" +#include "../PrintConfig.hpp" + +namespace Slic3r { + +class GCodeGenerator; + +namespace GCode { + +class WipeTowerIntegration { +public: + WipeTowerIntegration( + const PrintConfig &print_config, + const std::vector &priming, + const std::vector> &tool_changes, + const WipeTower::ToolChangeResult &final_purge) : + m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f), + m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)), + m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)), + m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)), + m_extruder_offsets(print_config.extruder_offset.values), + m_priming(priming), + m_tool_changes(tool_changes), + m_final_purge(final_purge), + m_layer_idx(-1), + m_tool_change_idx(0) + {} + + std::string prime(GCodeGenerator &gcodegen); + void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } + std::string tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer); + std::string finalize(GCodeGenerator &gcodegen); + std::vector used_filament_length() const; + +private: + WipeTowerIntegration& operator=(const WipeTowerIntegration&); + std::string append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const; + + // Postprocesses gcode: rotates and moves G1 extrusions and returns result + std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& 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 Vec2f m_wipe_tower_pos; + const float m_wipe_tower_rotation; + const std::vector m_extruder_offsets; + + // Reference to cached values at the Printer class. + const std::vector &m_priming; + const std::vector> &m_tool_changes; + const WipeTower::ToolChangeResult &m_final_purge; + // Current layer index. + int m_layer_idx; + int m_tool_change_idx; + double m_last_wipe_tower_print_z = 0.f; +}; + +} // namespace GCode +} // namespace Slic3r + +#endif // slic3r_GCode_WipeTowerIntegration_hpp_ diff --git a/src/libslic3r/Geometry/ArcWelder.cpp b/src/libslic3r/Geometry/ArcWelder.cpp new file mode 100644 index 0000000000..7a5421699d --- /dev/null +++ b/src/libslic3r/Geometry/ArcWelder.cpp @@ -0,0 +1,674 @@ +// The following code for merging circles into arches originates from https://github.com/FormerLurker/ArcWelderLib + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Arc Welder: Anti-Stutter Library +// +// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. +// This reduces file size and the number of gcodes per second. +// +// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality. +// +// Copyright(C) 2021 - Brad Hochgesang +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// This program is free software : you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +// GNU Affero General Public License for more details. +// +// +// You can contact the author at the following email address: +// FormerLurker@pm.me +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "ArcWelder.hpp" +#include "Circle.hpp" + +#include "../MultiPoint.hpp" +#include "../Polygon.hpp" + +#include +#include +#include + +namespace Slic3r { namespace Geometry { namespace ArcWelder { + +Points arc_discretize(const Point &p1, const Point &p2, const double radius, const bool ccw, const double deviation) +{ + Vec2d center = arc_center(p1.cast(), p2.cast(), radius, ccw); + double angle = arc_angle(p1.cast(), p2.cast(), radius); + assert(angle > 0); + + double r = std::abs(radius); + size_t num_steps = arc_discretization_steps(r, angle, deviation); + double angle_step = angle / num_steps; + + Points out; + out.reserve(num_steps + 1); + out.emplace_back(p1); + if (! ccw) + angle_step *= -1.; + for (size_t i = 1; i < num_steps; ++ i) + out.emplace_back(p1.rotated(angle_step * i, center.cast())); + out.emplace_back(p2); + return out; +} + +struct Circle +{ + Point center; + double radius; +}; + +// Interpolate three points with a circle. +// Returns false if the three points are collinear or if the radius is bigger than maximum allowed radius. +static std::optional try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius) +{ + if (auto center = Slic3r::Geometry::try_circle_center(p1.cast(), p2.cast(), p3.cast(), SCALED_EPSILON); center) { + Point c = center->cast(); + if (double r = sqrt(double((c - p1).cast().squaredNorm())); r <= max_radius) + return std::make_optional({ c, float(r) }); + } + return {}; +} + +// Returns a closest point on the segment. +// Returns false if the closest point is not inside the segment, but at its boundary. +static bool foot_pt_on_segment(const Point &p1, const Point &p2, const Point &pt, Point &out) +{ + Vec2i64 v21 = (p2 - p1).cast(); + int64_t l2 = v21.squaredNorm(); + if (l2 > int64_t(SCALED_EPSILON)) { + if (int64_t t = (pt - p1).cast().dot(v21); + t >= int64_t(SCALED_EPSILON) && t < l2 - int64_t(SCALED_EPSILON)) { + out = p1 + ((double(t) / double(l2)) * v21.cast()).cast(); + return true; + } + } + // The segment is short or the closest point is an end point. + return false; +} + +static inline bool circle_approximation_sufficient(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance) +{ + // The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated. + assert(std::abs((*begin - circle.center).cast().norm() - circle.radius) < SCALED_EPSILON); + assert(std::abs((*std::prev(end) - circle.center).cast().norm() - circle.radius) < SCALED_EPSILON); + assert(end - begin >= 3); + + // Test the 1st point. + if (double distance_from_center = (*begin - circle.center).cast().norm(); + std::abs(distance_from_center - circle.radius) > tolerance) + return false; + + for (auto it = std::next(begin); it != end; ++ it) { + if (double distance_from_center = (*it - circle.center).cast().norm(); + std::abs(distance_from_center - circle.radius) > tolerance) + return false; + Point closest_point; + if (foot_pt_on_segment(*std::prev(it), *it, circle.center, closest_point)) { + if (double distance_from_center = (closest_point - circle.center).cast().norm(); + std::abs(distance_from_center - circle.radius) > tolerance) + return false; + } + } + return true; +} + +static inline bool get_deviation_sum_squared(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance, double &total_deviation) +{ + // The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated. + assert(std::abs((*begin - circle.center).cast().norm() - circle.radius) < SCALED_EPSILON); + assert(std::abs((*std::prev(end) - circle.center).cast().norm() - circle.radius) < SCALED_EPSILON); + assert(end - begin >= 3); + + total_deviation = 0; + + const double tolerance2 = sqr(tolerance); + for (auto it = std::next(begin); std::next(it) != end; ++ it) + if (double deviation2 = sqr((*it - circle.center).cast().norm() - circle.radius); deviation2 > tolerance2) + return false; + else + total_deviation += deviation2; + + for (auto it = begin; std::next(it) != end; ++ it) { + Point closest_point; + if (foot_pt_on_segment(*it, *std::next(it), circle.center, closest_point)) { + if (double deviation2 = sqr((closest_point - circle.center).cast().norm() - circle.radius); deviation2 > tolerance2) + return false; + else + total_deviation += deviation2; + } + } + + return true; +} + +static std::optional try_create_circle(const Points::const_iterator begin, const Points::const_iterator end, const double max_radius, const double tolerance) +{ + std::optional out; + size_t size = end - begin; + if (size == 3) { + out = try_create_circle(*begin, *std::next(begin), *std::prev(end), max_radius); + if (out && ! circle_approximation_sufficient(*out, begin, end, tolerance)) + out.reset(); + } else { +#if 0 + size_t ipivot = size / 2; + // Take a center difference of points at the center of the path. + //FIXME does it really help? For short arches, the linear interpolation may be + Point pivot = (size % 2 == 0) ? (*(begin + ipivot) + *(begin + ipivot - 1)) / 2 : + (*(begin + ipivot - 1) + *(begin + ipivot + 1)) / 2; + if (std::optional circle = try_create_circle(*begin, pivot, *std::prev(end), max_radius); + circle && circle_approximation_sufficient(*circle, begin, end, tolerance)) + return circle; +#endif + // Find the circle with the least deviation, if one exists. + double least_deviation = std::numeric_limits::max(); + double current_deviation; + for (auto it = std::next(begin); std::next(it) != end; ++ it) + if (std::optional circle = try_create_circle(*begin, *it, *std::prev(end), max_radius); + circle && get_deviation_sum_squared(*circle, begin, end, tolerance, current_deviation)) { + if (current_deviation < least_deviation) { + out = circle; + least_deviation = current_deviation; + } + } + } + return out; +} + +// ported from ArcWelderLib/ArcWelder/segmented/shape.h class "arc" +class Arc { +public: + Point start_point; + Point end_point; + Point center; + double radius; + Orientation direction { Orientation::Unknown }; +}; + +static inline int sign(const int64_t i) +{ + return i > 0 ? 1 : i < 0 ? -1 : 0; +} + +// Return orientation of a polyline with regard to the center. +// Successive points are expected to take less than a PI angle step. +Orientation arc_orientation( + const Point ¢er, + const Points::const_iterator begin, + const Points::const_iterator end) +{ + assert(end - begin >= 3); + // Assumption: Two successive points of a single segment span an angle smaller than PI. + Vec2i64 vstart = (*begin - center).cast(); + Vec2i64 vprev = vstart; + int arc_dir = 0; + for (auto it = std::next(begin); it != end; ++ it) { + Vec2i64 v = (*it - center).cast(); + int dir = sign(cross2(vprev, v)); + if (dir == 0) { + // Ignore radial segments. + } else if (arc_dir * dir < 0) { + // The path turns back and overextrudes. Such path is likely invalid, but the arc interpolation should + // rather maintain such an invalid path instead of covering it up. + // Don't replace such a path with an arc. + return {}; + } else { + // Success, either establishing the direction for the first time, or moving in the same direction as the last time. + arc_dir = dir; + vprev = v; + } + } + return arc_dir == 0 ? + // All points are radial wrt. the center, this is unexpected. + Orientation::Unknown : + // Arc is valid, either CCW or CW. + arc_dir > 0 ? Orientation::CCW : Orientation::CW; +} + +static inline std::optional try_create_arc_impl( + const Circle &circle, + const Points::const_iterator begin, + const Points::const_iterator end, + const double tolerance, + const double path_tolerance_percent) +{ + assert(end - begin >= 3); + // Assumption: Two successive points of a single segment span an angle smaller than PI. + Orientation orientation = arc_orientation(circle.center, begin, end); + if (orientation == Orientation::Unknown) + return {}; + + Vec2i64 vstart = (*begin - circle.center).cast(); + Vec2i64 vend = (*std::prev(end) - circle.center).cast(); + double angle = atan2(double(cross2(vstart, vend)), double(vstart.dot(vend))); + if (orientation == Orientation::CW) + angle *= -1.; + if (angle < 0) + angle += 2. * M_PI; + assert(angle >= 0. && angle < 2. * M_PI + EPSILON); + + // Check the length against the original length. + // This can trigger simply due to the differing path lengths + // but also could indicate that the vector calculation above + // got wrong direction + const double arc_length = circle.radius * angle; + const double approximate_length = length(begin, end); + assert(approximate_length > 0); + const double arc_length_difference_relative = (arc_length - approximate_length) / approximate_length; + + if (std::fabs(arc_length_difference_relative) >= path_tolerance_percent) { + return {}; + } else { + assert(circle_approximation_sufficient(circle, begin, end, tolerance + SCALED_EPSILON)); + return std::make_optional(Arc{ + *begin, + *std::prev(end), + circle.center, + angle > M_PI ? - circle.radius : circle.radius, + orientation + }); + } +} + +static inline std::optional try_create_arc( + const Points::const_iterator begin, + const Points::const_iterator end, + double max_radius = default_scaled_max_radius, + double tolerance = default_scaled_resolution, + double path_tolerance_percent = default_arc_length_percent_tolerance) +{ + std::optional circle = try_create_circle(begin, end, max_radius, tolerance); + if (! circle) + return {}; + return try_create_arc_impl(*circle, begin, end, tolerance, path_tolerance_percent); +} + +float arc_angle(const Vec2f &start_pos, const Vec2f &end_pos, Vec2f ¢er_pos, bool is_ccw) +{ + if ((end_pos - start_pos).squaredNorm() < sqr(1e-6)) { + // If start equals end, full circle is considered. + return float(2. * M_PI); + } else { + Vec2f v1 = start_pos - center_pos; + Vec2f v2 = end_pos - center_pos; + if (! is_ccw) + std::swap(v1, v2); + float radian = atan2(cross2(v1, v2), v1.dot(v2)); + return radian < 0 ? float(2. * M_PI) + radian : radian; + } +} + +float arc_length(const Vec2f &start_pos, const Vec2f &end_pos, Vec2f ¢er_pos, bool is_ccw) +{ + return (center_pos - start_pos).norm() * arc_angle(start_pos, end_pos, center_pos, is_ccw); +} + +// Reduces polyline in the (begin, end, begin, tolerance, [](const Segment &s) { return s.point; }); +} + +Path fit_path(const Points &src, double tolerance, double fit_circle_percent_tolerance) +{ + assert(tolerance >= 0); + assert(fit_circle_percent_tolerance >= 0); + + Path out; + out.reserve(src.size()); + if (tolerance <= 0 || src.size() <= 2) { + // No simplification, just convert. + std::transform(src.begin(), src.end(), std::back_inserter(out), [](const Point &p) -> Segment { return { p }; }); + } else if (fit_circle_percent_tolerance <= 0) { + // Convert and simplify to a polyline. + std::transform(src.begin(), src.end(), std::back_inserter(out), [](const Point &p) -> Segment { return { p }; }); + out.erase(douglas_peucker_in_place(out.begin(), out.end(), tolerance), out.end()); + } else { + // Perform simplification & fitting. + // Index of the start of a last polyline, which has not yet been decimated. + int begin_pl_idx = 0; + out.push_back({ src.front(), 0.f }); + for (auto it = std::next(src.begin()); it != src.end();) { + // Minimum 2 additional points required for circle fitting. + auto begin = std::prev(it); + auto end = std::next(it); + assert(end <= src.end()); + std::optional arc; + while (end != src.end()) { + auto next_end = std::next(end); + if (std::optional this_arc = try_create_arc( + begin, next_end, + ArcWelder::default_scaled_max_radius, + tolerance, fit_circle_percent_tolerance); + this_arc) { + assert(this_arc->direction != Orientation::Unknown); + arc = this_arc; + end = next_end; + } else + break; + } + if (arc) { + // If there is a trailing polyline, decimate it first before saving a new arc. + if (out.size() - begin_pl_idx > 2) { + // Decimating linear segmens only. + assert(std::all_of(out.begin() + begin_pl_idx + 1, out.end(), [](const Segment &seg) { return seg.linear(); })); + out.erase(douglas_peucker_in_place(out.begin() + begin_pl_idx, out.end(), tolerance), out.end()); + assert(out.back().linear()); + } + // Save the index of an end of the new circle segment, which may become the first point of a possible future polyline. + begin_pl_idx = int(out.size()); + // This will be the next point to try to add. + it = end; + // Add the new arc. + assert(*begin == arc->start_point); + assert(*std::prev(it) == arc->end_point); + assert(out.back().point == arc->start_point); + out.push_back({ arc->end_point, float(arc->radius), arc->direction }); +#if 0 + // Verify that all the source points are at tolerance distance from the interpolated path. + { + const Segment &seg_start = *std::prev(std::prev(out.end())); + const Segment &seg_end = out.back(); + const Vec2d center = arc_center(seg_start.point.cast(), seg_end.point.cast(), double(seg_end.radius), seg_end.ccw()); + assert(seg_start.point == *begin); + assert(seg_end.point == *std::prev(end)); + assert(arc_orientation(center.cast(), begin, end) == arc->direction); + for (auto it = std::next(begin); it != end; ++ it) { + Point ptstart = *std::prev(it); + Point ptend = *it; + Point closest_point; + if (foot_pt_on_segment(ptstart, ptend, center.cast(), closest_point)) { + double distance_from_center = (closest_point.cast() - center).norm(); + assert(std::abs(distance_from_center - std::abs(seg_end.radius)) < tolerance + SCALED_EPSILON); + } + Vec2d v = (ptend - ptstart).cast(); + double len = v.norm(); + auto num_segments = std::min(10, ceil(2. * len / fit_circle_percent_tolerance)); + for (size_t i = 0; i < num_segments; ++ i) { + Point p = ptstart + (v * (double(i) / double(num_segments))).cast(); + assert(i == 0 || inside_arc_wedge(seg_start.point.cast(), seg_end.point.cast(), center, seg_end.radius > 0, seg_end.ccw(), p.cast())); + double d2 = sqr((p.cast() - center).norm() - std::abs(seg_end.radius)); + assert(d2 < sqr(tolerance + SCALED_EPSILON)); + } + } + } +#endif + } else { + // Arc is not valid, append a linear segment. + out.push_back({ *it ++ }); + } + } + if (out.size() - begin_pl_idx > 2) + // Do the final polyline decimation. + out.erase(douglas_peucker_in_place(out.begin() + begin_pl_idx, out.end(), tolerance), out.end()); + } + +#if 0 + // Verify that all the source points are at tolerance distance from the interpolated path. + for (auto it = std::next(src.begin()); it != src.end(); ++ it) { + Point start = *std::prev(it); + Point end = *it; + Vec2d v = (end - start).cast(); + double len = v.norm(); + auto num_segments = std::min(10, ceil(2. * len / fit_circle_percent_tolerance)); + for (size_t i = 0; i <= num_segments; ++ i) { + Point p = start + (v * (double(i) / double(num_segments))).cast(); + PathSegmentProjection proj = point_to_path_projection(out, p); + assert(proj.valid()); + assert(proj.distance2 < sqr(tolerance + SCALED_EPSILON)); + } + } +#endif + + return out; +} + +void reverse(Path &path) +{ + if (path.size() > 1) { + auto prev = path.begin(); + for (auto it = std::next(prev); it != path.end(); ++ it) { + prev->radius = it->radius; + prev->orientation = it->orientation == Orientation::CCW ? Orientation::CW : Orientation::CCW; + prev = it; + } + path.back().radius = 0; + std::reverse(path.begin(), path.end()); + } +} + +double clip_start(Path &path, const double len) +{ + reverse(path); + double remaining = clip_end(path, len); + reverse(path); + // Return remaining distance to go. + return remaining; +} + +double clip_end(Path &path, double distance) +{ + while (distance > 0) { + Segment &last = path.back(); + path.pop_back(); + if (path.empty()) + break; + if (last.linear()) { + // Linear segment + Vec2d v = (path.back().point - last.point).cast(); + double lsqr = v.squaredNorm(); + if (lsqr > sqr(distance)) { + path.push_back({ last.point + (v * (distance / sqrt(lsqr))).cast() }); + // Length to go is zero. + return 0; + } + distance -= sqrt(lsqr); + } else { + // Circular segment + double angle = arc_angle(path.back().point.cast(), last.point.cast(), last.radius); + double len = std::abs(last.radius) * angle; + if (len > distance) { + // Rotate the segment end point in reverse towards the start point. + if (last.ccw()) + angle *= -1.; + path.push_back({ + last.point.rotated(angle * (distance / len), + arc_center(path.back().point.cast(), last.point.cast(), double(last.radius), last.ccw()).cast()), + last.radius, last.orientation }); + // Length to go is zero. + return 0; + } + distance -= len; + } + } + + // Return remaining distance to go. + assert(distance >= 0); + return distance; +} + +PathSegmentProjection point_to_path_projection(const Path &path, const Point &point, double search_radius2) +{ + assert(path.size() != 1); + // initialized to "invalid" state. + PathSegmentProjection out; + out.distance2 = search_radius2; + if (path.size() < 2 || path.front().point == point) { + // First point is the closest point. + if (path.empty()) { + // No closest point available. + } else if (const Point p0 = path.front().point; p0 == point) { + out.segment_id = 0; + out.point = p0; + out.distance2 = 0; + } else if (double d2 = (p0 - point).cast().squaredNorm(); d2 < out.distance2) { + out.segment_id = 0; + out.point = p0; + out.distance2 = d2; + } + } else { + assert(path.size() >= 2); + // min_point_it will contain an end point of a segment with a closest projection found + // or path.cbegin() if no such closest projection closer than search_radius2 was found. + auto min_point_it = path.cbegin(); + Point prev = path.front().point; + for (auto it = std::next(path.cbegin()); it != path.cend(); ++ it) { + if (it->linear()) { + // Linear segment + Point proj; + // distance_to_squared() will possibly return the start or end point of a line segment. + if (double d2 = line_alg::distance_to_squared(Line(prev, it->point), point, &proj); d2 < out.distance2) { + out.point = proj; + out.distance2 = d2; + min_point_it = it; + } + } else { + // Circular arc + Vec2i64 center = arc_center(prev.cast(), it->point.cast(), double(it->radius), it->ccw()).cast(); + // Test whether point is inside the wedge. + Vec2i64 v1 = prev.cast() - center; + Vec2i64 v2 = it->point.cast() - center; + Vec2i64 vp = point.cast() - center; + if (inside_arc_wedge_vectors(v1, v2, it->radius > 0, it->ccw(), vp)) { + // Distance of the radii. + const auto r = double(std::abs(it->radius)); + const auto rtest = sqrt(double(vp.squaredNorm())); + if (double d2 = sqr(rtest - r); d2 < out.distance2) { + if (rtest > SCALED_EPSILON) + // Project vp to the arc. + out.point = center.cast() + (vp.cast() * (r / rtest)).cast(); + else + // Test point is very close to the center of the radius. Any point of the arc is the closest. + // Pick the start. + out.point = prev; + out.distance2 = d2; + out.center = center.cast(); + min_point_it = it; + } + } else { + // Distance to the start point. + if (double d2 = double((v1 - vp).squaredNorm()); d2 < out.distance2) { + out.point = prev; + out.distance2 = d2; + min_point_it = it; + } + } + } + prev = it->point; + } + if (! path.back().linear()) { + // Calculate distance to the end point. + if (double d2 = (path.back().point - point).cast().squaredNorm(); d2 < out.distance2) { + out.point = path.back().point; + out.distance2 = d2; + min_point_it = std::prev(path.end()); + } + } + // If a new closes point was found, it is closer than search_radius2. + assert((min_point_it == path.cbegin()) == (out.distance2 == search_radius2)); + // Output is not valid yet. + assert(! out.valid()); + if (min_point_it != path.cbegin()) { + // Make it valid by setting the segment. + out.segment_id = std::prev(min_point_it) - path.begin(); + assert(out.valid()); + } + } + + assert(! out.valid() || (out.segment_id >= 0 && out.segment_id < path.size())); + return out; +} + +std::pair split_at(const Path &path, const PathSegmentProjection &proj, const double min_segment_length) +{ + assert(proj.valid()); + assert(! proj.valid() || (proj.segment_id >= 0 && proj.segment_id < path.size())); + assert(path.size() > 1); + std::pair out; + if (! proj.valid() || proj.segment_id + 1 == path.size() || (proj.segment_id + 2 == path.size() && proj.point == path.back().point)) + out.first = path; + else if (proj.segment_id == 0 && proj.point == path.front().point) + out.second = path; + else { + // Path will likely be split to two pieces. + assert(proj.valid() && proj.segment_id >= 0 && proj.segment_id + 1 < path.size()); + const Segment &start = path[proj.segment_id]; + const Segment &end = path[proj.segment_id + 1]; + bool split_segment = true; + int split_segment_id = proj.segment_id; + if (int64_t d2 = (proj.point - start.point).cast().squaredNorm(); d2 < sqr(min_segment_length)) { + split_segment = false; + int64_t d22 = (proj.point - end.point).cast().squaredNorm(); + if (d22 < d2) + // Split at the end of the segment. + ++ split_segment_id; + } else if (int64_t d2 = (proj.point - end.point).cast().squaredNorm(); d2 < sqr(min_segment_length)) { + ++ split_segment_id; + split_segment = false; + } + if (split_segment) { + out.first.assign(path.begin(), path.begin() + split_segment_id + 2); + out.second.assign(path.begin() + split_segment_id, path.end()); + assert(out.first[out.first.size() - 2] == start); + assert(out.first.back() == end); + assert(out.second.front() == start); + assert(out.second[1] == end); + assert(out.first.size() + out.second.size() == path.size() + 2); + assert(out.first.back().radius == out.second[1].radius); + out.first.back().point = proj.point; + out.second.front().point = proj.point; + if (end.radius < 0) { + // A large arc (> PI) was split. + // At least one of the two arches that were created by splitting the original arch will become smaller. + // Make the radii of those arches that became < PI positive. + // In case of a projection onto an arc, proj.center should be filled in and valid. + auto vstart = (start.point - proj.center).cast(); + auto vend = (end.point - proj.center).cast(); + auto vproj = (proj.point - proj.center).cast(); + if ((cross2(vstart, vproj) > 0) == end.ccw()) + // Make the radius of a minor arc positive. + out.first.back().radius *= -1.f; + if ((cross2(vproj, vend) > 0) == end.ccw()) + // Make the radius of a minor arc positive. + out.second[1].radius *= -1.f; + assert(out.first.size() > 1); + assert(out.second.size() > 1); + out.second.front().radius = 0; + } + } else { + assert(split_segment_id >= 0 && split_segment_id < path.size()); + if (split_segment_id + 1 == int(path.size())) + out.first = path; + else if (split_segment_id == 0) + out.second = path; + else { + // Split at the start of proj.segment_id. + out.first.assign(path.begin(), path.begin() + split_segment_id + 1); + out.second.assign(path.begin() + split_segment_id, path.end()); + assert(out.first.size() + out.second.size() == path.size() + 1); + assert(out.first.back() == (split_segment_id == proj.segment_id ? start : end)); + assert(out.second.front() == (split_segment_id == proj.segment_id ? start : end)); + assert(out.first.size() > 1); + assert(out.second.size() > 1); + out.second.front().radius = 0; + } + } + } + + return out; +} + +std::pair split_at(const Path &path, const Point &point, const double min_segment_length) +{ + return split_at(path, point_to_path_projection(path, point), min_segment_length); +} + +} } } // namespace Slic3r::Geometry::ArcWelder diff --git a/src/libslic3r/Geometry/ArcWelder.hpp b/src/libslic3r/Geometry/ArcWelder.hpp new file mode 100644 index 0000000000..dfc6d886e6 --- /dev/null +++ b/src/libslic3r/Geometry/ArcWelder.hpp @@ -0,0 +1,324 @@ +#ifndef slic3r_Geometry_ArcWelder_hpp_ +#define slic3r_Geometry_ArcWelder_hpp_ + +#include + +#include "../Point.hpp" + +namespace Slic3r { namespace Geometry { namespace ArcWelder { + +// Calculate center point of an arc given two points and a radius. +// positive radius: take shorter arc +// negative radius: take longer arc +// radius must NOT be zero! +template +inline Eigen::Matrix arc_center( + const Eigen::MatrixBase &start_pos, + const Eigen::MatrixBase &end_pos, + const Float radius, + const bool is_ccw) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_center(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_center(): second parameter is not a 2D vector"); + static_assert(std::is_same::value, "arc_center(): Both vectors must be of the same type."); + static_assert(std::is_same::value, "arc_center(): Radius must be of the same type as the vectors."); + assert(radius != 0); + using Vector = Eigen::Matrix; + auto v = end_pos - start_pos; + Float q2 = v.squaredNorm(); + assert(q2 > 0); + Float t2 = sqr(radius) / q2 - Float(.25f); + // If the start_pos and end_pos are nearly antipodal, t2 may become slightly negative. + // In that case return a centroid of start_point & end_point. + Float t = t2 > 0 ? sqrt(t2) : Float(0); + auto mid = Float(0.5) * (start_pos + end_pos); + Vector vp{ -v.y() * t, v.x() * t }; + return (radius > Float(0)) == is_ccw ? (mid + vp).eval() : (mid - vp).eval(); +} + +// Calculate angle of an arc given two points and a radius. +// Returned angle is in the range <0, 2 PI) +// positive radius: take shorter arc +// negative radius: take longer arc +// radius must NOT be zero! +template +inline typename Derived::Scalar arc_angle( + const Eigen::MatrixBase &start_pos, + const Eigen::MatrixBase &end_pos, + const typename Derived::Scalar radius) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_angle(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_angle(): second parameter is not a 2D vector"); + static_assert(std::is_same::value, "arc_angle(): Both vectors must be of the same type."); + assert(radius != 0); + using Float = typename Derived::Scalar; + Float a = Float(0.5) * (end_pos - start_pos).norm() / radius; + return radius > Float(0) ? + // acute angle: + (a > Float( 1.) ? Float(M_PI) : Float(2.) * std::asin(a)) : + // obtuse angle: + (a < Float(-1.) ? Float(M_PI) : Float(2. * M_PI) + Float(2.) * std::asin(a)); +} + +// Calculate positive length of an arc given two points and a radius. +// positive radius: take shorter arc +// negative radius: take longer arc +// radius must NOT be zero! +template +inline typename Derived::Scalar arc_length( + const Eigen::MatrixBase &start_pos, + const Eigen::MatrixBase &end_pos, + const typename Derived::Scalar radius) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_length(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): second parameter is not a 2D vector"); + static_assert(std::is_same::value, "arc_length(): Both vectors must be of the same type."); + assert(radius != 0); + return arc_angle(start_pos, end_pos, radius) * std::abs(radius); +} + +// Calculate positive length of an arc given two points, center and orientation. +template +inline typename Derived::Scalar arc_length( + const Eigen::MatrixBase &start_pos, + const Eigen::MatrixBase &end_pos, + const Eigen::MatrixBase ¢er_pos, + const bool ccw) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_length(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): second parameter is not a 2D vector"); + static_assert(Derived3::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): third parameter is not a 2D vector"); + static_assert(std::is_same::value && + std::is_same::value, "arc_length(): All third points must be of the same type."); + using Float = typename Derived::Scalar; + auto vstart = start_pos - center_pos; + auto vend = end_pos - center_pos; + Float radius = vstart.norm(); + Float angle = atan2(double(cross2(vstart, vend)), double(vstart.dot(vend))); + if (! ccw) + angle *= Float(-1.); + if (angle < 0) + angle += Float(2. * M_PI); + assert(angle >= Float(0.) && angle < Float(2. * M_PI + EPSILON)); + return angle * radius; +} + +// Test whether a point is inside a wedge of an arc. +template +inline bool inside_arc_wedge_vectors( + const Eigen::MatrixBase &start_vec, + const Eigen::MatrixBase &end_vec, + const bool shorter_arc, + const bool ccw, + const Eigen::MatrixBase &query_vec) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): start_vec is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): end_vec is not a 2D vector"); + static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): query_vec is not a 2D vector"); + static_assert(std::is_same::value && + std::is_same::value, "inside_arc_wedge_vectors(): All vectors must be of the same type."); + return shorter_arc ? + // Smaller (convex) wedge. + (ccw ? + cross2(start_vec, query_vec) > 0 && cross2(query_vec, end_vec) > 0 : + cross2(start_vec, query_vec) < 0 && cross2(query_vec, end_vec) < 0) : + // Larger (concave) wedge. + (ccw ? + cross2(end_vec, query_vec) < 0 || cross2(query_vec, start_vec) < 0 : + cross2(end_vec, query_vec) > 0 || cross2(query_vec, start_vec) > 0); +} + +template +inline bool inside_arc_wedge( + const Eigen::MatrixBase &start_pt, + const Eigen::MatrixBase &end_pt, + const Eigen::MatrixBase ¢er_pt, + const bool shorter_arc, + const bool ccw, + const Eigen::MatrixBase &query_pt) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge(): start_pt is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge(): end_pt is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge(): center_pt is not a 2D vector"); + static_assert(Derived3::IsVectorAtCompileTime && int(Derived4::SizeAtCompileTime) == 2, "inside_arc_wedge(): query_pt is not a 2D vector"); + static_assert(std::is_same::value && + std::is_same::value && + std::is_same::value, "inside_arc_wedge(): All vectors must be of the same type."); + return inside_arc_wedge_vectors(start_pt - center_pt, end_pt - center_pt, shorter_arc, ccw, query_pt - center_pt); +} + +template +inline bool inside_arc_wedge( + const Eigen::MatrixBase &start_pt, + const Eigen::MatrixBase &end_pt, + const Float radius, + const bool ccw, + const Eigen::MatrixBase &query_pt) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge(): start_pt is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge(): end_pt is not a 2D vector"); + static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge(): query_pt is not a 2D vector"); + static_assert(std::is_same::value && + std::is_same::value && + std::is_same::value, "inside_arc_wedge(): All vectors + radius must be of the same type."); + return inside_arc_wedge(start_pt, end_pt, + arc_center(start_pt, end_pt, radius, ccw), + radius > 0, ccw, query_pt); +} + +// Return number of linear segments necessary to interpolate arc of a given positive radius and positive angle to satisfy +// maximum deviation of an interpolating polyline from an analytic arc. +template +size_t arc_discretization_steps(const FloatType radius, const FloatType angle, const FloatType deviation) +{ + assert(radius > 0); + assert(angle > 0); + assert(angle <= FloatType(2. * M_PI)); + assert(deviation > 0); + + FloatType d = radius - deviation; + return d < EPSILON ? + // Radius smaller than deviation. + ( // Acute angle: a single segment interpolates the arc with sufficient accuracy. + angle < M_PI || + // Obtuse angle: Test whether the furthest point (center) of an arc is closer than deviation to the center of a line segment. + radius * (FloatType(1.) + cos(M_PI - FloatType(.5) * angle)) < deviation ? + // Single segment is sufficient + 1 : + // Two segments are necessary, the middle point is at the center of the arc. + 2) : + size_t(ceil(angle / (2. * acos(d / radius)))); +} + +// Discretize arc given the radius, orientation and maximum deviation from the arc. +// Returned polygon starts with p1, ends with p2 and it is discretized to guarantee the maximum deviation. +Points arc_discretize(const Point &p1, const Point &p2, const double radius, const bool ccw, const double deviation); + +// 1.2m diameter, maximum given by coord_t +static constexpr const double default_scaled_max_radius = scaled(600.); +// 0.05mm +static constexpr const double default_scaled_resolution = scaled(0.05); +// 5 percent +static constexpr const double default_arc_length_percent_tolerance = 0.05; + +enum class Orientation : unsigned char { + Unknown, + CCW, + CW, +}; + +// Returns orientation of a polyline with regard to the center. +// Successive points are expected to take less than a PI angle step. +// Returns Orientation::Unknown if the orientation with regard to the center +// is not monotonous. +Orientation arc_orientation( + const Point ¢er, + const Points::const_iterator begin, + const Points::const_iterator end); + +// Single segment of a smooth path. +struct Segment +{ + // End point of a linear or circular segment. + // Start point is provided by the preceding segment. + Point point; + // Radius of a circular segment. Positive - take the shorter arc. Negative - take the longer arc. Zero - linear segment. + float radius{ 0.f }; + // CCW or CW. Ignored for zero radius (linear segment). + Orientation orientation{ Orientation::CCW }; + + bool linear() const { return radius == 0; } + bool ccw() const { return orientation == Orientation::CCW; } + bool cw() const { return orientation == Orientation::CW; } +}; + +inline bool operator==(const Segment &lhs, const Segment &rhs) { + return lhs.point == rhs.point && lhs.radius == rhs.radius && lhs.orientation == rhs.orientation; +} + +using Segments = std::vector; +using Path = Segments; + +// Interpolate polyline path with a sequence of linear / circular segments given the interpolation tolerance. +// Only convert to polyline if zero tolerance. +// Convert to polyline and decimate polyline if zero fit_circle_percent_tolerance. +// Path fitting is inspired with the arc fitting algorithm in +// Arc Welder: Anti-Stutter Library by Brad Hochgesang FormerLurker@pm.me +// https://github.com/FormerLurker/ArcWelderLib +Path fit_path(const Points &points, double tolerance, double fit_circle_percent_tolerance); + +// Decimate polyline into a smooth path structure using Douglas-Peucker polyline decimation algorithm. +inline Path fit_polyline(const Points &points, double tolerance) { return fit_path(points, tolerance, 0.); } + +template +inline FloatType segment_length(const Segment &start, const Segment &end) +{ + return end.linear() ? + (end.point - start.point).cast().norm() : + arc_length(start.point.cast(), end.point.cast(), FloatType(end.radius)); +} + +template +inline FloatType path_length(const Path &path) +{ + FloatType len = 0; + for (size_t i = 1; i < path.size(); ++ i) + len += segment_length(path[i - 1], path[i]); + return len; +} + +// Estimate minimum path length of a segment cheaply without having to calculate center of an arc and it arc length. +// Used for caching a smooth path chunk that is certainly longer than a threshold. +inline int64_t estimate_min_segment_length(const Segment &start, const Segment &end) +{ + if (end.linear() || end.radius > 0) { + // Linear segment or convex wedge, take the larger X or Y component. + Point v = (end.point - start.point).cwiseAbs(); + return std::max(v.x(), v.y()); + } else { + // Arc with angle > PI. + // Returns estimate of PI * r + return - int64_t(3) * int64_t(end.radius); + } +} + +// Estimate minimum path length cheaply without having to calculate center of an arc and it arc length. +// Used for caching a smooth path chunk that is certainly longer than a threshold. +inline int64_t estimate_path_length(const Path &path) +{ + int64_t len = 0; + for (size_t i = 1; i < path.size(); ++ i) + len += Geometry::ArcWelder::estimate_min_segment_length(path[i - 1], path[i]); + return len; +} + +void reverse(Path &path); + +// Clip start / end of a smooth path by len. +// If path is shorter than len, remaining path length to trim will be returned. +double clip_start(Path &path, const double len); +double clip_end(Path &path, const double len); + +struct PathSegmentProjection +{ + // Start segment of a projection on the path. + size_t segment_id { std::numeric_limits::max() }; + // Projection of the point on the segment. + Point point { 0, 0 }; + // If the point lies on an arc, the arc center is cached here. + Point center { 0, 0 }; + // Square of a distance of the projection. + double distance2 { std::numeric_limits::max() }; + + bool valid() const { return this->segment_id != std::numeric_limits::max(); } +}; +// Returns closest segment and a parameter along the closest segment of a path to a point. +PathSegmentProjection point_to_path_projection(const Path &path, const Point &point, double search_radius2 = std::numeric_limits::max()); +// Split a path into two paths at a segment point. Snap to an existing point if the projection of "point is closer than min_segment_length. +std::pair split_at(const Path &path, const PathSegmentProjection &proj, const double min_segment_length); +// Split a path into two paths at a point closest to "point". Snap to an existing point if the projection of "point is closer than min_segment_length. +std::pair split_at(const Path &path, const Point &point, const double min_segment_length); + +} } } // namespace Slic3r::Geometry::ArcWelder + +#endif // slic3r_Geometry_ArcWelder_hpp_ diff --git a/src/libslic3r/Geometry/Circle.cpp b/src/libslic3r/Geometry/Circle.cpp index 6796671954..cc222aa155 100644 --- a/src/libslic3r/Geometry/Circle.cpp +++ b/src/libslic3r/Geometry/Circle.cpp @@ -17,9 +17,14 @@ Point circle_center_taubin_newton(const Points::const_iterator& input_begin, con return Point::new_scale(center.x(), center.y()); } -/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126 -/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end -/// lie on. +// Robust and accurate algebraic circle fit, which works well even if data points are observed only within a small arc. +// The method was proposed by G. Taubin in +// "Estimation Of Planar Curves, Surfaces And Nonplanar Space Curves Defined By Implicit Equations, +// With Applications To Edge And Range Image Segmentation", IEEE Trans. PAMI, Vol. 13, pages 1115-1138, (1991)." +// This particular implementation was adapted from +// "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126" +// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end +// lie on. Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles) { // calculate the centroid of the data set diff --git a/src/libslic3r/Geometry/Circle.hpp b/src/libslic3r/Geometry/Circle.hpp index 653102e2ab..59d3c3b31b 100644 --- a/src/libslic3r/Geometry/Circle.hpp +++ b/src/libslic3r/Geometry/Circle.hpp @@ -9,10 +9,17 @@ namespace Slic3r { namespace Geometry { // https://en.wikipedia.org/wiki/Circumscribed_circle // Circumcenter coordinates, Cartesian coordinates -template -Vector circle_center(const Vector &a, const Vector &bsrc, const Vector &csrc, typename Vector::Scalar epsilon) +// In case the three points are collinear, returns their centroid. +template +Eigen::Matrix circle_center(const Derived &a, const Derived2 &bsrc, const Derived3 &csrc, typename Derived::Scalar epsilon) { - using Scalar = typename Vector::Scalar; + static_assert(Derived ::IsVectorAtCompileTime && int(Derived ::SizeAtCompileTime) == 2, "circle_center(): 1st point is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "circle_center(): 2nd point is not a 2D vector"); + static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "circle_center(): 3rd point is not a 2D vector"); + static_assert(std::is_same::value && std::is_same::value, + "circle_center(): All three points must be of the same type."); + using Scalar = typename Derived::Scalar; + using Vector = Eigen::Matrix; Vector b = bsrc - a; Vector c = csrc - a; Scalar lb = b.squaredNorm(); @@ -30,6 +37,32 @@ Vector circle_center(const Vector &a, const Vector &bsrc, const Vector &csrc, ty } } +// https://en.wikipedia.org/wiki/Circumscribed_circle +// Circumcenter coordinates, Cartesian coordinates +// Returns no value if the three points are collinear. +template +std::optional> try_circle_center(const Derived &a, const Derived2 &bsrc, const Derived3 &csrc, typename Derived::Scalar epsilon) +{ + static_assert(Derived ::IsVectorAtCompileTime && int(Derived ::SizeAtCompileTime) == 2, "try_circle_center(): 1st point is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "try_circle_center(): 2nd point is not a 2D vector"); + static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "try_circle_center(): 3rd point is not a 2D vector"); + static_assert(std::is_same::value && std::is_same::value, + "try_circle_center(): All three points must be of the same type."); + using Scalar = typename Derived::Scalar; + using Vector = Eigen::Matrix; + Vector b = bsrc - a; + Vector c = csrc - a; + Scalar lb = b.squaredNorm(); + Scalar lc = c.squaredNorm(); + if (Scalar d = b.x() * c.y() - b.y() * c.x(); std::abs(d) < epsilon) { + // The three points are collinear. + return {}; + } else { + Vector v = lc * b - lb * c; + return std::make_optional(a + Vector(- v.y(), v.x()) / (2 * d)); + } +} + // 2D circle defined by its center and squared radius template struct CircleSq { @@ -65,7 +98,7 @@ struct Circle { Vector center; Scalar radius; - Circle() {} + Circle() = default; Circle(const Vector ¢er, const Scalar radius) : center(center), radius(radius) {} Circle(const Vector &a, const Vector &b) : center(Scalar(0.5) * (a + b)) { radius = (a - center).norm(); } Circle(const Vector &a, const Vector &b, const Vector &c, const Scalar epsilon) { *this = CircleSq(a, b, c, epsilon); } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index bc02e06f67..8bfa9b771a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -27,7 +27,7 @@ #include "SVG.hpp" #include -#include "GCodeWriter.hpp" +#include "GCode/GCodeWriter.hpp" namespace Slic3r { diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index fb4727abe5..f234064534 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -103,100 +103,6 @@ bool MultiPoint::remove_duplicate_points() return false; } -Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance) -{ - Points result_pts; - auto tolerance_sq = int64_t(sqr(tolerance)); - if (! pts.empty()) { - const Point *anchor = &pts.front(); - size_t anchor_idx = 0; - const Point *floater = &pts.back(); - size_t floater_idx = pts.size() - 1; - result_pts.reserve(pts.size()); - result_pts.emplace_back(*anchor); - if (anchor_idx != floater_idx) { - assert(pts.size() > 1); - std::vector dpStack; - dpStack.reserve(pts.size()); - dpStack.emplace_back(floater_idx); - for (;;) { - int64_t max_dist_sq = 0; - size_t furthest_idx = anchor_idx; - // find point furthest from line seg created by (anchor, floater) and note it - { - const Point a = *anchor; - const Point f = *floater; - const Vec2i64 v = (f - a).cast(); - if (const int64_t l2 = v.squaredNorm(); l2 == 0) { - for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) - if (int64_t dist_sq = (pts[i] - a).cast().squaredNorm(); dist_sq > max_dist_sq) { - max_dist_sq = dist_sq; - furthest_idx = i; - } - } else { - const double dl2 = double(l2); - const Vec2d dv = v.cast(); - for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) { - const Point p = pts[i]; - const Vec2i64 va = (p - a).template cast(); - const int64_t t = va.dot(v); - int64_t dist_sq; - if (t <= 0) { - dist_sq = va.squaredNorm(); - } else if (t >= l2) { - dist_sq = (p - f).cast().squaredNorm(); - } else { - const Vec2i64 w = ((double(t) / dl2) * dv).cast(); - dist_sq = (w - va).squaredNorm(); - } - if (dist_sq > max_dist_sq) { - max_dist_sq = dist_sq; - furthest_idx = i; - } - } - } - } - // remove point if less than tolerance - if (max_dist_sq <= tolerance_sq) { - result_pts.emplace_back(*floater); - anchor_idx = floater_idx; - anchor = floater; - assert(dpStack.back() == floater_idx); - dpStack.pop_back(); - if (dpStack.empty()) - break; - floater_idx = dpStack.back(); - } else { - floater_idx = furthest_idx; - dpStack.emplace_back(floater_idx); - } - floater = &pts[floater_idx]; - } - } - assert(result_pts.front() == pts.front()); - assert(result_pts.back() == pts.back()); - -#if 0 - { - static int iRun = 0; - BoundingBox bbox(pts); - BoundingBox bbox2(result_pts); - bbox.merge(bbox2); - SVG svg(debug_out_path("douglas_peucker_%d.svg", iRun ++).c_str(), bbox); - if (pts.front() == pts.back()) - svg.draw(Polygon(pts), "black"); - else - svg.draw(Polyline(pts), "black"); - if (result_pts.front() == result_pts.back()) - svg.draw(Polygon(result_pts), "green", scale_(0.1)); - else - svg.draw(Polyline(result_pts), "green", scale_(0.1)); - } -#endif - } - return result_pts; -} - // Visivalingam simplification algorithm https://github.com/slic3r/Slic3r/pull/3825 // thanks to @fuchstraumer /* @@ -219,7 +125,7 @@ struct vis_node{ // other node if it's area is less than the other node's area bool operator<(const vis_node& other) { return (this->area < other.area); } }; -Points MultiPoint::visivalingam(const Points& pts, const double& tolerance) +Points MultiPoint::visivalingam(const Points &pts, const double tolerance) { // Make sure there's enough points in "pts" to bother with simplification. assert(pts.size() >= 2); diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 637b059220..5da80d10e0 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -12,6 +12,123 @@ namespace Slic3r { class BoundingBox; class BoundingBox3; +// Reduces polyline in the +inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, OutputIterator out, const double tolerance, PointGetter point_getter) +{ + using InputIteratorCategory = typename std::iterator_traits::iterator_category; + static_assert(std::is_base_of_v); + using Vector = Eigen::Matrix; + if (begin != end) { + // Supporting in-place reduction and the data type may be generic, thus we are always making a copy of the point value before there is a chance + // to override input by moving the data to the output. + auto a = point_getter(*begin); + *out ++ = std::move(*begin); + if (auto next = std::next(begin); next == end) { + // Single point input only. + } else if (std::next(next) == end) { + // Two points input. + *out ++ = std::move(*next); + } else { + const auto tolerance_sq = SquareLengthType(sqr(tolerance)); + InputIterator anchor = begin; + InputIterator floater = std::prev(end); + std::vector dpStack; + if constexpr (std::is_base_of_v) + dpStack.reserve(end - begin); + dpStack.emplace_back(floater); + auto f = point_getter(*floater); + for (;;) { + assert(anchor != floater); + bool take_floater = false; + InputIterator furthest = anchor; + if (std::next(anchor) == floater) { + // Two point segment. Accept the floater. + take_floater = true; + } else { + SquareLengthType max_dist_sq = 0; + // Find point furthest from line seg created by (anchor, floater) and note it. + const Vector v = (f - a).template cast(); + if (const SquareLengthType l2 = v.squaredNorm(); l2 == 0) { + // Zero length segment, find the furthest point between anchor and floater. + for (auto it = std::next(anchor); it != floater; ++ it) + if (SquareLengthType dist_sq = (point_getter(*it) - a).template cast().squaredNorm(); + dist_sq > max_dist_sq) { + max_dist_sq = dist_sq; + furthest = it; + } + } else { + // Find Find the furthest point from the line . + const double dl2 = double(l2); + const Vec2d dv = v.template cast(); + for (auto it = std::next(anchor); it != floater; ++ it) { + const auto p = point_getter(*it); + const Vector va = (p - a).template cast(); + const SquareLengthType t = va.dot(v); + SquareLengthType dist_sq; + if (t <= 0) { + dist_sq = va.squaredNorm(); + } else if (t >= l2) { + dist_sq = (p - f).template cast().squaredNorm(); + } else if (double dt = double(t) / dl2; dt <= 0) { + dist_sq = va.squaredNorm(); + } else if (dt >= 1.) { + dist_sq = (p - f).template cast().squaredNorm(); + } else { + const Vector w = (dt * dv).cast(); + dist_sq = (w - va).squaredNorm(); + } + if (dist_sq > max_dist_sq) { + max_dist_sq = dist_sq; + furthest = it; + } + } + } + // remove point if less than tolerance + take_floater = max_dist_sq <= tolerance_sq; + } + if (take_floater) { + // The points between anchor and floater are close to the line. + // Drop the points between them. + a = f; + *out ++ = std::move(*floater); + anchor = floater; + assert(dpStack.back() == floater); + dpStack.pop_back(); + if (dpStack.empty()) + break; + floater = dpStack.back(); + f = point_getter(*floater); + } else { + // The furthest point is too far from the segment . + // Divide recursively. + floater = furthest; + f = point_getter(*floater); + dpStack.emplace_back(floater); + } + } + } + } + return out; +} + +// Reduces polyline in the +inline OutputIterator douglas_peucker(Points::const_iterator begin, Points::const_iterator end, OutputIterator out, const double tolerance) +{ + return douglas_peucker(begin, end, out, tolerance, [](const Point &p) { return p; }); +} + +inline Points douglas_peucker(const Points &src, const double tolerance) +{ + Points out; + out.reserve(src.size()); + douglas_peucker(src.begin(), src.end(), std::back_inserter(out), tolerance); + return out; +} + class MultiPoint { public: @@ -81,8 +198,8 @@ public: } } - static Points douglas_peucker(const Points &points, const double tolerance); - static Points visivalingam(const Points& pts, const double& tolerance); + static Points douglas_peucker(const Points &src, const double tolerance) { return Slic3r::douglas_peucker(src, tolerance); } + static Points visivalingam(const Points &src, const double tolerance); inline auto begin() { return points.begin(); } inline auto begin() const { return points.begin(); } @@ -119,16 +236,20 @@ extern BoundingBox get_extents(const MultiPoint &mp); extern BoundingBox get_extents_rotated(const Points &points, double angle); extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle); -inline double length(const Points &pts) { +inline double length(const Points::const_iterator begin, const Points::const_iterator end) { double total = 0; - if (! pts.empty()) { - auto it = pts.begin(); - for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev) + if (begin != end) { + auto it = begin; + for (auto it_prev = it ++; it != end; ++ it, ++ it_prev) total += (*it - *it_prev).cast().norm(); } return total; } +inline double length(const Points &pts) { + return length(pts.begin(), pts.end()); +} + inline double area(const Points &polygon) { double area = 0.; for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++) diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index b0fd7f35b8..a108f42de8 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -119,20 +119,18 @@ ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickP const double w = fmax(line.a_width, line.b_width); const Flow new_flow = (role.is_bridge() && flow.bridge()) ? flow : flow.with_width(unscale(w) + flow.height() * float(1. - 0.25 * PI)); - if (path.polyline.points.empty()) { - path.polyline.append(line.a); - path.polyline.append(line.b); + if (path.empty()) { // Convert from spacing to extrusion width based on the extrusion model // of a square extrusion ended with semi circles. + path = { ExtrusionAttributes{ path.role(), new_flow } }; + path.polyline.append(line.a); + path.polyline.append(line.b); #ifdef SLIC3R_DEBUG printf(" filling %f gap\n", flow.width); #endif - path.mm3_per_mm = new_flow.mm3_per_mm(); - path.width = new_flow.width(); - path.height = new_flow.height(); } else { - assert(path.width >= EPSILON); - thickness_delta = scaled(fabs(path.width - new_flow.width())); + assert(path.width() >= EPSILON); + thickness_delta = scaled(fabs(path.width() - new_flow.width())); if (thickness_delta <= merge_tolerance) { // the width difference between this line and the current flow // (of the previous line) width is within the accepted tolerance @@ -325,10 +323,12 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator extrusion_paths_append( paths, intersection_pl({ polygon }, lower_slices_polygons_clipped), - role_normal, - is_external ? params.ext_mm3_per_mm : params.mm3_per_mm, - is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(), - float(params.layer_height)); + ExtrusionAttributes{ + role_normal, + ExtrusionFlow{ is_external ? params.ext_mm3_per_mm : params.mm3_per_mm, + is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(), + float(params.layer_height) + } }); // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between @@ -336,23 +336,26 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator extrusion_paths_append( paths, diff_pl({ polygon }, lower_slices_polygons_clipped), - role_overhang, - params.mm3_per_mm_overhang, - params.overhang_flow.width(), - params.overhang_flow.height()); + ExtrusionAttributes{ + role_overhang, + ExtrusionFlow{ params.mm3_per_mm_overhang, params.overhang_flow.width(), params.overhang_flow.height() } + }); // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); } else { - ExtrusionPath path(role_normal); - path.polyline = polygon.split_at_first_point(); - path.mm3_per_mm = is_external ? params.ext_mm3_per_mm : params.mm3_per_mm; - path.width = is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(); - path.height = float(params.layer_height); - paths.push_back(path); + paths.emplace_back(polygon.split_at_first_point(), + ExtrusionAttributes{ + role_normal, + ExtrusionFlow{ + is_external ? params.ext_mm3_per_mm : params.mm3_per_mm, + is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(), + float(params.layer_height) + } + }); } - + coll.append(ExtrusionLoop(std::move(paths), loop_role)); } @@ -383,11 +386,13 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator ExtrusionLoop *eloop = static_cast(coll.entities[idx.first]); coll.entities[idx.first] = nullptr; if (loop.is_contour) { - eloop->make_counter_clockwise(); + if (eloop->is_clockwise()) + eloop->reverse_loop(); out.append(std::move(children.entities)); out.entities.emplace_back(eloop); } else { - eloop->make_clockwise(); + if (eloop->is_counter_clockwise()) + eloop->reverse_loop(); out.entities.emplace_back(eloop); out.append(std::move(children.entities)); } @@ -603,10 +608,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P if (extrusion->is_closed) { ExtrusionLoop extrusion_loop(std::move(paths)); // Restore the orientation of the extrusion loop. - if (pg_extrusion.is_contour) - extrusion_loop.make_counter_clockwise(); - else - extrusion_loop.make_clockwise(); + if (pg_extrusion.is_contour == extrusion_loop.is_clockwise()) + extrusion_loop.reverse_loop(); for (auto it = std::next(extrusion_loop.paths.begin()); it != extrusion_loop.paths.end(); ++it) { assert(it->polyline.points.size() >= 2); @@ -960,11 +963,9 @@ std::tuple, Polygons> generate_extra_perimeters_over if (perimeter_polygon.empty()) { // fill possible gaps of single extrusion width Polygons shrinked = intersection(offset(prev, -0.3 * overhang_flow.scaled_spacing()), expanded_overhang_to_cover); - if (!shrinked.empty()) { + if (!shrinked.empty()) extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()), - ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(), - overhang_flow.height()); - } + ExtrusionAttributes{ ExtrusionRole::OverhangPerimeter, overhang_flow }); Polylines fills; ExPolygons gap = shrinked.empty() ? offset_ex(prev, overhang_flow.scaled_spacing() * 0.5) : to_expolygons(shrinked); @@ -975,14 +976,12 @@ std::tuple, Polygons> generate_extra_perimeters_over if (!fills.empty()) { fills = intersection_pl(fills, shrinked_overhang_to_cover); extrusion_paths_append(overhang_region, reconnect_polylines(fills, overhang_flow.scaled_spacing()), - ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(), - overhang_flow.height()); + ExtrusionAttributes{ ExtrusionRole::OverhangPerimeter, overhang_flow }); } break; } else { extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()), - ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(), - overhang_flow.height()); + ExtrusionAttributes{ExtrusionRole::OverhangPerimeter, overhang_flow }); } if (intersection(perimeter_polygon, real_overhang).empty()) { continuation_loops--; } diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 457bb44cef..4e579312d9 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -47,14 +47,12 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t) void Point::rotate(double angle, const Point ¢er) { - 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 ); + Vec2d cur = this->cast(); + double s = ::sin(angle); + double c = ::cos(angle); + auto d = cur - center.cast(); + this->x() = fast_round_up(center.x() + c * d.x() - s * d.y()); + this->y() = fast_round_up(center.y() + s * d.x() + c * d.y()); } bool has_duplicate_points(Points &&pts) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index c4b821ca6e..34e9f4135c 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -166,11 +166,12 @@ public: Point(const Point &rhs) { *this = rhs; } explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {} // This constructor allows you to construct Point from Eigen expressions + // This constructor has to be implicit (non-explicit) to allow implicit conversion from Eigen expressions. template Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } - static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } - static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } + template + static Point new_scale(const Eigen::MatrixBase &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } // This method allows you to assign Eigen expressions to MyVectorType template diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 5261a8cfce..ca4cc09f0e 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -146,8 +146,10 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const } if (this->points.front() == point) { + //FIXME why is p1 NOT empty as in the case above? *p1 = { point }; *p2 = *this; + return; } auto min_dist2 = std::numeric_limits::max(); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d6ecb782e1..47aff8647b 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -456,7 +456,8 @@ static std::vector s_Preset_print_options { "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", - "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", + "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "arc_fitting", "arc_fitting_tolerance", + "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 8f6c65f2f8..1ee545606c 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -921,8 +921,10 @@ void Print::process() tbb::parallel_for(tbb::blocked_range(0, m_objects.size(), 1), [this](const tbb::blocked_range &range) { for (size_t idx = range.begin(); idx < range.end(); ++idx) { - m_objects[idx]->generate_support_material(); - m_objects[idx]->estimate_curled_extrusions(); + PrintObject &obj = *m_objects[idx]; + obj.generate_support_material(); + obj.estimate_curled_extrusions(); + obj.calculate_overhanging_perimeters(); } }, tbb::simple_partitioner()); @@ -1009,7 +1011,7 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor this->set_status(90, message); // Create GCode on heap, it has quite a lot of data. - std::unique_ptr gcode(new GCode); + std::unique_ptr gcode(new GCodeGenerator); gcode->do_export(this, path.c_str(), result, thumbnail_cb); if (m_conflict_result.has_value()) @@ -1125,13 +1127,15 @@ void Print::_make_skirt() } // Extrude the skirt loop. ExtrusionLoop eloop(elrSkirt); - eloop.paths.emplace_back(ExtrusionPath( - ExtrusionPath( + eloop.paths.emplace_back( + ExtrusionAttributes{ ExtrusionRole::Skirt, - (float)mm3_per_mm, // this will be overridden at G-code export time - flow.width(), - (float)first_layer_height // this will be overridden at G-code export time - ))); + ExtrusionFlow{ + float(mm3_per_mm), // this will be overridden at G-code export time + flow.width(), + float(first_layer_height) // this will be overridden at G-code export time + } + }); eloop.paths.back().polyline = loop.split_at_first_point(); m_skirt.append(eloop); if (m_config.min_skirt_length.value > 0) { @@ -1637,8 +1641,8 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co } - ExtrusionPath path(ExtrusionRole::WipeTower, 0.0, 0.0, lh); - path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner }; + ExtrusionPath path({ minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner }, + ExtrusionAttributes{ ExtrusionRole::WipeTower, ExtrusionFlow{ 0.0, 0.0, lh } }); paths.push_back({ path }); // We added the border, now add several parallel lines so we can detect an object that is fully inside the tower. diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 059491951e..f16befc964 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -29,7 +29,7 @@ namespace Slic3r { -class GCode; +class GCodeGenerator; class Layer; class ModelObject; class Print; @@ -67,7 +67,7 @@ enum PrintStep : unsigned int { enum PrintObjectStep : unsigned int { posSlice, posPerimeters, posPrepareInfill, - posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCount, + posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters, posCount, }; // A PrintRegion object represents a group of volumes to print @@ -376,6 +376,7 @@ private: void generate_support_spots(); void generate_support_material(); void estimate_curled_extrusions(); + void calculate_overhanging_perimeters(); void slice_volumes(); // Has any support (not counting the raft). @@ -697,7 +698,7 @@ private: Polygons m_sequential_print_clearance_contours; // To allow GCode to set the Print's GCodeExport step status. - friend class GCode; + friend class GCodeGenerator; // To allow GCodeProcessor to emit warnings. friend class GCodeProcessor; // Allow PrintObject to access m_mutex and m_cancel_callback. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 5ccfdf9981..8f6057f904 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -34,6 +34,13 @@ static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values & template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values() { return s_keys_map_##NAME; } \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names() { return s_keys_names_##NAME; } +static const t_config_enum_values s_keys_map_ArcFittingType { + { "disabled", int(ArcFittingType::Disabled) }, + { "emit_center", int(ArcFittingType::EmitCenter) }, + { "emit_radius", int(ArcFittingType::EmitRadius) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ArcFittingType) + static t_config_enum_values s_keys_map_PrinterTechnology { { "FFF", ptFFF }, { "SLA", ptSLA } @@ -397,6 +404,27 @@ void PrintConfigDef::init_fff_params() { ConfigOptionDef* def; + def = this->add("arc_fitting", coEnum); + def->label = L("Arc fitting"); + def->tooltip = L("Enable this to get a G-code file which has G2 and G3 moves. " + "And the fitting tolerance is same with resolution"); + def->set_enum({ + { "disabled", "Disabled" }, + { "emit_center", "Enabled: G2/3 I J" }, + { "emit_radius", "Enabled: G2/3 R" } + }); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(ArcFittingType::Disabled)); + + def = this->add("arc_fitting_tolerance", coFloatOrPercent); + def->label = L("Arc fitting tolerance"); + def->sidetext = L("mm or %"); + def->tooltip = L("When using the arc_fitting option, allow the curve to deviate a cetain % from the collection of strait paths.\n" + "Can be a mm value or a percentage of the current extrusion width."); + def->mode = comAdvanced; + def->min = 0; + def->set_default_value(new ConfigOptionFloatOrPercent(5, true)); + // Maximum extruder temperature, bumped to 1500 to support printing of glass. const int max_temp = 1500; def = this->add("avoid_crossing_curled_overhangs", coBool); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index ab6f1ea907..59964c0b8a 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -30,6 +30,12 @@ namespace Slic3r { +enum class ArcFittingType { + Disabled, + EmitCenter, + EmitRadius, +}; + enum GCodeFlavor : unsigned char { gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinLegacy, gcfMarlinFirmware, gcfKlipper, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, @@ -141,6 +147,7 @@ enum class GCodeThumbnailsFormat { template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ArcFittingType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(MachineLimitsUsage) @@ -671,6 +678,8 @@ PRINT_CONFIG_CLASS_DEFINE( PRINT_CONFIG_CLASS_DEFINE( GCodeConfig, + ((ConfigOptionEnum, arc_fitting)) + ((ConfigOptionFloatOrPercent, arc_fitting_tolerance)) ((ConfigOptionBool, autoemit_temperature_commands)) ((ConfigOptionString, before_layer_gcode)) ((ConfigOptionString, between_objects_gcode)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 71b222f86b..5340599722 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -3,7 +3,9 @@ #include "ExPolygon.hpp" #include "Exception.hpp" #include "Flow.hpp" +#include "GCode/ExtrusionProcessor.hpp" #include "KDTreeIndirect.hpp" +#include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "Polyline.hpp" @@ -533,6 +535,65 @@ void PrintObject::estimate_curled_extrusions() } } +void PrintObject::calculate_overhanging_perimeters() +{ + if (this->set_started(posCalculateOverhangingPerimeters)) { + BOOST_LOG_TRIVIAL(debug) << "Calculating overhanging perimeters - start"; + m_print->set_status(89, _u8L("Calculating overhanging perimeters")); + std::vector extruders; + std::unordered_set regions_with_dynamic_speeds; + for (const PrintRegion *pr : this->print()->m_print_regions) { + if (pr->config().enable_dynamic_overhang_speeds.getBool()) { + regions_with_dynamic_speeds.insert(pr); + } + extruders.clear(); + pr->collect_object_printing_extruders(*this->print(), extruders); + auto cfg = this->print()->config(); + if (std::any_of(extruders.begin(), extruders.end(), + [&cfg](unsigned int extruder_id) { return cfg.enable_dynamic_fan_speeds.get_at(extruder_id); })) { + regions_with_dynamic_speeds.insert(pr); + } + } + + if (!regions_with_dynamic_speeds.empty()) { + std::unordered_map> curled_lines; + std::unordered_map> unscaled_polygons_lines; + for (const Layer *l : this->layers()) { + curled_lines[l->id()] = AABBTreeLines::LinesDistancer{l->curled_lines}; + unscaled_polygons_lines[l->id()] = AABBTreeLines::LinesDistancer{to_unscaled_linesf(l->lslices)}; + } + curled_lines[size_t(-1)] = {}; + unscaled_polygons_lines[size_t(-1)] = {}; + + tbb::parallel_for(tbb::blocked_range(0, m_layers.size()), [this, &curled_lines, &unscaled_polygons_lines, + ®ions_with_dynamic_speeds]( + const tbb::blocked_range &range) { + PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT); + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + auto l = m_layers[layer_idx]; + if (l->id() == 0) { // first layer, do not split + continue; + } + for (LayerRegion *layer_region : l->regions()) { + if (regions_with_dynamic_speeds.find(layer_region->m_region) == regions_with_dynamic_speeds.end()) { + continue; + } + size_t prev_layer_id = l->lower_layer ? l->lower_layer->id() : size_t(-1); + layer_region->m_perimeters = + ExtrusionProcessor::calculate_and_split_overhanging_extrusions(&layer_region->m_perimeters, + unscaled_polygons_lines[prev_layer_id], + curled_lines[l->id()]); + } + } + }); + + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Calculating overhanging perimeters - end"; + } + this->set_done(posCalculateOverhangingPerimeters); + } +} + std::pair PrintObject::prepare_adaptive_infill_data( const std::vector> &surfaces_w_bottom_z) const { @@ -644,7 +705,9 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "first_layer_extrusion_width" || opt_key == "perimeter_extrusion_width" || opt_key == "infill_overlap" - || opt_key == "external_perimeters_first") { + || opt_key == "external_perimeters_first" + || opt_key == "arc_fitting" + || opt_key == "arc_fitting_tolerance") { steps.emplace_back(posPerimeters); } else if ( opt_key == "gap_fill_enabled" @@ -845,7 +908,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step) // propagate to dependent steps if (step == posPerimeters) { - invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions }); + invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); } else if (step == posPrepareInfill) { invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch}); @@ -854,7 +917,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step) invalidated |= m_print->invalidate_steps({ psSkirtBrim }); } else if (step == posSlice) { invalidated |= this->invalidate_steps({posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, - posSupportMaterial, posEstimateCurledExtrusions}); + posSupportMaterial, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters}); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); m_slicing_params.valid = false; } else if (step == posSupportMaterial) { diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index da1a44ec75..d4433ded25 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1006,16 +1006,20 @@ std::vector> chain_segments_greedy2(SegmentEndPointFunc return chain_segments_greedy_constrained_reversals2_(end_point_func, could_reverse_func, num_segments, start_near); } -std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near) +std::vector> chain_extrusion_entities(const std::vector &entities, const Point *start_near, const bool reversed) { - auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); }; - auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); }; - std::vector> out = chain_segments_greedy_constrained_reversals(segment_end_point, could_reverse, entities.size(), start_near); + auto segment_end_point = [&entities, reversed](size_t idx, bool first_point) -> const Point& { return first_point == reversed ? entities[idx]->last_point() : entities[idx]->first_point(); }; + auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); }; + std::vector> out = chain_segments_greedy_constrained_reversals( + segment_end_point, could_reverse, entities.size(), start_near); for (std::pair &segment : out) { ExtrusionEntity *ee = entities[segment.first]; if (ee->is_loop()) // Ignore reversals for loops, as the start point equals the end point. segment.second = false; + else if (reversed) + // Input was already reversed. + segment.second = ! segment.second; // Is can_reverse() respected by the reversals? assert(ee->can_reverse() || ! segment.second); } @@ -1041,6 +1045,33 @@ void chain_and_reorder_extrusion_entities(std::vector &entitie reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near)); } +ExtrusionEntityReferences chain_extrusion_references(const std::vector &entities, const Point *start_near, const bool reversed) +{ + const std::vector> chain = chain_extrusion_entities(entities, start_near, reversed); + ExtrusionEntityReferences out; + out.reserve(chain.size()); + for (const std::pair &idx : chain) { + assert(entities[idx.first] != nullptr); + out.push_back({ *entities[idx.first], idx.second }); + } + return out; +} + +ExtrusionEntityReferences chain_extrusion_references(const ExtrusionEntityCollection &eec, const Point *start_near, const bool reversed) +{ + if (eec.no_sort) { + ExtrusionEntityReferences out; + out.reserve(eec.entities.size()); + for (const ExtrusionEntity *ee : eec.entities) { + assert(ee != nullptr); + // Never reverse a loop. + out.push_back({ *ee, ! ee->is_loop() && reversed }); + } + return out; + } else + return chain_extrusion_references(eec.entities, start_near, reversed); +} + std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near) { auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); }; diff --git a/src/libslic3r/ShortestPath.hpp b/src/libslic3r/ShortestPath.hpp index 1781c51889..89db309963 100644 --- a/src/libslic3r/ShortestPath.hpp +++ b/src/libslic3r/ShortestPath.hpp @@ -18,13 +18,25 @@ namespace Slic3r { class ExPolygon; using ExPolygons = std::vector; +// Used by chain_expolygons() std::vector chain_points(const Points &points, Point *start_near = nullptr); +// Used to give layer islands a print order. std::vector chain_expolygons(const ExPolygons &expolygons, Point *start_near = nullptr); -std::vector> chain_extrusion_entities(std::vector &entities, const Point *start_near = nullptr); +// Chain extrusion entities by a shortest distance. Returns the ordered extrusions together with a "reverse" flag. +// Set input "reversed" to true if the vector of "entities" is to be considered to be reversed once already. +std::vector> chain_extrusion_entities(const std::vector &entities, const Point *start_near = nullptr, const bool reversed = false); +// Reorder & reverse extrusion entities in place based on the "chain" ordering. void reorder_extrusion_entities(std::vector &entities, const std::vector> &chain); +// Reorder & reverse extrusion entities in place. void chain_and_reorder_extrusion_entities(std::vector &entities, const Point *start_near = nullptr); +// Chain extrusion entities by a shortest distance. Returns the ordered extrusions together with a "reverse" flag. +// Set input "reversed" to true if the vector of "entities" is to be considered to be reversed. +ExtrusionEntityReferences chain_extrusion_references(const std::vector &entities, const Point *start_near = nullptr, const bool reversed = false); +// The same as above, respect eec.no_sort flag. +ExtrusionEntityReferences chain_extrusion_references(const ExtrusionEntityCollection &eec, const Point *start_near = nullptr, const bool reversed = false); + std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near = nullptr); void reorder_extrusion_paths(std::vector &extrusion_paths, std::vector> &chain); void chain_and_reorder_extrusion_paths(std::vector &extrusion_paths, const Point *start_near = nullptr); diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index dce57a72c5..bcd65320ca 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -485,8 +485,7 @@ static inline void fill_expolygon_generate_paths( extrusion_entities_append_paths( dst, std::move(polylines), - role, - flow.mm3_per_mm(), flow.width(), flow.height()); + { role, flow }); } static inline void fill_expolygons_generate_paths( @@ -644,7 +643,7 @@ static inline void tree_supports_generate_paths( ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); if (level2.size() == 1) { Polylines polylines; - extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow }, // Disable reversal of the path, always start with the anchor, always print CCW. false); expoly = level2.front(); @@ -750,7 +749,7 @@ static inline void tree_supports_generate_paths( } ExtrusionEntitiesPtr &out = eec ? eec->entities : dst; - extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(), + extrusion_entities_append_paths(out, std::move(polylines), { ExtrusionRole::SupportMaterial, flow }, // Disable reversal of the path, always start with the anchor, always print CCW. false); if (eec) { @@ -794,7 +793,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( eec->no_sort = true; } ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; - extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height()); + extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow }); // Fill in the rest. fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); if (no_sort && ! eec->empty()) @@ -1106,7 +1105,7 @@ void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact extrusion_entities_append_paths( top_contact_layer.extrusions, std::move(loop_lines), - ExtrusionRole::SupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); + { ExtrusionRole::SupportMaterialInterface, flow }); } #ifdef SLIC3R_DEBUG @@ -1148,24 +1147,19 @@ static void modulate_extrusion_by_overlapping_layers( ExtrusionPath *extrusion_path_template = dynamic_cast(extrusions_in_out.front()); assert(extrusion_path_template != nullptr); ExtrusionRole extrusion_role = extrusion_path_template->role(); - float extrusion_width = extrusion_path_template->width; + float extrusion_width = extrusion_path_template->width(); struct ExtrusionPathFragment { - ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; - ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {}; - + ExtrusionFlow flow; Polylines polylines; - double mm3_per_mm; - float width; - float height; }; // Split the extrusions by the overlapping layers, reduce their extrusion rate. // The last path_fragment is from this_layer. std::vector path_fragments( n_overlapping_layers + 1, - ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height)); + ExtrusionPathFragment{ extrusion_path_template->attributes() }); // Don't use it, it will be released. extrusion_path_template = nullptr; @@ -1242,8 +1236,8 @@ static void modulate_extrusion_by_overlapping_layers( path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming); // Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter). assert(this_layer.print_z > overlapping_layer.print_z); - frag.height = float(this_layer.print_z - overlapping_layer.print_z); - frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm(); + frag.flow.height = float(this_layer.print_z - overlapping_layer.print_z); + frag.flow.mm3_per_mm = Flow(frag.flow.width, frag.flow.height, -1.f).mm3_per_mm(); #ifdef SLIC3R_DEBUG svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); #endif /* SLIC3R_DEBUG */ @@ -1326,15 +1320,14 @@ static void modulate_extrusion_by_overlapping_layers( ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); if (path != nullptr) { // Verify whether the path is compatible with the current fragment. - assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm); - if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) { + assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height() != frag.flow.height || path->mm3_per_mm() != frag.flow.mm3_per_mm); + if (path->height() != frag.flow.height || path->mm3_per_mm() != frag.flow.mm3_per_mm) path = nullptr; - } // Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer. } if (path == nullptr) { // Allocate a new path. - multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height)); + multipath.paths.emplace_back(ExtrusionAttributes{ extrusion_role, frag.flow }); path = &multipath.paths.back(); } // The Clipper library may flip the order of the clipped polylines arbitrarily. @@ -1369,8 +1362,8 @@ static void modulate_extrusion_by_overlapping_layers( } // If there are any non-consumed fragments, add them separately. //FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected. - for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment) - extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height); + for (ExtrusionPathFragment &fragment : path_fragments) + extrusion_entities_append_paths(extrusions_in_out, std::move(fragment.polylines), { extrusion_role, fragment.flow }); } // Support layer that is covered by some form of dense interface. diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index a21a48b9a3..d91d492d10 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1023,7 +1023,7 @@ namespace SupportMaterialInternal { assert(expansion_scaled >= 0.f); for (const ExtrusionPath &ep : loop.paths) if (ep.role() == ExtrusionRole::OverhangPerimeter && ! ep.polyline.empty()) { - float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled; + float exp = 0.5f * (float)scale_(ep.width()) + expansion_scaled; if (ep.is_closed()) { if (ep.size() >= 3) { // This is a complete loop. diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index e1ae58fd21..8e52dcf6fc 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -327,10 +327,10 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) { return {}; } - const float flow_width = get_flow_width(layer_region, entity->role()); - std::vector annotated_points = estimate_points_properties(entity->as_polyline().points, - prev_layer_boundary, flow_width, - params.bridge_distance); + const float flow_width = get_flow_width(layer_region, entity->role()); + std::vector annotated_points = + ExtrusionProcessor::estimate_points_properties(entity->as_polyline().points, prev_layer_boundary, + flow_width, params.bridge_distance); std::vector lines_out; lines_out.reserve(annotated_points.size()); @@ -339,8 +339,8 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit std::optional bridging_dir{}; for (size_t i = 0; i < annotated_points.size(); ++i) { - ExtendedPoint &curr_point = annotated_points[i]; - const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i]; + ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i]; + const ExtrusionProcessor::ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i]; SupportPointCause potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor : SupportPointCause::LongBridge; @@ -382,19 +382,19 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit const float flow_width = get_flow_width(layer_region, entity->role()); // Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable - std::vector annotated_points = estimate_points_properties(entity->as_polyline().points, - prev_layer_lines, flow_width, - params.bridge_distance); + std::vector annotated_points = + ExtrusionProcessor::estimate_points_properties(entity->as_polyline().points, prev_layer_lines, + flow_width, params.bridge_distance); std::vector lines_out; lines_out.reserve(annotated_points.size()); float bridged_distance = annotated_points.front().position != annotated_points.back().position ? (params.bridge_distance + 1.0f) : 0.0f; for (size_t i = 0; i < annotated_points.size(); ++i) { - ExtendedPoint &curr_point = annotated_points[i]; - const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i]; - float line_len = (prev_point.position - curr_point.position).norm(); - ExtrusionLine line_out{prev_point.position.cast(), curr_point.position.cast(), line_len, entity}; + ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i]; + const ExtrusionProcessor::ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i]; + float line_len = (prev_point.position - curr_point.position).norm(); + ExtrusionLine line_out{prev_point.position.cast(), curr_point.position.cast(), line_len, entity}; Vec2f middle = 0.5 * (line_out.a + line_out.b); auto [middle_distance, bottom_line_idx, x] = prev_layer_lines.distance_from_lines_extra(middle); @@ -1107,12 +1107,13 @@ void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width, Polygon pol(pl.points); pol.make_counter_clockwise(); - auto annotated_points = estimate_points_properties(pol.points, prev_layer_lines, flow_width); + auto annotated_points = ExtrusionProcessor::estimate_points_properties(pol.points, prev_layer_lines, + flow_width); for (size_t i = 0; i < annotated_points.size(); ++i) { - const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i]; - const ExtendedPoint &b = annotated_points[i]; - ExtrusionLine line_out{a.position.cast(), b.position.cast(), float((a.position - b.position).norm()), + const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i]; + const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i]; + ExtrusionLine line_out{a.position.cast(), b.position.cast(), float((a.position - b.position).norm()), extrusion}; Vec2f middle = 0.5 * (line_out.a + line_out.b); @@ -1182,11 +1183,13 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms) Points extrusion_pts; extrusion->collect_points(extrusion_pts); float flow_width = get_flow_width(layer_region, extrusion->role()); - auto annotated_points = estimate_points_properties(extrusion_pts, prev_layer_lines, flow_width, - params.bridge_distance); + auto annotated_points = ExtrusionProcessor::estimate_points_properties(extrusion_pts, + prev_layer_lines, + flow_width, + params.bridge_distance); for (size_t i = 0; i < annotated_points.size(); ++i) { - const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i]; - const ExtendedPoint &b = annotated_points[i]; + const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i]; + const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i]; ExtrusionLine line_out{a.position.cast(), b.position.cast(), float((a.position - b.position).norm()), extrusion}; @@ -1196,7 +1199,8 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms) prev_layer_lines.get_line(bottom_line_idx); // correctify the distance sign using slice polygons - float sign = (prev_layer_boundary.distance_from_lines(middle.cast()) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f; + float sign = (prev_layer_boundary.distance_from_lines(middle.cast()) + 0.5f * flow_width) < 0.0f ? -1.0f : + 1.0f; line_out.curled_up_height = estimate_curled_up_height(middle_distance * sign, 0.5 * (a.curvature + b.curvature), l->height, flow_width, bottom_line.curled_up_height, params); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 679abb709d..2990c0fb56 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1442,8 +1442,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionPath& extrusion_path, flo polyline.remove_duplicate_points(); polyline.translate(copy); const Lines lines = polyline.lines(); - std::vector widths(lines.size(), extrusion_path.width); - std::vector heights(lines.size(), extrusion_path.height); + std::vector widths(lines.size(), extrusion_path.width()); + std::vector heights(lines.size(), extrusion_path.height()); thick_lines_to_verts(lines, widths, heights, false, print_z, geometry); } @@ -1459,8 +1459,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, flo polyline.translate(copy); const Lines lines_this = polyline.lines(); append(lines, lines_this); - widths.insert(widths.end(), lines_this.size(), extrusion_path.width); - heights.insert(heights.end(), lines_this.size(), extrusion_path.height); + widths.insert(widths.end(), lines_this.size(), extrusion_path.width()); + heights.insert(heights.end(), lines_this.size(), extrusion_path.height()); } thick_lines_to_verts(lines, widths, heights, true, print_z, geometry); } @@ -1477,8 +1477,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_mult polyline.translate(copy); const Lines lines_this = polyline.lines(); append(lines, lines_this); - widths.insert(widths.end(), lines_this.size(), extrusion_path.width); - heights.insert(heights.end(), lines_this.size(), extrusion_path.height); + widths.insert(widths.end(), lines_this.size(), extrusion_path.width()); + heights.insert(heights.end(), lines_this.size(), extrusion_path.height()); } thick_lines_to_verts(lines, widths, heights, false, print_z, geometry); } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 8126576b79..0cb07f42b4 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -341,6 +341,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("min_feature_size", have_arachne); toggle_field("min_bead_width", have_arachne); toggle_field("thin_walls", !have_arachne); + + bool has_arc_fitting = config->opt_enum("arc_fitting") != ArcFittingType::Disabled; + toggle_field("arc_fitting_tolerance", has_arc_fitting); } void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index dc922f971b..5bbc2fa5d8 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -7,7 +7,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" -#include "libslic3r/GCodeWriter.hpp" +#include "libslic3r/GCode/GCodeWriter.hpp" #include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/PrintHost.hpp" @@ -1649,6 +1649,8 @@ void TabPrint::build() optgroup->append_single_option_line("slicing_mode"); optgroup->append_single_option_line("resolution"); optgroup->append_single_option_line("gcode_resolution"); + optgroup->append_single_option_line("arc_fitting"); + optgroup->append_single_option_line("arc_fitting_tolerance"); optgroup->append_single_option_line("xy_size_compensation"); optgroup->append_single_option_line("elefant_foot_compensation", "elephant-foot-compensation_114487"); diff --git a/t/geometry.t b/t/geometry.t deleted file mode 100644 index 83fb72eeab..0000000000 --- a/t/geometry.t +++ /dev/null @@ -1,95 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 10; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use Slic3r; -use Slic3r::Geometry qw(PI - chained_path_from epsilon scale); - -{ - # this test was failing on Windows (GH #1950) - my $polygon = Slic3r::Polygon->new( - [207802834,-57084522],[196528149,-37556190],[173626821,-25420928],[171285751,-21366123], - [118673592,-21366123],[116332562,-25420928],[93431208,-37556191],[82156517,-57084523], - [129714478,-84542120],[160244873,-84542120], - ); - my $point = Slic3r::Point->new(95706562, -57294774); - ok $polygon->contains_point($point), 'contains_point'; -} - -#========================================================== - -my $polygons = [ - Slic3r::Polygon->new( # contour, ccw - [45919000, 515273900], [14726100, 461246400], [14726100, 348753500], [33988700, 315389800], - [43749700, 343843000], [45422300, 352251500], [52362100, 362637800], [62748400, 369577600], - [75000000, 372014700], [87251500, 369577600], [97637800, 362637800], [104577600, 352251500], - [107014700, 340000000], [104577600, 327748400], [97637800, 317362100], [87251500, 310422300], - [82789200, 309534700], [69846100, 294726100], [254081000, 294726100], [285273900, 348753500], - [285273900, 461246400], [254081000, 515273900], - - ), - Slic3r::Polygon->new( # hole, cw - [75000000, 502014700], [87251500, 499577600], [97637800, 492637800], [104577600, 482251500], - [107014700, 470000000], [104577600, 457748400], [97637800, 447362100], [87251500, 440422300], - [75000000, 437985300], [62748400, 440422300], [52362100, 447362100], [45422300, 457748400], - [42985300, 470000000], [45422300, 482251500], [52362100, 492637800], [62748400, 499577600], - ), -]; - - -#========================================================== - -{ - my $polygon = Slic3r::Polygon->new([0, 0], [10, 0], [5, 5]); - my $result = $polygon->split_at_index(1); - is ref($result), 'Slic3r::Polyline', 'split_at_index returns polyline'; - is_deeply $result->pp, [ [10, 0], [5, 5], [0, 0], [10, 0] ], 'split_at_index'; -} - -#========================================================== - -#{ -# my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ map Slic3r::Point->new(@$_), [0, 1], [10, 2], [20, 2] ]); -# $bb->scale(2); -# is_deeply [ $bb->min_point->pp, $bb->max_point->pp ], [ [0,2], [40,4] ], 'bounding box is scaled correctly'; -#} - -#========================================================== - -{ - # if chained_path() works correctly, these points should be joined with no diagonal paths - # (thus 26 units long) - my @points = map Slic3r::Point->new_scale(@$_), [26,26],[52,26],[0,26],[26,52],[26,0],[0,52],[52,52],[52,0]; - my @ordered = @points[@{chained_path_from(\@points, $points[0])}]; - ok !(grep { abs($ordered[$_]->distance_to($ordered[$_+1]) - scale 26) > epsilon } 0..$#ordered-1), 'chained_path'; -} - -#========================================================== - -{ - my $line = Slic3r::Line->new([0, 0], [20, 0]); - is +Slic3r::Point->new(10, 10)->distance_to_line($line), 10, 'distance_to'; - is +Slic3r::Point->new(50, 0)->distance_to_line($line), 30, 'distance_to'; - is +Slic3r::Point->new(0, 0)->distance_to_line($line), 0, 'distance_to'; - is +Slic3r::Point->new(20, 0)->distance_to_line($line), 0, 'distance_to'; - is +Slic3r::Point->new(10, 0)->distance_to_line($line), 0, 'distance_to'; -} - -{ - my $triangle = Slic3r::Polygon->new( - [16000170,26257364], [714223,461012], [31286371,461008], - ); - my $simplified = $triangle->simplify(250000)->[0]; - is scalar(@$simplified), 3, 'triangle is never simplified to less than 3 points'; -} - -__END__ diff --git a/tests/fff_print/test_cooling.cpp b/tests/fff_print/test_cooling.cpp index 778a7da401..1f560d53f0 100644 --- a/tests/fff_print/test_cooling.cpp +++ b/tests/fff_print/test_cooling.cpp @@ -14,7 +14,7 @@ using namespace Slic3r; std::unique_ptr make_cooling_buffer( - GCode &gcode, + GCodeGenerator &gcode, const DynamicPrintConfig &config = DynamicPrintConfig{}, const std::vector &extruder_ids = { 0 }) { @@ -65,7 +65,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") { const double print_time = 100. / (3000. / 60.); //FIXME slowdown_below_layer_time is rounded down significantly from 1.8s to 1s. config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 0.999) } } }); - GCode gcodegen; + GCodeGenerator gcodegen; auto buffer = make_cooling_buffer(gcodegen, config); std::string gcode = buffer->process_layer("G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1", 0, true); bool speed_not_altered = gcode.find("F3000") != gcode.npos; @@ -83,7 +83,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") { // Print time of gcode. const double print_time = 50. / (2500. / 60.) + 100. / (3000. / 60.) + 4. / (400. / 60.); config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 1.001) } } }); - GCode gcodegen; + GCodeGenerator gcodegen; auto buffer = make_cooling_buffer(gcodegen, config); std::string gcode = buffer->process_layer(gcode_src, 0, true); THEN("speed is altered when elapsed time is lower than slowdown threshold") { @@ -106,7 +106,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") { { "fan_below_layer_time" , int(print_time1 * 0.88) }, { "slowdown_below_layer_time" , int(print_time1 * 0.99) } }); - GCode gcodegen; + GCodeGenerator gcodegen; auto buffer = make_cooling_buffer(gcodegen, config); std::string gcode = buffer->process_layer(gcode1, 0, true); bool fan_not_activated = gcode.find("M106") == gcode.npos; @@ -119,7 +119,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") { { "fan_below_layer_time", { int(print_time2 + 1.), int(print_time2 + 1.) } }, { "slowdown_below_layer_time", { int(print_time2 + 2.), int(print_time2 + 2.) } } }); - GCode gcodegen; + GCodeGenerator gcodegen; auto buffer = make_cooling_buffer(gcodegen, config, { 0, 1 }); std::string gcode = buffer->process_layer(gcode1 + "T1\nG1 X0 E1 F3000\n", 0, true); THEN("fan is activated for the 1st tool") { @@ -134,7 +134,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") { WHEN("G-code block 2") { THEN("slowdown is computed on all objects printing at the same Z") { config.set_deserialize_strict({ { "slowdown_below_layer_time", int(print_time2 * 0.99) } }); - GCode gcodegen; + GCodeGenerator gcodegen; auto buffer = make_cooling_buffer(gcodegen, config); std::string gcode = buffer->process_layer(gcode2, 0, true); bool ok = gcode.find("F3000") != gcode.npos; @@ -145,7 +145,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") { { "fan_below_layer_time", int(print_time2 * 0.65) }, { "slowdown_below_layer_time", int(print_time2 * 0.7) } }); - GCode gcodegen; + GCodeGenerator gcodegen; auto buffer = make_cooling_buffer(gcodegen, config); // use an elapsed time which is < the threshold but greater than it when summed twice std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true); @@ -158,7 +158,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") { { "fan_below_layer_time", int(print_time2 + 1) }, { "slowdown_below_layer_time", int(print_time2 + 1) } }); - GCode gcodegen; + GCodeGenerator gcodegen; auto buffer = make_cooling_buffer(gcodegen, config); // use an elapsed time which is < the threshold but greater than it when summed twice std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true); diff --git a/tests/fff_print/test_extrusion_entity.cpp b/tests/fff_print/test_extrusion_entity.cpp index 5f7de1f14a..0698f5eb90 100644 --- a/tests/fff_print/test_extrusion_entity.cpp +++ b/tests/fff_print/test_extrusion_entity.cpp @@ -21,7 +21,7 @@ static inline Slic3r::Point random_point(float LO=-50, float HI=50) // build a sample extrusion entity collection with random start and end points. static Slic3r::ExtrusionPath random_path(size_t length = 20, float LO = -50, float HI = 50) { - ExtrusionPath t { ExtrusionRole::Perimeter, 1.0, 1.0, 1.0 }; + ExtrusionPath t{ ExtrusionAttributes{ ExtrusionRole::Perimeter, ExtrusionFlow{ 1.0, 1.0, 1.0 } } }; for (size_t j = 0; j < length; ++ j) t.polyline.append(random_point(LO, HI)); return t; @@ -37,9 +37,8 @@ static Slic3r::ExtrusionPaths random_paths(size_t count = 10, size_t length = 20 SCENARIO("ExtrusionPath", "[ExtrusionEntity]") { GIVEN("Simple path") { - Slic3r::ExtrusionPath path{ ExtrusionRole::ExternalPerimeter }; - path.polyline = { { 100, 100 }, { 200, 100 }, { 200, 200 } }; - path.mm3_per_mm = 1.; + Slic3r::ExtrusionPath path{ { { 100, 100 }, { 200, 100 }, { 200, 200 } }, + ExtrusionAttributes{ ExtrusionRole::ExternalPerimeter, ExtrusionFlow{ 1., -1.f, -1.f } } }; THEN("first point") { REQUIRE(path.first_point() == path.polyline.front()); } @@ -52,10 +51,7 @@ SCENARIO("ExtrusionPath", "[ExtrusionEntity]") { static ExtrusionPath new_extrusion_path(const Polyline &polyline, ExtrusionRole role, double mm3_per_mm) { - ExtrusionPath path(role); - path.polyline = polyline; - path.mm3_per_mm = 1.; - return path; + return { polyline, ExtrusionAttributes{ role, ExtrusionFlow{ mm3_per_mm, -1.f, -1.f } } }; } SCENARIO("ExtrusionLoop", "[ExtrusionEntity]") @@ -67,6 +63,7 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]") loop.paths.emplace_back(new_extrusion_path(square.split_at_first_point(), ExtrusionRole::ExternalPerimeter, 1.)); THEN("polygon area") { REQUIRE(loop.polygon().area() == Approx(square.area())); + REQUIRE(loop.area() == Approx(square.area())); } THEN("loop length") { REQUIRE(loop.length() == Approx(square.length())); @@ -110,6 +107,9 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]") loop.paths.emplace_back(new_extrusion_path(polyline1, ExtrusionRole::ExternalPerimeter, 1.)); loop.paths.emplace_back(new_extrusion_path(polyline2, ExtrusionRole::OverhangPerimeter, 1.)); + THEN("area") { + REQUIRE(loop.area() == Approx(loop.polygon().area())); + } double tot_len = polyline1.length() + polyline2.length(); THEN("length") { REQUIRE(loop.length() == Approx(tot_len)); @@ -212,6 +212,9 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]") loop.paths.emplace_back(new_extrusion_path(polyline3, ExtrusionRole::ExternalPerimeter, 1.)); loop.paths.emplace_back(new_extrusion_path(polyline4, ExtrusionRole::OverhangPerimeter, 1.)); double len = loop.length(); + THEN("area") { + REQUIRE(loop.area() == Approx(loop.polygon().area())); + } WHEN("splitting at vertex") { Point point(4821067, 9321068); if (! loop.split_at_vertex(point)) @@ -234,6 +237,9 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]") Polyline { { 15896783, 15868739 }, { 24842049, 12117558 }, { 33853238, 15801279 }, { 37591780, 24780128 }, { 37591780, 24844970 }, { 33853231, 33825297 }, { 24842049, 37509013 }, { 15896798, 33757841 }, { 12211841, 24812544 }, { 15896783, 15868739 } }, ExtrusionRole::ExternalPerimeter, 1.)); + THEN("area") { + REQUIRE(loop.area() == Approx(loop.polygon().area())); + } double len = loop.length(); THEN("split_at() preserves total length") { loop.split_at({ 15896783, 15868739 }, false, 0); @@ -378,23 +384,27 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") { REQUIRE(chained == test.chained); ExtrusionEntityCollection unchained_extrusions; extrusion_entities_append_paths(unchained_extrusions.entities, test.unchained, - ExtrusionRole::InternalInfill, 0., 0.4f, 0.3f); + ExtrusionAttributes{ ExtrusionRole::InternalInfill, ExtrusionFlow{ 0., 0.4f, 0.3f } }); THEN("Chaining works") { - ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); - REQUIRE(chained_extrusions.entities.size() == test.chained.size()); - for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { + ExtrusionEntityReferences chained_extrusions = chain_extrusion_references(unchained_extrusions, &test.initial_point); + REQUIRE(chained_extrusions.size() == test.chained.size()); + for (size_t i = 0; i < chained_extrusions.size(); ++ i) { const Points &p1 = test.chained[i].points; - const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; + Points p2 = chained_extrusions[i].cast()->polyline.points; + if (chained_extrusions[i].flipped()) + std::reverse(p2.begin(), p2.end()); REQUIRE(p1 == p2); } } THEN("Chaining produces no change with no_sort") { unchained_extrusions.no_sort = true; - ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); - REQUIRE(chained_extrusions.entities.size() == test.unchained.size()); - for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { + ExtrusionEntityReferences chained_extrusions = chain_extrusion_references(unchained_extrusions, &test.initial_point); + REQUIRE(chained_extrusions.size() == test.unchained.size()); + for (size_t i = 0; i < chained_extrusions.size(); ++ i) { const Points &p1 = test.unchained[i].points; - const Points &p2 = dynamic_cast(chained_extrusions.entities[i])->polyline.points; + Points p2 = chained_extrusions[i].cast()->polyline.points; + if (chained_extrusions[i].flipped()) + std::reverse(p2.begin(), p2.end()); REQUIRE(p1 == p2); } } diff --git a/tests/fff_print/test_gcode.cpp b/tests/fff_print/test_gcode.cpp index 34b40d1ff4..3ec1758b4e 100644 --- a/tests/fff_print/test_gcode.cpp +++ b/tests/fff_print/test_gcode.cpp @@ -7,7 +7,7 @@ using namespace Slic3r; SCENARIO("Origin manipulation", "[GCode]") { - Slic3r::GCode gcodegen; + Slic3r::GCodeGenerator gcodegen; WHEN("set_origin to (10,0)") { gcodegen.set_origin(Vec2d(10,0)); REQUIRE(gcodegen.origin() == Vec2d(10, 0)); diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index 15ffaebb48..35c70ad0c8 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -2,7 +2,7 @@ #include -#include "libslic3r/GCodeWriter.hpp" +#include "libslic3r/GCode/GCodeWriter.hpp" using namespace Slic3r; diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 69ae90178a..90693c8a04 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(${_TEST_NAME}_tests test_aabbindirect.cpp test_kdtreeindirect.cpp test_arachne.cpp + test_arc_welder.cpp test_clipper_offset.cpp test_clipper_utils.cpp test_color.cpp diff --git a/tests/libslic3r/test_arc_welder.cpp b/tests/libslic3r/test_arc_welder.cpp new file mode 100644 index 0000000000..2686216954 --- /dev/null +++ b/tests/libslic3r/test_arc_welder.cpp @@ -0,0 +1,264 @@ +#include +#include + +#include +#include + +using namespace Slic3r; + +TEST_CASE("arc basics", "[ArcWelder]") { + using namespace Slic3r::Geometry; + + WHEN("arc from { 2000.f, 1000.f } to { 1000.f, 2000.f }") { + Vec2f p1{ 2000.f, 1000.f }; + Vec2f p2{ 1000.f, 2000.f }; + float r{ 1000.f }; + THEN("90 degrees arc, CCW") { + Vec2f c = ArcWelder::arc_center(p1, p2, r, true); + REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f })); + REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.5 * M_PI)); + REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.5 * M_PI).epsilon(0.001)); + } + THEN("90 degrees arc, CW") { + Vec2f c = ArcWelder::arc_center(p1, p2, r, false); + REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f })); + } + THEN("270 degrees arc, CCW") { + Vec2f c = ArcWelder::arc_center(p1, p2, - r, true); + REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f })); + REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx(1.5 * M_PI)); + REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * 1.5 * M_PI).epsilon(0.001)); + } + THEN("270 degrees arc, CW") { + Vec2f c = ArcWelder::arc_center(p1, p2, - r, false); + REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f })); + } + } + WHEN("arc from { 1707.11f, 1707.11f } to { 1000.f, 2000.f }") { + Vec2f p1{ 1707.11f, 1707.11f }; + Vec2f p2{ 1000.f, 2000.f }; + float r{ 1000.f }; + Vec2f center1 = Vec2f{ 1000.f, 1000.f }; + // Center on the other side of the CCW arch. + Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1); + THEN("45 degrees arc, CCW") { + Vec2f c = ArcWelder::arc_center(p1, p2, r, true); + REQUIRE(is_approx(c, center1, 1.f)); + REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.25 * M_PI)); + REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.25 * M_PI).epsilon(0.001)); + } + THEN("45 degrees arc, CW") { + Vec2f c = ArcWelder::arc_center(p1, p2, r, false); + REQUIRE(is_approx(c, center2, 1.f)); + } + THEN("315 degrees arc, CCW") { + Vec2f c = ArcWelder::arc_center(p1, p2, - r, true); + REQUIRE(is_approx(c, center2, 1.f)); + REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx((2. - 0.25) * M_PI)); + REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 0.25) * M_PI).epsilon(0.001)); + } + THEN("315 degrees arc, CW") { + Vec2f c = ArcWelder::arc_center(p1, p2, - r, false); + REQUIRE(is_approx(c, center1, 1.f)); + } + } + WHEN("arc from { 1866.f, 1500.f } to { 1000.f, 2000.f }") { + Vec2f p1{ 1866.f, 1500.f }; + Vec2f p2{ 1000.f, 2000.f }; + float r{ 1000.f }; + Vec2f center1 = Vec2f{ 1000.f, 1000.f }; + // Center on the other side of the CCW arch. + Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1); + THEN("60 degrees arc, CCW") { + Vec2f c = ArcWelder::arc_center(p1, p2, r, true); + REQUIRE(is_approx(c, center1, 1.f)); + REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, r), float(M_PI / 3.), 0.001f)); + REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * M_PI / 3.).epsilon(0.001)); + } + THEN("60 degrees arc, CW") { + Vec2f c = ArcWelder::arc_center(p1, p2, r, false); + REQUIRE(is_approx(c, center2, 1.f)); + } + THEN("300 degrees arc, CCW") { + Vec2f c = ArcWelder::arc_center(p1, p2, - r, true); + REQUIRE(is_approx(c, center2, 1.f)); + REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, - r), float((2. - 1./3.) * M_PI), 0.001f)); + REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 1. / 3.) * M_PI).epsilon(0.001)); + } + THEN("300 degrees arc, CW") { + Vec2f c = ArcWelder::arc_center(p1, p2, - r, false); + REQUIRE(is_approx(c, center1, 1.f)); + } + } +} + +TEST_CASE("arc discretization", "[ArcWelder]") { + using namespace Slic3r::Geometry; + WHEN("arc from { 2, 1 } to { 1, 2 }") { + const Point p1 = Point::new_scale(2., 1.); + const Point p2 = Point::new_scale(1., 2.); + const Point center = Point::new_scale(1., 1.); + const float radius = scaled(1.); + const float resolution = scaled(0.002); + auto test = [center, resolution, radius](const Point &p1, const Point &p2, const float r, const bool ccw) { + Vec2f c = ArcWelder::arc_center(p1.cast(), p2.cast(), r, ccw); + REQUIRE((p1.cast() - c).norm() == Approx(radius)); + REQUIRE((c - center.cast()).norm() == Approx(0.)); + Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution); + REQUIRE(pts.size() >= 2); + REQUIRE(pts.front() == p1); + REQUIRE(pts.back() == p2); + for (const Point &p : pts) + REQUIRE(std::abs((p.cast() - c.cast()).norm() - double(radius)) < double(resolution + SCALED_EPSILON)); + }; + THEN("90 degrees arc, CCW") { + test(p1, p2, radius, true); + } + THEN("270 degrees arc, CCW") { + test(p2, p1, - radius, true); + } + THEN("90 degrees arc, CW") { + test(p2, p1, radius, false); + } + THEN("270 degrees arc, CW") { + test(p1, p2, - radius, false); + } + } +} + +TEST_CASE("arc fitting", "[ArcWelder]") { + using namespace Slic3r::Geometry; + + WHEN("arc from { 2, 1 } to { 1, 2 }") { + const Point p1 = Point::new_scale(2., 1.); + const Point p2 = Point::new_scale(1., 2.); + const Point center = Point::new_scale(1., 1.); + const float radius = scaled(1.); + const float resolution = scaled(0.002); + auto test = [center, resolution](const Point &p1, const Point &p2, const float r, const bool ccw) { + Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution); + ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution); + REQUIRE(path.size() == 2); + REQUIRE(path.front().point == p1); + REQUIRE(path.front().radius == 0.f); + REQUIRE(path.back().point == p2); + REQUIRE(path.back().radius == Approx(r)); + REQUIRE(path.back().ccw() == ccw); + }; + THEN("90 degrees arc, CCW is fitted") { + test(p1, p2, radius, true); + } + THEN("270 degrees arc, CCW is fitted") { + test(p2, p1, - radius, true); + } + THEN("90 degrees arc, CW is fitted") { + test(p2, p1, radius, false); + } + THEN("270 degrees arc, CW is fitted") { + test(p1, p2, - radius, false); + } + } + + WHEN("arc from { 2, 1 } to { 1, 2 }, another arc from { 2, 1 } to { 0, 2 }, tangentially connected") { + const Point p1 = Point::new_scale(2., 1.); + const Point p2 = Point::new_scale(1., 2.); + const Point p3 = Point::new_scale(0., 3.); + const Point center1 = Point::new_scale(1., 1.); + const Point center2 = Point::new_scale(1., 3.); + const float radius = scaled(1.); + const float resolution = scaled(0.002); + auto test = [center1, center2, resolution](const Point &p1, const Point &p2, const Point &p3, const float r, const bool ccw) { + Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution); + { + Points pts2 = ArcWelder::arc_discretize(p2, p3, - r, ! ccw, resolution); + REQUIRE(pts.back() == pts2.front()); + pts.insert(pts.end(), std::next(pts2.begin()), pts2.end()); + } + ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution); + REQUIRE(path.size() == 3); + REQUIRE(path.front().point == p1); + REQUIRE(path.front().radius == 0.f); + REQUIRE(path[1].point == p2); + REQUIRE(path[1].radius == Approx(r)); + REQUIRE(path[1].ccw() == ccw); + REQUIRE(path.back().point == p3); + REQUIRE(path.back().radius == Approx(- r)); + REQUIRE(path.back().ccw() == ! ccw); + }; + THEN("90 degrees arches, CCW are fitted") { + test(p1, p2, p3, radius, true); + } + THEN("270 degrees arc, CCW is fitted") { + test(p3, p2, p1, -radius, true); + } + THEN("90 degrees arc, CW is fitted") { + test(p3, p2, p1, radius, false); + } + THEN("270 degrees arc, CW is fitted") { + test(p1, p2, p3, -radius, false); + } + } +} + +TEST_CASE("arc wedge test", "[ArcWelder]") { + using namespace Slic3r::Geometry; + + WHEN("test point inside wedge, arc from { 2, 1 } to { 1, 2 }") { + const int64_t s = 1000000; + const Vec2i64 p1{ 2 * s, s }; + const Vec2i64 p2{ s, 2 * s }; + const Vec2i64 center{ s, s }; + const int64_t radius{ s }; + auto test = [center]( + // Arc data + const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw, + // Test data + const Vec2i64 &ptest, const bool ptest_inside) { + const Vec2d c = ArcWelder::arc_center(p1.cast(), p2.cast(), double(r), ccw); + REQUIRE(is_approx(c, center.cast())); + REQUIRE(ArcWelder::inside_arc_wedge(p1, p2, center, r > 0, ccw, ptest) == ptest_inside); + REQUIRE(ArcWelder::inside_arc_wedge(p1.cast(), p2.cast(), double(r), ccw, ptest.cast()) == ptest_inside); + }; + auto test_quadrants = [center, test]( + // Arc data + const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw, + // Test data + const Vec2i64 &ptest1, const bool ptest_inside1, + const Vec2i64 &ptest2, const bool ptest_inside2, + const Vec2i64 &ptest3, const bool ptest_inside3, + const Vec2i64 &ptest4, const bool ptest_inside4) { + test(p1, p2, r, ccw, ptest1 + center, ptest_inside1); + test(p1, p2, r, ccw, ptest2 + center, ptest_inside2); + test(p1, p2, r, ccw, ptest3 + center, ptest_inside3); + test(p1, p2, r, ccw, ptest4 + center, ptest_inside4); + }; + THEN("90 degrees arc, CCW") { + test_quadrants(p1, p2, radius, true, + Vec2i64{ s, s }, true, + Vec2i64{ s, - s }, false, + Vec2i64{ - s, s }, false, + Vec2i64{ - s, - s }, false); + } + THEN("270 degrees arc, CCW") { + test_quadrants(p2, p1, -radius, true, + Vec2i64{ s, s }, false, + Vec2i64{ s, - s }, true, + Vec2i64{ - s, s }, true, + Vec2i64{ - s, - s }, true); + } + THEN("90 degrees arc, CW") { + test_quadrants(p2, p1, radius, false, + Vec2i64{ s, s }, true, + Vec2i64{ s, - s }, false, + Vec2i64{ - s, s }, false, + Vec2i64{ - s, - s }, false); + } + THEN("270 degrees arc, CW") { + test_quadrants(p1, p2, -radius, false, + Vec2i64{ s, s }, false, + Vec2i64{ s, - s }, true, + Vec2i64{ - s, s }, true, + Vec2i64{ - s, - s }, true); + } + } +} diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index d96a2b3e2a..867ff25c62 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -84,16 +84,16 @@ TEST_CASE("Line::perpendicular_to", "[Geometry]") { TEST_CASE("Polygon::contains works properly", "[Geometry]"){ // this test was failing on Windows (GH #1950) Slic3r::Polygon polygon(Points({ - Point(207802834,-57084522), - Point(196528149,-37556190), - Point(173626821,-25420928), - Point(171285751,-21366123), - Point(118673592,-21366123), - Point(116332562,-25420928), - Point(93431208,-37556191), - Point(82156517,-57084523), - Point(129714478,-84542120), - Point(160244873,-84542120) + {207802834,-57084522}, + {196528149,-37556190}, + {173626821,-25420928}, + {171285751,-21366123}, + {118673592,-21366123}, + {116332562,-25420928}, + {93431208,-37556191}, + {82156517,-57084523}, + {129714478,-84542120}, + {160244873,-84542120} })); Point point(95706562, -57294774); REQUIRE(polygon.contains(point)); @@ -196,6 +196,41 @@ TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){ REQUIRE(area.area() == Slic3r::Polygon(Points({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area()); } +SCENARIO("Circle Fit, 3 points", "[Geometry]") { + WHEN("Three points make a circle") { + double s1 = scaled(1.); + THEN("circle_center(): A center point { 0, 0 } is returned") { + Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON); + REQUIRE(is_approx(center, Vec2d(0, 0))); + } + THEN("circle_center(): A center point { 0, 0 } is returned for points in reverse") { + Vec2d center = Geometry::circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON); + REQUIRE(is_approx(center, Vec2d(0, 0))); + } + THEN("try_circle_center(): A center point { 0, 0 } is returned") { + std::optional center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON); + REQUIRE(center); + REQUIRE(is_approx(*center, Vec2d(0, 0))); + } + THEN("try_circle_center(): A center point { 0, 0 } is returned for points in reverse") { + std::optional center = Geometry::try_circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON); + REQUIRE(center); + REQUIRE(is_approx(*center, Vec2d(0, 0))); + } + } + WHEN("Three points are collinear") { + double s1 = scaled(1.); + THEN("circle_center(): A center point { 2, 0 } is returned") { + Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON); + REQUIRE(is_approx(center, Vec2d(2. * s1, 0))); + } + THEN("try_circle_center(): Fails for collinear points") { + std::optional center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON); + REQUIRE(! center); + } + } +} + SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") { GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") { Vec2d expected_center(-6, 0); @@ -310,6 +345,7 @@ SCENARIO("Path chaining", "[Geometry]") { GIVEN("A path") { Points points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) }; THEN("Chained with no diagonals (thus 26 units long)") { + // if chain_points() works correctly, these points should be joined with no diagonal paths std::vector indices = chain_points(points); for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) { double dist = (points.at(indices.at(i)).cast() - points.at(indices.at(i+1)).cast()).norm(); diff --git a/tests/libslic3r/test_polyline.cpp b/tests/libslic3r/test_polyline.cpp index 8271554041..89a84d40a3 100644 --- a/tests/libslic3r/test_polyline.cpp +++ b/tests/libslic3r/test_polyline.cpp @@ -5,6 +5,25 @@ using namespace Slic3r; +SCENARIO("Simplify polyne, template", "[Polyline]") +{ + Points polyline{ {0,0}, {1000,0}, {2000,0}, {2000,1000}, {2000,2000}, {1000,2000}, {0,2000}, {0,1000}, {0,0} }; + WHEN("simplified with Douglas-Peucker with back inserter") { + Points out; + douglas_peucker(polyline.begin(), polyline.end(), std::back_inserter(out), 10, [](const Point &p) { return p; }); + THEN("simplified correctly") { + REQUIRE(out == Points{ {0,0}, {2000,0}, {2000,2000}, {0,2000}, {0,0} }); + } + } + WHEN("simplified with Douglas-Peucker in place") { + Points out{ polyline }; + out.erase(douglas_peucker(out.begin(), out.end(), out.begin(), 10, [](const Point &p) { return p; }), out.end()); + THEN("simplified correctly") { + REQUIRE(out == Points{ {0,0}, {2000,0}, {2000,2000}, {0,2000}, {0,0} }); + } + } +} + SCENARIO("Simplify polyline", "[Polyline]") { GIVEN("polyline 1") { diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 3f75617dd5..500cbee836 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -4,7 +4,7 @@ namespace Slic3r { REGISTER_CLASS(ExPolygon, "ExPolygon"); -REGISTER_CLASS(GCode, "GCode"); +REGISTER_CLASS(GCodeGenerator, "GCode"); REGISTER_CLASS(Line, "Line"); REGISTER_CLASS(Polygon, "Polygon"); REGISTER_CLASS(Polyline, "Polyline"); diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp index f6365a20a5..d31438c0ac 100644 --- a/xs/xsp/Geometry.xsp +++ b/xs/xsp/Geometry.xsp @@ -20,15 +20,6 @@ convex_hull(points) OUTPUT: RETVAL -std::vector -chained_path_from(points, start_from) - Points points - Point* start_from - CODE: - RETVAL = chain_points(points, start_from); - OUTPUT: - RETVAL - double rad2deg(angle) double angle diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index 7b1fbb83c7..95c1d2da3d 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -19,7 +19,6 @@ Lines lines(); Clone split_at_vertex(Point* point) %code{% RETVAL = THIS->split_at_vertex(*point); %}; - Clone split_at_index(int index); Clone split_at_first_point(); double length(); double area();