diff --git a/src/clipper/CMakeLists.txt b/src/clipper/CMakeLists.txt index d6f3861dc..412ab53c7 100644 --- a/src/clipper/CMakeLists.txt +++ b/src/clipper/CMakeLists.txt @@ -4,4 +4,6 @@ cmake_minimum_required(VERSION 2.6) add_library(clipper STATIC clipper.cpp clipper.hpp + clipper_z.cpp + clipper_z.hpp ) diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 228e0c6ef..b85cf9025 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -51,7 +51,11 @@ #include <Shiny/Shiny.h> #include <libslic3r/Int128.hpp> +#ifdef use_xyz +namespace ClipperLib_Z { +#else /* use_xyz */ namespace ClipperLib { +#endif /* use_xyz */ static double const pi = 3.141592653589793238; static double const two_pi = pi *2; @@ -1616,7 +1620,7 @@ void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2) else if (pt == e1.Top) pt.Z = e1.Top.Z; else if (pt == e2.Bot) pt.Z = e2.Bot.Z; else if (pt == e2.Top) pt.Z = e2.Top.Z; - else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); + else m_ZFill(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); } //------------------------------------------------------------------------------ #endif diff --git a/src/clipper/clipper.hpp b/src/clipper/clipper.hpp index 8a28fe46f..8b34779e3 100644 --- a/src/clipper/clipper.hpp +++ b/src/clipper/clipper.hpp @@ -35,6 +35,7 @@ #define clipper_hpp #include <inttypes.h> +#include <functional> #define CLIPPER_VERSION "6.2.6" @@ -56,7 +57,11 @@ #include <functional> #include <queue> +#ifdef use_xyz +namespace ClipperLib_Z { +#else /* use_xyz */ namespace ClipperLib { +#endif /* use_xyz */ enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; enum PolyType { ptSubject, ptClip }; @@ -114,7 +119,7 @@ struct DoublePoint //------------------------------------------------------------------------------ #ifdef use_xyz -typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt); +typedef std::function<void(const IntPoint& e1bot, const IntPoint& e1top, const IntPoint& e2bot, const IntPoint& e2top, IntPoint& pt)> ZFillCallback; #endif enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4}; diff --git a/src/clipper/clipper_z.cpp b/src/clipper/clipper_z.cpp new file mode 100644 index 000000000..4a54ef346 --- /dev/null +++ b/src/clipper/clipper_z.cpp @@ -0,0 +1,7 @@ +// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support. + +// Enable the Z coordinate support. +#define use_xyz + +// and let it compile +#include "clipper.cpp" diff --git a/src/clipper/clipper_z.hpp b/src/clipper/clipper_z.hpp new file mode 100644 index 000000000..0f31ac11c --- /dev/null +++ b/src/clipper/clipper_z.hpp @@ -0,0 +1,18 @@ +// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support. + +#ifndef clipper_z_hpp +#ifdef clipper_hpp +#error "You should include the clipper_z.hpp first" +#endif + +#define clipper_z_hpp + +// Enable the Z coordinate support. +#define use_xyz + +#include "clipper.hpp" + +#undef clipper_hpp +#undef use_xyz + +#endif clipper_z_hpp diff --git a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp index 4bb2e72af..2df9a26c3 100644 --- a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp @@ -33,10 +33,16 @@ protected: // then it should be removed from the list auto it = c.begin(); while (it != c.end() && !stopcond_()) { - Placer p{bin}; - p.configure(pcfg); + + // WARNING: The copy of itm needs to be created before Placer. + // Placer is working with references and its destructor still + // manipulates the item this is why the order of stack creation + // matters here. const Item& itm = *it; Item cpy{itm}; + + Placer p{bin}; + p.configure(pcfg); if (!p.pack(cpy)) it = c.erase(it); else it++; } diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 035fe3242..7f57b78af 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -12,44 +12,35 @@ namespace Slic3r { -void -ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const +void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { this->_inflate_collection(intersection_pl(this->polyline, collection), retval); } -void -ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const +void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { this->_inflate_collection(diff_pl(this->polyline, collection), retval); } -void -ExtrusionPath::clip_end(double distance) +void ExtrusionPath::clip_end(double distance) { this->polyline.clip_end(distance); } -void -ExtrusionPath::simplify(double tolerance) +void ExtrusionPath::simplify(double tolerance) { this->polyline.simplify(tolerance); } -double -ExtrusionPath::length() const +double ExtrusionPath::length() const { return this->polyline.length(); } -void -ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const +void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const { - for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) { - ExtrusionPath* path = this->clone(); - path->polyline = *it; - collection->entities.push_back(path); - } + for (const Polyline &polyline : polylines) + collection->entities.emplace_back(new ExtrusionPath(polyline, *this)); } void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const @@ -67,36 +58,36 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale void ExtrusionMultiPath::reverse() { - for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) - path->reverse(); + for (ExtrusionPath &path : this->paths) + path.reverse(); std::reverse(this->paths.begin(), this->paths.end()); } double ExtrusionMultiPath::length() const { double len = 0; - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) - len += path->polyline.length(); + for (const ExtrusionPath &path : this->paths) + len += path.polyline.length(); return len; } void ExtrusionMultiPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) - path->polygons_covered_by_width(out, scaled_epsilon); + for (const ExtrusionPath &path : this->paths) + path.polygons_covered_by_width(out, scaled_epsilon); } void ExtrusionMultiPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const { - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) - path->polygons_covered_by_spacing(out, scaled_epsilon); + for (const ExtrusionPath &path : this->paths) + path.polygons_covered_by_spacing(out, scaled_epsilon); } double ExtrusionMultiPath::min_mm3_per_mm() const { double min_mm3_per_mm = std::numeric_limits<double>::max(); - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) - min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm); + for (const ExtrusionPath &path : this->paths) + min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm); return min_mm3_per_mm; } @@ -121,52 +112,46 @@ Polyline ExtrusionMultiPath::as_polyline() const return out; } -bool -ExtrusionLoop::make_clockwise() +bool ExtrusionLoop::make_clockwise() { bool was_ccw = this->polygon().is_counter_clockwise(); if (was_ccw) this->reverse(); return was_ccw; } -bool -ExtrusionLoop::make_counter_clockwise() +bool ExtrusionLoop::make_counter_clockwise() { bool was_cw = this->polygon().is_clockwise(); if (was_cw) this->reverse(); return was_cw; } -void -ExtrusionLoop::reverse() +void ExtrusionLoop::reverse() { - for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) - path->reverse(); + for (ExtrusionPath &path : this->paths) + path.reverse(); std::reverse(this->paths.begin(), this->paths.end()); } -Polygon -ExtrusionLoop::polygon() const +Polygon ExtrusionLoop::polygon() const { Polygon polygon; - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { + for (const ExtrusionPath &path : this->paths) { // for each polyline, append all points except the last one (because it coincides with the first one of the next polyline) - polygon.points.insert(polygon.points.end(), path->polyline.points.begin(), path->polyline.points.end()-1); + polygon.points.insert(polygon.points.end(), path.polyline.points.begin(), path.polyline.points.end()-1); } return polygon; } -double -ExtrusionLoop::length() const +double ExtrusionLoop::length() const { double len = 0; - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) - len += path->polyline.length(); + for (const ExtrusionPath &path : this->paths) + len += path.polyline.length(); return len; } -bool -ExtrusionLoop::split_at_vertex(const Point &point) +bool ExtrusionLoop::split_at_vertex(const Point &point) { for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) { int idx = path->polyline.find_point(point); @@ -220,18 +205,18 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang) Point p_non_overhang; size_t path_idx_non_overhang = 0; double min_non_overhang = std::numeric_limits<double>::max(); - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { - Point p_tmp = point.projection_onto(path->polyline); + for (const ExtrusionPath &path : this->paths) { + Point p_tmp = point.projection_onto(path.polyline); double dist = (p_tmp - point).cast<double>().norm(); if (dist < min) { p = p_tmp; min = dist; - path_idx = path - this->paths.begin(); + path_idx = &path - &this->paths.front(); } - if (prefer_non_overhang && ! is_bridge(path->role()) && dist < min_non_overhang) { + if (prefer_non_overhang && ! is_bridge(path.role()) && dist < min_non_overhang) { p_non_overhang = p_tmp; min_non_overhang = dist; - path_idx_non_overhang = path - this->paths.begin(); + path_idx_non_overhang = &path - &this->paths.front(); } } if (prefer_non_overhang && min_non_overhang != std::numeric_limits<double>::max()) { @@ -267,8 +252,7 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang) this->split_at_vertex(p); } -void -ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const +void ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const { *paths = this->paths; @@ -285,15 +269,14 @@ ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const } } -bool -ExtrusionLoop::has_overhang_point(const Point &point) const +bool ExtrusionLoop::has_overhang_point(const Point &point) const { - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) { - int pos = path->polyline.find_point(point); + for (const ExtrusionPath &path : this->paths) { + int pos = path.polyline.find_point(point); if (pos != -1) { // point belongs to this path // we consider it overhang only if it's not an endpoint - return (is_bridge(path->role()) && pos > 0 && pos != (int)(path->polyline.points.size())-1); + return (is_bridge(path.role()) && pos > 0 && pos != (int)(path.polyline.points.size())-1); } } return false; @@ -301,22 +284,21 @@ ExtrusionLoop::has_overhang_point(const Point &point) const void ExtrusionLoop::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) - path->polygons_covered_by_width(out, scaled_epsilon); + for (const ExtrusionPath &path : this->paths) + path.polygons_covered_by_width(out, scaled_epsilon); } void ExtrusionLoop::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const { - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) - path->polygons_covered_by_spacing(out, scaled_epsilon); + for (const ExtrusionPath &path : this->paths) + path.polygons_covered_by_spacing(out, scaled_epsilon); } -double -ExtrusionLoop::min_mm3_per_mm() const +double ExtrusionLoop::min_mm3_per_mm() const { double min_mm3_per_mm = std::numeric_limits<double>::max(); - for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) - min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm); + for (const ExtrusionPath &path : this->paths) + min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm); return min_mm3_per_mm; } @@ -344,15 +326,4 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role) return ""; } -//std::string ExtrusionLoop::role_to_string(ExtrusionLoopRole role) -//{ -// switch (role) { -// case elrDefault : return "elrDefault"; -// case elrContourInternalPerimeter: return "elrContourInternalPerimeter"; -// case elrSkirt : return "elrSkirt"; -// default : assert(false); -// } -//}; - - } diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 1d2465ae4..ce52ae152 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -75,6 +75,8 @@ public: virtual bool is_loop() const { return false; } virtual bool can_reverse() const { return true; } virtual ExtrusionEntity* clone() const = 0; + // Create a new object, initialize it with this object using the move semantics. + virtual ExtrusionEntity* clone_move() = 0; virtual ~ExtrusionEntity() {} virtual void reverse() = 0; virtual Point first_point() const = 0; @@ -111,25 +113,29 @@ public: double mm3_per_mm; // Width of the extrusion, used for visualization purposes. float width; - // Height of the extrusion, used for visualization purposed. + // Height of the extrusion, used for visualization purposes. float height; - // Feedrate of the extrusion, used for visualization purposed. + // Feedrate of the extrusion, used for visualization purposes. float feedrate; - // Id of the extruder, used for visualization purposed. + // Id of the extruder, used for visualization purposes. unsigned int extruder_id; - // Id of the color, used for visualization purposed in the color printing case. + // Id of the color, used for visualization purposes in the color printing case. unsigned int cp_color_id; ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {} ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {} ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), 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), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), 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), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), 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), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), 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), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} // ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {}; 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->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; 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->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = std::move(rhs.polyline); return *this; } - ExtrusionPath* clone() const override { return new ExtrusionPath (*this); } + ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); } + // Create a new object, initialize it with this object using the move semantics. + ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); } void reverse() override { this->polyline.reverse(); } Point first_point() const override { return this->polyline.points.front(); } Point last_point() const override { return this->polyline.points.back(); } @@ -188,7 +194,9 @@ public: bool is_loop() const override { return false; } bool can_reverse() const override { return true; } - ExtrusionMultiPath* clone() const override { return new ExtrusionMultiPath(*this); } + ExtrusionEntity* clone() const override { return new ExtrusionMultiPath(*this); } + // Create a new object, initialize it with this object using the move semantics. + ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); } void reverse() override; Point first_point() const override { return this->paths.front().polyline.points.front(); } Point last_point() const override { return this->paths.back().polyline.points.back(); } @@ -227,7 +235,9 @@ public: { this->paths.emplace_back(std::move(path)); } bool is_loop() const override{ return true; } bool can_reverse() const override { return false; } - ExtrusionLoop* clone() const override{ return new ExtrusionLoop (*this); } + ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); } + // Create a new object, initialize it with this object using the move semantics. + ExtrusionEntity* clone_move() override { return new ExtrusionLoop(std::move(*this)); } bool make_clockwise(); bool make_counter_clockwise(); void reverse() override; diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 7a086bcbf..9ae116c47 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -21,8 +21,7 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator= (const Extrusion return *this; } -void -ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c) +void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c) { std::swap(this->entities, c.entities); std::swap(this->orig_indices, c.orig_indices); @@ -39,15 +38,14 @@ void ExtrusionEntityCollection::clear() ExtrusionEntityCollection::operator ExtrusionPaths() const { ExtrusionPaths paths; - for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { - if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(*it)) + for (const ExtrusionEntity *ptr : this->entities) { + if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(ptr)) paths.push_back(*path); } return paths; } -ExtrusionEntityCollection* -ExtrusionEntityCollection::clone() const +ExtrusionEntity* ExtrusionEntityCollection::clone() const { ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this); for (size_t i = 0; i < coll->entities.size(); ++i) @@ -55,41 +53,36 @@ ExtrusionEntityCollection::clone() const return coll; } -void -ExtrusionEntityCollection::reverse() +void ExtrusionEntityCollection::reverse() { - for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) { + for (ExtrusionEntity *ptr : this->entities) // Don't reverse it if it's a loop, as it doesn't change anything in terms of elements ordering // and caller might rely on winding order - if (!(*it)->is_loop()) (*it)->reverse(); - } + if (! ptr->is_loop()) + ptr->reverse(); std::reverse(this->entities.begin(), this->entities.end()); } -void -ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity) +void ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity) { delete this->entities[i]; this->entities[i] = entity.clone(); } -void -ExtrusionEntityCollection::remove(size_t i) +void ExtrusionEntityCollection::remove(size_t i) { delete this->entities[i]; this->entities.erase(this->entities.begin() + i); } -ExtrusionEntityCollection -ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const +ExtrusionEntityCollection ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const { ExtrusionEntityCollection coll; this->chained_path(&coll, no_reverse, role); return coll; } -void -ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const +void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const { if (this->entities.empty()) return; this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role, orig_indices); @@ -108,6 +101,7 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt *retval = *this; return; } + retval->entities.reserve(this->entities.size()); retval->orig_indices.reserve(this->entities.size()); @@ -115,10 +109,10 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt std::map<ExtrusionEntity*,size_t> indices_map; ExtrusionEntitiesPtr my_paths; - for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { + for (ExtrusionEntity * const &entity_src : this->entities) { if (role != erMixed) { // The caller wants only paths with a specific extrusion role. - auto role2 = (*it)->role(); + auto role2 = entity_src->role(); if (role != role2) { // This extrusion entity does not match the role asked. assert(role2 != erMixed); @@ -126,32 +120,30 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt } } - ExtrusionEntity* entity = (*it)->clone(); + ExtrusionEntity *entity = entity_src->clone(); my_paths.push_back(entity); - if (orig_indices != NULL) indices_map[entity] = it - this->entities.begin(); + if (orig_indices != nullptr) + indices_map[entity] = &entity_src - &this->entities.front(); } Points endpoints; - for (ExtrusionEntitiesPtr::iterator it = my_paths.begin(); it != my_paths.end(); ++it) { - endpoints.push_back((*it)->first_point()); - if (no_reverse || !(*it)->can_reverse()) { - endpoints.push_back((*it)->first_point()); - } else { - endpoints.push_back((*it)->last_point()); - } + for (const ExtrusionEntity *entity : my_paths) { + endpoints.push_back(entity->first_point()); + endpoints.push_back((no_reverse || ! entity->can_reverse()) ? + entity->first_point() : entity->last_point()); } - while (!my_paths.empty()) { + while (! my_paths.empty()) { // find nearest point int start_index = start_near.nearest_point_index(endpoints); int path_index = start_index/2; ExtrusionEntity* entity = my_paths.at(path_index); // never reverse loops, since it's pointless for chained path and callers might depend on orientation - if (start_index % 2 && !no_reverse && entity->can_reverse()) { + if (start_index % 2 && !no_reverse && entity->can_reverse()) entity->reverse(); - } retval->entities.push_back(my_paths.at(path_index)); - if (orig_indices != NULL) orig_indices->push_back(indices_map[entity]); + if (orig_indices != nullptr) + orig_indices->push_back(indices_map[entity]); my_paths.erase(my_paths.begin() + path_index); endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2); start_near = retval->entities.back()->last_point(); @@ -160,60 +152,50 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { - for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) - (*it)->polygons_covered_by_width(out, scaled_epsilon); + for (const ExtrusionEntity *entity : this->entities) + entity->polygons_covered_by_width(out, scaled_epsilon); } void ExtrusionEntityCollection::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const { - for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) - (*it)->polygons_covered_by_spacing(out, scaled_epsilon); + for (const ExtrusionEntity *entity : this->entities) + entity->polygons_covered_by_spacing(out, scaled_epsilon); } -/* Recursively count paths and loops contained in this collection */ -size_t -ExtrusionEntityCollection::items_count() const +// Recursively count paths and loops contained in this collection. +size_t ExtrusionEntityCollection::items_count() const { size_t count = 0; - for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { - if ((*it)->is_collection()) { - ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(*it); - count += collection->items_count(); - } else { - ++count; - } - } + for (const ExtrusionEntity *entity : this->entities) + if (entity->is_collection()) + count += static_cast<const ExtrusionEntityCollection*>(entity)->items_count(); + else + ++ count; return count; } -/* Returns a single vector of pointers to all non-collection items contained in this one */ -void -ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const +// Returns a single vector of pointers to all non-collection items contained in this one. +void ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const { - for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) { - if ((*it)->is_collection()) { - ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(*it); - retval->append(collection->flatten().entities); - } else { - retval->append(**it); - } - } + for (const ExtrusionEntity *entity : this->entities) + if (entity->is_collection()) + retval->append(static_cast<const ExtrusionEntityCollection*>(entity)->flatten().entities); + else + retval->append(*entity); } -ExtrusionEntityCollection -ExtrusionEntityCollection::flatten() const +ExtrusionEntityCollection ExtrusionEntityCollection::flatten() const { ExtrusionEntityCollection coll; this->flatten(&coll); return coll; } -double -ExtrusionEntityCollection::min_mm3_per_mm() const +double ExtrusionEntityCollection::min_mm3_per_mm() const { double min_mm3_per_mm = std::numeric_limits<double>::max(); - for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) - min_mm3_per_mm = std::min(min_mm3_per_mm, (*it)->min_mm3_per_mm()); + for (const ExtrusionEntity *entity : this->entities) + min_mm3_per_mm = std::min(min_mm3_per_mm, entity->min_mm3_per_mm()); return min_mm3_per_mm; } diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 230c04160..4fe964ee1 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -9,7 +9,10 @@ namespace Slic3r { class ExtrusionEntityCollection : public ExtrusionEntity { public: - ExtrusionEntityCollection* clone() const; + ExtrusionEntity* clone() const override; + // Create a new object, initialize it with this object using the move semantics. + ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); } + ExtrusionEntitiesPtr entities; // we own these entities std::vector<size_t> orig_indices; // handy for XS bool no_sort; @@ -36,11 +39,12 @@ public: bool empty() const { return this->entities.empty(); }; void clear(); void swap (ExtrusionEntityCollection &c); - void append(const ExtrusionEntity &entity) { this->entities.push_back(entity.clone()); } - void append(const ExtrusionEntitiesPtr &entities) { + void append(const ExtrusionEntity &entity) { this->entities.emplace_back(entity.clone()); } + void append(ExtrusionEntity &&entity) { this->entities.emplace_back(entity.clone_move()); } + void append(const ExtrusionEntitiesPtr &entities) { this->entities.reserve(this->entities.size() + entities.size()); - for (ExtrusionEntitiesPtr::const_iterator ptr = entities.begin(); ptr != entities.end(); ++ptr) - this->entities.push_back((*ptr)->clone()); + for (const ExtrusionEntity *ptr : entities) + this->entities.emplace_back(ptr->clone()); } void append(ExtrusionEntitiesPtr &&src) { if (entities.empty()) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c35afe9fc..0ccc3ddf5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1869,8 +1869,9 @@ void GCode::process_layer( if (! m_brim_done) { this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp = true; - for (const ExtrusionEntity *ee : print.brim().entities) - gcode += this->extrude_loop(*dynamic_cast<const ExtrusionLoop*>(ee), "brim", m_config.support_material_speed.value); + for (const ExtrusionEntity *ee : print.brim().entities) { + gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value); + } m_brim_done = true; m_avoid_crossing_perimeters.use_external_mp = false; // Allow a straight travel move to the first object point. @@ -2504,10 +2505,9 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des return this->extrude_multi_path(*multipath, description, speed); else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(&entity)) return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid); - else { + else throw std::invalid_argument("Invalid argument supplied to extrude()"); - return ""; - } + return ""; } std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed) diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 74df07935..0c16f4a1d 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -6,21 +6,240 @@ namespace Slic3r { +static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance) +{ + ExtrusionPaths paths; + ExtrusionPath path(role); + ThickLines lines = thick_polyline.thicklines(); + + for (int i = 0; i < (int)lines.size(); ++i) { + const ThickLine& line = lines[i]; + + const coordf_t line_len = line.length(); + if (line_len < SCALED_EPSILON) continue; + + double thickness_delta = fabs(line.a_width - line.b_width); + if (thickness_delta > tolerance) { + const unsigned int segments = (unsigned int)ceil(thickness_delta / tolerance); + const coordf_t seg_len = line_len / segments; + Points pp; + std::vector<coordf_t> width; + { + pp.push_back(line.a); + width.push_back(line.a_width); + for (size_t j = 1; j < segments; ++j) { + pp.push_back((line.a.cast<double>() + (line.b - line.a).cast<double>().normalized() * (j * seg_len)).cast<coord_t>()); + + coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len; + width.push_back(w); + width.push_back(w); + } + pp.push_back(line.b); + width.push_back(line.b_width); + + assert(pp.size() == segments + 1u); + assert(width.size() == segments*2); + } + + // delete this line and insert new ones + lines.erase(lines.begin() + i); + for (size_t j = 0; j < segments; ++j) { + ThickLine new_line(pp[j], pp[j+1]); + new_line.a_width = width[2*j]; + new_line.b_width = width[2*j+1]; + lines.insert(lines.begin() + i + j, new_line); + } + + -- i; + continue; + } + + const double w = fmax(line.a_width, line.b_width); + if (path.polyline.points.empty()) { + path.polyline.append(line.a); + path.polyline.append(line.b); + // Convert from spacing to extrusion width based on the extrusion model + // of a square extrusion ended with semi circles. + flow.width = unscale<float>(w) + flow.height * float(1. - 0.25 * PI); + #ifdef SLIC3R_DEBUG + printf(" filling %f gap\n", flow.width); + #endif + path.mm3_per_mm = flow.mm3_per_mm(); + path.width = flow.width; + path.height = flow.height; + } else { + thickness_delta = fabs(scale_(flow.width) - w); + if (thickness_delta <= tolerance) { + // the width difference between this line and the current flow width is + // within the accepted tolerance + path.polyline.append(line.b); + } else { + // we need to initialize a new line + paths.emplace_back(std::move(path)); + path = ExtrusionPath(role); + -- i; + } + } + } + if (path.polyline.is_valid()) + paths.emplace_back(std::move(path)); + return paths; +} + +static ExtrusionEntityCollection variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow) +{ + // This value determines granularity of adaptive width, as G-code does not allow + // variable extrusion within a single move; this value shall only affect the amount + // of segments, and any pruning shall be performed before we apply this tolerance. + ExtrusionEntityCollection coll; + const float tolerance = float(scale_(0.05)); + for (const ThickPolyline &p : polylines) { + ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance); + // Append paths to collection. + if (! paths.empty()) { + if (paths.front().first_point() == paths.back().last_point()) + coll.append(ExtrusionLoop(std::move(paths))); + else + coll.append(std::move(paths)); + } + } + return coll; +} + +// Hierarchy of perimeters. +class PerimeterGeneratorLoop { +public: + // Polygon of this contour. + Polygon polygon; + // Is it a contour or a hole? + // Contours are CCW oriented, holes are CW oriented. + bool is_contour; + // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. + unsigned short depth; + // Children contour, may be both CCW and CW oriented (outer contours or holes). + std::vector<PerimeterGeneratorLoop> children; + + PerimeterGeneratorLoop(Polygon polygon, unsigned short depth, bool is_contour) : + polygon(polygon), is_contour(is_contour), depth(depth) {} + // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). + bool is_external() const { return this->depth == 0; } + // An island, which may have holes, but it does not have another internal island. + bool is_internal_contour() const; +}; + +typedef std::vector<PerimeterGeneratorLoop> PerimeterGeneratorLoops; + +static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) +{ + // loops is an arrayref of ::Loop objects + // turn each one into an ExtrusionLoop object + ExtrusionEntityCollection coll; + for (const PerimeterGeneratorLoop &loop : loops) { + bool is_external = loop.is_external(); + + ExtrusionRole role; + ExtrusionLoopRole loop_role; + role = is_external ? erExternalPerimeter : erPerimeter; + if (loop.is_internal_contour()) { + // Note that we set loop role to ContourInternalPerimeter + // also when loop is both internal and external (i.e. + // there's only one contour loop). + loop_role = elrContourInternalPerimeter; + } else { + loop_role = elrDefault; + } + + // detect overhanging/bridging perimeters + ExtrusionPaths paths; + if (perimeter_generator.config->overhangs && perimeter_generator.layer_id > 0 + && !(perimeter_generator.object_config->support_material && perimeter_generator.object_config->support_material_contact_distance.value == 0)) { + // get non-overhang paths by intersecting this loop with the grown lower slices + extrusion_paths_append( + paths, + intersection_pl(loop.polygon, perimeter_generator.lower_slices_polygons()), + role, + is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(), + is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width, + (float)perimeter_generator.layer_height); + + // get overhang paths by checking what parts of this loop fall + // outside the grown lower slices (thus where the distance between + // the loop centerline and original lower slices is >= half nozzle diameter + extrusion_paths_append( + paths, + diff_pl(loop.polygon, perimeter_generator.lower_slices_polygons()), + erOverhangPerimeter, + perimeter_generator.mm3_per_mm_overhang(), + perimeter_generator.overhang_flow.width, + perimeter_generator.overhang_flow.height); + + // reapply the nearest point search for starting point + // We allow polyline reversal because Clipper may have randomly + // reversed polylines during clipping. + paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path(); + } else { + ExtrusionPath path(role); + path.polyline = loop.polygon.split_at_first_point(); + path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(); + path.width = is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width; + path.height = (float)perimeter_generator.layer_height; + paths.push_back(path); + } + + coll.append(ExtrusionLoop(paths, loop_role)); + } + + // Append thin walls to the nearest-neighbor search (only for first iteration) + if (! thin_walls.empty()) { + ExtrusionEntityCollection tw = variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow); + coll.append(tw.entities); + thin_walls.clear(); + } + + // Sort entities into a new collection using a nearest-neighbor search, + // preserving the original indices which are useful for detecting thin walls. + ExtrusionEntityCollection sorted_coll; + coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices); + + // traverse children and build the final collection + ExtrusionEntityCollection entities; + for (const size_t &idx : sorted_coll.orig_indices) { + if (idx >= loops.size()) { + // This is a thin wall. Let's get it from the sorted collection as it might have been reversed. + entities.append(std::move(*sorted_coll.entities[&idx - &sorted_coll.orig_indices.front()])); + } else { + const PerimeterGeneratorLoop &loop = loops[idx]; + ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[idx]); + ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls); + if (loop.is_contour) { + eloop.make_counter_clockwise(); + entities.append(std::move(children.entities)); + entities.append(std::move(eloop)); + } else { + eloop.make_clockwise(); + entities.append(std::move(eloop)); + entities.append(std::move(children.entities)); + } + } + } + return entities; +} + void PerimeterGenerator::process() { // other perimeters - this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); + m_mm3_per_mm = this->perimeter_flow.mm3_per_mm(); coord_t perimeter_width = this->perimeter_flow.scaled_width(); coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing(); // external perimeters - this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); + m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm(); coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width(); coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing(); coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow); // overhang perimeters - this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); + m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); // solid infill coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); @@ -35,8 +254,8 @@ void PerimeterGenerator::process() // which is the spacing between external and internal, which is not correct // and would make the collapsing (thus the details resolution) dependent on // internal flow which is unrelated. - coord_t min_spacing = perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE); - coord_t ext_min_spacing = ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE); + coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); + coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE)); bool has_gap_fill = this->config->gap_fill_speed.value > 0; // prepare grown lower layer slices for overhang detection @@ -45,7 +264,7 @@ void PerimeterGenerator::process() // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1); - this->_lower_slices_p = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2))); + m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2))); } // we need to process each island separately because we might have different @@ -70,20 +289,20 @@ void PerimeterGenerator::process() offsets = this->config->thin_walls ? offset2_ex( last, - -(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1), - +(ext_min_spacing / 2 - 1)) : - offset_ex(last, - ext_perimeter_width / 2); + - float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1), + + float(ext_min_spacing / 2. - 1)) : + offset_ex(last, - float(ext_perimeter_width / 2.)); // look for thin walls if (this->config->thin_walls) { // the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width // (actually, something larger than that still may exist due to mitering or other causes) - coord_t min_width = scale_(this->ext_perimeter_flow.nozzle_diameter / 3); + coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter / 3)); ExPolygons expp = offset2_ex( // medial axis requires non-overlapping geometry diff_ex(to_polygons(last), - offset(offsets, ext_perimeter_width / 2), + offset(offsets, float(ext_perimeter_width / 2.)), true), - - min_width / 2, min_width / 2); + - float(min_width / 2.), float(min_width / 2.)); // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop for (ExPolygon &ex : expp) ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls); @@ -100,19 +319,19 @@ void PerimeterGenerator::process() // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than // the original. offset2_ex(last, - - (distance + min_spacing / 2 - 1), - min_spacing / 2 - 1) : + - float(distance + min_spacing / 2. - 1.), + float(min_spacing / 2. - 1.)) : // If "detect thin walls" is not enabled, this paths will be entered, which // leads to overflows, as in prusa3d/Slic3r GH #32 - offset_ex(last, - distance); + offset_ex(last, - float(distance)); // look for gaps if (has_gap_fill) // not using safety offset here would "detect" very narrow gaps // (but still long enough to escape the area threshold) that gap fill // won't be able to fill but we'd still remove from infill area append(gaps, diff_ex( - offset(last, -0.5 * distance), - offset(offsets, 0.5 * distance + 10))); // safety offset + offset(last, - float(0.5 * distance)), + offset(offsets, float(0.5 * distance + 10)))); // safety offset } if (offsets.empty()) { // Store the number of loops actually generated. @@ -125,6 +344,11 @@ void PerimeterGenerator::process() break; } for (const ExPolygon &expolygon : offsets) { + // Outer contour may overlap with an inner contour, + // inner contour may overlap with another inner contour, + // outer contour may overlap with itself. + //FIXME evaluate the overlaps, annotate each point with an overlap depth, + // compensate for the depth of intersection. contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true)); if (! expolygon.holes.empty()) { holes[i].reserve(holes[i].size() + expolygon.holes.size()); @@ -195,7 +419,7 @@ void PerimeterGenerator::process() } } // at this point, all loops should be in contours[0] - ExtrusionEntityCollection entities = this->_traverse_loops(contours.front(), thin_walls); + ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls); // if brim will be printed, reverse the order of perimeters so that // we continue inwards after having finished the brim // TODO: add test for perimeter order @@ -214,15 +438,14 @@ void PerimeterGenerator::process() double max = 2. * perimeter_spacing; ExPolygons gaps_ex = diff_ex( //FIXME offset2 would be enough and cheaper. - offset2_ex(gaps, -min/2, +min/2), - offset2_ex(gaps, -max/2, +max/2), + offset2_ex(gaps, - float(min / 2.), float(min / 2.)), + offset2_ex(gaps, - float(max / 2.), float(max / 2.)), true); ThickPolylines polylines; for (const ExPolygon &ex : gaps_ex) ex.medial_axis(max, min, &polylines); if (! polylines.empty()) { - ExtrusionEntityCollection gap_fill = this->_variable_width(polylines, - erGapFill, this->solid_infill_flow); + ExtrusionEntityCollection gap_fill = variable_width(polylines, erGapFill, this->solid_infill_flow); this->gap_fill->append(gap_fill.entities); /* Make sure we don't infill narrow parts that are already gap-filled (we only consider this surface's gaps to reduce the diff() complexity). @@ -249,229 +472,23 @@ void PerimeterGenerator::process() perimeter_spacing / 2; // only apply infill overlap if we actually have one perimeter if (inset > 0) - inset -= scale_(this->config->get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2))); + inset -= coord_t(scale_(this->config->get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2)))); // simplify infill contours according to resolution Polygons pp; for (ExPolygon &ex : last) ex.simplify_p(SCALED_RESOLUTION, &pp); // collapse too narrow infill areas - coord_t min_perimeter_infill_spacing = solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE); + coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); // append infill areas to fill_surfaces this->fill_surfaces->append( offset2_ex( union_ex(pp), - - inset - min_perimeter_infill_spacing / 2, - min_perimeter_infill_spacing / 2), + float(- inset - min_perimeter_infill_spacing / 2.), + float(min_perimeter_infill_spacing / 2.)), stInternal); } // for each island } -ExtrusionEntityCollection PerimeterGenerator::_traverse_loops( - const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const -{ - // loops is an arrayref of ::Loop objects - // turn each one into an ExtrusionLoop object - ExtrusionEntityCollection coll; - for (PerimeterGeneratorLoops::const_iterator loop = loops.begin(); - loop != loops.end(); ++loop) { - bool is_external = loop->is_external(); - - ExtrusionRole role; - ExtrusionLoopRole loop_role; - role = is_external ? erExternalPerimeter : erPerimeter; - if (loop->is_internal_contour()) { - // Note that we set loop role to ContourInternalPerimeter - // also when loop is both internal and external (i.e. - // there's only one contour loop). - loop_role = elrContourInternalPerimeter; - } else { - loop_role = elrDefault; - } - - // detect overhanging/bridging perimeters - ExtrusionPaths paths; - if (this->config->overhangs && this->layer_id > 0 - && !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) { - // get non-overhang paths by intersecting this loop with the grown lower slices - extrusion_paths_append( - paths, - intersection_pl(loop->polygon, this->_lower_slices_p), - role, - is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm, - is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width, - this->layer_height); - - // get overhang paths by checking what parts of this loop fall - // outside the grown lower slices (thus where the distance between - // the loop centerline and original lower slices is >= half nozzle diameter - extrusion_paths_append( - paths, - diff_pl(loop->polygon, this->_lower_slices_p), - erOverhangPerimeter, - this->_mm3_per_mm_overhang, - this->overhang_flow.width, - this->overhang_flow.height); - - // reapply the nearest point search for starting point - // We allow polyline reversal because Clipper may have randomly - // reversed polylines during clipping. - paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path(); - } else { - ExtrusionPath path(role); - path.polyline = loop->polygon.split_at_first_point(); - path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; - path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width; - path.height = this->layer_height; - paths.push_back(path); - } - - coll.append(ExtrusionLoop(paths, loop_role)); - } - - // append thin walls to the nearest-neighbor search (only for first iteration) - if (!thin_walls.empty()) { - ExtrusionEntityCollection tw = this->_variable_width - (thin_walls, erExternalPerimeter, this->ext_perimeter_flow); - - coll.append(tw.entities); - thin_walls.clear(); - } - - // sort entities into a new collection using a nearest-neighbor search, - // preserving the original indices which are useful for detecting thin walls - ExtrusionEntityCollection sorted_coll; - coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices); - - // traverse children and build the final collection - ExtrusionEntityCollection entities; - for (std::vector<size_t>::const_iterator idx = sorted_coll.orig_indices.begin(); - idx != sorted_coll.orig_indices.end(); - ++idx) { - - if (*idx >= loops.size()) { - // this is a thin wall - // let's get it from the sorted collection as it might have been reversed - size_t i = idx - sorted_coll.orig_indices.begin(); - entities.append(*sorted_coll.entities[i]); - } else { - const PerimeterGeneratorLoop &loop = loops[*idx]; - ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[*idx]); - - ExtrusionEntityCollection children = this->_traverse_loops(loop.children, thin_walls); - if (loop.is_contour) { - eloop.make_counter_clockwise(); - entities.append(children.entities); - entities.append(eloop); - } else { - eloop.make_clockwise(); - entities.append(eloop); - entities.append(children.entities); - } - } - } - return entities; -} - -static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance) -{ - ExtrusionPaths paths; - ExtrusionPath path(role); - ThickLines lines = thick_polyline.thicklines(); - - for (int i = 0; i < (int)lines.size(); ++i) { - const ThickLine& line = lines[i]; - - const coordf_t line_len = line.length(); - if (line_len < SCALED_EPSILON) continue; - - double thickness_delta = fabs(line.a_width - line.b_width); - if (thickness_delta > tolerance) { - const unsigned short segments = ceil(thickness_delta / tolerance); - const coordf_t seg_len = line_len / segments; - Points pp; - std::vector<coordf_t> width; - { - pp.push_back(line.a); - width.push_back(line.a_width); - for (size_t j = 1; j < segments; ++j) { - pp.push_back((line.a.cast<double>() + (line.b - line.a).cast<double>().normalized() * (j * seg_len)).cast<coord_t>()); - - coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len; - width.push_back(w); - width.push_back(w); - } - pp.push_back(line.b); - width.push_back(line.b_width); - - assert(pp.size() == segments + 1u); - assert(width.size() == segments*2); - } - - // delete this line and insert new ones - lines.erase(lines.begin() + i); - for (size_t j = 0; j < segments; ++j) { - ThickLine new_line(pp[j], pp[j+1]); - new_line.a_width = width[2*j]; - new_line.b_width = width[2*j+1]; - lines.insert(lines.begin() + i + j, new_line); - } - - -- i; - continue; - } - - const double w = fmax(line.a_width, line.b_width); - if (path.polyline.points.empty()) { - path.polyline.append(line.a); - path.polyline.append(line.b); - // Convert from spacing to extrusion width based on the extrusion model - // of a square extrusion ended with semi circles. - flow.width = unscale<float>(w) + flow.height * (1. - 0.25 * PI); - #ifdef SLIC3R_DEBUG - printf(" filling %f gap\n", flow.width); - #endif - path.mm3_per_mm = flow.mm3_per_mm(); - path.width = flow.width; - path.height = flow.height; - } else { - thickness_delta = fabs(scale_(flow.width) - w); - if (thickness_delta <= tolerance) { - // the width difference between this line and the current flow width is - // within the accepted tolerance - path.polyline.append(line.b); - } else { - // we need to initialize a new line - paths.emplace_back(std::move(path)); - path = ExtrusionPath(role); - -- i; - } - } - } - if (path.polyline.is_valid()) - paths.emplace_back(std::move(path)); - return paths; -} - -ExtrusionEntityCollection PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const -{ - // This value determines granularity of adaptive width, as G-code does not allow - // variable extrusion within a single move; this value shall only affect the amount - // of segments, and any pruning shall be performed before we apply this tolerance. - ExtrusionEntityCollection coll; - const double tolerance = scale_(0.05); - for (const ThickPolyline &p : polylines) { - ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance); - // Append paths to collection. - if (! paths.empty()) { - if (paths.front().first_point() == paths.back().last_point()) - coll.append(ExtrusionLoop(std::move(paths))); - else - coll.append(std::move(paths)); - } - } - return coll; -} - bool PerimeterGeneratorLoop::is_internal_contour() const { // An internal contour is a contour containing no other contours diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index c01abbb15..8cd71e697 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -11,29 +11,6 @@ namespace Slic3r { -// Hierarchy of perimeters. -class PerimeterGeneratorLoop { -public: - // Polygon of this contour. - Polygon polygon; - // Is it a contour or a hole? - // Contours are CCW oriented, holes are CW oriented. - bool is_contour; - // Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole. - unsigned short depth; - // Children contour, may be both CCW and CW oriented (outer contours or holes). - std::vector<PerimeterGeneratorLoop> children; - - PerimeterGeneratorLoop(Polygon polygon, unsigned short depth, bool is_contour) : - polygon(polygon), is_contour(is_contour), depth(depth) {} - // External perimeter. It may be CCW or CW oriented (outer contour or hole contour). - bool is_external() const { return this->depth == 0; } - // An island, which may have holes, but it does not have another internal island. - bool is_internal_contour() const; -}; - -typedef std::vector<PerimeterGeneratorLoop> PerimeterGeneratorLoops; - class PerimeterGenerator { public: // Inputs: @@ -73,18 +50,21 @@ public: overhang_flow(flow), solid_infill_flow(flow), config(config), object_config(object_config), print_config(print_config), loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), - _ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1) + m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1) {} - void process(); + + void process(); + + double ext_mm3_per_mm() const { return m_ext_mm3_per_mm; } + double mm3_per_mm() const { return m_mm3_per_mm; } + double mm3_per_mm_overhang() const { return m_mm3_per_mm_overhang; } + Polygons lower_slices_polygons() const { return m_lower_slices_polygons; } private: - double _ext_mm3_per_mm; - double _mm3_per_mm; - double _mm3_per_mm_overhang; - Polygons _lower_slices_p; - - ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const; - ExtrusionEntityCollection _variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const; + double m_ext_mm3_per_mm; + double m_mm3_per_mm; + double m_mm3_per_mm_overhang; + Polygons m_lower_slices_polygons; }; } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 9d4df56d0..cbbbf1f1a 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1,3 +1,5 @@ +#include "clipper/clipper_z.hpp" + #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -1639,9 +1641,7 @@ void Print::_make_skirt() // Initial offset of the brim inner edge from the object (possible with a support & raft). // The skirt will touch the brim if the brim is extruded. - Flow brim_flow = this->brim_flow(); - double actual_brim_width = brim_flow.spacing() * floor(m_config.brim_width.value / brim_flow.spacing()); - auto distance = float(scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.)); + auto distance = float(scale_(m_config.skirt_distance.value) - spacing/2.); // Draw outlines from outside to inside. // Loop while we have less skirts than required or any extruder hasn't reached the min length if any. std::vector<coordf_t> extruded_length(extruders.size(), 0.); @@ -1723,12 +1723,134 @@ void Print::_make_brim() } polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); } - loops = union_pt_chained(loops, false); // The function above produces ordering well suited for concentric infill (from outside to inside). // For Brim, the ordering should be reversed (from inside to outside). std::reverse(loops.begin(), loops.end()); - extrusion_entities_append_loops(m_brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())); + + // If there is a possibility that brim intersects skirt, go through loops and split those extrusions + // The result is either the original Polygon or a list of Polylines + if (! m_skirt.empty() && m_config.skirt_distance.value < m_config.brim_width) + { + // Find the bounding polygons of the skirt + const Polygons skirt_inners = offset(dynamic_cast<ExtrusionLoop*>(m_skirt.entities.back())->polygon(), + -float(scale_(this->skirt_flow().spacing()))/2.f, + ClipperLib::jtRound, + float(scale_(0.1))); + const Polygons skirt_outers = offset(dynamic_cast<ExtrusionLoop*>(m_skirt.entities.front())->polygon(), + float(scale_(this->skirt_flow().spacing()))/2.f, + ClipperLib::jtRound, + float(scale_(0.1))); + + // First calculate the trimming region. + ClipperLib_Z::Paths trimming; + { + ClipperLib_Z::Paths input_subject; + ClipperLib_Z::Paths input_clip; + for (const Polygon &poly : skirt_outers) { + input_subject.emplace_back(); + ClipperLib_Z::Path &out = input_subject.back(); + out.reserve(poly.points.size()); + for (const Point &pt : poly.points) + out.emplace_back(pt.x(), pt.y(), 0); + } + for (const Polygon &poly : skirt_inners) { + input_clip.emplace_back(); + ClipperLib_Z::Path &out = input_clip.back(); + out.reserve(poly.points.size()); + for (const Point &pt : poly.points) + out.emplace_back(pt.x(), pt.y(), 0); + } + // init Clipper + ClipperLib_Z::Clipper clipper; + // add polygons + clipper.AddPaths(input_subject, ClipperLib_Z::ptSubject, true); + clipper.AddPaths(input_clip, ClipperLib_Z::ptClip, true); + // perform operation + clipper.Execute(ClipperLib_Z::ctDifference, trimming, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); + } + + // Second, trim the extrusion loops with the trimming regions. + ClipperLib_Z::Paths loops_trimmed; + { + // Produce a closed polyline (repeat the first point at the end). + ClipperLib_Z::Paths input_clip; + for (const Polygon &loop : loops) { + input_clip.emplace_back(); + ClipperLib_Z::Path& out = input_clip.back(); + out.reserve(loop.points.size()); + int64_t loop_idx = &loop - &loops.front(); + for (const Point& pt : loop.points) + // The Z coordinate carries index of the source loop. + out.emplace_back(pt.x(), pt.y(), loop_idx + 1); + out.emplace_back(out.front()); + } + // init Clipper + ClipperLib_Z::Clipper clipper; + clipper.ZFillFunction([](const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top, ClipperLib_Z::IntPoint& pt) { + // Assign a valid input loop identifier. Such an identifier is strictly positive, the next line is safe even in case one side of a segment + // hat the Z coordinate not set to the contour coordinate. + pt.Z = std::max(std::max(e1bot.Z, e1top.Z), std::max(e2bot.Z, e2top.Z)); + }); + // add polygons + clipper.AddPaths(input_clip, ClipperLib_Z::ptSubject, false); + clipper.AddPaths(trimming, ClipperLib_Z::ptClip, true); + // perform operation + ClipperLib_Z::PolyTree loops_trimmed_tree; + clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd); + ClipperLib_Z::PolyTreeToPaths(loops_trimmed_tree, loops_trimmed); + } + + // Third, produce the extrusions, sorted by the source loop indices. + { + std::vector<std::pair<const ClipperLib_Z::Path*, size_t>> loops_trimmed_order; + loops_trimmed_order.reserve(loops_trimmed.size()); + for (const ClipperLib_Z::Path &path : loops_trimmed) { + size_t input_idx = 0; + for (const ClipperLib_Z::IntPoint &pt : path) + if (pt.Z > 0) { + input_idx = (size_t)pt.Z; + break; + } + assert(input_idx != 0); + loops_trimmed_order.emplace_back(&path, input_idx); + } + std::stable_sort(loops_trimmed_order.begin(), loops_trimmed_order.end(), + [](const std::pair<const ClipperLib_Z::Path*, size_t> &l, const std::pair<const ClipperLib_Z::Path*, size_t> &r) { + return l.second < r.second; + }); + Vec3f last_pt(0.f, 0.f, 0.f); + + for (size_t i = 0; i < loops_trimmed_order.size();) { + // Find all pieces that the initial loop was split into. + size_t j = i + 1; + for (; j < loops_trimmed_order.size() && loops_trimmed_order[i].first == loops_trimmed_order[j].first; ++ j) ; + const ClipperLib_Z::Path &first_path = *loops_trimmed_order[i].first; + 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(); + m_brim.entities.emplace_back(loop); + loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())); + Points &points = loop->paths.front().polyline.points; + points.reserve(first_path.size()); + for (const ClipperLib_Z::IntPoint &pt : first_path) + points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); + i = j; + } else { + //FIXME this is not optimal as the G-code generator will follow the sequence of paths verbatim without respect to minimum travel distance. + for (; i < j; ++ i) { + m_brim.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()))); + const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first; + Points &points = static_cast<ExtrusionPath*>(m_brim.entities.back())->polyline.points; + points.reserve(path.size()); + for (const ClipperLib_Z::IntPoint &pt : path) + points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); + } + } + } + } + } else { + extrusion_entities_append_loops(m_brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())); + } } // Wipe tower support. diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index c0ffe2108..67b3d3a21 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -105,6 +105,8 @@ #include <cereal/access.hpp> #include <cereal/types/base_class.hpp> +#include <clipper/clipper_z.hpp> +#include <clipper/clipper.hpp> #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Config.hpp" diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 161e1a1ff..17b76e629 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -85,6 +85,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_ObjectLayers.hpp GUI/LambdaObjectDialog.cpp GUI/LambdaObjectDialog.hpp + GUI/MeshUtils.cpp + GUI/MeshUtils.hpp GUI/Tab.cpp GUI/Tab.hpp GUI/ConfigManipulation.cpp diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 202d3ef77..b15402a52 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -12,6 +12,7 @@ #include "Selection.hpp" #include "Gizmos/GLGizmosManager.hpp" #include "GUI_ObjectLayers.hpp" +#include "MeshUtils.hpp" #include <float.h> @@ -67,36 +68,6 @@ public: }; -class ClippingPlane -{ - double m_data[4]; - -public: - ClippingPlane() - { - m_data[0] = 0.0; - m_data[1] = 0.0; - m_data[2] = 1.0; - m_data[3] = 0.0; - } - - ClippingPlane(const Vec3d& direction, double offset) - { - Vec3d norm_dir = direction.normalized(); - m_data[0] = norm_dir(0); - m_data[1] = norm_dir(1); - m_data[2] = norm_dir(2); - m_data[3] = offset; - } - - bool is_active() const { return m_data[3] != DBL_MAX; } - - static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); } - - const double* get_data() const { return m_data; } -}; - - wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); using Vec2dEvent = Event<Vec2d>; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 0a5c21cce..80afb29b1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1,6 +1,7 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoSlaSupports.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Gizmos/GLGizmos.hpp" #include <GL/glew.h> @@ -12,10 +13,10 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/MeshUtils.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/PresetBundle.hpp" #include "libslic3r/SLAPrint.hpp" -#include "libslic3r/Tesselate.hpp" namespace Slic3r { @@ -26,6 +27,7 @@ GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& ic , m_quadric(nullptr) , m_its(nullptr) { + m_clipping_plane.reset(new ClippingPlane(Vec3d::Zero(), 0.)); m_quadric = ::gluNewQuadric(); if (m_quadric != nullptr) // using GLU_FILL does not work when the instance's transformation @@ -137,127 +139,82 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const if (m_clipping_plane_distance == 0.f) return; - if (m_clipping_plane_normal == Vec3d::Zero()) - reset_clipping_plane_normal(); - - const Vec3d& direction_to_camera = m_clipping_plane_normal; - - // First cache instance transformation to be used later. + // Get transformation of the instance const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - Transform3f instance_matrix = vol->get_instance_transformation().get_matrix().cast<float>(); - Transform3f instance_matrix_no_translation_no_scaling = vol->get_instance_transformation().get_matrix(true,false,true).cast<float>(); - Vec3f scaling = vol->get_instance_scaling_factor().cast<float>(); - Vec3d instance_offset = vol->get_instance_offset(); + Geometry::Transformation trafo = vol->get_instance_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift)); - // Calculate distance from mesh origin to the clipping plane (in mesh coordinates). - Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera.cast<float>(); - Vec3f up = Vec3f(up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2)); - float height_mesh = (m_active_instance_bb_radius - m_clipping_plane_distance * 2*m_active_instance_bb_radius) * (up_noscale.norm()/up.norm()); + // Get transformation of supports + Geometry::Transformation supports_trafo; + supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), vol->get_sla_shift_z())); + supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2))); + // I don't know why, but following seems to be correct. + supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2), + 1, + 1.)); - // Get transformation of the supports and calculate how far from its origin the clipping plane is. - Transform3d supports_trafo = Transform3d::Identity(); - supports_trafo = supports_trafo.rotate(Eigen::AngleAxisd(vol->get_instance_rotation()(2), Vec3d::UnitZ())); - Vec3f up_supports = (supports_trafo.inverse() * direction_to_camera).cast<float>(); - supports_trafo = supports_trafo.pretranslate(Vec3d(instance_offset(0), instance_offset(1), vol->get_sla_shift_z())); - // Instance and supports origin do not coincide, so the following is quite messy: - float height_supports = height_mesh * (up.norm() / up_supports.norm()) + instance_offset(2) * (direction_to_camera(2) / direction_to_camera.norm()); + // Now initialize the TMS for the object, perform the cut and save the result. + if (! m_object_clipper) { + m_object_clipper.reset(new MeshClipper); + m_object_clipper->set_mesh(*m_mesh); + } + m_object_clipper->set_plane(*m_clipping_plane); + m_object_clipper->set_transformation(trafo); - // In case either of these was recently changed, the cached triangulated ExPolygons are invalid now. - // We are gonna recalculate them both for the object and for the support structures. - if (m_clipping_plane_distance != m_old_clipping_plane_distance - || m_old_clipping_plane_normal != direction_to_camera) { - m_old_clipping_plane_normal = direction_to_camera; - m_old_clipping_plane_distance = m_clipping_plane_distance; - - // Now initialize the TMS for the object, perform the cut and save the result. - if (! m_tms) { - m_tms.reset(new TriangleMeshSlicer); - m_tms->init(m_mesh, [](){}); + // Next, ask the backend if supports are already calculated. If so, we are gonna cut them too. + // First we need a pointer to the respective SLAPrintObject. The index into objects vector is + // cached so we don't have todo it on each render. We only search for the po if needed: + if (m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_print_objects_count) { + m_print_objects_count = m_parent.sla_print()->objects().size(); + m_print_object_idx = -1; + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + ++m_print_object_idx; + if (po->model_object()->id() == m_model_object->id()) + break; } - std::vector<ExPolygons> list_of_expolys; - m_tms->set_up_direction(up); - m_tms->slice(std::vector<float>{height_mesh}, 0.f, &list_of_expolys, [](){}); - m_triangles = triangulate_expolygons_2f(list_of_expolys[0]); + } + if (m_print_object_idx >= 0) { + const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_print_object_idx]; + if (print_object->is_step_done(slaposSupportTree)) { + // If the supports are already calculated, save the timestamp of the respective step + // so we can later tell they were recalculated. + size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp; - - // Next, ask the backend if supports are already calculated. If so, we are gonna cut them too. - // First we need a pointer to the respective SLAPrintObject. The index into objects vector is - // cached so we don't have todo it on each render. We only search for the po if needed: - if (m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_print_objects_count) { - m_print_objects_count = m_parent.sla_print()->objects().size(); - m_print_object_idx = -1; - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - ++m_print_object_idx; - if (po->model_object()->id() == m_model_object->id()) - break; - } - } - if (m_print_object_idx >= 0) { - const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_print_object_idx]; - - if (print_object->is_step_done(slaposSupportTree)) { - // If the supports are already calculated, save the timestamp of the respective step - // so we can later tell they were recalculated. - size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp; - - if (!m_supports_tms || (int)timestamp != m_old_timestamp) { - // The timestamp has changed - stash the mesh and initialize the TMS. - m_supports_mesh = &print_object->support_mesh(); - m_supports_tms.reset(new TriangleMeshSlicer); - // The mesh should already have the shared vertices calculated. - m_supports_tms->init(m_supports_mesh, [](){}); - m_old_timestamp = timestamp; - } - - // The TMS is initialized - let's do the cutting: - list_of_expolys.clear(); - m_supports_tms->set_up_direction(up_supports); - m_supports_tms->slice(std::vector<float>{height_supports}, 0.f, &list_of_expolys, [](){}); - m_supports_triangles = triangulate_expolygons_2f(list_of_expolys[0]); - } - else { - // The supports are not valid. We better dump the cached data. - m_supports_tms.reset(); - m_supports_triangles.clear(); + if (! m_supports_clipper || (int)timestamp != m_old_timestamp) { + // The timestamp has changed. + m_supports_clipper.reset(new MeshClipper); + // The mesh should already have the shared vertices calculated. + m_supports_clipper->set_mesh(print_object->support_mesh()); + m_old_timestamp = timestamp; } + m_supports_clipper->set_plane(*m_clipping_plane); + m_supports_clipper->set_transformation(supports_trafo); } + else + // The supports are not valid. We better dump the cached data. + m_supports_clipper.reset(); } // At this point we have the triangulated cuts for both the object and supports - let's render. - if (! m_triangles.empty()) { + if (! m_object_clipper->get_triangles().empty()) { ::glPushMatrix(); - ::glTranslated(0.0, 0.0, m_z_shift); - ::glMultMatrixf(instance_matrix.data()); - Eigen::Quaternionf q; - q.setFromTwoVectors(Vec3f::UnitZ(), up); - Eigen::AngleAxisf aa(q); - ::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)); - ::glTranslatef(0.f, 0.f, 0.01f); // to make sure the cut does not intersect the structure itself ::glColor3f(1.0f, 0.37f, 0.0f); ::glBegin(GL_TRIANGLES); - for (const Vec2f& point : m_triangles) - ::glVertex3f(point(0), point(1), height_mesh); - + for (const Vec3f& point : m_object_clipper->get_triangles()) + ::glVertex3f(point(0), point(1), point(2)); ::glEnd(); ::glPopMatrix(); } - if (! m_supports_triangles.empty() && !m_editing_mode) { + if (m_supports_clipper && ! m_supports_clipper->get_triangles().empty() && !m_editing_mode) { // The supports are hidden in the editing mode, so it makes no sense to render the cuts. - ::glPushMatrix(); - ::glMultMatrixd(supports_trafo.data()); - Eigen::Quaternionf q; - q.setFromTwoVectors(Vec3f::UnitZ(), up_supports); - Eigen::AngleAxisf aa(q); - ::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)); - ::glTranslatef(0.f, 0.f, 0.01f); + ::glPushMatrix(); ::glColor3f(1.0f, 0.f, 0.37f); ::glBegin(GL_TRIANGLES); - for (const Vec2f& point : m_supports_triangles) - ::glVertex3f(point(0), point(1), height_supports); - + for (const Vec3f& point : m_supports_clipper->get_triangles()) + ::glVertex3f(point(0), point(1), point(2)); ::glEnd(); ::glPopMatrix(); } @@ -379,15 +336,12 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const { - const Vec3d& direction_to_camera = m_clipping_plane_normal; - if (m_clipping_plane_distance == 0.f) return false; Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point; transformed_point(2) += m_z_shift; - return direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)) + m_active_instance_bb_radius - - m_clipping_plane_distance * 2*m_active_instance_bb_radius < direction_to_camera.dot(transformed_point); + return m_clipping_plane->distance(transformed_point) < 0.; } @@ -693,18 +647,18 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (action == SLAGizmoEventType::MouseWheelUp && control_down) { m_clipping_plane_distance = std::min(1.f, m_clipping_plane_distance + 0.01f); - m_parent.set_as_dirty(); + update_clipping_plane(true); return true; } if (action == SLAGizmoEventType::MouseWheelDown && control_down) { m_clipping_plane_distance = std::max(0.f, m_clipping_plane_distance - 0.01f); - m_parent.set_as_dirty(); + update_clipping_plane(true); return true; } if (action == SLAGizmoEventType::ResetClippingPlane) { - reset_clipping_plane_normal(); + update_clipping_plane(); return true; } @@ -796,18 +750,10 @@ void GLGizmoSlaSupports::update_cache_entry_normal(size_t i) const ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const { - if (!m_model_object || m_state == Off) + if (!m_model_object || m_state == Off || m_clipping_plane_distance == 0.f) return ClippingPlane::ClipsNothing(); - - //Eigen::Matrix<GLdouble, 4, 4, Eigen::DontAlign> modelview_matrix; - //::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); - // we'll recover current look direction from the modelview matrix (in world coords): - //Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]); - - const Vec3d& direction_to_camera = m_clipping_plane_normal; - float dist = direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)); - - return ClippingPlane(-direction_to_camera.normalized(),(dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius)); + else + return ClippingPlane(-m_clipping_plane->get_normal(), m_clipping_plane->get_data()[3]); } @@ -1019,14 +965,15 @@ RENDER_AGAIN: else { if (m_imgui->button(m_desc.at("reset_direction"))) { wxGetApp().CallAfter([this](){ - reset_clipping_plane_normal(); + update_clipping_plane(); }); } } ImGui::SameLine(clipping_slider_left); ImGui::PushItemWidth(window_width - clipping_slider_left); - ImGui::SliderFloat(" ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f"); + if (ImGui::SliderFloat(" ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f")) + update_clipping_plane(true); if (m_imgui->button("?")) { @@ -1156,8 +1103,8 @@ void GLGizmoSlaSupports::on_set_state() // Release triangle mesh slicer and the AABB spatial search structure. m_AABB.deinit(); m_its = nullptr; - m_tms.reset(); - m_supports_tms.reset(); + m_object_clipper.reset(); + m_supports_clipper.reset(); } } m_old_state = m_state; @@ -1198,7 +1145,7 @@ void GLGizmoSlaSupports::on_stop_dragging() void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) { ar(m_clipping_plane_distance, - m_clipping_plane_normal, + *m_clipping_plane, m_model_object_id, m_new_point_head_diameter, m_normal_cache, @@ -1212,7 +1159,7 @@ void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const { ar(m_clipping_plane_distance, - m_clipping_plane_normal, + *m_clipping_plane, m_model_object_id, m_new_point_head_diameter, m_normal_cache, @@ -1401,17 +1348,19 @@ bool GLGizmoSlaSupports::unsaved_changes() const } -void GLGizmoSlaSupports::reset_clipping_plane_normal() const +void GLGizmoSlaSupports::update_clipping_plane(bool keep_normal) const { - Eigen::Matrix<double, 4, 4, Eigen::DontAlign> modelview_matrix; - ::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data()); - m_clipping_plane_normal = Vec3d(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]); + Vec3d normal = (keep_normal && m_clipping_plane->get_normal() != Vec3d::Zero() ? + m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward()); + + const Vec3d& center = m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift); + float dist = normal.dot(center); + *m_clipping_plane = ClippingPlane(normal, (dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius)); m_parent.set_as_dirty(); } - SlaGizmoHelpDialog::SlaGizmoHelpDialog() -: wxDialog(NULL, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) +: wxDialog(nullptr, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); const wxString ctrl = GUI::shortkey_ctrl_prefix(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index d2451f64e..7bef33e1b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -2,7 +2,6 @@ #define slic3r_GLGizmoSlaSupports_hpp_ #include "GLGizmoBase.hpp" -#include "GLGizmos.hpp" #include "slic3r/GUI/GLSelectionRectangle.hpp" // There is an L function in igl that would be overridden by our localization macro - let's undefine it... @@ -19,9 +18,9 @@ namespace Slic3r { namespace GUI { - class ClippingPlane; - +class MeshClipper; +enum class SLAGizmoEventType : unsigned char; class GLGizmoSlaSupports : public GLGizmoBase { @@ -114,9 +113,7 @@ private: std::vector<sla::SupportPoint> m_normal_cache; // to restore after discarding changes or undo/redo float m_clipping_plane_distance = 0.f; - mutable float m_old_clipping_plane_distance = 0.f; - mutable Vec3d m_old_clipping_plane_normal; - mutable Vec3d m_clipping_plane_normal = Vec3d::Zero(); + std::unique_ptr<ClippingPlane> m_clipping_plane; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. @@ -128,8 +125,8 @@ private: bool m_selection_empty = true; EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) - mutable std::unique_ptr<TriangleMeshSlicer> m_tms; - mutable std::unique_ptr<TriangleMeshSlicer> m_supports_tms; + mutable std::unique_ptr<MeshClipper> m_object_clipper; + mutable std::unique_ptr<MeshClipper> m_supports_clipper; std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const; bool is_point_clipped(const Vec3d& point) const; @@ -151,6 +148,7 @@ private: void switch_to_editing_mode(); void disable_editing_mode(); void reset_clipping_plane_normal() const; + void update_clipping_plane(bool keep_normal = false) const; protected: void on_set_state() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmos.hpp b/src/slic3r/GUI/Gizmos/GLGizmos.hpp index 2e98899be..272fa098a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmos.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmos.hpp @@ -2,7 +2,10 @@ #define slic3r_GLGizmos_hpp_ // this describes events being passed from GLCanvas3D to SlaSupport gizmo -enum class SLAGizmoEventType { +namespace Slic3r { +namespace GUI { + +enum class SLAGizmoEventType : unsigned char { LeftDown = 1, LeftUp, RightDown, @@ -20,6 +23,9 @@ enum class SLAGizmoEventType { ResetClippingPlane }; +} // namespace GUI +} // namespace Slic3r + #include "slic3r/GUI/Gizmos/GLGizmoMove.hpp" #include "slic3r/GUI/Gizmos/GLGizmoScale.hpp" #include "slic3r/GUI/Gizmos/GLGizmoRotate.hpp" diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp new file mode 100644 index 000000000..9542f0b1f --- /dev/null +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -0,0 +1,95 @@ +#include "MeshUtils.hpp" + +#include "libslic3r/Tesselate.hpp" +#include "libslic3r/TriangleMesh.hpp" + +namespace Slic3r { +namespace GUI { + +void MeshClipper::set_plane(const ClippingPlane& plane) +{ + if (m_plane != plane) { + m_plane = plane; + m_triangles_valid = false; + } +} + + + +void MeshClipper::set_mesh(const TriangleMesh& mesh) +{ + if (m_mesh != &mesh) { + m_mesh = &mesh; + m_triangles_valid = false; + m_triangles2d.resize(0); + m_triangles3d.resize(0); + m_tms.reset(nullptr); + } +} + + + +void MeshClipper::set_transformation(const Geometry::Transformation& trafo) +{ + if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) { + m_trafo = trafo; + m_triangles_valid = false; + m_triangles2d.resize(0); + m_triangles3d.resize(0); + } +} + + + +const std::vector<Vec3f>& MeshClipper::get_triangles() +{ + if (! m_triangles_valid) + recalculate_triangles(); + + return m_triangles3d; +} + + + +void MeshClipper::recalculate_triangles() +{ + if (! m_tms) { + m_tms.reset(new TriangleMeshSlicer); + m_tms->init(m_mesh, [](){}); + } + + const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>(); + const Vec3f& scaling = m_trafo.get_scaling_factor().cast<float>(); + // Calculate clipping plane normal in mesh coordinates. + Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>(); + Vec3f up (up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2)); + // Calculate distance from mesh origin to the clipping plane (in mesh coordinates). + float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm()); + + // Now do the cutting + std::vector<ExPolygons> list_of_expolys; + m_tms->set_up_direction(up); + m_tms->slice(std::vector<float>{height_mesh}, 0.f, &list_of_expolys, [](){}); + m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); + + // Rotate the cut into world coords: + Eigen::Quaternionf q; + q.setFromTwoVectors(Vec3f::UnitZ(), up); + Transform3f tr = Transform3f::Identity(); + tr.rotate(q); + tr = m_trafo.get_matrix().cast<float>() * tr; + + m_triangles3d.clear(); + m_triangles3d.reserve(m_triangles2d.size()); + for (const Vec2f& pt : m_triangles2d) { + m_triangles3d.push_back(Vec3f(pt(0), pt(1), height_mesh+0.001f)); + m_triangles3d.back() = tr * m_triangles3d.back(); + } + + m_triangles_valid = true; +} + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp new file mode 100644 index 000000000..f97003a91 --- /dev/null +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -0,0 +1,94 @@ +#ifndef slic3r_MeshUtils_hpp_ +#define slic3r_MeshUtils_hpp_ + +#include "libslic3r/Point.hpp" +#include "libslic3r/Geometry.hpp" + + +#include <cfloat> + +namespace Slic3r { + +class TriangleMesh; +class TriangleMeshSlicer; + +namespace GUI { + + + +class ClippingPlane +{ + double m_data[4]; + +public: + ClippingPlane() + { + m_data[0] = 0.0; + m_data[1] = 0.0; + m_data[2] = 1.0; + m_data[3] = 0.0; + } + + ClippingPlane(const Vec3d& direction, double offset) + { + Vec3d norm_dir = direction.normalized(); + m_data[0] = norm_dir(0); + m_data[1] = norm_dir(1); + m_data[2] = norm_dir(2); + m_data[3] = offset; + } + + bool operator==(const ClippingPlane& cp) const { + return m_data[0]==cp.m_data[0] && m_data[1]==cp.m_data[1] && m_data[2]==cp.m_data[2] && m_data[3]==cp.m_data[3]; + } + bool operator!=(const ClippingPlane& cp) const { return ! (*this==cp); } + + double distance(const Vec3d& pt) const { + assert(is_approx(get_normal().norm(), 1.)); + return (-get_normal().dot(pt) + m_data[3]); + } + + void set_normal(const Vec3d& normal) { for (size_t i=0; i<3; ++i) m_data[i] = normal(i); } + void set_offset(double offset) { m_data[3] = offset; } + Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); } + bool is_active() const { return m_data[3] != DBL_MAX; } + static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); } + const double* get_data() const { return m_data; } + + // Serialization through cereal library + template <class Archive> + void serialize( Archive & ar ) + { + ar( m_data[0], m_data[1], m_data[2], m_data[3] ); + } +}; + + + +class MeshClipper { +public: + void set_plane(const ClippingPlane& plane); + void set_mesh(const TriangleMesh& mesh); + void set_transformation(const Geometry::Transformation& trafo); + + const std::vector<Vec3f>& get_triangles(); + +private: + void recalculate_triangles(); + + Geometry::Transformation m_trafo; + const TriangleMesh* m_mesh = nullptr; + ClippingPlane m_plane; + std::vector<Vec2f> m_triangles2d; + std::vector<Vec3f> m_triangles3d; + bool m_triangles_valid = false; + std::unique_ptr<TriangleMeshSlicer> m_tms; +}; + + + +} // namespace GUI +} // namespace Slic3r + + +#endif // slic3r_MeshUtils_hpp_