From 1d41ffdd7d0e5412af29ce520763613d81209a61 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 27 Jun 2022 14:53:33 +0200 Subject: [PATCH 1/2] When placing seam, never produce segments shorter than 1.5um. --- src/libslic3r/ExtrusionEntity.cpp | 110 ++++++++++++++++------------- src/libslic3r/ExtrusionEntity.hpp | 11 ++- src/libslic3r/GCode.cpp | 4 +- src/libslic3r/GCode/SeamPlacer.cpp | 8 ++- src/libslic3r/MultiPoint.cpp | 18 +++++ src/libslic3r/MultiPoint.hpp | 5 ++ src/libslic3r/Polyline.cpp | 23 ++++++ src/libslic3r/Polyline.hpp | 3 + 8 files changed, 126 insertions(+), 56 deletions(-) diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 7b2506a22..b0cef7602 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -153,11 +153,10 @@ double ExtrusionLoop::length() const return len; } -bool ExtrusionLoop::split_at_vertex(const Point &point) +bool ExtrusionLoop::split_at_vertex(const Point &point, const double scaled_epsilon) { - for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) { - int idx = path->polyline.find_point(point); - if (idx != -1) { + for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) + if (int idx = path->polyline.find_point(point, scaled_epsilon); idx != -1) { if (this->paths.size() == 1) { // just change the order of points path->polyline.points.insert(path->polyline.points.end(), path->polyline.points.begin() + 1, path->polyline.points.begin() + idx + 1); @@ -169,70 +168,85 @@ bool ExtrusionLoop::split_at_vertex(const Point &point) { ExtrusionPath p = *path; p.polyline.points.erase(p.polyline.points.begin(), p.polyline.points.begin() + idx); - if (p.polyline.is_valid()) new_paths.push_back(p); + if (p.polyline.is_valid()) + new_paths.emplace_back(std::move(p)); } // then we add all paths until the end of current path list - new_paths.insert(new_paths.end(), path+1, this->paths.end()); // not including this path - + std::move(path + 1, this->paths.end(), std::back_inserter(new_paths)); // not including this path + // then we add all paths since the beginning of current list up to the previous one - new_paths.insert(new_paths.end(), this->paths.begin(), path); // not including this path + std::move(this->paths.begin(), path, std::back_inserter(new_paths)); // not including this path // finally we add the first half of current path { - ExtrusionPath p = *path; + ExtrusionPath &p = *path; p.polyline.points.erase(p.polyline.points.begin() + idx + 1, p.polyline.points.end()); - if (p.polyline.is_valid()) new_paths.push_back(p); + if (p.polyline.is_valid()) + new_paths.emplace_back(std::move(p)); } // we can now override the old path list with the new one and stop looping - std::swap(this->paths, new_paths); + this->paths = std::move(new_paths); } return true; } - } + // The point was not found. return false; } -std::pair ExtrusionLoop::get_closest_path_and_point(const Point& point, bool prefer_non_overhang) const +ExtrusionLoop::ClosestPathPoint ExtrusionLoop::get_closest_path_and_point(const Point &point, bool prefer_non_overhang) const { // Find the closest path and closest point belonging to that path. Avoid overhangs, if asked for. - size_t path_idx = 0; - Point p; - { - double min = std::numeric_limits::max(); - Point p_non_overhang; - size_t path_idx_non_overhang = 0; - double min_non_overhang = std::numeric_limits::max(); - for (const ExtrusionPath& path : this->paths) { - Point p_tmp = point.projection_onto(path.polyline); - double dist = (p_tmp - point).cast().norm(); - if (dist < min) { - p = p_tmp; - min = dist; - path_idx = &path - &this->paths.front(); - } - 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.front(); - } + ClosestPathPoint out { 0, 0 }; + double min2 = std::numeric_limits::max(); + ClosestPathPoint best_non_overhang { 0, 0 }; + double min2_non_overhang = std::numeric_limits::max(); + for (const ExtrusionPath &path : this->paths) { + std::pair foot_pt_ = foot_pt(path.polyline.points, point); + double d2 = (foot_pt_.second - point).cast().squaredNorm(); + if (d2 < min2) { + out.foot_pt = foot_pt_.second; + out.path_idx = &path - &this->paths.front(); + out.segment_idx = foot_pt_.first; + min2 = d2; } - if (prefer_non_overhang && min_non_overhang != std::numeric_limits::max()) { - // Only apply the non-overhang point if there is one. - path_idx = path_idx_non_overhang; - p = p_non_overhang; + if (prefer_non_overhang && !is_bridge(path.role()) && d2 < min2_non_overhang) { + best_non_overhang.foot_pt = foot_pt_.second; + best_non_overhang.path_idx = &path - &this->paths.front(); + best_non_overhang.segment_idx = foot_pt_.first; + min2_non_overhang = d2; } } - return std::make_pair(path_idx, p); + if (prefer_non_overhang && min2_non_overhang != std::numeric_limits::max()) + // Only apply the non-overhang point if there is one. + out = best_non_overhang; + return out; } // Splitting an extrusion loop, possibly made of multiple segments, some of the segments may be bridging. -void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang) +void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon) { if (this->paths.empty()) return; - auto [path_idx, p] = get_closest_path_and_point(point, prefer_non_overhang); + auto [path_idx, segment_idx, p] = get_closest_path_and_point(point, prefer_non_overhang); + + // Snap p to start or end of segment_idx if closer than scaled_epsilon. + { + const Point *p1 = this->paths[path_idx].polyline.points.data() + segment_idx; + const Point *p2 = p1; + ++ p2; + double d2_1 = (point - *p1).cast().squaredNorm(); + double d2_2 = (point - *p2).cast().squaredNorm(); + const double thr2 = scaled_epsilon * scaled_epsilon; + if (d2_1 < d2_2) { + if (d2_1 < thr2) + p = *p1; + } else { + if (d2_2 < thr2) + p = *p2; + } + } // now split path_idx in two parts const ExtrusionPath &path = this->paths[path_idx]; @@ -241,14 +255,12 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang) path.polyline.split_at(p, &p1.polyline, &p2.polyline); if (this->paths.size() == 1) { - if (! p1.polyline.is_valid()) - std::swap(this->paths.front().polyline.points, p2.polyline.points); - else if (! p2.polyline.is_valid()) - std::swap(this->paths.front().polyline.points, p1.polyline.points); - else { - p2.polyline.points.insert(p2.polyline.points.end(), p1.polyline.points.begin() + 1, p1.polyline.points.end()); - std::swap(this->paths.front().polyline.points, p2.polyline.points); - } + if (p2.polyline.is_valid()) { + if (p1.polyline.is_valid()) + p2.polyline.points.insert(p2.polyline.points.end(), p1.polyline.points.begin() + 1, p1.polyline.points.end()); + this->paths.front().polyline.points = std::move(p2.polyline.points); + } else + this->paths.front().polyline.points = std::move(p1.polyline.points); } else { // install the two paths this->paths.erase(this->paths.begin() + path_idx); @@ -257,7 +269,7 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang) } // split at the new vertex - this->split_at_vertex(p); + this->split_at_vertex(p, 0.); } void ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index a82056b56..682ddad9f 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -256,9 +256,14 @@ public: const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); } Polygon polygon() const; double length() const override; - bool split_at_vertex(const Point &point); - void split_at(const Point &point, bool prefer_non_overhang); - std::pair get_closest_path_and_point(const Point& point, bool prefer_non_overhang) const; + bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled(0.001)); + void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled(0.001)); + struct ClosestPathPoint { + size_t path_idx; + size_t segment_idx; + Point foot_pt; + }; + ClosestPathPoint get_closest_path_and_point(const Point& point, bool prefer_non_overhang) const; void clip_end(double distance, ExtrusionPaths* paths) const; // Test, whether the point is extruded by a bridging flow. // This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead. diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index a6506db60..f2c435c4f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2638,7 +2638,9 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou assert(m_layer != nullptr); m_seam_placer.place_seam(m_layer, loop, m_config.external_perimeters_first, this->last_pos()); } else - loop.split_at(last_pos, false); + // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns, + // thus empty path segments will not be produced by G-code export. + loop.split_at(last_pos, false, scaled(0.0015)); // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 53edc0400..4185ae41b 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1573,16 +1573,18 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern + (perimeter_point.position - layer_perimeters.points[index_of_next].position).head<2>().normalized()) * 0.5; - auto [_, projected_point] = loop.get_closest_path_and_point(seam_point, true); + ExtrusionLoop::ClosestPathPoint projected_point = loop.get_closest_path_and_point(seam_point, true); //get closest projected point, determine depth of the seam point. - float depth = (float) unscale(Point(seam_point - projected_point)).norm(); + float depth = (float) unscale(Point(seam_point - projected_point.foot_pt)).norm(); float angle_factor = cos(-perimeter_point.local_ccw_angle / 2.0f); // There are some nice geometric identities in determination of the correct depth of new seam point. //overshoot the target depth, in concave angles it will correctly snap to the corner; TODO: find out why such big overshoot is needed. Vec2f final_pos = perimeter_point.position.head<2>() + (1.4142 * depth / angle_factor) * dir_to_middle; seam_point = Point::new_scale(final_pos.x(), final_pos.y()); } - if (!loop.split_at_vertex(seam_point)) { + // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns, + // thus empty path segments will not be produced by G-code export. + if (!loop.split_at_vertex(seam_point, scaled(0.0015))) { // The point is not in the original loop. // Insert it. loop.split_at(seam_point, true); diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index aa8295098..5ed9eb23c 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -63,6 +63,24 @@ int MultiPoint::find_point(const Point &point) const return -1; // not found } +int MultiPoint::find_point(const Point &point, double scaled_epsilon) const +{ + if (scaled_epsilon == 0) + return this->find_point(point); + + auto dist2_min = std::numeric_limits::max(); + auto eps2 = scaled_epsilon * scaled_epsilon; + int idx_min = -1; + for (const Point &pt : this->points) { + double d2 = (pt - point).cast().squaredNorm(); + if (d2 < dist2_min) { + idx_min = int(&pt - &this->points.front()); + dist2_min = d2; + } + } + return dist2_min < eps2 ? idx_min : -1; +} + bool MultiPoint::has_boundary_point(const Point &point) const { double dist = (point.projection_onto(*this) - point).cast().norm(); diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 935348279..fca3b1168 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -43,7 +43,12 @@ public: double length() const; bool is_valid() const { return this->points.size() >= 2; } + // Return index of a polygon point exactly equal to point. + // Return -1 if no such point exists. int find_point(const Point &point) const; + // Return index of the closest point to point closer than scaled_epsilon. + // Return -1 if no such point exists. + int find_point(const Point &point, const double scaled_epsilon) const; bool has_boundary_point(const Point &point) const; int closest_point_index(const Point &point) const { int idx = -1; diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index aaaa647c0..debdc0a49 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -225,6 +225,29 @@ bool remove_degenerate(Polylines &polylines) return modified; } +std::pair foot_pt(const Points &polyline, const Point &pt) +{ + if (polyline.size() < 2) + return std::make_pair(-1, Point(0, 0)); + + auto d2_min = std::numeric_limits::max(); + Point foot_pt_min; + Point prev = polyline.front(); + auto it = polyline.begin(); + auto it_proj = polyline.begin(); + for (++ it; it != polyline.end(); ++ it) { + Point foot_pt = pt.projection_onto(Line(prev, *it)); + double d2 = (foot_pt - pt).cast().squaredNorm(); + if (d2 < d2_min) { + d2_min = d2; + foot_pt_min = foot_pt; + it_proj = it; + } + prev = *it; + } + return std::make_pair(int(it_proj - polyline.begin()) - 1, foot_pt_min); +} + ThickLines ThickPolyline::thicklines() const { ThickLines lines; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 3277ac7d6..daaf01d48 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -162,6 +162,9 @@ const Point& leftmost_point(const Polylines &polylines); bool remove_degenerate(Polylines &polylines); +// Returns index of a segment of a polyline and foot point of pt on polyline. +std::pair foot_pt(const Points &polyline, const Point &pt); + class ThickPolyline : public Polyline { public: ThickPolyline() : endpoints(std::make_pair(false, false)) {} From 88f4641ffff55e5b2845fd29e33320ce967a3817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 28 Jun 2022 09:25:53 +0200 Subject: [PATCH 2/2] Follow-up to d547279aea40460e7ccfe7c5a4b77a6ef433e9bf: Some of the unit tests use unscaled coordinates, because of that, we will use for them ExtrusionLoop::split_at with zero epsilon. --- xs/xsp/ExtrusionLoop.xsp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/xsp/ExtrusionLoop.xsp b/xs/xsp/ExtrusionLoop.xsp index 6784fcbbe..ded17cb19 100644 --- a/xs/xsp/ExtrusionLoop.xsp +++ b/xs/xsp/ExtrusionLoop.xsp @@ -21,8 +21,8 @@ double length(); bool split_at_vertex(Point* point) %code{% RETVAL = THIS->split_at_vertex(*point); %}; - void split_at(Point* point, int prefer_non_overhang = 0) - %code{% THIS->split_at(*point, prefer_non_overhang != 0); %}; + void split_at(Point* point, int prefer_non_overhang = 0, double scaled_epsilon = 0.) + %code{% THIS->split_at(*point, prefer_non_overhang != 0, scaled_epsilon); %}; ExtrusionPaths clip_end(double distance) %code{% THIS->clip_end(distance, &RETVAL); %}; bool has_overhang_point(Point* point)