Merge branch 'vb_arc_welder' into master_262

This commit is contained in:
Vojtech Bubnik 2023-08-28 15:50:00 +02:00
commit 3cfe2f4a3a
73 changed files with 4085 additions and 1579 deletions

View File

@ -15,7 +15,6 @@ our @EXPORT_OK = qw(
X Y Z X Y Z
convex_hull convex_hull
chained_path_from
deg2rad deg2rad
rad2deg rad2deg
); );

View File

@ -345,7 +345,7 @@ public:
return dist; return dist;
} }
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius) std::vector<size_t> 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<Floating>(), radius * radius); return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
} }

View File

@ -490,8 +490,10 @@ static void make_inner_brim(const Print &print,
loops = union_pt_chained_outside_in(loops); loops = union_pt_chained_outside_in(loops);
std::reverse(loops.begin(), loops.end()); std::reverse(loops.begin(), loops.end());
extrusion_entities_append_loops(brim.entities, std::move(loops), ExtrusionRole::Skirt, float(flow.mm3_per_mm()), extrusion_entities_append_loops(brim.entities, std::move(loops),
float(flow.width()), float(print.skirt_first_layer_height())); 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. // 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()) { 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(); auto *loop = new ExtrusionLoop();
brim.entities.emplace_back(loop); 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 &points = loop->paths.front().polyline.points;
points.reserve(first_path.size()); points.reserve(first_path.size());
for (const ClipperLib_Z::IntPoint &pt : first_path) 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; ExtrusionEntityCollection this_loop_trimmed;
this_loop_trimmed.entities.reserve(j - i); this_loop_trimmed.entities.reserve(j - i);
for (; i < 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; const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
Points &points = dynamic_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points; Points &points = dynamic_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points;
points.reserve(path.size()); points.reserve(path.size());
@ -699,7 +705,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
} }
} }
} else { } 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); make_inner_brim(print, top_level_objects_with_brim, bottom_layers_expolygons, brim);

View File

@ -142,8 +142,12 @@ set(SLIC3R_SOURCES
GCode/ConflictChecker.hpp GCode/ConflictChecker.hpp
GCode/CoolingBuffer.cpp GCode/CoolingBuffer.cpp
GCode/CoolingBuffer.hpp GCode/CoolingBuffer.hpp
GCode/ExtrusionProcessor.cpp
GCode/ExtrusionProcessor.hpp
GCode/FindReplace.cpp GCode/FindReplace.cpp
GCode/FindReplace.hpp GCode/FindReplace.hpp
GCode/GCodeWriter.cpp
GCode/GCodeWriter.hpp
GCode/PostProcessor.cpp GCode/PostProcessor.cpp
GCode/PostProcessor.hpp GCode/PostProcessor.hpp
GCode/PressureEqualizer.cpp GCode/PressureEqualizer.cpp
@ -156,10 +160,16 @@ set(SLIC3R_SOURCES
GCode/SpiralVase.hpp GCode/SpiralVase.hpp
GCode/SeamPlacer.cpp GCode/SeamPlacer.cpp
GCode/SeamPlacer.hpp GCode/SeamPlacer.hpp
GCode/SmoothPath.cpp
GCode/SmoothPath.hpp
GCode/ToolOrdering.cpp GCode/ToolOrdering.cpp
GCode/ToolOrdering.hpp GCode/ToolOrdering.hpp
GCode/Wipe.cpp
GCode/Wipe.hpp
GCode/WipeTower.cpp GCode/WipeTower.cpp
GCode/WipeTower.hpp GCode/WipeTower.hpp
GCode/WipeTowerIntegration.cpp
GCode/WipeTowerIntegration.hpp
GCode/GCodeProcessor.cpp GCode/GCodeProcessor.cpp
GCode/GCodeProcessor.hpp GCode/GCodeProcessor.hpp
GCode/AvoidCrossingPerimeters.cpp GCode/AvoidCrossingPerimeters.cpp
@ -170,10 +180,10 @@ set(SLIC3R_SOURCES
GCodeReader.hpp GCodeReader.hpp
# GCodeSender.cpp # GCodeSender.cpp
# GCodeSender.hpp # GCodeSender.hpp
GCodeWriter.cpp
GCodeWriter.hpp
Geometry.cpp Geometry.cpp
Geometry.hpp Geometry.hpp
Geometry/ArcWelder.cpp
Geometry/ArcWelder.hpp
Geometry/Bicubic.hpp Geometry/Bicubic.hpp
Geometry/Circle.cpp Geometry/Circle.cpp
Geometry/Circle.hpp Geometry/Circle.hpp

View File

@ -1,7 +1,7 @@
#include "CustomGCode.hpp" #include "CustomGCode.hpp"
#include "Config.hpp" #include "Config.hpp"
#include "GCode.hpp" #include "GCode.hpp"
#include "GCodeWriter.hpp" #include "GCode/GCodeWriter.hpp"
namespace Slic3r { namespace Slic3r {

View File

@ -1,5 +1,5 @@
#include "Extruder.hpp" #include "Extruder.hpp"
#include "GCodeWriter.hpp" #include "GCode/GCodeWriter.hpp"
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
namespace Slic3r { namespace Slic3r {

View File

@ -2,6 +2,7 @@
#include "ExtrusionEntityCollection.hpp" #include "ExtrusionEntityCollection.hpp"
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Exception.hpp"
#include "Extruder.hpp" #include "Extruder.hpp"
#include "Flow.hpp" #include "Flow.hpp"
#include <cmath> #include <cmath>
@ -38,12 +39,12 @@ double ExtrusionPath::length() const
void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
{ {
for (const Polyline &polyline : polylines) 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 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 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. // 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. // 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(); bool bridge = this->role().is_bridge();
assert(! bridge || this->width == this->height); assert(! bridge || m_attributes.width == m_attributes.height);
auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f); 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)); 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<double>::max(); double min_mm3_per_mm = std::numeric_limits<double>::max();
for (const ExtrusionPath &path : this->paths) 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; return min_mm3_per_mm;
} }
@ -112,21 +113,34 @@ Polyline ExtrusionMultiPath::as_polyline() const
return out; return out;
} }
bool ExtrusionLoop::make_clockwise() double ExtrusionLoop::area() const
{ {
bool was_ccw = this->polygon().is_counter_clockwise(); double a = 0;
if (was_ccw) this->reverse(); for (const ExtrusionPath &path : this->paths) {
return was_ccw; 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<double>(), it->cast<double>());
prev = *it;
} }
}
bool ExtrusionLoop::make_counter_clockwise() }
{ return a * 0.5;
bool was_cw = this->polygon().is_clockwise();
if (was_cw) this->reverse();
return was_cw;
} }
void ExtrusionLoop::reverse() 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) for (ExtrusionPath &path : this->paths)
path.reverse(); 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 // now split path_idx in two parts
const ExtrusionPath &path = this->paths[path_idx]; const ExtrusionPath &path = this->paths[path_idx];
ExtrusionPath p1(path.role(), path.mm3_per_mm, path.width, path.height); ExtrusionPath p1(path.attributes());
ExtrusionPath p2(path.role(), path.mm3_per_mm, path.width, path.height); ExtrusionPath p2(path.attributes());
path.polyline.split_at(p, &p1.polyline, &p2.polyline); path.polyline.split_at(p, &p1.polyline, &p2.polyline);
if (this->paths.size() == 1) { if (this->paths.size() == 1) {
@ -316,7 +330,7 @@ double ExtrusionLoop::min_mm3_per_mm() const
{ {
double min_mm3_per_mm = std::numeric_limits<double>::max(); double min_mm3_per_mm = std::numeric_limits<double>::max();
for (const ExtrusionPath &path : this->paths) 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; return min_mm3_per_mm;
} }

View File

@ -3,10 +3,12 @@
#include "libslic3r.h" #include "libslic3r.h"
#include "ExtrusionRole.hpp" #include "ExtrusionRole.hpp"
#include "Flow.hpp"
#include "Polygon.hpp" #include "Polygon.hpp"
#include "Polyline.hpp" #include "Polyline.hpp"
#include <assert.h> #include <assert.h>
#include <optional>
#include <string_view> #include <string_view>
#include <numeric> #include <numeric>
@ -55,28 +57,91 @@ public:
virtual double total_volume() const = 0; virtual double total_volume() const = 0;
}; };
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr; using ExtrusionEntitiesPtr = std::vector<ExtrusionEntity*>;
// 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<typename Type>
const Type* cast() const { return dynamic_cast<const Type*>(m_extrusion_entity); }
bool flipped() const { return m_flipped; }
private:
const ExtrusionEntity *m_extrusion_entity;
bool m_flipped;
};
using ExtrusionEntityReferences = std::vector<ExtrusionEntityReference>;
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<OverhangAttributes> overhang_attributes;
};
inline bool operator==(const ExtrusionAttributes &lhs, const ExtrusionAttributes &rhs)
{
return static_cast<const ExtrusionFlow&>(lhs) == static_cast<const ExtrusionFlow&>(rhs) &&
lhs.role == rhs.role;
}
class ExtrusionPath : public ExtrusionEntity class ExtrusionPath : public ExtrusionEntity
{ {
public: public:
Polyline polyline; 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) : m_attributes{ 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 ExtrusionAttributes &attributes) : m_attributes(attributes) {}
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(const ExtrusionPath &rhs) : polyline(rhs.polyline), m_attributes(rhs.m_attributes) {}
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(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), m_attributes(rhs.m_attributes) {}
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(const Polyline &polyline, const ExtrusionAttributes &attribs) : polyline(polyline), m_attributes(attribs) {}
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(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=(const ExtrusionPath &rhs) { this->polyline = rhs.polyline; m_attributes = rhs.m_attributes; 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=(ExtrusionPath &&rhs) { this->polyline = std::move(rhs.polyline); m_attributes = rhs.m_attributes; return *this; }
ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); } ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
// Create a new object, initialize it with this object using the move semantics. // Create a new object, initialize it with this object using the move semantics.
@ -97,7 +162,16 @@ public:
void clip_end(double distance); void clip_end(double distance);
void simplify(double tolerance); void simplify(double tolerance);
double length() const override; 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<OverhangAttributes>& overhang_attributes_mutable() { return m_attributes.overhang_attributes; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // 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. // 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;
@ -109,23 +183,25 @@ public:
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } { 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; } { 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; } 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_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); } void collect_points(Points &dst) const override { append(dst, this->polyline.points); }
double total_volume() const override { return mm3_per_mm * unscale<double>(length()); } double total_volume() const override { return m_attributes.mm3_per_mm * unscale<double>(length()); }
private: 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 class ExtrusionPathOriented : public ExtrusionPath
{ {
public: 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); } ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); }
// Create a new object, initialize it with this object using the move semantics. // Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); } ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); }
@ -192,7 +268,8 @@ class ExtrusionLoop : public ExtrusionEntity
public: public:
ExtrusionPaths paths; 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(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(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {}
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role) ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
@ -204,9 +281,14 @@ public:
ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); } ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); }
// Create a new object, initialize it with this object using the move semantics. // Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionLoop(std::move(*this)); } ExtrusionEntity* clone_move() override { return new ExtrusionLoop(std::move(*this)); }
bool make_clockwise(); double area() const;
bool make_counter_clockwise(); 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; 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& 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& 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]; } const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; }
@ -259,59 +341,51 @@ public:
#endif /* NDEBUG */ #endif /* NDEBUG */
private: 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()); dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines) for (Polyline &polyline : polylines)
if (polyline.is_valid()) { if (polyline.is_valid())
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height)); dst.emplace_back(polyline, attributes);
dst.back().polyline = polyline;
}
} }
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()); dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines) for (Polyline &polyline : polylines)
if (polyline.is_valid()) { if (polyline.is_valid())
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height)); dst.emplace_back(std::move(polyline), attributes);
dst.back().polyline = std::move(polyline);
}
polylines.clear(); 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()); dst.reserve(dst.size() + polylines.size());
for (const Polyline &polyline : polylines) for (const Polyline &polyline : polylines)
if (polyline.is_valid()) { 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.emplace_back(can_reverse ? new ExtrusionPath(polyline, attributes) : new ExtrusionPathOriented(polyline, attributes));
dst.push_back(extrusion_path);
extrusion_path->polyline = polyline;
}
} }
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, 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()); dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines) for (Polyline &polyline : polylines)
if (polyline.is_valid()) { 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.emplace_back(can_reverse ?
dst.push_back(extrusion_path); new ExtrusionPath(std::move(polyline), attributes) :
extrusion_path->polyline = std::move(polyline); new ExtrusionPathOriented(std::move(polyline), attributes));
}
polylines.clear(); 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()); dst.reserve(dst.size() + loops.size());
for (Polygon &poly : loops) { for (Polygon &poly : loops) {
if (poly.is_valid()) { 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 = std::move(poly.points);
path.polyline.points.push_back(path.polyline.points.front()); path.polyline.points.push_back(path.polyline.points.front());
dst.emplace_back(new ExtrusionLoop(std::move(path))); dst.emplace_back(new ExtrusionLoop(std::move(path)));
@ -320,22 +394,14 @@ inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons
loops.clear(); 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()); dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines) { for (Polyline &polyline : polylines)
if (polyline.is_valid()) { if (polyline.is_valid())
if (polyline.is_closed()) { dst.emplace_back(polyline.is_closed() ?
ExtrusionPath extrusion_path(role, mm3_per_mm, width, height); static_cast<ExtrusionEntity*>(new ExtrusionLoop(ExtrusionPath{ std::move(polyline), attributes })) :
extrusion_path.polyline = std::move(polyline); static_cast<ExtrusionEntity*>(new ExtrusionPath(std::move(polyline), attributes)));
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);
}
}
}
polylines.clear(); polylines.clear();
} }

View File

@ -6,6 +6,7 @@
namespace Slic3r { namespace Slic3r {
#if 0
void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role) void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role)
{ {
if (role != ExtrusionRole::Mixed) { if (role != ExtrusionRole::Mixed) {
@ -17,6 +18,7 @@ void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities,
last); last);
} }
} }
#endif
ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths) ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths)
: no_sort(false) : no_sort(false)
@ -83,18 +85,6 @@ void ExtrusionEntityCollection::remove(size_t i)
this->entities.erase(this->entities.begin() + 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 void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{ {
for (const ExtrusionEntity *entity : this->entities) for (const ExtrusionEntity *entity : this->entities)

View File

@ -7,6 +7,7 @@
namespace Slic3r { namespace Slic3r {
#if 0
// Remove those items from extrusion_entities, that do not match role. // Remove those items from extrusion_entities, that do not match role.
// Do nothing if role is mixed. // Do nothing if role is mixed.
// Removed elements are NOT being deleted. // 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); filter_by_extrusion_role_in_place(out, role);
return out; return out;
} }
#endif
class ExtrusionEntityCollection : public ExtrusionEntity class ExtrusionEntityCollection : public ExtrusionEntity
{ {
@ -96,9 +98,6 @@ public:
} }
void replace(size_t i, const ExtrusionEntity &entity); void replace(size_t i, const ExtrusionEntity &entity);
void remove(size_t i); 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; void reverse() override;
const Point& first_point() const override { return this->entities.front()->first_point(); } const Point& first_point() const override { return this->entities.front()->first_point(); }
const Point& last_point() const override { return this->entities.back()->last_point(); } const Point& last_point() const override { return this->entities.back()->last_point(); }

View File

@ -82,6 +82,7 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
bool is_external_perimeter() const { return this->is_perimeter() && this->is_external(); } bool is_external_perimeter() const { return this->is_perimeter() && this->is_external(); }
bool is_infill() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Infill); } 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_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_external() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::External); }
bool is_bridge() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Bridge); } 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_base() const { return this->is_support() && ! this->is_external(); }
bool is_support_interface() 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); } 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 // Special flags describing loop

View File

@ -957,9 +957,9 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const
polyline.reserve(path.polyline.points.size()); polyline.reserve(path.polyline.points.size());
float scalex = float(viewport.size().x()) / float(bbox.size().x()); float scalex = float(viewport.size().x()) / float(bbox.size().x());
float scaley = float(viewport.size().y()) / float(bbox.size().y()); 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; //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("scalex: %f, scaley: %f\n", scalex, scaley);
// printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y); // printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y);
for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) { for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) {

View File

@ -558,8 +558,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
} else { } else {
extrusion_entities_append_paths( extrusion_entities_append_paths(
eec->entities, std::move(polylines), eec->entities, std::move(polylines),
surface_fill.params.extrusion_role, ExtrusionAttributes{ surface_fill.params.extrusion_role,
flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height()); 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())); 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; eec->no_sort = true;
extrusion_entities_append_paths( extrusion_entities_append_paths(
eec->entities, std::move(polylines), eec->entities, std::move(polylines),
ExtrusionRole::Ironing, ExtrusionAttributes{ ExtrusionRole::Ironing,
flow_mm3_per_mm, extrusion_width, float(extrusion_height)); 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())); insert_fills_into_islands(*this, ironing_params.region_id, fill_begin, uint32_t(ironing_params.layerm->fills().size()));
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -5,18 +5,22 @@
#include "JumpPointSearch.hpp" #include "JumpPointSearch.hpp"
#include "libslic3r.h" #include "libslic3r.h"
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include "GCodeWriter.hpp"
#include "Layer.hpp" #include "Layer.hpp"
#include "Point.hpp" #include "Point.hpp"
#include "PlaceholderParser.hpp" #include "PlaceholderParser.hpp"
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
#include "Geometry/ArcWelder.hpp"
#include "GCode/AvoidCrossingPerimeters.hpp" #include "GCode/AvoidCrossingPerimeters.hpp"
#include "GCode/CoolingBuffer.hpp" #include "GCode/CoolingBuffer.hpp"
#include "GCode/FindReplace.hpp" #include "GCode/FindReplace.hpp"
#include "GCode/GCodeWriter.hpp"
#include "GCode/PressureEqualizer.hpp"
#include "GCode/RetractWhenCrossingPerimeters.hpp" #include "GCode/RetractWhenCrossingPerimeters.hpp"
#include "GCode/SmoothPath.hpp"
#include "GCode/SpiralVase.hpp" #include "GCode/SpiralVase.hpp"
#include "GCode/ToolOrdering.hpp" #include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp" #include "GCode/Wipe.hpp"
#include "GCode/WipeTowerIntegration.hpp"
#include "GCode/SeamPlacer.hpp" #include "GCode/SeamPlacer.hpp"
#include "GCode/GCodeProcessor.hpp" #include "GCode/GCodeProcessor.hpp"
#include "EdgeGrid.hpp" #include "EdgeGrid.hpp"
@ -26,12 +30,10 @@
#include <map> #include <map>
#include <string> #include <string>
#include "GCode/PressureEqualizer.hpp"
namespace Slic3r { namespace Slic3r {
// Forward declarations. // Forward declarations.
class GCode; class GCodeGenerator;
namespace { struct Item; } namespace { struct Item; }
struct PrintInstance; struct PrintInstance;
@ -41,71 +43,11 @@ public:
bool enable; bool enable;
OozePrevention() : enable(false) {} OozePrevention() : enable(false) {}
std::string pre_toolchange(GCode &gcodegen); std::string pre_toolchange(GCodeGenerator &gcodegen);
std::string post_toolchange(GCode &gcodegen); std::string post_toolchange(GCodeGenerator &gcodegen);
private: private:
int _get_temp(const GCode &gcodegen) const; int _get_temp(const GCodeGenerator &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<WipeTower::ToolChangeResult> &priming,
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
const WipeTower::ToolChangeResult &final_purge) :
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
m_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<float> 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<Vec2d> m_extruder_offsets;
// Reference to cached values at the Printer class.
const std::vector<WipeTower::ToolChangeResult> &m_priming;
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
const WipeTower::ToolChangeResult &m_final_purge;
// Current layer index.
int m_layer_idx;
int m_tool_change_idx;
double m_last_wipe_tower_print_z = 0.f;
}; };
class ColorPrintColors class ColorPrintColors
@ -129,9 +71,9 @@ struct LayerResult {
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; } static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
}; };
class GCode { class GCodeGenerator {
public: public:
GCode() : GCodeGenerator() :
m_origin(Vec2d::Zero()), m_origin(Vec2d::Zero()),
m_enable_loop_clipping(true), m_enable_loop_clipping(true),
m_enable_cooling_markers(false), m_enable_cooling_markers(false),
@ -153,7 +95,7 @@ public:
m_silent_time_estimator_enabled(false), m_silent_time_estimator_enabled(false),
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max())) m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max()))
{} {}
~GCode() = default; ~GCodeGenerator() = default;
// throws std::runtime_exception on error, // throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled(). // 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)); } 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; } const Point& last_pos() const { return m_last_pos; }
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset. // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset.
Vec2d point_to_gcode(const Point &point) const; template<typename Derived>
Vec2d point_to_gcode(const Eigen::MatrixBase<Derived> &point) const {
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode(): first parameter is not a 2D vector");
return Vec2d(unscaled<double>(point.x()), unscaled<double>(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. // 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<typename Derived>
Vec2d point_to_gcode_quantized(const Eigen::MatrixBase<Derived> &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; Point gcode_to_point(const Vec2d &point) const;
const FullPrintConfig &config() const { return m_config; } const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; } 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. // Set of object & print layers of the same PrintObject and with the same print_z.
const ObjectsLayerToPrint &layers, const ObjectsLayerToPrint &layers,
const LayerTools &layer_tools, const LayerTools &layer_tools,
const GCode::SmoothPathCaches &smooth_path_caches,
const bool last_layer, const bool last_layer,
// Pairs of PrintObject index and its instance index. // Pairs of PrintObject index and its instance index.
const std::vector<const PrintInstance*> *ordering, const std::vector<const PrintInstance*> *ordering,
@ -264,6 +217,7 @@ private:
const ToolOrdering &tool_ordering, const ToolOrdering &tool_ordering,
const std::vector<const PrintInstance*> &print_object_instances_ordering, const std::vector<const PrintInstance*> &print_object_instances_ordering,
const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print, const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
const GCode::SmoothPathCache &smooth_path_cache_global,
GCodeOutputStream &output_stream); GCodeOutputStream &output_stream);
// Process all layers of a single object instance (sequential mode) with a parallel pipeline: // 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 // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
@ -273,6 +227,7 @@ private:
const ToolOrdering &tool_ordering, const ToolOrdering &tool_ordering,
ObjectsLayerToPrint layers_to_print, ObjectsLayerToPrint layers_to_print,
const size_t single_object_idx, const size_t single_object_idx,
const GCode::SmoothPathCache &smooth_path_cache_global,
GCodeOutputStream &output_stream); GCodeOutputStream &output_stream);
void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } 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<unsigned int> &extruder_ids); void set_extruders(const std::vector<unsigned int> &extruder_ids);
std::string preamble(); std::string preamble();
std::string change_layer(coordf_t print_z); 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_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, 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_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, 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_skirt(const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override,
std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.); 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 struct InstanceToPrint
{ {
@ -317,12 +275,14 @@ private:
const ObjectLayerToPrint &layer_to_print, const ObjectLayerToPrint &layer_to_print,
// Container for extruder overrides (when wiping into object or infill). // Container for extruder overrides (when wiping into object or infill).
const LayerTools &layer_tools, const LayerTools &layer_tools,
// Optional smooth path interpolating extrusion polylines.
const GCode::SmoothPathCache &smooth_path_cache,
// Is any extrusion possibly marked as wiping extrusion? // Is any extrusion possibly marked as wiping extrusion?
const bool is_anything_overridden, const bool is_anything_overridden,
// Round 1 (wiping into object or infill) or round 2 (normal extrusions). // Round 1 (wiping into object or infill) or round 2 (normal extrusions).
const bool print_wipe_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); std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
@ -333,8 +293,6 @@ private:
// Cache for custom seam enforcers/blockers for each layer. // Cache for custom seam enforcers/blockers for each layer.
SeamPlacer m_seam_placer; SeamPlacer m_seam_placer;
ExtrusionQualityEstimator m_extrusion_quality_estimator;
/* Origin of print coordinates expressed in unscaled G-code coordinates. /* Origin of print coordinates expressed in unscaled G-code coordinates.
This affects the input arguments supplied to the extrude*() and travel_to() This affects the input arguments supplied to the extrude*() and travel_to()
methods. */ methods. */
@ -375,7 +333,7 @@ private:
} m_placeholder_parser_integration; } m_placeholder_parser_integration;
OozePrevention m_ooze_prevention; OozePrevention m_ooze_prevention;
Wipe m_wipe; GCode::Wipe m_wipe;
AvoidCrossingPerimeters m_avoid_crossing_perimeters; AvoidCrossingPerimeters m_avoid_crossing_perimeters;
JPSPathFinder m_avoid_crossing_curled_overhangs; JPSPathFinder m_avoid_crossing_curled_overhangs;
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters; RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
@ -418,7 +376,7 @@ private:
std::unique_ptr<SpiralVase> m_spiral_vase; std::unique_ptr<SpiralVase> m_spiral_vase;
std::unique_ptr<GCodeFindReplace> m_find_replace; std::unique_ptr<GCodeFindReplace> m_find_replace;
std::unique_ptr<PressureEqualizer> m_pressure_equalizer; std::unique_ptr<PressureEqualizer> m_pressure_equalizer;
std::unique_ptr<WipeTowerIntegration> m_wipe_tower; std::unique_ptr<GCode::WipeTowerIntegration> m_wipe_tower;
// Heights (print_z) at which the skirt has already been extruded. // Heights (print_z) at which the skirt has already been extruded.
std::vector<coordf_t> m_skirt_done; std::vector<coordf_t> m_skirt_done;
@ -434,7 +392,8 @@ private:
// Processor // Processor
GCodeProcessor m_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_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_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); 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. // To control print speed of 1st object layer over raft interface.
bool object_layer_over_raft() const { return m_object_layer_over_raft; } bool object_layer_over_raft() const { return m_object_layer_over_raft; }
friend class Wipe; // Fill in cache of smooth paths for perimeters, fills and supports of the given object layers.
friend class WipeTowerIntegration; // 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 &params, GCode::SmoothPathCache &out);
friend class GCode::Wipe;
friend class GCode::WipeTowerIntegration;
friend class PressureEqualizer; friend class PressureEqualizer;
}; };

View File

@ -730,7 +730,7 @@ static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vec
return false; return false;
} }
static bool need_wipe(const GCode &gcodegen, static bool need_wipe(const GCodeGenerator &gcodegen,
const ExPolygons &lslices_offset, const ExPolygons &lslices_offset,
const std::vector<BoundingBox> &lslices_offset_bboxes, const std::vector<BoundingBox> &lslices_offset_bboxes,
const EdgeGrid::Grid &grid_lslices_offset, 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. // 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). // 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. // 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. // 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). // 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. // Otherwise perform the path planning in the coordinate system of the active object.

View File

@ -8,7 +8,7 @@
namespace Slic3r { namespace Slic3r {
// Forward declarations. // Forward declarations.
class GCode; class GCodeGenerator;
class Layer; class Layer;
class Point; class Point;
@ -25,13 +25,13 @@ public:
void init_layer(const Layer &layer); 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; bool could_be_wipe_disabled;
return this->travel_to(gcodegen, point, &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 { struct Boundary {
// Collection of boundaries used for detection of crossing perimeters for travels // Collection of boundaries used for detection of crossing perimeters for travels

View File

@ -43,7 +43,7 @@ public:
void raise() void raise()
{ {
if (valid()) { if (valid()) {
if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height; } if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height(); }
_curPileIdx++; _curPileIdx++;
} }
} }

View File

@ -19,7 +19,7 @@
namespace Slic3r { 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()); 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) void CoolingBuffer::reset(const Vec3d &position)
{ {
m_current_pos.assign(5, 0.f); assert(m_current_pos.size() == 5);
m_current_pos[0] = float(position.x()); m_current_pos[AxisIdx::X] = float(position.x());
m_current_pos[1] = float(position.y()); m_current_pos[AxisIdx::Y] = float(position.y());
m_current_pos[2] = float(position.z()); m_current_pos[AxisIdx::Z] = float(position.z());
m_current_pos[4] = float(m_config.travel_speed.value); m_current_pos[AxisIdx::E] = 0.f;
m_current_pos[AxisIdx::F] = float(m_config.travel_speed.value);
m_fan_speed = -1; m_fan_speed = -1;
} }
struct CoolingLine struct CoolingLine
{ {
enum Type { enum Type : uint32_t {
TYPE_SET_TOOL = 1 << 0, TYPE_SET_TOOL = 1 << 0,
TYPE_EXTRUDE_END = 1 << 1, TYPE_EXTRUDE_END = 1 << 1,
TYPE_BRIDGE_FAN_START = 1 << 2, TYPE_BRIDGE_FAN_START = 1 << 2,
TYPE_BRIDGE_FAN_END = 1 << 3, TYPE_BRIDGE_FAN_END = 1 << 3,
TYPE_G0 = 1 << 4, TYPE_G0 = 1 << 4,
TYPE_G1 = 1 << 5, TYPE_G1 = 1 << 5,
TYPE_ADJUSTABLE = 1 << 6, // G2 or G3: Arc interpolation
TYPE_EXTERNAL_PERIMETER = 1 << 7, 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. // The line sets a feedrate.
TYPE_HAS_F = 1 << 8, TYPE_HAS_F = 1 << 12,
TYPE_WIPE = 1 << 9, TYPE_WIPE = 1 << 13,
TYPE_G4 = 1 << 10, TYPE_G4 = 1 << 14,
TYPE_G92 = 1 << 11, TYPE_G92 = 1 << 15,
// Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block // 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!). // 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) // Custom fan speed (introduced for overhang fan speed)
TYPE_SET_FAN_SPEED = 1 << 13, TYPE_SET_FAN_SPEED = 1 << 17,
TYPE_RESET_FAN_SPEED = 1 << 14, TYPE_RESET_FAN_SPEED = 1 << 18,
}; };
CoolingLine(unsigned int type, size_t line_start, size_t line_end) : 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. // Parse the layer G-code for the moves, which could be adjusted.
// Return the list of parsed lines, bucketed by an extruder. // Return the list of parsed lines, bucketed by an extruder.
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::array<float, 5> &current_pos) const
{ {
std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size()); std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size());
std::vector<size_t> map_extruder_to_per_extruder_adjustment(m_num_extruders, 0); std::vector<size_t> map_extruder_to_per_extruder_adjustment(m_num_extruders, 0);
@ -347,7 +356,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
// for a sequence of extrusion moves. // for a sequence of extrusion moves.
size_t active_speed_modifier = size_t(-1); size_t active_speed_modifier = size_t(-1);
std::vector<float> new_pos; std::array<float, AxisIdx::Count> new_pos;
for (; *line_start != 0; line_start = line_end) for (; *line_start != 0; line_start = line_end)
{ {
while (*line_end != '\n' && *line_end != 0) while (*line_end != '\n' && *line_end != 0)
@ -362,12 +371,20 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
line.type = CoolingLine::TYPE_G0; line.type = CoolingLine::TYPE_G0;
else if (boost::starts_with(sline, "G1 ")) else if (boost::starts_with(sline, "G1 "))
line.type = CoolingLine::TYPE_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 ")) else if (boost::starts_with(sline, "G92 "))
line.type = CoolingLine::TYPE_G92; line.type = CoolingLine::TYPE_G92;
if (line.type) { 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. // Parse the G-code line.
new_pos = current_pos;
for (auto c = sline.begin() + 3;;) { for (auto c = sline.begin() + 3;;) {
// Skip whitespaces. // Skip whitespaces.
for (; c != sline.end() && (*c == ' ' || *c == '\t'); ++ c); for (; c != sline.end() && (*c == ' ' || *c == '\t'); ++ c);
@ -376,21 +393,31 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
// Parse the axis. // Parse the axis.
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : 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)) { if (axis != size_t(-1)) {
//auto [pend, ec] = //auto [pend, ec] =
fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]); 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. // Convert mm/min to mm/sec.
new_pos[4] /= 60.f; new_pos[AxisIdx::F] /= 60.f;
if ((line.type & CoolingLine::TYPE_G92) == 0) 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. // 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; 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. // Skip this word.
for (; c != sline.end() && *c != ' ' && *c != '\t'; ++ c); 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 external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
bool wipe = boost::contains(sline, ";_WIPE"); bool wipe = boost::contains(sline, ";_WIPE");
if (external_perimeter) if (external_perimeter)
@ -402,23 +429,41 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
active_speed_modifier = adjustment->lines.size(); active_speed_modifier = adjustment->lines.size();
} }
if ((line.type & CoolingLine::TYPE_G92) == 0) { 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) if (m_config.use_relative_e_distances.value)
// Reset extruder accumulator. // Reset extruder accumulator.
current_pos[3] = 0.f; current_pos[AxisIdx::E] = 0.f;
float dif[4]; float dif[4];
for (size_t i = 0; i < 4; ++ i) for (size_t i = 0; i < 4; ++ i)
dif[i] = new_pos[i] - current_pos[i]; dif[i] = new_pos[i] - current_pos[i];
float dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; float dxy2;
float dxyz2 = dxy2 + dif[2] * dif[2]; 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) { if (dxyz2 > 0.f) {
// Movement in xyz, calculate time from the xyz Euclidian distance. // Movement in xyz, calculate time from the xyz Euclidian distance.
line.length = sqrt(dxyz2); 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. // 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); assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
if (line.length > 0) { if (line.length > 0) {
assert(line.feedrate > 0); assert(line.feedrate > 0);
@ -430,7 +475,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
assert(adjustment->min_print_speed >= 0); 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); 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. // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
assert((line.type & CoolingLine::TYPE_HAS_F) == 0); assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
CoolingLine &sm = adjustment->lines[active_speed_modifier]; CoolingLine &sm = adjustment->lines[active_speed_modifier];
@ -447,7 +492,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
line.type = 0; 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")) { } else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
// Closing a block of non-zero length extrusion moves. // Closing a block of non-zero length extrusion moves.
line.type = CoolingLine::TYPE_EXTRUDE_END; line.type = CoolingLine::TYPE_EXTRUDE_END;

View File

@ -7,7 +7,7 @@
namespace Slic3r { namespace Slic3r {
class GCode; class GCodeGenerator;
class Layer; class Layer;
struct PerExtruderAdjustments; struct PerExtruderAdjustments;
@ -22,7 +22,7 @@ struct PerExtruderAdjustments;
// //
class CoolingBuffer { class CoolingBuffer {
public: public:
CoolingBuffer(GCode &gcodegen); CoolingBuffer(GCodeGenerator &gcodegen);
void reset(const Vec3d &position); void reset(const Vec3d &position);
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } 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); std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
@ -31,7 +31,7 @@ public:
private: private:
CoolingBuffer& operator=(const CoolingBuffer&) = delete; CoolingBuffer& operator=(const CoolingBuffer&) = delete;
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const; std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::array<float, 5> &current_pos) const;
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments); float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
// Returns the adjusted G-code. // Returns the adjusted G-code.
@ -40,9 +40,11 @@ private:
// G-code snippet cached for the support layers preceding an object layer. // G-code snippet cached for the support layers preceding an object layer.
std::string m_gcode; std::string m_gcode;
// Internal data. // Internal data.
// X,Y,Z,E,F
std::vector<char> m_axis; std::vector<char> m_axis;
std::vector<float> m_current_pos; enum AxisIdx : int {
X = 0, Y, Z, E, F, I, J, K, R, Count
};
std::array<float, 5> m_current_pos;
// Current known fan speed or -1 if not known yet. // Current known fan speed or -1 if not known yet.
int m_fan_speed; int m_fan_speed;
// Cached from GCodeWriter. // Cached from GCodeWriter.
@ -51,7 +53,7 @@ private:
// Highest of m_extruder_ids plus 1. // Highest of m_extruder_ids plus 1.
unsigned int m_num_extruders { 0 }; unsigned int m_num_extruders { 0 };
const std::string m_toolchange_prefix; 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. // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
const PrintConfig &m_config; const PrintConfig &m_config;
unsigned int m_current_extruder; unsigned int m_current_extruder;

View File

@ -0,0 +1,216 @@
#include "ExtrusionProcessor.hpp"
#include <string>
namespace Slic3r { namespace ExtrusionProcessor {
ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path,
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines)
{
std::vector<ExtendedPoint> extended_points = estimate_points_properties<true, true, true, true>(path.polyline.points,
unscaled_prev_layer, path.width());
std::vector<std::pair<float, float>> 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<double>()))));
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<OverhangAttributes>(
{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<Linef> &unscaled_prev_layer,
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines)
{
ExtrusionEntityCollection result{};
result.no_sort = ecc->no_sort;
for (const auto *e : ecc->entities) {
if (auto *col = dynamic_cast<const ExtrusionEntityCollection *>(e)) {
result.append(calculate_and_split_overhanging_extrusions(col, unscaled_prev_layer, prev_layer_curled_lines));
} else if (auto *loop = dynamic_cast<const ExtrusionLoop *>(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<const ExtrusionMultiPath *>(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<const ExtrusionPathOriented *>(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<const ExtrusionPath *>(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<float,float> 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<std::pair<int, ConfigOptionFloatOrPercent>> 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<std::pair<int, ConfigOptionInts>> 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<float, float> 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<float, float> 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<float, float> &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

View File

@ -14,19 +14,23 @@
#include "../Flow.hpp" #include "../Flow.hpp"
#include "../Config.hpp" #include "../Config.hpp"
#include "../Line.hpp" #include "../Line.hpp"
#include "../Exception.hpp"
#include "../PrintConfig.hpp"
#include <algorithm> #include <algorithm>
#include <cassert>
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
#include <iterator> #include <iterator>
#include <limits> #include <limits>
#include <numeric> #include <numeric>
#include <optional>
#include <ostream> #include <ostream>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <vector> #include <vector>
namespace Slic3r { namespace Slic3r { namespace ExtrusionProcessor {
struct ExtendedPoint struct ExtendedPoint
{ {
@ -54,19 +58,22 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
{ {
ExtendedPoint start_point{maybe_unscale(input_points.front())}; ExtendedPoint start_point{maybe_unscale(input_points.front())};
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>()); auto [distance, nearest_line,
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
start_point.distance = distance + boundary_offset; start_point.distance = distance + boundary_offset;
points.push_back(start_point); points.push_back(start_point);
} }
for (size_t i = 1; i < input_points.size(); i++) { for (size_t i = 1; i < input_points.size(); i++) {
ExtendedPoint next_point{maybe_unscale(input_points[i])}; ExtendedPoint next_point{maybe_unscale(input_points[i])};
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>()); auto [distance, nearest_line,
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
next_point.distance = distance + boundary_offset; next_point.distance = distance + boundary_offset;
if (ADD_INTERSECTIONS && if (ADD_INTERSECTIONS &&
((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) { ((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) {
const ExtendedPoint &prev_point = points.back(); const ExtendedPoint &prev_point = points.back();
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()}); auto intersections = unscaled_prev_layer.template intersections_with_line<true>(
L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
for (const auto &intersection : intersections) { for (const auto &intersection : intersections) {
ExtendedPoint p{}; ExtendedPoint p{};
p.position = intersection.first.template cast<double>(); p.position = intersection.first.template cast<double>();
@ -85,18 +92,19 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
const ExtendedPoint &curr = points[point_idx]; const ExtendedPoint &curr = points[point_idx];
const ExtendedPoint &next = points[point_idx + 1]; const ExtendedPoint &next = points[point_idx + 1];
if ((curr.distance > 0 && curr.distance < boundary_offset + 2.0f) || if ((curr.distance > -boundary_offset && curr.distance < boundary_offset + 2.0f) ||
(next.distance > 0 && next.distance < boundary_offset + 2.0f)) { (next.distance > -boundary_offset && next.distance < boundary_offset + 2.0f)) {
double line_len = (next.position - curr.position).norm(); double line_len = (next.position - curr.position).norm();
if (line_len > 4.0f) { if (line_len > 4.0f) {
double a0 = std::clamp((curr.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 + 2 * 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 t0 = std::min(a0, a1);
double t1 = std::max(a0, a1); double t1 = std::max(a0, a1);
if (t0 < 1.0) { if (t0 < 1.0) {
auto p0 = curr.position + t0 * (next.position - curr.position); 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<SIGNED_DISTANCE>(p0.cast<AABBScalar>()); auto [p0_dist, p0_near_l,
p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
ExtendedPoint new_p{}; ExtendedPoint new_p{};
new_p.position = p0; new_p.position = p0;
new_p.distance = float(p0_dist + boundary_offset); new_p.distance = float(p0_dist + boundary_offset);
@ -104,7 +112,8 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
} }
if (t1 > 0.0) { if (t1 > 0.0) {
auto p1 = curr.position + t1 * (next.position - curr.position); 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<SIGNED_DISTANCE>(p1.cast<AABBScalar>()); auto [p1_dist, p1_near_l,
p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
ExtendedPoint new_p{}; ExtendedPoint new_p{};
new_p.position = p1; new_p.position = p1;
new_p.distance = float(p1_dist + boundary_offset); new_p.distance = float(p1_dist + boundary_offset);
@ -114,7 +123,7 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
} }
new_points.push_back(next); new_points.push_back(next);
} }
points = new_points; points = std::move(new_points);
} }
if (max_line_length > 0) { if (max_line_length > 0) {
@ -140,7 +149,7 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
} }
new_points.push_back(points.back()); new_points.push_back(points.back());
} }
points = new_points; points = std::move(new_points);
} }
std::vector<float> angles_for_curvature(points.size()); std::vector<float> angles_for_curvature(points.size());
@ -212,144 +221,21 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
return points; return points;
} }
struct ProcessedPoint ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path,
{ const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
Point p; const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines);
float speed = 1.0f;
int fan_speed = 0;
};
class ExtrusionQualityEstimator ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(
{ const ExtrusionEntityCollection *ecc,
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> prev_layer_boundaries; const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> next_layer_boundaries; const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines);
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> prev_curled_extrusions;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> next_curled_extrusions;
const PrintObject *current_object;
public: std::pair<float, float> calculate_overhang_speed(const ExtrusionAttributes &attributes,
void set_current_object(const PrintObject *object) { current_object = object; } const FullPrintConfig &config,
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<Linef>{to_unscaled_linesf(layer->lslices)};
prev_curled_extrusions[object] = next_curled_extrusions[object];
next_curled_extrusions[object] = AABBTreeLines::LinesDistancer<CurledLine>{layer->curled_lines};
}
std::vector<ProcessedPoint> estimate_speed_from_extrusion_quality(
const ExtrusionPath &path,
const std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_w_speeds,
const std::vector<std::pair<int, ConfigOptionInts>> overhangs_w_fan_speeds,
size_t extruder_id, size_t extruder_id,
float ext_perimeter_speed, float external_perim_reference_speed,
float original_speed) float default_speed);
{
float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
std::map<float, float> 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<float, float> fan_speed_sections; }} // namespace Slic3r::ExtrusionProcessor
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<ExtendedPoint> extended_points =
estimate_points_properties<true, true, true, true>(path.polyline.points, prev_layer_boundaries[current_object], path.width);
std::vector<ProcessedPoint> 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<double>()))));
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<float, float> &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
#endif // slic3r_ExtrusionProcessor_hpp_ #endif // slic3r_ExtrusionProcessor_hpp_

View File

@ -4,8 +4,9 @@
#include "libslic3r/LocalesUtils.hpp" #include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/format.hpp" #include "libslic3r/format.hpp"
#include "libslic3r/I18N.hpp" #include "libslic3r/I18N.hpp"
#include "libslic3r/GCodeWriter.hpp" #include "libslic3r/GCode/GCodeWriter.hpp"
#include "libslic3r/I18N.hpp" #include "libslic3r/I18N.hpp"
#include "libslic3r/Geometry/ArcWelder.hpp"
#include "GCodeProcessor.hpp" #include "GCodeProcessor.hpp"
#include <boost/algorithm/string/case_conv.hpp> #include <boost/algorithm/string/case_conv.hpp>
@ -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] // taken from PrusaResearch.ini - [printer:Original Prusa i3 MK2.5 MMU2]
static const std::vector<std::string> DEFAULT_EXTRUDER_COLORS = { "#FF8000", "#DB5182", "#3EC0FF", "#FF4F4F", "#FBEB7D" }; static const std::vector<std::string> DEFAULT_EXTRUDER_COLORS = { "#FF8000", "#DB5182", "#3EC0FF", "#FF4F4F", "#FBEB7D" };
static const std::string INTERNAL_G2G3_TAG = "!#!#! internal only - from G2/G3 expansion !#!#!";
namespace Slic3r { namespace Slic3r {
const std::vector<std::string> GCodeProcessor::Reserved_Tags = { const std::vector<std::string> 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(); if (line.has_e()) g1_axes[E] = (double)line.e();
std::optional<double> g1_feedrate = std::nullopt; std::optional<double> g1_feedrate = std::nullopt;
if (line.has_f()) g1_feedrate = (double)line.f(); if (line.has_f()) g1_feedrate = (double)line.f();
std::optional<std::string> g1_cmt = std::nullopt; process_G1(g1_axes, g1_feedrate);
if (!line.comment().empty()) g1_cmt = line.comment();
process_G1(g1_axes, g1_feedrate, g1_cmt);
} }
void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes, std::optional<double> feedrate, std::optional<std::string> cmt) void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes, std::optional<double> feedrate, G1DiscretizationOrigin origin)
{ {
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back(); const float filament_diameter = (static_cast<size_t>(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 filament_radius = 0.5f * filament_diameter;
@ -2452,7 +2448,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
m_height = m_forced_height; m_height = m_forced_height;
else if (m_layer_id == 0) else if (m_layer_id == 0)
m_height = m_first_layer_height + m_z_offset; 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) 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; m_height = m_end_position[Z] - m_extruded_last_z;
} }
@ -2463,7 +2459,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
if (m_end_position[Z] == 0.0f || (m_extrusion_role == GCodeExtrusionRole::Custom && m_layer_id == 0)) if (m_end_position[Z] == 0.0f || (m_extrusion_role == GCodeExtrusionRole::Custom && m_layer_id == 0))
m_end_position[Z] = m_height; 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_extruded_last_z = m_end_position[Z];
m_options_z_corrector.update(m_height); m_options_z_corrector.update(m_height);
@ -2693,18 +2689,48 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
} }
// store move // 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) 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; return;
const float filament_diameter = (static_cast<size_t>(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<float>(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 // relative center
Vec3f rel_center = Vec3f::Zero(); Vec3f rel_center = Vec3f::Zero();
#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())) if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y()))
return; return;
}
// scale center, if needed // scale center, if needed
if (m_units == EUnits::Inches) 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.center = arc.start + rel_center.cast<double>(); arc.center = arc.start + rel_center.cast<double>();
const float filament_diameter = (static_cast<size_t>(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<float>(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 endpoint
arc.end = Vec3d(end_position[X], end_position[Y], end_position[Z]); 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 ??? // what to do ???
} }
assert(fitting != EFitting::R || std::abs(radius - arc.start_radius()) < EPSILON);
// updates feedrate from line // updates feedrate from line
std::optional<float> feedrate; std::optional<float> feedrate;
if (line.has_f()) if (line.has_f())
@ -2816,9 +2835,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
g1_feedrate = (double)*feedrate; g1_feedrate = (double)*feedrate;
if (extrusion.has_value()) if (extrusion.has_value())
g1_axes[E] = target[E]; g1_axes[E] = target[E];
std::optional<std::string> g1_cmt = INTERNAL_G2G3_TAG; process_G1(g1_axes, g1_feedrate, G1DiscretizationOrigin::G2G3);
process_G1(g1_axes, g1_feedrate, g1_cmt);
}; };
// calculate arc segments // 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 // https://github.com/prusa3d/Prusa-Firmware/blob/MK3/Firmware/motion_control.cpp
// segments count // segments count
#if 0
static const double MM_PER_ARC_SEGMENT = 1.0; static const double MM_PER_ARC_SEGMENT = 1.0;
const size_t segments = std::max<size_t>(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1); const size_t segments = std::max<size_t>(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 inv_segment = 1.0 / double(segments);
const double theta_per_segment = arc.angle * inv_segment; const double theta_per_segment = arc.angle * inv_segment;

View File

@ -56,9 +56,13 @@ namespace Slic3r {
time = 0.0f; time = 0.0f;
travel_time = 0.0f; travel_time = 0.0f;
custom_gcode_times.clear(); custom_gcode_times.clear();
custom_gcode_times.shrink_to_fit();
moves_times.clear(); moves_times.clear();
moves_times.shrink_to_fit();
roles_times.clear(); roles_times.clear();
roles_times.shrink_to_fit();
layers_times.clear(); layers_times.clear();
layers_times.shrink_to_fit();
} }
}; };
@ -76,6 +80,7 @@ namespace Slic3r {
m.reset(); m.reset();
} }
volumes_per_color_change.clear(); volumes_per_color_change.clear();
volumes_per_color_change.shrink_to_fit();
volumes_per_extruder.clear(); volumes_per_extruder.clear();
used_filaments_per_role.clear(); used_filaments_per_role.clear();
cost_per_extruder.clear(); cost_per_extruder.clear();
@ -680,8 +685,12 @@ namespace Slic3r {
// Move // Move
void process_G0(const GCodeReader::GCodeLine& line); void process_G0(const GCodeReader::GCodeLine& line);
void process_G1(const GCodeReader::GCodeLine& line); void process_G1(const GCodeReader::GCodeLine& line);
enum class G1DiscretizationOrigin {
G1,
G2G3,
};
void process_G1(const std::array<std::optional<double>, 4>& axes = { std::nullopt, std::nullopt, std::nullopt, std::nullopt }, void process_G1(const std::array<std::optional<double>, 4>& axes = { std::nullopt, std::nullopt, std::nullopt, std::nullopt },
std::optional<double> feedrate = std::nullopt, std::optional<std::string> cmt = std::nullopt); std::optional<double> feedrate = std::nullopt, G1DiscretizationOrigin origin = G1DiscretizationOrigin::G1);
// Arc Move // Arc Move
void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise); void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise);

View File

@ -1,10 +1,12 @@
#include "GCodeWriter.hpp" #include "GCodeWriter.hpp"
#include "CustomGCode.hpp" #include "../CustomGCode.hpp"
#include <algorithm> #include <algorithm>
#include <iomanip> #include <iomanip>
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <assert.h> #include <assert.h>
#include <string_view>
#ifdef __APPLE__ #ifdef __APPLE__
#include <boost/spirit/include/karma.hpp> #include <boost/spirit/include/karma.hpp>
@ -13,6 +15,8 @@
#define FLAVOR_IS(val) this->config.gcode_flavor == val #define FLAVOR_IS(val) this->config.gcode_flavor == val
#define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val #define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val
using namespace std::string_view_literals;
namespace Slic3r { namespace Slic3r {
// static // static
@ -90,17 +94,17 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish))) if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)))
return {}; return {};
std::string code, comment; std::string_view code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) { if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) {
code = "M109"; code = "M109"sv;
comment = "set temperature and wait for it to be reached"; comment = "set temperature and wait for it to be reached"sv;
} else { } else {
if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware
code = "G10"; code = "G10"sv;
} else { } else {
code = "M104"; code = "M104"sv;
} }
comment = "set temperature"; comment = "set temperature"sv;
} }
std::ostringstream gcode; 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) std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait)
{ {
if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached)) 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 = temperature;
m_last_bed_temperature_reached = wait; m_last_bed_temperature_reached = wait;
std::string code, comment; std::string_view code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup)) { if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
code = "M109"; code = "M109"sv;
} else { } 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 { } else {
code = "M140"; code = "M140"sv;
comment = "set bed temperature"; comment = "set bed temperature"sv;
} }
std::ostringstream gcode; 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 ; auto& last_value = separate_travel ? m_last_travel_acceleration : m_last_acceleration ;
if (acceleration == 0 || acceleration == last_value) if (acceleration == 0 || acceleration == last_value)
return std::string(); return {};
last_value = acceleration; last_value = acceleration;
@ -245,7 +249,7 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id)
return gcode.str(); 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 > 0.);
assert(F < 100000.); assert(F < 100000.);
@ -257,10 +261,9 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
return w.string(); 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.head<2>() = point.head<2>();
m_pos.y() = point.y();
GCodeG1Formatter w; GCodeG1Formatter w;
w.emit_xy(point); w.emit_xy(point);
@ -269,7 +272,40 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com
return w.string(); 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). // 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 // 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(); 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 /* 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 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); 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; m_pos.z() = z;
@ -348,10 +384,12 @@ bool GCodeWriter::will_move_z(double z) const
return true; 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(); assert(dE != 0);
m_pos.y() = point.y(); assert(std::abs(dE) < 1000.0);
m_pos.head<2>() = point.head<2>();
GCodeG1Formatter w; GCodeG1Formatter w;
w.emit_xy(point); w.emit_xy(point);
@ -360,8 +398,47 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
return w.string(); 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 #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_pos = point;
m_lifted = 0; 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 /* If firmware retraction is enabled, we use a fake value of 1
since we ignore the actual configured retract_length which since we ignore the actual configured retract_length which
might be 0, in which case the retraction logic gets skipped. */ might be 0, in which case the retraction logic gets skipped. */

View File

@ -1,13 +1,15 @@
#ifndef slic3r_GCodeWriter_hpp_ #ifndef slic3r_GCodeWriter_hpp_
#define 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 <string> #include <string>
#include <string_view>
#include <charconv> #include <charconv>
#include "Extruder.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "GCode/CoolingBuffer.hpp"
namespace Slic3r { namespace Slic3r {
@ -56,13 +58,17 @@ public:
// printed with the same extruder. // printed with the same extruder.
std::string toolchange_prefix() const; std::string toolchange_prefix() const;
std::string toolchange(unsigned int extruder_id); 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 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 &comment = std::string()); std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {});
std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string()); std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {});
std::string travel_to_z(double z, const std::string &comment = std::string()); 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; 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_xy(const Vec2d &point, double dE, const std::string_view comment = {});
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string()); 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(bool before_wipe = false);
std::string retract_for_toolchange(bool before_wipe = false); std::string retract_for_toolchange(bool before_wipe = false);
std::string unretract(); std::string unretract();
@ -113,8 +119,8 @@ private:
Print Print
}; };
std::string _travel_to_z(double z, 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 &comment); std::string _retract(double length, double restart_extra, const std::string_view comment);
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration); 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(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_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); }
static double quantize_e(double v) { return quantize(v, E_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); 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); 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()) { if (! axis.empty()) {
// not gcfNoExtrusion // not gcfNoExtrusion
this->emit_axis(axis[0], v, E_EXPORT_DIGITS); this->emit_axis(axis[0], v, E_EXPORT_DIGITS);
@ -181,12 +204,12 @@ public:
this->emit_axis('F', speed, XYZF_EXPORT_DIGITS); this->emit_axis('F', speed, XYZF_EXPORT_DIGITS);
} }
void emit_string(const std::string &s) { void emit_string(const std::string_view s) {
strncpy(ptr_err.ptr, s.c_str(), s.size()); strncpy(ptr_err.ptr, s.data(), s.size());
ptr_err.ptr += 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()) { if (allow_comments && ! comment.empty()) {
*ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' ';
this->emit_string(comment); this->emit_string(comment);
@ -210,14 +233,25 @@ public:
GCodeG1Formatter() { GCodeG1Formatter() {
this->buf[0] = 'G'; this->buf[0] = 'G';
this->buf[1] = '1'; this->buf[1] = '1';
this->buf_end = buf + buflen; this->ptr_err.ptr += 2;
this->ptr_err.ptr = this->buf + 2;
} }
GCodeG1Formatter(const GCodeG1Formatter&) = delete; GCodeG1Formatter(const GCodeG1Formatter&) = delete;
GCodeG1Formatter& operator=(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 */ } /* namespace Slic3r */
#endif /* slic3r_GCodeWriter_hpp_ */ #endif /* slic3r_GCodeWriter_hpp_ */

View File

@ -30,7 +30,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c
static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path) 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; BoundingBoxf bboxf;
if (! empty(bbox)) { if (! empty(bbox)) {
bboxf.min = unscale(bbox.min); bboxf.min = unscale(bbox.min);
@ -44,7 +44,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio
{ {
BoundingBox bbox; BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_loop.paths) 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; BoundingBoxf bboxf;
if (! empty(bbox)) { if (! empty(bbox)) {
bboxf.min = unscale(bbox.min); bboxf.min = unscale(bbox.min);
@ -58,7 +58,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext
{ {
BoundingBox bbox; BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths) 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; BoundingBoxf bboxf;
if (! empty(bbox)) { if (! empty(bbox)) {
bboxf.min = unscale(bbox.min); bboxf.min = unscale(bbox.min);

View File

@ -1484,7 +1484,7 @@ void SeamPlacer::init(const Print &print, std::function<void(void)> 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 { const Point &last_pos) const {
using namespace SeamPlacerImpl; using namespace SeamPlacerImpl;
const PrintObject *po = layer->object(); 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 //lastly, for internal perimeters, do the staggering if requested
if (po->config().staggered_inner_seams && loop.length() > 0.0) { if (po->config().staggered_inner_seams && loop.length() > 0.0) {
//fix depth, it is sometimes strongly underestimated //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) { while (depth > 0.0f) {
auto next_point = get_next_loop_point(projected_point); 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, return seam_point;
// thus empty path segments will not be produced by G-code export.
if (!loop.split_at_vertex(seam_point, scaled<double>(0.0015))) {
// The point is not in the original loop.
// Insert it.
loop.split_at(seam_point, true);
}
} }
} // namespace Slic3r } // namespace Slic3r

View File

@ -141,7 +141,7 @@ public:
void init(const Print &print, std::function<void(void)> throw_if_canceled_func); void init(const Print &print, std::function<void(void)> 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: private:
void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info);

View File

@ -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<double>(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<double>(*std::prev(it), *it);
if (length < 0)
return true;
}
}
return length < 0;
}
std::optional<Point> 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>();
double lsqr = v.squaredNorm();
if (lsqr > sqr(distance))
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
distance -= sqrt(lsqr);
} else {
// Circular segment
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), 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<Point>(prev_point.rotated(- angle * (distance / len),
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
}
distance -= len;
}
if (distance < 0)
return std::make_optional<Point>(point);
prev_point = point;
}
}
}
// Failed.
return {};
}
std::optional<Point> 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>();
double lsqr = v.squaredNorm();
if (lsqr > sqr(distance))
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
distance -= sqrt(lsqr);
}
else {
// Circular segment
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), 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<Point>(prev_point.rotated(-angle * (distance / len),
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
}
distance -= len;
}
if (distance < 0)
return std::make_optional<Point>(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 &params)
{
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 &params)
{
for (const ExtrusionPath &path : multi_path.paths)
this->interpolate_add(path, params);
}
void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters &params)
{
for (const ExtrusionPath &path : loop.paths)
this->interpolate_add(path, params);
}
void SmoothPathCache::interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters &params)
{
for (const ExtrusionEntity *ee : eec) {
if (ee->is_collection())
this->interpolate_add(*static_cast<const ExtrusionEntityCollection*>(ee), params);
else if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(ee); path)
this->interpolate_add(*path, params);
else if (const ExtrusionMultiPath *multi_path = dynamic_cast<const ExtrusionMultiPath*>(ee); multi_path)
this->interpolate_add(*multi_path, params);
else if (const ExtrusionLoop *loop = dynamic_cast<const ExtrusionLoop*>(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<Geometry::ArcWelder::Path, Geometry::ArcWelder::Path> 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

View File

@ -0,0 +1,87 @@
#ifndef slic3r_GCode_SmoothPath_hpp_
#define slic3r_GCode_SmoothPath_hpp_
#include <ankerl/unordered_dense.h>
#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<SmoothPathElement>;
// 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<Point> sample_path_point_at_distance_from_start(const SmoothPath &path, double distance);
std::optional<Point> 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 &params);
void interpolate_add(const ExtrusionMultiPath &ee, const InterpolationParameters &params);
void interpolate_add(const ExtrusionLoop &ee, const InterpolationParameters &params);
void interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters &params);
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<const Polyline*, Geometry::ArcWelder::Path> 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_

View File

@ -0,0 +1,252 @@
#include "Wipe.hpp"
#include "../GCode.hpp"
#include <string_view>
#include <Eigen/Geometry>
using namespace std::string_view_literals;
namespace Slic3r::GCode {
void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &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<double>(), p_quantized.cast<double>(), double(radius), ccw);
float angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast<double>(), p_quantized.cast<double>(), 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<double>(), p.cast<double>(), double(radius), ccw);
angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast<double>(), p.cast<double>(), 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<double>(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<Point> 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<double>().norm();
if (l > 0) {
// Not yet.
std::optional<Point> 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<Point> 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<double>().normalized();
return std::make_optional<Point>(p_current + (v_rotated * wipe_length).cast<coord_t>());
}
return {};
}
} // namespace Slic3r::GCode

View File

@ -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 <cassert>
#include <optional>
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<unsigned int> &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<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length);
} // namespace GCode
} // namespace Slic3r
#endif // slic3r_GCode_Wipe_hpp_

View File

@ -1,5 +1,5 @@
#ifndef WipeTower_ #ifndef slic3r_GCode_WipeTower_hpp_
#define WipeTower_ #define slic3r_GCode_WipeTower_hpp_
#include <cmath> #include <cmath>
#include <string> #include <string>
@ -411,4 +411,4 @@ private:
} // namespace Slic3r } // namespace Slic3r
#endif // WipeTowerPrusaMM_hpp_ #endif // slic3r_GCode_WipeTower_hpp_

View File

@ -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<double>());
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<float>();
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<float>();
// If the extruder offset changed, add an extra move so everything is continuous
if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) {
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

View File

@ -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<WipeTower::ToolChangeResult> &priming,
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
const WipeTower::ToolChangeResult &final_purge) :
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
m_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<float> 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<Vec2d> m_extruder_offsets;
// Reference to cached values at the Printer class.
const std::vector<WipeTower::ToolChangeResult> &m_priming;
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
const WipeTower::ToolChangeResult &m_final_purge;
// Current layer index.
int m_layer_idx;
int m_tool_change_idx;
double m_last_wipe_tower_print_z = 0.f;
};
} // namespace GCode
} // namespace Slic3r
#endif // slic3r_GCode_WipeTowerIntegration_hpp_

View File

@ -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 <numeric>
#include <random>
#include <boost/log/trivial.hpp>
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<double>(), p2.cast<double>(), radius, ccw);
double angle = arc_angle(p1.cast<double>(), p2.cast<double>(), 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<coord_t>()));
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<Circle> 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<double>(), p2.cast<double>(), p3.cast<double>(), SCALED_EPSILON); center) {
Point c = center->cast<coord_t>();
if (double r = sqrt(double((c - p1).cast<int64_t>().squaredNorm())); r <= max_radius)
return std::make_optional<Circle>({ 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>();
int64_t l2 = v21.squaredNorm();
if (l2 > int64_t(SCALED_EPSILON)) {
if (int64_t t = (pt - p1).cast<int64_t>().dot(v21);
t >= int64_t(SCALED_EPSILON) && t < l2 - int64_t(SCALED_EPSILON)) {
out = p1 + ((double(t) / double(l2)) * v21.cast<double>()).cast<coord_t>();
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<double>().norm() - circle.radius) < SCALED_EPSILON);
assert(std::abs((*std::prev(end) - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
assert(end - begin >= 3);
// Test the 1st point.
if (double distance_from_center = (*begin - circle.center).cast<double>().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<double>().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<double>().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<double>().norm() - circle.radius) < SCALED_EPSILON);
assert(std::abs((*std::prev(end) - circle.center).cast<double>().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<double>().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<double>().norm() - circle.radius); deviation2 > tolerance2)
return false;
else
total_deviation += deviation2;
}
}
return true;
}
static std::optional<Circle> try_create_circle(const Points::const_iterator begin, const Points::const_iterator end, const double max_radius, const double tolerance)
{
std::optional<Circle> 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> 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<double>::max();
double current_deviation;
for (auto it = std::next(begin); std::next(it) != end; ++ it)
if (std::optional<Circle> 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 &center,
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<int64_t>();
Vec2i64 vprev = vstart;
int arc_dir = 0;
for (auto it = std::next(begin); it != end; ++ it) {
Vec2i64 v = (*it - center).cast<int64_t>();
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<Arc> 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<int64_t>();
Vec2i64 vend = (*std::prev(end) - circle.center).cast<int64_t>();
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>(Arc{
*begin,
*std::prev(end),
circle.center,
angle > M_PI ? - circle.radius : circle.radius,
orientation
});
}
}
static inline std::optional<Arc> 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> 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 &center_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 &center_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) range in place,
// returns the new end iterator.
static inline Segments::iterator douglas_peucker_in_place(Segments::iterator begin, Segments::iterator end, const double tolerance)
{
return douglas_peucker<int64_t>(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> arc;
while (end != src.end()) {
auto next_end = std::next(end);
if (std::optional<Arc> 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<double>(), seg_end.point.cast<double>(), double(seg_end.radius), seg_end.ccw());
assert(seg_start.point == *begin);
assert(seg_end.point == *std::prev(end));
assert(arc_orientation(center.cast<coord_t>(), 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<coord_t>(), closest_point)) {
double distance_from_center = (closest_point.cast<double>() - center).norm();
assert(std::abs(distance_from_center - std::abs(seg_end.radius)) < tolerance + SCALED_EPSILON);
}
Vec2d v = (ptend - ptstart).cast<double>();
double len = v.norm();
auto num_segments = std::min<size_t>(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<coord_t>();
assert(i == 0 || inside_arc_wedge(seg_start.point.cast<double>(), seg_end.point.cast<double>(), center, seg_end.radius > 0, seg_end.ccw(), p.cast<double>()));
double d2 = sqr((p.cast<double>() - 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>();
double len = v.norm();
auto num_segments = std::min<size_t>(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<coord_t>();
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>();
double lsqr = v.squaredNorm();
if (lsqr > sqr(distance)) {
path.push_back({ last.point + (v * (distance / sqrt(lsqr))).cast<coord_t>() });
// Length to go is zero.
return 0;
}
distance -= sqrt(lsqr);
} else {
// Circular segment
double angle = arc_angle(path.back().point.cast<double>(), last.point.cast<double>(), 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<double>(), last.point.cast<double>(), double(last.radius), last.ccw()).cast<coord_t>()),
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<double>().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<double>(), it->point.cast<double>(), double(it->radius), it->ccw()).cast<int64_t>();
// Test whether point is inside the wedge.
Vec2i64 v1 = prev.cast<int64_t>() - center;
Vec2i64 v2 = it->point.cast<int64_t>() - center;
Vec2i64 vp = point.cast<int64_t>() - 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<coord_t>() + (vp.cast<double>() * (r / rtest)).cast<coord_t>();
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<coord_t>();
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<double>().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<Path, Path> 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<Path, Path> 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<int64_t>().squaredNorm(); d2 < sqr(min_segment_length)) {
split_segment = false;
int64_t d22 = (proj.point - end.point).cast<int64_t>().squaredNorm();
if (d22 < d2)
// Split at the end of the segment.
++ split_segment_id;
} else if (int64_t d2 = (proj.point - end.point).cast<int64_t>().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<int64_t>();
auto vend = (end.point - proj.center).cast<int64_t>();
auto vproj = (proj.point - proj.center).cast<int64_t>();
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<Path, Path> 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

View File

@ -0,0 +1,324 @@
#ifndef slic3r_Geometry_ArcWelder_hpp_
#define slic3r_Geometry_ArcWelder_hpp_
#include <optional>
#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<typename Derived, typename Derived2, typename Float>
inline Eigen::Matrix<Float, 2, 1, Eigen::DontAlign> arc_center(
const Eigen::MatrixBase<Derived> &start_pos,
const Eigen::MatrixBase<Derived2> &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<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_center(): Both vectors must be of the same type.");
static_assert(std::is_same<typename Derived::Scalar, Float>::value, "arc_center(): Radius must be of the same type as the vectors.");
assert(radius != 0);
using Vector = Eigen::Matrix<Float, 2, 1, Eigen::DontAlign>;
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<typename Derived, typename Derived2>
inline typename Derived::Scalar arc_angle(
const Eigen::MatrixBase<Derived> &start_pos,
const Eigen::MatrixBase<Derived2> &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<typename Derived::Scalar, typename Derived2::Scalar>::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<typename Derived, typename Derived2>
inline typename Derived::Scalar arc_length(
const Eigen::MatrixBase<Derived> &start_pos,
const Eigen::MatrixBase<Derived2> &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<typename Derived::Scalar, typename Derived2::Scalar>::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<typename Derived, typename Derived2, typename Derived3>
inline typename Derived::Scalar arc_length(
const Eigen::MatrixBase<Derived> &start_pos,
const Eigen::MatrixBase<Derived2> &end_pos,
const Eigen::MatrixBase<Derived3> &center_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<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::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<typename Derived, typename Derived2, typename Derived3>
inline bool inside_arc_wedge_vectors(
const Eigen::MatrixBase<Derived> &start_vec,
const Eigen::MatrixBase<Derived2> &end_vec,
const bool shorter_arc,
const bool ccw,
const Eigen::MatrixBase<Derived3> &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<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::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<typename Derived, typename Derived2, typename Derived3, typename Derived4>
inline bool inside_arc_wedge(
const Eigen::MatrixBase<Derived> &start_pt,
const Eigen::MatrixBase<Derived2> &end_pt,
const Eigen::MatrixBase<Derived3> &center_pt,
const bool shorter_arc,
const bool ccw,
const Eigen::MatrixBase<Derived4> &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<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived4::Scalar>::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<typename Derived, typename Derived2, typename Derived3, typename Float>
inline bool inside_arc_wedge(
const Eigen::MatrixBase<Derived> &start_pt,
const Eigen::MatrixBase<Derived2> &end_pt,
const Float radius,
const bool ccw,
const Eigen::MatrixBase<Derived3> &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<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value &&
std::is_same<typename Derived::Scalar, Float>::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<typename FloatType>
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<double>(600.);
// 0.05mm
static constexpr const double default_scaled_resolution = scaled<double>(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 &center,
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<Segment>;
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<typename FloatType>
inline FloatType segment_length(const Segment &start, const Segment &end)
{
return end.linear() ?
(end.point - start.point).cast<FloatType>().norm() :
arc_length(start.point.cast<FloatType>(), end.point.cast<FloatType>(), FloatType(end.radius));
}
template<typename FloatType>
inline FloatType path_length(const Path &path)
{
FloatType len = 0;
for (size_t i = 1; i < path.size(); ++ i)
len += segment_length<FloatType>(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<size_t>::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<double>::max() };
bool valid() const { return this->segment_id != std::numeric_limits<size_t>::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<double>::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<Path, Path> 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<Path, Path> split_at(const Path &path, const Point &point, const double min_segment_length);
} } } // namespace Slic3r::Geometry::ArcWelder
#endif // slic3r_Geometry_ArcWelder_hpp_

View File

@ -17,9 +17,14 @@ Point circle_center_taubin_newton(const Points::const_iterator& input_begin, con
return Point::new_scale(center.x(), center.y()); 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 // Robust and accurate algebraic circle fit, which works well even if data points are observed only within a small arc.
/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end // The method was proposed by G. Taubin in
/// lie on. // "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) 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 // calculate the centroid of the data set

View File

@ -9,10 +9,17 @@ namespace Slic3r { namespace Geometry {
// https://en.wikipedia.org/wiki/Circumscribed_circle // https://en.wikipedia.org/wiki/Circumscribed_circle
// Circumcenter coordinates, Cartesian coordinates // Circumcenter coordinates, Cartesian coordinates
template<typename Vector> // In case the three points are collinear, returns their centroid.
Vector circle_center(const Vector &a, const Vector &bsrc, const Vector &csrc, typename Vector::Scalar epsilon) template<typename Derived, typename Derived2, typename Derived3>
Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> 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<typename Derived::Scalar, typename Derived2::Scalar>::value && std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value,
"circle_center(): All three points must be of the same type.");
using Scalar = typename Derived::Scalar;
using Vector = Eigen::Matrix<Scalar, 2, 1, Eigen::DontAlign>;
Vector b = bsrc - a; Vector b = bsrc - a;
Vector c = csrc - a; Vector c = csrc - a;
Scalar lb = b.squaredNorm(); 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<typename Derived, typename Derived2, typename Derived3>
std::optional<Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign>> 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<typename Derived::Scalar, typename Derived2::Scalar>::value && std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value,
"try_circle_center(): All three points must be of the same type.");
using Scalar = typename Derived::Scalar;
using Vector = Eigen::Matrix<Scalar, 2, 1, Eigen::DontAlign>;
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<Vector>(a + Vector(- v.y(), v.x()) / (2 * d));
}
}
// 2D circle defined by its center and squared radius // 2D circle defined by its center and squared radius
template<typename Vector> template<typename Vector>
struct CircleSq { struct CircleSq {
@ -65,7 +98,7 @@ struct Circle {
Vector center; Vector center;
Scalar radius; Scalar radius;
Circle() {} Circle() = default;
Circle(const Vector &center, const Scalar radius) : center(center), radius(radius) {} Circle(const Vector &center, 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) : 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); } Circle(const Vector &a, const Vector &b, const Vector &c, const Scalar epsilon) { *this = CircleSq(a, b, c, epsilon); }

View File

@ -27,7 +27,7 @@
#include "SVG.hpp" #include "SVG.hpp"
#include <Eigen/Dense> #include <Eigen/Dense>
#include "GCodeWriter.hpp" #include "GCode/GCodeWriter.hpp"
namespace Slic3r { namespace Slic3r {

View File

@ -103,100 +103,6 @@ bool MultiPoint::remove_duplicate_points()
return false; 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<size_t> 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<int64_t>();
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<int64_t>().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<double>();
for (size_t i = anchor_idx + 1; i < floater_idx; ++ i) {
const Point p = pts[i];
const Vec2i64 va = (p - a).template cast<int64_t>();
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<int64_t>().squaredNorm();
} else {
const Vec2i64 w = ((double(t) / dl2) * dv).cast<int64_t>();
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 // Visivalingam simplification algorithm https://github.com/slic3r/Slic3r/pull/3825
// thanks to @fuchstraumer // thanks to @fuchstraumer
/* /*
@ -219,7 +125,7 @@ struct vis_node{
// other node if it's area is less than the other node's area // 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); } 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. // Make sure there's enough points in "pts" to bother with simplification.
assert(pts.size() >= 2); assert(pts.size() >= 2);

View File

@ -12,6 +12,123 @@ namespace Slic3r {
class BoundingBox; class BoundingBox;
class BoundingBox3; class BoundingBox3;
// Reduces polyline in the <begin, end) range, outputs into the output iterator.
// Output iterator may be equal to input iterator as long as the iterator value type move operator supports move at the same input / output address.
template<typename SquareLengthType, typename InputIterator, typename OutputIterator, typename PointGetter>
inline OutputIterator douglas_peucker(InputIterator begin, InputIterator end, OutputIterator out, const double tolerance, PointGetter point_getter)
{
using InputIteratorCategory = typename std::iterator_traits<InputIterator>::iterator_category;
static_assert(std::is_base_of_v<std::input_iterator_tag, InputIteratorCategory>);
using Vector = Eigen::Matrix<SquareLengthType, 2, 1, Eigen::DontAlign>;
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<InputIterator> dpStack;
if constexpr (std::is_base_of_v<std::random_access_iterator_tag, InputIteratorCategory>)
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<SquareLengthType>();
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<SquareLengthType>().squaredNorm();
dist_sq > max_dist_sq) {
max_dist_sq = dist_sq;
furthest = it;
}
} else {
// Find Find the furthest point from the line <anchor, floater>.
const double dl2 = double(l2);
const Vec2d dv = v.template cast<double>();
for (auto it = std::next(anchor); it != floater; ++ it) {
const auto p = point_getter(*it);
const Vector va = (p - a).template cast<SquareLengthType>();
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<SquareLengthType>().squaredNorm();
} else if (double dt = double(t) / dl2; dt <= 0) {
dist_sq = va.squaredNorm();
} else if (dt >= 1.) {
dist_sq = (p - f).template cast<SquareLengthType>().squaredNorm();
} else {
const Vector w = (dt * dv).cast<SquareLengthType>();
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 <anchor, floater> 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 <anchor, floater>.
// Divide recursively.
floater = furthest;
f = point_getter(*floater);
dpStack.emplace_back(floater);
}
}
}
}
return out;
}
// Reduces polyline in the <begin, end) range, outputs into the output iterator.
// Output iterator may be equal to input iterator as long as the iterator value type move operator supports move at the same input / output address.
template<typename OutputIterator>
inline OutputIterator douglas_peucker(Points::const_iterator begin, Points::const_iterator end, OutputIterator out, const double tolerance)
{
return douglas_peucker<int64_t>(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 class MultiPoint
{ {
public: public:
@ -81,8 +198,8 @@ public:
} }
} }
static Points douglas_peucker(const Points &points, const double tolerance); static Points douglas_peucker(const Points &src, const double tolerance) { return Slic3r::douglas_peucker(src, tolerance); }
static Points visivalingam(const Points& pts, const double& tolerance); static Points visivalingam(const Points &src, const double tolerance);
inline auto begin() { return points.begin(); } inline auto begin() { return points.begin(); }
inline auto begin() const { 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 Points &points, double angle);
extern BoundingBox get_extents_rotated(const MultiPoint &mp, 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; double total = 0;
if (! pts.empty()) { if (begin != end) {
auto it = pts.begin(); auto it = begin;
for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev) for (auto it_prev = it ++; it != end; ++ it, ++ it_prev)
total += (*it - *it_prev).cast<double>().norm(); total += (*it - *it_prev).cast<double>().norm();
} }
return total; return total;
} }
inline double length(const Points &pts) {
return length(pts.begin(), pts.end());
}
inline double area(const Points &polygon) { inline double area(const Points &polygon) {
double area = 0.; double area = 0.;
for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++) for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++)

View File

@ -119,20 +119,18 @@ ExtrusionMultiPath PerimeterGenerator::thick_polyline_to_multi_path(const ThickP
const double w = fmax(line.a_width, line.b_width); const double w = fmax(line.a_width, line.b_width);
const Flow new_flow = (role.is_bridge() && flow.bridge()) ? flow : flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI)); const Flow new_flow = (role.is_bridge() && flow.bridge()) ? flow : flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
if (path.polyline.points.empty()) { if (path.empty()) {
path.polyline.append(line.a);
path.polyline.append(line.b);
// Convert from spacing to extrusion width based on the extrusion model // Convert from spacing to extrusion width based on the extrusion model
// of a square extrusion ended with semi circles. // 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 #ifdef SLIC3R_DEBUG
printf(" filling %f gap\n", flow.width); printf(" filling %f gap\n", flow.width);
#endif #endif
path.mm3_per_mm = new_flow.mm3_per_mm();
path.width = new_flow.width();
path.height = new_flow.height();
} else { } else {
assert(path.width >= EPSILON); assert(path.width() >= EPSILON);
thickness_delta = scaled<double>(fabs(path.width - new_flow.width())); thickness_delta = scaled<double>(fabs(path.width() - new_flow.width()));
if (thickness_delta <= merge_tolerance) { if (thickness_delta <= merge_tolerance) {
// the width difference between this line and the current flow // the width difference between this line and the current flow
// (of the previous line) width is within the accepted tolerance // (of the previous line) width is within the accepted tolerance
@ -325,10 +323,12 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
extrusion_paths_append( extrusion_paths_append(
paths, paths,
intersection_pl({ polygon }, lower_slices_polygons_clipped), intersection_pl({ polygon }, lower_slices_polygons_clipped),
ExtrusionAttributes{
role_normal, role_normal,
is_external ? params.ext_mm3_per_mm : params.mm3_per_mm, ExtrusionFlow{ is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(), is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(),
float(params.layer_height)); float(params.layer_height)
} });
// get overhang paths by checking what parts of this loop fall // get overhang paths by checking what parts of this loop fall
// outside the grown lower slices (thus where the distance between // outside the grown lower slices (thus where the distance between
@ -336,21 +336,24 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
extrusion_paths_append( extrusion_paths_append(
paths, paths,
diff_pl({ polygon }, lower_slices_polygons_clipped), diff_pl({ polygon }, lower_slices_polygons_clipped),
ExtrusionAttributes{
role_overhang, role_overhang,
params.mm3_per_mm_overhang, ExtrusionFlow{ params.mm3_per_mm_overhang, params.overhang_flow.width(), params.overhang_flow.height() }
params.overhang_flow.width(), });
params.overhang_flow.height());
// Reapply the nearest point search for starting point. // Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point()); chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
} else { } else {
ExtrusionPath path(role_normal); paths.emplace_back(polygon.split_at_first_point(),
path.polyline = polygon.split_at_first_point(); ExtrusionAttributes{
path.mm3_per_mm = is_external ? params.ext_mm3_per_mm : params.mm3_per_mm; role_normal,
path.width = is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(); ExtrusionFlow{
path.height = float(params.layer_height); is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
paths.push_back(path); is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(),
float(params.layer_height)
}
});
} }
coll.append(ExtrusionLoop(std::move(paths), loop_role)); coll.append(ExtrusionLoop(std::move(paths), loop_role));
@ -383,11 +386,13 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]); ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]);
coll.entities[idx.first] = nullptr; coll.entities[idx.first] = nullptr;
if (loop.is_contour) { if (loop.is_contour) {
eloop->make_counter_clockwise(); if (eloop->is_clockwise())
eloop->reverse_loop();
out.append(std::move(children.entities)); out.append(std::move(children.entities));
out.entities.emplace_back(eloop); out.entities.emplace_back(eloop);
} else { } else {
eloop->make_clockwise(); if (eloop->is_counter_clockwise())
eloop->reverse_loop();
out.entities.emplace_back(eloop); out.entities.emplace_back(eloop);
out.append(std::move(children.entities)); out.append(std::move(children.entities));
} }
@ -603,10 +608,8 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
if (extrusion->is_closed) { if (extrusion->is_closed) {
ExtrusionLoop extrusion_loop(std::move(paths)); ExtrusionLoop extrusion_loop(std::move(paths));
// Restore the orientation of the extrusion loop. // Restore the orientation of the extrusion loop.
if (pg_extrusion.is_contour) if (pg_extrusion.is_contour == extrusion_loop.is_clockwise())
extrusion_loop.make_counter_clockwise(); extrusion_loop.reverse_loop();
else
extrusion_loop.make_clockwise();
for (auto it = std::next(extrusion_loop.paths.begin()); it != extrusion_loop.paths.end(); ++it) { for (auto it = std::next(extrusion_loop.paths.begin()); it != extrusion_loop.paths.end(); ++it) {
assert(it->polyline.points.size() >= 2); assert(it->polyline.points.size() >= 2);
@ -960,11 +963,9 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
if (perimeter_polygon.empty()) { // fill possible gaps of single extrusion width 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); 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()), extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(), ExtrusionAttributes{ ExtrusionRole::OverhangPerimeter, overhang_flow });
overhang_flow.height());
}
Polylines fills; Polylines fills;
ExPolygons gap = shrinked.empty() ? offset_ex(prev, overhang_flow.scaled_spacing() * 0.5) : to_expolygons(shrinked); ExPolygons gap = shrinked.empty() ? offset_ex(prev, overhang_flow.scaled_spacing() * 0.5) : to_expolygons(shrinked);
@ -975,14 +976,12 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
if (!fills.empty()) { if (!fills.empty()) {
fills = intersection_pl(fills, shrinked_overhang_to_cover); fills = intersection_pl(fills, shrinked_overhang_to_cover);
extrusion_paths_append(overhang_region, reconnect_polylines(fills, overhang_flow.scaled_spacing()), extrusion_paths_append(overhang_region, reconnect_polylines(fills, overhang_flow.scaled_spacing()),
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(), ExtrusionAttributes{ ExtrusionRole::OverhangPerimeter, overhang_flow });
overhang_flow.height());
} }
break; break;
} else { } else {
extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()), extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(), ExtrusionAttributes{ExtrusionRole::OverhangPerimeter, overhang_flow });
overhang_flow.height());
} }
if (intersection(perimeter_polygon, real_overhang).empty()) { continuation_loops--; } if (intersection(perimeter_polygon, real_overhang).empty()) { continuation_loops--; }

View File

@ -47,14 +47,12 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t)
void Point::rotate(double angle, const Point &center) void Point::rotate(double angle, const Point &center)
{ {
double cur_x = (double)(*this)(0); Vec2d cur = this->cast<double>();
double cur_y = (double)(*this)(1);
double s = ::sin(angle); double s = ::sin(angle);
double c = ::cos(angle); double c = ::cos(angle);
double dx = cur_x - (double)center(0); auto d = cur - center.cast<double>();
double dy = cur_y - (double)center(1); this->x() = fast_round_up<coord_t>(center.x() + c * d.x() - s * d.y());
(*this)(0) = (coord_t)round( (double)center(0) + c * dx - s * dy ); this->y() = fast_round_up<coord_t>(center.y() + s * d.x() + c * d.y());
(*this)(1) = (coord_t)round( (double)center(1) + c * dy + s * dx );
} }
bool has_duplicate_points(Points &&pts) bool has_duplicate_points(Points &&pts)

View File

@ -166,11 +166,12 @@ public:
Point(const Point &rhs) { *this = rhs; } Point(const Point &rhs) { *this = rhs; }
explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {} 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 allows you to construct Point from Eigen expressions
// This constructor has to be implicit (non-explicit) to allow implicit conversion from Eigen expressions.
template<typename OtherDerived> template<typename OtherDerived>
Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {} Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } 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()))); } template<typename OtherDerived>
static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } static Point new_scale(const Eigen::MatrixBase<OtherDerived> &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
// This method allows you to assign Eigen expressions to MyVectorType // This method allows you to assign Eigen expressions to MyVectorType
template<typename OtherDerived> template<typename OtherDerived>

View File

@ -146,8 +146,10 @@ void Polyline::split_at(const Point &point, Polyline* p1, Polyline* p2) const
} }
if (this->points.front() == point) { if (this->points.front() == point) {
//FIXME why is p1 NOT empty as in the case above?
*p1 = { point }; *p1 = { point };
*p2 = *this; *p2 = *this;
return;
} }
auto min_dist2 = std::numeric_limits<double>::max(); auto min_dist2 = std::numeric_limits<double>::max();

View File

@ -456,7 +456,8 @@ static std::vector<std::string> s_Preset_print_options {
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", "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", "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", "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", "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", "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", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",

View File

@ -921,8 +921,10 @@ void Print::process()
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_objects.size(), 1), [this](const tbb::blocked_range<size_t> &range) { tbb::parallel_for(tbb::blocked_range<size_t>(0, m_objects.size(), 1), [this](const tbb::blocked_range<size_t> &range) {
for (size_t idx = range.begin(); idx < range.end(); ++idx) { for (size_t idx = range.begin(); idx < range.end(); ++idx) {
m_objects[idx]->generate_support_material(); PrintObject &obj = *m_objects[idx];
m_objects[idx]->estimate_curled_extrusions(); obj.generate_support_material();
obj.estimate_curled_extrusions();
obj.calculate_overhanging_perimeters();
} }
}, tbb::simple_partitioner()); }, tbb::simple_partitioner());
@ -1009,7 +1011,7 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
this->set_status(90, message); this->set_status(90, message);
// Create GCode on heap, it has quite a lot of data. // Create GCode on heap, it has quite a lot of data.
std::unique_ptr<GCode> gcode(new GCode); std::unique_ptr<GCodeGenerator> gcode(new GCodeGenerator);
gcode->do_export(this, path.c_str(), result, thumbnail_cb); gcode->do_export(this, path.c_str(), result, thumbnail_cb);
if (m_conflict_result.has_value()) if (m_conflict_result.has_value())
@ -1125,13 +1127,15 @@ void Print::_make_skirt()
} }
// Extrude the skirt loop. // Extrude the skirt loop.
ExtrusionLoop eloop(elrSkirt); ExtrusionLoop eloop(elrSkirt);
eloop.paths.emplace_back(ExtrusionPath( eloop.paths.emplace_back(
ExtrusionPath( ExtrusionAttributes{
ExtrusionRole::Skirt, ExtrusionRole::Skirt,
(float)mm3_per_mm, // 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(), flow.width(),
(float)first_layer_height // this will be overridden at G-code export time float(first_layer_height) // this will be overridden at G-code export time
))); }
});
eloop.paths.back().polyline = loop.split_at_first_point(); eloop.paths.back().polyline = loop.split_at_first_point();
m_skirt.append(eloop); m_skirt.append(eloop);
if (m_config.min_skirt_length.value > 0) { 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); ExtrusionPath path({ minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner },
path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner }; ExtrusionAttributes{ ExtrusionRole::WipeTower, ExtrusionFlow{ 0.0, 0.0, lh } });
paths.push_back({ path }); 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. // We added the border, now add several parallel lines so we can detect an object that is fully inside the tower.

View File

@ -29,7 +29,7 @@
namespace Slic3r { namespace Slic3r {
class GCode; class GCodeGenerator;
class Layer; class Layer;
class ModelObject; class ModelObject;
class Print; class Print;
@ -67,7 +67,7 @@ enum PrintStep : unsigned int {
enum PrintObjectStep : unsigned int { enum PrintObjectStep : unsigned int {
posSlice, posPerimeters, posPrepareInfill, 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 // A PrintRegion object represents a group of volumes to print
@ -376,6 +376,7 @@ private:
void generate_support_spots(); void generate_support_spots();
void generate_support_material(); void generate_support_material();
void estimate_curled_extrusions(); void estimate_curled_extrusions();
void calculate_overhanging_perimeters();
void slice_volumes(); void slice_volumes();
// Has any support (not counting the raft). // Has any support (not counting the raft).
@ -697,7 +698,7 @@ private:
Polygons m_sequential_print_clearance_contours; Polygons m_sequential_print_clearance_contours;
// To allow GCode to set the Print's GCodeExport step status. // To allow GCode to set the Print's GCodeExport step status.
friend class GCode; friend class GCodeGenerator;
// To allow GCodeProcessor to emit warnings. // To allow GCodeProcessor to emit warnings.
friend class GCodeProcessor; friend class GCodeProcessor;
// Allow PrintObject to access m_mutex and m_cancel_callback. // Allow PrintObject to access m_mutex and m_cancel_callback.

View File

@ -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<NAME>::get_enum_values() { return s_keys_map_##NAME; } \ template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values() { return s_keys_map_##NAME; } \
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names() { return s_keys_names_##NAME; } template<> const t_config_enum_names& ConfigOptionEnum<NAME>::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 { static t_config_enum_values s_keys_map_PrinterTechnology {
{ "FFF", ptFFF }, { "FFF", ptFFF },
{ "SLA", ptSLA } { "SLA", ptSLA }
@ -397,6 +404,27 @@ void PrintConfigDef::init_fff_params()
{ {
ConfigOptionDef* def; 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<ArcFittingType>({
{ "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>(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. // Maximum extruder temperature, bumped to 1500 to support printing of glass.
const int max_temp = 1500; const int max_temp = 1500;
def = this->add("avoid_crossing_curled_overhangs", coBool); def = this->add("avoid_crossing_curled_overhangs", coBool);

View File

@ -30,6 +30,12 @@
namespace Slic3r { namespace Slic3r {
enum class ArcFittingType {
Disabled,
EmitCenter,
EmitRadius,
};
enum GCodeFlavor : unsigned char { enum GCodeFlavor : unsigned char {
gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinLegacy, gcfMarlinFirmware, gcfKlipper, gcfSailfish, gcfMach3, gcfMachinekit, gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinLegacy, gcfMarlinFirmware, gcfKlipper, gcfSailfish, gcfMach3, gcfMachinekit,
gcfSmoothie, gcfNoExtrusion, gcfSmoothie, gcfNoExtrusion,
@ -141,6 +147,7 @@ enum class GCodeThumbnailsFormat {
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names(); \ template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names(); \
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values(); template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values();
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ArcFittingType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(MachineLimitsUsage) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(MachineLimitsUsage)
@ -671,6 +678,8 @@ PRINT_CONFIG_CLASS_DEFINE(
PRINT_CONFIG_CLASS_DEFINE( PRINT_CONFIG_CLASS_DEFINE(
GCodeConfig, GCodeConfig,
((ConfigOptionEnum<ArcFittingType>, arc_fitting))
((ConfigOptionFloatOrPercent, arc_fitting_tolerance))
((ConfigOptionBool, autoemit_temperature_commands)) ((ConfigOptionBool, autoemit_temperature_commands))
((ConfigOptionString, before_layer_gcode)) ((ConfigOptionString, before_layer_gcode))
((ConfigOptionString, between_objects_gcode)) ((ConfigOptionString, between_objects_gcode))

View File

@ -3,7 +3,9 @@
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include "Exception.hpp" #include "Exception.hpp"
#include "Flow.hpp" #include "Flow.hpp"
#include "GCode/ExtrusionProcessor.hpp"
#include "KDTreeIndirect.hpp" #include "KDTreeIndirect.hpp"
#include "Line.hpp"
#include "Point.hpp" #include "Point.hpp"
#include "Polygon.hpp" #include "Polygon.hpp"
#include "Polyline.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<unsigned int> extruders;
std::unordered_set<const PrintRegion *> 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<size_t, AABBTreeLines::LinesDistancer<CurledLine>> curled_lines;
std::unordered_map<size_t, AABBTreeLines::LinesDistancer<Linef>> unscaled_polygons_lines;
for (const Layer *l : this->layers()) {
curled_lines[l->id()] = AABBTreeLines::LinesDistancer<CurledLine>{l->curled_lines};
unscaled_polygons_lines[l->id()] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(l->lslices)};
}
curled_lines[size_t(-1)] = {};
unscaled_polygons_lines[size_t(-1)] = {};
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layers.size()), [this, &curled_lines, &unscaled_polygons_lines,
&regions_with_dynamic_speeds](
const tbb::blocked_range<size_t> &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<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data( std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data(
const std::vector<std::pair<const Surface *, float>> &surfaces_w_bottom_z) const const std::vector<std::pair<const Surface *, float>> &surfaces_w_bottom_z) const
{ {
@ -644,7 +705,9 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "first_layer_extrusion_width" || opt_key == "first_layer_extrusion_width"
|| opt_key == "perimeter_extrusion_width" || opt_key == "perimeter_extrusion_width"
|| opt_key == "infill_overlap" || 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); steps.emplace_back(posPerimeters);
} else if ( } else if (
opt_key == "gap_fill_enabled" opt_key == "gap_fill_enabled"
@ -845,7 +908,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
// propagate to dependent steps // propagate to dependent steps
if (step == posPerimeters) { 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 }); invalidated |= m_print->invalidate_steps({ psSkirtBrim });
} else if (step == posPrepareInfill) { } else if (step == posPrepareInfill) {
invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch}); invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch});
@ -854,7 +917,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
invalidated |= m_print->invalidate_steps({ psSkirtBrim }); invalidated |= m_print->invalidate_steps({ psSkirtBrim });
} else if (step == posSlice) { } else if (step == posSlice) {
invalidated |= this->invalidate_steps({posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, invalidated |= this->invalidate_steps({posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch,
posSupportMaterial, posEstimateCurledExtrusions}); posSupportMaterial, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters});
invalidated |= m_print->invalidate_steps({ psSkirtBrim }); invalidated |= m_print->invalidate_steps({ psSkirtBrim });
m_slicing_params.valid = false; m_slicing_params.valid = false;
} else if (step == posSupportMaterial) { } else if (step == posSupportMaterial) {

View File

@ -1006,16 +1006,20 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy2(SegmentEndPointFunc
return chain_segments_greedy_constrained_reversals2_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near); return chain_segments_greedy_constrained_reversals2_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near);
} }
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near) std::vector<std::pair<size_t, bool>> chain_extrusion_entities(const std::vector<ExtrusionEntity*> &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 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(); }; auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); };
std::vector<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(segment_end_point, could_reverse, entities.size(), start_near); std::vector<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(
segment_end_point, could_reverse, entities.size(), start_near);
for (std::pair<size_t, bool> &segment : out) { for (std::pair<size_t, bool> &segment : out) {
ExtrusionEntity *ee = entities[segment.first]; ExtrusionEntity *ee = entities[segment.first];
if (ee->is_loop()) if (ee->is_loop())
// Ignore reversals for loops, as the start point equals the end point. // Ignore reversals for loops, as the start point equals the end point.
segment.second = false; segment.second = false;
else if (reversed)
// Input was already reversed.
segment.second = ! segment.second;
// Is can_reverse() respected by the reversals? // Is can_reverse() respected by the reversals?
assert(ee->can_reverse() || ! segment.second); assert(ee->can_reverse() || ! segment.second);
} }
@ -1041,6 +1045,33 @@ void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entitie
reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near)); reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near));
} }
ExtrusionEntityReferences chain_extrusion_references(const std::vector<ExtrusionEntity*> &entities, const Point *start_near, const bool reversed)
{
const std::vector<std::pair<size_t, bool>> chain = chain_extrusion_entities(entities, start_near, reversed);
ExtrusionEntityReferences out;
out.reserve(chain.size());
for (const std::pair<size_t, bool> &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<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near) std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &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(); }; 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(); };

View File

@ -18,13 +18,25 @@ namespace Slic3r {
class ExPolygon; class ExPolygon;
using ExPolygons = std::vector<ExPolygon>; using ExPolygons = std::vector<ExPolygon>;
// Used by chain_expolygons()
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr); std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
// Used to give layer islands a print order.
std::vector<size_t> chain_expolygons(const ExPolygons &expolygons, Point *start_near = nullptr); std::vector<size_t> chain_expolygons(const ExPolygons &expolygons, Point *start_near = nullptr);
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &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<std::pair<size_t, bool>> chain_extrusion_entities(const std::vector<ExtrusionEntity*> &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<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain); void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain);
// Reorder & reverse extrusion entities in place.
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr); void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &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<ExtrusionEntity*> &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<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr); std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);
void reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, std::vector<std::pair<size_t, bool>> &chain); void reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, std::vector<std::pair<size_t, bool>> &chain);
void chain_and_reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr); void chain_and_reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);

View File

@ -485,8 +485,7 @@ static inline void fill_expolygon_generate_paths(
extrusion_entities_append_paths( extrusion_entities_append_paths(
dst, dst,
std::move(polylines), std::move(polylines),
role, { role, flow });
flow.mm3_per_mm(), flow.width(), flow.height());
} }
static inline void fill_expolygons_generate_paths( 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()); ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width());
if (level2.size() == 1) { if (level2.size() == 1) {
Polylines polylines; 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. // Disable reversal of the path, always start with the anchor, always print CCW.
false); false);
expoly = level2.front(); expoly = level2.front();
@ -750,7 +749,7 @@ static inline void tree_supports_generate_paths(
} }
ExtrusionEntitiesPtr &out = eec ? eec->entities : dst; 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. // Disable reversal of the path, always start with the anchor, always print CCW.
false); false);
if (eec) { if (eec) {
@ -794,7 +793,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
eec->no_sort = true; eec->no_sort = true;
} }
ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; 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 in the rest.
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
if (no_sort && ! eec->empty()) if (no_sort && ! eec->empty())
@ -1106,7 +1105,7 @@ void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact
extrusion_entities_append_paths( extrusion_entities_append_paths(
top_contact_layer.extrusions, top_contact_layer.extrusions,
std::move(loop_lines), std::move(loop_lines),
ExtrusionRole::SupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height()); { ExtrusionRole::SupportMaterialInterface, flow });
} }
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
@ -1148,24 +1147,19 @@ static void modulate_extrusion_by_overlapping_layers(
ExtrusionPath *extrusion_path_template = dynamic_cast<ExtrusionPath*>(extrusions_in_out.front()); ExtrusionPath *extrusion_path_template = dynamic_cast<ExtrusionPath*>(extrusions_in_out.front());
assert(extrusion_path_template != nullptr); assert(extrusion_path_template != nullptr);
ExtrusionRole extrusion_role = extrusion_path_template->role(); ExtrusionRole extrusion_role = extrusion_path_template->role();
float extrusion_width = extrusion_path_template->width; float extrusion_width = extrusion_path_template->width();
struct ExtrusionPathFragment struct ExtrusionPathFragment
{ {
ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {}; ExtrusionFlow flow;
ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {};
Polylines polylines; Polylines polylines;
double mm3_per_mm;
float width;
float height;
}; };
// Split the extrusions by the overlapping layers, reduce their extrusion rate. // Split the extrusions by the overlapping layers, reduce their extrusion rate.
// The last path_fragment is from this_layer. // The last path_fragment is from this_layer.
std::vector<ExtrusionPathFragment> path_fragments( std::vector<ExtrusionPathFragment> path_fragments(
n_overlapping_layers + 1, 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. // Don't use it, it will be released.
extrusion_path_template = nullptr; 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); 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). // 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); assert(this_layer.print_z > overlapping_layer.print_z);
frag.height = float(this_layer.print_z - overlapping_layer.print_z); frag.flow.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.mm3_per_mm = Flow(frag.flow.width, frag.flow.height, -1.f).mm3_per_mm();
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1)); svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1));
#endif /* SLIC3R_DEBUG */ #endif /* SLIC3R_DEBUG */
@ -1326,15 +1320,14 @@ static void modulate_extrusion_by_overlapping_layers(
ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back(); ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back();
if (path != nullptr) { if (path != nullptr) {
// Verify whether the path is compatible with the current fragment. // 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); 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.height || path->mm3_per_mm != frag.mm3_per_mm) { if (path->height() != frag.flow.height || path->mm3_per_mm() != frag.flow.mm3_per_mm)
path = nullptr; 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. // 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) { if (path == nullptr) {
// Allocate a new path. // 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(); path = &multipath.paths.back();
} }
// The Clipper library may flip the order of the clipped polylines arbitrarily. // 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. // 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. //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) for (ExtrusionPathFragment &fragment : path_fragments)
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); 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. // Support layer that is covered by some form of dense interface.

View File

@ -1023,7 +1023,7 @@ namespace SupportMaterialInternal {
assert(expansion_scaled >= 0.f); assert(expansion_scaled >= 0.f);
for (const ExtrusionPath &ep : loop.paths) for (const ExtrusionPath &ep : loop.paths)
if (ep.role() == ExtrusionRole::OverhangPerimeter && ! ep.polyline.empty()) { 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.is_closed()) {
if (ep.size() >= 3) { if (ep.size() >= 3) {
// This is a complete loop. // This is a complete loop.

View File

@ -328,9 +328,9 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
return {}; return {};
} }
const float flow_width = get_flow_width(layer_region, entity->role()); const float flow_width = get_flow_width(layer_region, entity->role());
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, true, true>(entity->as_polyline().points, std::vector<ExtrusionProcessor::ExtendedPoint> annotated_points =
prev_layer_boundary, flow_width, ExtrusionProcessor::estimate_points_properties<true, true, true, true>(entity->as_polyline().points, prev_layer_boundary,
params.bridge_distance); flow_width, params.bridge_distance);
std::vector<ExtrusionLine> lines_out; std::vector<ExtrusionLine> lines_out;
lines_out.reserve(annotated_points.size()); lines_out.reserve(annotated_points.size());
@ -339,8 +339,8 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
std::optional<Vec2d> bridging_dir{}; std::optional<Vec2d> bridging_dir{};
for (size_t i = 0; i < annotated_points.size(); ++i) { for (size_t i = 0; i < annotated_points.size(); ++i) {
ExtendedPoint &curr_point = annotated_points[i]; ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i];
const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : 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 potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor :
SupportPointCause::LongBridge; SupportPointCause::LongBridge;
@ -382,17 +382,17 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
const float flow_width = get_flow_width(layer_region, entity->role()); 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 // Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, false, false>(entity->as_polyline().points, std::vector<ExtrusionProcessor::ExtendedPoint> annotated_points =
prev_layer_lines, flow_width, ExtrusionProcessor::estimate_points_properties<true, true, false, false>(entity->as_polyline().points, prev_layer_lines,
params.bridge_distance); flow_width, params.bridge_distance);
std::vector<ExtrusionLine> lines_out; std::vector<ExtrusionLine> lines_out;
lines_out.reserve(annotated_points.size()); lines_out.reserve(annotated_points.size());
float bridged_distance = annotated_points.front().position != annotated_points.back().position ? (params.bridge_distance + 1.0f) : float bridged_distance = annotated_points.front().position != annotated_points.back().position ? (params.bridge_distance + 1.0f) :
0.0f; 0.0f;
for (size_t i = 0; i < annotated_points.size(); ++i) { for (size_t i = 0; i < annotated_points.size(); ++i) {
ExtendedPoint &curr_point = annotated_points[i]; ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i];
const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : 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(); float line_len = (prev_point.position - curr_point.position).norm();
ExtrusionLine line_out{prev_point.position.cast<float>(), curr_point.position.cast<float>(), line_len, entity}; ExtrusionLine line_out{prev_point.position.cast<float>(), curr_point.position.cast<float>(), line_len, entity};
@ -1107,11 +1107,12 @@ void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width,
Polygon pol(pl.points); Polygon pol(pl.points);
pol.make_counter_clockwise(); pol.make_counter_clockwise();
auto annotated_points = estimate_points_properties<true, true, false, false>(pol.points, prev_layer_lines, flow_width); auto annotated_points = ExtrusionProcessor::estimate_points_properties<true, true, false, false>(pol.points, prev_layer_lines,
flow_width);
for (size_t i = 0; i < annotated_points.size(); ++i) { for (size_t i = 0; i < annotated_points.size(); ++i) {
const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i]; const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
const ExtendedPoint &b = annotated_points[i]; const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i];
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()), ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
extrusion}; extrusion};
@ -1182,11 +1183,13 @@ void estimate_malformations(LayerPtrs &layers, const Params &params)
Points extrusion_pts; Points extrusion_pts;
extrusion->collect_points(extrusion_pts); extrusion->collect_points(extrusion_pts);
float flow_width = get_flow_width(layer_region, extrusion->role()); float flow_width = get_flow_width(layer_region, extrusion->role());
auto annotated_points = estimate_points_properties<true, true, false, false>(extrusion_pts, prev_layer_lines, flow_width, auto annotated_points = ExtrusionProcessor::estimate_points_properties<true, true, false, false>(extrusion_pts,
prev_layer_lines,
flow_width,
params.bridge_distance); params.bridge_distance);
for (size_t i = 0; i < annotated_points.size(); ++i) { for (size_t i = 0; i < annotated_points.size(); ++i) {
const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i]; const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
const ExtendedPoint &b = annotated_points[i]; const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i];
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()), ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
extrusion}; extrusion};
@ -1196,7 +1199,8 @@ void estimate_malformations(LayerPtrs &layers, const Params &params)
prev_layer_lines.get_line(bottom_line_idx); prev_layer_lines.get_line(bottom_line_idx);
// correctify the distance sign using slice polygons // correctify the distance sign using slice polygons
float sign = (prev_layer_boundary.distance_from_lines<true>(middle.cast<double>()) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f; float sign = (prev_layer_boundary.distance_from_lines<true>(middle.cast<double>()) + 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), 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); l->height, flow_width, bottom_line.curled_up_height, params);

View File

@ -1442,8 +1442,8 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionPath& extrusion_path, flo
polyline.remove_duplicate_points(); polyline.remove_duplicate_points();
polyline.translate(copy); polyline.translate(copy);
const Lines lines = polyline.lines(); const Lines lines = polyline.lines();
std::vector<double> widths(lines.size(), extrusion_path.width); std::vector<double> widths(lines.size(), extrusion_path.width());
std::vector<double> heights(lines.size(), extrusion_path.height); std::vector<double> heights(lines.size(), extrusion_path.height());
thick_lines_to_verts(lines, widths, heights, false, print_z, geometry); 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); polyline.translate(copy);
const Lines lines_this = polyline.lines(); const Lines lines_this = polyline.lines();
append(lines, lines_this); append(lines, lines_this);
widths.insert(widths.end(), lines_this.size(), extrusion_path.width); widths.insert(widths.end(), lines_this.size(), extrusion_path.width());
heights.insert(heights.end(), lines_this.size(), extrusion_path.height); heights.insert(heights.end(), lines_this.size(), extrusion_path.height());
} }
thick_lines_to_verts(lines, widths, heights, true, print_z, geometry); 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); polyline.translate(copy);
const Lines lines_this = polyline.lines(); const Lines lines_this = polyline.lines();
append(lines, lines_this); append(lines, lines_this);
widths.insert(widths.end(), lines_this.size(), extrusion_path.width); widths.insert(widths.end(), lines_this.size(), extrusion_path.width());
heights.insert(heights.end(), lines_this.size(), extrusion_path.height); heights.insert(heights.end(), lines_this.size(), extrusion_path.height());
} }
thick_lines_to_verts(lines, widths, heights, false, print_z, geometry); thick_lines_to_verts(lines, widths, heights, false, print_z, geometry);
} }

View File

@ -341,6 +341,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field("min_feature_size", have_arachne); toggle_field("min_feature_size", have_arachne);
toggle_field("min_bead_width", have_arachne); toggle_field("min_bead_width", have_arachne);
toggle_field("thin_walls", !have_arachne); toggle_field("thin_walls", !have_arachne);
bool has_arc_fitting = config->opt_enum<ArcFittingType>("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*/) void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)

View File

@ -7,7 +7,7 @@
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp"
#include "libslic3r/GCodeWriter.hpp" #include "libslic3r/GCode/GCodeWriter.hpp"
#include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/PrintHost.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("slicing_mode");
optgroup->append_single_option_line("resolution"); optgroup->append_single_option_line("resolution");
optgroup->append_single_option_line("gcode_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("xy_size_compensation");
optgroup->append_single_option_line("elefant_foot_compensation", "elephant-foot-compensation_114487"); optgroup->append_single_option_line("elefant_foot_compensation", "elephant-foot-compensation_114487");

View File

@ -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__

View File

@ -14,7 +14,7 @@
using namespace Slic3r; using namespace Slic3r;
std::unique_ptr<CoolingBuffer> make_cooling_buffer( std::unique_ptr<CoolingBuffer> make_cooling_buffer(
GCode &gcode, GCodeGenerator &gcode,
const DynamicPrintConfig &config = DynamicPrintConfig{}, const DynamicPrintConfig &config = DynamicPrintConfig{},
const std::vector<unsigned int> &extruder_ids = { 0 }) const std::vector<unsigned int> &extruder_ids = { 0 })
{ {
@ -65,7 +65,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
const double print_time = 100. / (3000. / 60.); const double print_time = 100. / (3000. / 60.);
//FIXME slowdown_below_layer_time is rounded down significantly from 1.8s to 1s. //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) } } }); config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 0.999) } } });
GCode gcodegen; GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config); auto buffer = make_cooling_buffer(gcodegen, config);
std::string gcode = buffer->process_layer("G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1", 0, true); 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; bool speed_not_altered = gcode.find("F3000") != gcode.npos;
@ -83,7 +83,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
// Print time of gcode. // Print time of gcode.
const double print_time = 50. / (2500. / 60.) + 100. / (3000. / 60.) + 4. / (400. / 60.); 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) } } }); config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 1.001) } } });
GCode gcodegen; GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config); auto buffer = make_cooling_buffer(gcodegen, config);
std::string gcode = buffer->process_layer(gcode_src, 0, true); std::string gcode = buffer->process_layer(gcode_src, 0, true);
THEN("speed is altered when elapsed time is lower than slowdown threshold") { 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) }, { "fan_below_layer_time" , int(print_time1 * 0.88) },
{ "slowdown_below_layer_time" , int(print_time1 * 0.99) } { "slowdown_below_layer_time" , int(print_time1 * 0.99) }
}); });
GCode gcodegen; GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config); auto buffer = make_cooling_buffer(gcodegen, config);
std::string gcode = buffer->process_layer(gcode1, 0, true); std::string gcode = buffer->process_layer(gcode1, 0, true);
bool fan_not_activated = gcode.find("M106") == gcode.npos; 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.) } }, { "fan_below_layer_time", { int(print_time2 + 1.), int(print_time2 + 1.) } },
{ "slowdown_below_layer_time", { int(print_time2 + 2.), int(print_time2 + 2.) } } { "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 }); auto buffer = make_cooling_buffer(gcodegen, config, { 0, 1 });
std::string gcode = buffer->process_layer(gcode1 + "T1\nG1 X0 E1 F3000\n", 0, true); std::string gcode = buffer->process_layer(gcode1 + "T1\nG1 X0 E1 F3000\n", 0, true);
THEN("fan is activated for the 1st tool") { THEN("fan is activated for the 1st tool") {
@ -134,7 +134,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
WHEN("G-code block 2") { WHEN("G-code block 2") {
THEN("slowdown is computed on all objects printing at the same Z") { 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) } }); config.set_deserialize_strict({ { "slowdown_below_layer_time", int(print_time2 * 0.99) } });
GCode gcodegen; GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config); auto buffer = make_cooling_buffer(gcodegen, config);
std::string gcode = buffer->process_layer(gcode2, 0, true); std::string gcode = buffer->process_layer(gcode2, 0, true);
bool ok = gcode.find("F3000") != gcode.npos; 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) }, { "fan_below_layer_time", int(print_time2 * 0.65) },
{ "slowdown_below_layer_time", int(print_time2 * 0.7) } { "slowdown_below_layer_time", int(print_time2 * 0.7) }
}); });
GCode gcodegen; GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config); auto buffer = make_cooling_buffer(gcodegen, config);
// use an elapsed time which is < the threshold but greater than it when summed twice // 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); 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) }, { "fan_below_layer_time", int(print_time2 + 1) },
{ "slowdown_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); auto buffer = make_cooling_buffer(gcodegen, config);
// use an elapsed time which is < the threshold but greater than it when summed twice // 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); std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true);

View File

@ -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. // 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) 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) for (size_t j = 0; j < length; ++ j)
t.polyline.append(random_point(LO, HI)); t.polyline.append(random_point(LO, HI));
return t; return t;
@ -37,9 +37,8 @@ static Slic3r::ExtrusionPaths random_paths(size_t count = 10, size_t length = 20
SCENARIO("ExtrusionPath", "[ExtrusionEntity]") { SCENARIO("ExtrusionPath", "[ExtrusionEntity]") {
GIVEN("Simple path") { GIVEN("Simple path") {
Slic3r::ExtrusionPath path{ ExtrusionRole::ExternalPerimeter }; Slic3r::ExtrusionPath path{ { { 100, 100 }, { 200, 100 }, { 200, 200 } },
path.polyline = { { 100, 100 }, { 200, 100 }, { 200, 200 } }; ExtrusionAttributes{ ExtrusionRole::ExternalPerimeter, ExtrusionFlow{ 1., -1.f, -1.f } } };
path.mm3_per_mm = 1.;
THEN("first point") { THEN("first point") {
REQUIRE(path.first_point() == path.polyline.front()); 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) static ExtrusionPath new_extrusion_path(const Polyline &polyline, ExtrusionRole role, double mm3_per_mm)
{ {
ExtrusionPath path(role); return { polyline, ExtrusionAttributes{ role, ExtrusionFlow{ mm3_per_mm, -1.f, -1.f } } };
path.polyline = polyline;
path.mm3_per_mm = 1.;
return path;
} }
SCENARIO("ExtrusionLoop", "[ExtrusionEntity]") 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.)); loop.paths.emplace_back(new_extrusion_path(square.split_at_first_point(), ExtrusionRole::ExternalPerimeter, 1.));
THEN("polygon area") { THEN("polygon area") {
REQUIRE(loop.polygon().area() == Approx(square.area())); REQUIRE(loop.polygon().area() == Approx(square.area()));
REQUIRE(loop.area() == Approx(square.area()));
} }
THEN("loop length") { THEN("loop length") {
REQUIRE(loop.length() == Approx(square.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(polyline1, ExtrusionRole::ExternalPerimeter, 1.));
loop.paths.emplace_back(new_extrusion_path(polyline2, ExtrusionRole::OverhangPerimeter, 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(); double tot_len = polyline1.length() + polyline2.length();
THEN("length") { THEN("length") {
REQUIRE(loop.length() == Approx(tot_len)); 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(polyline3, ExtrusionRole::ExternalPerimeter, 1.));
loop.paths.emplace_back(new_extrusion_path(polyline4, ExtrusionRole::OverhangPerimeter, 1.)); loop.paths.emplace_back(new_extrusion_path(polyline4, ExtrusionRole::OverhangPerimeter, 1.));
double len = loop.length(); double len = loop.length();
THEN("area") {
REQUIRE(loop.area() == Approx(loop.polygon().area()));
}
WHEN("splitting at vertex") { WHEN("splitting at vertex") {
Point point(4821067, 9321068); Point point(4821067, 9321068);
if (! loop.split_at_vertex(point)) if (! loop.split_at_vertex(point))
@ -234,6 +237,9 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
Polyline { { 15896783, 15868739 }, { 24842049, 12117558 }, { 33853238, 15801279 }, { 37591780, 24780128 }, { 37591780, 24844970 }, Polyline { { 15896783, 15868739 }, { 24842049, 12117558 }, { 33853238, 15801279 }, { 37591780, 24780128 }, { 37591780, 24844970 },
{ 33853231, 33825297 }, { 24842049, 37509013 }, { 15896798, 33757841 }, { 12211841, 24812544 }, { 15896783, 15868739 } }, { 33853231, 33825297 }, { 24842049, 37509013 }, { 15896798, 33757841 }, { 12211841, 24812544 }, { 15896783, 15868739 } },
ExtrusionRole::ExternalPerimeter, 1.)); ExtrusionRole::ExternalPerimeter, 1.));
THEN("area") {
REQUIRE(loop.area() == Approx(loop.polygon().area()));
}
double len = loop.length(); double len = loop.length();
THEN("split_at() preserves total length") { THEN("split_at() preserves total length") {
loop.split_at({ 15896783, 15868739 }, false, 0); loop.split_at({ 15896783, 15868739 }, false, 0);
@ -378,23 +384,27 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") {
REQUIRE(chained == test.chained); REQUIRE(chained == test.chained);
ExtrusionEntityCollection unchained_extrusions; ExtrusionEntityCollection unchained_extrusions;
extrusion_entities_append_paths(unchained_extrusions.entities, test.unchained, 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") { THEN("Chaining works") {
ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); ExtrusionEntityReferences chained_extrusions = chain_extrusion_references(unchained_extrusions, &test.initial_point);
REQUIRE(chained_extrusions.entities.size() == test.chained.size()); REQUIRE(chained_extrusions.size() == test.chained.size());
for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { for (size_t i = 0; i < chained_extrusions.size(); ++ i) {
const Points &p1 = test.chained[i].points; const Points &p1 = test.chained[i].points;
const Points &p2 = dynamic_cast<const ExtrusionPath*>(chained_extrusions.entities[i])->polyline.points; Points p2 = chained_extrusions[i].cast<ExtrusionPath>()->polyline.points;
if (chained_extrusions[i].flipped())
std::reverse(p2.begin(), p2.end());
REQUIRE(p1 == p2); REQUIRE(p1 == p2);
} }
} }
THEN("Chaining produces no change with no_sort") { THEN("Chaining produces no change with no_sort") {
unchained_extrusions.no_sort = true; unchained_extrusions.no_sort = true;
ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point); ExtrusionEntityReferences chained_extrusions = chain_extrusion_references(unchained_extrusions, &test.initial_point);
REQUIRE(chained_extrusions.entities.size() == test.unchained.size()); REQUIRE(chained_extrusions.size() == test.unchained.size());
for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) { for (size_t i = 0; i < chained_extrusions.size(); ++ i) {
const Points &p1 = test.unchained[i].points; const Points &p1 = test.unchained[i].points;
const Points &p2 = dynamic_cast<const ExtrusionPath*>(chained_extrusions.entities[i])->polyline.points; Points p2 = chained_extrusions[i].cast<ExtrusionPath>()->polyline.points;
if (chained_extrusions[i].flipped())
std::reverse(p2.begin(), p2.end());
REQUIRE(p1 == p2); REQUIRE(p1 == p2);
} }
} }

View File

@ -7,7 +7,7 @@
using namespace Slic3r; using namespace Slic3r;
SCENARIO("Origin manipulation", "[GCode]") { SCENARIO("Origin manipulation", "[GCode]") {
Slic3r::GCode gcodegen; Slic3r::GCodeGenerator gcodegen;
WHEN("set_origin to (10,0)") { WHEN("set_origin to (10,0)") {
gcodegen.set_origin(Vec2d(10,0)); gcodegen.set_origin(Vec2d(10,0));
REQUIRE(gcodegen.origin() == Vec2d(10, 0)); REQUIRE(gcodegen.origin() == Vec2d(10, 0));

View File

@ -2,7 +2,7 @@
#include <memory> #include <memory>
#include "libslic3r/GCodeWriter.hpp" #include "libslic3r/GCode/GCodeWriter.hpp"
using namespace Slic3r; using namespace Slic3r;

View File

@ -6,6 +6,7 @@ add_executable(${_TEST_NAME}_tests
test_aabbindirect.cpp test_aabbindirect.cpp
test_kdtreeindirect.cpp test_kdtreeindirect.cpp
test_arachne.cpp test_arachne.cpp
test_arc_welder.cpp
test_clipper_offset.cpp test_clipper_offset.cpp
test_clipper_utils.cpp test_clipper_utils.cpp
test_color.cpp test_color.cpp

View File

@ -0,0 +1,264 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <libslic3r/Geometry/ArcWelder.hpp>
#include <libslic3r/libslic3r.h>
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<float>(1.);
const float resolution = scaled<float>(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<float>(), p2.cast<float>(), r, ccw);
REQUIRE((p1.cast<float>() - c).norm() == Approx(radius));
REQUIRE((c - center.cast<float>()).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<double>() - c.cast<double>()).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<float>(1.);
const float resolution = scaled<float>(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<float>(1.);
const float resolution = scaled<float>(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<double>(), p2.cast<double>(), double(r), ccw);
REQUIRE(is_approx(c, center.cast<double>()));
REQUIRE(ArcWelder::inside_arc_wedge(p1, p2, center, r > 0, ccw, ptest) == ptest_inside);
REQUIRE(ArcWelder::inside_arc_wedge(p1.cast<double>(), p2.cast<double>(), double(r), ccw, ptest.cast<double>()) == 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);
}
}
}

View File

@ -84,16 +84,16 @@ TEST_CASE("Line::perpendicular_to", "[Geometry]") {
TEST_CASE("Polygon::contains works properly", "[Geometry]"){ TEST_CASE("Polygon::contains works properly", "[Geometry]"){
// this test was failing on Windows (GH #1950) // this test was failing on Windows (GH #1950)
Slic3r::Polygon polygon(Points({ Slic3r::Polygon polygon(Points({
Point(207802834,-57084522), {207802834,-57084522},
Point(196528149,-37556190), {196528149,-37556190},
Point(173626821,-25420928), {173626821,-25420928},
Point(171285751,-21366123), {171285751,-21366123},
Point(118673592,-21366123), {118673592,-21366123},
Point(116332562,-25420928), {116332562,-25420928},
Point(93431208,-37556191), {93431208,-37556191},
Point(82156517,-57084523), {82156517,-57084523},
Point(129714478,-84542120), {129714478,-84542120},
Point(160244873,-84542120) {160244873,-84542120}
})); }));
Point point(95706562, -57294774); Point point(95706562, -57294774);
REQUIRE(polygon.contains(point)); 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()); 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<double>(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<Vec2d> 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<Vec2d> 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<double>(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<Vec2d> 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]") { 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") { GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
Vec2d expected_center(-6, 0); Vec2d expected_center(-6, 0);
@ -310,6 +345,7 @@ SCENARIO("Path chaining", "[Geometry]") {
GIVEN("A path") { 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) }; 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)") { 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<Points::size_type> indices = chain_points(points); std::vector<Points::size_type> indices = chain_points(points);
for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) { for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) {
double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm(); double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm();

View File

@ -5,6 +5,25 @@
using namespace Slic3r; 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<int64_t>(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<int64_t>(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]") SCENARIO("Simplify polyline", "[Polyline]")
{ {
GIVEN("polyline 1") { GIVEN("polyline 1") {

View File

@ -4,7 +4,7 @@
namespace Slic3r { namespace Slic3r {
REGISTER_CLASS(ExPolygon, "ExPolygon"); REGISTER_CLASS(ExPolygon, "ExPolygon");
REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(GCodeGenerator, "GCode");
REGISTER_CLASS(Line, "Line"); REGISTER_CLASS(Line, "Line");
REGISTER_CLASS(Polygon, "Polygon"); REGISTER_CLASS(Polygon, "Polygon");
REGISTER_CLASS(Polyline, "Polyline"); REGISTER_CLASS(Polyline, "Polyline");

View File

@ -20,15 +20,6 @@ convex_hull(points)
OUTPUT: OUTPUT:
RETVAL RETVAL
std::vector<Points::size_type>
chained_path_from(points, start_from)
Points points
Point* start_from
CODE:
RETVAL = chain_points(points, start_from);
OUTPUT:
RETVAL
double double
rad2deg(angle) rad2deg(angle)
double angle double angle

View File

@ -19,7 +19,6 @@
Lines lines(); Lines lines();
Clone<Polyline> split_at_vertex(Point* point) Clone<Polyline> split_at_vertex(Point* point)
%code{% RETVAL = THIS->split_at_vertex(*point); %}; %code{% RETVAL = THIS->split_at_vertex(*point); %};
Clone<Polyline> split_at_index(int index);
Clone<Polyline> split_at_first_point(); Clone<Polyline> split_at_first_point();
double length(); double length();
double area(); double area();