ArcWelder path interpolation based on the work by Brad Hochgesang @FormerLurker.

WIP GCode/SmoothPath.cpp,hpp cache for interpolating extrusion path with arches.
Removed Perl test t/geometry.t, replaced with C++ tests.
Refactored ExtrusionEntity and derived classes to hold extrusion attributes in new ExtrusionFlow/ExtrusionAttributes classes.
Reworked path ordering in G-code export to never copy polylines, but to work with a new "flipped" attribute.
Reworked G-code export to interpolate extrusion paths with smooth paths and to extrude those smooth paths.
New parameters: arc_fitting, arc_fitting_tolerance
Renamed GCode class to GCodeGenerator
Moved GCodeWriter.cpp/hpp to GCode/
Moved Wipe from from GCode.cpp,hpp to GCode/Wipe.cpp,hpp
Moved WipeTowerIntegration from GCode.cpp,hpp to GCode/WipeTowerIntegration.cpp,hpp
New variant of douglas_peucker() to simplify range of iterators in place.
Refactored wipe in general and wipe on perimeters / hiding seams.

WIP: Convert estimate_speed_from_extrusion_quality() and its application to smooth paths.
WIP: Cooling buffer to process G2G3, disable arc fitting for filters that cannot process it.
This commit is contained in:
Vojtech Bubnik 2023-07-13 11:54:42 +02:00
parent e0f7263a4c
commit 19062b4d5f
64 changed files with 2829 additions and 1250 deletions

View File

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

View File

@ -490,8 +490,10 @@ static void make_inner_brim(const Print &print,
loops = union_pt_chained_outside_in(loops);
std::reverse(loops.begin(), loops.end());
extrusion_entities_append_loops(brim.entities, std::move(loops), ExtrusionRole::Skirt, float(flow.mm3_per_mm()),
float(flow.width()), float(print.skirt_first_layer_height()));
extrusion_entities_append_loops(brim.entities, std::move(loops),
ExtrusionAttributes{
ExtrusionRole::Skirt,
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } });
}
// Produce brim lines around those objects, that have the brim enabled.
@ -672,7 +674,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
if (i + 1 == j && first_path.size() > 3 && first_path.front().x() == first_path.back().x() && first_path.front().y() == first_path.back().y()) {
auto *loop = new ExtrusionLoop();
brim.entities.emplace_back(loop);
loop->paths.emplace_back(ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
loop->paths.emplace_back(ExtrusionAttributes{
ExtrusionRole::Skirt,
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } });
Points &points = loop->paths.front().polyline.points;
points.reserve(first_path.size());
for (const ClipperLib_Z::IntPoint &pt : first_path)
@ -683,7 +687,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
ExtrusionEntityCollection this_loop_trimmed;
this_loop_trimmed.entities.reserve(j - i);
for (; i < j; ++ i) {
this_loop_trimmed.entities.emplace_back(new ExtrusionPath(ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())));
this_loop_trimmed.entities.emplace_back(new ExtrusionPath({
ExtrusionRole::Skirt,
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } }));
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
Points &points = dynamic_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points;
points.reserve(path.size());
@ -699,7 +705,9 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
}
}
} else {
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), ExtrusionRole::Skirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops),
ExtrusionAttributes{ ExtrusionRole::Skirt,
ExtrusionFlow{ float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()) } });
}
make_inner_brim(print, top_level_objects_with_brim, bottom_layers_expolygons, brim);

View File

@ -144,6 +144,8 @@ set(SLIC3R_SOURCES
GCode/CoolingBuffer.hpp
GCode/FindReplace.cpp
GCode/FindReplace.hpp
GCode/GCodeWriter.cpp
GCode/GCodeWriter.hpp
GCode/PostProcessor.cpp
GCode/PostProcessor.hpp
GCode/PressureEqualizer.cpp
@ -156,10 +158,16 @@ set(SLIC3R_SOURCES
GCode/SpiralVase.hpp
GCode/SeamPlacer.cpp
GCode/SeamPlacer.hpp
GCode/SmoothPath.cpp
GCode/SmoothPath.hpp
GCode/ToolOrdering.cpp
GCode/ToolOrdering.hpp
GCode/Wipe.cpp
GCode/Wipe.hpp
GCode/WipeTower.cpp
GCode/WipeTower.hpp
GCode/WipeTowerIntegration.cpp
GCode/WipeTowerIntegration.hpp
GCode/GCodeProcessor.cpp
GCode/GCodeProcessor.hpp
GCode/AvoidCrossingPerimeters.cpp
@ -170,10 +178,10 @@ set(SLIC3R_SOURCES
GCodeReader.hpp
# GCodeSender.cpp
# GCodeSender.hpp
GCodeWriter.cpp
GCodeWriter.hpp
Geometry.cpp
Geometry.hpp
Geometry/ArcWelder.cpp
Geometry/ArcWelder.hpp
Geometry/Bicubic.hpp
Geometry/Circle.cpp
Geometry/Circle.hpp

View File

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

View File

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

View File

@ -2,6 +2,7 @@
#include "ExtrusionEntityCollection.hpp"
#include "ExPolygon.hpp"
#include "ClipperUtils.hpp"
#include "Exception.hpp"
#include "Extruder.hpp"
#include "Flow.hpp"
#include <cmath>
@ -9,7 +10,7 @@
#include <sstream>
namespace Slic3r {
void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const
{
this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection), retval);
@ -38,12 +39,12 @@ double ExtrusionPath::length() const
void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
{
for (const Polyline &polyline : polylines)
collection->entities.emplace_back(new ExtrusionPath(polyline, *this));
collection->entities.emplace_back(new ExtrusionPath(polyline, this->attributes()));
}
void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon));
polygons_append(out, offset(this->polyline, float(scale_(m_attributes.width/2)) + scaled_epsilon));
}
void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
@ -51,8 +52,8 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale
// Instantiating the Flow class to get the line spacing.
// Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler.
bool bridge = this->role().is_bridge();
assert(! bridge || this->width == this->height);
auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f);
assert(! bridge || m_attributes.width == m_attributes.height);
auto flow = bridge ? Flow::bridging_flow(m_attributes.width, 0.f) : Flow(m_attributes.width, m_attributes.height, 0.f);
polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
}
@ -87,7 +88,7 @@ double ExtrusionMultiPath::min_mm3_per_mm() const
{
double min_mm3_per_mm = std::numeric_limits<double>::max();
for (const ExtrusionPath &path : this->paths)
min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
min_mm3_per_mm = std::min(min_mm3_per_mm, path.min_mm3_per_mm());
return min_mm3_per_mm;
}
@ -112,21 +113,34 @@ Polyline ExtrusionMultiPath::as_polyline() const
return out;
}
bool ExtrusionLoop::make_clockwise()
double ExtrusionLoop::area() const
{
bool was_ccw = this->polygon().is_counter_clockwise();
if (was_ccw) this->reverse();
return was_ccw;
}
bool ExtrusionLoop::make_counter_clockwise()
{
bool was_cw = this->polygon().is_clockwise();
if (was_cw) this->reverse();
return was_cw;
double a = 0;
for (const ExtrusionPath &path : this->paths) {
assert(path.size() >= 2);
if (path.size() >= 2) {
// Assumming that the last point of one path segment is repeated at the start of the following path segment.
auto it = path.polyline.points.begin();
Point prev = *it ++;
for (; it != path.polyline.points.end(); ++ it) {
a += cross2(prev.cast<double>(), it->cast<double>());
prev = *it;
}
}
}
return a * 0.5;
}
void ExtrusionLoop::reverse()
{
#if 0
this->reverse_loop();
#else
throw Slic3r::LogicError("ExtrusionLoop::reverse() must NOT be called");
#endif
}
void ExtrusionLoop::reverse_loop()
{
for (ExtrusionPath &path : this->paths)
path.reverse();
@ -248,8 +262,8 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang, const
// now split path_idx in two parts
const ExtrusionPath &path = this->paths[path_idx];
ExtrusionPath p1(path.role(), path.mm3_per_mm, path.width, path.height);
ExtrusionPath p2(path.role(), path.mm3_per_mm, path.width, path.height);
ExtrusionPath p1(path.attributes());
ExtrusionPath p2(path.attributes());
path.polyline.split_at(p, &p1.polyline, &p2.polyline);
if (this->paths.size() == 1) {
@ -316,7 +330,7 @@ double ExtrusionLoop::min_mm3_per_mm() const
{
double min_mm3_per_mm = std::numeric_limits<double>::max();
for (const ExtrusionPath &path : this->paths)
min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
min_mm3_per_mm = std::min(min_mm3_per_mm, path.min_mm3_per_mm());
return min_mm3_per_mm;
}

View File

@ -3,6 +3,7 @@
#include "libslic3r.h"
#include "ExtrusionRole.hpp"
#include "Flow.hpp"
#include "Polygon.hpp"
#include "Polyline.hpp"
@ -55,28 +56,80 @@ public:
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; }
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 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 };
};
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
{
public:
Polyline polyline;
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
double mm3_per_mm;
// Width of the extrusion, used for visualization purposes.
float width;
// Height of the extrusion, used for visualization purposes.
float height;
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role) {}
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {}
ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role) {}
ExtrusionPath(ExtrusionRole role) : m_attributes{ role } {}
ExtrusionPath(const ExtrusionAttributes &attributes) : m_attributes(attributes) {}
ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), m_attributes(rhs.m_attributes) {}
ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), m_attributes(rhs.m_attributes) {}
ExtrusionPath(const Polyline &polyline, const ExtrusionAttributes &attribs) : polyline(polyline), m_attributes(attribs) {}
ExtrusionPath(Polyline &&polyline, const ExtrusionAttributes &attribs) : polyline(std::move(polyline)), m_attributes(attribs) {}
ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = rhs.polyline; return *this; }
ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->polyline = std::move(rhs.polyline); return *this; }
ExtrusionPath& operator=(const ExtrusionPath &rhs) { this->polyline = rhs.polyline; m_attributes = rhs.m_attributes; return *this; }
ExtrusionPath& operator=(ExtrusionPath &&rhs) { this->polyline = std::move(rhs.polyline); m_attributes = rhs.m_attributes; return *this; }
ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
// Create a new object, initialize it with this object using the move semantics.
@ -97,35 +150,45 @@ public:
void clip_end(double distance);
void simplify(double tolerance);
double length() const override;
ExtrusionRole role() const override { return m_role; }
const ExtrusionAttributes& attributes() const { return m_attributes; }
ExtrusionRole role() const override { return m_attributes.role; }
float width() const { return m_attributes.width; }
float height() const { return m_attributes.height; }
double mm3_per_mm() const { return m_attributes.mm3_per_mm; }
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const override { return m_attributes.mm3_per_mm; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const override { return this->mm3_per_mm; }
Polyline as_polyline() const override { return this->polyline; }
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
void collect_points(Points &dst) const override { append(dst, this->polyline.points); }
double total_volume() const override { return mm3_per_mm * unscale<double>(length()); }
Polyline as_polyline() const override { return this->polyline; }
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
void collect_points(Points &dst) const override { append(dst, this->polyline.points); }
double total_volume() const override { return m_attributes.mm3_per_mm * unscale<double>(length()); }
private:
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
ExtrusionRole m_role;
ExtrusionAttributes m_attributes;
};
class ExtrusionPathOriented : public ExtrusionPath
{
public:
ExtrusionPathOriented(ExtrusionRole role, double mm3_per_mm, float width, float height) : ExtrusionPath(role, mm3_per_mm, width, height) {}
ExtrusionPathOriented(const ExtrusionAttributes &attribs) : ExtrusionPath(attribs) {}
ExtrusionPathOriented(const Polyline &polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(polyline, attribs) {}
ExtrusionPathOriented(Polyline &&polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(std::move(polyline), attribs) {}
ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); }
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); }
@ -192,7 +255,8 @@ class ExtrusionLoop : public ExtrusionEntity
public:
ExtrusionPaths paths;
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {}
ExtrusionLoop() = default;
ExtrusionLoop(ExtrusionLoopRole role) : m_loop_role(role) {}
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {}
ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {}
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
@ -204,16 +268,21 @@ public:
ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); }
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionLoop(std::move(*this)); }
bool make_clockwise();
bool make_counter_clockwise();
void reverse() override;
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; }
Polygon polygon() const;
double length() const override;
bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled<double>(0.001));
void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled<double>(0.001));
double area() const;
bool is_counter_clockwise() const { return this->area() > 0; }
bool is_clockwise() const { return this->area() < 0; }
// Reverse shall never be called on ExtrusionLoop using a virtual function call, it is most likely never what one wants,
// as this->can_reverse() returns false for an ExtrusionLoop.
void reverse() override;
// Used by PerimeterGenerator to reorient extrusion loops.
void reverse_loop();
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; }
Polygon polygon() const;
double length() const override;
bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled<double>(0.001));
void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled<double>(0.001));
struct ClosestPathPoint {
size_t path_idx;
size_t segment_idx;
@ -259,59 +328,51 @@ public:
#endif /* NDEBUG */
private:
ExtrusionLoopRole m_loop_role;
ExtrusionLoopRole m_loop_role{ elrDefault };
};
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, const ExtrusionAttributes &attributes)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
dst.back().polyline = polyline;
}
if (polyline.is_valid())
dst.emplace_back(polyline, attributes);
}
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, const ExtrusionAttributes &attributes)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
dst.back().polyline = std::move(polyline);
}
if (polyline.is_valid())
dst.emplace_back(std::move(polyline), attributes);
polylines.clear();
}
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, const ExtrusionAttributes &attributes, bool can_reverse = true)
{
dst.reserve(dst.size() + polylines.size());
for (const Polyline &polyline : polylines)
if (polyline.is_valid()) {
ExtrusionPath* extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = polyline;
}
if (polyline.is_valid())
dst.emplace_back(can_reverse ? new ExtrusionPath(polyline, attributes) : new ExtrusionPathOriented(polyline, attributes));
}
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, const ExtrusionAttributes &attributes, bool can_reverse = true)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = std::move(polyline);
}
if (polyline.is_valid())
dst.emplace_back(can_reverse ?
new ExtrusionPath(std::move(polyline), attributes) :
new ExtrusionPathOriented(std::move(polyline), attributes));
polylines.clear();
}
inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, const ExtrusionAttributes &attributes)
{
dst.reserve(dst.size() + loops.size());
for (Polygon &poly : loops) {
if (poly.is_valid()) {
ExtrusionPath path(role, mm3_per_mm, width, height);
ExtrusionPath path(attributes);
path.polyline.points = std::move(poly.points);
path.polyline.points.push_back(path.polyline.points.front());
dst.emplace_back(new ExtrusionLoop(std::move(path)));
@ -320,22 +381,14 @@ inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons
loops.clear();
}
inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, const ExtrusionAttributes &attributes)
{
dst.reserve(dst.size() + polylines.size());
for (Polyline &polyline : polylines) {
if (polyline.is_valid()) {
if (polyline.is_closed()) {
ExtrusionPath extrusion_path(role, mm3_per_mm, width, height);
extrusion_path.polyline = std::move(polyline);
dst.emplace_back(new ExtrusionLoop(std::move(extrusion_path)));
} else {
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
extrusion_path->polyline = std::move(polyline);
dst.emplace_back(extrusion_path);
}
}
}
for (Polyline &polyline : polylines)
if (polyline.is_valid())
dst.emplace_back(polyline.is_closed() ?
static_cast<ExtrusionEntity*>(new ExtrusionLoop(ExtrusionPath{ std::move(polyline), attributes })) :
static_cast<ExtrusionEntity*>(new ExtrusionPath(std::move(polyline), attributes)));
polylines.clear();
}

View File

@ -6,6 +6,7 @@
namespace Slic3r {
#if 0
void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role)
{
if (role != ExtrusionRole::Mixed) {
@ -17,6 +18,7 @@ void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities,
last);
}
}
#endif
ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths)
: no_sort(false)
@ -83,18 +85,6 @@ void ExtrusionEntityCollection::remove(size_t i)
this->entities.erase(this->entities.begin() + i);
}
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const ExtrusionEntitiesPtr& extrusion_entities, const Point &start_near, ExtrusionRole role)
{
// Return a filtered copy of the collection.
ExtrusionEntityCollection out;
out.entities = filter_by_extrusion_role(extrusion_entities, role);
// Clone the extrusion entities.
for (auto &ptr : out.entities)
ptr = ptr->clone();
chain_and_reorder_extrusion_entities(out.entities, &start_near);
return out;
}
void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
for (const ExtrusionEntity *entity : this->entities)

View File

@ -7,6 +7,7 @@
namespace Slic3r {
#if 0
// Remove those items from extrusion_entities, that do not match role.
// Do nothing if role is mixed.
// Removed elements are NOT being deleted.
@ -21,6 +22,7 @@ inline ExtrusionEntitiesPtr filter_by_extrusion_role(const ExtrusionEntitiesPtr
filter_by_extrusion_role_in_place(out, role);
return out;
}
#endif
class ExtrusionEntityCollection : public ExtrusionEntity
{
@ -96,9 +98,6 @@ public:
}
void replace(size_t i, const ExtrusionEntity &entity);
void remove(size_t i);
static ExtrusionEntityCollection chained_path_from(const ExtrusionEntitiesPtr &extrusion_entities, const Point &start_near, ExtrusionRole role = ExtrusionRole::Mixed);
ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = ExtrusionRole::Mixed) const
{ return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
void reverse() override;
const Point& first_point() const override { return this->entities.front()->first_point(); }
const Point& last_point() const override { return this->entities.back()->last_point(); }

View File

@ -957,9 +957,9 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const
polyline.reserve(path.polyline.points.size());
float scalex = float(viewport.size().x()) / float(bbox.size().x());
float scaley = float(viewport.size().y()) / float(bbox.size().y());
float w = scale_(path.width) * scalex;
float w = scale_(path.width()) * scalex;
//float h = scale_(path.height) * scalex;
w = scale_(path.mm3_per_mm / path.height) * scalex;
w = scale_(path.mm3_per_mm() / path.height()) * scalex;
// printf("scalex: %f, scaley: %f\n", scalex, scaley);
// printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y);
for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) {

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -5,18 +5,22 @@
#include "JumpPointSearch.hpp"
#include "libslic3r.h"
#include "ExPolygon.hpp"
#include "GCodeWriter.hpp"
#include "Layer.hpp"
#include "Point.hpp"
#include "PlaceholderParser.hpp"
#include "PrintConfig.hpp"
#include "Geometry/ArcWelder.hpp"
#include "GCode/AvoidCrossingPerimeters.hpp"
#include "GCode/CoolingBuffer.hpp"
#include "GCode/FindReplace.hpp"
#include "GCode/GCodeWriter.hpp"
#include "GCode/PressureEqualizer.hpp"
#include "GCode/RetractWhenCrossingPerimeters.hpp"
#include "GCode/SmoothPath.hpp"
#include "GCode/SpiralVase.hpp"
#include "GCode/ToolOrdering.hpp"
#include "GCode/WipeTower.hpp"
#include "GCode/Wipe.hpp"
#include "GCode/WipeTowerIntegration.hpp"
#include "GCode/SeamPlacer.hpp"
#include "GCode/GCodeProcessor.hpp"
#include "EdgeGrid.hpp"
@ -26,12 +30,10 @@
#include <map>
#include <string>
#include "GCode/PressureEqualizer.hpp"
namespace Slic3r {
// Forward declarations.
class GCode;
class GCodeGenerator;
namespace { struct Item; }
struct PrintInstance;
@ -41,71 +43,11 @@ public:
bool enable;
OozePrevention() : enable(false) {}
std::string pre_toolchange(GCode &gcodegen);
std::string post_toolchange(GCode &gcodegen);
std::string pre_toolchange(GCodeGenerator &gcodegen);
std::string post_toolchange(GCodeGenerator &gcodegen);
private:
int _get_temp(const GCode &gcodegen) const;
};
class Wipe {
public:
bool enable;
Polyline path;
Wipe() : enable(false) {}
bool has_path() const { return ! this->path.empty(); }
void reset_path() { this->path.clear(); }
std::string wipe(GCode &gcodegen, bool toolchange);
};
class WipeTowerIntegration {
public:
WipeTowerIntegration(
const PrintConfig &print_config,
const std::vector<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;
int _get_temp(const GCodeGenerator &gcodegen) const;
};
class ColorPrintColors
@ -129,9 +71,9 @@ struct LayerResult {
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
};
class GCode {
class GCodeGenerator {
public:
GCode() :
GCodeGenerator() :
m_origin(Vec2d::Zero()),
m_enable_loop_clipping(true),
m_enable_cooling_markers(false),
@ -153,7 +95,7 @@ public:
m_silent_time_estimator_enabled(false),
m_last_obj_copy(nullptr, Point(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max()))
{}
~GCode() = default;
~GCodeGenerator() = default;
// throws std::runtime_exception on error,
// throws CanceledException through print->throw_if_canceled().
@ -165,9 +107,19 @@ public:
void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); }
const Point& last_pos() const { return m_last_pos; }
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset.
Vec2d point_to_gcode(const Point &point) const;
template<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.
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;
const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; }
@ -250,6 +202,7 @@ private:
// Set of object & print layers of the same PrintObject and with the same print_z.
const ObjectsLayerToPrint &layers,
const LayerTools &layer_tools,
const GCode::SmoothPathCache *smooth_path_cache,
const bool last_layer,
// Pairs of PrintObject index and its instance index.
const std::vector<const PrintInstance*> *ordering,
@ -280,10 +233,10 @@ private:
void set_extruders(const std::vector<unsigned int> &extruder_ids);
std::string preamble();
std::string change_layer(coordf_t print_z);
std::string extrude_entity(const ExtrusionEntity &entity, const std::string_view description, double speed = -1.);
std::string extrude_loop(ExtrusionLoop loop, const std::string_view description, double speed = -1.);
std::string extrude_multi_path(ExtrusionMultiPath multipath, const std::string_view description, double speed = -1.);
std::string extrude_path(ExtrusionPath path, const std::string_view description, double speed = -1.);
std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed = -1.);
std::string extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache *smooth_path_cache, const std::string_view description, double speed = -1.);
struct InstanceToPrint
{
@ -317,12 +270,14 @@ private:
const ObjectLayerToPrint &layer_to_print,
// Container for extruder overrides (when wiping into object or infill).
const LayerTools &layer_tools,
// Optional smooth path interpolating extrusion polylines.
const GCode::SmoothPathCache *smooth_path_cache,
// Is any extrusion possibly marked as wiping extrusion?
const bool is_anything_overridden,
// Round 1 (wiping into object or infill) or round 2 (normal extrusions).
const bool print_wipe_extrusions);
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache *smooth_path_cache);
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
@ -375,7 +330,7 @@ private:
} m_placeholder_parser_integration;
OozePrevention m_ooze_prevention;
Wipe m_wipe;
GCode::Wipe m_wipe;
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
JPSPathFinder m_avoid_crossing_curled_overhangs;
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
@ -418,7 +373,7 @@ private:
std::unique_ptr<SpiralVase> m_spiral_vase;
std::unique_ptr<GCodeFindReplace> m_find_replace;
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.
std::vector<coordf_t> m_skirt_done;
@ -434,7 +389,8 @@ private:
// Processor
GCodeProcessor m_processor;
std::string _extrude(const ExtrusionPath &path, const std::string_view description, double speed = -1);
std::string _extrude(
const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, const std::string_view description, double speed = -1);
void print_machine_envelope(GCodeOutputStream &file, Print &print);
void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
@ -443,8 +399,12 @@ private:
// To control print speed of 1st object layer over raft interface.
bool object_layer_over_raft() const { return m_object_layer_over_raft; }
friend class Wipe;
friend class WipeTowerIntegration;
// Fill in cache of smooth paths for perimeters, fills and supports of the given object layers.
// Based on params, the paths are either decimated to sparser polylines, or interpolated with circular arches.
static void smooth_path_interpolate(const ObjectLayerToPrint &layers, const GCode::SmoothPathCache::InterpolationParameters &params, GCode::SmoothPathCache &out);
friend class GCode::Wipe;
friend class GCode::WipeTowerIntegration;
friend class PressureEqualizer;
};

View File

@ -730,7 +730,7 @@ static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vec
return false;
}
static bool need_wipe(const GCode &gcodegen,
static bool need_wipe(const GCodeGenerator &gcodegen,
const ExPolygons &lslices_offset,
const std::vector<BoundingBox> &lslices_offset_bboxes,
const EdgeGrid::Grid &grid_lslices_offset,
@ -1167,7 +1167,7 @@ static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons
}
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled)
{
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object.
@ -1470,7 +1470,7 @@ static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary
}
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled)
{
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object.

View File

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

View File

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

View File

@ -19,7 +19,7 @@
namespace Slic3r {
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
CoolingBuffer::CoolingBuffer(GCodeGenerator &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
{
this->reset(gcodegen.writer().get_position());

View File

@ -7,7 +7,7 @@
namespace Slic3r {
class GCode;
class GCodeGenerator;
class Layer;
struct PerExtruderAdjustments;
@ -22,7 +22,7 @@ struct PerExtruderAdjustments;
//
class CoolingBuffer {
public:
CoolingBuffer(GCode &gcodegen);
CoolingBuffer(GCodeGenerator &gcodegen);
void reset(const Vec3d &position);
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
@ -51,7 +51,7 @@ private:
// Highest of m_extruder_ids plus 1.
unsigned int m_num_extruders { 0 };
const std::string m_toolchange_prefix;
// Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
// Referencs GCodeGenerator::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
// the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
const PrintConfig &m_config;
unsigned int m_current_extruder;

View File

@ -114,7 +114,7 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
}
new_points.push_back(next);
}
points = new_points;
points = std::move(new_points);
}
if (max_line_length > 0) {
@ -140,7 +140,7 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
}
new_points.push_back(points.back());
}
points = new_points;
points = std::move(new_points);
}
std::vector<float> angles_for_curvature(points.size());
@ -241,7 +241,8 @@ public:
}
std::vector<ProcessedPoint> estimate_speed_from_extrusion_quality(
const ExtrusionPath &path,
const Points &path,
const ExtrusionFlow &flow,
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,
@ -251,7 +252,7 @@ public:
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 distance = flow.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;
@ -260,13 +261,13 @@ public:
std::map<float, float> fan_speed_sections;
for (size_t i = 0; i < overhangs_w_fan_speeds.size(); i++) {
float distance = path.width * (1.0 - (overhangs_w_fan_speeds[i].first / 100.0));
float distance = flow.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);
estimate_points_properties<true, true, true, true>(path, prev_layer_boundaries[current_object], flow.width);
std::vector<ProcessedPoint> processed_points;
processed_points.reserve(extended_points.size());
@ -276,7 +277,7 @@ public:
// 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;
const double dist_limit = 10.0 * flow.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));
@ -314,9 +315,9 @@ public:
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)) *
float dist = flow.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
(line.curled_height / (flow.height * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
artificial_distance_to_curled_lines = std::max(artificial_distance_to_curled_lines, dist);
}
}

View File

@ -4,7 +4,7 @@
#include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/I18N.hpp"
#include "libslic3r/GCodeWriter.hpp"
#include "libslic3r/GCode/GCodeWriter.hpp"
#include "libslic3r/I18N.hpp"
#include "GCodeProcessor.hpp"

View File

@ -1,10 +1,12 @@
#include "GCodeWriter.hpp"
#include "CustomGCode.hpp"
#include "../CustomGCode.hpp"
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <assert.h>
#include <string_view>
#ifdef __APPLE__
#include <boost/spirit/include/karma.hpp>
@ -13,6 +15,8 @@
#define FLAVOR_IS(val) this->config.gcode_flavor == val
#define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val
using namespace std::string_view_literals;
namespace Slic3r {
// static
@ -90,17 +94,17 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)))
return {};
std::string code, comment;
std::string_view code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) {
code = "M109";
comment = "set temperature and wait for it to be reached";
code = "M109"sv;
comment = "set temperature and wait for it to be reached"sv;
} else {
if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware
code = "G10";
code = "G10"sv;
} else {
code = "M104";
code = "M104"sv;
}
comment = "set temperature";
comment = "set temperature"sv;
}
std::ostringstream gcode;
@ -130,22 +134,22 @@ std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, in
std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait)
{
if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached))
return std::string();
return {};
m_last_bed_temperature = temperature;
m_last_bed_temperature_reached = wait;
std::string code, comment;
std::string_view code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
code = "M109";
code = "M109"sv;
} else {
code = "M190";
code = "M190"sv;
}
comment = "set bed temperature and wait for it to be reached";
comment = "set bed temperature and wait for it to be reached"sv;
} else {
code = "M140";
comment = "set bed temperature";
code = "M140"sv;
comment = "set bed temperature"sv;
}
std::ostringstream gcode;
@ -176,7 +180,7 @@ std::string GCodeWriter::set_acceleration_internal(Acceleration type, unsigned i
auto& last_value = separate_travel ? m_last_travel_acceleration : m_last_acceleration ;
if (acceleration == 0 || acceleration == last_value)
return std::string();
return {};
last_value = acceleration;
@ -245,7 +249,7 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id)
return gcode.str();
}
std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const
std::string GCodeWriter::set_speed(double F, const std::string_view comment, const std::string_view cooling_marker) const
{
assert(F > 0.);
assert(F < 100000.);
@ -257,10 +261,9 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
return w.string();
}
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment)
{
m_pos.x() = point.x();
m_pos.y() = point.y();
m_pos.head<2>() = point.head<2>();
GCodeG1Formatter w;
w.emit_xy(point);
@ -269,7 +272,29 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com
return w.string();
}
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment)
std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment)
{
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)
{
m_pos.head<2>() = point.head<2>();
GCodeG2G3Formatter w(ccw);
w.emit_xy(point);
w.emit_radius(radius);
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
}
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment)
{
// FIXME: This function was not being used when travel_speed_z was separated (bd6badf).
// Calculation of feedrate was not updated accordingly. If you want to use
@ -302,7 +327,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
return w.string();
}
std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
std::string GCodeWriter::travel_to_z(double z, const std::string_view comment)
{
/* If target Z is lower than current Z but higher than nominal Z
we don't perform the move but we only adjust the nominal Z by
@ -321,7 +346,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
return this->_travel_to_z(z, comment);
}
std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
std::string GCodeWriter::_travel_to_z(double z, const std::string_view comment)
{
m_pos.z() = z;
@ -348,10 +373,9 @@ bool GCodeWriter::will_move_z(double z) const
return true;
}
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment)
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment)
{
m_pos.x() = point.x();
m_pos.y() = point.y();
m_pos.head<2>() = point.head<2>();
GCodeG1Formatter w;
w.emit_xy(point);
@ -360,8 +384,34 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
return w.string();
}
std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, double dE, const bool ccw, const std::string_view comment)
{
assert(dE > 0);
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, double dE, const bool ccw, const std::string_view comment)
{
assert(dE > 0);
m_pos.head<2>() = point.head<2>();
GCodeG2G3Formatter w(ccw);
w.emit_xy(point);
w.emit_radius(radius);
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
}
#if 0
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment)
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment)
{
m_pos = point;
m_lifted = 0;
@ -397,7 +447,7 @@ 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)
{
/* If firmware retraction is enabled, we use a fake value of 1
since we ignore the actual configured retract_length which

View File

@ -1,13 +1,15 @@
#ifndef slic3r_GCodeWriter_hpp_
#define slic3r_GCodeWriter_hpp_
#include "libslic3r.h"
#include "../libslic3r.h"
#include "../Extruder.hpp"
#include "../Point.hpp"
#include "../PrintConfig.hpp"
#include "CoolingBuffer.hpp"
#include <string>
#include <string_view>
#include <charconv>
#include "Extruder.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "GCode/CoolingBuffer.hpp"
namespace Slic3r {
@ -56,13 +58,17 @@ public:
// printed with the same extruder.
std::string toolchange_prefix() const;
std::string toolchange(unsigned int extruder_id);
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const;
std::string travel_to_xy(const Vec2d &point, const std::string &comment = std::string());
std::string travel_to_xyz(const Vec3d &point, const std::string &comment = std::string());
std::string travel_to_z(double z, const std::string &comment = std::string());
std::string set_speed(double F, const std::string_view comment = {}, const std::string_view cooling_marker = {}) const;
std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {});
std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {});
std::string travel_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, const std::string_view comment = {});
std::string travel_to_xyz(const Vec3d &point, const std::string_view comment = {});
std::string travel_to_z(double z, const std::string_view comment = {});
bool will_move_z(double z) const;
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string());
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {});
std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, double dE, const bool ccw, const std::string_view comment);
std::string extrude_to_xy_G2G3R(const Vec2d &point, const double radius, double dE, const bool ccw, const std::string_view comment);
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {});
std::string retract(bool before_wipe = false);
std::string retract_for_toolchange(bool before_wipe = false);
std::string unretract();
@ -113,8 +119,8 @@ private:
Print
};
std::string _travel_to_z(double z, const std::string &comment);
std::string _retract(double length, double restart_extra, const std::string &comment);
std::string _travel_to_z(double z, const std::string_view comment);
std::string _retract(double length, double restart_extra, const std::string_view comment);
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);
};
@ -170,7 +176,18 @@ public:
this->emit_axis('Z', z, XYZF_EXPORT_DIGITS);
}
void emit_e(const std::string &axis, double v) {
void emit_ij(const Vec2d &point) {
this->emit_axis('I', point.x(), XYZF_EXPORT_DIGITS);
this->emit_axis('J', point.y(), XYZF_EXPORT_DIGITS);
}
// Positive radius means a smaller arc,
// negative radius means a larger arc.
void emit_radius(const double radius) {
this->emit_axis('R', radius, XYZF_EXPORT_DIGITS);
}
void emit_e(const std::string_view axis, double v) {
if (! axis.empty()) {
// not gcfNoExtrusion
this->emit_axis(axis[0], v, E_EXPORT_DIGITS);
@ -181,12 +198,12 @@ public:
this->emit_axis('F', speed, XYZF_EXPORT_DIGITS);
}
void emit_string(const std::string &s) {
strncpy(ptr_err.ptr, s.c_str(), s.size());
void emit_string(const std::string_view s) {
strncpy(ptr_err.ptr, s.data(), s.size());
ptr_err.ptr += s.size();
}
void emit_comment(bool allow_comments, const std::string &comment) {
void emit_comment(bool allow_comments, const std::string_view comment) {
if (allow_comments && ! comment.empty()) {
*ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' ';
this->emit_string(comment);
@ -210,14 +227,25 @@ public:
GCodeG1Formatter() {
this->buf[0] = 'G';
this->buf[1] = '1';
this->buf_end = buf + buflen;
this->ptr_err.ptr = this->buf + 2;
this->ptr_err.ptr += 2;
}
GCodeG1Formatter(const GCodeG1Formatter&) = delete;
GCodeG1Formatter& operator=(const GCodeG1Formatter&) = delete;
};
class GCodeG2G3Formatter : public GCodeFormatter {
public:
GCodeG2G3Formatter(bool ccw) {
this->buf[0] = 'G';
this->buf[1] = ccw ? '3' : '2';
this->ptr_err.ptr += 2;
}
GCodeG2G3Formatter(const GCodeG2G3Formatter&) = delete;
GCodeG2G3Formatter& operator=(const GCodeG2G3Formatter&) = delete;
};
} /* namespace Slic3r */
#endif /* slic3r_GCodeWriter_hpp_ */

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

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 {
using namespace SeamPlacerImpl;
const PrintObject *po = layer->object();
@ -1587,7 +1587,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern
//lastly, for internal perimeters, do the staggering if requested
if (po->config().staggered_inner_seams && loop.length() > 0.0) {
//fix depth, it is sometimes strongly underestimated
depth = std::max(loop.paths[projected_point.path_idx].width, depth);
depth = std::max(loop.paths[projected_point.path_idx].width(), depth);
while (depth > 0.0f) {
auto next_point = get_next_loop_point(projected_point);
@ -1605,14 +1605,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern
}
}
// Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns,
// thus empty path segments will not be produced by G-code export.
if (!loop.split_at_vertex(seam_point, scaled<double>(0.0015))) {
// The point is not in the original loop.
// Insert it.
loop.split_at(seam_point, true);
}
return seam_point;
}
} // namespace Slic3r

View File

@ -141,7 +141,7 @@ public:
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:
void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info);

View File

@ -0,0 +1,258 @@
#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) {
auto it = el.path.begin();
auto end = el.path.end();
Point prev_point = it->point;
for (++ it; it != end; ++ it) {
Point point = it->point;
l += it->linear() ?
(point - prev_point).cast<double>().norm() :
Geometry::ArcWelder::arc_length(prev_point.cast<float>(), point.cast<float>(), it->radius);
prev_point = point;
}
}
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) {
auto it = el.path.begin();
auto end = el.path.end();
Point prev_point = it->point;
for (++it; it != end; ++it) {
Point point = it->point;
length -= it->linear() ?
(point - prev_point).cast<double>().norm() :
Geometry::ArcWelder::arc_length(prev_point.cast<float>(), point.cast<float>(), it->radius);
if (length < 0)
return true;
prev_point = point;
}
}
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 {
assert(distance == 0);
return 0;
}
}
return distance;
}
void SmoothPathCache::interpolate_add(const Polyline &polyline, const InterpolationParameters &params)
{
m_cache[&polyline] = Slic3r::Geometry::ArcWelder::fit_path(polyline.points, params.tolerance, params.fit_circle_tolerance);
}
void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters &params)
{
this->interpolate_add(path.polyline, params);
}
void SmoothPathCache::interpolate_add(const ExtrusionMultiPath &multi_path, const InterpolationParameters &params)
{
for (const ExtrusionPath &path : multi_path.paths)
this->interpolate_add(path.polyline, params);
}
void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters &params)
{
for (const ExtrusionPath &path : loop.paths)
this->interpolate_add(path.polyline, 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()) {
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.distance2 < proj.distance2) {
// Found a better (closer) projection.
proj = this_proj;
proj_path = &el - out.data();
}
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());
out.back().path = std::move(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,70 @@
#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 Polyline &pl, const InterpolationParameters &params);
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;
};
} // namespace GCode
} // namespace Slic3r
#endif // slic3r_GCode_SmoothPath_hpp_

View File

@ -0,0 +1,218 @@
#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();
// 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, Vec2d &p) {
double segment_length = (p - prev).norm();
// Quantize E axis as it is to be
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 = gcodegen.point_to_gcode_quantized(prev + (p - prev) * (retract_length / dE));
dE = retract_length;
done = true;
}
gcode += gcodegen.writer().extrude_to_xy(p, -dE, "wipe and retract"sv);
retract_length -= dE;
return done;
};
auto wipe_arc = [&gcode, &gcodegen, &retract_length, xy_to_e](const Vec2d &prev, Vec2d &p, float radius_in, const bool ccw) {
double radius = GCodeFormatter::quantize_xyzf(radius_in);
Vec2f center = Geometry::ArcWelder::arc_center(prev.cast<float>(), p.cast<float>(), float(radius), ccw);
float angle = Geometry::ArcWelder::arc_angle(prev.cast<float>(), p.cast<float>(), float(radius));
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.
p = gcodegen.point_to_gcode_quantized(Point(prev).rotated((ccw ? angle : -angle) * (retract_length / dE), center.cast<coord_t>()));
dE = retract_length;
done = true;
}
gcode += gcodegen.writer().extrude_to_xy_G2G3R(p, radius, ccw, -dE, "wipe and retract"sv);
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_quantized(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_quantized(it->point + m_offset);
if (p != prev) {
start_wipe();
if (it->linear() ?
wipe_linear(prev, p) :
wipe_arc(prev, p, it->radius, it->ccw()))
break;
prev = p;
}
}
}
if (wiped) {
// add tag for processor
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,72 @@
#ifndef slic3r_GCode_Wipe_hpp_
#define slic3r_GCode_Wipe_hpp_
#include "Geometry/ArcWelder.hpp"
#include "SmoothPath.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_
#define WipeTower_
#ifndef slic3r_GCode_WipeTower_hpp_
#define slic3r_GCode_WipeTower_hpp_
#include <cmath>
#include <string>
@ -403,4 +403,4 @@ private:
} // namespace Slic3r
#endif // WipeTowerPrusaMM_hpp_
#endif // slic3r_GCode_WipeTower_hpp_

View File

@ -0,0 +1,247 @@
#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);
if (tcr.force_travel || ! needs_toolchange || (gcodegen.config().single_extruder_multi_material && ! tcr.priming)) {
// Move over the wipe tower. If this is not single-extruder MM, the first wipe tower move following the
// toolchange will travel there anyway (if there is a toolchange).
// FIXME: It would be better if the wipe tower set the force_travel flag for all toolchanges,
// then we could simplify the condition and make it more readable.
gcode += gcodegen.retract();
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, start_pos),
ExtrusionRole::Mixed,
"Travel to a Wipe Tower");
gcode += gcodegen.unretract();
}
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 (gcodegen.config().single_extruder_multi_material)
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,543 @@
// 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 "../MultiPoint.hpp"
#include "../Polygon.hpp"
#include <numeric>
#include <random>
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace Geometry { namespace ArcWelder {
// Area of a parallelogram of two vectors to be considered collinear.
static constexpr const double Parallel_area_threshold = 0.0001;
static constexpr const auto Parallel_area_threshold_scaled = int64_t(Parallel_area_threshold / sqr(SCALING_FACTOR));
// FIXME do we want to use EPSILON here?
static constexpr const double epsilon = 0.000005;
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.
//FIXME unit test!
static std::optional<Circle> try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius)
{
// Use area of triangle to judge whether three points are considered collinear.
Vec2i64 v2 = (p2 - p1).cast<int64_t>();
Vec2i64 v3 = (p3 - p2).cast<int64_t>();
if (std::abs(cross2(v2, v3)) <= Parallel_area_threshold_scaled)
return {};
int64_t det = cross2(p2.cast<int64_t>(), p3.cast<int64_t>()) - cross2(p1.cast<int64_t>(), v3);
if (std::abs(det) < int64_t(SCALED_EPSILON))
return {};
Point center = ((1. / 2.0 * double(det)) *
(double(p1.cast<int64_t>().squaredNorm()) * perp(v3).cast<double>() +
double(p2.cast<int64_t>().squaredNorm()) * perp(p1 - p3).cast<double>() +
double(p3.cast<int64_t>().squaredNorm()) * perp(v2).cast<double>())).cast<coord_t>();
double r = sqrt(double((center - p1).squaredNorm()));
return r > max_radius ? std::make_optional<Circle>() : std::make_optional<Circle>({ center, r });
}
// 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 &c, Point &out)
{
Vec2i64 v21 = (p2 - p1).cast<int64_t>();
int64_t denom = v21.squaredNorm();
if (denom > epsilon) {
if (double t = double((c - p1).cast<int64_t>().dot(v21)) / double(denom);
t >= epsilon && t < 1. - epsilon) {
out = p1 + (t * 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) < epsilon);
assert(std::abs((*std::prev(end) - circle.center).cast<double>().norm() - circle.radius) < epsilon);
assert(end - begin >= 3);
for (auto it = begin; std::next(it) != end; ++ it) {
if (it != begin) {
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(*it, *std::next(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) < epsilon);
assert(std::abs((*std::prev(end) - circle.center).cast<double>().norm() - circle.radius) < 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 (! circle_approximation_sufficient(*out, begin, end, tolerance))
out.reset();
} else {
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_approximation_sufficient(*circle, begin, end, tolerance))
return circle;
// Find the circle with the least deviation, if one exists.
double least_deviation;
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 (! out || current_deviation < least_deviation) {
out = circle;
least_deviation = current_deviation;
}
}
}
return out;
}
// ported from ArcWelderLib/ArcWelder/segmented/shape.h class "arc"
class Arc {
public:
Arc() {}
#if 0
Arc(Point center, double radius, Point start, Point end, Orientation dir) :
center(center),
radius(radius),
start_point(start),
end_point(end),
direction(dir) {
if (radius == 0.0 ||
start_point == center ||
end_point == center ||
start_point == end_point) {
is_arc = false;
return;
}
is_arc = true;
}
#endif
Point center;
double radius { 0 };
bool is_arc { false };
Point start_point{ 0, 0 };
Point end_point{ 0, 0 };
Orientation direction { Orientation::Unknown };
static 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);
bool is_valid() const { return is_arc; }
};
static inline int sign(const int64_t i)
{
return i > 0 ? 1 : i < 0 ? -1 : 0;
}
static inline std::optional<Arc> try_create_arc_impl(
const Circle &circle,
const Points::const_iterator begin,
const Points::const_iterator end,
double path_tolerance_percent)
{
assert(end - begin >= 3);
// Assumption: Two successive points of a single segment span an angle smaller than PI.
Vec2i64 vstart = (*begin - circle.center).cast<int64_t>();
Vec2i64 vprev = vstart;
int arc_dir = 0;
for (auto it = std::next(begin); it != end; ++ it) {
Vec2i64 v = (*it - circle.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 not cover it.
return {};
} else {
// Success, moving in the same direction.
arc_dir = dir;
vprev = v;
}
}
if (arc_dir == 0)
// All points were radial, this should not happen.
return {};
Vec2i64 vend = (*std::prev(end) - circle.center).cast<int64_t>();
double angle = atan2(double(cross2(vstart, vend)), double(vstart.dot(vend)));
if (arc_dir > 0) {
if (angle < 0)
angle += 2. * M_PI;
} else {
if (angle > 0)
angle -= 2. * M_PI;
}
// 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 = std::abs(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 {};
Arc out;
out.is_arc = true;
out.direction = arc_dir > 0 ? Orientation::CCW : Orientation::CW;
out.center = circle.center;
out.radius = circle.radius;
out.start_point = *begin;
out.end_point = *std::prev(end);
return std::make_optional<Arc>(out);
}
std::optional<Arc> Arc::try_create_arc(
const Points::const_iterator begin,
const Points::const_iterator end,
double max_radius,
double tolerance,
double path_tolerance_percent)
{
std::optional<Circle> circle = try_create_circle(begin, end, max_radius, tolerance);
if (! circle)
return {};
return try_create_arc_impl(*circle, begin, end, 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.
int begin_pl_idx = 0;
for (auto begin = src.begin(); begin < src.end();) {
// Minimum 3 points required for circle fitting.
auto end = begin + 3;
std::optional<Arc> arc;
while (end <= src.end()) {
if (std::optional<Arc> this_arc = ArcWelder::Arc::try_create_arc(
begin, end,
ArcWelder::default_scaled_max_radius,
tolerance, fit_circle_percent_tolerance);
this_arc) {
arc = this_arc;
++ 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)
out.erase(douglas_peucker_in_place(out.begin() + begin_pl_idx, out.end(), tolerance), out.end());
// Save the end of the last circle segment, which may become the first point of a possible future polyline.
begin_pl_idx = int(out.size());
-- end;
out.push_back({ arc->end_point, float(arc->direction == Orientation::CCW ? arc->radius : - arc->radius) });
} else
out.push_back({ arc->end_point, 0.f });
}
}
return out;
}
void reverse(Path &path)
{
if (path.size() > 1) {
std::reverse(path.begin(), path.end());
auto prev = path.begin();
for (auto it = std::next(prev); it != path.end(); ++ it) {
it->radius = prev->radius;
it->orientation = prev->orientation == Orientation::CCW ? Orientation::CW : Orientation::CCW;
prev = it;
}
path.front().radius = 0;
}
}
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>(), 0.f, Orientation::CCW });
return 0;
}
distance -= sqrt(lsqr);
} else {
// Circular segment
float angle = arc_angle(path.back().point.cast<float>(), last.point.cast<float>(), last.radius);
double len = std::abs(last.radius) * angle;
if (len > distance) {
// Rotate the segment end point in reverse towards the start point.
path.push_back({
last.point.rotated(- angle * (distance / len),
arc_center(path.back().point.cast<float>(), last.point.cast<float>(), last.radius, last.ccw()).cast<coord_t>()),
last.radius, last.orientation });
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);
PathSegmentProjection out;
out.distance2 = search_radius2;
if (path.size() < 2 || path.front().point == point) {
// First point is the closest point.
if (path.empty()) {
} 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 {
auto min_point_it = path.cbegin();
Point prev = path.front().point;
for (auto it = path.cbegin() + 1; it != path.cend(); ++ it) {
if (it->linear()) {
// Linear segment
Point proj;
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<float>(), it->point.cast<float>(), 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;
bool inside = it->radius > 0 ?
// Smaller (convex) wedge.
(it->ccw() ?
cross2(v1, vp) > 0 && cross2(vp, v2) > 0 :
cross2(v1, vp) < 0 && cross2(vp, v2) < 0) :
// Larger (concave) wedge.
(it->ccw() ?
cross2(v2, vp) < 0 || cross2(vp, v1) < 0 :
cross2(v2, vp) > 0 || cross2(vp, v1) > 0);
if (inside) {
// Distance of the radii.
if (double d2 = sqr(std::abs(it->radius) - sqrt(double(v1.squaredNorm()))); d2 < out.distance2) {
out.distance2 = d2;
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>().norm(); d2 < out.distance2) {
out.point = path.back().point;
out.distance2 = d2;
min_point_it = std::prev(path.end());
}
}
out.segment_id = min_point_it - path.begin();
}
return out;
}
std::pair<Path, Path> split_at(const Path &path, PathSegmentProjection proj, const double min_segment_length)
{
std::pair<Path, Path> out;
if (proj.segment_id == 0 && proj.point == path.front().point)
out.second = path;
else if (proj.segment_id + 1 == path.size() || (proj.segment_id + 2 == path.size() && proj.point == path.back().point))
out.first = path;
else {
const Segment &start = path[proj.segment_id];
const Segment &end = path[proj.segment_id + 1];
bool split_segment = true;
if (int64_t d = (proj.point - start.point).cast<int64_t>().squaredNorm(); d < sqr(min_segment_length)) {
split_segment = false;
} else if (int64_t d = (proj.point - end.point).cast<int64_t>().squaredNorm(); d < sqr(min_segment_length)) {
++ proj.segment_id;
split_segment = false;
}
if (split_segment) {
out.first.assign(path.begin(), path.begin() + proj.segment_id + 2);
out.second.assign(path.begin() + proj.segment_id, path.end());
out.first.back().point = proj.point;
out.second.front().point = proj.point;
} else {
out.first.assign(path.begin(), path.begin() + proj.segment_id + 1);
out.second.assign(path.begin() + proj.segment_id, path.end());
}
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,196 @@
// 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
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#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>
inline Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> arc_center(
const Eigen::MatrixBase<Derived> &start_pos,
const Eigen::MatrixBase<Derived2> &end_pos,
const typename Derived::Scalar 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.");
assert(radius != 0);
using Float = typename Derived::Scalar;
using Vector = Eigen::Matrix<Float, 2, 1, Eigen::DontAlign>;
auto v = end_pos - start_pos;
Float q2 = v.squaredNorm();
assert(q2 > 0);
Float t = sqrt(sqr(radius) / q2 - Float(.25f));
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(2.) * asin(Float(0.5) * (end_pos - start_pos).norm() / radius);
return radius > Float(0) ? a : Float(2. * M_PI) + 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);
}
// 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,
};
// 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; }
};
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 fit_path(const Points &points, double tolerance, double fit_circle_percent_tolerance);
inline Path fit_polyline(const Points &points, double tolerance) { return fit_path(points, tolerance, 0.); }
inline double segment_length(const Segment &start, const Segment &end)
{
return end.linear() ?
(end.point - start.point).cast<double>().norm() :
arc_length(start.point.cast<float>(), end.point.cast<float>(), end.radius);
}
// 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 - 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() };
Point point { 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, 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

@ -65,7 +65,7 @@ struct Circle {
Vector center;
Scalar radius;
Circle() {}
Circle() = default;
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, const Vector &c, const Scalar epsilon) { *this = CircleSq(a, b, c, epsilon); }

View File

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

View File

@ -103,100 +103,6 @@ bool MultiPoint::remove_duplicate_points()
return false;
}
Points MultiPoint::douglas_peucker(const Points &pts, const double tolerance)
{
Points result_pts;
auto tolerance_sq = int64_t(sqr(tolerance));
if (! pts.empty()) {
const Point *anchor = &pts.front();
size_t anchor_idx = 0;
const Point *floater = &pts.back();
size_t floater_idx = pts.size() - 1;
result_pts.reserve(pts.size());
result_pts.emplace_back(*anchor);
if (anchor_idx != floater_idx) {
assert(pts.size() > 1);
std::vector<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
// thanks to @fuchstraumer
/*
@ -219,7 +125,7 @@ struct vis_node{
// other node if it's area is less than the other node's area
bool operator<(const vis_node& other) { return (this->area < other.area); }
};
Points MultiPoint::visivalingam(const Points& pts, const double& tolerance)
Points MultiPoint::visivalingam(const Points &pts, const double tolerance)
{
// Make sure there's enough points in "pts" to bother with simplification.
assert(pts.size() >= 2);

View File

@ -12,6 +12,123 @@ namespace Slic3r {
class BoundingBox;
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 Point = typename InputIterator::value_type;
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).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).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.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).cast<SquareLengthType>().squaredNorm();
} else if (double dt = double(t) / dl2; dt <= 0) {
dist_sq = va.squaredNorm();
} else if (dt >= 1.) {
dist_sq = (p - f).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
{
public:
@ -81,8 +198,8 @@ public:
}
}
static Points douglas_peucker(const Points &points, const double tolerance);
static Points visivalingam(const Points& pts, const double& tolerance);
static Points douglas_peucker(const Points &src, const double tolerance) { return Slic3r::douglas_peucker(src, tolerance); }
static Points visivalingam(const Points &src, const double tolerance);
inline auto begin() { return points.begin(); }
inline auto begin() const { return points.begin(); }
@ -113,16 +230,20 @@ extern BoundingBox get_extents(const MultiPoint &mp);
extern BoundingBox get_extents_rotated(const Points &points, double angle);
extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle);
inline double length(const Points &pts) {
inline double length(const Points::const_iterator begin, const Points::const_iterator end) {
double total = 0;
if (! pts.empty()) {
auto it = pts.begin();
for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev)
if (begin != end) {
auto it = begin;
for (auto it_prev = it ++; it != end; ++ it, ++ it_prev)
total += (*it - *it_prev).cast<double>().norm();
}
return total;
}
inline double length(const Points &pts) {
return length(pts.begin(), pts.end());
}
inline double area(const Points &polygon) {
double area = 0.;
for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++)

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

View File

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

View File

@ -166,11 +166,12 @@ public:
Point(const Point &rhs) { *this = rhs; }
explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {}
// This constructor allows you to construct Point from Eigen expressions
// This constructor has to be implicit (non-explicit) to allow implicit conversion from Eigen expressions.
template<typename OtherDerived>
Point(const Eigen::MatrixBase<OtherDerived> &other) : Vec2crd(other) {}
static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); }
static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); }
template<typename OtherDerived>
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
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) {
//FIXME why is p1 NOT empty as in the case above?
*p1 = { point };
*p2 = *this;
return;
}
auto min_dist2 = std::numeric_limits<double>::max();

View File

@ -997,7 +997,7 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
this->set_status(90, message);
// Create GCode on heap, it has quite a lot of data.
std::unique_ptr<GCode> gcode(new GCode);
std::unique_ptr<GCodeGenerator> gcode(new GCodeGenerator);
gcode->do_export(this, path.c_str(), result, thumbnail_cb);
if (m_conflict_result.has_value())
@ -1113,13 +1113,15 @@ void Print::_make_skirt()
}
// Extrude the skirt loop.
ExtrusionLoop eloop(elrSkirt);
eloop.paths.emplace_back(ExtrusionPath(
ExtrusionPath(
eloop.paths.emplace_back(
ExtrusionAttributes{
ExtrusionRole::Skirt,
(float)mm3_per_mm, // this will be overridden at G-code export time
flow.width(),
(float)first_layer_height // this will be overridden at G-code export time
)));
ExtrusionFlow{
float(mm3_per_mm), // this will be overridden at G-code export time
flow.width(),
float(first_layer_height) // this will be overridden at G-code export time
}
});
eloop.paths.back().polyline = loop.split_at_first_point();
m_skirt.append(eloop);
if (m_config.min_skirt_length.value > 0) {
@ -1625,8 +1627,8 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co
}
ExtrusionPath path(ExtrusionRole::WipeTower, 0.0, 0.0, lh);
path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner };
ExtrusionPath path({ minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner },
ExtrusionAttributes{ ExtrusionRole::WipeTower, ExtrusionFlow{ 0.0, 0.0, lh } });
paths.push_back({ path });
// We added the border, now add several parallel lines so we can detect an object that is fully inside the tower.

View File

@ -29,7 +29,7 @@
namespace Slic3r {
class GCode;
class GCodeGenerator;
class Layer;
class ModelObject;
class Print;
@ -697,7 +697,7 @@ private:
Polygons m_sequential_print_clearance_contours;
// To allow GCode to set the Print's GCodeExport step status.
friend class GCode;
friend class GCodeGenerator;
// To allow GCodeProcessor to emit warnings.
friend class GCodeProcessor;
// Allow PrintObject to access m_mutex and m_cancel_callback.

View File

@ -397,6 +397,22 @@ void PrintConfigDef::init_fff_params()
{
ConfigOptionDef* def;
def = this->add("arc_fitting", coBool);
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->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("arc_fitting_tolerance", coFloatOrPercent);
def->label = L("Arc fitting tolerance");
def->sidetext = L("mm or %");
def->tooltip = L("When using the arc_fitting option, allow the curve to deviate a cetain % from the collection of strait paths.\n"
"Can be a mm value or a percentage of the current extrusion width.");
def->mode = comAdvanced;
def->min = 0;
def->set_default_value(new ConfigOptionFloatOrPercent(5, true));
// Maximum extruder temperature, bumped to 1500 to support printing of glass.
const int max_temp = 1500;
def = this->add("avoid_crossing_curled_overhangs", coBool);

View File

@ -662,6 +662,8 @@ PRINT_CONFIG_CLASS_DEFINE(
PRINT_CONFIG_CLASS_DEFINE(
GCodeConfig,
((ConfigOptionBool, arc_fitting))
((ConfigOptionFloatOrPercent, arc_fitting_tolerance))
((ConfigOptionBool, autoemit_temperature_commands))
((ConfigOptionString, before_layer_gcode))
((ConfigOptionString, between_objects_gcode))

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);
}
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 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);
auto segment_end_point = [&entities, reversed](size_t idx, bool first_point) -> const Point& { return first_point == reversed ? entities[idx]->last_point() : entities[idx]->first_point(); };
auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); };
std::vector<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) {
ExtrusionEntity *ee = entities[segment.first];
if (ee->is_loop())
// Ignore reversals for loops, as the start point equals the end point.
segment.second = false;
else if (reversed)
// Input was already reversed.
segment.second = ! segment.second;
// Is can_reverse() respected by the reversals?
assert(ee->can_reverse() || ! segment.second);
}
@ -1041,6 +1045,33 @@ void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entitie
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)
{
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;
using ExPolygons = std::vector<ExPolygon>;
// Used by chain_expolygons()
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<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);
// Reorder & reverse extrusion entities in place.
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);
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);

View File

@ -485,8 +485,7 @@ static inline void fill_expolygon_generate_paths(
extrusion_entities_append_paths(
dst,
std::move(polylines),
role,
flow.mm3_per_mm(), flow.width(), flow.height());
{ role, flow });
}
static inline void fill_expolygons_generate_paths(
@ -644,7 +643,7 @@ static inline void tree_supports_generate_paths(
ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width());
if (level2.size() == 1) {
Polylines polylines;
extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(),
extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow },
// Disable reversal of the path, always start with the anchor, always print CCW.
false);
expoly = level2.front();
@ -750,7 +749,7 @@ static inline void tree_supports_generate_paths(
}
ExtrusionEntitiesPtr &out = eec ? eec->entities : dst;
extrusion_entities_append_paths(out, std::move(polylines), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(),
extrusion_entities_append_paths(out, std::move(polylines), { ExtrusionRole::SupportMaterial, flow },
// Disable reversal of the path, always start with the anchor, always print CCW.
false);
if (eec) {
@ -794,7 +793,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
eec->no_sort = true;
}
ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst;
extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), ExtrusionRole::SupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height());
extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow });
// Fill in the rest.
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
if (no_sort && ! eec->empty())
@ -1106,7 +1105,7 @@ void LoopInterfaceProcessor::generate(SupportGeneratorLayerExtruded &top_contact
extrusion_entities_append_paths(
top_contact_layer.extrusions,
std::move(loop_lines),
ExtrusionRole::SupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height());
{ ExtrusionRole::SupportMaterialInterface, flow });
}
#ifdef SLIC3R_DEBUG
@ -1148,24 +1147,19 @@ static void modulate_extrusion_by_overlapping_layers(
ExtrusionPath *extrusion_path_template = dynamic_cast<ExtrusionPath*>(extrusions_in_out.front());
assert(extrusion_path_template != nullptr);
ExtrusionRole extrusion_role = extrusion_path_template->role();
float extrusion_width = extrusion_path_template->width;
float extrusion_width = extrusion_path_template->width();
struct ExtrusionPathFragment
{
ExtrusionPathFragment() : mm3_per_mm(-1), width(-1), height(-1) {};
ExtrusionPathFragment(double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height) {};
ExtrusionFlow flow;
Polylines polylines;
double mm3_per_mm;
float width;
float height;
};
// Split the extrusions by the overlapping layers, reduce their extrusion rate.
// The last path_fragment is from this_layer.
std::vector<ExtrusionPathFragment> path_fragments(
n_overlapping_layers + 1,
ExtrusionPathFragment(extrusion_path_template->mm3_per_mm, extrusion_path_template->width, extrusion_path_template->height));
ExtrusionPathFragment{ extrusion_path_template->attributes() });
// Don't use it, it will be released.
extrusion_path_template = nullptr;
@ -1242,8 +1236,8 @@ static void modulate_extrusion_by_overlapping_layers(
path_fragments.back().polylines = diff_pl(path_fragments.back().polylines, polygons_trimming);
// Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter).
assert(this_layer.print_z > overlapping_layer.print_z);
frag.height = float(this_layer.print_z - overlapping_layer.print_z);
frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm();
frag.flow.height = float(this_layer.print_z - overlapping_layer.print_z);
frag.flow.mm3_per_mm = Flow(frag.flow.width, frag.flow.height, -1.f).mm3_per_mm();
#ifdef SLIC3R_DEBUG
svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1));
#endif /* SLIC3R_DEBUG */
@ -1326,15 +1320,14 @@ static void modulate_extrusion_by_overlapping_layers(
ExtrusionPath *path = multipath.paths.empty() ? nullptr : &multipath.paths.back();
if (path != nullptr) {
// Verify whether the path is compatible with the current fragment.
assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm);
if (path->height != frag.height || path->mm3_per_mm != frag.mm3_per_mm) {
assert(this_layer.layer_type == SupporLayerType::BottomContact || path->height() != frag.flow.height || path->mm3_per_mm() != frag.flow.mm3_per_mm);
if (path->height() != frag.flow.height || path->mm3_per_mm() != frag.flow.mm3_per_mm)
path = nullptr;
}
// Merging with the previous path. This can only happen if the current layer was reduced by a base layer, which was split into a base and interface layer.
}
if (path == nullptr) {
// Allocate a new path.
multipath.paths.push_back(ExtrusionPath(extrusion_role, frag.mm3_per_mm, frag.width, frag.height));
multipath.paths.emplace_back(ExtrusionAttributes{ extrusion_role, frag.flow });
path = &multipath.paths.back();
}
// The Clipper library may flip the order of the clipped polylines arbitrarily.
@ -1369,8 +1362,8 @@ static void modulate_extrusion_by_overlapping_layers(
}
// If there are any non-consumed fragments, add them separately.
//FIXME this shall not happen, if the Clipper works as expected and all paths split to fragments could be re-connected.
for (auto it_fragment = path_fragments.begin(); it_fragment != path_fragments.end(); ++ it_fragment)
extrusion_entities_append_paths(extrusions_in_out, std::move(it_fragment->polylines), extrusion_role, it_fragment->mm3_per_mm, it_fragment->width, it_fragment->height);
for (ExtrusionPathFragment &fragment : path_fragments)
extrusion_entities_append_paths(extrusions_in_out, std::move(fragment.polylines), { extrusion_role, fragment.flow });
}
// Support layer that is covered by some form of dense interface.

View File

@ -1023,7 +1023,7 @@ namespace SupportMaterialInternal {
assert(expansion_scaled >= 0.f);
for (const ExtrusionPath &ep : loop.paths)
if (ep.role() == ExtrusionRole::OverhangPerimeter && ! ep.polyline.empty()) {
float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled;
float exp = 0.5f * (float)scale_(ep.width()) + expansion_scaled;
if (ep.is_closed()) {
if (ep.size() >= 3) {
// This is a complete loop.

View File

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

View File

@ -7,7 +7,7 @@
#include "libslic3r/Utils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "libslic3r/GCodeWriter.hpp"
#include "libslic3r/GCode/GCodeWriter.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/Utils/PrintHost.hpp"

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

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,91 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <libslic3r/Geometry/ArcWelder.hpp>
TEST_CASE("arc_center", "[ArcWelder]") {
using namespace Slic3r;
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));
}
}
}

View File

@ -84,16 +84,16 @@ TEST_CASE("Line::perpendicular_to", "[Geometry]") {
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
// this test was failing on Windows (GH #1950)
Slic3r::Polygon polygon(Points({
Point(207802834,-57084522),
Point(196528149,-37556190),
Point(173626821,-25420928),
Point(171285751,-21366123),
Point(118673592,-21366123),
Point(116332562,-25420928),
Point(93431208,-37556191),
Point(82156517,-57084523),
Point(129714478,-84542120),
Point(160244873,-84542120)
{207802834,-57084522},
{196528149,-37556190},
{173626821,-25420928},
{171285751,-21366123},
{118673592,-21366123},
{116332562,-25420928},
{93431208,-37556191},
{82156517,-57084523},
{129714478,-84542120},
{160244873,-84542120}
}));
Point point(95706562, -57294774);
REQUIRE(polygon.contains(point));
@ -310,6 +310,7 @@ SCENARIO("Path chaining", "[Geometry]") {
GIVEN("A path") {
Points points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
THEN("Chained with no diagonals (thus 26 units long)") {
// if chain_points() works correctly, these points should be joined with no diagonal paths
std::vector<Points::size_type> indices = chain_points(points);
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();

View File

@ -5,6 +5,25 @@
using namespace Slic3r;
SCENARIO("Simplify polyne, template", "[Polyline]")
{
Points polyline{ {0,0}, {1000,0}, {2000,0}, {2000,1000}, {2000,2000}, {1000,2000}, {0,2000}, {0,1000}, {0,0} };
WHEN("simplified with Douglas-Peucker with back inserter") {
Points out;
douglas_peucker<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]")
{
GIVEN("polyline 1") {

View File

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

View File

@ -20,15 +20,6 @@ convex_hull(points)
OUTPUT:
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
rad2deg(angle)
double angle

View File

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