mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-09-26 04:43:14 +08:00
Merge branch 'vb_arc_welder' into master_262
This commit is contained in:
commit
3cfe2f4a3a
@ -15,7 +15,6 @@ our @EXPORT_OK = qw(
|
||||
|
||||
X Y Z
|
||||
convex_hull
|
||||
chained_path_from
|
||||
deg2rad
|
||||
rad2deg
|
||||
);
|
||||
|
@ -345,7 +345,7 @@ public:
|
||||
return dist;
|
||||
}
|
||||
|
||||
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius)
|
||||
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius) const
|
||||
{
|
||||
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -142,8 +142,12 @@ set(SLIC3R_SOURCES
|
||||
GCode/ConflictChecker.hpp
|
||||
GCode/CoolingBuffer.cpp
|
||||
GCode/CoolingBuffer.hpp
|
||||
GCode/ExtrusionProcessor.cpp
|
||||
GCode/ExtrusionProcessor.hpp
|
||||
GCode/FindReplace.cpp
|
||||
GCode/FindReplace.hpp
|
||||
GCode/GCodeWriter.cpp
|
||||
GCode/GCodeWriter.hpp
|
||||
GCode/PostProcessor.cpp
|
||||
GCode/PostProcessor.hpp
|
||||
GCode/PressureEqualizer.cpp
|
||||
@ -156,10 +160,16 @@ set(SLIC3R_SOURCES
|
||||
GCode/SpiralVase.hpp
|
||||
GCode/SeamPlacer.cpp
|
||||
GCode/SeamPlacer.hpp
|
||||
GCode/SmoothPath.cpp
|
||||
GCode/SmoothPath.hpp
|
||||
GCode/ToolOrdering.cpp
|
||||
GCode/ToolOrdering.hpp
|
||||
GCode/Wipe.cpp
|
||||
GCode/Wipe.hpp
|
||||
GCode/WipeTower.cpp
|
||||
GCode/WipeTower.hpp
|
||||
GCode/WipeTowerIntegration.cpp
|
||||
GCode/WipeTowerIntegration.hpp
|
||||
GCode/GCodeProcessor.cpp
|
||||
GCode/GCodeProcessor.hpp
|
||||
GCode/AvoidCrossingPerimeters.cpp
|
||||
@ -170,10 +180,10 @@ set(SLIC3R_SOURCES
|
||||
GCodeReader.hpp
|
||||
# GCodeSender.cpp
|
||||
# GCodeSender.hpp
|
||||
GCodeWriter.cpp
|
||||
GCodeWriter.hpp
|
||||
Geometry.cpp
|
||||
Geometry.hpp
|
||||
Geometry/ArcWelder.cpp
|
||||
Geometry/ArcWelder.hpp
|
||||
Geometry/Bicubic.hpp
|
||||
Geometry/Circle.cpp
|
||||
Geometry/Circle.hpp
|
||||
|
@ -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>
|
||||
@ -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,10 +3,12 @@
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ExtrusionRole.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <numeric>
|
||||
|
||||
@ -55,28 +57,91 @@ 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; return *this; }
|
||||
|
||||
const ExtrusionEntity& extrusion_entity() const { return *m_extrusion_entity; }
|
||||
template<typename Type>
|
||||
const Type* cast() const { return dynamic_cast<const Type*>(m_extrusion_entity); }
|
||||
bool flipped() const { return m_flipped; }
|
||||
|
||||
private:
|
||||
const ExtrusionEntity *m_extrusion_entity;
|
||||
bool m_flipped;
|
||||
};
|
||||
|
||||
using ExtrusionEntityReferences = std::vector<ExtrusionEntityReference>;
|
||||
|
||||
struct ExtrusionFlow
|
||||
{
|
||||
ExtrusionFlow() = default;
|
||||
ExtrusionFlow(double mm3_per_mm, float width, float height) :
|
||||
mm3_per_mm{ mm3_per_mm }, width{ width }, height{ height } {}
|
||||
ExtrusionFlow(const Flow &flow) :
|
||||
mm3_per_mm(flow.mm3_per_mm()), width(flow.width()), height(flow.height()) {}
|
||||
|
||||
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
|
||||
double mm3_per_mm{ -1. };
|
||||
// Width of the extrusion, used for visualization purposes.
|
||||
float width{ -1.f };
|
||||
// Height of the extrusion, used for visualization purposes.
|
||||
float height{ -1.f };
|
||||
};
|
||||
|
||||
inline bool operator==(const ExtrusionFlow &lhs, const ExtrusionFlow &rhs)
|
||||
{
|
||||
return lhs.mm3_per_mm == rhs.mm3_per_mm && lhs.width == rhs.width && lhs.height == rhs.height;
|
||||
}
|
||||
|
||||
struct OverhangAttributes {
|
||||
float start_distance_from_prev_layer;
|
||||
float end_distance_from_prev_layer;
|
||||
float proximity_to_curled_lines; //value between 0 and 1
|
||||
};
|
||||
|
||||
struct ExtrusionAttributes : ExtrusionFlow
|
||||
{
|
||||
ExtrusionAttributes() = default;
|
||||
ExtrusionAttributes(ExtrusionRole role) : role{ role } {}
|
||||
ExtrusionAttributes(ExtrusionRole role, const Flow &flow) : role{ role }, ExtrusionFlow{ flow } {}
|
||||
ExtrusionAttributes(ExtrusionRole role, const ExtrusionFlow &flow) : role{ role }, ExtrusionFlow{ flow } {}
|
||||
|
||||
// What is the role / purpose of this extrusion?
|
||||
ExtrusionRole role{ ExtrusionRole::None };
|
||||
// OVerhangAttributes are currently computed for perimeters if dynamic overhangs are enabled.
|
||||
// They are used to control fan and print speed in export.
|
||||
std::optional<OverhangAttributes> overhang_attributes;
|
||||
};
|
||||
|
||||
inline bool operator==(const ExtrusionAttributes &lhs, const ExtrusionAttributes &rhs)
|
||||
{
|
||||
return static_cast<const ExtrusionFlow&>(lhs) == static_cast<const ExtrusionFlow&>(rhs) &&
|
||||
lhs.role == rhs.role;
|
||||
}
|
||||
|
||||
class ExtrusionPath : public ExtrusionEntity
|
||||
{
|
||||
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,7 +162,16 @@ public:
|
||||
void clip_end(double distance);
|
||||
void simplify(double tolerance);
|
||||
double length() const override;
|
||||
ExtrusionRole role() const override { return m_role; }
|
||||
|
||||
const ExtrusionAttributes& attributes() const { return m_attributes; }
|
||||
ExtrusionRole role() const override { return m_attributes.role; }
|
||||
float width() const { return m_attributes.width; }
|
||||
float height() const { return m_attributes.height; }
|
||||
double mm3_per_mm() const { return m_attributes.mm3_per_mm; }
|
||||
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
|
||||
double min_mm3_per_mm() const override { return m_attributes.mm3_per_mm; }
|
||||
std::optional<OverhangAttributes>& overhang_attributes_mutable() { return m_attributes.overhang_attributes; }
|
||||
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
|
||||
// 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;
|
||||
@ -109,23 +183,25 @@ public:
|
||||
{ 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 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()); }
|
||||
double total_volume() const override { return m_attributes.mm3_per_mm * unscale<double>(length()); }
|
||||
|
||||
private:
|
||||
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
|
||||
|
||||
ExtrusionRole m_role;
|
||||
ExtrusionAttributes m_attributes;
|
||||
};
|
||||
|
||||
class ExtrusionPathOriented : public ExtrusionPath
|
||||
{
|
||||
public:
|
||||
ExtrusionPathOriented(ExtrusionRole role, double mm3_per_mm, float width, float height) : ExtrusionPath(role, mm3_per_mm, width, height) {}
|
||||
ExtrusionPathOriented(const ExtrusionAttributes &attribs) : ExtrusionPath(attribs) {}
|
||||
ExtrusionPathOriented(const Polyline &polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(polyline, attribs) {}
|
||||
ExtrusionPathOriented(Polyline &&polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(std::move(polyline), attribs) {}
|
||||
|
||||
ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); }
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); }
|
||||
@ -192,7 +268,8 @@ class ExtrusionLoop : public ExtrusionEntity
|
||||
public:
|
||||
ExtrusionPaths paths;
|
||||
|
||||
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {}
|
||||
ExtrusionLoop() = default;
|
||||
ExtrusionLoop(ExtrusionLoopRole role) : m_loop_role(role) {}
|
||||
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {}
|
||||
ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {}
|
||||
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
|
||||
@ -204,9 +281,14 @@ 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();
|
||||
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]; }
|
||||
@ -259,59 +341,51 @@ public:
|
||||
#endif /* NDEBUG */
|
||||
|
||||
private:
|
||||
ExtrusionLoopRole m_loop_role;
|
||||
ExtrusionLoopRole m_loop_role{ elrDefault };
|
||||
};
|
||||
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, const ExtrusionAttributes &attributes)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
|
||||
dst.back().polyline = polyline;
|
||||
}
|
||||
if (polyline.is_valid())
|
||||
dst.emplace_back(polyline, attributes);
|
||||
}
|
||||
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, const ExtrusionAttributes &attributes)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
|
||||
dst.back().polyline = std::move(polyline);
|
||||
}
|
||||
if (polyline.is_valid())
|
||||
dst.emplace_back(std::move(polyline), attributes);
|
||||
polylines.clear();
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
|
||||
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, const ExtrusionAttributes &attributes, bool can_reverse = true)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (const Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
ExtrusionPath* extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
|
||||
dst.push_back(extrusion_path);
|
||||
extrusion_path->polyline = polyline;
|
||||
}
|
||||
if (polyline.is_valid())
|
||||
dst.emplace_back(can_reverse ? new ExtrusionPath(polyline, attributes) : new ExtrusionPathOriented(polyline, attributes));
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
|
||||
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, const ExtrusionAttributes &attributes, bool can_reverse = true)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
|
||||
dst.push_back(extrusion_path);
|
||||
extrusion_path->polyline = std::move(polyline);
|
||||
}
|
||||
if (polyline.is_valid())
|
||||
dst.emplace_back(can_reverse ?
|
||||
new ExtrusionPath(std::move(polyline), attributes) :
|
||||
new ExtrusionPathOriented(std::move(polyline), attributes));
|
||||
polylines.clear();
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, const ExtrusionAttributes &attributes)
|
||||
{
|
||||
dst.reserve(dst.size() + loops.size());
|
||||
for (Polygon &poly : loops) {
|
||||
if (poly.is_valid()) {
|
||||
ExtrusionPath path(role, mm3_per_mm, width, height);
|
||||
ExtrusionPath path(attributes);
|
||||
path.polyline.points = std::move(poly.points);
|
||||
path.polyline.points.push_back(path.polyline.points.front());
|
||||
dst.emplace_back(new ExtrusionLoop(std::move(path)));
|
||||
@ -320,22 +394,14 @@ inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons
|
||||
loops.clear();
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, const ExtrusionAttributes &attributes)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines) {
|
||||
if (polyline.is_valid()) {
|
||||
if (polyline.is_closed()) {
|
||||
ExtrusionPath extrusion_path(role, mm3_per_mm, width, height);
|
||||
extrusion_path.polyline = std::move(polyline);
|
||||
dst.emplace_back(new ExtrusionLoop(std::move(extrusion_path)));
|
||||
} else {
|
||||
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
|
||||
extrusion_path->polyline = std::move(polyline);
|
||||
dst.emplace_back(extrusion_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid())
|
||||
dst.emplace_back(polyline.is_closed() ?
|
||||
static_cast<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(); }
|
||||
|
@ -82,6 +82,7 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
|
||||
bool is_external_perimeter() const { return this->is_perimeter() && this->is_external(); }
|
||||
bool is_infill() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Infill); }
|
||||
bool is_solid_infill() const { return this->is_infill() && this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); }
|
||||
bool is_sparse_infill() const { return this->is_infill() && ! this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Solid); }
|
||||
bool is_external() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::External); }
|
||||
bool is_bridge() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Bridge); }
|
||||
|
||||
@ -89,6 +90,9 @@ struct ExtrusionRole : public ExtrusionRoleModifiers
|
||||
bool is_support_base() const { return this->is_support() && ! this->is_external(); }
|
||||
bool is_support_interface() const { return this->is_support() && this->is_external(); }
|
||||
bool is_mixed() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Mixed); }
|
||||
|
||||
// Brim is currently marked as skirt.
|
||||
bool is_skirt() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Skirt); }
|
||||
};
|
||||
|
||||
// Special flags describing loop
|
||||
|
@ -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::SmoothPathCaches &smooth_path_caches,
|
||||
const bool last_layer,
|
||||
// Pairs of PrintObject index and its instance index.
|
||||
const std::vector<const PrintInstance*> *ordering,
|
||||
@ -264,6 +217,7 @@ private:
|
||||
const ToolOrdering &tool_ordering,
|
||||
const std::vector<const PrintInstance*> &print_object_instances_ordering,
|
||||
const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
|
||||
const GCode::SmoothPathCache &smooth_path_cache_global,
|
||||
GCodeOutputStream &output_stream);
|
||||
// Process all layers of a single object instance (sequential mode) with a parallel pipeline:
|
||||
// Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
|
||||
@ -273,6 +227,7 @@ private:
|
||||
const ToolOrdering &tool_ordering,
|
||||
ObjectsLayerToPrint layers_to_print,
|
||||
const size_t single_object_idx,
|
||||
const GCode::SmoothPathCache &smooth_path_cache_global,
|
||||
GCodeOutputStream &output_stream);
|
||||
|
||||
void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; }
|
||||
@ -280,10 +235,13 @@ private:
|
||||
void set_extruders(const std::vector<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_skirt(const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override,
|
||||
const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed);
|
||||
|
||||
std::string extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
|
||||
std::string extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.);
|
||||
|
||||
struct InstanceToPrint
|
||||
{
|
||||
@ -317,12 +275,14 @@ private:
|
||||
const ObjectLayerToPrint &layer_to_print,
|
||||
// Container for extruder overrides (when wiping into object or infill).
|
||||
const LayerTools &layer_tools,
|
||||
// Optional smooth path interpolating extrusion polylines.
|
||||
const GCode::SmoothPathCache &smooth_path_cache,
|
||||
// Is any extrusion possibly marked as wiping extrusion?
|
||||
const bool is_anything_overridden,
|
||||
// Round 1 (wiping into object or infill) or round 2 (normal extrusions).
|
||||
const bool print_wipe_extrusions);
|
||||
|
||||
std::string extrude_support(const ExtrusionEntityCollection &support_fills);
|
||||
std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache);
|
||||
|
||||
std::string travel_to(const Point &point, ExtrusionRole role, std::string comment);
|
||||
bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None);
|
||||
@ -333,8 +293,6 @@ private:
|
||||
// Cache for custom seam enforcers/blockers for each layer.
|
||||
SeamPlacer m_seam_placer;
|
||||
|
||||
ExtrusionQualityEstimator m_extrusion_quality_estimator;
|
||||
|
||||
/* Origin of print coordinates expressed in unscaled G-code coordinates.
|
||||
This affects the input arguments supplied to the extrude*() and travel_to()
|
||||
methods. */
|
||||
@ -375,7 +333,7 @@ private:
|
||||
} m_placeholder_parser_integration;
|
||||
|
||||
OozePrevention m_ooze_prevention;
|
||||
Wipe m_wipe;
|
||||
GCode::Wipe m_wipe;
|
||||
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
|
||||
JPSPathFinder m_avoid_crossing_curled_overhangs;
|
||||
RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters;
|
||||
@ -418,7 +376,7 @@ private:
|
||||
std::unique_ptr<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 +392,8 @@ private:
|
||||
// Processor
|
||||
GCodeProcessor m_processor;
|
||||
|
||||
std::string _extrude(const ExtrusionPath &path, const std::string_view description, double speed = -1);
|
||||
std::string _extrude(
|
||||
const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, const std::string_view description, double speed = -1);
|
||||
void print_machine_envelope(GCodeOutputStream &file, Print &print);
|
||||
void _print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
|
||||
@ -443,8 +402,12 @@ private:
|
||||
// To control print speed of 1st object layer over raft interface.
|
||||
bool object_layer_over_raft() const { return m_object_layer_over_raft; }
|
||||
|
||||
friend class Wipe;
|
||||
friend class WipeTowerIntegration;
|
||||
// Fill in cache of smooth paths for perimeters, fills and supports of the given object layers.
|
||||
// Based on params, the paths are either decimated to sparser polylines, or interpolated with circular arches.
|
||||
static void smooth_path_interpolate(const ObjectLayerToPrint &layers, const GCode::SmoothPathCache::InterpolationParameters ¶ms, GCode::SmoothPathCache &out);
|
||||
|
||||
friend class GCode::Wipe;
|
||||
friend class GCode::WipeTowerIntegration;
|
||||
friend class PressureEqualizer;
|
||||
};
|
||||
|
||||
|
@ -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());
|
||||
|
||||
@ -33,36 +33,45 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_t
|
||||
|
||||
void CoolingBuffer::reset(const Vec3d &position)
|
||||
{
|
||||
m_current_pos.assign(5, 0.f);
|
||||
m_current_pos[0] = float(position.x());
|
||||
m_current_pos[1] = float(position.y());
|
||||
m_current_pos[2] = float(position.z());
|
||||
m_current_pos[4] = float(m_config.travel_speed.value);
|
||||
assert(m_current_pos.size() == 5);
|
||||
m_current_pos[AxisIdx::X] = float(position.x());
|
||||
m_current_pos[AxisIdx::Y] = float(position.y());
|
||||
m_current_pos[AxisIdx::Z] = float(position.z());
|
||||
m_current_pos[AxisIdx::E] = 0.f;
|
||||
m_current_pos[AxisIdx::F] = float(m_config.travel_speed.value);
|
||||
m_fan_speed = -1;
|
||||
}
|
||||
|
||||
struct CoolingLine
|
||||
{
|
||||
enum Type {
|
||||
enum Type : uint32_t {
|
||||
TYPE_SET_TOOL = 1 << 0,
|
||||
TYPE_EXTRUDE_END = 1 << 1,
|
||||
TYPE_BRIDGE_FAN_START = 1 << 2,
|
||||
TYPE_BRIDGE_FAN_END = 1 << 3,
|
||||
TYPE_G0 = 1 << 4,
|
||||
TYPE_G1 = 1 << 5,
|
||||
TYPE_ADJUSTABLE = 1 << 6,
|
||||
TYPE_EXTERNAL_PERIMETER = 1 << 7,
|
||||
// G2 or G3: Arc interpolation
|
||||
TYPE_G2G3 = 1 << 6,
|
||||
TYPE_ADJUSTABLE = 1 << 7,
|
||||
TYPE_EXTERNAL_PERIMETER = 1 << 8,
|
||||
// Arc interpolation, counter-clockwise.
|
||||
TYPE_G2G3_CCW = 1 << 9,
|
||||
// Arc interpolation, arc defined by IJ (offset of arc center from its start position).
|
||||
TYPE_G2G3_IJ = 1 << 10,
|
||||
// Arc interpolation, arc defined by R (arc radius, positive - smaller, negative - larger).
|
||||
TYPE_G2G3_R = 1 << 11,
|
||||
// The line sets a feedrate.
|
||||
TYPE_HAS_F = 1 << 8,
|
||||
TYPE_WIPE = 1 << 9,
|
||||
TYPE_G4 = 1 << 10,
|
||||
TYPE_G92 = 1 << 11,
|
||||
TYPE_HAS_F = 1 << 12,
|
||||
TYPE_WIPE = 1 << 13,
|
||||
TYPE_G4 = 1 << 14,
|
||||
TYPE_G92 = 1 << 15,
|
||||
// Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block
|
||||
// cannot have its speed adjusted. This should not happen (sic!).
|
||||
TYPE_ADJUSTABLE_EMPTY = 1 << 12,
|
||||
TYPE_ADJUSTABLE_EMPTY = 1 << 16,
|
||||
// Custom fan speed (introduced for overhang fan speed)
|
||||
TYPE_SET_FAN_SPEED = 1 << 13,
|
||||
TYPE_RESET_FAN_SPEED = 1 << 14,
|
||||
TYPE_SET_FAN_SPEED = 1 << 17,
|
||||
TYPE_RESET_FAN_SPEED = 1 << 18,
|
||||
};
|
||||
|
||||
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
|
||||
@ -324,7 +333,7 @@ std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, b
|
||||
|
||||
// Parse the layer G-code for the moves, which could be adjusted.
|
||||
// Return the list of parsed lines, bucketed by an extruder.
|
||||
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const
|
||||
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::array<float, 5> ¤t_pos) const
|
||||
{
|
||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size());
|
||||
std::vector<size_t> map_extruder_to_per_extruder_adjustment(m_num_extruders, 0);
|
||||
@ -347,7 +356,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
// for a sequence of extrusion moves.
|
||||
size_t active_speed_modifier = size_t(-1);
|
||||
|
||||
std::vector<float> new_pos;
|
||||
std::array<float, AxisIdx::Count> new_pos;
|
||||
for (; *line_start != 0; line_start = line_end)
|
||||
{
|
||||
while (*line_end != '\n' && *line_end != 0)
|
||||
@ -362,12 +371,20 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
line.type = CoolingLine::TYPE_G0;
|
||||
else if (boost::starts_with(sline, "G1 "))
|
||||
line.type = CoolingLine::TYPE_G1;
|
||||
else if (boost::starts_with(sline, "G2 "))
|
||||
// Arc, clockwise.
|
||||
line.type = CoolingLine::TYPE_G2G3;
|
||||
else if (boost::starts_with(sline, "G3 "))
|
||||
// Arc, counter-clockwise.
|
||||
line.type = CoolingLine::TYPE_G2G3 | CoolingLine::TYPE_G2G3_CCW;
|
||||
else if (boost::starts_with(sline, "G92 "))
|
||||
line.type = CoolingLine::TYPE_G92;
|
||||
if (line.type) {
|
||||
// G0, G1 or G92
|
||||
// G0, G1, G2, G3 or G92
|
||||
// Initialize current_pos from new_pos, set IJKR to zero.
|
||||
std::fill(std::copy(std::begin(current_pos), std::end(current_pos), std::begin(new_pos)),
|
||||
std::end(new_pos), 0.f);
|
||||
// Parse the G-code line.
|
||||
new_pos = current_pos;
|
||||
for (auto c = sline.begin() + 3;;) {
|
||||
// Skip whitespaces.
|
||||
for (; c != sline.end() && (*c == ' ' || *c == '\t'); ++ c);
|
||||
@ -376,21 +393,31 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
|
||||
// Parse the axis.
|
||||
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
|
||||
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
|
||||
(*c == extrusion_axis) ? AxisIdx::E : (*c == 'F') ? AxisIdx::F :
|
||||
(*c >= 'I' && *c <= 'K') ? int(AxisIdx::I) + (*c - 'I') :
|
||||
(*c == 'R') ? AxisIdx::R : size_t(-1);
|
||||
if (axis != size_t(-1)) {
|
||||
//auto [pend, ec] =
|
||||
fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
|
||||
if (axis == 4) {
|
||||
if (axis == AxisIdx::F) {
|
||||
// Convert mm/min to mm/sec.
|
||||
new_pos[4] /= 60.f;
|
||||
new_pos[AxisIdx::F] /= 60.f;
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0)
|
||||
// This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
|
||||
line.type |= CoolingLine::TYPE_HAS_F;
|
||||
}
|
||||
} else if (axis >= AxisIdx::I && axis <= AxisIdx::J)
|
||||
line.type |= CoolingLine::TYPE_G2G3_IJ;
|
||||
else if (axis == AxisIdx::R)
|
||||
line.type |= CoolingLine::TYPE_G2G3_R;
|
||||
}
|
||||
// Skip this word.
|
||||
for (; c != sline.end() && *c != ' ' && *c != '\t'; ++ c);
|
||||
}
|
||||
// If G2 or G3, then either center of the arc or radius has to be defined.
|
||||
assert(! (line.type & CoolingLine::TYPE_G2G3) ||
|
||||
(line.type & (CoolingLine::TYPE_G2G3_IJ | CoolingLine::TYPE_G2G3_R)));
|
||||
// Arc is defined either by IJ or by R, not by both.
|
||||
assert(! ((line.type & CoolingLine::TYPE_G2G3_IJ) && (line.type & CoolingLine::TYPE_G2G3_R)));
|
||||
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
|
||||
bool wipe = boost::contains(sline, ";_WIPE");
|
||||
if (external_perimeter)
|
||||
@ -402,23 +429,41 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
active_speed_modifier = adjustment->lines.size();
|
||||
}
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0) {
|
||||
// G0 or G1. Calculate the duration.
|
||||
// G0, G1, G2, G3. Calculate the duration.
|
||||
assert((line.type & CoolingLine::TYPE_G0) != 0 + (line.type & CoolingLine::TYPE_G1) != 0 + (line.type & CoolingLine::TYPE_G2G3) != 0 == 1);
|
||||
if (m_config.use_relative_e_distances.value)
|
||||
// Reset extruder accumulator.
|
||||
current_pos[3] = 0.f;
|
||||
current_pos[AxisIdx::E] = 0.f;
|
||||
float dif[4];
|
||||
for (size_t i = 0; i < 4; ++ i)
|
||||
dif[i] = new_pos[i] - current_pos[i];
|
||||
float dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
|
||||
float dxyz2 = dxy2 + dif[2] * dif[2];
|
||||
float dxy2;
|
||||
if (line.type & CoolingLine::TYPE_G2G3) {
|
||||
// Measure arc length.
|
||||
if (line.type & CoolingLine::TYPE_G2G3_IJ) {
|
||||
dxy2 = sqr(Geometry::ArcWelder::arc_length(
|
||||
Vec2d(current_pos[AxisIdx::X], current_pos[AxisIdx::Y]),
|
||||
Vec2d(new_pos[AxisIdx::X], new_pos[AxisIdx::Y]),
|
||||
Vec2d(current_pos[AxisIdx::X] + new_pos[AxisIdx::I], current_pos[AxisIdx::Y] + new_pos[AxisIdx::J]),
|
||||
line.type & CoolingLine::TYPE_G2G3_CCW));
|
||||
} else if (line.type & CoolingLine::TYPE_G2G3_R) {
|
||||
dxy2 = sqr(Geometry::ArcWelder::arc_length(
|
||||
Vec2d(current_pos[AxisIdx::X], current_pos[AxisIdx::Y]),
|
||||
Vec2d(new_pos[AxisIdx::X], new_pos[AxisIdx::Y]),
|
||||
double(new_pos[AxisIdx::R])));
|
||||
} else
|
||||
dxy2 = 0;
|
||||
} else
|
||||
dxy2 = sqr(dif[AxisIdx::X]) + sqr(dif[AxisIdx::Y]);
|
||||
float dxyz2 = dxy2 + sqr(dif[AxisIdx::Z]);
|
||||
if (dxyz2 > 0.f) {
|
||||
// Movement in xyz, calculate time from the xyz Euclidian distance.
|
||||
line.length = sqrt(dxyz2);
|
||||
} else if (std::abs(dif[3]) > 0.f) {
|
||||
} else if (std::abs(dif[AxisIdx::E]) > 0.f) {
|
||||
// Movement in the extruder axis.
|
||||
line.length = std::abs(dif[3]);
|
||||
line.length = std::abs(dif[AxisIdx::E]);
|
||||
}
|
||||
line.feedrate = new_pos[4];
|
||||
line.feedrate = new_pos[AxisIdx::F];
|
||||
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
|
||||
if (line.length > 0) {
|
||||
assert(line.feedrate > 0);
|
||||
@ -430,7 +475,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
assert(adjustment->min_print_speed >= 0);
|
||||
line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
|
||||
}
|
||||
if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) {
|
||||
if (active_speed_modifier < adjustment->lines.size() && (line.type & (CoolingLine::TYPE_G1 | CoolingLine::TYPE_G2G3))) {
|
||||
// Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
|
||||
assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
|
||||
CoolingLine &sm = adjustment->lines[active_speed_modifier];
|
||||
@ -447,7 +492,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
line.type = 0;
|
||||
}
|
||||
}
|
||||
current_pos = std::move(new_pos);
|
||||
std::copy(std::begin(new_pos), std::begin(new_pos) + 5, std::begin(current_pos));
|
||||
} else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
|
||||
// Closing a block of non-zero length extrusion moves.
|
||||
line.type = CoolingLine::TYPE_EXTRUDE_END;
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class GCodeGenerator;
|
||||
class Layer;
|
||||
struct PerExtruderAdjustments;
|
||||
|
||||
@ -22,7 +22,7 @@ struct PerExtruderAdjustments;
|
||||
//
|
||||
class CoolingBuffer {
|
||||
public:
|
||||
CoolingBuffer(GCode &gcodegen);
|
||||
CoolingBuffer(GCodeGenerator &gcodegen);
|
||||
void reset(const Vec3d &position);
|
||||
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
|
||||
std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
|
||||
@ -31,7 +31,7 @@ public:
|
||||
|
||||
private:
|
||||
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::array<float, 5> ¤t_pos) const;
|
||||
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
@ -40,9 +40,11 @@ private:
|
||||
// G-code snippet cached for the support layers preceding an object layer.
|
||||
std::string m_gcode;
|
||||
// Internal data.
|
||||
// X,Y,Z,E,F
|
||||
std::vector<char> m_axis;
|
||||
std::vector<float> m_current_pos;
|
||||
enum AxisIdx : int {
|
||||
X = 0, Y, Z, E, F, I, J, K, R, Count
|
||||
};
|
||||
std::array<float, 5> m_current_pos;
|
||||
// Current known fan speed or -1 if not known yet.
|
||||
int m_fan_speed;
|
||||
// Cached from GCodeWriter.
|
||||
@ -51,7 +53,7 @@ private:
|
||||
// Highest of m_extruder_ids plus 1.
|
||||
unsigned int m_num_extruders { 0 };
|
||||
const std::string m_toolchange_prefix;
|
||||
// Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
|
||||
// Referencs GCodeGenerator::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
|
||||
// the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
|
||||
const PrintConfig &m_config;
|
||||
unsigned int m_current_extruder;
|
||||
|
216
src/libslic3r/GCode/ExtrusionProcessor.cpp
Normal file
216
src/libslic3r/GCode/ExtrusionProcessor.cpp
Normal file
@ -0,0 +1,216 @@
|
||||
#include "ExtrusionProcessor.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r { namespace ExtrusionProcessor {
|
||||
|
||||
ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines)
|
||||
{
|
||||
std::vector<ExtendedPoint> extended_points = estimate_points_properties<true, true, true, true>(path.polyline.points,
|
||||
unscaled_prev_layer, path.width());
|
||||
std::vector<std::pair<float, float>> calculated_distances(extended_points.size());
|
||||
|
||||
for (size_t i = 0; i < extended_points.size(); i++) {
|
||||
const ExtendedPoint &curr = extended_points[i];
|
||||
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
|
||||
|
||||
// The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines
|
||||
float proximity_to_curled_lines = 0.0;
|
||||
const double dist_limit = 10.0 * path.width();
|
||||
{
|
||||
Vec2d middle = 0.5 * (curr.position + next.position);
|
||||
auto line_indices = prev_layer_curled_lines.all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit));
|
||||
if (!line_indices.empty()) {
|
||||
double len = (next.position - curr.position).norm();
|
||||
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle
|
||||
// of this long line
|
||||
// The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down.
|
||||
// NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point
|
||||
// TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge
|
||||
if (len > 8) {
|
||||
Vec2d dir = Vec2d(next.position - curr.position) / len;
|
||||
Vec2d right = Vec2d(-dir.y(), dir.x());
|
||||
|
||||
Polygon box_of_influence = {
|
||||
scaled(Vec2d(curr.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position - right * dist_limit)),
|
||||
scaled(Vec2d(curr.position - right * dist_limit)),
|
||||
};
|
||||
|
||||
double projected_lengths_sum = 0;
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_layer_curled_lines.get_line(idx);
|
||||
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
|
||||
if (inside.empty())
|
||||
continue;
|
||||
double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast<double>()))));
|
||||
projected_lengths_sum += projected_length;
|
||||
}
|
||||
if (projected_lengths_sum < 0.4 * len) {
|
||||
line_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_layer_curled_lines.get_line(idx);
|
||||
float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle)));
|
||||
float proximity = (1.0 - (distance_from_curled / dist_limit)) * (1.0 - (distance_from_curled / dist_limit)) *
|
||||
(line.curled_height / (path.height() * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
|
||||
proximity_to_curled_lines = std::max(proximity_to_curled_lines, proximity);
|
||||
}
|
||||
}
|
||||
}
|
||||
calculated_distances[i].first = std::max(curr.distance, next.distance);
|
||||
calculated_distances[i].second = proximity_to_curled_lines;
|
||||
}
|
||||
|
||||
ExtrusionPaths result;
|
||||
ExtrusionAttributes new_attrs = path.attributes();
|
||||
new_attrs.overhang_attributes = std::optional<OverhangAttributes>(
|
||||
{calculated_distances[0].first, calculated_distances[0].first, calculated_distances[0].second});
|
||||
result.emplace_back(new_attrs);
|
||||
result.back().polyline.append(Point::new_scale(extended_points[0].position));
|
||||
size_t sequence_start_index = 0;
|
||||
for (size_t i = 1; i < extended_points.size(); i++) {
|
||||
result.back().polyline.append(Point::new_scale(extended_points[i].position));
|
||||
result.back().overhang_attributes_mutable()->end_distance_from_prev_layer = extended_points[i].distance;
|
||||
|
||||
if (std::abs(calculated_distances[sequence_start_index].first - calculated_distances[i].first) < 0.001 * path.attributes().width &&
|
||||
std::abs(calculated_distances[sequence_start_index].second - calculated_distances[i].second) < 0.001) {
|
||||
// do not start new path, the attributes are similar enough
|
||||
// NOTE: a larger tolerance may be applied here. However, it makes the gcode preview much less smooth
|
||||
// (But it has very likely zero impact on the print quality.)
|
||||
} else if (i + 1 < extended_points.size()) { // do not start new path if this is last point!
|
||||
// start new path, parameters differ
|
||||
new_attrs.overhang_attributes->start_distance_from_prev_layer = calculated_distances[i].first;
|
||||
new_attrs.overhang_attributes->end_distance_from_prev_layer = calculated_distances[i].first;
|
||||
new_attrs.overhang_attributes->proximity_to_curled_lines = calculated_distances[i].second;
|
||||
sequence_start_index = i;
|
||||
result.emplace_back(new_attrs);
|
||||
result.back().polyline.append(Point::new_scale(extended_points[i].position));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(const ExtrusionEntityCollection *ecc,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines)
|
||||
{
|
||||
ExtrusionEntityCollection result{};
|
||||
result.no_sort = ecc->no_sort;
|
||||
for (const auto *e : ecc->entities) {
|
||||
if (auto *col = dynamic_cast<const ExtrusionEntityCollection *>(e)) {
|
||||
result.append(calculate_and_split_overhanging_extrusions(col, unscaled_prev_layer, prev_layer_curled_lines));
|
||||
} else if (auto *loop = dynamic_cast<const ExtrusionLoop *>(e)) {
|
||||
ExtrusionLoop new_loop = *loop;
|
||||
new_loop.paths.clear();
|
||||
for (const ExtrusionPath &p : loop->paths) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
new_loop.paths.insert(new_loop.paths.end(), paths.begin(), paths.end());
|
||||
}
|
||||
result.append(new_loop);
|
||||
} else if (auto *mp = dynamic_cast<const ExtrusionMultiPath *>(e)) {
|
||||
ExtrusionMultiPath new_mp = *mp;
|
||||
new_mp.paths.clear();
|
||||
for (const ExtrusionPath &p : mp->paths) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
new_mp.paths.insert(new_mp.paths.end(), paths.begin(), paths.end());
|
||||
}
|
||||
result.append(new_mp);
|
||||
} else if (auto *op = dynamic_cast<const ExtrusionPathOriented *>(e)) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(*op, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
for (const ExtrusionPath &p : paths) {
|
||||
result.append(ExtrusionPathOriented(p.polyline, p.attributes()));
|
||||
}
|
||||
} else if (auto *p = dynamic_cast<const ExtrusionPath *>(e)) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(*p, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
result.append(paths);
|
||||
} else {
|
||||
throw Slic3r::InvalidArgument("Unknown extrusion entity type");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
std::pair<float,float> calculate_overhang_speed(const ExtrusionAttributes &attributes,
|
||||
const FullPrintConfig &config,
|
||||
size_t extruder_id,
|
||||
float external_perim_reference_speed,
|
||||
float default_speed)
|
||||
{
|
||||
assert(attributes.overhang_attributes.has_value());
|
||||
std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_with_speeds = {
|
||||
{100, ConfigOptionFloatOrPercent{default_speed, false}}};
|
||||
if (config.enable_dynamic_overhang_speeds) {
|
||||
overhangs_with_speeds = {{0, config.overhang_speed_0},
|
||||
{25, config.overhang_speed_1},
|
||||
{50, config.overhang_speed_2},
|
||||
{75, config.overhang_speed_3},
|
||||
{100, ConfigOptionFloatOrPercent{default_speed, false}}};
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, ConfigOptionInts>> overhang_with_fan_speeds = {{100, ConfigOptionInts{0}}};
|
||||
if (config.enable_dynamic_fan_speeds.get_at(extruder_id)) {
|
||||
overhang_with_fan_speeds = {{0, config.overhang_fan_speed_0},
|
||||
{25, config.overhang_fan_speed_1},
|
||||
{50, config.overhang_fan_speed_2},
|
||||
{75, config.overhang_fan_speed_3},
|
||||
{100, ConfigOptionInts{0}}};
|
||||
}
|
||||
|
||||
float speed_base = external_perim_reference_speed > 0 ? external_perim_reference_speed : default_speed;
|
||||
std::map<float, float> speed_sections;
|
||||
for (size_t i = 0; i < overhangs_with_speeds.size(); i++) {
|
||||
float distance = attributes.width * (1.0 - (overhangs_with_speeds[i].first / 100.0));
|
||||
float speed = overhangs_with_speeds[i].second.percent ? (speed_base * overhangs_with_speeds[i].second.value / 100.0) :
|
||||
overhangs_with_speeds[i].second.value;
|
||||
if (speed < EPSILON)
|
||||
speed = speed_base;
|
||||
speed_sections[distance] = speed;
|
||||
}
|
||||
|
||||
std::map<float, float> fan_speed_sections;
|
||||
for (size_t i = 0; i < overhang_with_fan_speeds.size(); i++) {
|
||||
float distance = attributes.width * (1.0 - (overhang_with_fan_speeds[i].first / 100.0));
|
||||
float fan_speed = overhang_with_fan_speeds[i].second.get_at(extruder_id);
|
||||
fan_speed_sections[distance] = fan_speed;
|
||||
}
|
||||
|
||||
auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
|
||||
auto upper_dist = values.lower_bound(distance);
|
||||
if (upper_dist == values.end()) {
|
||||
return values.rbegin()->second;
|
||||
}
|
||||
if (upper_dist == values.begin()) {
|
||||
return upper_dist->second;
|
||||
}
|
||||
|
||||
auto lower_dist = std::prev(upper_dist);
|
||||
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
|
||||
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
|
||||
};
|
||||
|
||||
float extrusion_speed = std::min(interpolate_speed(speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer),
|
||||
interpolate_speed(speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer));
|
||||
float curled_base_speed = interpolate_speed(speed_sections,
|
||||
attributes.width * attributes.overhang_attributes->proximity_to_curled_lines);
|
||||
float final_speed = std::min(curled_base_speed, extrusion_speed);
|
||||
float fan_speed = std::min(interpolate_speed(fan_speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer),
|
||||
interpolate_speed(fan_speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer));
|
||||
|
||||
if (!config.enable_dynamic_overhang_speeds) {
|
||||
final_speed = -1;
|
||||
}
|
||||
if (!config.enable_dynamic_fan_speeds.get_at(extruder_id)) {
|
||||
fan_speed = -1;
|
||||
}
|
||||
|
||||
return {final_speed, fan_speed};
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::ExtrusionProcessor
|
@ -14,19 +14,23 @@
|
||||
#include "../Flow.hpp"
|
||||
#include "../Config.hpp"
|
||||
#include "../Line.hpp"
|
||||
#include "../Exception.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Slic3r { namespace ExtrusionProcessor {
|
||||
|
||||
struct ExtendedPoint
|
||||
{
|
||||
@ -54,19 +58,22 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
|
||||
{
|
||||
ExtendedPoint start_point{maybe_unscale(input_points.front())};
|
||||
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
|
||||
auto [distance, nearest_line,
|
||||
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
|
||||
start_point.distance = distance + boundary_offset;
|
||||
points.push_back(start_point);
|
||||
}
|
||||
for (size_t i = 1; i < input_points.size(); i++) {
|
||||
ExtendedPoint next_point{maybe_unscale(input_points[i])};
|
||||
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
|
||||
auto [distance, nearest_line,
|
||||
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
|
||||
next_point.distance = distance + boundary_offset;
|
||||
|
||||
if (ADD_INTERSECTIONS &&
|
||||
((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) {
|
||||
const ExtendedPoint &prev_point = points.back();
|
||||
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
|
||||
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(
|
||||
L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
|
||||
for (const auto &intersection : intersections) {
|
||||
ExtendedPoint p{};
|
||||
p.position = intersection.first.template cast<double>();
|
||||
@ -79,24 +86,25 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
|
||||
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
|
||||
std::vector<ExtendedPoint> new_points;
|
||||
new_points.reserve(points.size()*2);
|
||||
new_points.reserve(points.size() * 2);
|
||||
new_points.push_back(points.front());
|
||||
for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) {
|
||||
const ExtendedPoint &curr = points[point_idx];
|
||||
const ExtendedPoint &next = points[point_idx + 1];
|
||||
|
||||
if ((curr.distance > 0 && curr.distance < boundary_offset + 2.0f) ||
|
||||
(next.distance > 0 && next.distance < boundary_offset + 2.0f)) {
|
||||
if ((curr.distance > -boundary_offset && curr.distance < boundary_offset + 2.0f) ||
|
||||
(next.distance > -boundary_offset && next.distance < boundary_offset + 2.0f)) {
|
||||
double line_len = (next.position - curr.position).norm();
|
||||
if (line_len > 4.0f) {
|
||||
double a0 = std::clamp((curr.distance + 2 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double a1 = std::clamp(1.0f - (next.distance + 2 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double a0 = std::clamp((curr.distance + 3 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double a1 = std::clamp(1.0f - (next.distance + 3 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double t0 = std::min(a0, a1);
|
||||
double t1 = std::max(a0, a1);
|
||||
|
||||
if (t0 < 1.0) {
|
||||
auto p0 = curr.position + t0 * (next.position - curr.position);
|
||||
auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
|
||||
auto [p0_dist, p0_near_l,
|
||||
p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
|
||||
ExtendedPoint new_p{};
|
||||
new_p.position = p0;
|
||||
new_p.distance = float(p0_dist + boundary_offset);
|
||||
@ -104,7 +112,8 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
}
|
||||
if (t1 > 0.0) {
|
||||
auto p1 = curr.position + t1 * (next.position - curr.position);
|
||||
auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
|
||||
auto [p1_dist, p1_near_l,
|
||||
p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
|
||||
ExtendedPoint new_p{};
|
||||
new_p.position = p1;
|
||||
new_p.distance = float(p1_dist + boundary_offset);
|
||||
@ -114,12 +123,12 @@ 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) {
|
||||
std::vector<ExtendedPoint> new_points;
|
||||
new_points.reserve(points.size()*2);
|
||||
new_points.reserve(points.size() * 2);
|
||||
{
|
||||
for (size_t i = 0; i + 1 < points.size(); i++) {
|
||||
const ExtendedPoint &curr = points[i];
|
||||
@ -140,7 +149,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());
|
||||
@ -212,144 +221,21 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
return points;
|
||||
}
|
||||
|
||||
struct ProcessedPoint
|
||||
{
|
||||
Point p;
|
||||
float speed = 1.0f;
|
||||
int fan_speed = 0;
|
||||
};
|
||||
ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines);
|
||||
|
||||
class ExtrusionQualityEstimator
|
||||
{
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> prev_layer_boundaries;
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> next_layer_boundaries;
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> prev_curled_extrusions;
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> next_curled_extrusions;
|
||||
const PrintObject *current_object;
|
||||
ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(
|
||||
const ExtrusionEntityCollection *ecc,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines);
|
||||
|
||||
public:
|
||||
void set_current_object(const PrintObject *object) { current_object = object; }
|
||||
|
||||
void prepare_for_new_layer(const Layer *layer)
|
||||
{
|
||||
if (layer == nullptr)
|
||||
return;
|
||||
const PrintObject *object = layer->object();
|
||||
prev_layer_boundaries[object] = next_layer_boundaries[object];
|
||||
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(layer->lslices)};
|
||||
prev_curled_extrusions[object] = next_curled_extrusions[object];
|
||||
next_curled_extrusions[object] = AABBTreeLines::LinesDistancer<CurledLine>{layer->curled_lines};
|
||||
}
|
||||
|
||||
std::vector<ProcessedPoint> estimate_speed_from_extrusion_quality(
|
||||
const ExtrusionPath &path,
|
||||
const std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_w_speeds,
|
||||
const std::vector<std::pair<int, ConfigOptionInts>> overhangs_w_fan_speeds,
|
||||
std::pair<float, float> calculate_overhang_speed(const ExtrusionAttributes &attributes,
|
||||
const FullPrintConfig &config,
|
||||
size_t extruder_id,
|
||||
float ext_perimeter_speed,
|
||||
float original_speed)
|
||||
{
|
||||
float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
|
||||
std::map<float, float> speed_sections;
|
||||
for (size_t i = 0; i < overhangs_w_speeds.size(); i++) {
|
||||
float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0));
|
||||
float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) :
|
||||
overhangs_w_speeds[i].second.value;
|
||||
if (speed < EPSILON) speed = speed_base;
|
||||
speed_sections[distance] = speed;
|
||||
}
|
||||
float external_perim_reference_speed,
|
||||
float default_speed);
|
||||
|
||||
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 fan_speed = overhangs_w_fan_speeds[i].second.get_at(extruder_id);
|
||||
fan_speed_sections[distance] = fan_speed;
|
||||
}
|
||||
|
||||
std::vector<ExtendedPoint> extended_points =
|
||||
estimate_points_properties<true, true, true, true>(path.polyline.points, prev_layer_boundaries[current_object], path.width);
|
||||
|
||||
std::vector<ProcessedPoint> processed_points;
|
||||
processed_points.reserve(extended_points.size());
|
||||
for (size_t i = 0; i < extended_points.size(); i++) {
|
||||
const ExtendedPoint &curr = extended_points[i];
|
||||
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
|
||||
|
||||
// The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines
|
||||
float artificial_distance_to_curled_lines = 0.0;
|
||||
const double dist_limit = 10.0 * path.width;
|
||||
{
|
||||
Vec2d middle = 0.5 * (curr.position + next.position);
|
||||
auto line_indices = prev_curled_extrusions[current_object].all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit));
|
||||
if (!line_indices.empty()) {
|
||||
double len = (next.position - curr.position).norm();
|
||||
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle of this long line
|
||||
// The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down.
|
||||
// NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point
|
||||
// TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge
|
||||
if (len > 8) {
|
||||
Vec2d dir = Vec2d(next.position - curr.position) / len;
|
||||
Vec2d right = Vec2d(-dir.y(), dir.x());
|
||||
|
||||
Polygon box_of_influence = {
|
||||
scaled(Vec2d(curr.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position - right * dist_limit)),
|
||||
scaled(Vec2d(curr.position - right * dist_limit)),
|
||||
};
|
||||
|
||||
double projected_lengths_sum = 0;
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
|
||||
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
|
||||
if (inside.empty())
|
||||
continue;
|
||||
double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast<double>()))));
|
||||
projected_lengths_sum += projected_length;
|
||||
}
|
||||
if (projected_lengths_sum < 0.4 * len) {
|
||||
line_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
|
||||
float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle)));
|
||||
float dist = path.width * (1.0 - (distance_from_curled / dist_limit)) *
|
||||
(1.0 - (distance_from_curled / dist_limit)) *
|
||||
(line.curled_height / (path.height * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
|
||||
artificial_distance_to_curled_lines = std::max(artificial_distance_to_curled_lines, dist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
|
||||
auto upper_dist = values.lower_bound(distance);
|
||||
if (upper_dist == values.end()) {
|
||||
return values.rbegin()->second;
|
||||
}
|
||||
if (upper_dist == values.begin()) {
|
||||
return upper_dist->second;
|
||||
}
|
||||
|
||||
auto lower_dist = std::prev(upper_dist);
|
||||
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
|
||||
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
|
||||
};
|
||||
|
||||
float extrusion_speed = std::min(interpolate_speed(speed_sections, curr.distance),
|
||||
interpolate_speed(speed_sections, next.distance));
|
||||
float curled_base_speed = interpolate_speed(speed_sections, artificial_distance_to_curled_lines);
|
||||
float final_speed = std::min(curled_base_speed, extrusion_speed);
|
||||
float fan_speed = std::min(interpolate_speed(fan_speed_sections, curr.distance),
|
||||
interpolate_speed(fan_speed_sections, next.distance));
|
||||
|
||||
processed_points.push_back({scaled(curr.position), final_speed, int(fan_speed)});
|
||||
}
|
||||
return processed_points;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
}} // namespace Slic3r::ExtrusionProcessor
|
||||
|
||||
#endif // slic3r_ExtrusionProcessor_hpp_
|
||||
|
@ -4,8 +4,9 @@
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
#include "libslic3r/GCode/GCodeWriter.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "libslic3r/Geometry/ArcWelder.hpp"
|
||||
#include "GCodeProcessor.hpp"
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
@ -43,8 +44,6 @@ static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero();
|
||||
// taken from PrusaResearch.ini - [printer:Original Prusa i3 MK2.5 MMU2]
|
||||
static const std::vector<std::string> DEFAULT_EXTRUDER_COLORS = { "#FF8000", "#DB5182", "#3EC0FF", "#FF4F4F", "#FBEB7D" };
|
||||
|
||||
static const std::string INTERNAL_G2G3_TAG = "!#!#! internal only - from G2/G3 expansion !#!#!";
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
|
||||
@ -2363,13 +2362,10 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
||||
if (line.has_e()) g1_axes[E] = (double)line.e();
|
||||
std::optional<double> g1_feedrate = std::nullopt;
|
||||
if (line.has_f()) g1_feedrate = (double)line.f();
|
||||
std::optional<std::string> g1_cmt = std::nullopt;
|
||||
if (!line.comment().empty()) g1_cmt = line.comment();
|
||||
|
||||
process_G1(g1_axes, g1_feedrate, g1_cmt);
|
||||
process_G1(g1_axes, g1_feedrate);
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes, std::optional<double> feedrate, std::optional<std::string> cmt)
|
||||
void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes, std::optional<double> feedrate, G1DiscretizationOrigin origin)
|
||||
{
|
||||
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
|
||||
const float filament_radius = 0.5f * filament_diameter;
|
||||
@ -2452,7 +2448,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
m_height = m_forced_height;
|
||||
else if (m_layer_id == 0)
|
||||
m_height = m_first_layer_height + m_z_offset;
|
||||
else if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG) {
|
||||
else if (origin == G1DiscretizationOrigin::G1) {
|
||||
if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0)
|
||||
m_height = m_end_position[Z] - m_extruded_last_z;
|
||||
}
|
||||
@ -2463,7 +2459,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
if (m_end_position[Z] == 0.0f || (m_extrusion_role == GCodeExtrusionRole::Custom && m_layer_id == 0))
|
||||
m_end_position[Z] = m_height;
|
||||
|
||||
if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG)
|
||||
if (origin == G1DiscretizationOrigin::G1)
|
||||
m_extruded_last_z = m_end_position[Z];
|
||||
m_options_z_corrector.update(m_height);
|
||||
|
||||
@ -2693,18 +2689,48 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
}
|
||||
|
||||
// store move
|
||||
store_move_vertex(type, cmt.has_value() && *cmt == INTERNAL_G2G3_TAG);
|
||||
store_move_vertex(type, origin == G1DiscretizationOrigin::G2G3);
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise)
|
||||
{
|
||||
if (!line.has('I') || !line.has('J'))
|
||||
enum class EFitting { None, IJ, R };
|
||||
const EFitting fitting = line.has('R') ? EFitting::R : (line.has('I') && line.has('J')) ? EFitting::IJ : EFitting::None;
|
||||
|
||||
if (fitting == EFitting::None)
|
||||
return;
|
||||
|
||||
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
|
||||
const float filament_radius = 0.5f * filament_diameter;
|
||||
const float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
|
||||
|
||||
AxisCoords end_position = m_start_position;
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section));
|
||||
}
|
||||
|
||||
// relative center
|
||||
Vec3f rel_center = Vec3f::Zero();
|
||||
#ifndef _NDEBUG
|
||||
double radius = 0.0;
|
||||
#endif // _NDEBUG
|
||||
if (fitting == EFitting::R) {
|
||||
float r;
|
||||
if (!line.has_value('R', r) || r == 0.0f)
|
||||
return;
|
||||
#ifndef _NDEBUG
|
||||
radius = (double)std::abs(r);
|
||||
#endif // _NDEBUG
|
||||
const Vec2f start_pos((float)m_start_position[X], (float)m_start_position[Y]);
|
||||
const Vec2f end_pos((float)end_position[X], (float)end_position[Y]);
|
||||
const Vec2f c = Geometry::ArcWelder::arc_center(start_pos, end_pos, r, !clockwise);
|
||||
rel_center.x() = c.x() - m_start_position[X];
|
||||
rel_center.y() = c.y() - m_start_position[Y];
|
||||
}
|
||||
else {
|
||||
if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y()))
|
||||
return;
|
||||
}
|
||||
|
||||
// scale center, if needed
|
||||
if (m_units == EUnits::Inches)
|
||||
@ -2740,15 +2766,6 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
// arc center
|
||||
arc.center = arc.start + rel_center.cast<double>();
|
||||
|
||||
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
|
||||
const float filament_radius = 0.5f * filament_diameter;
|
||||
const float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
|
||||
|
||||
AxisCoords end_position = m_start_position;
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section));
|
||||
}
|
||||
|
||||
// arc end endpoint
|
||||
arc.end = Vec3d(end_position[X], end_position[Y], end_position[Z]);
|
||||
|
||||
@ -2757,6 +2774,8 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
// what to do ???
|
||||
}
|
||||
|
||||
assert(fitting != EFitting::R || std::abs(radius - arc.start_radius()) < EPSILON);
|
||||
|
||||
// updates feedrate from line
|
||||
std::optional<float> feedrate;
|
||||
if (line.has_f())
|
||||
@ -2816,9 +2835,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
g1_feedrate = (double)*feedrate;
|
||||
if (extrusion.has_value())
|
||||
g1_axes[E] = target[E];
|
||||
std::optional<std::string> g1_cmt = INTERNAL_G2G3_TAG;
|
||||
|
||||
process_G1(g1_axes, g1_feedrate, g1_cmt);
|
||||
process_G1(g1_axes, g1_feedrate, G1DiscretizationOrigin::G2G3);
|
||||
};
|
||||
|
||||
// calculate arc segments
|
||||
@ -2827,8 +2844,13 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
// https://github.com/prusa3d/Prusa-Firmware/blob/MK3/Firmware/motion_control.cpp
|
||||
|
||||
// segments count
|
||||
#if 0
|
||||
static const double MM_PER_ARC_SEGMENT = 1.0;
|
||||
const size_t segments = std::max<size_t>(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1);
|
||||
#else
|
||||
static const double gcode_arc_tolerance = 0.0125;
|
||||
const size_t segments = Geometry::ArcWelder::arc_discretization_steps(arc.start_radius(), std::abs(arc.angle), gcode_arc_tolerance);
|
||||
#endif
|
||||
|
||||
const double inv_segment = 1.0 / double(segments);
|
||||
const double theta_per_segment = arc.angle * inv_segment;
|
||||
|
@ -56,9 +56,13 @@ namespace Slic3r {
|
||||
time = 0.0f;
|
||||
travel_time = 0.0f;
|
||||
custom_gcode_times.clear();
|
||||
custom_gcode_times.shrink_to_fit();
|
||||
moves_times.clear();
|
||||
moves_times.shrink_to_fit();
|
||||
roles_times.clear();
|
||||
roles_times.shrink_to_fit();
|
||||
layers_times.clear();
|
||||
layers_times.shrink_to_fit();
|
||||
}
|
||||
};
|
||||
|
||||
@ -76,6 +80,7 @@ namespace Slic3r {
|
||||
m.reset();
|
||||
}
|
||||
volumes_per_color_change.clear();
|
||||
volumes_per_color_change.shrink_to_fit();
|
||||
volumes_per_extruder.clear();
|
||||
used_filaments_per_role.clear();
|
||||
cost_per_extruder.clear();
|
||||
@ -680,8 +685,12 @@ namespace Slic3r {
|
||||
// Move
|
||||
void process_G0(const GCodeReader::GCodeLine& line);
|
||||
void process_G1(const GCodeReader::GCodeLine& line);
|
||||
enum class G1DiscretizationOrigin {
|
||||
G1,
|
||||
G2G3,
|
||||
};
|
||||
void process_G1(const std::array<std::optional<double>, 4>& axes = { std::nullopt, std::nullopt, std::nullopt, std::nullopt },
|
||||
std::optional<double> feedrate = std::nullopt, std::optional<std::string> cmt = std::nullopt);
|
||||
std::optional<double> feedrate = std::nullopt, G1DiscretizationOrigin origin = G1DiscretizationOrigin::G1);
|
||||
|
||||
// Arc Move
|
||||
void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise);
|
||||
|
@ -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,40 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &comment)
|
||||
std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(point.x()) < 1200.);
|
||||
assert(std::abs(point.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) < 1200.);
|
||||
assert(std::abs(ij.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG2G3Formatter w(ccw);
|
||||
w.emit_xy(point);
|
||||
w.emit_ij(ij);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(point.x()) < 1200.);
|
||||
assert(std::abs(point.y()) < 1200.);
|
||||
assert(std::abs(radius) >= 0.001);
|
||||
assert(std::abs(radius) < 1800.);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG2G3Formatter w(ccw);
|
||||
w.emit_xy(point);
|
||||
w.emit_radius(radius);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment)
|
||||
{
|
||||
// FIXME: This function was not being used when travel_speed_z was separated (bd6badf).
|
||||
// Calculation of feedrate was not updated accordingly. If you want to use
|
||||
@ -302,7 +338,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
|
||||
std::string GCodeWriter::travel_to_z(double z, const std::string_view comment)
|
||||
{
|
||||
/* If target Z is lower than current Z but higher than nominal Z
|
||||
we don't perform the move but we only adjust the nominal Z by
|
||||
@ -321,7 +357,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
|
||||
return this->_travel_to_z(z, comment);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
|
||||
std::string GCodeWriter::_travel_to_z(double z, const std::string_view comment)
|
||||
{
|
||||
m_pos.z() = z;
|
||||
|
||||
@ -348,10 +384,12 @@ bool GCodeWriter::will_move_z(double z) const
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment)
|
||||
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment)
|
||||
{
|
||||
m_pos.x() = point.x();
|
||||
m_pos.y() = point.y();
|
||||
assert(dE != 0);
|
||||
assert(std::abs(dE) < 1000.0);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xy(point);
|
||||
@ -360,8 +398,47 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(dE) < 1000.0);
|
||||
assert(dE != 0);
|
||||
assert(std::abs(point.x()) < 1200.);
|
||||
assert(std::abs(point.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) < 1200.);
|
||||
assert(std::abs(ij.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG2G3Formatter w(ccw);
|
||||
w.emit_xy(point);
|
||||
w.emit_ij(ij);
|
||||
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, double dE, const std::string_view comment)
|
||||
{
|
||||
assert(dE != 0);
|
||||
assert(std::abs(dE) < 1000.0);
|
||||
assert(std::abs(point.x()) < 1200.);
|
||||
assert(std::abs(point.y()) < 1200.);
|
||||
assert(std::abs(radius) >= 0.001);
|
||||
assert(std::abs(radius) < 1800.);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG2G3Formatter w(ccw);
|
||||
w.emit_xy(point);
|
||||
w.emit_radius(radius);
|
||||
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
#if 0
|
||||
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment)
|
||||
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment)
|
||||
{
|
||||
m_pos = point;
|
||||
m_lifted = 0;
|
||||
@ -397,8 +474,11 @@ std::string GCodeWriter::retract_for_toolchange(bool before_wipe)
|
||||
);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::_retract(double length, double restart_extra, const std::string &comment)
|
||||
std::string GCodeWriter::_retract(double length, double restart_extra, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(length) < 1000.0);
|
||||
assert(std::abs(restart_extra) < 1000.0);
|
||||
|
||||
/* If firmware retraction is enabled, we use a fake value of 1
|
||||
since we ignore the actual configured retract_length which
|
||||
might be 0, in which case the retraction logic gets skipped. */
|
@ -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, const bool ccw, double dE, const std::string_view comment);
|
||||
std::string extrude_to_xy_G2G3R(const Vec2d &point, const double radius, const bool ccw, double dE, const std::string_view comment);
|
||||
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {});
|
||||
std::string retract(bool before_wipe = false);
|
||||
std::string retract_for_toolchange(bool before_wipe = false);
|
||||
std::string unretract();
|
||||
@ -113,8 +119,8 @@ private:
|
||||
Print
|
||||
};
|
||||
|
||||
std::string _travel_to_z(double z, const std::string &comment);
|
||||
std::string _retract(double length, double restart_extra, const std::string &comment);
|
||||
std::string _travel_to_z(double z, const std::string_view comment);
|
||||
std::string _retract(double length, double restart_extra, const std::string_view comment);
|
||||
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);
|
||||
};
|
||||
|
||||
@ -152,6 +158,10 @@ public:
|
||||
static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; }
|
||||
static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); }
|
||||
static double quantize_e(double v) { return quantize(v, E_EXPORT_DIGITS); }
|
||||
static Vec2d quantize(const Vec2d &pt)
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec3d quantize(const Vec3d &pt)
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS), quantize(pt.z(), XYZF_EXPORT_DIGITS) }; }
|
||||
|
||||
void emit_axis(const char axis, const double v, size_t digits);
|
||||
|
||||
@ -170,7 +180,20 @@ public:
|
||||
this->emit_axis('Z', z, XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
void emit_e(const std::string &axis, double v) {
|
||||
void emit_ij(const Vec2d &point) {
|
||||
if (point.x() != 0)
|
||||
this->emit_axis('I', point.x(), XYZF_EXPORT_DIGITS);
|
||||
if (point.y() != 0)
|
||||
this->emit_axis('J', point.y(), XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
// Positive radius means a smaller arc,
|
||||
// negative radius means a larger arc.
|
||||
void emit_radius(const double radius) {
|
||||
this->emit_axis('R', radius, XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
void emit_e(const std::string_view axis, double v) {
|
||||
if (! axis.empty()) {
|
||||
// not gcfNoExtrusion
|
||||
this->emit_axis(axis[0], v, E_EXPORT_DIGITS);
|
||||
@ -181,12 +204,12 @@ public:
|
||||
this->emit_axis('F', speed, XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
void emit_string(const std::string &s) {
|
||||
strncpy(ptr_err.ptr, s.c_str(), s.size());
|
||||
void emit_string(const std::string_view s) {
|
||||
strncpy(ptr_err.ptr, s.data(), s.size());
|
||||
ptr_err.ptr += s.size();
|
||||
}
|
||||
|
||||
void emit_comment(bool allow_comments, const std::string &comment) {
|
||||
void emit_comment(bool allow_comments, const std::string_view comment) {
|
||||
if (allow_comments && ! comment.empty()) {
|
||||
*ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' ';
|
||||
this->emit_string(comment);
|
||||
@ -210,14 +233,25 @@ public:
|
||||
GCodeG1Formatter() {
|
||||
this->buf[0] = 'G';
|
||||
this->buf[1] = '1';
|
||||
this->buf_end = buf + buflen;
|
||||
this->ptr_err.ptr = this->buf + 2;
|
||||
this->ptr_err.ptr += 2;
|
||||
}
|
||||
|
||||
GCodeG1Formatter(const GCodeG1Formatter&) = delete;
|
||||
GCodeG1Formatter& operator=(const GCodeG1Formatter&) = delete;
|
||||
};
|
||||
|
||||
class GCodeG2G3Formatter : public GCodeFormatter {
|
||||
public:
|
||||
GCodeG2G3Formatter(bool ccw) {
|
||||
this->buf[0] = 'G';
|
||||
this->buf[1] = ccw ? '3' : '2';
|
||||
this->ptr_err.ptr += 2;
|
||||
}
|
||||
|
||||
GCodeG2G3Formatter(const GCodeG2G3Formatter&) = delete;
|
||||
GCodeG2G3Formatter& operator=(const GCodeG2G3Formatter&) = delete;
|
||||
};
|
||||
|
||||
} /* namespace Slic3r */
|
||||
|
||||
#endif /* slic3r_GCodeWriter_hpp_ */
|
@ -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);
|
||||
|
257
src/libslic3r/GCode/SmoothPath.cpp
Normal file
257
src/libslic3r/GCode/SmoothPath.cpp
Normal file
@ -0,0 +1,257 @@
|
||||
#include "SmoothPath.hpp"
|
||||
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../ExtrusionEntityCollection.hpp"
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
// Length of a smooth path.
|
||||
double length(const SmoothPath &path)
|
||||
{
|
||||
double l = 0;
|
||||
for (const SmoothPathElement &el : path)
|
||||
l += Geometry::ArcWelder::path_length<double>(el.path);
|
||||
return l;
|
||||
}
|
||||
|
||||
// Returns true if the smooth path is longer than a threshold.
|
||||
bool longer_than(const SmoothPath &path, double length)
|
||||
{
|
||||
for (const SmoothPathElement &el : path) {
|
||||
for (auto it = std::next(el.path.begin()); it != el.path.end(); ++ it) {
|
||||
length -= Geometry::ArcWelder::segment_length<double>(*std::prev(it), *it);
|
||||
if (length < 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return length < 0;
|
||||
}
|
||||
|
||||
std::optional<Point> sample_path_point_at_distance_from_start(const SmoothPath &path, double distance)
|
||||
{
|
||||
if (distance >= 0) {
|
||||
for (const SmoothPathElement &el : path) {
|
||||
auto it = el.path.begin();
|
||||
auto end = el.path.end();
|
||||
Point prev_point = it->point;
|
||||
for (++ it; it != end; ++ it) {
|
||||
Point point = it->point;
|
||||
if (it->linear()) {
|
||||
// Linear segment
|
||||
Vec2d v = (point - prev_point).cast<double>();
|
||||
double lsqr = v.squaredNorm();
|
||||
if (lsqr > sqr(distance))
|
||||
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
|
||||
distance -= sqrt(lsqr);
|
||||
} else {
|
||||
// Circular segment
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), it->radius);
|
||||
double len = std::abs(it->radius) * angle;
|
||||
if (len > distance) {
|
||||
// Rotate the segment end point in reverse towards the start point.
|
||||
return std::make_optional<Point>(prev_point.rotated(- angle * (distance / len),
|
||||
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
|
||||
}
|
||||
distance -= len;
|
||||
}
|
||||
if (distance < 0)
|
||||
return std::make_optional<Point>(point);
|
||||
prev_point = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Failed.
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &path, double distance)
|
||||
{
|
||||
if (distance >= 0) {
|
||||
for (const SmoothPathElement& el : path) {
|
||||
auto it = el.path.begin();
|
||||
auto end = el.path.end();
|
||||
Point prev_point = it->point;
|
||||
for (++it; it != end; ++it) {
|
||||
Point point = it->point;
|
||||
if (it->linear()) {
|
||||
// Linear segment
|
||||
Vec2d v = (point - prev_point).cast<double>();
|
||||
double lsqr = v.squaredNorm();
|
||||
if (lsqr > sqr(distance))
|
||||
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
|
||||
distance -= sqrt(lsqr);
|
||||
}
|
||||
else {
|
||||
// Circular segment
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), it->radius);
|
||||
double len = std::abs(it->radius) * angle;
|
||||
if (len > distance) {
|
||||
// Rotate the segment end point in reverse towards the start point.
|
||||
return std::make_optional<Point>(prev_point.rotated(-angle * (distance / len),
|
||||
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
|
||||
}
|
||||
distance -= len;
|
||||
}
|
||||
if (distance < 0)
|
||||
return std::make_optional<Point>(point);
|
||||
prev_point = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Failed.
|
||||
return {};
|
||||
}
|
||||
|
||||
double clip_end(SmoothPath &path, double distance)
|
||||
{
|
||||
while (! path.empty() && distance > 0) {
|
||||
Geometry::ArcWelder::Path &p = path.back().path;
|
||||
distance = clip_end(p, distance);
|
||||
if (p.empty()) {
|
||||
path.pop_back();
|
||||
} else {
|
||||
// Trailing path was trimmed and it is valid.
|
||||
assert(path.back().path.size() > 1);
|
||||
assert(distance == 0);
|
||||
// Distance to go is zero.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Return distance to go after the whole smooth path was trimmed to zero.
|
||||
return distance;
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
double tolerance = params.tolerance;
|
||||
if (path.role().is_sparse_infill())
|
||||
// Use 3x lower resolution than the object fine detail for sparse infill.
|
||||
tolerance *= 3.;
|
||||
else if (path.role().is_support())
|
||||
// Use 4x lower resolution than the object fine detail for support.
|
||||
tolerance *= 4.;
|
||||
else if (path.role().is_skirt())
|
||||
// Brim is currently marked as skirt.
|
||||
// Use 4x lower resolution than the object fine detail for skirt & brim.
|
||||
tolerance *= 4.;
|
||||
m_cache[&path.polyline] = Slic3r::Geometry::ArcWelder::fit_path(path.polyline.points, tolerance, params.fit_circle_tolerance);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionMultiPath &multi_path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionPath &path : multi_path.paths)
|
||||
this->interpolate_add(path, params);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionPath &path : loop.paths)
|
||||
this->interpolate_add(path, params);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionEntity *ee : eec) {
|
||||
if (ee->is_collection())
|
||||
this->interpolate_add(*static_cast<const ExtrusionEntityCollection*>(ee), params);
|
||||
else if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(ee); path)
|
||||
this->interpolate_add(*path, params);
|
||||
else if (const ExtrusionMultiPath *multi_path = dynamic_cast<const ExtrusionMultiPath*>(ee); multi_path)
|
||||
this->interpolate_add(*multi_path, params);
|
||||
else if (const ExtrusionLoop *loop = dynamic_cast<const ExtrusionLoop*>(ee); loop)
|
||||
this->interpolate_add(*loop, params);
|
||||
else
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
const Geometry::ArcWelder::Path* SmoothPathCache::resolve(const Polyline *pl) const
|
||||
{
|
||||
auto it = m_cache.find(pl);
|
||||
return it == m_cache.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
const Geometry::ArcWelder::Path* SmoothPathCache::resolve(const ExtrusionPath &path) const
|
||||
{
|
||||
return this->resolve(&path.polyline);
|
||||
}
|
||||
|
||||
Geometry::ArcWelder::Path SmoothPathCache::resolve_or_fit(const ExtrusionPath &path, bool reverse, double tolerance) const
|
||||
{
|
||||
Geometry::ArcWelder::Path out;
|
||||
if (const Geometry::ArcWelder::Path *cached = this->resolve(path); cached)
|
||||
out = *cached;
|
||||
else
|
||||
out = Geometry::ArcWelder::fit_polyline(path.polyline.points, tolerance);
|
||||
if (reverse)
|
||||
Geometry::ArcWelder::reverse(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const
|
||||
{
|
||||
SmoothPath out;
|
||||
out.reserve(paths.size());
|
||||
if (reverse) {
|
||||
for (auto it = paths.crbegin(); it != paths.crend(); ++ it)
|
||||
out.push_back({ it->attributes(), this->resolve_or_fit(*it, true, resolution) });
|
||||
} else {
|
||||
for (auto it = paths.cbegin(); it != paths.cend(); ++ it)
|
||||
out.push_back({ it->attributes(), this->resolve_or_fit(*it, false, resolution) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionMultiPath &multipath, bool reverse, double resolution) const
|
||||
{
|
||||
return this->resolve_or_fit(multipath.paths, reverse, resolution);
|
||||
}
|
||||
|
||||
SmoothPath SmoothPathCache::resolve_or_fit_split_with_seam(
|
||||
const ExtrusionLoop &loop, const bool reverse, const double resolution,
|
||||
const Point &seam_point, const double seam_point_merge_distance_threshold) const
|
||||
{
|
||||
SmoothPath out = this->resolve_or_fit(loop.paths, reverse, resolution);
|
||||
assert(! out.empty());
|
||||
if (! out.empty()) {
|
||||
// Find a closest point on a vector of smooth paths.
|
||||
Geometry::ArcWelder::PathSegmentProjection proj;
|
||||
int proj_path = -1;
|
||||
for (const SmoothPathElement &el : out)
|
||||
if (Geometry::ArcWelder::PathSegmentProjection this_proj = Geometry::ArcWelder::point_to_path_projection(el.path, seam_point, proj.distance2);
|
||||
this_proj.valid()) {
|
||||
// Found a better (closer) projection.
|
||||
assert(this_proj.distance2 < proj.distance2);
|
||||
assert(this_proj.segment_id >= 0 && this_proj.segment_id < el.path.size());
|
||||
proj = this_proj;
|
||||
proj_path = &el - out.data();
|
||||
if (proj.distance2 == 0)
|
||||
// There will be no better split point found than one with zero distance.
|
||||
break;
|
||||
}
|
||||
assert(proj_path >= 0);
|
||||
// Split the path at the closest point.
|
||||
Geometry::ArcWelder::Path &path = out[proj_path].path;
|
||||
std::pair<Geometry::ArcWelder::Path, Geometry::ArcWelder::Path> split = Geometry::ArcWelder::split_at(
|
||||
path, proj, seam_point_merge_distance_threshold);
|
||||
if (split.second.empty()) {
|
||||
std::rotate(out.begin(), out.begin() + proj_path + 1, out.end());
|
||||
assert(out.back().path == split.first);
|
||||
} else {
|
||||
ExtrusionAttributes attr = out[proj_path].path_attributes;
|
||||
std::rotate(out.begin(), out.begin() + proj_path, out.end());
|
||||
out.front().path = std::move(split.second);
|
||||
if (! split.first.empty()) {
|
||||
if (out.back().path_attributes == attr) {
|
||||
// Merge with the last segment.
|
||||
out.back().path.insert(out.back().path.end(), std::next(split.first.begin()), split.first.end());
|
||||
} else
|
||||
out.push_back({ attr, std::move(split.first) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCode
|
87
src/libslic3r/GCode/SmoothPath.hpp
Normal file
87
src/libslic3r/GCode/SmoothPath.hpp
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef slic3r_GCode_SmoothPath_hpp_
|
||||
#define slic3r_GCode_SmoothPath_hpp_
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../Geometry/ArcWelder.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExtrusionEntityCollection;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
struct SmoothPathElement
|
||||
{
|
||||
ExtrusionAttributes path_attributes;
|
||||
Geometry::ArcWelder::Path path;
|
||||
};
|
||||
|
||||
using SmoothPath = std::vector<SmoothPathElement>;
|
||||
|
||||
// Length of a smooth path.
|
||||
double length(const SmoothPath &path);
|
||||
// Returns true if the smooth path is longer than a threshold.
|
||||
bool longer_than(const SmoothPath &path, const double length);
|
||||
|
||||
std::optional<Point> sample_path_point_at_distance_from_start(const SmoothPath &path, double distance);
|
||||
std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &path, double distance);
|
||||
|
||||
// Clip end of a smooth path, for seam hiding.
|
||||
double clip_end(SmoothPath &path, double distance);
|
||||
|
||||
class SmoothPathCache
|
||||
{
|
||||
public:
|
||||
struct InterpolationParameters {
|
||||
double tolerance;
|
||||
double fit_circle_tolerance;
|
||||
};
|
||||
|
||||
void interpolate_add(const ExtrusionPath &ee, const InterpolationParameters ¶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;
|
||||
};
|
||||
|
||||
// Encapsulates references to global and layer local caches of smooth extrusion paths.
|
||||
class SmoothPathCaches final
|
||||
{
|
||||
public:
|
||||
SmoothPathCaches() = delete;
|
||||
SmoothPathCaches(const SmoothPathCache &global, const SmoothPathCache &layer_local) :
|
||||
m_global(&global), m_layer_local(&layer_local) {}
|
||||
SmoothPathCaches operator=(const SmoothPathCaches &rhs)
|
||||
{ m_global = rhs.m_global; m_layer_local = rhs.m_layer_local; return *this; }
|
||||
|
||||
const SmoothPathCache& global() const { return *m_global; }
|
||||
const SmoothPathCache& layer_local() const { return *m_layer_local; }
|
||||
|
||||
private:
|
||||
const SmoothPathCache *m_global;
|
||||
const SmoothPathCache *m_layer_local;
|
||||
};
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCode_SmoothPath_hpp_
|
252
src/libslic3r/GCode/Wipe.cpp
Normal file
252
src/libslic3r/GCode/Wipe.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
#include "Wipe.hpp"
|
||||
#include "../GCode.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &extruders)
|
||||
{
|
||||
this->reset_path();
|
||||
|
||||
// Calculate maximum wipe length to accumulate by the wipe cache.
|
||||
// Paths longer than wipe_xy should never be needed for the wipe move.
|
||||
double wipe_xy = 0;
|
||||
const bool multimaterial = extruders.size() > 1;
|
||||
for (auto id : extruders)
|
||||
if (config.wipe.get_at(id)) {
|
||||
// Wipe length to extrusion ratio.
|
||||
const double xy_to_e = this->calc_xy_to_e_ratio(config, id);
|
||||
wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length.get_at(id));
|
||||
if (multimaterial)
|
||||
wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length_toolchange.get_at(id));
|
||||
}
|
||||
|
||||
if (wipe_xy == 0)
|
||||
this->disable();
|
||||
else
|
||||
this->enable(wipe_xy);
|
||||
}
|
||||
|
||||
void Wipe::set_path(SmoothPath &&path, bool reversed)
|
||||
{
|
||||
this->reset_path();
|
||||
|
||||
if (this->enabled() && ! path.empty()) {
|
||||
if (reversed) {
|
||||
m_path = std::move(path.back().path);
|
||||
Geometry::ArcWelder::reverse(m_path);
|
||||
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
|
||||
for (auto it = std::next(path.rbegin()); len < m_wipe_len_max && it != path.rend(); ++ it) {
|
||||
if (it->path_attributes.role.is_bridge())
|
||||
break; // Do not perform a wipe on bridges.
|
||||
assert(it->path.size() >= 2);
|
||||
assert(m_path.back().point == it->path.back().point);
|
||||
if (m_path.back().point != it->path.back().point)
|
||||
// ExtrusionMultiPath is interrupted in some place. This should not really happen.
|
||||
break;
|
||||
len += Geometry::ArcWelder::estimate_path_length(it->path);
|
||||
m_path.insert(m_path.end(), it->path.rbegin() + 1, it->path.rend());
|
||||
}
|
||||
} else {
|
||||
m_path = std::move(path.front().path);
|
||||
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
|
||||
for (auto it = std::next(path.begin()); len < m_wipe_len_max && it != path.end(); ++ it) {
|
||||
if (it->path_attributes.role.is_bridge())
|
||||
break; // Do not perform a wipe on bridges.
|
||||
assert(it->path.size() >= 2);
|
||||
assert(m_path.back().point == it->path.front().point);
|
||||
if (m_path.back().point != it->path.front().point)
|
||||
// ExtrusionMultiPath is interrupted in some place. This should not really happen.
|
||||
break;
|
||||
len += Geometry::ArcWelder::estimate_path_length(it->path);
|
||||
m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(m_path.empty() || m_path.size() > 1);
|
||||
}
|
||||
|
||||
std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange)
|
||||
{
|
||||
std::string gcode;
|
||||
const Extruder &extruder = *gcodegen.writer().extruder();
|
||||
static constexpr const std::string_view wipe_retract_comment = "wipe and retract"sv;
|
||||
|
||||
// Remaining quantized retraction length.
|
||||
if (double retract_length = extruder.retract_to_go(toolchange ? extruder.retract_length_toolchange() : extruder.retract_length());
|
||||
retract_length > 0 && this->has_path()) {
|
||||
// Delayed emitting of a wipe start tag.
|
||||
bool wiped = false;
|
||||
const double wipe_speed = this->calc_wipe_speed(gcodegen.writer().config);
|
||||
auto start_wipe = [&wiped, &gcode, &gcodegen, wipe_speed](){
|
||||
if (! wiped) {
|
||||
wiped = true;
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n";
|
||||
gcode += gcodegen.writer().set_speed(wipe_speed * 60, {}, gcodegen.enable_cooling_markers() ? ";_WIPE"sv : ""sv);
|
||||
}
|
||||
};
|
||||
const double xy_to_e = this->calc_xy_to_e_ratio(gcodegen.writer().config, extruder.id());
|
||||
auto wipe_linear = [&gcode, &gcodegen, &retract_length, xy_to_e](const Vec2d &prev_quantized, Vec2d &p) {
|
||||
Vec2d p_quantized = GCodeFormatter::quantize(p);
|
||||
if (p_quantized == prev_quantized) {
|
||||
p = p_quantized;
|
||||
return false;
|
||||
}
|
||||
double segment_length = (p_quantized - prev_quantized).norm();
|
||||
// Quantize E axis as it is to be extruded as a whole segment.
|
||||
double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length);
|
||||
bool done = false;
|
||||
if (dE > retract_length - EPSILON) {
|
||||
if (dE > retract_length + EPSILON)
|
||||
// Shorten the segment.
|
||||
p = GCodeFormatter::quantize(Vec2d(prev_quantized + (p - prev_quantized) * (retract_length / dE)));
|
||||
else
|
||||
p = p_quantized;
|
||||
dE = retract_length;
|
||||
done = true;
|
||||
} else
|
||||
p = p_quantized;
|
||||
gcode += gcodegen.writer().extrude_to_xy(p, -dE, wipe_retract_comment);
|
||||
retract_length -= dE;
|
||||
return done;
|
||||
};
|
||||
const bool emit_radius = gcodegen.config().arc_fitting == ArcFittingType::EmitRadius;
|
||||
auto wipe_arc = [&gcode, &gcodegen, &retract_length, xy_to_e, emit_radius](
|
||||
const Vec2d &prev_quantized, Vec2d &p, double radius_in, const bool ccw) {
|
||||
Vec2d p_quantized = GCodeFormatter::quantize(p);
|
||||
if (p_quantized == prev_quantized) {
|
||||
p = p_quantized;
|
||||
return false;
|
||||
}
|
||||
// Only quantize radius if emitting it directly into G-code. Otherwise use the exact radius for calculating the IJ values.
|
||||
double radius = emit_radius ? GCodeFormatter::quantize_xyzf(radius_in) : radius_in;
|
||||
Vec2d center = Geometry::ArcWelder::arc_center(prev_quantized.cast<double>(), p_quantized.cast<double>(), double(radius), ccw);
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast<double>(), p_quantized.cast<double>(), double(radius));
|
||||
assert(angle > 0);
|
||||
double segment_length = angle * std::abs(radius);
|
||||
double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length);
|
||||
bool done = false;
|
||||
if (dE > retract_length - EPSILON) {
|
||||
if (dE > retract_length + EPSILON) {
|
||||
// Shorten the segment. Recalculate the arc from the unquantized end coordinate.
|
||||
center = Geometry::ArcWelder::arc_center(prev_quantized.cast<double>(), p.cast<double>(), double(radius), ccw);
|
||||
angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast<double>(), p.cast<double>(), double(radius));
|
||||
segment_length = angle * std::abs(radius);
|
||||
dE = xy_to_e * segment_length;
|
||||
p = GCodeFormatter::quantize(
|
||||
Vec2d(center + Eigen::Rotation2D((ccw ? angle : -angle) * (retract_length / dE)) * (prev_quantized - center)));
|
||||
} else
|
||||
p = p_quantized;
|
||||
dE = retract_length;
|
||||
done = true;
|
||||
} else
|
||||
p = p_quantized;
|
||||
assert(dE > 0);
|
||||
if (emit_radius) {
|
||||
gcode += gcodegen.writer().extrude_to_xy_G2G3R(p, radius, ccw, -dE, wipe_retract_comment);
|
||||
} else {
|
||||
// Calculate quantized IJ circle center offset.
|
||||
gcode += gcodegen.writer().extrude_to_xy_G2G3IJ(
|
||||
p, GCodeFormatter::quantize(Vec2d(center - prev_quantized)), ccw, -dE, wipe_retract_comment);
|
||||
}
|
||||
retract_length -= dE;
|
||||
return done;
|
||||
};
|
||||
// Start with the current position, which may be different from the wipe path start in case of loop clipping.
|
||||
Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos());
|
||||
auto it = this->path().begin();
|
||||
Vec2d p = gcodegen.point_to_gcode(it->point + m_offset);
|
||||
++ it;
|
||||
bool done = false;
|
||||
if (p != prev) {
|
||||
start_wipe();
|
||||
done = wipe_linear(prev, p);
|
||||
}
|
||||
if (! done) {
|
||||
prev = p;
|
||||
auto end = this->path().end();
|
||||
for (; it != end && ! done; ++ it) {
|
||||
p = gcodegen.point_to_gcode(it->point + m_offset);
|
||||
if (p != prev) {
|
||||
start_wipe();
|
||||
if (it->linear() ?
|
||||
wipe_linear(prev, p) :
|
||||
wipe_arc(prev, p, unscaled<double>(it->radius), it->ccw()))
|
||||
break;
|
||||
prev = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wiped) {
|
||||
// add tag for processor
|
||||
assert(p == GCodeFormatter::quantize(p));
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n";
|
||||
gcodegen.set_last_pos(gcodegen.gcode_to_point(p));
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent wiping again on the same path.
|
||||
this->reset_path();
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// Make a little move inwards before leaving loop after path was extruded,
|
||||
// thus the current extruder position is at the end of a path and the path
|
||||
// may not be closed in case the loop was clipped to hide a seam.
|
||||
std::optional<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length)
|
||||
{
|
||||
assert(! path.empty());
|
||||
assert(path.front().path.size() >= 2);
|
||||
assert(path.back().path.size() >= 2);
|
||||
|
||||
// Heuristics for estimating whether there is a chance that the wipe move will fit inside a small perimeter
|
||||
// or that the wipe move direction could be calculated with reasonable accuracy.
|
||||
if (longer_than(path, 2.5 * wipe_length)) {
|
||||
// The print head will be moved away from path end inside the island.
|
||||
Point p_current = path.back().path.back().point;
|
||||
Point p_next = path.front().path.front().point;
|
||||
Point p_prev;
|
||||
{
|
||||
// Is the seam hiding gap large enough already?
|
||||
double l = wipe_length - (p_next - p_current).cast<double>().norm();
|
||||
if (l > 0) {
|
||||
// Not yet.
|
||||
std::optional<Point> n = sample_path_point_at_distance_from_start(path, l);
|
||||
assert(n);
|
||||
if (! n)
|
||||
// Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above.
|
||||
return {};
|
||||
}
|
||||
if (std::optional<Point> p = sample_path_point_at_distance_from_end(path, wipe_length); p)
|
||||
p_prev = *p;
|
||||
else
|
||||
// Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above.
|
||||
return {};
|
||||
}
|
||||
// Detect angle between last and first segment.
|
||||
// The side depends on the original winding order of the polygon (left for contours, right for holes).
|
||||
double angle_inside = angle(p_next - p_current, p_prev - p_current);
|
||||
assert(angle_inside >= -M_PI && angle_inside <= M_PI);
|
||||
// 3rd of this angle will be taken, thus make the angle monotonic before interpolation.
|
||||
if (is_hole) {
|
||||
if (angle_inside > 0)
|
||||
angle_inside -= 2.0 * M_PI;
|
||||
} else {
|
||||
if (angle_inside < 0)
|
||||
angle_inside += 2.0 * M_PI;
|
||||
}
|
||||
// Rotate the forward segment inside by 1/3 of the wedge angle.
|
||||
auto v_rotated = Eigen::Rotation2D(angle_inside) * (p_next - p_current).cast<double>().normalized();
|
||||
return std::make_optional<Point>(p_current + (v_rotated * wipe_length).cast<coord_t>());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCode
|
73
src/libslic3r/GCode/Wipe.hpp
Normal file
73
src/libslic3r/GCode/Wipe.hpp
Normal file
@ -0,0 +1,73 @@
|
||||
#ifndef slic3r_GCode_Wipe_hpp_
|
||||
#define slic3r_GCode_Wipe_hpp_
|
||||
|
||||
#include "SmoothPath.hpp"
|
||||
|
||||
#include "../Geometry/ArcWelder.hpp"
|
||||
#include "../Point.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCodeGenerator;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
class Wipe {
|
||||
public:
|
||||
using Path = Slic3r::Geometry::ArcWelder::Path;
|
||||
|
||||
Wipe() = default;
|
||||
|
||||
void init(const PrintConfig &config, const std::vector<unsigned int> &extruders);
|
||||
void enable(double wipe_len_max) { m_enabled = true; m_wipe_len_max = wipe_len_max; }
|
||||
void disable() { m_enabled = false; }
|
||||
bool enabled() const { return m_enabled; }
|
||||
|
||||
const Path& path() const { return m_path; }
|
||||
bool has_path() const { assert(m_path.empty() || m_path.size() > 1); return ! m_path.empty(); }
|
||||
void reset_path() { m_path.clear(); m_offset = Point::Zero(); }
|
||||
void set_path(const Path &path) {
|
||||
assert(path.empty() || path.size() > 1);
|
||||
this->reset_path();
|
||||
if (this->enabled() && path.size() > 1)
|
||||
m_path = path;
|
||||
}
|
||||
void set_path(Path &&path) {
|
||||
assert(path.empty() || path.size() > 1);
|
||||
this->reset_path();
|
||||
if (this->enabled() && path.size() > 1)
|
||||
m_path = std::move(path);
|
||||
}
|
||||
void set_path(SmoothPath &&path, bool reversed);
|
||||
void offset_path(const Point &v) { m_offset += v; }
|
||||
|
||||
std::string wipe(GCodeGenerator &gcodegen, bool toolchange);
|
||||
|
||||
// Reduce feedrate a bit; travel speed is often too high to move on existing material.
|
||||
// Too fast = ripping of existing material; too slow = short wipe path, thus more blob.
|
||||
static double calc_wipe_speed(const GCodeConfig &config) { return config.travel_speed.value * 0.8; }
|
||||
// Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
|
||||
// due to rounding (TODO: test and/or better math for this).
|
||||
static double calc_xy_to_e_ratio(const GCodeConfig &config, unsigned int extruder_id)
|
||||
{ return 0.95 * floor(config.retract_speed.get_at(extruder_id) + 0.5) / calc_wipe_speed(config); }
|
||||
|
||||
private:
|
||||
bool m_enabled{ false };
|
||||
// Maximum length of a path to accumulate. Only wipes shorter than this threshold will be requested.
|
||||
double m_wipe_len_max{ 0. };
|
||||
Path m_path;
|
||||
// Offset from m_path to the current PrintObject active.
|
||||
Point m_offset{ Point::Zero() };
|
||||
};
|
||||
|
||||
// Make a little move inwards before leaving loop.
|
||||
std::optional<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length);
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCode_Wipe_hpp_
|
@ -1,5 +1,5 @@
|
||||
#ifndef WipeTower_
|
||||
#define WipeTower_
|
||||
#ifndef slic3r_GCode_WipeTower_hpp_
|
||||
#define slic3r_GCode_WipeTower_hpp_
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
@ -411,4 +411,4 @@ private:
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // WipeTowerPrusaMM_hpp_
|
||||
#endif // slic3r_GCode_WipeTower_hpp_
|
||||
|
251
src/libslic3r/GCode/WipeTowerIntegration.cpp
Normal file
251
src/libslic3r/GCode/WipeTowerIntegration.cpp
Normal file
@ -0,0 +1,251 @@
|
||||
#include "WipeTowerIntegration.hpp"
|
||||
|
||||
#include "../GCode.hpp"
|
||||
#include "../libslic3r.h"
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
static inline Point wipe_tower_point_to_object_point(GCodeGenerator &gcodegen, const Vec2f& wipe_tower_pt)
|
||||
{
|
||||
return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1)));
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
|
||||
{
|
||||
if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
|
||||
throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
|
||||
|
||||
std::string gcode;
|
||||
|
||||
// Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
|
||||
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
|
||||
float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
||||
|
||||
auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f {
|
||||
Vec2f out = Eigen::Rotation2Df(alpha) * pt;
|
||||
out += m_wipe_tower_pos;
|
||||
return out;
|
||||
};
|
||||
|
||||
Vec2f start_pos = tcr.start_pos;
|
||||
Vec2f end_pos = tcr.end_pos;
|
||||
if (! tcr.priming) {
|
||||
start_pos = transform_wt_pt(start_pos);
|
||||
end_pos = transform_wt_pt(end_pos);
|
||||
}
|
||||
|
||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
|
||||
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
|
||||
|
||||
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
|
||||
|
||||
gcode += gcodegen.writer().unlift(); // Make sure there is no z-hop (in most cases, there isn't).
|
||||
|
||||
double current_z = gcodegen.writer().get_position().z();
|
||||
if (z == -1.) // in case no specific z was provided, print at current_z pos
|
||||
z = current_z;
|
||||
|
||||
const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id);
|
||||
const bool will_go_down = ! is_approx(z, current_z);
|
||||
const bool is_ramming = (gcodegen.config().single_extruder_multi_material && ! tcr.priming)
|
||||
|| (! gcodegen.config().single_extruder_multi_material && gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool));
|
||||
const bool should_travel_to_tower = tcr.force_travel // wipe tower says so
|
||||
|| ! needs_toolchange // this is just finishing the tower with no toolchange
|
||||
|| is_ramming;
|
||||
if (should_travel_to_tower) {
|
||||
gcode += gcodegen.retract();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcode += gcodegen.travel_to(
|
||||
wipe_tower_point_to_object_point(gcodegen, start_pos),
|
||||
ExtrusionRole::Mixed,
|
||||
"Travel to a Wipe Tower");
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
// When this is multiextruder printer without any ramming, we can just change
|
||||
// the tool without travelling to the tower.
|
||||
}
|
||||
|
||||
if (will_go_down) {
|
||||
gcode += gcodegen.writer().retract();
|
||||
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
|
||||
gcode += gcodegen.writer().unretract();
|
||||
}
|
||||
|
||||
std::string toolchange_gcode_str;
|
||||
std::string deretraction_str;
|
||||
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
|
||||
if (is_ramming)
|
||||
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
|
||||
if (gcodegen.config().wipe_tower)
|
||||
deretraction_str = gcodegen.unretract();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Insert the toolchange and deretraction gcode into the generated gcode.
|
||||
DynamicConfig config;
|
||||
config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str));
|
||||
config.set_key_value("deretraction_from_wipe_tower_generator", new ConfigOptionString(deretraction_str));
|
||||
std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config);
|
||||
unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode);
|
||||
gcode += tcr_gcode;
|
||||
if (! toolchange_gcode_str.empty() && toolchange_gcode_str.back() != '\n')
|
||||
toolchange_gcode_str += '\n';
|
||||
|
||||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
|
||||
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
|
||||
if (!is_approx(z, current_z)) {
|
||||
gcode += gcodegen.writer().retract();
|
||||
gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
|
||||
gcode += gcodegen.writer().unretract();
|
||||
}
|
||||
|
||||
else {
|
||||
// Prepare a future wipe.
|
||||
// Convert to a smooth path.
|
||||
Geometry::ArcWelder::Path path;
|
||||
path.reserve(tcr.wipe_path.size());
|
||||
std::transform(tcr.wipe_path.begin(), tcr.wipe_path.end(), std::back_inserter(path),
|
||||
[&gcodegen, &transform_wt_pt](const Vec2f &wipe_pt) {
|
||||
return Geometry::ArcWelder::Segment{ wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt)) };
|
||||
});
|
||||
// Pass to the wipe cache.
|
||||
gcodegen.m_wipe.set_path(std::move(path));
|
||||
}
|
||||
|
||||
// Let the planner know we are traveling between objects.
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
|
||||
// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
|
||||
std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const
|
||||
{
|
||||
Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast<float>();
|
||||
|
||||
std::istringstream gcode_str(tcr.gcode);
|
||||
std::string gcode_out;
|
||||
std::string line;
|
||||
Vec2f pos = tcr.start_pos;
|
||||
Vec2f transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
|
||||
Vec2f old_pos(-1000.1f, -1000.1f);
|
||||
|
||||
while (gcode_str) {
|
||||
std::getline(gcode_str, line); // we read the gcode line by line
|
||||
|
||||
// All G1 commands should be translated and rotated. X and Y coords are
|
||||
// only pushed to the output when they differ from last time.
|
||||
// WT generator can override this by appending the never_skip_tag
|
||||
if (boost::starts_with(line, "G1 ")) {
|
||||
bool never_skip = false;
|
||||
auto it = line.find(WipeTower::never_skip_tag());
|
||||
if (it != std::string::npos) {
|
||||
// remove the tag and remember we saw it
|
||||
never_skip = true;
|
||||
line.erase(it, it + WipeTower::never_skip_tag().size());
|
||||
}
|
||||
std::ostringstream line_out;
|
||||
std::istringstream line_str(line);
|
||||
line_str >> std::noskipws; // don't skip whitespace
|
||||
char ch = 0;
|
||||
line_str >> ch >> ch; // read the "G1"
|
||||
while (line_str >> ch) {
|
||||
if (ch == 'X' || ch == 'Y')
|
||||
line_str >> (ch == 'X' ? pos.x() : pos.y());
|
||||
else
|
||||
line_out << ch;
|
||||
}
|
||||
|
||||
transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
|
||||
|
||||
if (transformed_pos != old_pos || never_skip) {
|
||||
line = line_out.str();
|
||||
boost::trim_left(line); // Remove leading spaces
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(3) << "G1";
|
||||
if (transformed_pos.x() != old_pos.x() || never_skip)
|
||||
oss << " X" << transformed_pos.x() - extruder_offset.x();
|
||||
if (transformed_pos.y() != old_pos.y() || never_skip)
|
||||
oss << " Y" << transformed_pos.y() - extruder_offset.y();
|
||||
if (! line.empty())
|
||||
oss << " ";
|
||||
line = oss.str() + line;
|
||||
old_pos = transformed_pos;
|
||||
}
|
||||
}
|
||||
|
||||
gcode_out += line + "\n";
|
||||
|
||||
// If this was a toolchange command, we should change current extruder offset
|
||||
if (line == "[toolchange_gcode]") {
|
||||
extruder_offset = m_extruder_offsets[tcr.new_tool].cast<float>();
|
||||
|
||||
// If the extruder offset changed, add an extra move so everything is continuous
|
||||
if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) {
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(3)
|
||||
<< "G1 X" << transformed_pos.x() - extruder_offset.x()
|
||||
<< " Y" << transformed_pos.y() - extruder_offset.y()
|
||||
<< "\n";
|
||||
gcode_out += oss.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
return gcode_out;
|
||||
}
|
||||
|
||||
|
||||
std::string WipeTowerIntegration::prime(GCodeGenerator &gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
for (const WipeTower::ToolChangeResult& tcr : m_priming) {
|
||||
if (! tcr.extrusions.empty())
|
||||
gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
|
||||
}
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer)
|
||||
{
|
||||
std::string gcode;
|
||||
assert(m_layer_idx >= 0);
|
||||
if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
|
||||
if (m_layer_idx < (int)m_tool_changes.size()) {
|
||||
if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
|
||||
throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer.");
|
||||
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
// resulting in a wipe tower with sparse layers.
|
||||
double wipe_tower_z = -1;
|
||||
bool ignore_sparse = false;
|
||||
if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
|
||||
wipe_tower_z = m_last_wipe_tower_print_z;
|
||||
ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool && m_layer_idx != 0);
|
||||
if (m_tool_change_idx == 0 && !ignore_sparse)
|
||||
wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
|
||||
}
|
||||
|
||||
if (!ignore_sparse) {
|
||||
gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
|
||||
m_last_wipe_tower_print_z = wipe_tower_z;
|
||||
}
|
||||
}
|
||||
}
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
|
||||
std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
|
||||
gcode += gcodegen.change_layer(m_final_purge.print_z);
|
||||
gcode += append_tcr(gcodegen, m_final_purge, -1);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCode
|
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_
|
674
src/libslic3r/Geometry/ArcWelder.cpp
Normal file
674
src/libslic3r/Geometry/ArcWelder.cpp
Normal file
@ -0,0 +1,674 @@
|
||||
// The following code for merging circles into arches originates from https://github.com/FormerLurker/ArcWelderLib
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Arc Welder: Anti-Stutter Library
|
||||
//
|
||||
// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution.
|
||||
// This reduces file size and the number of gcodes per second.
|
||||
//
|
||||
// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality.
|
||||
//
|
||||
// Copyright(C) 2021 - Brad Hochgesang
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// This program is free software : you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
//
|
||||
// You can contact the author at the following email address:
|
||||
// FormerLurker@pm.me
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "ArcWelder.hpp"
|
||||
#include "Circle.hpp"
|
||||
|
||||
#include "../MultiPoint.hpp"
|
||||
#include "../Polygon.hpp"
|
||||
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r { namespace Geometry { namespace ArcWelder {
|
||||
|
||||
Points arc_discretize(const Point &p1, const Point &p2, const double radius, const bool ccw, const double deviation)
|
||||
{
|
||||
Vec2d center = arc_center(p1.cast<double>(), p2.cast<double>(), radius, ccw);
|
||||
double angle = arc_angle(p1.cast<double>(), p2.cast<double>(), radius);
|
||||
assert(angle > 0);
|
||||
|
||||
double r = std::abs(radius);
|
||||
size_t num_steps = arc_discretization_steps(r, angle, deviation);
|
||||
double angle_step = angle / num_steps;
|
||||
|
||||
Points out;
|
||||
out.reserve(num_steps + 1);
|
||||
out.emplace_back(p1);
|
||||
if (! ccw)
|
||||
angle_step *= -1.;
|
||||
for (size_t i = 1; i < num_steps; ++ i)
|
||||
out.emplace_back(p1.rotated(angle_step * i, center.cast<coord_t>()));
|
||||
out.emplace_back(p2);
|
||||
return out;
|
||||
}
|
||||
|
||||
struct Circle
|
||||
{
|
||||
Point center;
|
||||
double radius;
|
||||
};
|
||||
|
||||
// Interpolate three points with a circle.
|
||||
// Returns false if the three points are collinear or if the radius is bigger than maximum allowed radius.
|
||||
static std::optional<Circle> try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius)
|
||||
{
|
||||
if (auto center = Slic3r::Geometry::try_circle_center(p1.cast<double>(), p2.cast<double>(), p3.cast<double>(), SCALED_EPSILON); center) {
|
||||
Point c = center->cast<coord_t>();
|
||||
if (double r = sqrt(double((c - p1).cast<int64_t>().squaredNorm())); r <= max_radius)
|
||||
return std::make_optional<Circle>({ c, float(r) });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Returns a closest point on the segment.
|
||||
// Returns false if the closest point is not inside the segment, but at its boundary.
|
||||
static bool foot_pt_on_segment(const Point &p1, const Point &p2, const Point &pt, Point &out)
|
||||
{
|
||||
Vec2i64 v21 = (p2 - p1).cast<int64_t>();
|
||||
int64_t l2 = v21.squaredNorm();
|
||||
if (l2 > int64_t(SCALED_EPSILON)) {
|
||||
if (int64_t t = (pt - p1).cast<int64_t>().dot(v21);
|
||||
t >= int64_t(SCALED_EPSILON) && t < l2 - int64_t(SCALED_EPSILON)) {
|
||||
out = p1 + ((double(t) / double(l2)) * v21.cast<double>()).cast<coord_t>();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// The segment is short or the closest point is an end point.
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool circle_approximation_sufficient(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance)
|
||||
{
|
||||
// The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated.
|
||||
assert(std::abs((*begin - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
|
||||
assert(std::abs((*std::prev(end) - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
|
||||
assert(end - begin >= 3);
|
||||
|
||||
// Test the 1st point.
|
||||
if (double distance_from_center = (*begin - circle.center).cast<double>().norm();
|
||||
std::abs(distance_from_center - circle.radius) > tolerance)
|
||||
return false;
|
||||
|
||||
for (auto it = std::next(begin); it != end; ++ it) {
|
||||
if (double distance_from_center = (*it - circle.center).cast<double>().norm();
|
||||
std::abs(distance_from_center - circle.radius) > tolerance)
|
||||
return false;
|
||||
Point closest_point;
|
||||
if (foot_pt_on_segment(*std::prev(it), *it, circle.center, closest_point)) {
|
||||
if (double distance_from_center = (closest_point - circle.center).cast<double>().norm();
|
||||
std::abs(distance_from_center - circle.radius) > tolerance)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool get_deviation_sum_squared(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance, double &total_deviation)
|
||||
{
|
||||
// The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated.
|
||||
assert(std::abs((*begin - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
|
||||
assert(std::abs((*std::prev(end) - circle.center).cast<double>().norm() - circle.radius) < SCALED_EPSILON);
|
||||
assert(end - begin >= 3);
|
||||
|
||||
total_deviation = 0;
|
||||
|
||||
const double tolerance2 = sqr(tolerance);
|
||||
for (auto it = std::next(begin); std::next(it) != end; ++ it)
|
||||
if (double deviation2 = sqr((*it - circle.center).cast<double>().norm() - circle.radius); deviation2 > tolerance2)
|
||||
return false;
|
||||
else
|
||||
total_deviation += deviation2;
|
||||
|
||||
for (auto it = begin; std::next(it) != end; ++ it) {
|
||||
Point closest_point;
|
||||
if (foot_pt_on_segment(*it, *std::next(it), circle.center, closest_point)) {
|
||||
if (double deviation2 = sqr((closest_point - circle.center).cast<double>().norm() - circle.radius); deviation2 > tolerance2)
|
||||
return false;
|
||||
else
|
||||
total_deviation += deviation2;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::optional<Circle> try_create_circle(const Points::const_iterator begin, const Points::const_iterator end, const double max_radius, const double tolerance)
|
||||
{
|
||||
std::optional<Circle> out;
|
||||
size_t size = end - begin;
|
||||
if (size == 3) {
|
||||
out = try_create_circle(*begin, *std::next(begin), *std::prev(end), max_radius);
|
||||
if (out && ! circle_approximation_sufficient(*out, begin, end, tolerance))
|
||||
out.reset();
|
||||
} else {
|
||||
#if 0
|
||||
size_t ipivot = size / 2;
|
||||
// Take a center difference of points at the center of the path.
|
||||
//FIXME does it really help? For short arches, the linear interpolation may be
|
||||
Point pivot = (size % 2 == 0) ? (*(begin + ipivot) + *(begin + ipivot - 1)) / 2 :
|
||||
(*(begin + ipivot - 1) + *(begin + ipivot + 1)) / 2;
|
||||
if (std::optional<Circle> circle = try_create_circle(*begin, pivot, *std::prev(end), max_radius);
|
||||
circle && circle_approximation_sufficient(*circle, begin, end, tolerance))
|
||||
return circle;
|
||||
#endif
|
||||
// Find the circle with the least deviation, if one exists.
|
||||
double least_deviation = std::numeric_limits<double>::max();
|
||||
double current_deviation;
|
||||
for (auto it = std::next(begin); std::next(it) != end; ++ it)
|
||||
if (std::optional<Circle> circle = try_create_circle(*begin, *it, *std::prev(end), max_radius);
|
||||
circle && get_deviation_sum_squared(*circle, begin, end, tolerance, current_deviation)) {
|
||||
if (current_deviation < least_deviation) {
|
||||
out = circle;
|
||||
least_deviation = current_deviation;
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ported from ArcWelderLib/ArcWelder/segmented/shape.h class "arc"
|
||||
class Arc {
|
||||
public:
|
||||
Point start_point;
|
||||
Point end_point;
|
||||
Point center;
|
||||
double radius;
|
||||
Orientation direction { Orientation::Unknown };
|
||||
};
|
||||
|
||||
static inline int sign(const int64_t i)
|
||||
{
|
||||
return i > 0 ? 1 : i < 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
// Return orientation of a polyline with regard to the center.
|
||||
// Successive points are expected to take less than a PI angle step.
|
||||
Orientation arc_orientation(
|
||||
const Point ¢er,
|
||||
const Points::const_iterator begin,
|
||||
const Points::const_iterator end)
|
||||
{
|
||||
assert(end - begin >= 3);
|
||||
// Assumption: Two successive points of a single segment span an angle smaller than PI.
|
||||
Vec2i64 vstart = (*begin - center).cast<int64_t>();
|
||||
Vec2i64 vprev = vstart;
|
||||
int arc_dir = 0;
|
||||
for (auto it = std::next(begin); it != end; ++ it) {
|
||||
Vec2i64 v = (*it - center).cast<int64_t>();
|
||||
int dir = sign(cross2(vprev, v));
|
||||
if (dir == 0) {
|
||||
// Ignore radial segments.
|
||||
} else if (arc_dir * dir < 0) {
|
||||
// The path turns back and overextrudes. Such path is likely invalid, but the arc interpolation should
|
||||
// rather maintain such an invalid path instead of covering it up.
|
||||
// Don't replace such a path with an arc.
|
||||
return {};
|
||||
} else {
|
||||
// Success, either establishing the direction for the first time, or moving in the same direction as the last time.
|
||||
arc_dir = dir;
|
||||
vprev = v;
|
||||
}
|
||||
}
|
||||
return arc_dir == 0 ?
|
||||
// All points are radial wrt. the center, this is unexpected.
|
||||
Orientation::Unknown :
|
||||
// Arc is valid, either CCW or CW.
|
||||
arc_dir > 0 ? Orientation::CCW : Orientation::CW;
|
||||
}
|
||||
|
||||
static inline std::optional<Arc> try_create_arc_impl(
|
||||
const Circle &circle,
|
||||
const Points::const_iterator begin,
|
||||
const Points::const_iterator end,
|
||||
const double tolerance,
|
||||
const double path_tolerance_percent)
|
||||
{
|
||||
assert(end - begin >= 3);
|
||||
// Assumption: Two successive points of a single segment span an angle smaller than PI.
|
||||
Orientation orientation = arc_orientation(circle.center, begin, end);
|
||||
if (orientation == Orientation::Unknown)
|
||||
return {};
|
||||
|
||||
Vec2i64 vstart = (*begin - circle.center).cast<int64_t>();
|
||||
Vec2i64 vend = (*std::prev(end) - circle.center).cast<int64_t>();
|
||||
double angle = atan2(double(cross2(vstart, vend)), double(vstart.dot(vend)));
|
||||
if (orientation == Orientation::CW)
|
||||
angle *= -1.;
|
||||
if (angle < 0)
|
||||
angle += 2. * M_PI;
|
||||
assert(angle >= 0. && angle < 2. * M_PI + EPSILON);
|
||||
|
||||
// Check the length against the original length.
|
||||
// This can trigger simply due to the differing path lengths
|
||||
// but also could indicate that the vector calculation above
|
||||
// got wrong direction
|
||||
const double arc_length = circle.radius * angle;
|
||||
const double approximate_length = length(begin, end);
|
||||
assert(approximate_length > 0);
|
||||
const double arc_length_difference_relative = (arc_length - approximate_length) / approximate_length;
|
||||
|
||||
if (std::fabs(arc_length_difference_relative) >= path_tolerance_percent) {
|
||||
return {};
|
||||
} else {
|
||||
assert(circle_approximation_sufficient(circle, begin, end, tolerance + SCALED_EPSILON));
|
||||
return std::make_optional<Arc>(Arc{
|
||||
*begin,
|
||||
*std::prev(end),
|
||||
circle.center,
|
||||
angle > M_PI ? - circle.radius : circle.radius,
|
||||
orientation
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::optional<Arc> try_create_arc(
|
||||
const Points::const_iterator begin,
|
||||
const Points::const_iterator end,
|
||||
double max_radius = default_scaled_max_radius,
|
||||
double tolerance = default_scaled_resolution,
|
||||
double path_tolerance_percent = default_arc_length_percent_tolerance)
|
||||
{
|
||||
std::optional<Circle> circle = try_create_circle(begin, end, max_radius, tolerance);
|
||||
if (! circle)
|
||||
return {};
|
||||
return try_create_arc_impl(*circle, begin, end, tolerance, path_tolerance_percent);
|
||||
}
|
||||
|
||||
float arc_angle(const Vec2f &start_pos, const Vec2f &end_pos, Vec2f ¢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.
|
||||
// Index of the start of a last polyline, which has not yet been decimated.
|
||||
int begin_pl_idx = 0;
|
||||
out.push_back({ src.front(), 0.f });
|
||||
for (auto it = std::next(src.begin()); it != src.end();) {
|
||||
// Minimum 2 additional points required for circle fitting.
|
||||
auto begin = std::prev(it);
|
||||
auto end = std::next(it);
|
||||
assert(end <= src.end());
|
||||
std::optional<Arc> arc;
|
||||
while (end != src.end()) {
|
||||
auto next_end = std::next(end);
|
||||
if (std::optional<Arc> this_arc = try_create_arc(
|
||||
begin, next_end,
|
||||
ArcWelder::default_scaled_max_radius,
|
||||
tolerance, fit_circle_percent_tolerance);
|
||||
this_arc) {
|
||||
assert(this_arc->direction != Orientation::Unknown);
|
||||
arc = this_arc;
|
||||
end = next_end;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
if (arc) {
|
||||
// If there is a trailing polyline, decimate it first before saving a new arc.
|
||||
if (out.size() - begin_pl_idx > 2) {
|
||||
// Decimating linear segmens only.
|
||||
assert(std::all_of(out.begin() + begin_pl_idx + 1, out.end(), [](const Segment &seg) { return seg.linear(); }));
|
||||
out.erase(douglas_peucker_in_place(out.begin() + begin_pl_idx, out.end(), tolerance), out.end());
|
||||
assert(out.back().linear());
|
||||
}
|
||||
// Save the index of an end of the new circle segment, which may become the first point of a possible future polyline.
|
||||
begin_pl_idx = int(out.size());
|
||||
// This will be the next point to try to add.
|
||||
it = end;
|
||||
// Add the new arc.
|
||||
assert(*begin == arc->start_point);
|
||||
assert(*std::prev(it) == arc->end_point);
|
||||
assert(out.back().point == arc->start_point);
|
||||
out.push_back({ arc->end_point, float(arc->radius), arc->direction });
|
||||
#if 0
|
||||
// Verify that all the source points are at tolerance distance from the interpolated path.
|
||||
{
|
||||
const Segment &seg_start = *std::prev(std::prev(out.end()));
|
||||
const Segment &seg_end = out.back();
|
||||
const Vec2d center = arc_center(seg_start.point.cast<double>(), seg_end.point.cast<double>(), double(seg_end.radius), seg_end.ccw());
|
||||
assert(seg_start.point == *begin);
|
||||
assert(seg_end.point == *std::prev(end));
|
||||
assert(arc_orientation(center.cast<coord_t>(), begin, end) == arc->direction);
|
||||
for (auto it = std::next(begin); it != end; ++ it) {
|
||||
Point ptstart = *std::prev(it);
|
||||
Point ptend = *it;
|
||||
Point closest_point;
|
||||
if (foot_pt_on_segment(ptstart, ptend, center.cast<coord_t>(), closest_point)) {
|
||||
double distance_from_center = (closest_point.cast<double>() - center).norm();
|
||||
assert(std::abs(distance_from_center - std::abs(seg_end.radius)) < tolerance + SCALED_EPSILON);
|
||||
}
|
||||
Vec2d v = (ptend - ptstart).cast<double>();
|
||||
double len = v.norm();
|
||||
auto num_segments = std::min<size_t>(10, ceil(2. * len / fit_circle_percent_tolerance));
|
||||
for (size_t i = 0; i < num_segments; ++ i) {
|
||||
Point p = ptstart + (v * (double(i) / double(num_segments))).cast<coord_t>();
|
||||
assert(i == 0 || inside_arc_wedge(seg_start.point.cast<double>(), seg_end.point.cast<double>(), center, seg_end.radius > 0, seg_end.ccw(), p.cast<double>()));
|
||||
double d2 = sqr((p.cast<double>() - center).norm() - std::abs(seg_end.radius));
|
||||
assert(d2 < sqr(tolerance + SCALED_EPSILON));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
// Arc is not valid, append a linear segment.
|
||||
out.push_back({ *it ++ });
|
||||
}
|
||||
}
|
||||
if (out.size() - begin_pl_idx > 2)
|
||||
// Do the final polyline decimation.
|
||||
out.erase(douglas_peucker_in_place(out.begin() + begin_pl_idx, out.end(), tolerance), out.end());
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Verify that all the source points are at tolerance distance from the interpolated path.
|
||||
for (auto it = std::next(src.begin()); it != src.end(); ++ it) {
|
||||
Point start = *std::prev(it);
|
||||
Point end = *it;
|
||||
Vec2d v = (end - start).cast<double>();
|
||||
double len = v.norm();
|
||||
auto num_segments = std::min<size_t>(10, ceil(2. * len / fit_circle_percent_tolerance));
|
||||
for (size_t i = 0; i <= num_segments; ++ i) {
|
||||
Point p = start + (v * (double(i) / double(num_segments))).cast<coord_t>();
|
||||
PathSegmentProjection proj = point_to_path_projection(out, p);
|
||||
assert(proj.valid());
|
||||
assert(proj.distance2 < sqr(tolerance + SCALED_EPSILON));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void reverse(Path &path)
|
||||
{
|
||||
if (path.size() > 1) {
|
||||
auto prev = path.begin();
|
||||
for (auto it = std::next(prev); it != path.end(); ++ it) {
|
||||
prev->radius = it->radius;
|
||||
prev->orientation = it->orientation == Orientation::CCW ? Orientation::CW : Orientation::CCW;
|
||||
prev = it;
|
||||
}
|
||||
path.back().radius = 0;
|
||||
std::reverse(path.begin(), path.end());
|
||||
}
|
||||
}
|
||||
|
||||
double clip_start(Path &path, const double len)
|
||||
{
|
||||
reverse(path);
|
||||
double remaining = clip_end(path, len);
|
||||
reverse(path);
|
||||
// Return remaining distance to go.
|
||||
return remaining;
|
||||
}
|
||||
|
||||
double clip_end(Path &path, double distance)
|
||||
{
|
||||
while (distance > 0) {
|
||||
Segment &last = path.back();
|
||||
path.pop_back();
|
||||
if (path.empty())
|
||||
break;
|
||||
if (last.linear()) {
|
||||
// Linear segment
|
||||
Vec2d v = (path.back().point - last.point).cast<double>();
|
||||
double lsqr = v.squaredNorm();
|
||||
if (lsqr > sqr(distance)) {
|
||||
path.push_back({ last.point + (v * (distance / sqrt(lsqr))).cast<coord_t>() });
|
||||
// Length to go is zero.
|
||||
return 0;
|
||||
}
|
||||
distance -= sqrt(lsqr);
|
||||
} else {
|
||||
// Circular segment
|
||||
double angle = arc_angle(path.back().point.cast<double>(), last.point.cast<double>(), last.radius);
|
||||
double len = std::abs(last.radius) * angle;
|
||||
if (len > distance) {
|
||||
// Rotate the segment end point in reverse towards the start point.
|
||||
if (last.ccw())
|
||||
angle *= -1.;
|
||||
path.push_back({
|
||||
last.point.rotated(angle * (distance / len),
|
||||
arc_center(path.back().point.cast<double>(), last.point.cast<double>(), double(last.radius), last.ccw()).cast<coord_t>()),
|
||||
last.radius, last.orientation });
|
||||
// Length to go is zero.
|
||||
return 0;
|
||||
}
|
||||
distance -= len;
|
||||
}
|
||||
}
|
||||
|
||||
// Return remaining distance to go.
|
||||
assert(distance >= 0);
|
||||
return distance;
|
||||
}
|
||||
|
||||
PathSegmentProjection point_to_path_projection(const Path &path, const Point &point, double search_radius2)
|
||||
{
|
||||
assert(path.size() != 1);
|
||||
// initialized to "invalid" state.
|
||||
PathSegmentProjection out;
|
||||
out.distance2 = search_radius2;
|
||||
if (path.size() < 2 || path.front().point == point) {
|
||||
// First point is the closest point.
|
||||
if (path.empty()) {
|
||||
// No closest point available.
|
||||
} else if (const Point p0 = path.front().point; p0 == point) {
|
||||
out.segment_id = 0;
|
||||
out.point = p0;
|
||||
out.distance2 = 0;
|
||||
} else if (double d2 = (p0 - point).cast<double>().squaredNorm(); d2 < out.distance2) {
|
||||
out.segment_id = 0;
|
||||
out.point = p0;
|
||||
out.distance2 = d2;
|
||||
}
|
||||
} else {
|
||||
assert(path.size() >= 2);
|
||||
// min_point_it will contain an end point of a segment with a closest projection found
|
||||
// or path.cbegin() if no such closest projection closer than search_radius2 was found.
|
||||
auto min_point_it = path.cbegin();
|
||||
Point prev = path.front().point;
|
||||
for (auto it = std::next(path.cbegin()); it != path.cend(); ++ it) {
|
||||
if (it->linear()) {
|
||||
// Linear segment
|
||||
Point proj;
|
||||
// distance_to_squared() will possibly return the start or end point of a line segment.
|
||||
if (double d2 = line_alg::distance_to_squared(Line(prev, it->point), point, &proj); d2 < out.distance2) {
|
||||
out.point = proj;
|
||||
out.distance2 = d2;
|
||||
min_point_it = it;
|
||||
}
|
||||
} else {
|
||||
// Circular arc
|
||||
Vec2i64 center = arc_center(prev.cast<double>(), it->point.cast<double>(), double(it->radius), it->ccw()).cast<int64_t>();
|
||||
// Test whether point is inside the wedge.
|
||||
Vec2i64 v1 = prev.cast<int64_t>() - center;
|
||||
Vec2i64 v2 = it->point.cast<int64_t>() - center;
|
||||
Vec2i64 vp = point.cast<int64_t>() - center;
|
||||
if (inside_arc_wedge_vectors(v1, v2, it->radius > 0, it->ccw(), vp)) {
|
||||
// Distance of the radii.
|
||||
const auto r = double(std::abs(it->radius));
|
||||
const auto rtest = sqrt(double(vp.squaredNorm()));
|
||||
if (double d2 = sqr(rtest - r); d2 < out.distance2) {
|
||||
if (rtest > SCALED_EPSILON)
|
||||
// Project vp to the arc.
|
||||
out.point = center.cast<coord_t>() + (vp.cast<double>() * (r / rtest)).cast<coord_t>();
|
||||
else
|
||||
// Test point is very close to the center of the radius. Any point of the arc is the closest.
|
||||
// Pick the start.
|
||||
out.point = prev;
|
||||
out.distance2 = d2;
|
||||
out.center = center.cast<coord_t>();
|
||||
min_point_it = it;
|
||||
}
|
||||
} else {
|
||||
// Distance to the start point.
|
||||
if (double d2 = double((v1 - vp).squaredNorm()); d2 < out.distance2) {
|
||||
out.point = prev;
|
||||
out.distance2 = d2;
|
||||
min_point_it = it;
|
||||
}
|
||||
}
|
||||
}
|
||||
prev = it->point;
|
||||
}
|
||||
if (! path.back().linear()) {
|
||||
// Calculate distance to the end point.
|
||||
if (double d2 = (path.back().point - point).cast<double>().squaredNorm(); d2 < out.distance2) {
|
||||
out.point = path.back().point;
|
||||
out.distance2 = d2;
|
||||
min_point_it = std::prev(path.end());
|
||||
}
|
||||
}
|
||||
// If a new closes point was found, it is closer than search_radius2.
|
||||
assert((min_point_it == path.cbegin()) == (out.distance2 == search_radius2));
|
||||
// Output is not valid yet.
|
||||
assert(! out.valid());
|
||||
if (min_point_it != path.cbegin()) {
|
||||
// Make it valid by setting the segment.
|
||||
out.segment_id = std::prev(min_point_it) - path.begin();
|
||||
assert(out.valid());
|
||||
}
|
||||
}
|
||||
|
||||
assert(! out.valid() || (out.segment_id >= 0 && out.segment_id < path.size()));
|
||||
return out;
|
||||
}
|
||||
|
||||
std::pair<Path, Path> split_at(const Path &path, const PathSegmentProjection &proj, const double min_segment_length)
|
||||
{
|
||||
assert(proj.valid());
|
||||
assert(! proj.valid() || (proj.segment_id >= 0 && proj.segment_id < path.size()));
|
||||
assert(path.size() > 1);
|
||||
std::pair<Path, Path> out;
|
||||
if (! proj.valid() || proj.segment_id + 1 == path.size() || (proj.segment_id + 2 == path.size() && proj.point == path.back().point))
|
||||
out.first = path;
|
||||
else if (proj.segment_id == 0 && proj.point == path.front().point)
|
||||
out.second = path;
|
||||
else {
|
||||
// Path will likely be split to two pieces.
|
||||
assert(proj.valid() && proj.segment_id >= 0 && proj.segment_id + 1 < path.size());
|
||||
const Segment &start = path[proj.segment_id];
|
||||
const Segment &end = path[proj.segment_id + 1];
|
||||
bool split_segment = true;
|
||||
int split_segment_id = proj.segment_id;
|
||||
if (int64_t d2 = (proj.point - start.point).cast<int64_t>().squaredNorm(); d2 < sqr(min_segment_length)) {
|
||||
split_segment = false;
|
||||
int64_t d22 = (proj.point - end.point).cast<int64_t>().squaredNorm();
|
||||
if (d22 < d2)
|
||||
// Split at the end of the segment.
|
||||
++ split_segment_id;
|
||||
} else if (int64_t d2 = (proj.point - end.point).cast<int64_t>().squaredNorm(); d2 < sqr(min_segment_length)) {
|
||||
++ split_segment_id;
|
||||
split_segment = false;
|
||||
}
|
||||
if (split_segment) {
|
||||
out.first.assign(path.begin(), path.begin() + split_segment_id + 2);
|
||||
out.second.assign(path.begin() + split_segment_id, path.end());
|
||||
assert(out.first[out.first.size() - 2] == start);
|
||||
assert(out.first.back() == end);
|
||||
assert(out.second.front() == start);
|
||||
assert(out.second[1] == end);
|
||||
assert(out.first.size() + out.second.size() == path.size() + 2);
|
||||
assert(out.first.back().radius == out.second[1].radius);
|
||||
out.first.back().point = proj.point;
|
||||
out.second.front().point = proj.point;
|
||||
if (end.radius < 0) {
|
||||
// A large arc (> PI) was split.
|
||||
// At least one of the two arches that were created by splitting the original arch will become smaller.
|
||||
// Make the radii of those arches that became < PI positive.
|
||||
// In case of a projection onto an arc, proj.center should be filled in and valid.
|
||||
auto vstart = (start.point - proj.center).cast<int64_t>();
|
||||
auto vend = (end.point - proj.center).cast<int64_t>();
|
||||
auto vproj = (proj.point - proj.center).cast<int64_t>();
|
||||
if ((cross2(vstart, vproj) > 0) == end.ccw())
|
||||
// Make the radius of a minor arc positive.
|
||||
out.first.back().radius *= -1.f;
|
||||
if ((cross2(vproj, vend) > 0) == end.ccw())
|
||||
// Make the radius of a minor arc positive.
|
||||
out.second[1].radius *= -1.f;
|
||||
assert(out.first.size() > 1);
|
||||
assert(out.second.size() > 1);
|
||||
out.second.front().radius = 0;
|
||||
}
|
||||
} else {
|
||||
assert(split_segment_id >= 0 && split_segment_id < path.size());
|
||||
if (split_segment_id + 1 == int(path.size()))
|
||||
out.first = path;
|
||||
else if (split_segment_id == 0)
|
||||
out.second = path;
|
||||
else {
|
||||
// Split at the start of proj.segment_id.
|
||||
out.first.assign(path.begin(), path.begin() + split_segment_id + 1);
|
||||
out.second.assign(path.begin() + split_segment_id, path.end());
|
||||
assert(out.first.size() + out.second.size() == path.size() + 1);
|
||||
assert(out.first.back() == (split_segment_id == proj.segment_id ? start : end));
|
||||
assert(out.second.front() == (split_segment_id == proj.segment_id ? start : end));
|
||||
assert(out.first.size() > 1);
|
||||
assert(out.second.size() > 1);
|
||||
out.second.front().radius = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::pair<Path, Path> split_at(const Path &path, const Point &point, const double min_segment_length)
|
||||
{
|
||||
return split_at(path, point_to_path_projection(path, point), min_segment_length);
|
||||
}
|
||||
|
||||
} } } // namespace Slic3r::Geometry::ArcWelder
|
324
src/libslic3r/Geometry/ArcWelder.hpp
Normal file
324
src/libslic3r/Geometry/ArcWelder.hpp
Normal file
@ -0,0 +1,324 @@
|
||||
#ifndef slic3r_Geometry_ArcWelder_hpp_
|
||||
#define slic3r_Geometry_ArcWelder_hpp_
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "../Point.hpp"
|
||||
|
||||
namespace Slic3r { namespace Geometry { namespace ArcWelder {
|
||||
|
||||
// Calculate center point of an arc given two points and a radius.
|
||||
// positive radius: take shorter arc
|
||||
// negative radius: take longer arc
|
||||
// radius must NOT be zero!
|
||||
template<typename Derived, typename Derived2, typename Float>
|
||||
inline Eigen::Matrix<Float, 2, 1, Eigen::DontAlign> arc_center(
|
||||
const Eigen::MatrixBase<Derived> &start_pos,
|
||||
const Eigen::MatrixBase<Derived2> &end_pos,
|
||||
const Float radius,
|
||||
const bool is_ccw)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_center(): first parameter is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_center(): second parameter is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_center(): Both vectors must be of the same type.");
|
||||
static_assert(std::is_same<typename Derived::Scalar, Float>::value, "arc_center(): Radius must be of the same type as the vectors.");
|
||||
assert(radius != 0);
|
||||
using Vector = Eigen::Matrix<Float, 2, 1, Eigen::DontAlign>;
|
||||
auto v = end_pos - start_pos;
|
||||
Float q2 = v.squaredNorm();
|
||||
assert(q2 > 0);
|
||||
Float t2 = sqr(radius) / q2 - Float(.25f);
|
||||
// If the start_pos and end_pos are nearly antipodal, t2 may become slightly negative.
|
||||
// In that case return a centroid of start_point & end_point.
|
||||
Float t = t2 > 0 ? sqrt(t2) : Float(0);
|
||||
auto mid = Float(0.5) * (start_pos + end_pos);
|
||||
Vector vp{ -v.y() * t, v.x() * t };
|
||||
return (radius > Float(0)) == is_ccw ? (mid + vp).eval() : (mid - vp).eval();
|
||||
}
|
||||
|
||||
// Calculate angle of an arc given two points and a radius.
|
||||
// Returned angle is in the range <0, 2 PI)
|
||||
// positive radius: take shorter arc
|
||||
// negative radius: take longer arc
|
||||
// radius must NOT be zero!
|
||||
template<typename Derived, typename Derived2>
|
||||
inline typename Derived::Scalar arc_angle(
|
||||
const Eigen::MatrixBase<Derived> &start_pos,
|
||||
const Eigen::MatrixBase<Derived2> &end_pos,
|
||||
const typename Derived::Scalar radius)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_angle(): first parameter is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_angle(): second parameter is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_angle(): Both vectors must be of the same type.");
|
||||
assert(radius != 0);
|
||||
using Float = typename Derived::Scalar;
|
||||
Float a = Float(0.5) * (end_pos - start_pos).norm() / radius;
|
||||
return radius > Float(0) ?
|
||||
// acute angle:
|
||||
(a > Float( 1.) ? Float(M_PI) : Float(2.) * std::asin(a)) :
|
||||
// obtuse angle:
|
||||
(a < Float(-1.) ? Float(M_PI) : Float(2. * M_PI) + Float(2.) * std::asin(a));
|
||||
}
|
||||
|
||||
// Calculate positive length of an arc given two points and a radius.
|
||||
// positive radius: take shorter arc
|
||||
// negative radius: take longer arc
|
||||
// radius must NOT be zero!
|
||||
template<typename Derived, typename Derived2>
|
||||
inline typename Derived::Scalar arc_length(
|
||||
const Eigen::MatrixBase<Derived> &start_pos,
|
||||
const Eigen::MatrixBase<Derived2> &end_pos,
|
||||
const typename Derived::Scalar radius)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_length(): first parameter is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): second parameter is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_length(): Both vectors must be of the same type.");
|
||||
assert(radius != 0);
|
||||
return arc_angle(start_pos, end_pos, radius) * std::abs(radius);
|
||||
}
|
||||
|
||||
// Calculate positive length of an arc given two points, center and orientation.
|
||||
template<typename Derived, typename Derived2, typename Derived3>
|
||||
inline typename Derived::Scalar arc_length(
|
||||
const Eigen::MatrixBase<Derived> &start_pos,
|
||||
const Eigen::MatrixBase<Derived2> &end_pos,
|
||||
const Eigen::MatrixBase<Derived3> ¢er_pos,
|
||||
const bool ccw)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_length(): first parameter is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): second parameter is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_length(): third parameter is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value, "arc_length(): All third points must be of the same type.");
|
||||
using Float = typename Derived::Scalar;
|
||||
auto vstart = start_pos - center_pos;
|
||||
auto vend = end_pos - center_pos;
|
||||
Float radius = vstart.norm();
|
||||
Float angle = atan2(double(cross2(vstart, vend)), double(vstart.dot(vend)));
|
||||
if (! ccw)
|
||||
angle *= Float(-1.);
|
||||
if (angle < 0)
|
||||
angle += Float(2. * M_PI);
|
||||
assert(angle >= Float(0.) && angle < Float(2. * M_PI + EPSILON));
|
||||
return angle * radius;
|
||||
}
|
||||
|
||||
// Test whether a point is inside a wedge of an arc.
|
||||
template<typename Derived, typename Derived2, typename Derived3>
|
||||
inline bool inside_arc_wedge_vectors(
|
||||
const Eigen::MatrixBase<Derived> &start_vec,
|
||||
const Eigen::MatrixBase<Derived2> &end_vec,
|
||||
const bool shorter_arc,
|
||||
const bool ccw,
|
||||
const Eigen::MatrixBase<Derived3> &query_vec)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): start_vec is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): end_vec is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): query_vec is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value, "inside_arc_wedge_vectors(): All vectors must be of the same type.");
|
||||
return shorter_arc ?
|
||||
// Smaller (convex) wedge.
|
||||
(ccw ?
|
||||
cross2(start_vec, query_vec) > 0 && cross2(query_vec, end_vec) > 0 :
|
||||
cross2(start_vec, query_vec) < 0 && cross2(query_vec, end_vec) < 0) :
|
||||
// Larger (concave) wedge.
|
||||
(ccw ?
|
||||
cross2(end_vec, query_vec) < 0 || cross2(query_vec, start_vec) < 0 :
|
||||
cross2(end_vec, query_vec) > 0 || cross2(query_vec, start_vec) > 0);
|
||||
}
|
||||
|
||||
template<typename Derived, typename Derived2, typename Derived3, typename Derived4>
|
||||
inline bool inside_arc_wedge(
|
||||
const Eigen::MatrixBase<Derived> &start_pt,
|
||||
const Eigen::MatrixBase<Derived2> &end_pt,
|
||||
const Eigen::MatrixBase<Derived3> ¢er_pt,
|
||||
const bool shorter_arc,
|
||||
const bool ccw,
|
||||
const Eigen::MatrixBase<Derived4> &query_pt)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge(): start_pt is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge(): end_pt is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge(): center_pt is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived4::SizeAtCompileTime) == 2, "inside_arc_wedge(): query_pt is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, typename Derived4::Scalar>::value, "inside_arc_wedge(): All vectors must be of the same type.");
|
||||
return inside_arc_wedge_vectors(start_pt - center_pt, end_pt - center_pt, shorter_arc, ccw, query_pt - center_pt);
|
||||
}
|
||||
|
||||
template<typename Derived, typename Derived2, typename Derived3, typename Float>
|
||||
inline bool inside_arc_wedge(
|
||||
const Eigen::MatrixBase<Derived> &start_pt,
|
||||
const Eigen::MatrixBase<Derived2> &end_pt,
|
||||
const Float radius,
|
||||
const bool ccw,
|
||||
const Eigen::MatrixBase<Derived3> &query_pt)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge(): start_pt is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge(): end_pt is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge(): query_pt is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value &&
|
||||
std::is_same<typename Derived::Scalar, Float>::value, "inside_arc_wedge(): All vectors + radius must be of the same type.");
|
||||
return inside_arc_wedge(start_pt, end_pt,
|
||||
arc_center(start_pt, end_pt, radius, ccw),
|
||||
radius > 0, ccw, query_pt);
|
||||
}
|
||||
|
||||
// Return number of linear segments necessary to interpolate arc of a given positive radius and positive angle to satisfy
|
||||
// maximum deviation of an interpolating polyline from an analytic arc.
|
||||
template<typename FloatType>
|
||||
size_t arc_discretization_steps(const FloatType radius, const FloatType angle, const FloatType deviation)
|
||||
{
|
||||
assert(radius > 0);
|
||||
assert(angle > 0);
|
||||
assert(angle <= FloatType(2. * M_PI));
|
||||
assert(deviation > 0);
|
||||
|
||||
FloatType d = radius - deviation;
|
||||
return d < EPSILON ?
|
||||
// Radius smaller than deviation.
|
||||
( // Acute angle: a single segment interpolates the arc with sufficient accuracy.
|
||||
angle < M_PI ||
|
||||
// Obtuse angle: Test whether the furthest point (center) of an arc is closer than deviation to the center of a line segment.
|
||||
radius * (FloatType(1.) + cos(M_PI - FloatType(.5) * angle)) < deviation ?
|
||||
// Single segment is sufficient
|
||||
1 :
|
||||
// Two segments are necessary, the middle point is at the center of the arc.
|
||||
2) :
|
||||
size_t(ceil(angle / (2. * acos(d / radius))));
|
||||
}
|
||||
|
||||
// Discretize arc given the radius, orientation and maximum deviation from the arc.
|
||||
// Returned polygon starts with p1, ends with p2 and it is discretized to guarantee the maximum deviation.
|
||||
Points arc_discretize(const Point &p1, const Point &p2, const double radius, const bool ccw, const double deviation);
|
||||
|
||||
// 1.2m diameter, maximum given by coord_t
|
||||
static constexpr const double default_scaled_max_radius = scaled<double>(600.);
|
||||
// 0.05mm
|
||||
static constexpr const double default_scaled_resolution = scaled<double>(0.05);
|
||||
// 5 percent
|
||||
static constexpr const double default_arc_length_percent_tolerance = 0.05;
|
||||
|
||||
enum class Orientation : unsigned char {
|
||||
Unknown,
|
||||
CCW,
|
||||
CW,
|
||||
};
|
||||
|
||||
// Returns orientation of a polyline with regard to the center.
|
||||
// Successive points are expected to take less than a PI angle step.
|
||||
// Returns Orientation::Unknown if the orientation with regard to the center
|
||||
// is not monotonous.
|
||||
Orientation arc_orientation(
|
||||
const Point ¢er,
|
||||
const Points::const_iterator begin,
|
||||
const Points::const_iterator end);
|
||||
|
||||
// Single segment of a smooth path.
|
||||
struct Segment
|
||||
{
|
||||
// End point of a linear or circular segment.
|
||||
// Start point is provided by the preceding segment.
|
||||
Point point;
|
||||
// Radius of a circular segment. Positive - take the shorter arc. Negative - take the longer arc. Zero - linear segment.
|
||||
float radius{ 0.f };
|
||||
// CCW or CW. Ignored for zero radius (linear segment).
|
||||
Orientation orientation{ Orientation::CCW };
|
||||
|
||||
bool linear() const { return radius == 0; }
|
||||
bool ccw() const { return orientation == Orientation::CCW; }
|
||||
bool cw() const { return orientation == Orientation::CW; }
|
||||
};
|
||||
|
||||
inline bool operator==(const Segment &lhs, const Segment &rhs) {
|
||||
return lhs.point == rhs.point && lhs.radius == rhs.radius && lhs.orientation == rhs.orientation;
|
||||
}
|
||||
|
||||
using Segments = std::vector<Segment>;
|
||||
using Path = Segments;
|
||||
|
||||
// Interpolate polyline path with a sequence of linear / circular segments given the interpolation tolerance.
|
||||
// Only convert to polyline if zero tolerance.
|
||||
// Convert to polyline and decimate polyline if zero fit_circle_percent_tolerance.
|
||||
// Path fitting is inspired with the arc fitting algorithm in
|
||||
// Arc Welder: Anti-Stutter Library by Brad Hochgesang FormerLurker@pm.me
|
||||
// https://github.com/FormerLurker/ArcWelderLib
|
||||
Path fit_path(const Points &points, double tolerance, double fit_circle_percent_tolerance);
|
||||
|
||||
// Decimate polyline into a smooth path structure using Douglas-Peucker polyline decimation algorithm.
|
||||
inline Path fit_polyline(const Points &points, double tolerance) { return fit_path(points, tolerance, 0.); }
|
||||
|
||||
template<typename FloatType>
|
||||
inline FloatType segment_length(const Segment &start, const Segment &end)
|
||||
{
|
||||
return end.linear() ?
|
||||
(end.point - start.point).cast<FloatType>().norm() :
|
||||
arc_length(start.point.cast<FloatType>(), end.point.cast<FloatType>(), FloatType(end.radius));
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
inline FloatType path_length(const Path &path)
|
||||
{
|
||||
FloatType len = 0;
|
||||
for (size_t i = 1; i < path.size(); ++ i)
|
||||
len += segment_length<FloatType>(path[i - 1], path[i]);
|
||||
return len;
|
||||
}
|
||||
|
||||
// Estimate minimum path length of a segment cheaply without having to calculate center of an arc and it arc length.
|
||||
// Used for caching a smooth path chunk that is certainly longer than a threshold.
|
||||
inline int64_t estimate_min_segment_length(const Segment &start, const Segment &end)
|
||||
{
|
||||
if (end.linear() || end.radius > 0) {
|
||||
// Linear segment or convex wedge, take the larger X or Y component.
|
||||
Point v = (end.point - start.point).cwiseAbs();
|
||||
return std::max(v.x(), v.y());
|
||||
} else {
|
||||
// Arc with angle > PI.
|
||||
// Returns estimate of PI * r
|
||||
return - int64_t(3) * int64_t(end.radius);
|
||||
}
|
||||
}
|
||||
|
||||
// Estimate minimum path length cheaply without having to calculate center of an arc and it arc length.
|
||||
// Used for caching a smooth path chunk that is certainly longer than a threshold.
|
||||
inline int64_t estimate_path_length(const Path &path)
|
||||
{
|
||||
int64_t len = 0;
|
||||
for (size_t i = 1; i < path.size(); ++ i)
|
||||
len += Geometry::ArcWelder::estimate_min_segment_length(path[i - 1], path[i]);
|
||||
return len;
|
||||
}
|
||||
|
||||
void reverse(Path &path);
|
||||
|
||||
// Clip start / end of a smooth path by len.
|
||||
// If path is shorter than len, remaining path length to trim will be returned.
|
||||
double clip_start(Path &path, const double len);
|
||||
double clip_end(Path &path, const double len);
|
||||
|
||||
struct PathSegmentProjection
|
||||
{
|
||||
// Start segment of a projection on the path.
|
||||
size_t segment_id { std::numeric_limits<size_t>::max() };
|
||||
// Projection of the point on the segment.
|
||||
Point point { 0, 0 };
|
||||
// If the point lies on an arc, the arc center is cached here.
|
||||
Point center { 0, 0 };
|
||||
// Square of a distance of the projection.
|
||||
double distance2 { std::numeric_limits<double>::max() };
|
||||
|
||||
bool valid() const { return this->segment_id != std::numeric_limits<size_t>::max(); }
|
||||
};
|
||||
// Returns closest segment and a parameter along the closest segment of a path to a point.
|
||||
PathSegmentProjection point_to_path_projection(const Path &path, const Point &point, double search_radius2 = std::numeric_limits<double>::max());
|
||||
// Split a path into two paths at a segment point. Snap to an existing point if the projection of "point is closer than min_segment_length.
|
||||
std::pair<Path, Path> split_at(const Path &path, const PathSegmentProjection &proj, const double min_segment_length);
|
||||
// Split a path into two paths at a point closest to "point". Snap to an existing point if the projection of "point is closer than min_segment_length.
|
||||
std::pair<Path, Path> split_at(const Path &path, const Point &point, const double min_segment_length);
|
||||
|
||||
} } } // namespace Slic3r::Geometry::ArcWelder
|
||||
|
||||
#endif // slic3r_Geometry_ArcWelder_hpp_
|
@ -17,9 +17,14 @@ Point circle_center_taubin_newton(const Points::const_iterator& input_begin, con
|
||||
return Point::new_scale(center.x(), center.y());
|
||||
}
|
||||
|
||||
/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126
|
||||
/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
|
||||
/// lie on.
|
||||
// Robust and accurate algebraic circle fit, which works well even if data points are observed only within a small arc.
|
||||
// The method was proposed by G. Taubin in
|
||||
// "Estimation Of Planar Curves, Surfaces And Nonplanar Space Curves Defined By Implicit Equations,
|
||||
// With Applications To Edge And Range Image Segmentation", IEEE Trans. PAMI, Vol. 13, pages 1115-1138, (1991)."
|
||||
// This particular implementation was adapted from
|
||||
// "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126"
|
||||
// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end
|
||||
// lie on.
|
||||
Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles)
|
||||
{
|
||||
// calculate the centroid of the data set
|
||||
|
@ -9,10 +9,17 @@ namespace Slic3r { namespace Geometry {
|
||||
|
||||
// https://en.wikipedia.org/wiki/Circumscribed_circle
|
||||
// Circumcenter coordinates, Cartesian coordinates
|
||||
template<typename Vector>
|
||||
Vector circle_center(const Vector &a, const Vector &bsrc, const Vector &csrc, typename Vector::Scalar epsilon)
|
||||
// In case the three points are collinear, returns their centroid.
|
||||
template<typename Derived, typename Derived2, typename Derived3>
|
||||
Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> circle_center(const Derived &a, const Derived2 &bsrc, const Derived3 &csrc, typename Derived::Scalar epsilon)
|
||||
{
|
||||
using Scalar = typename Vector::Scalar;
|
||||
static_assert(Derived ::IsVectorAtCompileTime && int(Derived ::SizeAtCompileTime) == 2, "circle_center(): 1st point is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "circle_center(): 2nd point is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "circle_center(): 3rd point is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value && std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value,
|
||||
"circle_center(): All three points must be of the same type.");
|
||||
using Scalar = typename Derived::Scalar;
|
||||
using Vector = Eigen::Matrix<Scalar, 2, 1, Eigen::DontAlign>;
|
||||
Vector b = bsrc - a;
|
||||
Vector c = csrc - a;
|
||||
Scalar lb = b.squaredNorm();
|
||||
@ -30,6 +37,32 @@ Vector circle_center(const Vector &a, const Vector &bsrc, const Vector &csrc, ty
|
||||
}
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Circumscribed_circle
|
||||
// Circumcenter coordinates, Cartesian coordinates
|
||||
// Returns no value if the three points are collinear.
|
||||
template<typename Derived, typename Derived2, typename Derived3>
|
||||
std::optional<Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign>> try_circle_center(const Derived &a, const Derived2 &bsrc, const Derived3 &csrc, typename Derived::Scalar epsilon)
|
||||
{
|
||||
static_assert(Derived ::IsVectorAtCompileTime && int(Derived ::SizeAtCompileTime) == 2, "try_circle_center(): 1st point is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "try_circle_center(): 2nd point is not a 2D vector");
|
||||
static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "try_circle_center(): 3rd point is not a 2D vector");
|
||||
static_assert(std::is_same<typename Derived::Scalar, typename Derived2::Scalar>::value && std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value,
|
||||
"try_circle_center(): All three points must be of the same type.");
|
||||
using Scalar = typename Derived::Scalar;
|
||||
using Vector = Eigen::Matrix<Scalar, 2, 1, Eigen::DontAlign>;
|
||||
Vector b = bsrc - a;
|
||||
Vector c = csrc - a;
|
||||
Scalar lb = b.squaredNorm();
|
||||
Scalar lc = c.squaredNorm();
|
||||
if (Scalar d = b.x() * c.y() - b.y() * c.x(); std::abs(d) < epsilon) {
|
||||
// The three points are collinear.
|
||||
return {};
|
||||
} else {
|
||||
Vector v = lc * b - lb * c;
|
||||
return std::make_optional<Vector>(a + Vector(- v.y(), v.x()) / (2 * d));
|
||||
}
|
||||
}
|
||||
|
||||
// 2D circle defined by its center and squared radius
|
||||
template<typename Vector>
|
||||
struct CircleSq {
|
||||
@ -65,7 +98,7 @@ struct Circle {
|
||||
Vector center;
|
||||
Scalar radius;
|
||||
|
||||
Circle() {}
|
||||
Circle() = default;
|
||||
Circle(const Vector ¢er, const Scalar radius) : center(center), radius(radius) {}
|
||||
Circle(const Vector &a, const Vector &b) : center(Scalar(0.5) * (a + b)) { radius = (a - center).norm(); }
|
||||
Circle(const Vector &a, const Vector &b, const Vector &c, const Scalar epsilon) { *this = CircleSq(a, b, c, epsilon); }
|
||||
|
@ -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 Vector = Eigen::Matrix<SquareLengthType, 2, 1, Eigen::DontAlign>;
|
||||
if (begin != end) {
|
||||
// Supporting in-place reduction and the data type may be generic, thus we are always making a copy of the point value before there is a chance
|
||||
// to override input by moving the data to the output.
|
||||
auto a = point_getter(*begin);
|
||||
*out ++ = std::move(*begin);
|
||||
if (auto next = std::next(begin); next == end) {
|
||||
// Single point input only.
|
||||
} else if (std::next(next) == end) {
|
||||
// Two points input.
|
||||
*out ++ = std::move(*next);
|
||||
} else {
|
||||
const auto tolerance_sq = SquareLengthType(sqr(tolerance));
|
||||
InputIterator anchor = begin;
|
||||
InputIterator floater = std::prev(end);
|
||||
std::vector<InputIterator> dpStack;
|
||||
if constexpr (std::is_base_of_v<std::random_access_iterator_tag, InputIteratorCategory>)
|
||||
dpStack.reserve(end - begin);
|
||||
dpStack.emplace_back(floater);
|
||||
auto f = point_getter(*floater);
|
||||
for (;;) {
|
||||
assert(anchor != floater);
|
||||
bool take_floater = false;
|
||||
InputIterator furthest = anchor;
|
||||
if (std::next(anchor) == floater) {
|
||||
// Two point segment. Accept the floater.
|
||||
take_floater = true;
|
||||
} else {
|
||||
SquareLengthType max_dist_sq = 0;
|
||||
// Find point furthest from line seg created by (anchor, floater) and note it.
|
||||
const Vector v = (f - a).template cast<SquareLengthType>();
|
||||
if (const SquareLengthType l2 = v.squaredNorm(); l2 == 0) {
|
||||
// Zero length segment, find the furthest point between anchor and floater.
|
||||
for (auto it = std::next(anchor); it != floater; ++ it)
|
||||
if (SquareLengthType dist_sq = (point_getter(*it) - a).template cast<SquareLengthType>().squaredNorm();
|
||||
dist_sq > max_dist_sq) {
|
||||
max_dist_sq = dist_sq;
|
||||
furthest = it;
|
||||
}
|
||||
} else {
|
||||
// Find Find the furthest point from the line <anchor, floater>.
|
||||
const double dl2 = double(l2);
|
||||
const Vec2d dv = v.template cast<double>();
|
||||
for (auto it = std::next(anchor); it != floater; ++ it) {
|
||||
const auto p = point_getter(*it);
|
||||
const Vector va = (p - a).template cast<SquareLengthType>();
|
||||
const SquareLengthType t = va.dot(v);
|
||||
SquareLengthType dist_sq;
|
||||
if (t <= 0) {
|
||||
dist_sq = va.squaredNorm();
|
||||
} else if (t >= l2) {
|
||||
dist_sq = (p - f).template cast<SquareLengthType>().squaredNorm();
|
||||
} else if (double dt = double(t) / dl2; dt <= 0) {
|
||||
dist_sq = va.squaredNorm();
|
||||
} else if (dt >= 1.) {
|
||||
dist_sq = (p - f).template cast<SquareLengthType>().squaredNorm();
|
||||
} else {
|
||||
const Vector w = (dt * dv).cast<SquareLengthType>();
|
||||
dist_sq = (w - va).squaredNorm();
|
||||
}
|
||||
if (dist_sq > max_dist_sq) {
|
||||
max_dist_sq = dist_sq;
|
||||
furthest = it;
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove point if less than tolerance
|
||||
take_floater = max_dist_sq <= tolerance_sq;
|
||||
}
|
||||
if (take_floater) {
|
||||
// The points between anchor and floater are close to the <anchor, floater> line.
|
||||
// Drop the points between them.
|
||||
a = f;
|
||||
*out ++ = std::move(*floater);
|
||||
anchor = floater;
|
||||
assert(dpStack.back() == floater);
|
||||
dpStack.pop_back();
|
||||
if (dpStack.empty())
|
||||
break;
|
||||
floater = dpStack.back();
|
||||
f = point_getter(*floater);
|
||||
} else {
|
||||
// The furthest point is too far from the segment <anchor, floater>.
|
||||
// Divide recursively.
|
||||
floater = furthest;
|
||||
f = point_getter(*floater);
|
||||
dpStack.emplace_back(floater);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Reduces polyline in the <begin, end) range, outputs into the output iterator.
|
||||
// Output iterator may be equal to input iterator as long as the iterator value type move operator supports move at the same input / output address.
|
||||
template<typename OutputIterator>
|
||||
inline OutputIterator douglas_peucker(Points::const_iterator begin, Points::const_iterator end, OutputIterator out, const double tolerance)
|
||||
{
|
||||
return douglas_peucker<int64_t>(begin, end, out, tolerance, [](const Point &p) { return p; });
|
||||
}
|
||||
|
||||
inline Points douglas_peucker(const Points &src, const double tolerance)
|
||||
{
|
||||
Points out;
|
||||
out.reserve(src.size());
|
||||
douglas_peucker(src.begin(), src.end(), std::back_inserter(out), tolerance);
|
||||
return out;
|
||||
}
|
||||
|
||||
class MultiPoint
|
||||
{
|
||||
public:
|
||||
@ -81,8 +198,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static Points douglas_peucker(const Points &points, const double tolerance);
|
||||
static Points visivalingam(const Points& pts, const double& tolerance);
|
||||
static Points douglas_peucker(const Points &src, const double tolerance) { return Slic3r::douglas_peucker(src, tolerance); }
|
||||
static Points visivalingam(const Points &src, const double tolerance);
|
||||
|
||||
inline auto begin() { return points.begin(); }
|
||||
inline auto begin() const { return points.begin(); }
|
||||
@ -119,16 +236,20 @@ extern BoundingBox get_extents(const MultiPoint &mp);
|
||||
extern BoundingBox get_extents_rotated(const Points &points, double angle);
|
||||
extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle);
|
||||
|
||||
inline double length(const Points &pts) {
|
||||
inline double length(const Points::const_iterator begin, const Points::const_iterator end) {
|
||||
double total = 0;
|
||||
if (! pts.empty()) {
|
||||
auto it = pts.begin();
|
||||
for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev)
|
||||
if (begin != end) {
|
||||
auto it = begin;
|
||||
for (auto it_prev = it ++; it != end; ++ it, ++ it_prev)
|
||||
total += (*it - *it_prev).cast<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),
|
||||
ExtrusionAttributes{
|
||||
role_normal,
|
||||
is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
|
||||
ExtrusionFlow{ is_external ? params.ext_mm3_per_mm : params.mm3_per_mm,
|
||||
is_external ? params.ext_perimeter_flow.width() : params.perimeter_flow.width(),
|
||||
float(params.layer_height));
|
||||
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,21 +336,24 @@ static ExtrusionEntityCollection traverse_loops_classic(const PerimeterGenerator
|
||||
extrusion_paths_append(
|
||||
paths,
|
||||
diff_pl({ polygon }, lower_slices_polygons_clipped),
|
||||
ExtrusionAttributes{
|
||||
role_overhang,
|
||||
params.mm3_per_mm_overhang,
|
||||
params.overhang_flow.width(),
|
||||
params.overhang_flow.height());
|
||||
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);
|
||||
Vec2d cur = this->cast<double>();
|
||||
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 );
|
||||
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() + s * d.x() + c * d.y());
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -456,7 +456,8 @@ static std::vector<std::string> s_Preset_print_options {
|
||||
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",
|
||||
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
|
||||
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "arc_fitting", "arc_fitting_tolerance",
|
||||
"wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
|
||||
"mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
|
||||
"perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
|
||||
|
@ -921,8 +921,10 @@ void Print::process()
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_objects.size(), 1), [this](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t idx = range.begin(); idx < range.end(); ++idx) {
|
||||
m_objects[idx]->generate_support_material();
|
||||
m_objects[idx]->estimate_curled_extrusions();
|
||||
PrintObject &obj = *m_objects[idx];
|
||||
obj.generate_support_material();
|
||||
obj.estimate_curled_extrusions();
|
||||
obj.calculate_overhanging_perimeters();
|
||||
}
|
||||
}, tbb::simple_partitioner());
|
||||
|
||||
@ -1009,7 +1011,7 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor
|
||||
this->set_status(90, message);
|
||||
|
||||
// Create GCode on heap, it has quite a lot of data.
|
||||
std::unique_ptr<GCode> 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())
|
||||
@ -1125,13 +1127,15 @@ void Print::_make_skirt()
|
||||
}
|
||||
// Extrude the skirt loop.
|
||||
ExtrusionLoop eloop(elrSkirt);
|
||||
eloop.paths.emplace_back(ExtrusionPath(
|
||||
ExtrusionPath(
|
||||
eloop.paths.emplace_back(
|
||||
ExtrusionAttributes{
|
||||
ExtrusionRole::Skirt,
|
||||
(float)mm3_per_mm, // this will be overridden at G-code export time
|
||||
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
|
||||
)));
|
||||
float(first_layer_height) // this will be overridden at G-code export time
|
||||
}
|
||||
});
|
||||
eloop.paths.back().polyline = loop.split_at_first_point();
|
||||
m_skirt.append(eloop);
|
||||
if (m_config.min_skirt_length.value > 0) {
|
||||
@ -1637,8 +1641,8 @@ std::string PrintStatistics::finalize_output_path(const std::string &path_in) co
|
||||
}
|
||||
|
||||
|
||||
ExtrusionPath path(ExtrusionRole::WipeTower, 0.0, 0.0, lh);
|
||||
path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner };
|
||||
ExtrusionPath path({ minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner },
|
||||
ExtrusionAttributes{ ExtrusionRole::WipeTower, ExtrusionFlow{ 0.0, 0.0, lh } });
|
||||
paths.push_back({ path });
|
||||
|
||||
// We added the border, now add several parallel lines so we can detect an object that is fully inside the tower.
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class GCodeGenerator;
|
||||
class Layer;
|
||||
class ModelObject;
|
||||
class Print;
|
||||
@ -67,7 +67,7 @@ enum PrintStep : unsigned int {
|
||||
|
||||
enum PrintObjectStep : unsigned int {
|
||||
posSlice, posPerimeters, posPrepareInfill,
|
||||
posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCount,
|
||||
posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters, posCount,
|
||||
};
|
||||
|
||||
// A PrintRegion object represents a group of volumes to print
|
||||
@ -376,6 +376,7 @@ private:
|
||||
void generate_support_spots();
|
||||
void generate_support_material();
|
||||
void estimate_curled_extrusions();
|
||||
void calculate_overhanging_perimeters();
|
||||
|
||||
void slice_volumes();
|
||||
// Has any support (not counting the raft).
|
||||
@ -697,7 +698,7 @@ private:
|
||||
Polygons m_sequential_print_clearance_contours;
|
||||
|
||||
// To allow GCode to set the Print's GCodeExport step status.
|
||||
friend class GCode;
|
||||
friend class GCodeGenerator;
|
||||
// To allow GCodeProcessor to emit warnings.
|
||||
friend class GCodeProcessor;
|
||||
// Allow PrintObject to access m_mutex and m_cancel_callback.
|
||||
|
@ -34,6 +34,13 @@ static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values &
|
||||
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values() { return s_keys_map_##NAME; } \
|
||||
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names() { return s_keys_names_##NAME; }
|
||||
|
||||
static const t_config_enum_values s_keys_map_ArcFittingType {
|
||||
{ "disabled", int(ArcFittingType::Disabled) },
|
||||
{ "emit_center", int(ArcFittingType::EmitCenter) },
|
||||
{ "emit_radius", int(ArcFittingType::EmitRadius) }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ArcFittingType)
|
||||
|
||||
static t_config_enum_values s_keys_map_PrinterTechnology {
|
||||
{ "FFF", ptFFF },
|
||||
{ "SLA", ptSLA }
|
||||
@ -397,6 +404,27 @@ void PrintConfigDef::init_fff_params()
|
||||
{
|
||||
ConfigOptionDef* def;
|
||||
|
||||
def = this->add("arc_fitting", coEnum);
|
||||
def->label = L("Arc fitting");
|
||||
def->tooltip = L("Enable this to get a G-code file which has G2 and G3 moves. "
|
||||
"And the fitting tolerance is same with resolution");
|
||||
def->set_enum<ArcFittingType>({
|
||||
{ "disabled", "Disabled" },
|
||||
{ "emit_center", "Enabled: G2/3 I J" },
|
||||
{ "emit_radius", "Enabled: G2/3 R" }
|
||||
});
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<ArcFittingType>(ArcFittingType::Disabled));
|
||||
|
||||
def = this->add("arc_fitting_tolerance", coFloatOrPercent);
|
||||
def->label = L("Arc fitting tolerance");
|
||||
def->sidetext = L("mm or %");
|
||||
def->tooltip = L("When using the arc_fitting option, allow the curve to deviate a cetain % from the collection of strait paths.\n"
|
||||
"Can be a mm value or a percentage of the current extrusion width.");
|
||||
def->mode = comAdvanced;
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloatOrPercent(5, true));
|
||||
|
||||
// Maximum extruder temperature, bumped to 1500 to support printing of glass.
|
||||
const int max_temp = 1500;
|
||||
def = this->add("avoid_crossing_curled_overhangs", coBool);
|
||||
|
@ -30,6 +30,12 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class ArcFittingType {
|
||||
Disabled,
|
||||
EmitCenter,
|
||||
EmitRadius,
|
||||
};
|
||||
|
||||
enum GCodeFlavor : unsigned char {
|
||||
gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinLegacy, gcfMarlinFirmware, gcfKlipper, gcfSailfish, gcfMach3, gcfMachinekit,
|
||||
gcfSmoothie, gcfNoExtrusion,
|
||||
@ -141,6 +147,7 @@ enum class GCodeThumbnailsFormat {
|
||||
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names(); \
|
||||
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values();
|
||||
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ArcFittingType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(MachineLimitsUsage)
|
||||
@ -671,6 +678,8 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
PRINT_CONFIG_CLASS_DEFINE(
|
||||
GCodeConfig,
|
||||
|
||||
((ConfigOptionEnum<ArcFittingType>, arc_fitting))
|
||||
((ConfigOptionFloatOrPercent, arc_fitting_tolerance))
|
||||
((ConfigOptionBool, autoemit_temperature_commands))
|
||||
((ConfigOptionString, before_layer_gcode))
|
||||
((ConfigOptionString, between_objects_gcode))
|
||||
|
@ -3,7 +3,9 @@
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "GCode/ExtrusionProcessor.hpp"
|
||||
#include "KDTreeIndirect.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
@ -533,6 +535,65 @@ void PrintObject::estimate_curled_extrusions()
|
||||
}
|
||||
}
|
||||
|
||||
void PrintObject::calculate_overhanging_perimeters()
|
||||
{
|
||||
if (this->set_started(posCalculateOverhangingPerimeters)) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Calculating overhanging perimeters - start";
|
||||
m_print->set_status(89, _u8L("Calculating overhanging perimeters"));
|
||||
std::vector<unsigned int> extruders;
|
||||
std::unordered_set<const PrintRegion *> regions_with_dynamic_speeds;
|
||||
for (const PrintRegion *pr : this->print()->m_print_regions) {
|
||||
if (pr->config().enable_dynamic_overhang_speeds.getBool()) {
|
||||
regions_with_dynamic_speeds.insert(pr);
|
||||
}
|
||||
extruders.clear();
|
||||
pr->collect_object_printing_extruders(*this->print(), extruders);
|
||||
auto cfg = this->print()->config();
|
||||
if (std::any_of(extruders.begin(), extruders.end(),
|
||||
[&cfg](unsigned int extruder_id) { return cfg.enable_dynamic_fan_speeds.get_at(extruder_id); })) {
|
||||
regions_with_dynamic_speeds.insert(pr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!regions_with_dynamic_speeds.empty()) {
|
||||
std::unordered_map<size_t, AABBTreeLines::LinesDistancer<CurledLine>> curled_lines;
|
||||
std::unordered_map<size_t, AABBTreeLines::LinesDistancer<Linef>> unscaled_polygons_lines;
|
||||
for (const Layer *l : this->layers()) {
|
||||
curled_lines[l->id()] = AABBTreeLines::LinesDistancer<CurledLine>{l->curled_lines};
|
||||
unscaled_polygons_lines[l->id()] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(l->lslices)};
|
||||
}
|
||||
curled_lines[size_t(-1)] = {};
|
||||
unscaled_polygons_lines[size_t(-1)] = {};
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_layers.size()), [this, &curled_lines, &unscaled_polygons_lines,
|
||||
®ions_with_dynamic_speeds](
|
||||
const tbb::blocked_range<size_t> &range) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
auto l = m_layers[layer_idx];
|
||||
if (l->id() == 0) { // first layer, do not split
|
||||
continue;
|
||||
}
|
||||
for (LayerRegion *layer_region : l->regions()) {
|
||||
if (regions_with_dynamic_speeds.find(layer_region->m_region) == regions_with_dynamic_speeds.end()) {
|
||||
continue;
|
||||
}
|
||||
size_t prev_layer_id = l->lower_layer ? l->lower_layer->id() : size_t(-1);
|
||||
layer_region->m_perimeters =
|
||||
ExtrusionProcessor::calculate_and_split_overhanging_extrusions(&layer_region->m_perimeters,
|
||||
unscaled_polygons_lines[prev_layer_id],
|
||||
curled_lines[l->id()]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
m_print->throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Calculating overhanging perimeters - end";
|
||||
}
|
||||
this->set_done(posCalculateOverhangingPerimeters);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data(
|
||||
const std::vector<std::pair<const Surface *, float>> &surfaces_w_bottom_z) const
|
||||
{
|
||||
@ -644,7 +705,9 @@ bool PrintObject::invalidate_state_by_config_options(
|
||||
|| opt_key == "first_layer_extrusion_width"
|
||||
|| opt_key == "perimeter_extrusion_width"
|
||||
|| opt_key == "infill_overlap"
|
||||
|| opt_key == "external_perimeters_first") {
|
||||
|| opt_key == "external_perimeters_first"
|
||||
|| opt_key == "arc_fitting"
|
||||
|| opt_key == "arc_fitting_tolerance") {
|
||||
steps.emplace_back(posPerimeters);
|
||||
} else if (
|
||||
opt_key == "gap_fill_enabled"
|
||||
@ -845,7 +908,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
||||
|
||||
// propagate to dependent steps
|
||||
if (step == posPerimeters) {
|
||||
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions });
|
||||
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
} else if (step == posPrepareInfill) {
|
||||
invalidated |= this->invalidate_steps({ posInfill, posIroning, posSupportSpotsSearch});
|
||||
@ -854,7 +917,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
} else if (step == posSlice) {
|
||||
invalidated |= this->invalidate_steps({posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch,
|
||||
posSupportMaterial, posEstimateCurledExtrusions});
|
||||
posSupportMaterial, posEstimateCurledExtrusions, posCalculateOverhangingPerimeters});
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
m_slicing_params.valid = false;
|
||||
} else if (step == posSupportMaterial) {
|
||||
|
@ -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 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);
|
||||
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.
|
||||
|
@ -328,9 +328,9 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
|
||||
return {};
|
||||
}
|
||||
const float flow_width = get_flow_width(layer_region, entity->role());
|
||||
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, true, true>(entity->as_polyline().points,
|
||||
prev_layer_boundary, flow_width,
|
||||
params.bridge_distance);
|
||||
std::vector<ExtrusionProcessor::ExtendedPoint> annotated_points =
|
||||
ExtrusionProcessor::estimate_points_properties<true, true, true, true>(entity->as_polyline().points, prev_layer_boundary,
|
||||
flow_width, params.bridge_distance);
|
||||
|
||||
std::vector<ExtrusionLine> lines_out;
|
||||
lines_out.reserve(annotated_points.size());
|
||||
@ -339,8 +339,8 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
|
||||
std::optional<Vec2d> bridging_dir{};
|
||||
|
||||
for (size_t i = 0; i < annotated_points.size(); ++i) {
|
||||
ExtendedPoint &curr_point = annotated_points[i];
|
||||
const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
|
||||
SupportPointCause potential_cause = std::abs(curr_point.curvature) > 0.1 ? SupportPointCause::FloatingBridgeAnchor :
|
||||
SupportPointCause::LongBridge;
|
||||
@ -382,17 +382,17 @@ std::vector<ExtrusionLine> check_extrusion_entity_stability(const ExtrusionEntit
|
||||
|
||||
const float flow_width = get_flow_width(layer_region, entity->role());
|
||||
// Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable
|
||||
std::vector<ExtendedPoint> annotated_points = estimate_points_properties<true, true, false, false>(entity->as_polyline().points,
|
||||
prev_layer_lines, flow_width,
|
||||
params.bridge_distance);
|
||||
std::vector<ExtrusionProcessor::ExtendedPoint> annotated_points =
|
||||
ExtrusionProcessor::estimate_points_properties<true, true, false, false>(entity->as_polyline().points, prev_layer_lines,
|
||||
flow_width, params.bridge_distance);
|
||||
|
||||
std::vector<ExtrusionLine> lines_out;
|
||||
lines_out.reserve(annotated_points.size());
|
||||
float bridged_distance = annotated_points.front().position != annotated_points.back().position ? (params.bridge_distance + 1.0f) :
|
||||
0.0f;
|
||||
for (size_t i = 0; i < annotated_points.size(); ++i) {
|
||||
ExtendedPoint &curr_point = annotated_points[i];
|
||||
const ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
float line_len = (prev_point.position - curr_point.position).norm();
|
||||
ExtrusionLine line_out{prev_point.position.cast<float>(), curr_point.position.cast<float>(), line_len, entity};
|
||||
|
||||
@ -1107,11 +1107,12 @@ void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width,
|
||||
Polygon pol(pl.points);
|
||||
pol.make_counter_clockwise();
|
||||
|
||||
auto annotated_points = estimate_points_properties<true, true, false, false>(pol.points, prev_layer_lines, flow_width);
|
||||
auto annotated_points = ExtrusionProcessor::estimate_points_properties<true, true, false, false>(pol.points, prev_layer_lines,
|
||||
flow_width);
|
||||
|
||||
for (size_t i = 0; i < annotated_points.size(); ++i) {
|
||||
const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
const ExtendedPoint &b = annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i];
|
||||
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
|
||||
extrusion};
|
||||
|
||||
@ -1182,11 +1183,13 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms)
|
||||
Points extrusion_pts;
|
||||
extrusion->collect_points(extrusion_pts);
|
||||
float flow_width = get_flow_width(layer_region, extrusion->role());
|
||||
auto annotated_points = estimate_points_properties<true, true, false, false>(extrusion_pts, prev_layer_lines, flow_width,
|
||||
auto annotated_points = ExtrusionProcessor::estimate_points_properties<true, true, false, false>(extrusion_pts,
|
||||
prev_layer_lines,
|
||||
flow_width,
|
||||
params.bridge_distance);
|
||||
for (size_t i = 0; i < annotated_points.size(); ++i) {
|
||||
const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
const ExtendedPoint &b = annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
|
||||
const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i];
|
||||
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
|
||||
extrusion};
|
||||
|
||||
@ -1196,7 +1199,8 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms)
|
||||
prev_layer_lines.get_line(bottom_line_idx);
|
||||
|
||||
// correctify the distance sign using slice polygons
|
||||
float sign = (prev_layer_boundary.distance_from_lines<true>(middle.cast<double>()) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f;
|
||||
float sign = (prev_layer_boundary.distance_from_lines<true>(middle.cast<double>()) + 0.5f * flow_width) < 0.0f ? -1.0f :
|
||||
1.0f;
|
||||
|
||||
line_out.curled_up_height = estimate_curled_up_height(middle_distance * sign, 0.5 * (a.curvature + b.curvature),
|
||||
l->height, flow_width, bottom_line.curled_up_height, params);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -341,6 +341,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
||||
toggle_field("min_feature_size", have_arachne);
|
||||
toggle_field("min_bead_width", have_arachne);
|
||||
toggle_field("thin_walls", !have_arachne);
|
||||
|
||||
bool has_arc_fitting = config->opt_enum<ArcFittingType>("arc_fitting") != ArcFittingType::Disabled;
|
||||
toggle_field("arc_fitting_tolerance", has_arc_fitting);
|
||||
}
|
||||
|
||||
void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
#include "libslic3r/GCode/GCodeWriter.hpp"
|
||||
|
||||
#include "slic3r/Utils/Http.hpp"
|
||||
#include "slic3r/Utils/PrintHost.hpp"
|
||||
@ -1649,6 +1649,8 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("slicing_mode");
|
||||
optgroup->append_single_option_line("resolution");
|
||||
optgroup->append_single_option_line("gcode_resolution");
|
||||
optgroup->append_single_option_line("arc_fitting");
|
||||
optgroup->append_single_option_line("arc_fitting_tolerance");
|
||||
optgroup->append_single_option_line("xy_size_compensation");
|
||||
optgroup->append_single_option_line("elefant_foot_compensation", "elephant-foot-compensation_114487");
|
||||
|
||||
|
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
|
||||
|
264
tests/libslic3r/test_arc_welder.cpp
Normal file
264
tests/libslic3r/test_arc_welder.cpp
Normal file
@ -0,0 +1,264 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
#include <libslic3r/Geometry/ArcWelder.hpp>
|
||||
#include <libslic3r/libslic3r.h>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("arc basics", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
WHEN("arc from { 2000.f, 1000.f } to { 1000.f, 2000.f }") {
|
||||
Vec2f p1{ 2000.f, 1000.f };
|
||||
Vec2f p2{ 1000.f, 2000.f };
|
||||
float r{ 1000.f };
|
||||
THEN("90 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
|
||||
REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f }));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.5 * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.5 * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("90 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
|
||||
REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f }));
|
||||
}
|
||||
THEN("270 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
|
||||
REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f }));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx(1.5 * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * 1.5 * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("270 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
|
||||
REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f }));
|
||||
}
|
||||
}
|
||||
WHEN("arc from { 1707.11f, 1707.11f } to { 1000.f, 2000.f }") {
|
||||
Vec2f p1{ 1707.11f, 1707.11f };
|
||||
Vec2f p2{ 1000.f, 2000.f };
|
||||
float r{ 1000.f };
|
||||
Vec2f center1 = Vec2f{ 1000.f, 1000.f };
|
||||
// Center on the other side of the CCW arch.
|
||||
Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1);
|
||||
THEN("45 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.25 * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.25 * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("45 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
}
|
||||
THEN("315 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx((2. - 0.25) * M_PI));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 0.25) * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("315 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
}
|
||||
}
|
||||
WHEN("arc from { 1866.f, 1500.f } to { 1000.f, 2000.f }") {
|
||||
Vec2f p1{ 1866.f, 1500.f };
|
||||
Vec2f p2{ 1000.f, 2000.f };
|
||||
float r{ 1000.f };
|
||||
Vec2f center1 = Vec2f{ 1000.f, 1000.f };
|
||||
// Center on the other side of the CCW arch.
|
||||
Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1);
|
||||
THEN("60 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, r), float(M_PI / 3.), 0.001f));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * M_PI / 3.).epsilon(0.001));
|
||||
}
|
||||
THEN("60 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
}
|
||||
THEN("300 degrees arc, CCW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
|
||||
REQUIRE(is_approx(c, center2, 1.f));
|
||||
REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, - r), float((2. - 1./3.) * M_PI), 0.001f));
|
||||
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 1. / 3.) * M_PI).epsilon(0.001));
|
||||
}
|
||||
THEN("300 degrees arc, CW") {
|
||||
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
|
||||
REQUIRE(is_approx(c, center1, 1.f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("arc discretization", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
WHEN("arc from { 2, 1 } to { 1, 2 }") {
|
||||
const Point p1 = Point::new_scale(2., 1.);
|
||||
const Point p2 = Point::new_scale(1., 2.);
|
||||
const Point center = Point::new_scale(1., 1.);
|
||||
const float radius = scaled<float>(1.);
|
||||
const float resolution = scaled<float>(0.002);
|
||||
auto test = [center, resolution, radius](const Point &p1, const Point &p2, const float r, const bool ccw) {
|
||||
Vec2f c = ArcWelder::arc_center(p1.cast<float>(), p2.cast<float>(), r, ccw);
|
||||
REQUIRE((p1.cast<float>() - c).norm() == Approx(radius));
|
||||
REQUIRE((c - center.cast<float>()).norm() == Approx(0.));
|
||||
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
|
||||
REQUIRE(pts.size() >= 2);
|
||||
REQUIRE(pts.front() == p1);
|
||||
REQUIRE(pts.back() == p2);
|
||||
for (const Point &p : pts)
|
||||
REQUIRE(std::abs((p.cast<double>() - c.cast<double>()).norm() - double(radius)) < double(resolution + SCALED_EPSILON));
|
||||
};
|
||||
THEN("90 degrees arc, CCW") {
|
||||
test(p1, p2, radius, true);
|
||||
}
|
||||
THEN("270 degrees arc, CCW") {
|
||||
test(p2, p1, - radius, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW") {
|
||||
test(p2, p1, radius, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW") {
|
||||
test(p1, p2, - radius, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("arc fitting", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
WHEN("arc from { 2, 1 } to { 1, 2 }") {
|
||||
const Point p1 = Point::new_scale(2., 1.);
|
||||
const Point p2 = Point::new_scale(1., 2.);
|
||||
const Point center = Point::new_scale(1., 1.);
|
||||
const float radius = scaled<float>(1.);
|
||||
const float resolution = scaled<float>(0.002);
|
||||
auto test = [center, resolution](const Point &p1, const Point &p2, const float r, const bool ccw) {
|
||||
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
|
||||
ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution);
|
||||
REQUIRE(path.size() == 2);
|
||||
REQUIRE(path.front().point == p1);
|
||||
REQUIRE(path.front().radius == 0.f);
|
||||
REQUIRE(path.back().point == p2);
|
||||
REQUIRE(path.back().radius == Approx(r));
|
||||
REQUIRE(path.back().ccw() == ccw);
|
||||
};
|
||||
THEN("90 degrees arc, CCW is fitted") {
|
||||
test(p1, p2, radius, true);
|
||||
}
|
||||
THEN("270 degrees arc, CCW is fitted") {
|
||||
test(p2, p1, - radius, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW is fitted") {
|
||||
test(p2, p1, radius, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW is fitted") {
|
||||
test(p1, p2, - radius, false);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("arc from { 2, 1 } to { 1, 2 }, another arc from { 2, 1 } to { 0, 2 }, tangentially connected") {
|
||||
const Point p1 = Point::new_scale(2., 1.);
|
||||
const Point p2 = Point::new_scale(1., 2.);
|
||||
const Point p3 = Point::new_scale(0., 3.);
|
||||
const Point center1 = Point::new_scale(1., 1.);
|
||||
const Point center2 = Point::new_scale(1., 3.);
|
||||
const float radius = scaled<float>(1.);
|
||||
const float resolution = scaled<float>(0.002);
|
||||
auto test = [center1, center2, resolution](const Point &p1, const Point &p2, const Point &p3, const float r, const bool ccw) {
|
||||
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
|
||||
{
|
||||
Points pts2 = ArcWelder::arc_discretize(p2, p3, - r, ! ccw, resolution);
|
||||
REQUIRE(pts.back() == pts2.front());
|
||||
pts.insert(pts.end(), std::next(pts2.begin()), pts2.end());
|
||||
}
|
||||
ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution);
|
||||
REQUIRE(path.size() == 3);
|
||||
REQUIRE(path.front().point == p1);
|
||||
REQUIRE(path.front().radius == 0.f);
|
||||
REQUIRE(path[1].point == p2);
|
||||
REQUIRE(path[1].radius == Approx(r));
|
||||
REQUIRE(path[1].ccw() == ccw);
|
||||
REQUIRE(path.back().point == p3);
|
||||
REQUIRE(path.back().radius == Approx(- r));
|
||||
REQUIRE(path.back().ccw() == ! ccw);
|
||||
};
|
||||
THEN("90 degrees arches, CCW are fitted") {
|
||||
test(p1, p2, p3, radius, true);
|
||||
}
|
||||
THEN("270 degrees arc, CCW is fitted") {
|
||||
test(p3, p2, p1, -radius, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW is fitted") {
|
||||
test(p3, p2, p1, radius, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW is fitted") {
|
||||
test(p1, p2, p3, -radius, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("arc wedge test", "[ArcWelder]") {
|
||||
using namespace Slic3r::Geometry;
|
||||
|
||||
WHEN("test point inside wedge, arc from { 2, 1 } to { 1, 2 }") {
|
||||
const int64_t s = 1000000;
|
||||
const Vec2i64 p1{ 2 * s, s };
|
||||
const Vec2i64 p2{ s, 2 * s };
|
||||
const Vec2i64 center{ s, s };
|
||||
const int64_t radius{ s };
|
||||
auto test = [center](
|
||||
// Arc data
|
||||
const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw,
|
||||
// Test data
|
||||
const Vec2i64 &ptest, const bool ptest_inside) {
|
||||
const Vec2d c = ArcWelder::arc_center(p1.cast<double>(), p2.cast<double>(), double(r), ccw);
|
||||
REQUIRE(is_approx(c, center.cast<double>()));
|
||||
REQUIRE(ArcWelder::inside_arc_wedge(p1, p2, center, r > 0, ccw, ptest) == ptest_inside);
|
||||
REQUIRE(ArcWelder::inside_arc_wedge(p1.cast<double>(), p2.cast<double>(), double(r), ccw, ptest.cast<double>()) == ptest_inside);
|
||||
};
|
||||
auto test_quadrants = [center, test](
|
||||
// Arc data
|
||||
const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw,
|
||||
// Test data
|
||||
const Vec2i64 &ptest1, const bool ptest_inside1,
|
||||
const Vec2i64 &ptest2, const bool ptest_inside2,
|
||||
const Vec2i64 &ptest3, const bool ptest_inside3,
|
||||
const Vec2i64 &ptest4, const bool ptest_inside4) {
|
||||
test(p1, p2, r, ccw, ptest1 + center, ptest_inside1);
|
||||
test(p1, p2, r, ccw, ptest2 + center, ptest_inside2);
|
||||
test(p1, p2, r, ccw, ptest3 + center, ptest_inside3);
|
||||
test(p1, p2, r, ccw, ptest4 + center, ptest_inside4);
|
||||
};
|
||||
THEN("90 degrees arc, CCW") {
|
||||
test_quadrants(p1, p2, radius, true,
|
||||
Vec2i64{ s, s }, true,
|
||||
Vec2i64{ s, - s }, false,
|
||||
Vec2i64{ - s, s }, false,
|
||||
Vec2i64{ - s, - s }, false);
|
||||
}
|
||||
THEN("270 degrees arc, CCW") {
|
||||
test_quadrants(p2, p1, -radius, true,
|
||||
Vec2i64{ s, s }, false,
|
||||
Vec2i64{ s, - s }, true,
|
||||
Vec2i64{ - s, s }, true,
|
||||
Vec2i64{ - s, - s }, true);
|
||||
}
|
||||
THEN("90 degrees arc, CW") {
|
||||
test_quadrants(p2, p1, radius, false,
|
||||
Vec2i64{ s, s }, true,
|
||||
Vec2i64{ s, - s }, false,
|
||||
Vec2i64{ - s, s }, false,
|
||||
Vec2i64{ - s, - s }, false);
|
||||
}
|
||||
THEN("270 degrees arc, CW") {
|
||||
test_quadrants(p1, p2, -radius, false,
|
||||
Vec2i64{ s, s }, false,
|
||||
Vec2i64{ s, - s }, true,
|
||||
Vec2i64{ - s, s }, true,
|
||||
Vec2i64{ - s, - s }, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -84,16 +84,16 @@ TEST_CASE("Line::perpendicular_to", "[Geometry]") {
|
||||
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
|
||||
// this test was failing on Windows (GH #1950)
|
||||
Slic3r::Polygon polygon(Points({
|
||||
Point(207802834,-57084522),
|
||||
Point(196528149,-37556190),
|
||||
Point(173626821,-25420928),
|
||||
Point(171285751,-21366123),
|
||||
Point(118673592,-21366123),
|
||||
Point(116332562,-25420928),
|
||||
Point(93431208,-37556191),
|
||||
Point(82156517,-57084523),
|
||||
Point(129714478,-84542120),
|
||||
Point(160244873,-84542120)
|
||||
{207802834,-57084522},
|
||||
{196528149,-37556190},
|
||||
{173626821,-25420928},
|
||||
{171285751,-21366123},
|
||||
{118673592,-21366123},
|
||||
{116332562,-25420928},
|
||||
{93431208,-37556191},
|
||||
{82156517,-57084523},
|
||||
{129714478,-84542120},
|
||||
{160244873,-84542120}
|
||||
}));
|
||||
Point point(95706562, -57294774);
|
||||
REQUIRE(polygon.contains(point));
|
||||
@ -196,6 +196,41 @@ TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
|
||||
REQUIRE(area.area() == Slic3r::Polygon(Points({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
|
||||
}
|
||||
|
||||
SCENARIO("Circle Fit, 3 points", "[Geometry]") {
|
||||
WHEN("Three points make a circle") {
|
||||
double s1 = scaled<double>(1.);
|
||||
THEN("circle_center(): A center point { 0, 0 } is returned") {
|
||||
Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(is_approx(center, Vec2d(0, 0)));
|
||||
}
|
||||
THEN("circle_center(): A center point { 0, 0 } is returned for points in reverse") {
|
||||
Vec2d center = Geometry::circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(is_approx(center, Vec2d(0, 0)));
|
||||
}
|
||||
THEN("try_circle_center(): A center point { 0, 0 } is returned") {
|
||||
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(center);
|
||||
REQUIRE(is_approx(*center, Vec2d(0, 0)));
|
||||
}
|
||||
THEN("try_circle_center(): A center point { 0, 0 } is returned for points in reverse") {
|
||||
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(center);
|
||||
REQUIRE(is_approx(*center, Vec2d(0, 0)));
|
||||
}
|
||||
}
|
||||
WHEN("Three points are collinear") {
|
||||
double s1 = scaled<double>(1.);
|
||||
THEN("circle_center(): A center point { 2, 0 } is returned") {
|
||||
Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(is_approx(center, Vec2d(2. * s1, 0)));
|
||||
}
|
||||
THEN("try_circle_center(): Fails for collinear points") {
|
||||
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON);
|
||||
REQUIRE(! center);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
|
||||
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
|
||||
Vec2d expected_center(-6, 0);
|
||||
@ -310,6 +345,7 @@ SCENARIO("Path chaining", "[Geometry]") {
|
||||
GIVEN("A path") {
|
||||
Points points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
|
||||
THEN("Chained with no diagonals (thus 26 units long)") {
|
||||
// if chain_points() works correctly, these points should be joined with no diagonal paths
|
||||
std::vector<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