mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-12 07:19:05 +08:00
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:
parent
e0f7263a4c
commit
19062b4d5f
@ -15,7 +15,6 @@ our @EXPORT_OK = qw(
|
||||
|
||||
X Y Z
|
||||
convex_hull
|
||||
chained_path_from
|
||||
deg2rad
|
||||
rad2deg
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "CustomGCode.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "Extruder.hpp"
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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(); }
|
||||
|
@ -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) {
|
||||
|
@ -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
@ -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 ¶ms, GCode::SmoothPathCache &out);
|
||||
|
||||
friend class GCode::Wipe;
|
||||
friend class GCode::WipeTowerIntegration;
|
||||
friend class PressureEqualizer;
|
||||
};
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
@ -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_ */
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
258
src/libslic3r/GCode/SmoothPath.cpp
Normal file
258
src/libslic3r/GCode/SmoothPath.cpp
Normal 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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
this->interpolate_add(path.polyline, params);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionMultiPath &multi_path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionPath &path : multi_path.paths)
|
||||
this->interpolate_add(path.polyline, params);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionPath &path : loop.paths)
|
||||
this->interpolate_add(path.polyline, params);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionEntity *ee : eec) {
|
||||
if (ee->is_collection())
|
||||
this->interpolate_add(*static_cast<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
|
70
src/libslic3r/GCode/SmoothPath.hpp
Normal file
70
src/libslic3r/GCode/SmoothPath.hpp
Normal 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 ¶ms);
|
||||
void interpolate_add(const ExtrusionPath &ee, const InterpolationParameters ¶ms);
|
||||
void interpolate_add(const ExtrusionMultiPath &ee, const InterpolationParameters ¶ms);
|
||||
void interpolate_add(const ExtrusionLoop &ee, const InterpolationParameters ¶ms);
|
||||
void interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters ¶ms);
|
||||
|
||||
const Geometry::ArcWelder::Path* resolve(const Polyline *pl) const;
|
||||
const Geometry::ArcWelder::Path* resolve(const ExtrusionPath &path) const;
|
||||
|
||||
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
|
||||
Geometry::ArcWelder::Path resolve_or_fit(const ExtrusionPath &path, bool reverse, double resolution) const;
|
||||
|
||||
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
|
||||
SmoothPath resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const;
|
||||
SmoothPath resolve_or_fit(const ExtrusionMultiPath &path, bool reverse, double resolution) const;
|
||||
|
||||
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
|
||||
SmoothPath resolve_or_fit_split_with_seam(
|
||||
const ExtrusionLoop &path, const bool reverse, const double resolution,
|
||||
const Point &seam_point, const double seam_point_merge_distance_threshold) const;
|
||||
|
||||
private:
|
||||
ankerl::unordered_dense::map<const Polyline*, Geometry::ArcWelder::Path> m_cache;
|
||||
};
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCode_SmoothPath_hpp_
|
218
src/libslic3r/GCode/Wipe.cpp
Normal file
218
src/libslic3r/GCode/Wipe.cpp
Normal 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
|
72
src/libslic3r/GCode/Wipe.hpp
Normal file
72
src/libslic3r/GCode/Wipe.hpp
Normal 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_
|
@ -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_
|
||||
|
247
src/libslic3r/GCode/WipeTowerIntegration.cpp
Normal file
247
src/libslic3r/GCode/WipeTowerIntegration.cpp
Normal 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
|
65
src/libslic3r/GCode/WipeTowerIntegration.hpp
Normal file
65
src/libslic3r/GCode/WipeTowerIntegration.hpp
Normal 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_
|
543
src/libslic3r/Geometry/ArcWelder.cpp
Normal file
543
src/libslic3r/Geometry/ArcWelder.cpp
Normal 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 ¢er_pos, bool is_ccw)
|
||||
{
|
||||
if ((end_pos - start_pos).squaredNorm() < sqr(1e-6)) {
|
||||
// If start equals end, full circle is considered.
|
||||
return float(2. * M_PI);
|
||||
} else {
|
||||
Vec2f v1 = start_pos - center_pos;
|
||||
Vec2f v2 = end_pos - center_pos;
|
||||
if (! is_ccw)
|
||||
std::swap(v1, v2);
|
||||
float radian = atan2(cross2(v1, v2), v1.dot(v2));
|
||||
return radian < 0 ? float(2. * M_PI) + radian : radian;
|
||||
}
|
||||
}
|
||||
|
||||
float arc_length(const Vec2f &start_pos, const Vec2f &end_pos, Vec2f ¢er_pos, bool is_ccw)
|
||||
{
|
||||
return (center_pos - start_pos).norm() * arc_angle(start_pos, end_pos, center_pos, is_ccw);
|
||||
}
|
||||
|
||||
// Reduces polyline in the <begin, end) 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
|
196
src/libslic3r/Geometry/ArcWelder.hpp
Normal file
196
src/libslic3r/Geometry/ArcWelder.hpp
Normal 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_
|
@ -65,7 +65,7 @@ struct Circle {
|
||||
Vector center;
|
||||
Scalar radius;
|
||||
|
||||
Circle() {}
|
||||
Circle() = default;
|
||||
Circle(const Vector ¢er, const Scalar radius) : center(center), radius(radius) {}
|
||||
Circle(const Vector &a, const Vector &b) : center(Scalar(0.5) * (a + b)) { radius = (a - center).norm(); }
|
||||
Circle(const Vector &a, const Vector &b, const Vector &c, const Scalar epsilon) { *this = CircleSq(a, b, c, epsilon); }
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
#include "SVG.hpp"
|
||||
#include <Eigen/Dense>
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "GCode/GCodeWriter.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 ++)
|
||||
|
@ -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--; }
|
||||
|
@ -47,14 +47,12 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t)
|
||||
|
||||
void Point::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
double cur_x = (double)(*this)(0);
|
||||
double cur_y = (double)(*this)(1);
|
||||
double s = ::sin(angle);
|
||||
double c = ::cos(angle);
|
||||
double dx = cur_x - (double)center(0);
|
||||
double dy = cur_y - (double)center(1);
|
||||
(*this)(0) = (coord_t)round( (double)center(0) + c * dx - s * dy );
|
||||
(*this)(1) = (coord_t)round( (double)center(1) + c * dy + s * dx );
|
||||
Vec2d cur = this->cast<double>();
|
||||
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)
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
|
@ -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(); };
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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"
|
||||
|
95
t/geometry.t
95
t/geometry.t
@ -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__
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
#include "libslic3r/GCode/GCodeWriter.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
|
@ -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
|
||||
|
91
tests/libslic3r/test_arc_welder.cpp
Normal file
91
tests/libslic3r/test_arc_welder.cpp
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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") {
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user