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/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index c22478d84..a5137b3e4 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" @@ -1729,53 +1731,115 @@ void Print::_make_brim() ClipperLib::jtRound, float(scale_(0.1))); - const Polygon& skirt_inner = skirt_inners.front(); - const Polygon& skirt_outer = skirt_outers.front(); + // 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); + } - for (size_t i=0; i<loops.size(); ++i) { - Polylines lines; - lines.push_back(Polyline()); - bool del = false; - const Polygon& poly = loops[i]; - // Check all points of the polygon, consider the first to also be at the end so the loop is closed - for (int pt_idx=0; pt_idx <= (int)poly.points.size(); ++pt_idx) { - const Point* pt = (pt_idx == (int)poly.points.size() ? &poly.points[0] : &poly.points[pt_idx]); - const Point* last_pt = (pt_idx == 0 ? nullptr : &poly.points[pt_idx-1]); - bool valid_point = skirt_inner.contains(*pt) || ! skirt_outer.contains(*pt); - Points intersections(2); // inner and outer intersection + // 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); + } - if (pt_idx > 0) { - Line line(*last_pt, *pt); - bool inner = skirt_inner.first_intersection(line, &intersections[0]); - bool outer = skirt_outer.first_intersection(line, &intersections[1]); - del = del || inner || outer; - if (inner != outer) {// there is exactly one intersection - lines.back().append(inner ? intersections[0] : intersections[1]); - } - if (inner && outer) { - int nearest_idx = pt->nearest_point_index(intersections); - lines.back().append(intersections[! nearest_idx]); - lines.push_back(Polyline()); - lines.back().append(intersections[nearest_idx]); - } - } - if (valid_point) - lines.back().append(*pt); - else { - del = true; - lines.push_back(Polyline()); - } - } - // If we found a single intersection, we should erase the respective ExtrusionLoop - // and append the polylines that we created. - if (del) { - loops.erase(loops.begin() + (i--)); - extrusion_entities_append_paths(m_brim.entities, std::move(lines), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())); - } - } + // 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())); } - - 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"