From 7a4ba7d131328b43f1db6e55aff317ea0837b510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 21 Sep 2020 02:05:52 +0200 Subject: [PATCH 01/35] A simple algorithm to follow the boundary of polygons --- src/libslic3r/GCode.cpp | 87 +++++++++++++++++++++++++++++++++++++++++ src/libslic3r/GCode.hpp | 28 +++++++++++-- 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 61ef9a1bd..10514ef39 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -176,6 +176,93 @@ namespace Slic3r { return islands; } + Matrix2d rotation_by_direction(const Point &direction) + { + Matrix2d rotation; + rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); + rotation(1, 0) = -rotation(0, 1); + rotation(1, 1) = rotation(0, 0); + + return rotation; + } + + Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) + { + const Point &start = gcodegen.last_pos(); + const Point &end = point; + const Point direction = end - start; + Matrix2d transform_to_x_axis = rotation_by_direction(direction); + Matrix2d transform_from_x_axis = transform_to_x_axis.transpose(); + + const Line travel_line((transform_to_x_axis * start.cast()).cast(), + (transform_to_x_axis * end.cast()).cast()); + + Polygons borders; + borders.reserve(gcodegen.layer()->lslices.size()); + + for (const ExPolygon &ex_polygon : gcodegen.layer()->lslices) { + borders.emplace_back(ex_polygon.contour); + + for (const Polygon &hole : ex_polygon.holes) borders.emplace_back(hole); + } + + std::vector intersections; + for (size_t border_idx = 0; border_idx < borders.size(); ++border_idx) { + const Polygon &border = borders[border_idx]; + Lines border_lines = border.lines(); + + for (size_t line_idx = 0; line_idx < border_lines.size(); ++line_idx) { + const Line &border_line = border_lines[line_idx]; + Line border_line_transformed((transform_to_x_axis * border_line.a.cast()).cast(), + (transform_to_x_axis * border_line.b.cast()).cast()); + + Point intersection_transformed; + + if (travel_line.intersection(border_line_transformed, &intersection_transformed)) + intersections.emplace_back(border_idx, line_idx, intersection_transformed); + } + } + + // Sort intersections from the nearest to the farthest + std::sort(intersections.begin(), intersections.end()); + + // Polyline result(start, end); + Polyline result; + result.append(start); + + for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { + const Intersection &intersection_first = *it_first; + + for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { + const Intersection &intersection_second = *it_second; + + if (intersection_first.border_idx == intersection_second.border_idx) { + Lines border_lines = borders[intersection_first.border_idx].lines(); + + // Append the nearest intersection into the path + result.append((transform_from_x_axis * intersection_first.point.cast()).cast()); + + // Append the path around the border into the path + for (size_t line_idx = intersection_first.line_idx; line_idx != intersection_second.line_idx;) { + result.append(border_lines[line_idx].b); + + if (++line_idx == border_lines.size()) line_idx = 0; + } + + // Append the farthest intersection into the path + result.append((transform_from_x_axis * intersection_second.point.cast()).cast()); + + // Skip intersections in between + it_first = it_second; + break; + } + } + } + + result.append(end); + + return result; + } std::string OozePrevention::pre_toolchange(GCode& gcodegen) { diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index aeac4fcf8..ad9a2e2fb 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -46,13 +46,13 @@ public: bool disable_once; AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {} - ~AvoidCrossingPerimeters() {} + virtual ~AvoidCrossingPerimeters() = default; void reset() { m_external_mp.reset(); m_layer_mp.reset(); } void init_external_mp(const Print &print); void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } - Polyline travel_to(const GCode &gcodegen, const Point &point); + virtual Polyline travel_to(const GCode &gcodegen, const Point &point); private: // For initializing the regions to avoid. @@ -62,6 +62,28 @@ private: std::unique_ptr m_layer_mp; }; +class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters +{ +protected: + struct Intersection + { + size_t border_idx; + size_t line_idx; + Point point; + + Intersection(size_t border_idx, size_t line_idx, Point point) + : border_idx(border_idx), line_idx(line_idx), point(point){}; + + inline bool operator<(const Intersection &other) const { return this->point.x() < other.point.x(); } + }; + +public: + AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} + + virtual ~AvoidCrossingPerimeters2() = default; + + virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override; +}; class OozePrevention { public: @@ -326,7 +348,7 @@ private: std::set m_placeholder_parser_failed_templates; OozePrevention m_ooze_prevention; Wipe m_wipe; - AvoidCrossingPerimeters m_avoid_crossing_perimeters; + AvoidCrossingPerimeters2 m_avoid_crossing_perimeters; bool m_enable_loop_clipping; // If enabled, the G-code generator will put following comments at the ends // of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END From 6573bc15a5e274eacf9c8ee76b3037ec66f923ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 21 Sep 2020 14:49:22 +0200 Subject: [PATCH 02/35] Selection of shortest path around polygon boundary --- src/libslic3r/GCode.cpp | 60 ++++++++++++++++++++++++++++++++++------- src/libslic3r/GCode.hpp | 9 +++++++ 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 10514ef39..7874cda4f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -176,7 +176,7 @@ namespace Slic3r { return islands; } - Matrix2d rotation_by_direction(const Point &direction) + static Matrix2d rotation_by_direction(const Point &direction) { Matrix2d rotation; rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); @@ -186,6 +186,33 @@ namespace Slic3r { return rotation; } + AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last) + { + double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); + double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); + + for (int line_idx = int(start_idx) + 1; line_idx != int(end_idx); ++line_idx) { + if (line_idx == int(lines.size())) line_idx = 0; + + total_length_forward += lines[line_idx].length(); + } + + for (int line_idx = int(start_idx) - 1; line_idx != int(end_idx); --line_idx) { + if (line_idx < 0) line_idx = int(lines.size()) - 1; + + total_length_backward += lines[line_idx].length(); + } + + total_length_forward += (lines[end_idx].a - intersection_last).cast().norm(); + total_length_backward += (lines[end_idx].b - intersection_last).cast().norm(); + + return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; + } + Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) { const Point &start = gcodegen.last_pos(); @@ -213,7 +240,7 @@ namespace Slic3r { for (size_t line_idx = 0; line_idx < border_lines.size(); ++line_idx) { const Line &border_line = border_lines[line_idx]; - Line border_line_transformed((transform_to_x_axis * border_line.a.cast()).cast(), + Line border_line_transformed((transform_to_x_axis * border_line.a.cast()).cast(), (transform_to_x_axis * border_line.b.cast()).cast()); Point intersection_transformed; @@ -226,32 +253,45 @@ namespace Slic3r { // Sort intersections from the nearest to the farthest std::sort(intersections.begin(), intersections.end()); - // Polyline result(start, end); + // Polyline result(start, end); Polyline result; result.append(start); for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { const Intersection &intersection_first = *it_first; + Point intersection_first_point((transform_from_x_axis * intersection_first.point.cast()).cast()); for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { const Intersection &intersection_second = *it_second; + Point intersection_second_point( + (transform_from_x_axis * intersection_second.point.cast()).cast()); if (intersection_first.border_idx == intersection_second.border_idx) { Lines border_lines = borders[intersection_first.border_idx].lines(); - // Append the nearest intersection into the path - result.append((transform_from_x_axis * intersection_first.point.cast()).cast()); + result.append(intersection_first_point); + Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, + intersection_second.line_idx, intersection_first_point, + intersection_second_point); // Append the path around the border into the path - for (size_t line_idx = intersection_first.line_idx; line_idx != intersection_second.line_idx;) { - result.append(border_lines[line_idx].b); + if (shortest_direction == Direction::Forward) { + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); ++line_idx) { + if (line_idx == int(border_lines.size())) line_idx = 0; - if (++line_idx == border_lines.size()) line_idx = 0; + result.append(border_lines[line_idx].b); + } + } else { + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + --line_idx) { + if (line_idx < 0) line_idx = int(border_lines.size()) - 1; + + result.append(border_lines[line_idx].a); + } } // Append the farthest intersection into the path - result.append((transform_from_x_axis * intersection_second.point.cast()).cast()); - + result.append(intersection_second_point); // Skip intersections in between it_first = it_second; break; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index ad9a2e2fb..54ffa209f 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -77,6 +77,15 @@ protected: inline bool operator<(const Intersection &other) const { return this->point.x() < other.point.x(); } }; + enum class Direction { Forward, Backward }; + +private: + static Direction get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last); + public: AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} From 074406647aa926236d05acf992b37c26ea7ff787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 21 Sep 2020 18:17:34 +0200 Subject: [PATCH 03/35] Fixed bug when algorithm stuck in a loop --- src/libslic3r/GCode.cpp | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7874cda4f..8a12068d4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -195,17 +195,20 @@ namespace Slic3r { double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); - for (int line_idx = int(start_idx) + 1; line_idx != int(end_idx); ++line_idx) { - if (line_idx == int(lines.size())) line_idx = 0; + auto cyclic_index = [&lines](int index) { + if (index >= int(lines.size())) + index = 0; + else if (index < 0) + index = lines.size() - 1; + return index; + }; + + for (int line_idx = cyclic_index(int(start_idx) + 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx + 1)) total_length_forward += lines[line_idx].length(); - } - - for (int line_idx = int(start_idx) - 1; line_idx != int(end_idx); --line_idx) { - if (line_idx < 0) line_idx = int(lines.size()) - 1; + for (int line_idx = cyclic_index(int(start_idx) - 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx - 1)) total_length_backward += lines[line_idx].length(); - } total_length_forward += (lines[end_idx].a - intersection_last).cast().norm(); total_length_backward += (lines[end_idx].b - intersection_last).cast().norm(); @@ -275,20 +278,14 @@ namespace Slic3r { intersection_second.line_idx, intersection_first_point, intersection_second_point); // Append the path around the border into the path - if (shortest_direction == Direction::Forward) { - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); ++line_idx) { - if (line_idx == int(border_lines.size())) line_idx = 0; - - result.append(border_lines[line_idx].b); - } - } else { + if (shortest_direction == Direction::Forward) for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - --line_idx) { - if (line_idx < 0) line_idx = int(border_lines.size()) - 1; - + line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) + result.append(border_lines[line_idx].b); + else + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) result.append(border_lines[line_idx].a); - } - } // Append the farthest intersection into the path result.append(intersection_second_point); From 46bae74e48cc3473c89b86c63851101ad69e52a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 7 Oct 2020 14:34:59 +0200 Subject: [PATCH 04/35] Finalization of avoid crossing perimeters algorithm EdgeGrid::Grid is used to find the intersection of a path with a polygon. Simplification of find path. Calculation of boundaries that not be crossed. --- src/libslic3r/GCode.cpp | 244 ++++++++++++++++++++++++++++++++++------ src/libslic3r/GCode.hpp | 9 +- 2 files changed, 215 insertions(+), 38 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 8a12068d4..2879fb418 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -46,6 +46,8 @@ using namespace std::literals::string_view_literals; #endif #include +#include +#include namespace Slic3r { @@ -216,76 +218,243 @@ namespace Slic3r { return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } + Polyline AvoidCrossingPerimeters2::simplify_travel(const Polyline &travel, const GCode &gcodegen) + { + struct Visitor + { + Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; + } visitor(m_grid); + + Polyline optimized_comb_path; + optimized_comb_path.points.reserve(travel.points.size()); + optimized_comb_path.points.emplace_back(travel.points.front()); + + for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) { + const Point ¤t_point = travel.points[point_idx - 1]; + Point next = travel.points[point_idx]; + + visitor.pt_current = ¤t_point; + + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { + visitor.pt_next = &travel.points[point_idx_2]; + m_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + if (!visitor.intersect) { + next = travel.points[point_idx_2]; + point_idx = point_idx_2; + } + } + + optimized_comb_path.append(next); + } + + return optimized_comb_path; + } + + void AvoidCrossingPerimeters2::init_layer(const Layer &layer) + { + BoundingBox bbox = get_extents(layer.lslices); + bbox.offset(SCALED_EPSILON); + ExPolygons boundaries = get_boundary(layer); + + for (const ExPolygon &ex_polygon : boundaries) { + m_boundaries.emplace_back(ex_polygon.contour); + + for (const Polygon &hole : ex_polygon.holes) m_boundaries.emplace_back(hole); + } + + m_grid.set_bbox(bbox); + m_grid.create(m_boundaries, scale_(10.)); + } + + ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) + { + size_t regions_count = 0; + size_t polygons_count = 0; + long perimeter_spacing = 0; + for (const LayerRegion *layer_region : layer.regions()) { + polygons_count += layer_region->slices.surfaces.size(); + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + perimeter_spacing /= regions_count; + const long offset = perimeter_spacing / 2; + + ExPolygons boundary; + boundary.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); + + boundary = union_ex(boundary); + ExPolygons perimeter_boundary = offset_ex(boundary, -offset); + ExPolygons final_boundary; + if (perimeter_boundary.size() != boundary.size()) { + // If any part of the polygon is missing after shrinking, the boundary os slice is used instead. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON)), + offset + SCALED_EPSILON); + perimeter_boundary = offset_ex(perimeter_boundary, offset + SCALED_EPSILON); + perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); + final_boundary = union_ex(perimeter_boundary); + } else { + final_boundary = std::move(perimeter_boundary); + } + + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(final_boundary, offset_ex(top_layer_polygons, -offset)); + } + + return final_boundary; + } + + static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) + { + const Point &p0 = polygon.points[(point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1)]; + const Point &p1 = polygon.points[point_idx]; + const Point &p2 = polygon.points[(point_idx >= (polygon.size() - 1)) ? (0) : (point_idx + 1)]; + + assert(p0 != p1); + assert(p1 != p2); + + Vec2d normal_1(-1 * (p1.y() - p0.y()), p1.x() - p0.x()); + Vec2d normal_2(-1 * (p2.y() - p1.y()), p2.x() - p1.x()); + normal_1.normalize(); + normal_2.normalize(); + + return (normal_1 + normal_2).normalized(); + }; + + static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) + { + return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); + } + Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) { + bool use_external = this->use_external_mp || this->use_external_mp_once; + if (use_external) { + Point scaled_origin = Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)); + Polyline result = m_external_mp.get()->shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); + result.translate(-scaled_origin); + return result; + } + const Point &start = gcodegen.last_pos(); const Point &end = point; const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); Matrix2d transform_from_x_axis = transform_to_x_axis.transpose(); + const Line travel_line_orig(start, end); const Line travel_line((transform_to_x_axis * start.cast()).cast(), (transform_to_x_axis * end.cast()).cast()); - Polygons borders; - borders.reserve(gcodegen.layer()->lslices.size()); - - for (const ExPolygon &ex_polygon : gcodegen.layer()->lslices) { - borders.emplace_back(ex_polygon.contour); - - for (const Polygon &hole : ex_polygon.holes) borders.emplace_back(hole); - } - std::vector intersections; - for (size_t border_idx = 0; border_idx < borders.size(); ++border_idx) { - const Polygon &border = borders[border_idx]; - Lines border_lines = border.lines(); + { + struct Visitor + { + Visitor(const EdgeGrid::Grid & grid, + std::vector &intersections, + const Matrix2d & transform_to_x_axis, + const Line & travel_line) + : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + {} - for (size_t line_idx = 0; line_idx < border_lines.size(); ++line_idx) { - const Line &border_line = border_lines[line_idx]; - Line border_line_transformed((transform_to_x_axis * border_line.a.cast()).cast(), - (transform_to_x_axis * border_line.b.cast()).cast()); + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; + ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); - Point intersection_transformed; + Point intersection_point; + if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { + intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, + (transform_to_x_axis * intersection_point.cast()).cast()); + intersection_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } - if (travel_line.intersection(border_line_transformed, &intersection_transformed)) - intersections.emplace_back(border_idx, line_idx, intersection_transformed); - } + const EdgeGrid::Grid &grid; + std::vector &intersections; + const Matrix2d &transform_to_x_axis; + const Line &travel_line; + std::unordered_set, boost::hash>> intersection_set; + } visitor(m_grid, intersections, transform_to_x_axis, travel_line_orig); + + m_grid.visit_cells_intersecting_line(start, end, visitor); } - // Sort intersections from the nearest to the farthest std::sort(intersections.begin(), intersections.end()); - // Polyline result(start, end); Polyline result; result.append(start); - for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { const Intersection &intersection_first = *it_first; - Point intersection_first_point((transform_from_x_axis * intersection_first.point.cast()).cast()); + Point intersection_first_point((transform_from_x_axis * intersection_first.point.cast()).cast()); for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { const Intersection &intersection_second = *it_second; - Point intersection_second_point( - (transform_from_x_axis * intersection_second.point.cast()).cast()); + Point intersection_second_point((transform_from_x_axis * intersection_second.point.cast()).cast()); if (intersection_first.border_idx == intersection_second.border_idx) { - Lines border_lines = borders[intersection_first.border_idx].lines(); + Lines border_lines = m_boundaries[intersection_first.border_idx].lines(); // Append the nearest intersection into the path result.append(intersection_first_point); - Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, - intersection_second.line_idx, intersection_first_point, - intersection_second_point); + Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, + intersection_first_point, intersection_second_point); // Append the path around the border into the path if (shortest_direction == Direction::Forward) for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) - result.append(border_lines[line_idx].b); + line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) + result.append(get_polygon_vertex_offset(m_boundaries[intersection_first.border_idx], + (line_idx + 1 == int(m_boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); else for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(border_lines[line_idx].a); + line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) + result.append(get_polygon_vertex_offset(m_boundaries[intersection_first.border_idx], line_idx + 0, SCALED_EPSILON)); // Append the farthest intersection into the path result.append(intersection_second_point); @@ -297,8 +466,7 @@ namespace Slic3r { } result.append(end); - - return result; + return simplify_travel(result, gcodegen); } std::string OozePrevention::pre_toolchange(GCode& gcodegen) @@ -2264,8 +2432,10 @@ void GCode::process_layer( for (InstanceToPrint &instance_to_print : instances_to_print) { m_config.apply(instance_to_print.print_object.config(), true); m_layer = layers[instance_to_print.layer_id].layer(); - if (m_config.avoid_crossing_perimeters) + if (m_config.avoid_crossing_perimeters) { m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->lslices, true)); + m_avoid_crossing_perimeters.init_layer(*m_layer); + } if (this->config().gcode_label_objects) gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 54ffa209f..428287087 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -54,7 +54,7 @@ public: virtual Polyline travel_to(const GCode &gcodegen, const Point &point); -private: +protected: // For initializing the regions to avoid. static Polygons collect_contours_all_layers(const PrintObjectPtrs& objects); @@ -85,13 +85,20 @@ private: const size_t end_idx, const Point &intersection_first, const Point &intersection_last); + static ExPolygons get_boundary(const Layer &layer); + Polyline simplify_travel(const Polyline &travel, const GCode &gcodegen); + + Polygons m_boundaries; + EdgeGrid::Grid m_grid; public: AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} virtual ~AvoidCrossingPerimeters2() = default; virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override; + + void init_layer(const Layer &layer); }; class OozePrevention { From 5c073d0ddb9a7e0b68188c04b7155d884a85ec51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 7 Oct 2020 14:52:57 +0200 Subject: [PATCH 05/35] Improved documentation of avoid crossing perimeters algorithm. --- src/libslic3r/GCode.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 2879fb418..b37b9e5cd 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -178,6 +178,7 @@ namespace Slic3r { return islands; } + // Create a rotation matrix for projection on the given vector static Matrix2d rotation_by_direction(const Point &direction) { Matrix2d rotation; @@ -188,6 +189,7 @@ namespace Slic3r { return rotation; } + // Returns a direction of the shortest path along the polygon boundary AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, const size_t start_idx, const size_t end_idx, @@ -253,6 +255,7 @@ namespace Slic3r { optimized_comb_path.points.reserve(travel.points.size()); optimized_comb_path.points.emplace_back(travel.points.front()); + // Try to skip some points in the path. for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) { const Point ¤t_point = travel.points[point_idx - 1]; Point next = travel.points[point_idx]; @@ -262,6 +265,7 @@ namespace Slic3r { for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { visitor.pt_next = &travel.points[point_idx_2]; m_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary if (!visitor.intersect) { next = travel.points[point_idx_2]; point_idx = point_idx_2; @@ -359,6 +363,7 @@ namespace Slic3r { return (normal_1 + normal_2).normalized(); }; + // Compute offset of polygon's in a direction inward normal static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) { return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); @@ -446,6 +451,7 @@ namespace Slic3r { Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first_point, intersection_second_point); // Append the path around the border into the path + // Offset of the polygon's point is used to simplify calculation of intersection between boundary if (shortest_direction == Direction::Forward) for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) From a4fc435f75e105011c3f3ce4ef8342dad751d85b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 7 Oct 2020 15:27:18 +0200 Subject: [PATCH 06/35] Clear generated boundaries from previous layer --- src/libslic3r/GCode.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index b37b9e5cd..054826fc1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -280,6 +280,7 @@ namespace Slic3r { void AvoidCrossingPerimeters2::init_layer(const Layer &layer) { + m_boundaries.clear(); BoundingBox bbox = get_extents(layer.lslices); bbox.offset(SCALED_EPSILON); ExPolygons boundaries = get_boundary(layer); From 39e3358af5a55b9c6a2dcb4dcf3b17f1fd1b01d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 8 Oct 2020 03:54:03 +0200 Subject: [PATCH 07/35] Avoid crossing perimeters bugfix --- src/libslic3r/GCode.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 054826fc1..ae8e3cfda 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -263,6 +263,12 @@ namespace Slic3r { visitor.pt_current = ¤t_point; for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { + if (travel.points[point_idx_2] == current_point) { + next = travel.points[point_idx_2]; + point_idx = point_idx_2; + continue; + } + visitor.pt_next = &travel.points[point_idx_2]; m_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); // Check if deleting point causes crossing a boundary @@ -282,6 +288,9 @@ namespace Slic3r { { m_boundaries.clear(); BoundingBox bbox = get_extents(layer.lslices); + // The path could start in the previous layer. Because of it, we need to extend bounding box by the previous layer + if (layer.lower_layer != nullptr) bbox.merge(get_extents(layer.lower_layer->lslices)); + bbox.offset(SCALED_EPSILON); ExPolygons boundaries = get_boundary(layer); @@ -292,7 +301,7 @@ namespace Slic3r { } m_grid.set_bbox(bbox); - m_grid.create(m_boundaries, scale_(10.)); + m_grid.create(m_boundaries, scale_(1.)); } ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) @@ -317,12 +326,12 @@ namespace Slic3r { ExPolygons perimeter_boundary = offset_ex(boundary, -offset); ExPolygons final_boundary; if (perimeter_boundary.size() != boundary.size()) { - // If any part of the polygon is missing after shrinking, the boundary os slice is used instead. - ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON)), + // If any part of the polygon is missing after shrinking, the boundary of slice is used instead. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON / 2)), offset + SCALED_EPSILON); - perimeter_boundary = offset_ex(perimeter_boundary, offset + SCALED_EPSILON); + perimeter_boundary = offset_ex(perimeter_boundary, offset); perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - final_boundary = union_ex(perimeter_boundary); + final_boundary = offset_ex(union_ex(perimeter_boundary), -offset); } else { final_boundary = std::move(perimeter_boundary); } From 556c212f9d0e64d365a96f51666e75f479190a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 13 Oct 2020 22:29:36 +0200 Subject: [PATCH 08/35] Fixed crossing perimeters when option "Wipe while retracting" is enabled --- src/libslic3r/GCode.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ae8e3cfda..b047d748b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3076,9 +3076,18 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // generate G-code for the travel move std::string gcode; - if (needs_retraction) + if (needs_retraction) { + Point last_post_before_retract = this->last_pos(); gcode += this->retract(); - else + // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters. + // Because of it, it is necessary to call avoid crossing perimeters for the path between previous last_post and last_post after calling retraction() + if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters && !m_avoid_crossing_perimeters.disable_once) { + Polyline retract_travel = m_avoid_crossing_perimeters.travel_to(*this, last_post_before_retract); + retract_travel.points.reserve(retract_travel.points.size() + travel.points.size()); + append(retract_travel.points, travel.points); + travel = std::move(retract_travel); + } + } else // Reset the wipe path when traveling, so one would not wipe along an old path. m_wipe.reset_path(); From 69658a57d822c52554b25493b7ae1ebb93f47c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 14 Oct 2020 01:02:39 +0200 Subject: [PATCH 09/35] Fixed crossing perimeters in some cases --- src/libslic3r/GCode.cpp | 44 +++++++++++++++++++++++------------------ src/libslic3r/GCode.hpp | 7 ++++--- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index b047d748b..3e04805d8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -356,29 +356,38 @@ namespace Slic3r { return final_boundary; } - static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) + static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { - const Point &p0 = polygon.points[(point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1)]; - const Point &p1 = polygon.points[point_idx]; - const Point &p2 = polygon.points[(point_idx >= (polygon.size() - 1)) ? (0) : (point_idx + 1)]; + assert(left != middle); + assert(middle != right); - assert(p0 != p1); - assert(p1 != p2); - - Vec2d normal_1(-1 * (p1.y() - p0.y()), p1.x() - p0.x()); - Vec2d normal_2(-1 * (p2.y() - p1.y()), p2.x() - p1.x()); + Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x()); + Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x()); normal_1.normalize(); normal_2.normalize(); return (normal_1 + normal_2).normalized(); }; + static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) + { + const Point &p0 = polygon.points[(point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1)]; + const Point &p1 = polygon.points[point_idx]; + const Point &p2 = polygon.points[(point_idx >= (polygon.size() - 1)) ? (0) : (point_idx + 1)]; + return three_points_inward_normal(p0, p1, p2); + }; + // Compute offset of polygon's in a direction inward normal static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) { return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); } + static Point get_middle_point_offset(const Point &left, const Point &middle, const Point &right, const int offset) + { + return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); + } + Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) { bool use_external = this->use_external_mp || this->use_external_mp_once; @@ -393,7 +402,6 @@ namespace Slic3r { const Point &end = point; const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); - Matrix2d transform_from_x_axis = transform_to_x_axis.transpose(); const Line travel_line_orig(start, end); const Line travel_line((transform_to_x_axis * start.cast()).cast(), @@ -423,7 +431,7 @@ namespace Slic3r { if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast()); + (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); intersection_set.insert(*it_contour_and_segment); } } @@ -447,19 +455,17 @@ namespace Slic3r { result.append(start); for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { const Intersection &intersection_first = *it_first; - Point intersection_first_point((transform_from_x_axis * intersection_first.point.cast()).cast()); - for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { const Intersection &intersection_second = *it_second; - Point intersection_second_point((transform_from_x_axis * intersection_second.point.cast()).cast()); - if (intersection_first.border_idx == intersection_second.border_idx) { Lines border_lines = m_boundaries[intersection_first.border_idx].lines(); + const Line &first_intersected_line = border_lines[intersection_first.line_idx]; + const Line &second_intersected_line = border_lines[intersection_second.line_idx]; // Append the nearest intersection into the path - result.append(intersection_first_point); + result.append(get_middle_point_offset(first_intersected_line.a, intersection_first.point, first_intersected_line.b, SCALED_EPSILON)); Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, - intersection_first_point, intersection_second_point); + intersection_first.point, intersection_second.point); // Append the path around the border into the path // Offset of the polygon's point is used to simplify calculation of intersection between boundary if (shortest_direction == Direction::Forward) @@ -473,9 +479,9 @@ namespace Slic3r { result.append(get_polygon_vertex_offset(m_boundaries[intersection_first.border_idx], line_idx + 0, SCALED_EPSILON)); // Append the farthest intersection into the path - result.append(intersection_second_point); + result.append(get_middle_point_offset(second_intersected_line.a, intersection_second.point, second_intersected_line.b, SCALED_EPSILON)); // Skip intersections in between - it_first = it_second; + it_first = (it_second - 1); break; } } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 428287087..c13a7d3b5 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -69,12 +69,13 @@ protected: { size_t border_idx; size_t line_idx; + Point point_transformed; Point point; - Intersection(size_t border_idx, size_t line_idx, Point point) - : border_idx(border_idx), line_idx(line_idx), point(point){}; + Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) + : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; - inline bool operator<(const Intersection &other) const { return this->point.x() < other.point.x(); } + inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } }; enum class Direction { Forward, Backward }; From c16aad7e0b8f97cb29b0e97547d8dc35a181e4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 01:57:26 +0200 Subject: [PATCH 10/35] Reworked the algorithm for avoid crossing perimeters for multiple objects --- src/libslic3r/GCode.cpp | 175 ++++++++++++++++++++++++++++------- src/libslic3r/GCode.hpp | 13 ++- src/libslic3r/MultiPoint.cpp | 11 +++ src/libslic3r/MultiPoint.hpp | 3 +- 4 files changed, 167 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3e04805d8..fd619f7e5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -220,7 +220,7 @@ namespace Slic3r { return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } - Polyline AvoidCrossingPerimeters2::simplify_travel(const Polyline &travel, const GCode &gcodegen) + Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel) { struct Visitor { @@ -249,7 +249,7 @@ namespace Slic3r { const Slic3r::Point *pt_current = nullptr; const Slic3r::Point *pt_next = nullptr; bool intersect = false; - } visitor(m_grid); + } visitor(edge_grid); Polyline optimized_comb_path; optimized_comb_path.points.reserve(travel.points.size()); @@ -270,7 +270,7 @@ namespace Slic3r { } visitor.pt_next = &travel.points[point_idx_2]; - m_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); // Check if deleting point causes crossing a boundary if (!visitor.intersect) { next = travel.points[point_idx_2]; @@ -287,21 +287,75 @@ namespace Slic3r { void AvoidCrossingPerimeters2::init_layer(const Layer &layer) { m_boundaries.clear(); - BoundingBox bbox = get_extents(layer.lslices); - // The path could start in the previous layer. Because of it, we need to extend bounding box by the previous layer - if (layer.lower_layer != nullptr) bbox.merge(get_extents(layer.lower_layer->lslices)); + m_boundaries_external.clear(); - bbox.offset(SCALED_EPSILON); - ExPolygons boundaries = get_boundary(layer); + ExPolygons boundaries = get_boundary(layer); + ExPolygons boundaries_external = get_boundary_external(layer); - for (const ExPolygon &ex_polygon : boundaries) { - m_boundaries.emplace_back(ex_polygon.contour); + m_bbox = get_extents(boundaries); + m_bbox.offset(SCALED_EPSILON); + m_bbox_external = get_extents(boundaries_external); + m_bbox_external.offset(SCALED_EPSILON); - for (const Polygon &hole : ex_polygon.holes) m_boundaries.emplace_back(hole); + for (const ExPolygon &ex_poly : boundaries) { + m_boundaries.emplace_back(ex_poly.contour); + append(m_boundaries, ex_poly.holes); + } + for (const ExPolygon &ex_poly : boundaries_external) { + m_boundaries_external.emplace_back(ex_poly.contour); + append(m_boundaries_external, ex_poly.holes); } - m_grid.set_bbox(bbox); + m_grid.set_bbox(m_bbox); m_grid.create(m_boundaries, scale_(1.)); + m_grid_external.set_bbox(m_bbox_external); + m_grid_external.create(m_boundaries_external, scale_(1.)); + } + + ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &llayer) + { + size_t regions_count = 0; + long perimeter_spacing = 0; + ExPolygons boundary; + for (const PrintObject *object : llayer.object()->print()->objects()) { + ExPolygons polygons_per_obj; + for (Layer *layer : object->layers()) { + if ((llayer.print_z - EPSILON) <= layer->print_z && layer->print_z <= (llayer.print_z + EPSILON)) { + for (const LayerRegion *layer_region : layer->regions()) { + for (const Surface &surface : layer_region->slices.surfaces) + polygons_per_obj.emplace_back(surface.expolygon); + + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + } + } + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + boundary.reserve(boundary.size() + polygons_per_obj.size()); + boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); + } + } + + perimeter_spacing /= regions_count; + const long perimeter_offset = perimeter_spacing / 2; + + Polygons contours; + Polygons holes; + for (ExPolygon &poly : boundary) { + contours.emplace_back(poly.contour); + append(holes, poly.holes); + } + + ExPolygons final_boundary = union_ex(diff(offset(contours, perimeter_spacing * 3), offset(contours, 3 * perimeter_spacing - perimeter_offset))); + ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); + final_boundary.reserve(final_boundary.size() + holes_boundary.size()); + final_boundary.insert(final_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + final_boundary = union_ex(final_boundary); + return final_boundary; } ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) @@ -331,7 +385,7 @@ namespace Slic3r { offset + SCALED_EPSILON); perimeter_boundary = offset_ex(perimeter_boundary, offset); perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - final_boundary = offset_ex(union_ex(perimeter_boundary), -offset); + final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary)); } else { final_boundary = std::move(perimeter_boundary); } @@ -388,20 +442,51 @@ namespace Slic3r { return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); } - Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) + bool check_if_could_cross_perimeters(const BoundingBox &bbox, const Point &start, const Point &end) { - bool use_external = this->use_external_mp || this->use_external_mp_once; - if (use_external) { - Point scaled_origin = Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)); - Polyline result = m_external_mp.get()->shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); - result.translate(-scaled_origin); - return result; + bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); + // When both endpoints are out of the bounding box, it needs to check in more detail. + if (start_out_of_bound && end_out_of_bound) { + Point intersection; + return bbox.polygon().intersection(Line(start, end), &intersection); + } + return true; + } + + std::pair clamp_endpoints_by_bounding_box(const BoundingBox &bbox, const Point &start, const Point &end) + { + bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); + Point start_clamped = start, end_clamped = end; + Points intersections; + if (start_out_of_bound || end_out_of_bound) { + bbox.polygon().intersections(Line(start, end), &intersections); + assert(intersections.size() <= 2); } - const Point &start = gcodegen.last_pos(); - const Point &end = point; - const Point direction = end - start; - Matrix2d transform_to_x_axis = rotation_by_direction(direction); + if (start_out_of_bound && !end_out_of_bound && intersections.size() == 1) { + start_clamped = intersections[0]; + } else if (!start_out_of_bound && end_out_of_bound && intersections.size() == 1) { + end_clamped = intersections[0]; + } else if (start_out_of_bound && end_out_of_bound && intersections.size() == 2) { + if ((intersections[0] - start).cast().norm() < (intersections[1] - start).cast().norm()) { + start_clamped = intersections[0]; + end_clamped = intersections[1]; + } else { + start_clamped = intersections[1]; + end_clamped = intersections[0]; + } + } + + return std::make_pair(start_clamped, end_clamped); + } + + Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons & boundaries, + const EdgeGrid::Grid &edge_grid, + const Point & start, + const Point & end) + { + const Point direction = end - start; + Matrix2d transform_to_x_axis = rotation_by_direction(direction); const Line travel_line_orig(start, end); const Line travel_line((transform_to_x_axis * start.cast()).cast(), @@ -444,9 +529,9 @@ namespace Slic3r { const Matrix2d &transform_to_x_axis; const Line &travel_line; std::unordered_set, boost::hash>> intersection_set; - } visitor(m_grid, intersections, transform_to_x_axis, travel_line_orig); + } visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); - m_grid.visit_cells_intersecting_line(start, end, visitor); + edge_grid.visit_cells_intersecting_line(start, end, visitor); } std::sort(intersections.begin(), intersections.end()); @@ -458,8 +543,8 @@ namespace Slic3r { for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { const Intersection &intersection_second = *it_second; if (intersection_first.border_idx == intersection_second.border_idx) { - Lines border_lines = m_boundaries[intersection_first.border_idx].lines(); - const Line &first_intersected_line = border_lines[intersection_first.line_idx]; + Lines border_lines = boundaries[intersection_first.border_idx].lines(); + const Line &first_intersected_line = border_lines[intersection_first.line_idx]; const Line &second_intersected_line = border_lines[intersection_second.line_idx]; // Append the nearest intersection into the path result.append(get_middle_point_offset(first_intersected_line.a, intersection_first.point, first_intersected_line.b, SCALED_EPSILON)); @@ -471,12 +556,12 @@ namespace Slic3r { if (shortest_direction == Direction::Forward) for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) - result.append(get_polygon_vertex_offset(m_boundaries[intersection_first.border_idx], - (line_idx + 1 == int(m_boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); + result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); else for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(get_polygon_vertex_offset(m_boundaries[intersection_first.border_idx], line_idx + 0, SCALED_EPSILON)); + result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); // Append the farthest intersection into the path result.append(get_middle_point_offset(second_intersected_line.a, intersection_second.point, second_intersected_line.b, SCALED_EPSILON)); @@ -488,7 +573,33 @@ namespace Slic3r { } result.append(end); - return simplify_travel(result, gcodegen); + return simplify_travel(edge_grid, result); + } + + Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) + { + // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). + // Otherwise perform the path planning in the coordinate system of the active object. + bool use_external = this->use_external_mp || this->use_external_mp_once; + Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); + Point start = gcodegen.last_pos() + scaled_origin; + Point end = point + scaled_origin; + Polyline result; + if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { + result = Polyline({start, end}); + } else { + auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); + if (use_external) + result = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped); + else + result = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped); + } + + result.points.front() = start; + result.points.back() = end; + if (use_external) + result.translate(-scaled_origin); + return result; } std::string OozePrevention::pre_toolchange(GCode& gcodegen) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index c13a7d3b5..e7a54b571 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -88,10 +88,19 @@ private: const Point &intersection_last); static ExPolygons get_boundary(const Layer &layer); - Polyline simplify_travel(const Polyline &travel, const GCode &gcodegen); + static ExPolygons get_boundary_external(const Layer &layer); - Polygons m_boundaries; + static Polyline simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel); + + static Polyline avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end); + + Polygons m_boundaries; + Polygons m_boundaries_external; + BoundingBox m_bbox; + BoundingBox m_bbox_external; EdgeGrid::Grid m_grid; + EdgeGrid::Grid m_grid_external; + public: AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 39b07e7d8..a00dd802b 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -140,6 +140,17 @@ bool MultiPoint::first_intersection(const Line& line, Point* intersection) const return found; } +bool MultiPoint::intersections(const Line &line, Points *intersections) const +{ + size_t intersections_size = intersections->size(); + for (const Line &polygon_line : this->lines()) { + Point intersection; + if (polygon_line.intersection(line, &intersection)) + intersections->emplace_back(std::move(intersection)); + } + return intersections->size() > intersections_size; +} + std::vector MultiPoint::_douglas_peucker(const std::vector& pts, const double tolerance) { std::vector result_pts; diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 9ff91b502..653e59cd8 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -81,7 +81,8 @@ public: bool intersection(const Line& line, Point* intersection) const; bool first_intersection(const Line& line, Point* intersection) const; - + bool intersections(const Line &line, Points *intersections) const; + static Points _douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double& tolerance); }; From c828a5d6e9fac6de138ab576f02105cfff8dd8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 02:25:45 +0200 Subject: [PATCH 11/35] Added the possibility to set the maximum length of the detour --- src/libslic3r/GCode.cpp | 6 ++++++ src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 1 + src/libslic3r/PrintConfig.cpp | 10 ++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/slic3r/GUI/Tab.cpp | 3 ++- 6 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index fd619f7e5..56052ebb7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -597,6 +597,12 @@ namespace Slic3r { result.points.front() = start; result.points.back() = end; + + Line travel(start, end); + double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); + if ((max_detour_length > 0) && ((result.length() - travel.length()) > max_detour_length)) { + result = Polyline({start, end}); + } if (use_external) result.translate(-scaled_origin); return result; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 199f4e936..b6c63c4fb 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -410,7 +410,7 @@ const std::vector& Preset::print_options() "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing", - "max_print_speed", "max_volumetric_speed", + "max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour", #ifdef HAS_PRESSURE_EQUALIZER "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", #endif /* HAS_PRESSURE_EQUALIZER */ diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 47d48dd40..53b24a01e 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -71,6 +71,7 @@ bool Print::invalidate_state_by_config_options(const std::vector steps_gcode = { "avoid_crossing_perimeters", + "avoid_crossing_perimeters_max_detour", "bed_shape", "bed_temperature", "before_layer_gcode", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index e62347487..3de4f3e0e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -180,6 +180,16 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("avoid_crossing_perimeters_max_detour", coFloat); + def->label = L("Avoid crossing perimeters - The max detour lenght"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("The maximum detour length for avoid crossing perimeters. " + "If the detour is longer than this value, avoid crossing perimeters is not applied for this path."); + def->sidetext = L("mm (zero to disable)"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("bed_temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Bed temperature for layers after the first one. " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 89c9c7a97..dfb336676 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -821,6 +821,7 @@ class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig public: ConfigOptionBool avoid_crossing_perimeters; + ConfigOptionFloat avoid_crossing_perimeters_max_detour; ConfigOptionPoints bed_shape; ConfigOptionInts bed_temperature; ConfigOptionFloat bridge_acceleration; @@ -894,6 +895,7 @@ protected: this->MachineEnvelopeConfig::initialize(cache, base_ptr); this->GCodeConfig::initialize(cache, base_ptr); OPT_PTR(avoid_crossing_perimeters); + OPT_PTR(avoid_crossing_perimeters_max_detour); OPT_PTR(bed_shape); OPT_PTR(bed_temperature); OPT_PTR(bridge_acceleration); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 45ee92c74..399751e11 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -530,7 +530,7 @@ void Tab::decorate() wxColour* colored_label_clr = nullptr; if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || - opt.first == "compatible_prints" || opt.first == "compatible_printers") + opt.first == "compatible_prints" || opt.first == "compatible_printers") colored_label_clr = (m_colored_Label_colors.find(opt.first) == m_colored_Label_colors.end()) ? nullptr : m_colored_Label_colors.at(opt.first); if (!colored_label_clr) { @@ -1410,6 +1410,7 @@ void TabPrint::build() optgroup->append_single_option_line("extra_perimeters", category_path + "extra-perimeters-if-needed"); optgroup->append_single_option_line("ensure_vertical_shell_thickness", category_path + "ensure-vertical-shell-thickness"); optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters"); + optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour"); optgroup->append_single_option_line("thin_walls", category_path + "detect-thin-walls"); optgroup->append_single_option_line("overhangs", category_path + "detect-bridging-perimeters"); From 4288be0e06885c668a76b2ed07b417a93d70d9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 07:39:15 +0200 Subject: [PATCH 12/35] Fixed a case when the intersection is one of the endpoints of the line --- src/libslic3r/GCode.cpp | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 56052ebb7..22297796e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -410,6 +410,19 @@ namespace Slic3r { return final_boundary; } + static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) + { + if (point != polygon.points[point_idx]) + return polygon.points[point_idx]; + + int line_idx = point_idx; + if (forward) + for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); + else + for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1))); + return polygon.points[line_idx]; + } + static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { assert(left != middle); @@ -425,11 +438,13 @@ namespace Slic3r { static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) { - const Point &p0 = polygon.points[(point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1)]; - const Point &p1 = polygon.points[point_idx]; - const Point &p2 = polygon.points[(point_idx >= (polygon.size() - 1)) ? (0) : (point_idx + 1)]; - return three_points_inward_normal(p0, p1, p2); - }; + const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); + const size_t right_idx = (point_idx >= (polygon.size() - 1)) ? 0 : (point_idx + 1); + const Point &middle = polygon.points[point_idx]; + const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); + const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + return three_points_inward_normal(left, middle, right); + } // Compute offset of polygon's in a direction inward normal static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) @@ -437,8 +452,10 @@ namespace Slic3r { return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); } - static Point get_middle_point_offset(const Point &left, const Point &middle, const Point &right, const int offset) + static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const int offset) { + const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); + const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); } @@ -543,11 +560,11 @@ namespace Slic3r { for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { const Intersection &intersection_second = *it_second; if (intersection_first.border_idx == intersection_second.border_idx) { - Lines border_lines = boundaries[intersection_first.border_idx].lines(); - const Line &first_intersected_line = border_lines[intersection_first.line_idx]; - const Line &second_intersected_line = border_lines[intersection_second.line_idx]; + Lines border_lines = boundaries[intersection_first.border_idx].lines(); // Append the nearest intersection into the path - result.append(get_middle_point_offset(first_intersected_line.a, intersection_first.point, first_intersected_line.b, SCALED_EPSILON)); + size_t left_idx = intersection_first.line_idx; + size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); + result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point); @@ -564,7 +581,9 @@ namespace Slic3r { result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); // Append the farthest intersection into the path - result.append(get_middle_point_offset(second_intersected_line.a, intersection_second.point, second_intersected_line.b, SCALED_EPSILON)); + left_idx = intersection_second.line_idx; + right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); + result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); // Skip intersections in between it_first = (it_second - 1); break; From 20916e23628d9ed70cb311945a3c1f5750f3722e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 07:48:05 +0200 Subject: [PATCH 13/35] Disable filed with max detour length when avoid crossing perimeters is disabled --- src/slic3r/GUI/ConfigManipulation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 845dc1c0b..d23f938f6 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -312,6 +312,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); + + bool have_avoid_crossing_perimeters = config->opt_bool("avoid_crossing_perimeters"); + toggle_field("avoid_crossing_perimeters_max_detour", have_avoid_crossing_perimeters); } void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/) From 8adf02a289490968d32be11337e12ad7959364bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 09:03:49 +0200 Subject: [PATCH 14/35] Moved AvoidCrossingPerimeters to separate file --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 536 ----------------- src/libslic3r/GCode.hpp | 78 +-- .../GCode/AvoidCrossingPerimeters.cpp | 554 ++++++++++++++++++ .../GCode/AvoidCrossingPerimeters.hpp | 101 ++++ 5 files changed, 658 insertions(+), 613 deletions(-) create mode 100644 src/libslic3r/GCode/AvoidCrossingPerimeters.cpp create mode 100644 src/libslic3r/GCode/AvoidCrossingPerimeters.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 150d66d75..93f947545 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -99,6 +99,8 @@ add_library(libslic3r STATIC GCode/WipeTower.hpp GCode/GCodeProcessor.cpp GCode/GCodeProcessor.hpp + GCode/AvoidCrossingPerimeters.cpp + GCode/AvoidCrossingPerimeters.hpp GCode.cpp GCode.hpp GCodeReader.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 22297796e..f152edebf 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -91,542 +91,6 @@ namespace Slic3r { return ok; } - void AvoidCrossingPerimeters::init_external_mp(const Print& print) - { - m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects()))); - } - - // Plan a travel move while minimizing the number of perimeter crossings. - // point is in unscaled coordinates, in the coordinate system of the current active object - // (set by gcodegen.set_origin()). - Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point) - { - // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). - // Otherwise perform the path planning in the coordinate system of the active object. - bool use_external = this->use_external_mp || this->use_external_mp_once; - Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); - Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())-> - shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); - if (use_external) - result.translate(-scaled_origin); - return result; - } - - // Collect outer contours of all objects over all layers. - // Discard objects only containing thin walls (offset would fail on an empty polygon). - // Used by avoid crossing perimeters feature. - Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects) - { - Polygons islands; - for (const PrintObject* object : objects) { - // Reducing all the object slices into the Z projection in a logarithimc fashion. - // First reduce to half the number of layers. - std::vector polygons_per_layer((object->layers().size() + 1) / 2); - tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2), - [&object, &polygons_per_layer](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - const Layer* layer1 = object->layers()[i * 2]; - const Layer* layer2 = object->layers()[i * 2 + 1]; - Polygons polys; - polys.reserve(layer1->lslices.size() + layer2->lslices.size()); - for (const ExPolygon& expoly : layer1->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - for (const ExPolygon& expoly : layer2->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer[i] = union_(polys); - } - }); - if (object->layers().size() & 1) { - const Layer* layer = object->layers().back(); - Polygons polys; - polys.reserve(layer->lslices.size()); - for (const ExPolygon& expoly : layer->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer.back() = union_(polys); - } - // Now reduce down to a single layer. - size_t cnt = polygons_per_layer.size(); - while (cnt > 1) { - tbb::parallel_for(tbb::blocked_range(0, cnt / 2), - [&polygons_per_layer](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - Polygons polys; - polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size()); - polygons_append(polys, polygons_per_layer[i * 2]); - polygons_append(polys, polygons_per_layer[i * 2 + 1]); - polygons_per_layer[i * 2] = union_(polys); - } - }); - for (size_t i = 1; i < cnt / 2; ++i) - polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); - if (cnt & 1) - polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); - cnt = (cnt + 1) / 2; - } - // And collect copies of the objects. - for (const PrintInstance& instance : object->instances()) { - // All the layers were reduced to the 1st item of polygons_per_layer. - size_t i = islands.size(); - polygons_append(islands, polygons_per_layer.front()); - for (; i < islands.size(); ++i) - islands[i].translate(instance.shift); - } - } - return islands; - } - - // Create a rotation matrix for projection on the given vector - static Matrix2d rotation_by_direction(const Point &direction) - { - Matrix2d rotation; - rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); - rotation(1, 0) = -rotation(0, 1); - rotation(1, 1) = rotation(0, 0); - - return rotation; - } - - // Returns a direction of the shortest path along the polygon boundary - AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last) - { - double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); - double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); - - auto cyclic_index = [&lines](int index) { - if (index >= int(lines.size())) - index = 0; - else if (index < 0) - index = lines.size() - 1; - - return index; - }; - - for (int line_idx = cyclic_index(int(start_idx) + 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx + 1)) - total_length_forward += lines[line_idx].length(); - - for (int line_idx = cyclic_index(int(start_idx) - 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx - 1)) - total_length_backward += lines[line_idx].length(); - - total_length_forward += (lines[end_idx].a - intersection_last).cast().norm(); - total_length_backward += (lines[end_idx].b - intersection_last).cast().norm(); - - return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; - } - - Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel) - { - struct Visitor - { - Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} - - bool operator()(coord_t iy, coord_t ix) - { - assert(pt_current != nullptr); - assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - this->intersect = false; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { - this->intersect = true; - return false; - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - const Slic3r::Point *pt_current = nullptr; - const Slic3r::Point *pt_next = nullptr; - bool intersect = false; - } visitor(edge_grid); - - Polyline optimized_comb_path; - optimized_comb_path.points.reserve(travel.points.size()); - optimized_comb_path.points.emplace_back(travel.points.front()); - - // Try to skip some points in the path. - for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) { - const Point ¤t_point = travel.points[point_idx - 1]; - Point next = travel.points[point_idx]; - - visitor.pt_current = ¤t_point; - - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { - if (travel.points[point_idx_2] == current_point) { - next = travel.points[point_idx_2]; - point_idx = point_idx_2; - continue; - } - - visitor.pt_next = &travel.points[point_idx_2]; - edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); - // Check if deleting point causes crossing a boundary - if (!visitor.intersect) { - next = travel.points[point_idx_2]; - point_idx = point_idx_2; - } - } - - optimized_comb_path.append(next); - } - - return optimized_comb_path; - } - - void AvoidCrossingPerimeters2::init_layer(const Layer &layer) - { - m_boundaries.clear(); - m_boundaries_external.clear(); - - ExPolygons boundaries = get_boundary(layer); - ExPolygons boundaries_external = get_boundary_external(layer); - - m_bbox = get_extents(boundaries); - m_bbox.offset(SCALED_EPSILON); - m_bbox_external = get_extents(boundaries_external); - m_bbox_external.offset(SCALED_EPSILON); - - for (const ExPolygon &ex_poly : boundaries) { - m_boundaries.emplace_back(ex_poly.contour); - append(m_boundaries, ex_poly.holes); - } - for (const ExPolygon &ex_poly : boundaries_external) { - m_boundaries_external.emplace_back(ex_poly.contour); - append(m_boundaries_external, ex_poly.holes); - } - - m_grid.set_bbox(m_bbox); - m_grid.create(m_boundaries, scale_(1.)); - m_grid_external.set_bbox(m_bbox_external); - m_grid_external.create(m_boundaries_external, scale_(1.)); - } - - ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &llayer) - { - size_t regions_count = 0; - long perimeter_spacing = 0; - ExPolygons boundary; - for (const PrintObject *object : llayer.object()->print()->objects()) { - ExPolygons polygons_per_obj; - for (Layer *layer : object->layers()) { - if ((llayer.print_z - EPSILON) <= layer->print_z && layer->print_z <= (llayer.print_z + EPSILON)) { - for (const LayerRegion *layer_region : layer->regions()) { - for (const Surface &surface : layer_region->slices.surfaces) - polygons_per_obj.emplace_back(surface.expolygon); - - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - } - } - - for (const PrintInstance &instance : object->instances()) { - size_t boundary_idx = boundary.size(); - boundary.reserve(boundary.size() + polygons_per_obj.size()); - boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); - for (; boundary_idx < boundary.size(); ++boundary_idx) - boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); - } - } - - perimeter_spacing /= regions_count; - const long perimeter_offset = perimeter_spacing / 2; - - Polygons contours; - Polygons holes; - for (ExPolygon &poly : boundary) { - contours.emplace_back(poly.contour); - append(holes, poly.holes); - } - - ExPolygons final_boundary = union_ex(diff(offset(contours, perimeter_spacing * 3), offset(contours, 3 * perimeter_spacing - perimeter_offset))); - ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); - final_boundary.reserve(final_boundary.size() + holes_boundary.size()); - final_boundary.insert(final_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - final_boundary = union_ex(final_boundary); - return final_boundary; - } - - ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) - { - size_t regions_count = 0; - size_t polygons_count = 0; - long perimeter_spacing = 0; - for (const LayerRegion *layer_region : layer.regions()) { - polygons_count += layer_region->slices.surfaces.size(); - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - perimeter_spacing /= regions_count; - const long offset = perimeter_spacing / 2; - - ExPolygons boundary; - boundary.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); - - boundary = union_ex(boundary); - ExPolygons perimeter_boundary = offset_ex(boundary, -offset); - ExPolygons final_boundary; - if (perimeter_boundary.size() != boundary.size()) { - // If any part of the polygon is missing after shrinking, the boundary of slice is used instead. - ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON / 2)), - offset + SCALED_EPSILON); - perimeter_boundary = offset_ex(perimeter_boundary, offset); - perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary)); - } else { - final_boundary = std::move(perimeter_boundary); - } - - // Collect all top layers that will not be crossed. - polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) ++polygons_count; - - if (polygons_count > 0) { - ExPolygons top_layer_polygons; - top_layer_polygons.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); - - top_layer_polygons = union_ex(top_layer_polygons); - return diff_ex(final_boundary, offset_ex(top_layer_polygons, -offset)); - } - - return final_boundary; - } - - static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) - { - if (point != polygon.points[point_idx]) - return polygon.points[point_idx]; - - int line_idx = point_idx; - if (forward) - for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); - else - for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1))); - return polygon.points[line_idx]; - } - - static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) - { - assert(left != middle); - assert(middle != right); - - Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x()); - Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x()); - normal_1.normalize(); - normal_2.normalize(); - - return (normal_1 + normal_2).normalized(); - }; - - static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) - { - const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); - const size_t right_idx = (point_idx >= (polygon.size() - 1)) ? 0 : (point_idx + 1); - const Point &middle = polygon.points[point_idx]; - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); - return three_points_inward_normal(left, middle, right); - } - - // Compute offset of polygon's in a direction inward normal - static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) - { - return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); - } - - static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const int offset) - { - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); - return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); - } - - bool check_if_could_cross_perimeters(const BoundingBox &bbox, const Point &start, const Point &end) - { - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - // When both endpoints are out of the bounding box, it needs to check in more detail. - if (start_out_of_bound && end_out_of_bound) { - Point intersection; - return bbox.polygon().intersection(Line(start, end), &intersection); - } - return true; - } - - std::pair clamp_endpoints_by_bounding_box(const BoundingBox &bbox, const Point &start, const Point &end) - { - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - Point start_clamped = start, end_clamped = end; - Points intersections; - if (start_out_of_bound || end_out_of_bound) { - bbox.polygon().intersections(Line(start, end), &intersections); - assert(intersections.size() <= 2); - } - - if (start_out_of_bound && !end_out_of_bound && intersections.size() == 1) { - start_clamped = intersections[0]; - } else if (!start_out_of_bound && end_out_of_bound && intersections.size() == 1) { - end_clamped = intersections[0]; - } else if (start_out_of_bound && end_out_of_bound && intersections.size() == 2) { - if ((intersections[0] - start).cast().norm() < (intersections[1] - start).cast().norm()) { - start_clamped = intersections[0]; - end_clamped = intersections[1]; - } else { - start_clamped = intersections[1]; - end_clamped = intersections[0]; - } - } - - return std::make_pair(start_clamped, end_clamped); - } - - Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons & boundaries, - const EdgeGrid::Grid &edge_grid, - const Point & start, - const Point & end) - { - const Point direction = end - start; - Matrix2d transform_to_x_axis = rotation_by_direction(direction); - - const Line travel_line_orig(start, end); - const Line travel_line((transform_to_x_axis * start.cast()).cast(), - (transform_to_x_axis * end.cast()).cast()); - - std::vector intersections; - { - struct Visitor - { - Visitor(const EdgeGrid::Grid & grid, - std::vector &intersections, - const Matrix2d & transform_to_x_axis, - const Line & travel_line) - : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) - {} - - bool operator()(coord_t iy, coord_t ix) - { - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; - ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - - Point intersection_point; - if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && - intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { - intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); - intersection_set.insert(*it_contour_and_segment); - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - std::vector &intersections; - const Matrix2d &transform_to_x_axis; - const Line &travel_line; - std::unordered_set, boost::hash>> intersection_set; - } visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); - - edge_grid.visit_cells_intersecting_line(start, end, visitor); - } - - std::sort(intersections.begin(), intersections.end()); - - Polyline result; - result.append(start); - for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { - const Intersection &intersection_first = *it_first; - for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { - const Intersection &intersection_second = *it_second; - if (intersection_first.border_idx == intersection_second.border_idx) { - Lines border_lines = boundaries[intersection_first.border_idx].lines(); - // Append the nearest intersection into the path - size_t left_idx = intersection_first.line_idx; - size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); - result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); - - Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, - intersection_first.point, intersection_second.point); - // Append the path around the border into the path - // Offset of the polygon's point is used to simplify calculation of intersection between boundary - if (shortest_direction == Direction::Forward) - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) - result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], - (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); - else - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); - - // Append the farthest intersection into the path - left_idx = intersection_second.line_idx; - right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); - result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); - // Skip intersections in between - it_first = (it_second - 1); - break; - } - } - } - - result.append(end); - return simplify_travel(edge_grid, result); - } - - Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) - { - // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). - // Otherwise perform the path planning in the coordinate system of the active object. - bool use_external = this->use_external_mp || this->use_external_mp_once; - Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); - Point start = gcodegen.last_pos() + scaled_origin; - Point end = point + scaled_origin; - Polyline result; - if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { - result = Polyline({start, end}); - } else { - auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); - if (use_external) - result = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped); - else - result = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped); - } - - result.points.front() = start; - result.points.back() = end; - - Line travel(start, end); - double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); - if ((max_detour_length > 0) && ((result.length() - travel.length()) > max_detour_length)) { - result = Polyline({start, end}); - } - if (use_external) - result.translate(-scaled_origin); - return result; - } - std::string OozePrevention::pre_toolchange(GCode& gcodegen) { std::string gcode; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index e7a54b571..60c1dd9e1 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -9,6 +9,7 @@ #include "Point.hpp" #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" +#include "GCode/AvoidCrossingPerimeters.hpp" #include "GCode/CoolingBuffer.hpp" #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" @@ -34,83 +35,6 @@ namespace { struct Item; } struct PrintInstance; using PrintObjectPtrs = std::vector; -class AvoidCrossingPerimeters { -public: - - // this flag triggers the use of the external configuration space - bool use_external_mp; - bool use_external_mp_once; // just for the next travel move - - // this flag disables avoid_crossing_perimeters just for the next travel move - // we enable it by default for the first travel move in print - bool disable_once; - - AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {} - virtual ~AvoidCrossingPerimeters() = default; - - void reset() { m_external_mp.reset(); m_layer_mp.reset(); } - void init_external_mp(const Print &print); - void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point); - -protected: - // For initializing the regions to avoid. - static Polygons collect_contours_all_layers(const PrintObjectPtrs& objects); - - std::unique_ptr m_external_mp; - std::unique_ptr m_layer_mp; -}; - -class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters -{ -protected: - struct Intersection - { - size_t border_idx; - size_t line_idx; - Point point_transformed; - Point point; - - Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) - : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; - - inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } - }; - - enum class Direction { Forward, Backward }; - -private: - static Direction get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last); - static ExPolygons get_boundary(const Layer &layer); - - static ExPolygons get_boundary_external(const Layer &layer); - - static Polyline simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel); - - static Polyline avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end); - - Polygons m_boundaries; - Polygons m_boundaries_external; - BoundingBox m_bbox; - BoundingBox m_bbox_external; - EdgeGrid::Grid m_grid; - EdgeGrid::Grid m_grid_external; - -public: - AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} - - virtual ~AvoidCrossingPerimeters2() = default; - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override; - - void init_layer(const Layer &layer); -}; - class OozePrevention { public: bool enable; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp new file mode 100644 index 000000000..8fe30b7c1 --- /dev/null +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -0,0 +1,554 @@ +#include "../Layer.hpp" +#include "../MotionPlanner.hpp" +#include "../GCode.hpp" +#include "../MotionPlanner.hpp" +#include "../EdgeGrid.hpp" +#include "../Geometry.hpp" +#include "../ShortestPath.hpp" +#include "../Print.hpp" +#include "../Polygon.hpp" +#include "../ExPolygon.hpp" +#include "../ClipperUtils.hpp" +#include "AvoidCrossingPerimeters.hpp" + +#include + +namespace Slic3r { + +void AvoidCrossingPerimeters::init_external_mp(const Print& print) +{ + m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects()))); +} + +// Plan a travel move while minimizing the number of perimeter crossings. +// point is in unscaled coordinates, in the coordinate system of the current active object +// (set by gcodegen.set_origin()). +Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point) +{ + // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). + // Otherwise perform the path planning in the coordinate system of the active object. + bool use_external = this->use_external_mp || this->use_external_mp_once; + Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); + Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())-> + shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); + if (use_external) + result.translate(-scaled_origin); + return result; +} + +// Collect outer contours of all objects over all layers. +// Discard objects only containing thin walls (offset would fail on an empty polygon). +// Used by avoid crossing perimeters feature. +Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects) +{ + Polygons islands; + for (const PrintObject* object : objects) { + // Reducing all the object slices into the Z projection in a logarithimc fashion. + // First reduce to half the number of layers. + std::vector polygons_per_layer((object->layers().size() + 1) / 2); + tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2), + [&object, &polygons_per_layer](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + const Layer* layer1 = object->layers()[i * 2]; + const Layer* layer2 = object->layers()[i * 2 + 1]; + Polygons polys; + polys.reserve(layer1->lslices.size() + layer2->lslices.size()); + for (const ExPolygon& expoly : layer1->lslices) + //FIXME no holes? + polys.emplace_back(expoly.contour); + for (const ExPolygon& expoly : layer2->lslices) + //FIXME no holes? + polys.emplace_back(expoly.contour); + polygons_per_layer[i] = union_(polys); + } + }); + if (object->layers().size() & 1) { + const Layer* layer = object->layers().back(); + Polygons polys; + polys.reserve(layer->lslices.size()); + for (const ExPolygon& expoly : layer->lslices) + //FIXME no holes? + polys.emplace_back(expoly.contour); + polygons_per_layer.back() = union_(polys); + } + // Now reduce down to a single layer. + size_t cnt = polygons_per_layer.size(); + while (cnt > 1) { + tbb::parallel_for(tbb::blocked_range(0, cnt / 2), + [&polygons_per_layer](const tbb::blocked_range& range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + Polygons polys; + polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size()); + polygons_append(polys, polygons_per_layer[i * 2]); + polygons_append(polys, polygons_per_layer[i * 2 + 1]); + polygons_per_layer[i * 2] = union_(polys); + } + }); + for (size_t i = 0; i < cnt / 2; ++i) + polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); + if (cnt & 1) + polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); + cnt = (cnt + 1) / 2; + } + // And collect copies of the objects. + for (const PrintInstance& instance : object->instances()) { + // All the layers were reduced to the 1st item of polygons_per_layer. + size_t i = islands.size(); + polygons_append(islands, polygons_per_layer.front()); + for (; i < islands.size(); ++i) + islands[i].translate(instance.shift); + } + } + return islands; +} + +// Create a rotation matrix for projection on the given vector +static Matrix2d rotation_by_direction(const Point &direction) +{ + Matrix2d rotation; + rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); + rotation(1, 0) = -rotation(0, 1); + rotation(1, 1) = rotation(0, 0); + + return rotation; +} + +static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) +{ + if (point != polygon.points[point_idx]) + return polygon.points[point_idx]; + + int line_idx = point_idx; + if (forward) + for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); + else + for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1))); + return polygon.points[line_idx]; +} + +static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) +{ + assert(left != middle); + assert(middle != right); + + Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x()); + Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x()); + normal_1.normalize(); + normal_2.normalize(); + + return (normal_1 + normal_2).normalized(); +}; + +static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) +{ + const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); + const size_t right_idx = (point_idx >= (polygon.size() - 1)) ? 0 : (point_idx + 1); + const Point &middle = polygon.points[point_idx]; + const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); + const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + return three_points_inward_normal(left, middle, right); +} + +// Compute offset of polygon's in a direction inward normal +static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) +{ + return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); +} + +static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const int offset) +{ + const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); + const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); +} + +static bool check_if_could_cross_perimeters(const BoundingBox &bbox, const Point &start, const Point &end) +{ + bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); + // When both endpoints are out of the bounding box, it needs to check in more detail. + if (start_out_of_bound && end_out_of_bound) { + Point intersection; + return bbox.polygon().intersection(Line(start, end), &intersection); + } + return true; +} + +static std::pair clamp_endpoints_by_bounding_box(const BoundingBox &bbox, const Point &start, const Point &end) +{ + bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); + Point start_clamped = start, end_clamped = end; + Points intersections; + if (start_out_of_bound || end_out_of_bound) { + bbox.polygon().intersections(Line(start, end), &intersections); + assert(intersections.size() <= 2); + } + + if (start_out_of_bound && !end_out_of_bound && intersections.size() == 1) { + start_clamped = intersections[0]; + } else if (!start_out_of_bound && end_out_of_bound && intersections.size() == 1) { + end_clamped = intersections[0]; + } else if (start_out_of_bound && end_out_of_bound && intersections.size() == 2) { + if ((intersections[0] - start).cast().norm() < (intersections[1] - start).cast().norm()) { + start_clamped = intersections[0]; + end_clamped = intersections[1]; + } else { + start_clamped = intersections[1]; + end_clamped = intersections[0]; + } + } + + return std::make_pair(start_clamped, end_clamped); +} + +ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) +{ + size_t regions_count = 0; + size_t polygons_count = 0; + long perimeter_spacing = 0; + for (const LayerRegion *layer_region : layer.regions()) { + polygons_count += layer_region->slices.surfaces.size(); + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + perimeter_spacing /= regions_count; + const long offset = perimeter_spacing / 2; + + ExPolygons boundary; + boundary.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); + + boundary = union_ex(boundary); + ExPolygons perimeter_boundary = offset_ex(boundary, -offset); + ExPolygons final_boundary; + if (perimeter_boundary.size() != boundary.size()) { + // If any part of the polygon is missing after shrinking, the boundary of slice is used instead. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON / 2)), + offset + SCALED_EPSILON); + perimeter_boundary = offset_ex(perimeter_boundary, offset); + perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); + final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary)); + } else { + final_boundary = std::move(perimeter_boundary); + } + + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(final_boundary, offset_ex(top_layer_polygons, -offset)); + } + + return final_boundary; +} + +ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &llayer) +{ + size_t regions_count = 0; + long perimeter_spacing = 0; + ExPolygons boundary; + for (const PrintObject *object : llayer.object()->print()->objects()) { + ExPolygons polygons_per_obj; + for (Layer *layer : object->layers()) { + if ((llayer.print_z - EPSILON) <= layer->print_z && layer->print_z <= (llayer.print_z + EPSILON)) { + for (const LayerRegion *layer_region : layer->regions()) { + for (const Surface &surface : layer_region->slices.surfaces) + polygons_per_obj.emplace_back(surface.expolygon); + + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + } + } + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + boundary.reserve(boundary.size() + polygons_per_obj.size()); + boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); + } + } + + perimeter_spacing /= regions_count; + const long perimeter_offset = perimeter_spacing / 2; + + Polygons contours; + Polygons holes; + for (ExPolygon &poly : boundary) { + contours.emplace_back(poly.contour); + append(holes, poly.holes); + } + + ExPolygons final_boundary = union_ex(diff(offset(contours, perimeter_spacing * 3), offset(contours, 3 * perimeter_spacing - perimeter_offset))); + ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); + final_boundary.reserve(final_boundary.size() + holes_boundary.size()); + final_boundary.insert(final_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + final_boundary = union_ex(final_boundary); + return final_boundary; +} + +// Returns a direction of the shortest path along the polygon boundary +AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last) +{ + double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); + double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); + + auto cyclic_index = [&lines](int index) { + if (index >= int(lines.size())) + index = 0; + else if (index < 0) + index = lines.size() - 1; + + return index; + }; + + for (int line_idx = cyclic_index(int(start_idx) + 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx + 1)) + total_length_forward += lines[line_idx].length(); + + for (int line_idx = cyclic_index(int(start_idx) - 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx - 1)) + total_length_backward += lines[line_idx].length(); + + total_length_forward += (lines[end_idx].a - intersection_last).cast().norm(); + total_length_backward += (lines[end_idx].b - intersection_last).cast().norm(); + + return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; +} + +Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel) +{ + struct Visitor + { + Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; + } visitor(edge_grid); + + Polyline optimized_comb_path; + optimized_comb_path.points.reserve(travel.points.size()); + optimized_comb_path.points.emplace_back(travel.points.front()); + + // Try to skip some points in the path. + for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) { + const Point ¤t_point = travel.points[point_idx - 1]; + Point next = travel.points[point_idx]; + + visitor.pt_current = ¤t_point; + + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { + if (travel.points[point_idx_2] == current_point) { + next = travel.points[point_idx_2]; + point_idx = point_idx_2; + continue; + } + + visitor.pt_next = &travel.points[point_idx_2]; + edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary + if (!visitor.intersect) { + next = travel.points[point_idx_2]; + point_idx = point_idx_2; + } + } + + optimized_comb_path.append(next); + } + + return optimized_comb_path; +} + +Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end) +{ + const Point direction = end - start; + Matrix2d transform_to_x_axis = rotation_by_direction(direction); + + const Line travel_line_orig(start, end); + const Line travel_line((transform_to_x_axis * start.cast()).cast(), + (transform_to_x_axis * end.cast()).cast()); + + std::vector intersections; + { + struct Visitor + { + Visitor(const EdgeGrid::Grid & grid, + std::vector &intersections, + const Matrix2d & transform_to_x_axis, + const Line & travel_line) + : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + {} + + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; + ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + + Point intersection_point; + if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { + intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, + (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); + intersection_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + std::vector &intersections; + const Matrix2d &transform_to_x_axis; + const Line &travel_line; + std::unordered_set, boost::hash>> intersection_set; + } visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); + + edge_grid.visit_cells_intersecting_line(start, end, visitor); + } + + std::sort(intersections.begin(), intersections.end()); + + Polyline result; + result.append(start); + for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { + const Intersection &intersection_first = *it_first; + for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { + const Intersection &intersection_second = *it_second; + if (intersection_first.border_idx == intersection_second.border_idx) { + Lines border_lines = boundaries[intersection_first.border_idx].lines(); + // Append the nearest intersection into the path + size_t left_idx = intersection_first.line_idx; + size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); + result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); + + Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, + intersection_first.point, intersection_second.point); + // Append the path around the border into the path + // Offset of the polygon's point is used to simplify calculation of intersection between boundary + if (shortest_direction == Direction::Forward) + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) + result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); + else + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) + result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); + + // Append the farthest intersection into the path + left_idx = intersection_second.line_idx; + right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); + result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); + // Skip intersections in between + it_first = (it_second - 1); + break; + } + } + } + + result.append(end); + return simplify_travel(edge_grid, result); +} + +Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) +{ + // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). + // Otherwise perform the path planning in the coordinate system of the active object. + bool use_external = this->use_external_mp || this->use_external_mp_once; + Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); + Point start = gcodegen.last_pos() + scaled_origin; + Point end = point + scaled_origin; + Polyline result; + if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { + result = Polyline({start, end}); + } else { + auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); + if (use_external) + result = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped); + else + result = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped); + } + + result.points.front() = start; + result.points.back() = end; + + Line travel(start, end); + double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); + if ((max_detour_length > 0) && ((result.length() - travel.length()) > max_detour_length)) { + result = Polyline({start, end}); + } + if (use_external) + result.translate(-scaled_origin); + return result; +} + +void AvoidCrossingPerimeters2::init_layer(const Layer &layer) +{ + m_boundaries.clear(); + m_boundaries_external.clear(); + + ExPolygons boundaries = get_boundary(layer); + ExPolygons boundaries_external = get_boundary_external(layer); + + m_bbox = get_extents(boundaries); + m_bbox.offset(SCALED_EPSILON); + m_bbox_external = get_extents(boundaries_external); + m_bbox_external.offset(SCALED_EPSILON); + + for (const ExPolygon &ex_poly : boundaries) { + m_boundaries.emplace_back(ex_poly.contour); + append(m_boundaries, ex_poly.holes); + } + for (const ExPolygon &ex_poly : boundaries_external) { + m_boundaries_external.emplace_back(ex_poly.contour); + append(m_boundaries_external, ex_poly.holes); + } + + m_grid.set_bbox(m_bbox); + m_grid.create(m_boundaries, scale_(1.)); + m_grid_external.set_bbox(m_bbox_external); + m_grid_external.create(m_boundaries_external, scale_(1.)); +} + +} // namespace Slic3r diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp new file mode 100644 index 000000000..bc1f79654 --- /dev/null +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -0,0 +1,101 @@ +#ifndef slic3r_AvoidCrossingPerimeters_hpp_ +#define slic3r_AvoidCrossingPerimeters_hpp_ + +#include "../libslic3r.h" +#include "../ExPolygon.hpp" +#include "../EdgeGrid.hpp" + +namespace Slic3r { + +// Forward declarations. +class GCode; +class Layer; +class MotionPlanner; +class Point; +class Print; +class PrintObject; + +struct PrintInstance; +using PrintObjectPtrs = std::vector; + +class AvoidCrossingPerimeters +{ +public: + // this flag triggers the use of the external configuration space + bool use_external_mp; + bool use_external_mp_once; // just for the next travel move + + // this flag disables avoid_crossing_perimeters just for the next travel move + // we enable it by default for the first travel move in print + bool disable_once; + + AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {} + virtual ~AvoidCrossingPerimeters() = default; + + void reset() + { + m_external_mp.reset(); + m_layer_mp.reset(); + } + void init_external_mp(const Print &print); + void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } + + virtual Polyline travel_to(const GCode &gcodegen, const Point &point); + +protected: + // For initializing the regions to avoid. + static Polygons collect_contours_all_layers(const PrintObjectPtrs &objects); + + std::unique_ptr m_external_mp; + std::unique_ptr m_layer_mp; +}; + +class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters +{ +protected: + struct Intersection + { + size_t border_idx; + size_t line_idx; + Point point_transformed; + Point point; + + Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) + : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; + + inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } + }; + + enum class Direction { Forward, Backward }; + +private: + static ExPolygons get_boundary(const Layer &layer); + + static ExPolygons get_boundary_external(const Layer &layer); + + static Direction get_shortest_direction( + const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last); + + static Polyline simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel); + + static Polyline avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end); + + Polygons m_boundaries; + Polygons m_boundaries_external; + BoundingBox m_bbox; + BoundingBox m_bbox_external; + EdgeGrid::Grid m_grid; + EdgeGrid::Grid m_grid_external; + +public: + AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} + + virtual ~AvoidCrossingPerimeters2() = default; + + virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override; + + void init_layer(const Layer &layer); +}; +} // namespace Slic3r + +#endif // slic3r_AvoidCrossingPerimeters_hpp_ \ No newline at end of file From 3e98e2a4bdc21ec4999d796bbb146c1b1e103fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 10:56:19 +0200 Subject: [PATCH 15/35] Fixed avoiding of other printed objects, again Calling std::move on itself causes that the first polygon is empty, which results in disabling this feature on Linux. This was fixed before, but I accidentally reverted it when AvoidCrossingPerimeters was moved to separate file. --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 8fe30b7c1..1581eb7ad 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -84,7 +84,7 @@ Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectP polygons_per_layer[i * 2] = union_(polys); } }); - for (size_t i = 0; i < cnt / 2; ++i) + for (size_t i = 1; i < cnt / 2; ++i) polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); if (cnt & 1) polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); From 2afeea5b6fedc606c52068481f804b60100d3dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 16 Oct 2020 14:37:16 +0200 Subject: [PATCH 16/35] Fixed division by zero when the layer is empty --- .../GCode/AvoidCrossingPerimeters.cpp | 77 +++++++++++++------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 1581eb7ad..d510156c6 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -200,18 +200,56 @@ static std::pair clamp_endpoints_by_bounding_box(const BoundingBox return std::make_pair(start_clamped, end_clamped); } -ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) +static inline coord_t get_default_perimeter_spacing(const Print &print) { - size_t regions_count = 0; - size_t polygons_count = 0; - long perimeter_spacing = 0; + const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; + return scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end())); +} + +static coord_t get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + coord_t perimeter_spacing = 0; for (const LayerRegion *layer_region : layer.regions()) { - polygons_count += layer_region->slices.surfaces.size(); perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); ++regions_count; } - perimeter_spacing /= regions_count; - const long offset = perimeter_spacing / 2; + + assert(perimeter_spacing >= 0); + if (regions_count != 0) + perimeter_spacing /= regions_count; + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +static coord_t get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + coord_t perimeter_spacing = 0; + for (const PrintObject *object : layer.object()->print()->objects()) + for (Layer *l : object->layers()) + if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) + for (const LayerRegion *layer_region : l->regions()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + + assert(perimeter_spacing >= 0); + if (regions_count != 0) + perimeter_spacing /= regions_count; + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) +{ + const coord_t perimeter_spacing = get_perimeter_spacing(layer); + const coord_t offset = perimeter_spacing / 2; + size_t polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + polygons_count += layer_region->slices.surfaces.size(); ExPolygons boundary; boundary.reserve(polygons_count); @@ -252,36 +290,27 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) return final_boundary; } -ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &llayer) +ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) { - size_t regions_count = 0; - long perimeter_spacing = 0; ExPolygons boundary; - for (const PrintObject *object : llayer.object()->print()->objects()) { + for (const PrintObject *object : layer.object()->print()->objects()) { ExPolygons polygons_per_obj; - for (Layer *layer : object->layers()) { - if ((llayer.print_z - EPSILON) <= layer->print_z && layer->print_z <= (llayer.print_z + EPSILON)) { - for (const LayerRegion *layer_region : layer->regions()) { + for (Layer *l : object->layers()) + if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) + for (const LayerRegion *layer_region : l->regions()) for (const Surface &surface : layer_region->slices.surfaces) polygons_per_obj.emplace_back(surface.expolygon); - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - } - } - for (const PrintInstance &instance : object->instances()) { size_t boundary_idx = boundary.size(); boundary.reserve(boundary.size() + polygons_per_obj.size()); boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); - for (; boundary_idx < boundary.size(); ++boundary_idx) - boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); + for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); } } - perimeter_spacing /= regions_count; - const long perimeter_offset = perimeter_spacing / 2; + const coord_t perimeter_spacing = get_perimeter_spacing_external(layer); + const coord_t perimeter_offset = perimeter_spacing / 2; Polygons contours; Polygons holes; From 7f94e9fa5939ace11719d8de4a7b9b05d28c6639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 19 Oct 2020 09:44:33 +0200 Subject: [PATCH 17/35] Fixed perimeters crossing when supports are printed. --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index d510156c6..c5e43a65a 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -264,12 +264,19 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON / 2)), offset + SCALED_EPSILON); perimeter_boundary = offset_ex(perimeter_boundary, offset); + perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size()); perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary)); } else { final_boundary = std::move(perimeter_boundary); } + // Add an outer boundary to avoid crossing perimeters from supports + ExPolygons outer_boundary = diff_ex(offset_ex(boundary, 2 * perimeter_spacing), offset_ex(boundary, 2 * perimeter_spacing - offset)); + final_boundary.reserve(final_boundary.size() + outer_boundary.size()); + final_boundary.insert(final_boundary.begin(), outer_boundary.begin(), outer_boundary.end()); + final_boundary = union_ex(final_boundary); + // Collect all top layers that will not be crossed. polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) From ef9de07740622e238e21d4c6a795eb9765408a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 22 Oct 2020 06:20:38 +0200 Subject: [PATCH 18/35] Disabling wipe for avoid crossing perimeters --- src/libslic3r/GCode.cpp | 9 +- src/libslic3r/GCode.hpp | 1 + .../GCode/AvoidCrossingPerimeters.cpp | 105 +++++++++++++----- .../GCode/AvoidCrossingPerimeters.hpp | 36 +++++- 4 files changed, 119 insertions(+), 32 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f152edebf..f1c8f5bbf 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2662,14 +2662,16 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string travel.append(point); // check whether a straight travel move would need retraction - bool needs_retraction = this->needs_retraction(travel, role); + bool needs_retraction = this->needs_retraction(travel, role); + // check whether wipe could be disabled without causing visible stringing + bool could_be_wipe_disabled = false; // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a // multi-hop travel path inside the configuration space if (needs_retraction && m_config.avoid_crossing_perimeters && ! m_avoid_crossing_perimeters.disable_once) { - travel = m_avoid_crossing_perimeters.travel_to(*this, point); + travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); // check again whether the new travel path still needs a retraction needs_retraction = this->needs_retraction(travel, role); @@ -2683,6 +2685,9 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // generate G-code for the travel move std::string gcode; if (needs_retraction) { + if (m_config.avoid_crossing_perimeters && !m_avoid_crossing_perimeters.disable_once && could_be_wipe_disabled) + m_wipe.reset_path(); + Point last_post_before_retract = this->last_pos(); gcode += this->retract(); // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters. diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 60c1dd9e1..d13ace30b 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -156,6 +156,7 @@ public: const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } GCodeWriter& writer() { return m_writer; } + const GCodeWriter& writer() const { return m_writer; } PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; } // Process a template through the placeholder parser, collect error messages to be reported diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index c5e43a65a..25fe631c2 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -115,6 +115,7 @@ static Matrix2d rotation_by_direction(const Point &direction) static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) { + assert(point_idx < polygon.size()); if (point != polygon.points[point_idx]) return polygon.points[point_idx]; @@ -137,8 +138,9 @@ static Vec2d three_points_inward_normal(const Point &left, const Point &middle, normal_2.normalize(); return (normal_1 + normal_2).normalized(); -}; +} +// Compute normal of the polygon's vertex in an inward direction static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) { const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); @@ -149,12 +151,13 @@ static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size return three_points_inward_normal(left, middle, right); } -// Compute offset of polygon's in a direction inward normal +// Compute offset of point_idx of the polygon in a direction of inward normal static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) { return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); } +// Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx). static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const int offset) { const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); @@ -243,6 +246,15 @@ static coord_t get_perimeter_spacing_external(const Layer &layer) return perimeter_spacing; } +// Check if anyone of ExPolygons contains whole travel. +template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) +{ + for (const ExPolygon &ex_polygon : ex_polygons) + if (ex_polygon.contains(travel)) return true; + + return false; +} + ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) { const coord_t perimeter_spacing = get_perimeter_spacing(layer); @@ -321,6 +333,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) Polygons contours; Polygons holes; + contours.reserve(boundary.size()); for (ExPolygon &poly : boundary) { contours.emplace_back(poly.contour); append(holes, poly.holes); @@ -429,10 +442,11 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr return optimized_comb_path; } -Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end) +size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + Polyline *result_out) { const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); @@ -496,12 +510,12 @@ Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &bounda // Append the nearest intersection into the path size_t left_idx = intersection_first.line_idx; size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); + // Offset of the polygon's point using get_middle_point_offset is used to simplify calculation of intersection between boundary result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point); // Append the path around the border into the path - // Offset of the polygon's point is used to simplify calculation of intersection between boundary if (shortest_direction == Direction::Forward) for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) @@ -524,10 +538,48 @@ Polyline AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &bounda } result.append(end); - return simplify_travel(edge_grid, result); + if(!intersections.empty()) { + result = simplify_travel(edge_grid, result); + } + + append(result_out->points, result.points); + return intersections.size(); } -Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point) +bool AvoidCrossingPerimeters2::needs_wipe(const GCode & gcodegen, + const Line & original_travel, + const Polyline &result_travel, + const size_t intersection_count) +{ + bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; + bool wipe_needed = false; + + // If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely + // outside the object. + if (intersection_count > 0) { + // The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test. + // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. + if (z_lift_enabled) { + if (any_expolygon_contains(m_slice, original_travel)) { + // Check if original_travel and are not same result_travel + if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { + wipe_needed = false; + } else { + wipe_needed = !any_expolygon_contains(m_slice, result_travel); + } + } else { + wipe_needed = true; + } + } else { + wipe_needed = !any_expolygon_contains(m_slice, result_travel); + } + } + + return wipe_needed; +} + +// Plan travel, which avoids perimeter crossings by following the boundaries of the layer. +Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) { // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). // Otherwise perform the path planning in the coordinate system of the active object. @@ -536,14 +588,16 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point Point start = gcodegen.last_pos() + scaled_origin; Point end = point + scaled_origin; Polyline result; + size_t travel_intersection_count = 0; if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { - result = Polyline({start, end}); + result = Polyline({start, end}); + travel_intersection_count = 0; } else { auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); if (use_external) - result = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped); + travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, &result); else - result = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped); + travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, &result); } result.points.front() = start; @@ -554,33 +608,32 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point if ((max_detour_length > 0) && ((result.length() - travel.length()) > max_detour_length)) { result = Polyline({start, end}); } - if (use_external) + if (use_external) { result.translate(-scaled_origin); + *could_be_wipe_disabled = false; + } else + *could_be_wipe_disabled = !needs_wipe(gcodegen, travel, result, travel_intersection_count); + return result; } void AvoidCrossingPerimeters2::init_layer(const Layer &layer) { + m_slice.clear(); m_boundaries.clear(); m_boundaries_external.clear(); - ExPolygons boundaries = get_boundary(layer); - ExPolygons boundaries_external = get_boundary_external(layer); + for (const LayerRegion *layer_region : layer.regions()) + append(m_slice, (ExPolygons) layer_region->slices); - m_bbox = get_extents(boundaries); + m_boundaries = to_polygons(get_boundary(layer)); + m_boundaries_external = to_polygons(get_boundary_external(layer)); + + m_bbox = get_extents(m_boundaries); m_bbox.offset(SCALED_EPSILON); - m_bbox_external = get_extents(boundaries_external); + m_bbox_external = get_extents(m_boundaries_external); m_bbox_external.offset(SCALED_EPSILON); - for (const ExPolygon &ex_poly : boundaries) { - m_boundaries.emplace_back(ex_poly.contour); - append(m_boundaries, ex_poly.holes); - } - for (const ExPolygon &ex_poly : boundaries_external) { - m_boundaries_external.emplace_back(ex_poly.contour); - append(m_boundaries_external, ex_poly.holes); - } - m_grid.set_bbox(m_bbox); m_grid.create(m_boundaries, scale_(1.)); m_grid_external.set_bbox(m_bbox_external); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index bc1f79654..e288a5b06 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -37,11 +37,17 @@ public: m_external_mp.reset(); m_layer_mp.reset(); } - void init_external_mp(const Print &print); - void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } + virtual void init_external_mp(const Print &print); + virtual void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } virtual Polyline travel_to(const GCode &gcodegen, const Point &point); + virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) + { + *could_be_wipe_disabled = true; + return this->travel_to(gcodegen, point); + } + protected: // For initializing the regions to avoid. static Polygons collect_contours_all_layers(const PrintObjectPtrs &objects); @@ -55,9 +61,13 @@ class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters protected: struct Intersection { + // Index of the polygon containing this point of intersection. size_t border_idx; + // Index of the line on the polygon containing this point of intersection. size_t line_idx; + // Point of intersection projected on the travel path. Point point_transformed; + // Point of intersection. Point point; Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) @@ -78,11 +88,19 @@ private: static Polyline simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel); - static Polyline avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end); + static size_t avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end, Polyline *result_out); + bool needs_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count); + + // Slice of layer with elephant foot compensation + ExPolygons m_slice; + // Collection of boundaries used for detection of crossing perimetrs for travels inside object Polygons m_boundaries; + // Collection of boundaries used for detection of crossing perimetrs for travels outside object Polygons m_boundaries_external; + // Bounding box of m_boundaries BoundingBox m_bbox; + // Bounding box of m_boundaries_external BoundingBox m_bbox_external; EdgeGrid::Grid m_grid; EdgeGrid::Grid m_grid_external; @@ -92,7 +110,17 @@ public: virtual ~AvoidCrossingPerimeters2() = default; - virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override; + // Used for disabling unnecessary calling collect_contours_all_layers + virtual void init_external_mp(const Print &print) override {}; + virtual void init_layer_mp(const ExPolygons &islands) override {}; + + virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override + { + bool could_be_wipe_disabled; + return this->travel_to(gcodegen, point, &could_be_wipe_disabled); + } + + virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *needs_wipe) override; void init_layer(const Layer &layer); }; From c00c7eaed36af04c4bf4d9dad9833116fcdad759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 28 Oct 2020 01:52:50 +0100 Subject: [PATCH 19/35] Rework of outer borders to reduce unnecessary detours along the border. The resulting path now contains all intersection with borders, which allows eliminating more unnecessary detours and more simplify the path. --- .../GCode/AvoidCrossingPerimeters.cpp | 204 ++++++++++++------ .../GCode/AvoidCrossingPerimeters.hpp | 2 +- 2 files changed, 135 insertions(+), 71 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 25fe631c2..e05b5b0ff 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -255,10 +255,43 @@ template static bool any_expolygon_contains(const ExPolygons &ex_polygo return false; } +static std::pair split_expolygon(const ExPolygons &ex_polygons) +{ + Polygons contours, holes; + contours.reserve(ex_polygons.size()); + holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), 0, + [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); + for (const ExPolygon &ex_poly : ex_polygons) { + contours.emplace_back(ex_poly.contour); + append(holes, ex_poly.holes); + } + return std::make_pair(std::move(contours), std::move(holes)); +} + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const Polyline &result_travel, + const std::vector &intersections, + const std::string &path) +{ + BoundingBox bbox = get_extents(boundary); + ::Slic3r::SVG svg(path, bbox); + svg.draw_outline(boundary, "green"); + svg.draw(original_travel, "blue"); + svg.draw(result_travel, "red"); + svg.draw(original_travel.a, "black"); + svg.draw(original_travel.b, "grey"); + + for (const AvoidCrossingPerimeters2::Intersection &intersection : intersections) + svg.draw(intersection.point, "lightseagreen"); +} +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) { const coord_t perimeter_spacing = get_perimeter_spacing(layer); - const coord_t offset = perimeter_spacing / 2; + const coord_t perimeter_offset = perimeter_spacing / 2; size_t polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) polygons_count += layer_region->slices.surfaces.size(); @@ -269,25 +302,31 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); boundary = union_ex(boundary); - ExPolygons perimeter_boundary = offset_ex(boundary, -offset); - ExPolygons final_boundary; + ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); + ExPolygons result_boundary; if (perimeter_boundary.size() != boundary.size()) { - // If any part of the polygon is missing after shrinking, the boundary of slice is used instead. - ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, offset_ex(perimeter_boundary, offset + SCALED_EPSILON / 2)), - offset + SCALED_EPSILON); - perimeter_boundary = offset_ex(perimeter_boundary, offset); + // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, + offset_ex(perimeter_boundary, perimeter_offset + SCALED_EPSILON / 2)), + perimeter_offset + SCALED_EPSILON); + perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size()); - perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary)); + perimeter_boundary.insert(perimeter_boundary.end(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); + // By calling intersection_ex some artifacts arose by previous operations are removed. + result_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary)); } else { - final_boundary = std::move(perimeter_boundary); + result_boundary = std::move(perimeter_boundary); } + auto [contours, holes] = split_expolygon(boundary); // Add an outer boundary to avoid crossing perimeters from supports - ExPolygons outer_boundary = diff_ex(offset_ex(boundary, 2 * perimeter_spacing), offset_ex(boundary, 2 * perimeter_spacing - offset)); - final_boundary.reserve(final_boundary.size() + outer_boundary.size()); - final_boundary.insert(final_boundary.begin(), outer_boundary.begin(), outer_boundary.end()); - final_boundary = union_ex(final_boundary); + ExPolygons outer_boundary = union_ex( + diff(static_cast(Geometry::convex_hull(offset(contours, 2 * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); + ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); + result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + result_boundary = union_ex(result_boundary); // Collect all top layers that will not be crossed. polygons_count = 0; @@ -303,15 +342,18 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); top_layer_polygons = union_ex(top_layer_polygons); - return diff_ex(final_boundary, offset_ex(top_layer_polygons, -offset)); + return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); } - return final_boundary; + return result_boundary; } ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) { - ExPolygons boundary; + const coord_t perimeter_spacing = get_perimeter_spacing_external(layer); + const coord_t perimeter_offset = perimeter_spacing / 2; + ExPolygons boundary; + // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". for (const PrintObject *object : layer.object()->print()->objects()) { ExPolygons polygons_per_obj; for (Layer *l : object->layers()) @@ -327,24 +369,19 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); } } - - const coord_t perimeter_spacing = get_perimeter_spacing_external(layer); - const coord_t perimeter_offset = perimeter_spacing / 2; - - Polygons contours; - Polygons holes; - contours.reserve(boundary.size()); - for (ExPolygon &poly : boundary) { - contours.emplace_back(poly.contour); - append(holes, poly.holes); - } - - ExPolygons final_boundary = union_ex(diff(offset(contours, perimeter_spacing * 3), offset(contours, 3 * perimeter_spacing - perimeter_offset))); + boundary = union_ex(boundary); + auto [contours, holes] = split_expolygon(boundary); + // Polygons in which is possible traveling without crossing perimeters of another object. + // A convex hull allows removing unnecessary detour caused by following the boundary of the object. + ExPolygons result_boundary = union_ex( + diff(static_cast(Geometry::convex_hull(offset(contours, 2 * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); - final_boundary.reserve(final_boundary.size() + holes_boundary.size()); - final_boundary.insert(final_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - final_boundary = union_ex(final_boundary); - return final_boundary; + result_boundary.reserve(result_boundary.size() + holes_boundary.size()); + result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + result_boundary = union_ex(result_boundary); + return result_boundary; } // Returns a direction of the shortest path along the polygon boundary @@ -409,12 +446,12 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr bool intersect = false; } visitor(edge_grid); - Polyline optimized_comb_path; - optimized_comb_path.points.reserve(travel.points.size()); - optimized_comb_path.points.emplace_back(travel.points.front()); + Polyline simplified_path; + simplified_path.points.reserve(travel.points.size()); + simplified_path.points.emplace_back(travel.points.front()); // Try to skip some points in the path. - for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) { + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { const Point ¤t_point = travel.points[point_idx - 1]; Point next = travel.points[point_idx]; @@ -436,10 +473,10 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr } } - optimized_comb_path.append(next); + simplified_path.append(next); } - return optimized_comb_path; + return simplified_path; } size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, @@ -502,45 +539,72 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari Polyline result; result.append(start); for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { + // The entry point to the boundary polygon const Intersection &intersection_first = *it_first; - for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) { + // Skip the it_first from the search for the farthest exit point from the boundary polygon + auto it_last_item = std::make_reverse_iterator(it_first) - 1; + // Search for the farthest intersection different from it_first but with the same border_idx + auto it_second_r = std::find_if(intersections.rbegin(), it_last_item, [&intersection_first](const Intersection &intersection) { + return intersection_first.border_idx == intersection.border_idx; + }); + + // Append the first intersection into the path + size_t left_idx = intersection_first.line_idx; + size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); + // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the + // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the + // appended point will be inside the polygon and not on the polygon border. + result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); + + // Check if intersection line also exit the boundary polygon + if (it_second_r != it_last_item) { + // Transform reverse iterator to forward + auto it_second = (it_second_r.base() - 1); + // The exit point from the boundary polygon const Intersection &intersection_second = *it_second; - if (intersection_first.border_idx == intersection_second.border_idx) { - Lines border_lines = boundaries[intersection_first.border_idx].lines(); - // Append the nearest intersection into the path - size_t left_idx = intersection_first.line_idx; - size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); - // Offset of the polygon's point using get_middle_point_offset is used to simplify calculation of intersection between boundary - result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); + Lines border_lines = boundaries[intersection_first.border_idx].lines(); - Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, - intersection_first.point, intersection_second.point); - // Append the path around the border into the path - if (shortest_direction == Direction::Forward) - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) - result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], - (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); - else - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); + Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point); + // Append the path around the border into the path + if (shortest_direction == Direction::Forward) + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) + result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); + else + for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) + result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); - // Append the farthest intersection into the path - left_idx = intersection_second.line_idx; - right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); - result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); - // Skip intersections in between - it_first = (it_second - 1); - break; - } + // Append the farthest intersection into the path + left_idx = intersection_second.line_idx; + right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); + result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); + // Skip intersections in between + it_first = it_second; } } result.append(end); - if(!intersections.empty()) { - result = simplify_travel(edge_grid, result); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, travel_line_orig, result, intersections, + debug_out_path("AvoidCrossingPerimeters-initial-%d.svg", iRun++)); } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + if(!intersections.empty()) + result = simplify_travel(edge_grid, result); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, travel_line_orig, result, intersections, + debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ append(result_out->points, result.points); return intersections.size(); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index e288a5b06..01f844de4 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -58,7 +58,7 @@ protected: class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters { -protected: +public: struct Intersection { // Index of the polygon containing this point of intersection. From 266e6dee5d1f0adfaed747e301ed3e8955af83b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 28 Oct 2020 12:57:45 +0100 Subject: [PATCH 20/35] Fix compiler warnings --- .../GCode/AvoidCrossingPerimeters.cpp | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index e05b5b0ff..9a2324395 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -119,7 +119,7 @@ static Point find_first_different_vertex(const Polygon &polygon, const size_t po if (point != polygon.points[point_idx]) return polygon.points[point_idx]; - int line_idx = point_idx; + int line_idx = int(point_idx); if (forward) for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); else @@ -158,7 +158,7 @@ static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t poin } // Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx). -static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const int offset) +static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const coord_t offset) { const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); @@ -203,33 +203,33 @@ static std::pair clamp_endpoints_by_bounding_box(const BoundingBox return std::make_pair(start_clamped, end_clamped); } -static inline coord_t get_default_perimeter_spacing(const Print &print) +static inline float get_default_perimeter_spacing(const Print &print) { const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; - return scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end())); + return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); } -static coord_t get_perimeter_spacing(const Layer &layer) +static float get_perimeter_spacing(const Layer &layer) { - size_t regions_count = 0; - coord_t perimeter_spacing = 0; + size_t regions_count = 0; + float perimeter_spacing = 0.f; for (const LayerRegion *layer_region : layer.regions()) { perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); ++regions_count; } - assert(perimeter_spacing >= 0); + assert(perimeter_spacing >= 0.f); if (regions_count != 0) - perimeter_spacing /= regions_count; + perimeter_spacing /= float(regions_count); else perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); return perimeter_spacing; } -static coord_t get_perimeter_spacing_external(const Layer &layer) +static float get_perimeter_spacing_external(const Layer &layer) { - size_t regions_count = 0; - coord_t perimeter_spacing = 0; + size_t regions_count = 0; + float perimeter_spacing = 0.f; for (const PrintObject *object : layer.object()->print()->objects()) for (Layer *l : object->layers()) if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) @@ -238,9 +238,9 @@ static coord_t get_perimeter_spacing_external(const Layer &layer) ++regions_count; } - assert(perimeter_spacing >= 0); + assert(perimeter_spacing >= 0.f); if (regions_count != 0) - perimeter_spacing /= regions_count; + perimeter_spacing /= float(regions_count); else perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); return perimeter_spacing; @@ -290,9 +290,9 @@ static void export_travel_to_svg(const Polygons ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) { - const coord_t perimeter_spacing = get_perimeter_spacing(layer); - const coord_t perimeter_offset = perimeter_spacing / 2; - size_t polygons_count = 0; + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + size_t polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) polygons_count += layer_region->slices.surfaces.size(); @@ -307,8 +307,8 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) if (perimeter_boundary.size() != boundary.size()) { // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, - offset_ex(perimeter_boundary, perimeter_offset + SCALED_EPSILON / 2)), - perimeter_offset + SCALED_EPSILON); + offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), + perimeter_offset + float(SCALED_EPSILON)); perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size()); perimeter_boundary.insert(perimeter_boundary.end(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); @@ -321,7 +321,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) auto [contours, holes] = split_expolygon(boundary); // Add an outer boundary to avoid crossing perimeters from supports ExPolygons outer_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2 * perimeter_spacing))), + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), offset(contours, perimeter_spacing + perimeter_offset))); result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); @@ -350,9 +350,9 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) { - const coord_t perimeter_spacing = get_perimeter_spacing_external(layer); - const coord_t perimeter_offset = perimeter_spacing / 2; - ExPolygons boundary; + const float perimeter_spacing = get_perimeter_spacing_external(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + ExPolygons boundary; // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". for (const PrintObject *object : layer.object()->print()->objects()) { ExPolygons polygons_per_obj; @@ -374,7 +374,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) // Polygons in which is possible traveling without crossing perimeters of another object. // A convex hull allows removing unnecessary detour caused by following the boundary of the object. ExPolygons result_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2 * perimeter_spacing))), + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), offset(contours, perimeter_spacing + perimeter_offset))); // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); @@ -398,7 +398,7 @@ AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direc if (index >= int(lines.size())) index = 0; else if (index < 0) - index = lines.size() - 1; + index = int(lines.size()) - 1; return index; }; @@ -554,7 +554,7 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the // appended point will be inside the polygon and not on the polygon border. - result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON)); + result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON))); // Check if intersection line also exit the boundary polygon if (it_second_r != it_last_item) { @@ -567,19 +567,19 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point); // Append the path around the border into the path if (shortest_direction == Direction::Forward) - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], - (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), SCALED_EPSILON)); + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON))); else - for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx); + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, SCALED_EPSILON)); + result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON))); // Append the farthest intersection into the path left_idx = intersection_second.line_idx; right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); - result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON)); + result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON))); // Skip intersections in between it_first = it_second; } @@ -699,9 +699,9 @@ void AvoidCrossingPerimeters2::init_layer(const Layer &layer) m_bbox_external.offset(SCALED_EPSILON); m_grid.set_bbox(m_bbox); - m_grid.create(m_boundaries, scale_(1.)); + m_grid.create(m_boundaries, coord_t(scale_(1.))); m_grid_external.set_bbox(m_bbox_external); - m_grid_external.create(m_boundaries_external, scale_(1.)); + m_grid_external.create(m_boundaries_external, coord_t(scale_(1.))); } } // namespace Slic3r From 3db66af71686e08379e94201034685d05279893a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Wed, 28 Oct 2020 13:21:06 +0100 Subject: [PATCH 21/35] Fix another compiler warning --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 9a2324395..07551d055 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -259,7 +259,7 @@ static std::pair split_expolygon(const ExPolygons &ex_polygo { Polygons contours, holes; contours.reserve(ex_polygons.size()); - holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), 0, + holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); for (const ExPolygon &ex_poly : ex_polygons) { contours.emplace_back(ex_poly.contour); From 9936b8e34e280216543c3ce849816eb1cdc10b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 15 Nov 2020 19:46:17 +0100 Subject: [PATCH 22/35] Add missing includes --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 14 ++++++++++---- src/libslic3r/GCode/AvoidCrossingPerimeters.hpp | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 07551d055..5e3a091a1 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -12,6 +12,11 @@ #include "AvoidCrossingPerimeters.hpp" #include +#include +#include + +#include +#include namespace Slic3r { @@ -457,7 +462,7 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr visitor.pt_current = ¤t_point; - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); point_idx_2++) { + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { if (travel.points[point_idx_2] == current_point) { next = travel.points[point_idx_2]; point_idx = point_idx_2; @@ -610,7 +615,7 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari return intersections.size(); } -bool AvoidCrossingPerimeters2::needs_wipe(const GCode & gcodegen, +bool AvoidCrossingPerimeters2::need_wipe(const GCode & gcodegen, const Line & original_travel, const Polyline &result_travel, const size_t intersection_count) @@ -625,7 +630,8 @@ bool AvoidCrossingPerimeters2::needs_wipe(const GCode & gcodegen, // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. if (z_lift_enabled) { if (any_expolygon_contains(m_slice, original_travel)) { - // Check if original_travel and are not same result_travel + // Check if original_travel and result_travel are not same. + // If both are the same, then it is possible to skip testing of result_travel if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { wipe_needed = false; } else { @@ -676,7 +682,7 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point result.translate(-scaled_origin); *could_be_wipe_disabled = false; } else - *could_be_wipe_disabled = !needs_wipe(gcodegen, travel, result, travel_intersection_count); + *could_be_wipe_disabled = !need_wipe(gcodegen, travel, result, travel_intersection_count); return result; } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index 01f844de4..c73fda82d 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -90,7 +90,7 @@ private: static size_t avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end, Polyline *result_out); - bool needs_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count); + bool need_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count); // Slice of layer with elephant foot compensation ExPolygons m_slice; @@ -120,7 +120,7 @@ public: return this->travel_to(gcodegen, point, &could_be_wipe_disabled); } - virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *needs_wipe) override; + virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) override; void init_layer(const Layer &layer); }; From c702b3b71dfc94d71dc642f37d9b8b9e1c005b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 16 Nov 2020 13:43:15 +0100 Subject: [PATCH 23/35] Add heuristics for removing unnecessary detours --- .../GCode/AvoidCrossingPerimeters.cpp | 233 ++++++++++++------ .../GCode/AvoidCrossingPerimeters.hpp | 71 +++++- 2 files changed, 228 insertions(+), 76 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 5e3a091a1..4e45ed28b 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -9,6 +9,7 @@ #include "../Polygon.hpp" #include "../ExPolygon.hpp" #include "../ClipperUtils.hpp" +#include "../SVG.hpp" #include "AvoidCrossingPerimeters.hpp" #include @@ -273,6 +274,23 @@ static std::pair split_expolygon(const ExPolygons &ex_polygo return std::make_pair(std::move(contours), std::move(holes)); } +static Polyline to_polyline(const std::vector &travel) +{ + Polyline result; + result.points.reserve(travel.size()); + for (const AvoidCrossingPerimeters2::TravelPoint &t_point : travel) + result.append(t_point.point); + return result; +} + +static double travel_length(const std::vector &travel) { + double total_length = 0; + for (size_t idx = 1; idx < travel.size(); ++idx) + total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); + + return total_length; +} + #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT static void export_travel_to_svg(const Polygons &boundary, const Line &original_travel, @@ -291,6 +309,15 @@ static void export_travel_to_svg(const Polygons for (const AvoidCrossingPerimeters2::Intersection &intersection : intersections) svg.draw(intersection.point, "lightseagreen"); } + +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const std::vector &result_travel, + const std::vector &intersections, + const std::string &path) +{ + export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path); +} #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) @@ -420,7 +447,10 @@ AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direc return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel) +std::vector AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics) { struct Visitor { @@ -451,44 +481,131 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr bool intersect = false; } visitor(edge_grid); - Polyline simplified_path; - simplified_path.points.reserve(travel.points.size()); - simplified_path.points.emplace_back(travel.points.front()); + std::vector simplified_path; + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); // Try to skip some points in the path. for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { - const Point ¤t_point = travel.points[point_idx - 1]; - Point next = travel.points[point_idx]; + const Point ¤t_point = travel[point_idx - 1].point; + TravelPoint next = travel[point_idx]; visitor.pt_current = ¤t_point; for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - if (travel.points[point_idx_2] == current_point) { - next = travel.points[point_idx_2]; + if (travel[point_idx_2].point == current_point) { + next = travel[point_idx_2]; point_idx = point_idx_2; continue; } - visitor.pt_next = &travel.points[point_idx_2]; + visitor.pt_next = &travel[point_idx_2].point; edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); // Check if deleting point causes crossing a boundary if (!visitor.intersect) { - next = travel.points[point_idx_2]; + next = travel[point_idx_2]; point_idx = point_idx_2; } } - simplified_path.append(next); + simplified_path.emplace_back(next); + } + + if(use_heuristics) { + simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); + std::reverse(simplified_path.begin(),simplified_path.end()); + simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); + std::reverse(simplified_path.begin(),simplified_path.end()); } return simplified_path; } -size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - Polyline *result_out) +std::vector AvoidCrossingPerimeters2::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries) +{ + std::vector simplified_path; + std::vector intersections; + AllIntersectionsVisitor visitor(edge_grid, intersections); + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + // Skip all indexes on the same polygon + while (point_idx < travel.size() && travel[point_idx - 1].border_idx == travel[point_idx].border_idx) { + simplified_path.emplace_back(travel[point_idx]); + point_idx++; + } + + if (point_idx < travel.size()) { + const TravelPoint ¤t = travel[point_idx - 1]; + const TravelPoint &next = travel[point_idx]; + TravelPoint new_next = next; + size_t new_point_idx = point_idx; + double path_length = (next.point - current.point).cast().norm(); + double new_path_shorter_by = 0.; + size_t border_idx_change_count = 0; + std::vector shortcut; + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + const TravelPoint &possible_new_next = travel[point_idx_2]; + if (travel[point_idx_2 - 1].border_idx != travel[point_idx_2].border_idx) + border_idx_change_count++; + + if (border_idx_change_count >= 2) + break; + + path_length += (possible_new_next.point - travel[point_idx_2 - 1].point).cast().norm(); + double shortcut_length = (possible_new_next.point - current.point).cast().norm(); + if ((path_length - shortcut_length) <= scale_(10.0)) + continue; + + intersections.clear(); + visitor.reset(); + visitor.travel_line.a = current.point; + visitor.travel_line.b = possible_new_next.point; + visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector()); + edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); + if (!intersections.empty()) { + std::sort(intersections.begin(), intersections.end()); + size_t last_border_idx_count = 0; + for (const Intersection &intersection : intersections) + if (intersection.border_idx == int(possible_new_next.border_idx)) + ++last_border_idx_count; + + if (last_border_idx_count > 0) + continue; + + std::vector possible_shortcut; + avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut); + double shortcut_travel = travel_length(possible_shortcut); + if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) { + new_path_shorter_by = path_length - shortcut_travel; + shortcut = possible_shortcut; + new_next = possible_new_next; + new_point_idx = point_idx_2; + } + } + } + + if (!shortcut.empty()) { + assert(shortcut.size() >= 2); + simplified_path.insert(simplified_path.end(), shortcut.begin() + 1, shortcut.end() - 1); + point_idx = new_point_idx; + } + + simplified_path.emplace_back(new_next); + } + } + + return simplified_path; +} + +size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + const bool use_heuristics, + std::vector *result_out) { const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); @@ -499,50 +616,14 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari std::vector intersections; { - struct Visitor - { - Visitor(const EdgeGrid::Grid & grid, - std::vector &intersections, - const Matrix2d & transform_to_x_axis, - const Line & travel_line) - : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) - {} - - bool operator()(coord_t iy, coord_t ix) - { - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; - ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - - Point intersection_point; - if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && - intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { - intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); - intersection_set.insert(*it_contour_and_segment); - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - std::vector &intersections; - const Matrix2d &transform_to_x_axis; - const Line &travel_line; - std::unordered_set, boost::hash>> intersection_set; - } visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); - + AllIntersectionsVisitor visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); edge_grid.visit_cells_intersecting_line(start, end, visitor); } std::sort(intersections.begin(), intersections.end()); - Polyline result; - result.append(start); + std::vector result; + result.push_back({start, -1}); for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { // The entry point to the boundary polygon const Intersection &intersection_first = *it_first; @@ -559,7 +640,7 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the // appended point will be inside the polygon and not on the polygon border. - result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON))); + result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); // Check if intersection line also exit the boundary polygon if (it_second_r != it_last_item) { @@ -574,23 +655,23 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari if (shortest_direction == Direction::Forward) for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) - result.append(get_polygon_vertex_offset(boundaries[intersection_first.border_idx], - (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON))); + result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); else for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) - result.append(get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON))); + result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); // Append the farthest intersection into the path left_idx = intersection_second.line_idx; right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); - result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON))); + result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)), int(intersection_second.border_idx)}); // Skip intersections in between it_first = it_second; } } - result.append(end); + result.push_back({end, -1}); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { @@ -601,7 +682,7 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ if(!intersections.empty()) - result = simplify_travel(edge_grid, result); + result = simplify_travel(edge_grid, result, boundaries, use_heuristics); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { @@ -611,7 +692,8 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - append(result_out->points, result.points); + result_out->reserve(result_out->size() + result.size()); + result_out->insert(result_out->end(), result.begin(), result.end()); return intersections.size(); } @@ -657,34 +739,37 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); Point start = gcodegen.last_pos() + scaled_origin; Point end = point + scaled_origin; - Polyline result; + Polyline result_pl; size_t travel_intersection_count = 0; if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { - result = Polyline({start, end}); + result_pl = Polyline({start, end}); travel_intersection_count = 0; } else { + std::vector result; auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); if (use_external) - travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, &result); + travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, false, &result); else - travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, &result); + travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, false, &result); + + result_pl = to_polyline(result); } - result.points.front() = start; - result.points.back() = end; + result_pl.points.front() = start; + result_pl.points.back() = end; Line travel(start, end); double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); - if ((max_detour_length > 0) && ((result.length() - travel.length()) > max_detour_length)) { - result = Polyline({start, end}); + if ((max_detour_length > 0) && ((result_pl.length() - travel.length()) > max_detour_length)) { + result_pl = Polyline({start, end}); } if (use_external) { - result.translate(-scaled_origin); + result_pl.translate(-scaled_origin); *could_be_wipe_disabled = false; } else - *could_be_wipe_disabled = !need_wipe(gcodegen, travel, result, travel_intersection_count); + *could_be_wipe_disabled = !need_wipe(gcodegen, travel, result_pl, travel_intersection_count); - return result; + return result_pl; } void AvoidCrossingPerimeters2::init_layer(const Layer &layer) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index c73fda82d..f1a5324f4 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -5,6 +5,9 @@ #include "../ExPolygon.hpp" #include "../EdgeGrid.hpp" +#include +#include + namespace Slic3r { // Forward declarations. @@ -76,6 +79,58 @@ public: inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } }; + struct TravelPoint + { + Point point; + // Index of the polygon containing this point. A negative value indicates that the point is not on any border + int border_idx; + }; + + struct AllIntersectionsVisitor + { + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) + : grid(grid), intersections(intersections) + {} + + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, + std::vector &intersections, + const Matrix2d &transform_to_x_axis, + const Line &travel_line) + : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + {} + + void reset() { + intersection_set.clear(); + } + + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; + ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + + Point intersection_point; + if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { + intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, + (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); + intersection_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + std::vector &intersections; + Matrix2d transform_to_x_axis; + Line travel_line; + std::unordered_set, boost::hash>> intersection_set; + }; + enum class Direction { Forward, Backward }; private: @@ -86,9 +141,21 @@ private: static Direction get_shortest_direction( const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last); - static Polyline simplify_travel(const EdgeGrid::Grid &edge_grid, const Polyline &travel); + static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics); - static size_t avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, const Point &start, const Point &end, Polyline *result_out); + static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries); + + static size_t avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &grid, + const Point &start, + const Point &end, + const bool use_heuristics, + std::vector *result_out); bool need_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count); From 49ce613be7b64426b29ff1cbca6173a29b8da94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 16 Nov 2020 14:37:42 +0100 Subject: [PATCH 24/35] Enable previous heuristics which was disabled by mistake --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 4e45ed28b..4aafa77fb 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -569,7 +569,7 @@ std::vector AvoidCrossingPerimeters2::sim std::sort(intersections.begin(), intersections.end()); size_t last_border_idx_count = 0; for (const Intersection &intersection : intersections) - if (intersection.border_idx == int(possible_new_next.border_idx)) + if (int(intersection.border_idx) == possible_new_next.border_idx) ++last_border_idx_count; if (last_border_idx_count > 0) @@ -748,9 +748,9 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point std::vector result; auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); if (use_external) - travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, false, &result); + travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); else - travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, false, &result); + travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); result_pl = to_polyline(result); } From 04c2fde671772600b98e094f9714acc5190b9e4c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Nov 2020 09:33:10 +0100 Subject: [PATCH 25/35] Removed the old motion planner. --- src/libslic3r/CMakeLists.txt | 2 - src/libslic3r/GCode.cpp | 12 +- src/libslic3r/GCode.hpp | 3 +- .../GCode/AvoidCrossingPerimeters.cpp | 169 ++------ .../GCode/AvoidCrossingPerimeters.hpp | 86 ++--- src/libslic3r/MotionPlanner.cpp | 363 ------------------ src/libslic3r/MotionPlanner.hpp | 91 ----- xs/src/perlglue.cpp | 3 - xs/t/18_motionplanner.t | 92 ----- xs/xsp/MotionPlanner.xsp | 15 - xs/xsp/my.map | 12 - xs/xsp/typemap.xspt | 6 - 12 files changed, 67 insertions(+), 787 deletions(-) delete mode 100644 src/libslic3r/MotionPlanner.cpp delete mode 100644 src/libslic3r/MotionPlanner.hpp delete mode 100644 xs/t/18_motionplanner.t delete mode 100644 xs/xsp/MotionPlanner.xsp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 93f947545..4d65de81c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -128,8 +128,6 @@ add_library(libslic3r STATIC CustomGCode.hpp Arrange.hpp Arrange.cpp - MotionPlanner.cpp - MotionPlanner.hpp MultiPoint.cpp MultiPoint.hpp MutablePriorityQueue.hpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f1c8f5bbf..96eff24e7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1140,13 +1140,6 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Set other general things. _write(file, this->preamble()); - // Initialize a motion planner for object-to-object travel moves. - m_avoid_crossing_perimeters.reset(); - if (print.config().avoid_crossing_perimeters.value) { - m_avoid_crossing_perimeters.init_external_mp(print); - print.throw_if_canceled(); - } - // Calculate wiping points if needed DoExport::init_ooze_prevention(print, m_ooze_prevention); print.throw_if_canceled(); @@ -2054,11 +2047,8 @@ void GCode::process_layer( for (InstanceToPrint &instance_to_print : instances_to_print) { m_config.apply(instance_to_print.print_object.config(), true); m_layer = layers[instance_to_print.layer_id].layer(); - if (m_config.avoid_crossing_perimeters) { - m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->lslices, true)); + if (m_config.avoid_crossing_perimeters) m_avoid_crossing_perimeters.init_layer(*m_layer); - } - if (this->config().gcode_label_objects) gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; // When starting a new object, use the external motion planner for the first travel move. diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index d13ace30b..b21f4c97d 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -5,7 +5,6 @@ #include "ExPolygon.hpp" #include "GCodeWriter.hpp" #include "Layer.hpp" -#include "MotionPlanner.hpp" #include "Point.hpp" #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" @@ -299,7 +298,7 @@ private: std::set m_placeholder_parser_failed_templates; OozePrevention m_ooze_prevention; Wipe m_wipe; - AvoidCrossingPerimeters2 m_avoid_crossing_perimeters; + AvoidCrossingPerimeters m_avoid_crossing_perimeters; bool m_enable_loop_clipping; // If enabled, the G-code generator will put following comments at the ends // of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 4aafa77fb..6a211e05b 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -1,7 +1,5 @@ #include "../Layer.hpp" -#include "../MotionPlanner.hpp" #include "../GCode.hpp" -#include "../MotionPlanner.hpp" #include "../EdgeGrid.hpp" #include "../Geometry.hpp" #include "../ShortestPath.hpp" @@ -21,93 +19,6 @@ namespace Slic3r { -void AvoidCrossingPerimeters::init_external_mp(const Print& print) -{ - m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects()))); -} - -// Plan a travel move while minimizing the number of perimeter crossings. -// point is in unscaled coordinates, in the coordinate system of the current active object -// (set by gcodegen.set_origin()). -Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point) -{ - // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). - // Otherwise perform the path planning in the coordinate system of the active object. - bool use_external = this->use_external_mp || this->use_external_mp_once; - Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); - Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())-> - shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); - if (use_external) - result.translate(-scaled_origin); - return result; -} - -// Collect outer contours of all objects over all layers. -// Discard objects only containing thin walls (offset would fail on an empty polygon). -// Used by avoid crossing perimeters feature. -Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects) -{ - Polygons islands; - for (const PrintObject* object : objects) { - // Reducing all the object slices into the Z projection in a logarithimc fashion. - // First reduce to half the number of layers. - std::vector polygons_per_layer((object->layers().size() + 1) / 2); - tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2), - [&object, &polygons_per_layer](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - const Layer* layer1 = object->layers()[i * 2]; - const Layer* layer2 = object->layers()[i * 2 + 1]; - Polygons polys; - polys.reserve(layer1->lslices.size() + layer2->lslices.size()); - for (const ExPolygon& expoly : layer1->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - for (const ExPolygon& expoly : layer2->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer[i] = union_(polys); - } - }); - if (object->layers().size() & 1) { - const Layer* layer = object->layers().back(); - Polygons polys; - polys.reserve(layer->lslices.size()); - for (const ExPolygon& expoly : layer->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer.back() = union_(polys); - } - // Now reduce down to a single layer. - size_t cnt = polygons_per_layer.size(); - while (cnt > 1) { - tbb::parallel_for(tbb::blocked_range(0, cnt / 2), - [&polygons_per_layer](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - Polygons polys; - polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size()); - polygons_append(polys, polygons_per_layer[i * 2]); - polygons_append(polys, polygons_per_layer[i * 2 + 1]); - polygons_per_layer[i * 2] = union_(polys); - } - }); - for (size_t i = 1; i < cnt / 2; ++i) - polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); - if (cnt & 1) - polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); - cnt = (cnt + 1) / 2; - } - // And collect copies of the objects. - for (const PrintInstance& instance : object->instances()) { - // All the layers were reduced to the 1st item of polygons_per_layer. - size_t i = islands.size(); - polygons_append(islands, polygons_per_layer.front()); - for (; i < islands.size(); ++i) - islands[i].translate(instance.shift); - } - } - return islands; -} - // Create a rotation matrix for projection on the given vector static Matrix2d rotation_by_direction(const Point &direction) { @@ -274,16 +185,16 @@ static std::pair split_expolygon(const ExPolygons &ex_polygo return std::make_pair(std::move(contours), std::move(holes)); } -static Polyline to_polyline(const std::vector &travel) +static Polyline to_polyline(const std::vector &travel) { Polyline result; result.points.reserve(travel.size()); - for (const AvoidCrossingPerimeters2::TravelPoint &t_point : travel) + for (const AvoidCrossingPerimeters::TravelPoint &t_point : travel) result.append(t_point.point); return result; } -static double travel_length(const std::vector &travel) { +static double travel_length(const std::vector &travel) { double total_length = 0; for (size_t idx = 1; idx < travel.size(); ++idx) total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); @@ -292,11 +203,11 @@ static double travel_length(const std::vector &intersections, - const std::string &path) +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const Polyline &result_travel, + const std::vector &intersections, + const std::string &path) { BoundingBox bbox = get_extents(boundary); ::Slic3r::SVG svg(path, bbox); @@ -306,21 +217,21 @@ static void export_travel_to_svg(const Polygons svg.draw(original_travel.a, "black"); svg.draw(original_travel.b, "grey"); - for (const AvoidCrossingPerimeters2::Intersection &intersection : intersections) + for (const AvoidCrossingPerimeters::Intersection &intersection : intersections) svg.draw(intersection.point, "lightseagreen"); } -static void export_travel_to_svg(const Polygons &boundary, - const Line &original_travel, - const std::vector &result_travel, - const std::vector &intersections, - const std::string &path) +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const std::vector &result_travel, + const std::vector &intersections, + const std::string &path) { export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ -ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) +ExPolygons AvoidCrossingPerimeters::get_boundary(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; @@ -380,7 +291,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) return result_boundary; } -ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) +ExPolygons AvoidCrossingPerimeters::get_boundary_external(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing_external(layer); const float perimeter_offset = perimeter_spacing / 2.f; @@ -417,11 +328,11 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) } // Returns a direction of the shortest path along the polygon boundary -AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last) +AvoidCrossingPerimeters::Direction AvoidCrossingPerimeters::get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last) { double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); @@ -447,10 +358,10 @@ AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direc return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -std::vector AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics) +std::vector AvoidCrossingPerimeters::simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics) { struct Visitor { @@ -521,9 +432,9 @@ std::vector AvoidCrossingPerimeters2::sim return simplified_path; } -std::vector AvoidCrossingPerimeters2::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries) +std::vector AvoidCrossingPerimeters::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries) { std::vector simplified_path; std::vector intersections; @@ -600,12 +511,12 @@ std::vector AvoidCrossingPerimeters2::sim return simplified_path; } -size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out) +size_t AvoidCrossingPerimeters::avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + const bool use_heuristics, + std::vector *result_out) { const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); @@ -697,10 +608,10 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boun return intersections.size(); } -bool AvoidCrossingPerimeters2::need_wipe(const GCode & gcodegen, - const Line & original_travel, - const Polyline &result_travel, - const size_t intersection_count) +bool AvoidCrossingPerimeters::need_wipe(const GCode & gcodegen, + const Line & original_travel, + const Polyline &result_travel, + const size_t intersection_count) { bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; bool wipe_needed = false; @@ -731,7 +642,7 @@ bool AvoidCrossingPerimeters2::need_wipe(const GCode & gcodegen, } // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. -Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) +Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) { // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). // Otherwise perform the path planning in the coordinate system of the active object. @@ -772,7 +683,7 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point return result_pl; } -void AvoidCrossingPerimeters2::init_layer(const Layer &layer) +void AvoidCrossingPerimeters::init_layer(const Layer &layer) { m_slice.clear(); m_boundaries.clear(); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index f1a5324f4..0f99108f9 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -13,7 +13,6 @@ namespace Slic3r { // Forward declarations. class GCode; class Layer; -class MotionPlanner; class Point; class Print; class PrintObject; @@ -23,44 +22,6 @@ using PrintObjectPtrs = std::vector; class AvoidCrossingPerimeters { -public: - // this flag triggers the use of the external configuration space - bool use_external_mp; - bool use_external_mp_once; // just for the next travel move - - // this flag disables avoid_crossing_perimeters just for the next travel move - // we enable it by default for the first travel move in print - bool disable_once; - - AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {} - virtual ~AvoidCrossingPerimeters() = default; - - void reset() - { - m_external_mp.reset(); - m_layer_mp.reset(); - } - virtual void init_external_mp(const Print &print); - virtual void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point); - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) - { - *could_be_wipe_disabled = true; - return this->travel_to(gcodegen, point); - } - -protected: - // For initializing the regions to avoid. - static Polygons collect_contours_all_layers(const PrintObjectPtrs &objects); - - std::unique_ptr m_external_mp; - std::unique_ptr m_layer_mp; -}; - -class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters -{ public: struct Intersection { @@ -88,14 +49,14 @@ public: struct AllIntersectionsVisitor { - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) : grid(grid), intersections(intersections) {} - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, - std::vector &intersections, - const Matrix2d &transform_to_x_axis, - const Line &travel_line) + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, + std::vector &intersections, + const Matrix2d &transform_to_x_axis, + const Line &travel_line) : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) {} @@ -125,7 +86,7 @@ public: } const EdgeGrid::Grid &grid; - std::vector &intersections; + std::vector &intersections; Matrix2d transform_to_x_axis; Line travel_line; std::unordered_set, boost::hash>> intersection_set; @@ -141,14 +102,14 @@ private: static Direction get_shortest_direction( const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last); - static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics); + static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics); - static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries); + static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries); static size_t avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, @@ -173,24 +134,27 @@ private: EdgeGrid::Grid m_grid_external; public: - AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} + // this flag triggers the use of the external configuration space + bool use_external_mp { false }; + // just for the next travel move + bool use_external_mp_once { false }; + // this flag disables avoid_crossing_perimeters just for the next travel move + // we enable it by default for the first travel move in print + bool disable_once { true }; - virtual ~AvoidCrossingPerimeters2() = default; + AvoidCrossingPerimeters() = default; - // Used for disabling unnecessary calling collect_contours_all_layers - virtual void init_external_mp(const Print &print) override {}; - virtual void init_layer_mp(const ExPolygons &islands) override {}; - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override + Polyline travel_to(const GCode& gcodegen, const Point& point) { bool could_be_wipe_disabled; return this->travel_to(gcodegen, point, &could_be_wipe_disabled); } - virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) override; + Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled); void init_layer(const Layer &layer); }; + } // namespace Slic3r -#endif // slic3r_AvoidCrossingPerimeters_hpp_ \ No newline at end of file +#endif // slic3r_AvoidCrossingPerimeters_hpp_ diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp deleted file mode 100644 index ae50df8f4..000000000 --- a/src/libslic3r/MotionPlanner.cpp +++ /dev/null @@ -1,363 +0,0 @@ -#include "BoundingBox.hpp" -#include "MotionPlanner.hpp" -#include "MutablePriorityQueue.hpp" -#include "Utils.hpp" - -#include // for numeric_limits -#include - -#define BOOST_VORONOI_USE_GMP 1 -#include "boost/polygon/voronoi.hpp" -using boost::polygon::voronoi_builder; -using boost::polygon::voronoi_diagram; - -namespace Slic3r { - -MotionPlanner::MotionPlanner(const ExPolygons &islands) : m_initialized(false) -{ - ExPolygons expp; - for (const ExPolygon &island : islands) { - island.simplify(SCALED_EPSILON, &expp); - for (ExPolygon &island : expp) - m_islands.emplace_back(MotionPlannerEnv(island)); - expp.clear(); - } -} - -void MotionPlanner::initialize() -{ - // prevent initialization of empty BoundingBox - if (m_initialized || m_islands.empty()) - return; - - // loop through islands in order to create inner expolygons and collect their contours. - Polygons outer_holes; - for (MotionPlannerEnv &island : m_islands) { - // Generate the internal env boundaries by shrinking the island - // we'll use these inner rings for motion planning (endpoints of the Voronoi-based - // graph, visibility check) in order to avoid moving too close to the boundaries. - island.m_env = ExPolygonCollection(offset_ex(island.m_island, -MP_INNER_MARGIN)); - // Island contours are holes of our external environment. - outer_holes.push_back(island.m_island.contour); - } - - // Generate a box contour around everyting. - Polygons contour = offset(get_extents(outer_holes).polygon(), +MP_OUTER_MARGIN*2); - assert(contour.size() == 1); - // make expolygon for outer environment - ExPolygons outer = diff_ex(contour, outer_holes); - assert(outer.size() == 1); - // If some of the islands are nested, then the 0th contour is the outer contour due to the order of conversion - // from Clipper data structure into the Slic3r expolygons inside diff_ex(). - m_outer = MotionPlannerEnv(outer.front()); - m_outer.m_env = ExPolygonCollection(diff_ex(contour, offset(outer_holes, +MP_OUTER_MARGIN))); - m_graphs.resize(m_islands.size() + 1); - m_initialized = true; -} - -Polyline MotionPlanner::shortest_path(const Point &from, const Point &to) -{ - // If we have an empty configuration space, return a straight move. - if (m_islands.empty()) - return Polyline(from, to); - - // Are both points in the same island? - int island_idx_from = -1; - int island_idx_to = -1; - int island_idx = -1; - for (MotionPlannerEnv &island : m_islands) { - int idx = &island - m_islands.data(); - if (island.island_contains(from)) - island_idx_from = idx; - if (island.island_contains(to)) - island_idx_to = idx; - if (island_idx_from == idx && island_idx_to == idx) { - // Since both points are in the same island, is a direct move possible? - // If so, we avoid generating the visibility environment. - if (island.m_island.contains(Line(from, to))) - return Polyline(from, to); - // Both points are inside a single island, but the straight line crosses the island boundary. - island_idx = idx; - break; - } - } - - // lazy generation of configuration space. - this->initialize(); - - // Get environment. If the from / to points do not share an island, then they cross an open space, - // therefore island_idx == -1 and env will be set to the environment of the empty space. - const MotionPlannerEnv &env = this->get_env(island_idx); - if (env.m_env.expolygons.empty()) { - // if this environment is empty (probably because it's too small), perform straight move - // and avoid running the algorithms on empty dataset - return Polyline(from, to); - } - - // Now check whether points are inside the environment. - Point inner_from = from; - Point inner_to = to; - - if (island_idx == -1) { - // The end points do not share the same island. In that case some of the travel - // will be likely performed inside the empty space. - // TODO: instead of using the nearest_env_point() logic, we should - // create a temporary graph where we connect 'from' and 'to' to the - // nodes which don't require more than one crossing, and let Dijkstra - // figure out the entire path - this should also replace the call to - // find_node() below - if (island_idx_from != -1) - // The start point is inside some island. Find the closest point at the empty space to start from. - inner_from = env.nearest_env_point(from, to); - if (island_idx_to != -1) - // The start point is inside some island. Find the closest point at the empty space to start from. - inner_to = env.nearest_env_point(to, inner_from); - } - - // Perform a path search either in the open space, or in a common island of from/to. - const MotionPlannerGraph &graph = this->init_graph(island_idx); - // If no path exists without crossing perimeters, returns a straight segment. - Polyline polyline = graph.shortest_path(inner_from, inner_to); - polyline.points.insert(polyline.points.begin(), from); - polyline.points.emplace_back(to); - - { - // grow our environment slightly in order for simplify_by_visibility() - // to work best by considering moves on boundaries valid as well - ExPolygonCollection grown_env(offset_ex(env.m_env.expolygons, float(+SCALED_EPSILON))); - - if (island_idx == -1) { - /* If 'from' or 'to' are not inside our env, they were connected using the - nearest_env_point() search which maybe produce ugly paths since it does not - include the endpoint in the Dijkstra search; the simplify_by_visibility() - call below will not work in many cases where the endpoint is not contained in - grown_env (whose contour was arbitrarily constructed with MP_OUTER_MARGIN, - which may not be enough for, say, including a skirt point). So we prune - the extra points manually. */ - if (! grown_env.contains(from)) { - // delete second point while the line connecting first to third crosses the - // boundaries as many times as the current first to second - while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), (Polygons)grown_env).size() == 1) - polyline.points.erase(polyline.points.begin() + 1); - } - if (! grown_env.contains(to)) - while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), (Polygons)grown_env).size() == 1) - polyline.points.erase(polyline.points.end() - 2); - } - - // Perform some quick simplification (simplify_by_visibility() would make this - // unnecessary, but this is much faster) - polyline.simplify(MP_INNER_MARGIN/10); - - // remove unnecessary vertices - // Note: this is computationally intensive and does not look very necessary - // now that we prune the endpoints with the logic above, - // so we comment it for now until a good test case arises - //polyline.simplify_by_visibility(grown_env); - - /* - SVG svg("shortest_path.svg"); - svg.draw(grown_env.expolygons); - svg.arrows = false; - for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) { - Point a = graph->nodes[it - graph->adjacency_list.begin()]; - for (std::vector::const_iterator n = it->begin(); n != it->end(); ++n) { - Point b = graph->nodes[n->target]; - svg.draw(Line(a, b)); - } - } - svg.arrows = true; - svg.draw(from); - svg.draw(inner_from, "red"); - svg.draw(to); - svg.draw(inner_to, "red"); - svg.draw(polyline, "red"); - svg.Close(); - */ - } - - return polyline; -} - -const MotionPlannerGraph& MotionPlanner::init_graph(int island_idx) -{ - // 0th graph is the graph for m_outer. Other graphs are 1 indexed. - MotionPlannerGraph *graph = m_graphs[island_idx + 1].get(); - if (graph == nullptr) { - // If this graph doesn't exist, initialize it. - m_graphs[island_idx + 1] = make_unique(); - graph = m_graphs[island_idx + 1].get(); - - /* We don't add polygon boundaries as graph edges, because we'd need to connect - them to the Voronoi-generated edges by recognizing coinciding nodes. */ - - typedef voronoi_diagram VD; - VD vd; - // Mapping between Voronoi vertices and graph nodes. - std::map vd_vertices; - // get boundaries as lines - const MotionPlannerEnv &env = this->get_env(island_idx); - Lines lines = env.m_env.lines(); - boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd); - // traverse the Voronoi diagram and generate graph nodes and edges - for (const VD::edge_type &edge : vd.edges()) { - if (edge.is_infinite()) - continue; - const VD::vertex_type* v0 = edge.vertex0(); - const VD::vertex_type* v1 = edge.vertex1(); - Point p0(v0->x(), v0->y()); - Point p1(v1->x(), v1->y()); - // Insert only Voronoi edges fully contained in the island. - //FIXME This test has a terrible O(n^2) time complexity. - if (env.island_contains_b(p0) && env.island_contains_b(p1)) { - // Find v0 in the graph, allocate a new node if v0 does not exist in the graph yet. - auto i_v0 = vd_vertices.find(v0); - size_t v0_idx; - if (i_v0 == vd_vertices.end()) - vd_vertices[v0] = v0_idx = graph->add_node(p0); - else - v0_idx = i_v0->second; - // Find v1 in the graph, allocate a new node if v0 does not exist in the graph yet. - auto i_v1 = vd_vertices.find(v1); - size_t v1_idx; - if (i_v1 == vd_vertices.end()) - vd_vertices[v1] = v1_idx = graph->add_node(p1); - else - v1_idx = i_v1->second; - // Euclidean distance is used as weight for the graph edge - graph->add_edge(v0_idx, v1_idx, (p1 - p0).cast().norm()); - } - } - } - - return *graph; -} - -// Find a middle point on the path from start_point to end_point with the shortest path. -static inline size_t nearest_waypoint_index(const Point &start_point, const Points &middle_points, const Point &end_point) -{ - size_t idx = size_t(-1); - double dmin = std::numeric_limits::infinity(); - for (const Point &p : middle_points) { - double d = (p - start_point).cast().norm() + (end_point - p).cast().norm(); - if (d < dmin) { - idx = &p - middle_points.data(); - dmin = d; - if (dmin < EPSILON) - break; - } - } - return idx; -} - -Point MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) const -{ - /* In order to ensure that the move between 'from' and the initial env point does - not violate any of the configuration space boundaries, we limit our search to - the points that satisfy this condition. */ - - /* Assume that this method is never called when 'env' contains 'from'; - so 'from' is either inside a hole or outside all contours */ - - // get the points of the hole containing 'from', if any - Points pp; - for (const ExPolygon &ex : m_env.expolygons) { - for (const Polygon &hole : ex.holes) - if (hole.contains(from)) - pp = hole; - if (! pp.empty()) - break; - } - - // If 'from' is not inside a hole, it's outside of all contours, so take all contours' points. - if (pp.empty()) - for (const ExPolygon &ex : m_env.expolygons) - append(pp, ex.contour.points); - - // Find the candidate result and check that it doesn't cross too many boundaries. - while (pp.size() > 1) { - // find the point in pp that is closest to both 'from' and 'to' - size_t result = nearest_waypoint_index(from, pp, to); - // as we assume 'from' is outside env, any node will require at least one crossing - if (intersection_ln(Line(from, pp[result]), m_island).size() > 1) { - // discard result - pp.erase(pp.begin() + result); - } else - return pp[result]; - } - - // if we're here, return last point if any (better than nothing) - // if we have no points at all, then we have an empty environment and we - // make this method behave as a no-op (we shouldn't get here by the way) - return pp.empty() ? from : pp.front(); -} - -// Add a new directed edge to the adjacency graph. -void MotionPlannerGraph::add_edge(size_t from, size_t to, double weight) -{ - // Extend adjacency list until this start node. - if (m_adjacency_list.size() < from + 1) { - // Reserve in powers of two to avoid repeated reallocation. - m_adjacency_list.reserve(std::max(8, next_highest_power_of_2((uint32_t)(from + 1)))); - // Allocate new empty adjacency vectors. - m_adjacency_list.resize(from + 1); - } - m_adjacency_list[from].emplace_back(Neighbor(node_t(to), weight)); -} - -// Dijkstra's shortest path in a weighted graph from node_start to node_end. -// The returned path contains the end points. -// If no path exists from node_start to node_end, a straight segment is returned. -Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) const -{ - // This prevents a crash in case for some reason we got here with an empty adjacency list. - if (this->empty()) - return Polyline(); - - // Dijkstra algorithm, previous node of the current node 'u' in the shortest path towards node_start. - std::vector previous(m_adjacency_list.size(), -1); - std::vector distance(m_adjacency_list.size(), std::numeric_limits::infinity()); - std::vector map_node_to_queue_id(m_adjacency_list.size(), size_t(-1)); - distance[node_start] = 0.; - - auto queue = make_mutable_priority_queue( - [&map_node_to_queue_id](const node_t node, size_t idx) { map_node_to_queue_id[node] = idx; }, - [&distance](const node_t node1, const node_t node2) { return distance[node1] < distance[node2]; }); - queue.reserve(m_adjacency_list.size()); - for (size_t i = 0; i < m_adjacency_list.size(); ++ i) - queue.push(node_t(i)); - - while (! queue.empty()) { - // Get the next node with the lowest distance to node_start. - node_t u = node_t(queue.top()); - queue.pop(); - map_node_to_queue_id[u] = size_t(-1); - // Stop searching if we reached our destination. - if (size_t(u) == node_end) - break; - // Visit each edge starting at node u. - for (const Neighbor& neighbor : m_adjacency_list[u]) - if (map_node_to_queue_id[neighbor.target] != size_t(-1)) { - weight_t alt = distance[u] + neighbor.weight; - // If total distance through u is shorter than the previous - // distance (if any) between node_start and neighbor.target, replace it. - if (alt < distance[neighbor.target]) { - distance[neighbor.target] = alt; - previous[neighbor.target] = u; - queue.update(map_node_to_queue_id[neighbor.target]); - } - } - } - - // In case the end point was not reached, previous[node_end] contains -1 - // and a straight line from node_start to node_end is returned. - Polyline polyline; - polyline.points.reserve(m_adjacency_list.size()); - for (node_t vertex = node_t(node_end); vertex != -1; vertex = previous[vertex]) - polyline.points.emplace_back(m_nodes[vertex]); - polyline.points.emplace_back(m_nodes[node_start]); - polyline.reverse(); - return polyline; -} - -} diff --git a/src/libslic3r/MotionPlanner.hpp b/src/libslic3r/MotionPlanner.hpp deleted file mode 100644 index e912f2fb4..000000000 --- a/src/libslic3r/MotionPlanner.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef slic3r_MotionPlanner_hpp_ -#define slic3r_MotionPlanner_hpp_ - -#include "libslic3r.h" -#include "BoundingBox.hpp" -#include "ClipperUtils.hpp" -#include "ExPolygonCollection.hpp" -#include "Polyline.hpp" -#include -#include -#include -#include - -#define MP_INNER_MARGIN scale_(1.0) -#define MP_OUTER_MARGIN scale_(2.0) - -namespace Slic3r { - -class MotionPlanner; - -class MotionPlannerEnv -{ - friend class MotionPlanner; - -public: - MotionPlannerEnv() {}; - MotionPlannerEnv(const ExPolygon &island) : m_island(island), m_island_bbox(get_extents(island)) {}; - Point nearest_env_point(const Point &from, const Point &to) const; - bool island_contains(const Point &pt) const - { return m_island_bbox.contains(pt) && m_island.contains(pt); } - bool island_contains_b(const Point &pt) const - { return m_island_bbox.contains(pt) && m_island.contains_b(pt); } - -private: - ExPolygon m_island; - BoundingBox m_island_bbox; - // Region, where the travel is allowed. - ExPolygonCollection m_env; -}; - -// A 2D directed graph for searching a shortest path using the famous Dijkstra algorithm. -class MotionPlannerGraph -{ -public: - // Add a directed edge into the graph. - size_t add_node(const Point &p) { m_nodes.emplace_back(p); return m_nodes.size() - 1; } - void add_edge(size_t from, size_t to, double weight); - size_t find_closest_node(const Point &point) const { return point.nearest_point_index(m_nodes); } - - bool empty() const { return m_adjacency_list.empty(); } - Polyline shortest_path(size_t from, size_t to) const; - Polyline shortest_path(const Point &from, const Point &to) const - { return this->shortest_path(this->find_closest_node(from), this->find_closest_node(to)); } - -private: - typedef int node_t; - typedef double weight_t; - struct Neighbor { - Neighbor(node_t target, weight_t weight) : target(target), weight(weight) {} - node_t target; - weight_t weight; - }; - Points m_nodes; - std::vector> m_adjacency_list; -}; - -class MotionPlanner -{ -public: - MotionPlanner(const ExPolygons &islands); - ~MotionPlanner() {} - - Polyline shortest_path(const Point &from, const Point &to); - size_t islands_count() const { return m_islands.size(); } - -private: - bool m_initialized; - std::vector m_islands; - MotionPlannerEnv m_outer; - // 0th graph is the graph for m_outer. Other graphs are 1 indexed. - std::vector> m_graphs; - - void initialize(); - const MotionPlannerGraph& init_graph(int island_idx); - const MotionPlannerEnv& get_env(int island_idx) const - { return (island_idx == -1) ? m_outer : m_islands[island_idx]; } -}; - -} - -#endif diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 47961c623..9e406828a 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -15,8 +15,6 @@ REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(Flow, "Flow"); REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); -//REGISTER_CLASS(GCodePreviewData, "GCode::PreviewData"); -// REGISTER_CLASS(GCodeSender, "GCode::Sender"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(SupportLayer, "Layer::Support"); REGISTER_CLASS(LayerRegion, "Layer::Region"); @@ -35,7 +33,6 @@ REGISTER_CLASS(ModelMaterial, "Model::Material"); REGISTER_CLASS(ModelObject, "Model::Object"); REGISTER_CLASS(ModelVolume, "Model::Volume"); REGISTER_CLASS(ModelInstance, "Model::Instance"); -REGISTER_CLASS(MotionPlanner, "MotionPlanner"); REGISTER_CLASS(BoundingBox, "Geometry::BoundingBox"); REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf"); REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3"); diff --git a/xs/t/18_motionplanner.t b/xs/t/18_motionplanner.t deleted file mode 100644 index dfcfec67f..000000000 --- a/xs/t/18_motionplanner.t +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../../lib"; -} - -use Slic3r::XS; -use Test::More tests => 20; - -my $square = Slic3r::Polygon->new( # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -); -my $hole_in_square = Slic3r::Polygon->new( # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], -); -$_->scale(1/0.000001) for $square, $hole_in_square; - -my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - -{ - my $mp = Slic3r::MotionPlanner->new([ $expolygon ]); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(120, 120); - my $to = Slic3r::Point->new(180,180); - $_->scale(1/0.000001) for $from, $to; - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; - ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; - ok $path->first_point->coincides_with($from), 'first path point coincides with initial point'; - ok $path->last_point->coincides_with($to), 'last path point coincides with destination point'; - ok $expolygon->contains_polyline($path), 'path is fully contained in expolygon'; -} - -{ - my $mp = Slic3r::MotionPlanner->new([ $expolygon ]); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(80, 100); - my $to = Slic3r::Point->new(220,200); - $_->scale(1/0.000001) for $from, $to; - - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; - ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; - ok $path->first_point->coincides_with($from), 'first path point coincides with initial point'; - ok $path->last_point->coincides_with($to), 'last path point coincides with destination point'; - is scalar(@{ Slic3r::Geometry::Clipper::intersection_pl([$path], [@$expolygon]) }), 0, 'path has no intersection with expolygon'; -} - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->translate(300/0.000001, 0); - my $mp = Slic3r::MotionPlanner->new([ $expolygon, $expolygon2 ]); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(120, 120); - my $to = Slic3r::Point->new(120 + 300, 120); - $_->scale(1/0.000001) for $from, $to; - ok $expolygon->contains_point($from), 'start point is contained in first expolygon'; - ok $expolygon2->contains_point($to), 'end point is contained in second expolygon'; - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; -} - -{ - my $expolygons = [ - Slic3r::ExPolygon->new([[123800962,89330311],[123959159,89699438],[124000004,89898430],[124000012,110116427],[123946510,110343065],[123767391,110701303],[123284087,111000001],[102585791,111000009],[102000004,110414223],[102000004,89585787],[102585790,89000000],[123300022,88999993]]), - Slic3r::ExPolygon->new([[97800954,89330311],[97959151,89699438],[97999996,89898430],[98000004,110116427],[97946502,110343065],[97767383,110701303],[97284079,111000001],[76585783,111000009],[75999996,110414223],[75999996,89585787],[76585782,89000000],[97300014,88999993]]), - ]; - my $mp = Slic3r::MotionPlanner->new($expolygons); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(79120520, 107839491); - my $to = Slic3r::Point->new(104664164, 108335852); - ok $expolygons->[1]->contains_point($from), 'start point is contained in second expolygon'; - ok $expolygons->[0]->contains_point($to), 'end point is contained in first expolygon'; - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; -} - -__END__ diff --git a/xs/xsp/MotionPlanner.xsp b/xs/xsp/MotionPlanner.xsp deleted file mode 100644 index d93ed09ea..000000000 --- a/xs/xsp/MotionPlanner.xsp +++ /dev/null @@ -1,15 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/MotionPlanner.hpp" -%} - -%name{Slic3r::MotionPlanner} class MotionPlanner { - MotionPlanner(ExPolygons islands); - ~MotionPlanner(); - - int islands_count(); - Clone shortest_path(Point* from, Point* to) - %code%{ RETVAL = THIS->shortest_path(*from, *to); %}; -}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 7e51b237c..2ecff6e3f 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -191,18 +191,6 @@ GCode* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -//GCodePreviewData* O_OBJECT_SLIC3R -//Ref O_OBJECT_SLIC3R_T -//Clone O_OBJECT_SLIC3R_T - -MotionPlanner* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -// GCodeSender* O_OBJECT_SLIC3R -// Ref O_OBJECT_SLIC3R_T -// Clone O_OBJECT_SLIC3R_T - BridgeDetector* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 385b50f1a..2d364628e 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -97,12 +97,6 @@ %typemap{PolylineCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{MotionPlanner*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -// %typemap{GCodeSender*}; -// %typemap{Ref}{simple}; -// %typemap{Clone}{simple}; %typemap{BridgeDetector*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; From 62ab17bf6e337b7bd86424383d6eb6ff684b900f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Nov 2020 10:42:27 +0100 Subject: [PATCH 26/35] AvoidCrossingPerimeters: Refactored for better encapsulation. --- src/libslic3r/GCode.cpp | 48 +- .../GCode/AvoidCrossingPerimeters.cpp | 459 ++++++++++-------- .../GCode/AvoidCrossingPerimeters.hpp | 141 +----- 3 files changed, 303 insertions(+), 345 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 96eff24e7..47da80b6c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -46,8 +46,6 @@ using namespace std::literals::string_view_literals; #endif #include -#include -#include namespace Slic3r { @@ -232,7 +230,7 @@ namespace Slic3r { // Move over the wipe tower. // Retract for a tool change, using the toolchange retract value and setting the priming extra length. gcode += gcodegen.retract(true); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); gcode += gcodegen.travel_to( wipe_tower_point_to_object_point(gcodegen, start_pos), erMixed, @@ -327,7 +325,7 @@ namespace Slic3r { } // Let the planner know we are traveling between objects. - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); return gcode; } @@ -1175,12 +1173,12 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Move to the origin position for the copy we're going to print. // This happens before Z goes down to layer 0 again, so that no collision happens hopefully. m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer - m_avoid_crossing_perimeters.use_external_mp_once = true; + m_avoid_crossing_perimeters.use_external_mp_once(); _write(file, this->retract()); _write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. - m_avoid_crossing_perimeters.disable_once = true; + m_avoid_crossing_perimeters.disable_once(); // Ff we are printing the bottom layer of an object, and we have already finished // another one, set first layer temperatures. This happens before the Z move // is triggered, so machine has more time to reach such temperatures. @@ -1998,7 +1996,7 @@ void GCode::process_layer( if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { const std::pair loops = loops_it->second; this->set_origin(0., 0.); - m_avoid_crossing_perimeters.use_external_mp = true; + m_avoid_crossing_perimeters.use_external_mp(); Flow layer_skirt_flow(print.skirt_flow()); layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])); double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); @@ -2012,23 +2010,23 @@ void GCode::process_layer( //FIXME using the support_material_speed of the 1st object printed. gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value); } - m_avoid_crossing_perimeters.use_external_mp = false; + m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). if (first_layer && loops.first == 0) - m_avoid_crossing_perimeters.disable_once = true; + m_avoid_crossing_perimeters.disable_once(); } // Extrude brim with the extruder of the 1st region. if (! m_brim_done) { this->set_origin(0., 0.); - m_avoid_crossing_perimeters.use_external_mp = true; + m_avoid_crossing_perimeters.use_external_mp(); 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; + m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. - m_avoid_crossing_perimeters.disable_once = true; + m_avoid_crossing_perimeters.disable_once(); } @@ -2055,7 +2053,7 @@ void GCode::process_layer( const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; std::pair this_object_copy(&instance_to_print.print_object, offset); if (m_last_obj_copy != this_object_copy) - m_avoid_crossing_perimeters.use_external_mp_once = true; + m_avoid_crossing_perimeters.use_external_mp_once(); m_last_obj_copy = this_object_copy; this->set_origin(unscale(offset)); if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { @@ -2647,9 +2645,7 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by this->origin in order to get G-code coordinates. */ - Polyline travel; - travel.append(this->last_pos()); - travel.append(point); + Polyline travel { this->last_pos(), point }; // check whether a straight travel move would need retraction bool needs_retraction = this->needs_retraction(travel, role); @@ -2660,31 +2656,28 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string // multi-hop travel path inside the configuration space if (needs_retraction && m_config.avoid_crossing_perimeters - && ! m_avoid_crossing_perimeters.disable_once) { + && ! m_avoid_crossing_perimeters.disabled_once()) { travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); - // check again whether the new travel path still needs a retraction needs_retraction = this->needs_retraction(travel, role); //if (needs_retraction && m_layer_index > 1) exit(0); } // Re-allow avoid_crossing_perimeters for the next travel moves - m_avoid_crossing_perimeters.disable_once = false; - m_avoid_crossing_perimeters.use_external_mp_once = false; + m_avoid_crossing_perimeters.reset_once_modifiers(); // generate G-code for the travel move std::string gcode; if (needs_retraction) { - if (m_config.avoid_crossing_perimeters && !m_avoid_crossing_perimeters.disable_once && could_be_wipe_disabled) + if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled) m_wipe.reset_path(); Point last_post_before_retract = this->last_pos(); gcode += this->retract(); // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters. // Because of it, it is necessary to call avoid crossing perimeters for the path between previous last_post and last_post after calling retraction() - if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters && !m_avoid_crossing_perimeters.disable_once) { + if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters) { Polyline retract_travel = m_avoid_crossing_perimeters.travel_to(*this, last_post_before_retract); - retract_travel.points.reserve(retract_travel.points.size() + travel.points.size()); append(retract_travel.points, travel.points); travel = std::move(retract_travel); } @@ -2693,11 +2686,10 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string m_wipe.reset_path(); // use G1 because we rely on paths being straight (G0 may make round paths) - Lines lines = travel.lines(); - if (! lines.empty()) { - for (const Line &line : lines) - gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment); - this->set_last_pos(lines.back().b); + if (travel.size() >= 2) { + for (size_t i = 1; i < travel.size(); ++ i) + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); + this->set_last_pos(travel.points.back()); } return gcode; } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 6a211e05b..7c0213556 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -1,8 +1,6 @@ #include "../Layer.hpp" #include "../GCode.hpp" #include "../EdgeGrid.hpp" -#include "../Geometry.hpp" -#include "../ShortestPath.hpp" #include "../Print.hpp" #include "../Polygon.hpp" #include "../ExPolygon.hpp" @@ -10,15 +8,80 @@ #include "../SVG.hpp" #include "AvoidCrossingPerimeters.hpp" -#include #include #include -#include -#include - namespace Slic3r { +struct TravelPoint +{ + Point point; + // Index of the polygon containing this point. A negative value indicates that the point is not on any border + int border_idx; +}; + +struct Intersection +{ + // Index of the polygon containing this point of intersection. + size_t border_idx; + // Index of the line on the polygon containing this point of intersection. + size_t line_idx; + // Point of intersection projected on the travel path. + Point point_transformed; + // Point of intersection. + Point point; + + Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) + : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; + + inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } +}; + +struct AllIntersectionsVisitor +{ + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) + : grid(grid), intersections(intersections) + {} + + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, + std::vector &intersections, + const Matrix2d &transform_to_x_axis, + const Line &travel_line) + : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + {} + + void reset() { + intersection_set.clear(); + } + + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; + ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + + Point intersection_point; + if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { + intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, + (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); + intersection_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + std::vector &intersections; + Matrix2d transform_to_x_axis; + Line travel_line; + std::unordered_set, boost::hash>> intersection_set; +}; + // Create a rotation matrix for projection on the given vector static Matrix2d rotation_by_direction(const Point &direction) { @@ -185,16 +248,16 @@ static std::pair split_expolygon(const ExPolygons &ex_polygo return std::make_pair(std::move(contours), std::move(holes)); } -static Polyline to_polyline(const std::vector &travel) +static Polyline to_polyline(const std::vector &travel) { Polyline result; result.points.reserve(travel.size()); - for (const AvoidCrossingPerimeters::TravelPoint &t_point : travel) + for (const TravelPoint &t_point : travel) result.append(t_point.point); return result; } -static double travel_length(const std::vector &travel) { +static double travel_length(const std::vector &travel) { double total_length = 0; for (size_t idx = 1; idx < travel.size(); ++idx) total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); @@ -203,11 +266,11 @@ static double travel_length(const std::vector &intersections, - const std::string &path) +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const Polyline &result_travel, + const std::vector &intersections, + const std::string &path) { BoundingBox bbox = get_extents(boundary); ::Slic3r::SVG svg(path, bbox); @@ -217,21 +280,21 @@ static void export_travel_to_svg(const Polygons svg.draw(original_travel.a, "black"); svg.draw(original_travel.b, "grey"); - for (const AvoidCrossingPerimeters::Intersection &intersection : intersections) + for (const Intersection &intersection : intersections) svg.draw(intersection.point, "lightseagreen"); } -static void export_travel_to_svg(const Polygons &boundary, - const Line &original_travel, - const std::vector &result_travel, - const std::vector &intersections, - const std::string &path) +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const std::vector &result_travel, + const std::vector &intersections, + const std::string &path) { export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ -ExPolygons AvoidCrossingPerimeters::get_boundary(const Layer &layer) +static ExPolygons get_boundary(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; @@ -291,7 +354,7 @@ ExPolygons AvoidCrossingPerimeters::get_boundary(const Layer &layer) return result_boundary; } -ExPolygons AvoidCrossingPerimeters::get_boundary_external(const Layer &layer) +static ExPolygons get_boundary_external(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing_external(layer); const float perimeter_offset = perimeter_spacing / 2.f; @@ -328,11 +391,12 @@ ExPolygons AvoidCrossingPerimeters::get_boundary_external(const Layer &layer) } // Returns a direction of the shortest path along the polygon boundary -AvoidCrossingPerimeters::Direction AvoidCrossingPerimeters::get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last) +enum class Direction { Forward, Backward }; +static Direction get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last) { double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); @@ -358,165 +422,14 @@ AvoidCrossingPerimeters::Direction AvoidCrossingPerimeters::get_shortest_directi return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -std::vector AvoidCrossingPerimeters::simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics) -{ - struct Visitor - { - Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} +static std::vector simplify_travel(const EdgeGrid::Grid& edge_grid, const std::vector& travel, const Polygons& boundaries, const bool use_heuristics); - bool operator()(coord_t iy, coord_t ix) - { - assert(pt_current != nullptr); - assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - this->intersect = false; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { - this->intersect = true; - return false; - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - const Slic3r::Point *pt_current = nullptr; - const Slic3r::Point *pt_next = nullptr; - bool intersect = false; - } visitor(edge_grid); - - std::vector simplified_path; - simplified_path.reserve(travel.size()); - simplified_path.emplace_back(travel.front()); - - // Try to skip some points in the path. - for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { - const Point ¤t_point = travel[point_idx - 1].point; - TravelPoint next = travel[point_idx]; - - visitor.pt_current = ¤t_point; - - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - if (travel[point_idx_2].point == current_point) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - continue; - } - - visitor.pt_next = &travel[point_idx_2].point; - edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); - // Check if deleting point causes crossing a boundary - if (!visitor.intersect) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - } - } - - simplified_path.emplace_back(next); - } - - if(use_heuristics) { - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - } - - return simplified_path; -} - -std::vector AvoidCrossingPerimeters::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries) -{ - std::vector simplified_path; - std::vector intersections; - AllIntersectionsVisitor visitor(edge_grid, intersections); - simplified_path.reserve(travel.size()); - simplified_path.emplace_back(travel.front()); - for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { - // Skip all indexes on the same polygon - while (point_idx < travel.size() && travel[point_idx - 1].border_idx == travel[point_idx].border_idx) { - simplified_path.emplace_back(travel[point_idx]); - point_idx++; - } - - if (point_idx < travel.size()) { - const TravelPoint ¤t = travel[point_idx - 1]; - const TravelPoint &next = travel[point_idx]; - TravelPoint new_next = next; - size_t new_point_idx = point_idx; - double path_length = (next.point - current.point).cast().norm(); - double new_path_shorter_by = 0.; - size_t border_idx_change_count = 0; - std::vector shortcut; - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - const TravelPoint &possible_new_next = travel[point_idx_2]; - if (travel[point_idx_2 - 1].border_idx != travel[point_idx_2].border_idx) - border_idx_change_count++; - - if (border_idx_change_count >= 2) - break; - - path_length += (possible_new_next.point - travel[point_idx_2 - 1].point).cast().norm(); - double shortcut_length = (possible_new_next.point - current.point).cast().norm(); - if ((path_length - shortcut_length) <= scale_(10.0)) - continue; - - intersections.clear(); - visitor.reset(); - visitor.travel_line.a = current.point; - visitor.travel_line.b = possible_new_next.point; - visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector()); - edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); - if (!intersections.empty()) { - std::sort(intersections.begin(), intersections.end()); - size_t last_border_idx_count = 0; - for (const Intersection &intersection : intersections) - if (int(intersection.border_idx) == possible_new_next.border_idx) - ++last_border_idx_count; - - if (last_border_idx_count > 0) - continue; - - std::vector possible_shortcut; - avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut); - double shortcut_travel = travel_length(possible_shortcut); - if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) { - new_path_shorter_by = path_length - shortcut_travel; - shortcut = possible_shortcut; - new_next = possible_new_next; - new_point_idx = point_idx_2; - } - } - } - - if (!shortcut.empty()) { - assert(shortcut.size() >= 2); - simplified_path.insert(simplified_path.end(), shortcut.begin() + 1, shortcut.end() - 1); - point_idx = new_point_idx; - } - - simplified_path.emplace_back(new_next); - } - } - - return simplified_path; -} - -size_t AvoidCrossingPerimeters::avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out) +static size_t avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + const bool use_heuristics, + std::vector *result_out) { const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); @@ -608,10 +521,164 @@ size_t AvoidCrossingPerimeters::avoid_perimeters(const Polygons &bound return intersections.size(); } -bool AvoidCrossingPerimeters::need_wipe(const GCode & gcodegen, - const Line & original_travel, - const Polyline &result_travel, - const size_t intersection_count) +static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries) +{ + std::vector simplified_path; + std::vector intersections; + AllIntersectionsVisitor visitor(edge_grid, intersections); + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + // Skip all indexes on the same polygon + while (point_idx < travel.size() && travel[point_idx - 1].border_idx == travel[point_idx].border_idx) { + simplified_path.emplace_back(travel[point_idx]); + point_idx++; + } + + if (point_idx < travel.size()) { + const TravelPoint ¤t = travel[point_idx - 1]; + const TravelPoint &next = travel[point_idx]; + TravelPoint new_next = next; + size_t new_point_idx = point_idx; + double path_length = (next.point - current.point).cast().norm(); + double new_path_shorter_by = 0.; + size_t border_idx_change_count = 0; + std::vector shortcut; + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + const TravelPoint &possible_new_next = travel[point_idx_2]; + if (travel[point_idx_2 - 1].border_idx != travel[point_idx_2].border_idx) + border_idx_change_count++; + + if (border_idx_change_count >= 2) + break; + + path_length += (possible_new_next.point - travel[point_idx_2 - 1].point).cast().norm(); + double shortcut_length = (possible_new_next.point - current.point).cast().norm(); + if ((path_length - shortcut_length) <= scale_(10.0)) + continue; + + intersections.clear(); + visitor.reset(); + visitor.travel_line.a = current.point; + visitor.travel_line.b = possible_new_next.point; + visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector()); + edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); + if (!intersections.empty()) { + std::sort(intersections.begin(), intersections.end()); + size_t last_border_idx_count = 0; + for (const Intersection &intersection : intersections) + if (int(intersection.border_idx) == possible_new_next.border_idx) + ++last_border_idx_count; + + if (last_border_idx_count > 0) + continue; + + std::vector possible_shortcut; + avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut); + double shortcut_travel = travel_length(possible_shortcut); + if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) { + new_path_shorter_by = path_length - shortcut_travel; + shortcut = possible_shortcut; + new_next = possible_new_next; + new_point_idx = point_idx_2; + } + } + } + + if (!shortcut.empty()) { + assert(shortcut.size() >= 2); + simplified_path.insert(simplified_path.end(), shortcut.begin() + 1, shortcut.end() - 1); + point_idx = new_point_idx; + } + + simplified_path.emplace_back(new_next); + } + } + + return simplified_path; +} + +static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics) +{ + struct Visitor + { + Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; + } visitor(edge_grid); + + std::vector simplified_path; + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + + // Try to skip some points in the path. + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + const Point ¤t_point = travel[point_idx - 1].point; + TravelPoint next = travel[point_idx]; + + visitor.pt_current = ¤t_point; + + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + if (travel[point_idx_2].point == current_point) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + continue; + } + + visitor.pt_next = &travel[point_idx_2].point; + edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary + if (!visitor.intersect) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + } + } + + simplified_path.emplace_back(next); + } + + if(use_heuristics) { + simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); + std::reverse(simplified_path.begin(),simplified_path.end()); + simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); + std::reverse(simplified_path.begin(),simplified_path.end()); + } + + return simplified_path; +} + +static bool need_wipe(const GCode &gcodegen, + const ExPolygons &slice, + const Line &original_travel, + const Polyline &result_travel, + const size_t intersection_count) { bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; bool wipe_needed = false; @@ -622,19 +689,19 @@ bool AvoidCrossingPerimeters::need_wipe(const GCode & gcodegen, // The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test. // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. if (z_lift_enabled) { - if (any_expolygon_contains(m_slice, original_travel)) { + if (any_expolygon_contains(slice, original_travel)) { // Check if original_travel and result_travel are not same. // If both are the same, then it is possible to skip testing of result_travel if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { wipe_needed = false; } else { - wipe_needed = !any_expolygon_contains(m_slice, result_travel); + wipe_needed = !any_expolygon_contains(slice, result_travel); } } else { wipe_needed = true; } } else { - wipe_needed = !any_expolygon_contains(m_slice, result_travel); + wipe_needed = !any_expolygon_contains(slice, result_travel); } } @@ -646,7 +713,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & { // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). // Otherwise perform the path planning in the coordinate system of the active object. - bool use_external = this->use_external_mp || this->use_external_mp_once; + bool use_external = m_use_external_mp || m_use_external_mp_once; Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); Point start = gcodegen.last_pos() + scaled_origin; Point end = point + scaled_origin; @@ -659,9 +726,9 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & std::vector result; auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); if (use_external) - travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); + travel_intersection_count = avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); else - travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); + travel_intersection_count = avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); result_pl = to_polyline(result); } @@ -678,7 +745,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & result_pl.translate(-scaled_origin); *could_be_wipe_disabled = false; } else - *could_be_wipe_disabled = !need_wipe(gcodegen, travel, result_pl, travel_intersection_count); + *could_be_wipe_disabled = !need_wipe(gcodegen, m_slice, travel, result_pl, travel_intersection_count); return result_pl; } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index 0f99108f9..d33311a90 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -5,120 +5,40 @@ #include "../ExPolygon.hpp" #include "../EdgeGrid.hpp" -#include -#include - namespace Slic3r { // Forward declarations. class GCode; class Layer; class Point; -class Print; -class PrintObject; - -struct PrintInstance; -using PrintObjectPtrs = std::vector; class AvoidCrossingPerimeters { public: - struct Intersection + // Routing around the objects vs. inside a single object. + void use_external_mp(bool use = true) { m_use_external_mp = use; }; + void use_external_mp_once() { m_use_external_mp_once = true; } + void disable_once() { m_disabled_once = true; } + bool disabled_once() const { return m_disabled_once; } + void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; } + + void init_layer(const Layer &layer); + + Polyline travel_to(const GCode& gcodegen, const Point& point) { - // Index of the polygon containing this point of intersection. - size_t border_idx; - // Index of the line on the polygon containing this point of intersection. - size_t line_idx; - // Point of intersection projected on the travel path. - Point point_transformed; - // Point of intersection. - Point point; + bool could_be_wipe_disabled; + return this->travel_to(gcodegen, point, &could_be_wipe_disabled); + } - Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) - : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; - - inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } - }; - - struct TravelPoint - { - Point point; - // Index of the polygon containing this point. A negative value indicates that the point is not on any border - int border_idx; - }; - - struct AllIntersectionsVisitor - { - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) - : grid(grid), intersections(intersections) - {} - - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, - std::vector &intersections, - const Matrix2d &transform_to_x_axis, - const Line &travel_line) - : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) - {} - - void reset() { - intersection_set.clear(); - } - - bool operator()(coord_t iy, coord_t ix) - { - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; - ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - - Point intersection_point; - if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && - intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { - intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); - intersection_set.insert(*it_contour_and_segment); - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - std::vector &intersections; - Matrix2d transform_to_x_axis; - Line travel_line; - std::unordered_set, boost::hash>> intersection_set; - }; - - enum class Direction { Forward, Backward }; + Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled); private: - static ExPolygons get_boundary(const Layer &layer); - - static ExPolygons get_boundary_external(const Layer &layer); - - static Direction get_shortest_direction( - const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last); - - static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics); - - static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries); - - static size_t avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out); - - bool need_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count); + bool m_use_external_mp { false }; + // just for the next travel move + bool m_use_external_mp_once { false }; + // this flag disables avoid_crossing_perimeters just for the next travel move + // we enable it by default for the first travel move in print + bool m_disabled_once { true }; // Slice of layer with elephant foot compensation ExPolygons m_slice; @@ -132,27 +52,6 @@ private: BoundingBox m_bbox_external; EdgeGrid::Grid m_grid; EdgeGrid::Grid m_grid_external; - -public: - // this flag triggers the use of the external configuration space - bool use_external_mp { false }; - // just for the next travel move - bool use_external_mp_once { false }; - // this flag disables avoid_crossing_perimeters just for the next travel move - // we enable it by default for the first travel move in print - bool disable_once { true }; - - AvoidCrossingPerimeters() = default; - - Polyline travel_to(const GCode& gcodegen, const Point& point) - { - bool could_be_wipe_disabled; - return this->travel_to(gcodegen, point, &could_be_wipe_disabled); - } - - Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled); - - void init_layer(const Layer &layer); }; } // namespace Slic3r From 656b90dbe555406f07afee8d18655f30224f1d4b Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Nov 2020 15:18:19 +0100 Subject: [PATCH 27/35] Avoid crossing perimeters: Further refactoring for clarity, code review. --- src/libslic3r/EdgeGrid.hpp | 9 +- src/libslic3r/ExPolygon.cpp | 6 +- src/libslic3r/ExPolygon.hpp | 3 +- .../GCode/AvoidCrossingPerimeters.cpp | 724 +++++++++--------- .../GCode/AvoidCrossingPerimeters.hpp | 4 +- src/libslic3r/MultiPoint.cpp | 7 - src/libslic3r/MultiPoint.hpp | 2 +- xs/CMakeLists.txt | 1 - 8 files changed, 363 insertions(+), 393 deletions(-) diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 6a9f482a1..bff56523b 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -238,7 +238,14 @@ public: { const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; size_t ipt = contour_and_segment_idx.second; - return std::pair(ipts[ipt], ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]); + return std::pair(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); + } + + Line line(const std::pair &contour_and_segment_idx) const + { + const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; + size_t ipt = contour_and_segment_idx.second; + return Line(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); } protected: diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 5bdd5055e..f6e03dd1c 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -42,11 +42,11 @@ void ExPolygon::scale(double factor) hole.scale(factor); } -void ExPolygon::translate(double x, double y) +void ExPolygon::translate(const Point &p) { - contour.translate(x, y); + contour.translate(p); for (Polygon &hole : holes) - hole.translate(x, y); + hole.translate(p); } void ExPolygon::rotate(double angle) diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 373853f97..0c8c42368 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -42,7 +42,8 @@ public: operator Polylines() const; void clear() { contour.points.clear(); holes.clear(); } void scale(double factor); - void translate(double x, double y); + void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } + void translate(const Point &vector); void rotate(double angle); void rotate(double angle, const Point ¢er); double area() const; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 7c0213556..f577c42a5 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -4,6 +4,7 @@ #include "../Print.hpp" #include "../Polygon.hpp" #include "../ExPolygon.hpp" +#include "../Geometry.hpp" #include "../ClipperUtils.hpp" #include "../SVG.hpp" #include "AvoidCrossingPerimeters.hpp" @@ -16,7 +17,7 @@ namespace Slic3r { struct TravelPoint { Point point; - // Index of the polygon containing this point. A negative value indicates that the point is not on any border + // Index of the polygon containing this point. A negative value indicates that the point is not on any border. int border_idx; }; @@ -26,17 +27,11 @@ struct Intersection size_t border_idx; // Index of the line on the polygon containing this point of intersection. size_t line_idx; - // Point of intersection projected on the travel path. - Point point_transformed; // Point of intersection. Point point; - - Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) - : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; - - inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } }; +// Finding all intersections of a set of contours with a line segment. struct AllIntersectionsVisitor { AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) @@ -45,9 +40,8 @@ struct AllIntersectionsVisitor AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections, - const Matrix2d &transform_to_x_axis, const Line &travel_line) - : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + : grid(grid), intersections(intersections), travel_line(travel_line) {} void reset() { @@ -58,16 +52,11 @@ struct AllIntersectionsVisitor { // Called with a row and colum of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; - ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { Point intersection_point; - if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + if (travel_line.intersection(grid.line(*it_contour_and_segment), &intersection_point) && intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { - intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); + intersections.push_back({ it_contour_and_segment->first, it_contour_and_segment->second, intersection_point }); intersection_set.insert(*it_contour_and_segment); } } @@ -77,57 +66,42 @@ struct AllIntersectionsVisitor const EdgeGrid::Grid &grid; std::vector &intersections; - Matrix2d transform_to_x_axis; Line travel_line; std::unordered_set, boost::hash>> intersection_set; }; -// Create a rotation matrix for projection on the given vector -static Matrix2d rotation_by_direction(const Point &direction) -{ - Matrix2d rotation; - rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); - rotation(1, 0) = -rotation(0, 1); - rotation(1, 1) = rotation(0, 0); - - return rotation; -} - -static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) +template +static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) { assert(point_idx < polygon.size()); - if (point != polygon.points[point_idx]) - return polygon.points[point_idx]; - - int line_idx = int(point_idx); - if (forward) - for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); + auto line_idx = int(point_idx); + //FIXME endless loop if all points are equal to point? + if constexpr (forward) + for (; point == polygon.points[line_idx]; line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0); else - for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1))); + for (; point == polygon.points[line_idx]; line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1); return polygon.points[line_idx]; } +//FIXME will be in Point.h in the master +template +inline Eigen::Matrix perp(const Eigen::MatrixBase>& v) { return Eigen::Matrix(-v.y(), v.x()); } + static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { assert(left != middle); assert(middle != right); - - Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x()); - Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x()); - normal_1.normalize(); - normal_2.normalize(); - - return (normal_1 + normal_2).normalized(); + return (perp(Point(middle - left)).cast().normalized() + perp(Point(right - middle)).cast().normalized()).normalized(); } // Compute normal of the polygon's vertex in an inward direction static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) { - const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); - const size_t right_idx = (point_idx >= (polygon.size() - 1)) ? 0 : (point_idx + 1); + const size_t left_idx = point_idx == 0 ? polygon.size() - 1 : point_idx - 1; + const size_t right_idx = point_idx + 1 == polygon.size() ? 0 : point_idx + 1; const Point &middle = polygon.points[point_idx]; - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + const Point &left = find_first_different_vertex(polygon, left_idx, middle); + const Point &right = find_first_different_vertex(polygon, right_idx, middle); return three_points_inward_normal(left, middle, right); } @@ -140,114 +114,11 @@ static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t poin // Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx). static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const coord_t offset) { - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + const Point &left = find_first_different_vertex(polygon, left_idx, middle); + const Point &right = find_first_different_vertex(polygon, right_idx, middle); return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); } -static bool check_if_could_cross_perimeters(const BoundingBox &bbox, const Point &start, const Point &end) -{ - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - // When both endpoints are out of the bounding box, it needs to check in more detail. - if (start_out_of_bound && end_out_of_bound) { - Point intersection; - return bbox.polygon().intersection(Line(start, end), &intersection); - } - return true; -} - -static std::pair clamp_endpoints_by_bounding_box(const BoundingBox &bbox, const Point &start, const Point &end) -{ - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - Point start_clamped = start, end_clamped = end; - Points intersections; - if (start_out_of_bound || end_out_of_bound) { - bbox.polygon().intersections(Line(start, end), &intersections); - assert(intersections.size() <= 2); - } - - if (start_out_of_bound && !end_out_of_bound && intersections.size() == 1) { - start_clamped = intersections[0]; - } else if (!start_out_of_bound && end_out_of_bound && intersections.size() == 1) { - end_clamped = intersections[0]; - } else if (start_out_of_bound && end_out_of_bound && intersections.size() == 2) { - if ((intersections[0] - start).cast().norm() < (intersections[1] - start).cast().norm()) { - start_clamped = intersections[0]; - end_clamped = intersections[1]; - } else { - start_clamped = intersections[1]; - end_clamped = intersections[0]; - } - } - - return std::make_pair(start_clamped, end_clamped); -} - -static inline float get_default_perimeter_spacing(const Print &print) -{ - const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; - return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); -} - -static float get_perimeter_spacing(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const LayerRegion *layer_region : layer.regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); - return perimeter_spacing; -} - -static float get_perimeter_spacing_external(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const PrintObject *object : layer.object()->print()->objects()) - for (Layer *l : object->layers()) - if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) - for (const LayerRegion *layer_region : l->regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); - return perimeter_spacing; -} - -// Check if anyone of ExPolygons contains whole travel. -template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) -{ - for (const ExPolygon &ex_polygon : ex_polygons) - if (ex_polygon.contains(travel)) return true; - - return false; -} - -static std::pair split_expolygon(const ExPolygons &ex_polygons) -{ - Polygons contours, holes; - contours.reserve(ex_polygons.size()); - holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), - [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); - for (const ExPolygon &ex_poly : ex_polygons) { - contours.emplace_back(ex_poly.contour); - append(holes, ex_poly.holes); - } - return std::make_pair(std::move(contours), std::move(holes)); -} - static Polyline to_polyline(const std::vector &travel) { Polyline result; @@ -265,6 +136,8 @@ static double travel_length(const std::vector &travel) { return total_length; } +// #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT static void export_travel_to_svg(const Polygons &boundary, const Line &original_travel, @@ -294,102 +167,6 @@ static void export_travel_to_svg(const Polygons &boundary, } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ -static ExPolygons get_boundary(const Layer &layer) -{ - const float perimeter_spacing = get_perimeter_spacing(layer); - const float perimeter_offset = perimeter_spacing / 2.f; - size_t polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - polygons_count += layer_region->slices.surfaces.size(); - - ExPolygons boundary; - boundary.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); - - boundary = union_ex(boundary); - ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); - ExPolygons result_boundary; - if (perimeter_boundary.size() != boundary.size()) { - // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. - ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, - offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), - perimeter_offset + float(SCALED_EPSILON)); - perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); - perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size()); - perimeter_boundary.insert(perimeter_boundary.end(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - // By calling intersection_ex some artifacts arose by previous operations are removed. - result_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary)); - } else { - result_boundary = std::move(perimeter_boundary); - } - - auto [contours, holes] = split_expolygon(boundary); - // Add an outer boundary to avoid crossing perimeters from supports - ExPolygons outer_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); - result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); - ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); - result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - result_boundary = union_ex(result_boundary); - - // Collect all top layers that will not be crossed. - polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) ++polygons_count; - - if (polygons_count > 0) { - ExPolygons top_layer_polygons; - top_layer_polygons.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); - - top_layer_polygons = union_ex(top_layer_polygons); - return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); - } - - return result_boundary; -} - -static ExPolygons get_boundary_external(const Layer &layer) -{ - const float perimeter_spacing = get_perimeter_spacing_external(layer); - const float perimeter_offset = perimeter_spacing / 2.f; - ExPolygons boundary; - // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". - for (const PrintObject *object : layer.object()->print()->objects()) { - ExPolygons polygons_per_obj; - for (Layer *l : object->layers()) - if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) - for (const LayerRegion *layer_region : l->regions()) - for (const Surface &surface : layer_region->slices.surfaces) - polygons_per_obj.emplace_back(surface.expolygon); - - for (const PrintInstance &instance : object->instances()) { - size_t boundary_idx = boundary.size(); - boundary.reserve(boundary.size() + polygons_per_obj.size()); - boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); - for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); - } - } - boundary = union_ex(boundary); - auto [contours, holes] = split_expolygon(boundary); - // Polygons in which is possible traveling without crossing perimeters of another object. - // A convex hull allows removing unnecessary detour caused by following the boundary of the object. - ExPolygons result_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); - // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. - ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); - result_boundary.reserve(result_boundary.size() + holes_boundary.size()); - result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - result_boundary = union_ex(result_boundary); - return result_boundary; -} - // Returns a direction of the shortest path along the polygon boundary enum class Direction { Forward, Backward }; static Direction get_shortest_direction(const Lines &lines, @@ -422,29 +199,89 @@ static Direction get_shortest_direction(const Lines &lines, return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -static std::vector simplify_travel(const EdgeGrid::Grid& edge_grid, const std::vector& travel, const Polygons& boundaries, const bool use_heuristics); - -static size_t avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out) +// Straighten the travel path as long as it does not collide with the contours stored in edge_grid. +static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, const std::vector &travel) { - const Point direction = end - start; - Matrix2d transform_to_x_axis = rotation_by_direction(direction); - - const Line travel_line_orig(start, end); - const Line travel_line((transform_to_x_axis * start.cast()).cast(), - (transform_to_x_axis * end.cast()).cast()); - - std::vector intersections; + // Visitor to check for a collision of a line segment with any contour stored inside the edge_grid. + struct Visitor { - AllIntersectionsVisitor visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); - edge_grid.visit_cells_intersecting_line(start, end, visitor); + Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; + } visitor(edge_grid); + + std::vector simplified_path; + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + + // Try to skip some points in the path. + //FIXME maybe use a binary search to trim the line? + //FIXME how about searching tangent point at long segments? + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + const Point ¤t_point = travel[point_idx - 1].point; + TravelPoint next = travel[point_idx]; + + visitor.pt_current = ¤t_point; + + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + if (travel[point_idx_2].point == current_point) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + continue; + } + + visitor.pt_next = &travel[point_idx_2].point; + edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary + if (!visitor.intersect) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + } + } + + simplified_path.emplace_back(next); } - std::sort(intersections.begin(), intersections.end()); + return simplified_path; +} + +// Called by avoid_perimeters() and by simplify_travel_heuristics(). +static size_t avoid_perimeters_inner(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + std::vector &result_out) +{ + // Find all intersections between boundaries and the line segment, sort them along the line segment. + std::vector intersections; + { + AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); + edge_grid.visit_cells_intersecting_line(start, end, visitor); + Vec2d dir = (end - start).cast(); + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).cast().dot(dir) > 0.; }); + } std::vector result; result.push_back({start, -1}); @@ -500,24 +337,23 @@ static size_t avoid_perimeters(const Polygons &boundaries, #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundaries, travel_line_orig, result, intersections, - debug_out_path("AvoidCrossingPerimeters-initial-%d.svg", iRun++)); + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - if(!intersections.empty()) - result = simplify_travel(edge_grid, result, boundaries, use_heuristics); + if (! intersections.empty()) + result = simplify_travel(edge_grid, result); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundaries, travel_line_orig, result, intersections, - debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun++)); + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - result_out->reserve(result_out->size() + result.size()); - result_out->insert(result_out->end(), result.begin(), result.end()); + append(result_out, std::move(result)); return intersections.size(); } @@ -563,10 +399,10 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid visitor.reset(); visitor.travel_line.a = current.point; visitor.travel_line.b = possible_new_next.point; - visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector()); edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); if (!intersections.empty()) { - std::sort(intersections.begin(), intersections.end()); + Vec2d dir = (visitor.travel_line.b - visitor.travel_line.a).cast(); + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).cast().dot(dir) > 0.; }); size_t last_border_idx_count = 0; for (const Intersection &intersection : intersections) if (int(intersection.border_idx) == possible_new_next.border_idx) @@ -576,9 +412,9 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid continue; std::vector possible_shortcut; - avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut); + avoid_perimeters_inner(boundaries, edge_grid, current.point, possible_new_next.point, possible_shortcut); double shortcut_travel = travel_length(possible_shortcut); - if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) { + if (path_length > shortcut_travel && path_length - shortcut_travel > new_path_shorter_by) { new_path_shorter_by = path_length - shortcut_travel; shortcut = possible_shortcut; new_next = possible_new_next; @@ -600,78 +436,44 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid return simplified_path; } -static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics) +// Called by AvoidCrossingPerimeters::travel_to() +static size_t avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + Polyline &result_out) { - struct Visitor + // Travel line is completely or partially inside the bounding box. + std::vector path; + size_t num_intersections = avoid_perimeters_inner(boundaries, edge_grid, start, end, path); + if (num_intersections) { + path = simplify_travel_heuristics(edge_grid, path, boundaries); + std::reverse(path.begin(), path.end()); + path = simplify_travel_heuristics(edge_grid, path, boundaries); + std::reverse(path.begin(), path.end()); + } + + result_out = to_polyline(path); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { - Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - bool operator()(coord_t iy, coord_t ix) - { - assert(pt_current != nullptr); - assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - this->intersect = false; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { - this->intersect = true; - return false; - } - } - // Continue traversing the grid along the edge. + return num_intersections; +} + +// Check if anyone of ExPolygons contains whole travel. +// called by need_wipe() +template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) +{ + //FIXME filter by bounding boxes! + for (const ExPolygon &ex_polygon : ex_polygons) + if (ex_polygon.contains(travel)) return true; - } - - const EdgeGrid::Grid &grid; - const Slic3r::Point *pt_current = nullptr; - const Slic3r::Point *pt_next = nullptr; - bool intersect = false; - } visitor(edge_grid); - - std::vector simplified_path; - simplified_path.reserve(travel.size()); - simplified_path.emplace_back(travel.front()); - - // Try to skip some points in the path. - for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { - const Point ¤t_point = travel[point_idx - 1].point; - TravelPoint next = travel[point_idx]; - - visitor.pt_current = ¤t_point; - - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - if (travel[point_idx_2].point == current_point) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - continue; - } - - visitor.pt_next = &travel[point_idx_2].point; - edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); - // Check if deleting point causes crossing a boundary - if (!visitor.intersect) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - } - } - - simplified_path.emplace_back(next); - } - - if(use_heuristics) { - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - } - - return simplified_path; + return false; } static bool need_wipe(const GCode &gcodegen, @@ -692,11 +494,8 @@ static bool need_wipe(const GCode &gcodegen, if (any_expolygon_contains(slice, original_travel)) { // Check if original_travel and result_travel are not same. // If both are the same, then it is possible to skip testing of result_travel - if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { - wipe_needed = false; - } else { - wipe_needed = !any_expolygon_contains(slice, result_travel); - } + wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && + !any_expolygon_contains(slice, result_travel); } else { wipe_needed = true; } @@ -719,28 +518,27 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & Point end = point + scaled_origin; Polyline result_pl; size_t travel_intersection_count = 0; - if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { - result_pl = Polyline({start, end}); - travel_intersection_count = 0; + Vec2d startf = start.cast(); + Vec2d endf = end .cast(); + // Trim the travel line by the bounding box. + if (Geometry::liang_barsky_line_clipping(startf, endf, use_external ? m_bbox_external : m_bbox)) { + // Travel line is completely or partially inside the bounding box. + travel_intersection_count = use_external ? + avoid_perimeters(m_boundaries_external, m_grid_external, startf.cast(), endf.cast(), result_pl) : + avoid_perimeters(m_boundaries, m_grid, startf.cast(), endf.cast(), result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; } else { - std::vector result; - auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); - if (use_external) - travel_intersection_count = avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); - else - travel_intersection_count = avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); - - result_pl = to_polyline(result); + // Travel line is completely outside the bounding box. + result_pl = {start, end}; + travel_intersection_count = 0; } - result_pl.points.front() = start; - result_pl.points.back() = end; - Line travel(start, end); double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); - if ((max_detour_length > 0) && ((result_pl.length() - travel.length()) > max_detour_length)) { - result_pl = Polyline({start, end}); - } + if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length) + result_pl = {start, end}; + if (use_external) { result_pl.translate(-scaled_origin); *could_be_wipe_disabled = false; @@ -750,6 +548,172 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & return result_pl; } +// ************************************* AvoidCrossingPerimeters::init_layer() ***************************************** + +// called by get_perimeter_spacing() / get_perimeter_spacing_external() +static inline float get_default_perimeter_spacing(const Print &print) +{ + //FIXME better use extruders printing this PrintObject or this Print? + //FIXME maybe better use an average of printing extruders? + const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; + return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); +} + +// called by get_boundary() +static float get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + //FIXME not all regions are printing. Collect only non-empty regions? + for (const LayerRegion *layer_region : layer.regions()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +// called by get_boundary_external() +static float get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const PrintObject *object : layer.object()->print()->objects()) + //FIXME with different layering, layers on other objects will not be found at this object's print_z. + // Search an overlap of layers? + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + //FIXME not all regions are printing. Collect only non-empty regions? + for (const LayerRegion *layer_region : l->regions()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +// called by AvoidCrossingPerimeters::init_layer()->get_boundary()/get_boundary_external() +static std::pair split_expolygon(const ExPolygons &ex_polygons) +{ + Polygons contours, holes; + contours.reserve(ex_polygons.size()); + holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), + [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); + for (const ExPolygon &ex_poly : ex_polygons) { + contours.emplace_back(ex_poly.contour); + append(holes, ex_poly.holes); + } + return std::make_pair(std::move(contours), std::move(holes)); +} + +// called by AvoidCrossingPerimeters::init_layer() +static ExPolygons get_boundary(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + size_t polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + polygons_count += layer_region->slices.surfaces.size(); + + ExPolygons boundary; + boundary.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->slices.surfaces) + boundary.emplace_back(surface.expolygon); + + boundary = union_ex(boundary); + ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); + ExPolygons result_boundary; + if (perimeter_boundary.size() != boundary.size()) { + //FIXME ??? + // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, + offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), + perimeter_offset + float(SCALED_EPSILON)); + perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); + append(perimeter_boundary, std::move(missing_perimeter_boundary)); + // By calling intersection_ex some artifacts arose by previous operations are removed. + result_boundary = intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary); + } else { + result_boundary = std::move(perimeter_boundary); + } + + auto [contours, holes] = split_expolygon(boundary); + // Add an outer boundary to avoid crossing perimeters from supports + ExPolygons outer_boundary = union_ex( + //FIXME flip order of offset and convex_hull + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); + ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); + result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + result_boundary = union_ex(result_boundary); + + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); + } + + return result_boundary; +} + +// called by AvoidCrossingPerimeters::init_layer() +static ExPolygons get_boundary_external(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing_external(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + ExPolygons boundary; + // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". + for (const PrintObject *object : layer.object()->print()->objects()) { + ExPolygons polygons_per_obj; + //FIXME with different layering, layers on other objects will not be found at this object's print_z. + // Search an overlap of layers? + if (const Layer* l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + for (const Surface &surface : layer_region->slices.surfaces) + polygons_per_obj.emplace_back(surface.expolygon); + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift); + } + } + boundary = union_ex(boundary); + auto [contours, holes] = split_expolygon(boundary); + // Polygons in which is possible traveling without crossing perimeters of another object. + // A convex hull allows removing unnecessary detour caused by following the boundary of the object. + ExPolygons result_boundary = union_ex( + //FIXME flip order of offset and convex_hull + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. + append(result_boundary, union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset)))); + return union_ex(result_boundary); +} + void AvoidCrossingPerimeters::init_layer(const Layer &layer) { m_slice.clear(); @@ -757,19 +721,25 @@ void AvoidCrossingPerimeters::init_layer(const Layer &layer) m_boundaries_external.clear(); for (const LayerRegion *layer_region : layer.regions()) + //FIXME making copies? append(m_slice, (ExPolygons) layer_region->slices); m_boundaries = to_polygons(get_boundary(layer)); m_boundaries_external = to_polygons(get_boundary_external(layer)); - m_bbox = get_extents(m_boundaries); - m_bbox.offset(SCALED_EPSILON); - m_bbox_external = get_extents(m_boundaries_external); - m_bbox_external.offset(SCALED_EPSILON); + BoundingBox bbox(get_extents(m_boundaries)); + bbox.offset(SCALED_EPSILON); + BoundingBox bbox_external = get_extents(m_boundaries_external); + bbox_external.offset(SCALED_EPSILON); - m_grid.set_bbox(m_bbox); + m_bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + m_bbox_external = BoundingBoxf(bbox_external.min.cast(), bbox_external.max.cast()); + + m_grid.set_bbox(bbox); + //FIXME 1mm grid? m_grid.create(m_boundaries, coord_t(scale_(1.))); - m_grid_external.set_bbox(m_bbox_external); + m_grid_external.set_bbox(bbox_external); + //FIXME 1mm grid? m_grid_external.create(m_boundaries_external, coord_t(scale_(1.))); } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index d33311a90..bdae775a1 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -47,9 +47,9 @@ private: // Collection of boundaries used for detection of crossing perimetrs for travels outside object Polygons m_boundaries_external; // Bounding box of m_boundaries - BoundingBox m_bbox; + BoundingBoxf m_bbox; // Bounding box of m_boundaries_external - BoundingBox m_bbox_external; + BoundingBoxf m_bbox_external; EdgeGrid::Grid m_grid; EdgeGrid::Grid m_grid_external; }; diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index a00dd802b..03f7ff59c 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -18,13 +18,6 @@ void MultiPoint::scale(double factor_x, double factor_y) } } -void MultiPoint::translate(double x, double y) -{ - Vector v(x, y); - for (Point &pt : points) - pt += v; -} - void MultiPoint::translate(const Point &v) { for (Point &pt : points) diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 653e59cd8..b7a5ab684 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -28,7 +28,7 @@ public: MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } void scale(double factor); void scale(double factor_x, double factor_y); - void translate(double x, double y); + void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } void translate(const Point &vector); void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double cos_angle, double sin_angle); diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 75d236a54..962e2e04d 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -62,7 +62,6 @@ set(XS_XSP_FILES ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp ${XSP_DIR}/Model.xsp - ${XSP_DIR}/MotionPlanner.xsp ${XSP_DIR}/PerimeterGenerator.xsp ${XSP_DIR}/PlaceholderParser.xsp ${XSP_DIR}/Point.xsp From f206b743fd8195a41e9aa13d17f865a35fc97693 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Nov 2020 15:18:19 +0100 Subject: [PATCH 28/35] Avoid crossing perimeters: Further refactoring for clarity, code review. --- src/libslic3r/EdgeGrid.hpp | 9 +- src/libslic3r/ExPolygon.cpp | 6 +- src/libslic3r/ExPolygon.hpp | 3 +- .../GCode/AvoidCrossingPerimeters.cpp | 724 +++++++++--------- .../GCode/AvoidCrossingPerimeters.hpp | 4 +- src/libslic3r/MultiPoint.cpp | 7 - src/libslic3r/MultiPoint.hpp | 2 +- xs/CMakeLists.txt | 1 - 8 files changed, 363 insertions(+), 393 deletions(-) diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 6a9f482a1..bff56523b 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -238,7 +238,14 @@ public: { const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; size_t ipt = contour_and_segment_idx.second; - return std::pair(ipts[ipt], ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]); + return std::pair(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); + } + + Line line(const std::pair &contour_and_segment_idx) const + { + const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; + size_t ipt = contour_and_segment_idx.second; + return Line(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); } protected: diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 5bdd5055e..f6e03dd1c 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -42,11 +42,11 @@ void ExPolygon::scale(double factor) hole.scale(factor); } -void ExPolygon::translate(double x, double y) +void ExPolygon::translate(const Point &p) { - contour.translate(x, y); + contour.translate(p); for (Polygon &hole : holes) - hole.translate(x, y); + hole.translate(p); } void ExPolygon::rotate(double angle) diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 373853f97..0c8c42368 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -42,7 +42,8 @@ public: operator Polylines() const; void clear() { contour.points.clear(); holes.clear(); } void scale(double factor); - void translate(double x, double y); + void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } + void translate(const Point &vector); void rotate(double angle); void rotate(double angle, const Point ¢er); double area() const; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 7c0213556..f757a268e 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -4,6 +4,7 @@ #include "../Print.hpp" #include "../Polygon.hpp" #include "../ExPolygon.hpp" +#include "../Geometry.hpp" #include "../ClipperUtils.hpp" #include "../SVG.hpp" #include "AvoidCrossingPerimeters.hpp" @@ -16,7 +17,7 @@ namespace Slic3r { struct TravelPoint { Point point; - // Index of the polygon containing this point. A negative value indicates that the point is not on any border + // Index of the polygon containing this point. A negative value indicates that the point is not on any border. int border_idx; }; @@ -26,17 +27,11 @@ struct Intersection size_t border_idx; // Index of the line on the polygon containing this point of intersection. size_t line_idx; - // Point of intersection projected on the travel path. - Point point_transformed; // Point of intersection. Point point; - - Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) - : border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){}; - - inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); } }; +// Finding all intersections of a set of contours with a line segment. struct AllIntersectionsVisitor { AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) @@ -45,9 +40,8 @@ struct AllIntersectionsVisitor AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections, - const Matrix2d &transform_to_x_axis, const Line &travel_line) - : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) + : grid(grid), intersections(intersections), travel_line(travel_line) {} void reset() { @@ -58,16 +52,11 @@ struct AllIntersectionsVisitor { // Called with a row and colum of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; - ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { Point intersection_point; - if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) && + if (travel_line.intersection(grid.line(*it_contour_and_segment), &intersection_point) && intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { - intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, - (transform_to_x_axis * intersection_point.cast()).cast(), intersection_point); + intersections.push_back({ it_contour_and_segment->first, it_contour_and_segment->second, intersection_point }); intersection_set.insert(*it_contour_and_segment); } } @@ -77,57 +66,42 @@ struct AllIntersectionsVisitor const EdgeGrid::Grid &grid; std::vector &intersections; - Matrix2d transform_to_x_axis; Line travel_line; std::unordered_set, boost::hash>> intersection_set; }; -// Create a rotation matrix for projection on the given vector -static Matrix2d rotation_by_direction(const Point &direction) -{ - Matrix2d rotation; - rotation.block<1, 2>(0, 0) = direction.cast() / direction.cast().norm(); - rotation(1, 0) = -rotation(0, 1); - rotation(1, 1) = rotation(0, 0); - - return rotation; -} - -static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point, bool forward) +template +static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) { assert(point_idx < polygon.size()); - if (point != polygon.points[point_idx]) - return polygon.points[point_idx]; - - int line_idx = int(point_idx); - if (forward) - for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); + auto line_idx = int(point_idx); + //FIXME endless loop if all points are equal to point? + if constexpr (forward) + for (; point == polygon.points[line_idx]; line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0); else - for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1))); + for (; point == polygon.points[line_idx]; line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1); return polygon.points[line_idx]; } +//FIXME will be in Point.h in the master +template +inline Eigen::Matrix perp(const Eigen::MatrixBase>& v) { return Eigen::Matrix(-v.y(), v.x()); } + static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { assert(left != middle); assert(middle != right); - - Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x()); - Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x()); - normal_1.normalize(); - normal_2.normalize(); - - return (normal_1 + normal_2).normalized(); + return (perp(Point(middle - left)).cast().normalized() + perp(Point(right - middle)).cast().normalized()).normalized(); } // Compute normal of the polygon's vertex in an inward direction static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) { - const size_t left_idx = (point_idx <= 0) ? (polygon.size() - 1) : (point_idx - 1); - const size_t right_idx = (point_idx >= (polygon.size() - 1)) ? 0 : (point_idx + 1); + const size_t left_idx = point_idx == 0 ? polygon.size() - 1 : point_idx - 1; + const size_t right_idx = point_idx + 1 == polygon.size() ? 0 : point_idx + 1; const Point &middle = polygon.points[point_idx]; - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + const Point &left = find_first_different_vertex(polygon, left_idx, middle); + const Point &right = find_first_different_vertex(polygon, right_idx, middle); return three_points_inward_normal(left, middle, right); } @@ -140,114 +114,11 @@ static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t poin // Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx). static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const coord_t offset) { - const Point &left = find_first_different_vertex(polygon, left_idx, middle, false); - const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); + const Point &left = find_first_different_vertex(polygon, left_idx, middle); + const Point &right = find_first_different_vertex(polygon, right_idx, middle); return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); } -static bool check_if_could_cross_perimeters(const BoundingBox &bbox, const Point &start, const Point &end) -{ - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - // When both endpoints are out of the bounding box, it needs to check in more detail. - if (start_out_of_bound && end_out_of_bound) { - Point intersection; - return bbox.polygon().intersection(Line(start, end), &intersection); - } - return true; -} - -static std::pair clamp_endpoints_by_bounding_box(const BoundingBox &bbox, const Point &start, const Point &end) -{ - bool start_out_of_bound = !bbox.contains(start), end_out_of_bound = !bbox.contains(end); - Point start_clamped = start, end_clamped = end; - Points intersections; - if (start_out_of_bound || end_out_of_bound) { - bbox.polygon().intersections(Line(start, end), &intersections); - assert(intersections.size() <= 2); - } - - if (start_out_of_bound && !end_out_of_bound && intersections.size() == 1) { - start_clamped = intersections[0]; - } else if (!start_out_of_bound && end_out_of_bound && intersections.size() == 1) { - end_clamped = intersections[0]; - } else if (start_out_of_bound && end_out_of_bound && intersections.size() == 2) { - if ((intersections[0] - start).cast().norm() < (intersections[1] - start).cast().norm()) { - start_clamped = intersections[0]; - end_clamped = intersections[1]; - } else { - start_clamped = intersections[1]; - end_clamped = intersections[0]; - } - } - - return std::make_pair(start_clamped, end_clamped); -} - -static inline float get_default_perimeter_spacing(const Print &print) -{ - const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; - return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); -} - -static float get_perimeter_spacing(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const LayerRegion *layer_region : layer.regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); - return perimeter_spacing; -} - -static float get_perimeter_spacing_external(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const PrintObject *object : layer.object()->print()->objects()) - for (Layer *l : object->layers()) - if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) - for (const LayerRegion *layer_region : l->regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); - return perimeter_spacing; -} - -// Check if anyone of ExPolygons contains whole travel. -template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) -{ - for (const ExPolygon &ex_polygon : ex_polygons) - if (ex_polygon.contains(travel)) return true; - - return false; -} - -static std::pair split_expolygon(const ExPolygons &ex_polygons) -{ - Polygons contours, holes; - contours.reserve(ex_polygons.size()); - holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), - [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); - for (const ExPolygon &ex_poly : ex_polygons) { - contours.emplace_back(ex_poly.contour); - append(holes, ex_poly.holes); - } - return std::make_pair(std::move(contours), std::move(holes)); -} - static Polyline to_polyline(const std::vector &travel) { Polyline result; @@ -265,6 +136,8 @@ static double travel_length(const std::vector &travel) { return total_length; } +// #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT static void export_travel_to_svg(const Polygons &boundary, const Line &original_travel, @@ -294,102 +167,6 @@ static void export_travel_to_svg(const Polygons &boundary, } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ -static ExPolygons get_boundary(const Layer &layer) -{ - const float perimeter_spacing = get_perimeter_spacing(layer); - const float perimeter_offset = perimeter_spacing / 2.f; - size_t polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - polygons_count += layer_region->slices.surfaces.size(); - - ExPolygons boundary; - boundary.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon); - - boundary = union_ex(boundary); - ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); - ExPolygons result_boundary; - if (perimeter_boundary.size() != boundary.size()) { - // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. - ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, - offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), - perimeter_offset + float(SCALED_EPSILON)); - perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); - perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size()); - perimeter_boundary.insert(perimeter_boundary.end(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end()); - // By calling intersection_ex some artifacts arose by previous operations are removed. - result_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary)); - } else { - result_boundary = std::move(perimeter_boundary); - } - - auto [contours, holes] = split_expolygon(boundary); - // Add an outer boundary to avoid crossing perimeters from supports - ExPolygons outer_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); - result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); - ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); - result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - result_boundary = union_ex(result_boundary); - - // Collect all top layers that will not be crossed. - polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) ++polygons_count; - - if (polygons_count > 0) { - ExPolygons top_layer_polygons; - top_layer_polygons.reserve(polygons_count); - for (const LayerRegion *layer_region : layer.regions()) - for (const Surface &surface : layer_region->fill_surfaces.surfaces) - if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); - - top_layer_polygons = union_ex(top_layer_polygons); - return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); - } - - return result_boundary; -} - -static ExPolygons get_boundary_external(const Layer &layer) -{ - const float perimeter_spacing = get_perimeter_spacing_external(layer); - const float perimeter_offset = perimeter_spacing / 2.f; - ExPolygons boundary; - // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". - for (const PrintObject *object : layer.object()->print()->objects()) { - ExPolygons polygons_per_obj; - for (Layer *l : object->layers()) - if ((layer.print_z - EPSILON) <= l->print_z && l->print_z <= (layer.print_z + EPSILON)) - for (const LayerRegion *layer_region : l->regions()) - for (const Surface &surface : layer_region->slices.surfaces) - polygons_per_obj.emplace_back(surface.expolygon); - - for (const PrintInstance &instance : object->instances()) { - size_t boundary_idx = boundary.size(); - boundary.reserve(boundary.size() + polygons_per_obj.size()); - boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); - for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y()); - } - } - boundary = union_ex(boundary); - auto [contours, holes] = split_expolygon(boundary); - // Polygons in which is possible traveling without crossing perimeters of another object. - // A convex hull allows removing unnecessary detour caused by following the boundary of the object. - ExPolygons result_boundary = union_ex( - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); - // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. - ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); - result_boundary.reserve(result_boundary.size() + holes_boundary.size()); - result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); - result_boundary = union_ex(result_boundary); - return result_boundary; -} - // Returns a direction of the shortest path along the polygon boundary enum class Direction { Forward, Backward }; static Direction get_shortest_direction(const Lines &lines, @@ -422,29 +199,89 @@ static Direction get_shortest_direction(const Lines &lines, return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -static std::vector simplify_travel(const EdgeGrid::Grid& edge_grid, const std::vector& travel, const Polygons& boundaries, const bool use_heuristics); - -static size_t avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out) +// Straighten the travel path as long as it does not collide with the contours stored in edge_grid. +static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, const std::vector &travel) { - const Point direction = end - start; - Matrix2d transform_to_x_axis = rotation_by_direction(direction); - - const Line travel_line_orig(start, end); - const Line travel_line((transform_to_x_axis * start.cast()).cast(), - (transform_to_x_axis * end.cast()).cast()); - - std::vector intersections; + // Visitor to check for a collision of a line segment with any contour stored inside the edge_grid. + struct Visitor { - AllIntersectionsVisitor visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); - edge_grid.visit_cells_intersecting_line(start, end, visitor); + Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; + } visitor(edge_grid); + + std::vector simplified_path; + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + + // Try to skip some points in the path. + //FIXME maybe use a binary search to trim the line? + //FIXME how about searching tangent point at long segments? + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + const Point ¤t_point = travel[point_idx - 1].point; + TravelPoint next = travel[point_idx]; + + visitor.pt_current = ¤t_point; + + for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { + if (travel[point_idx_2].point == current_point) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + continue; + } + + visitor.pt_next = &travel[point_idx_2].point; + edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + // Check if deleting point causes crossing a boundary + if (!visitor.intersect) { + next = travel[point_idx_2]; + point_idx = point_idx_2; + } + } + + simplified_path.emplace_back(next); } - std::sort(intersections.begin(), intersections.end()); + return simplified_path; +} + +// Called by avoid_perimeters() and by simplify_travel_heuristics(). +static size_t avoid_perimeters_inner(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + std::vector &result_out) +{ + // Find all intersections between boundaries and the line segment, sort them along the line segment. + std::vector intersections; + { + AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); + edge_grid.visit_cells_intersecting_line(start, end, visitor); + Vec2d dir = (end - start).cast(); + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); + } std::vector result; result.push_back({start, -1}); @@ -500,24 +337,23 @@ static size_t avoid_perimeters(const Polygons &boundaries, #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundaries, travel_line_orig, result, intersections, - debug_out_path("AvoidCrossingPerimeters-initial-%d.svg", iRun++)); + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - if(!intersections.empty()) - result = simplify_travel(edge_grid, result, boundaries, use_heuristics); + if (! intersections.empty()) + result = simplify_travel(edge_grid, result); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { static int iRun = 0; - export_travel_to_svg(boundaries, travel_line_orig, result, intersections, - debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun++)); + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - result_out->reserve(result_out->size() + result.size()); - result_out->insert(result_out->end(), result.begin(), result.end()); + append(result_out, std::move(result)); return intersections.size(); } @@ -563,10 +399,10 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid visitor.reset(); visitor.travel_line.a = current.point; visitor.travel_line.b = possible_new_next.point; - visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector()); edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); if (!intersections.empty()) { - std::sort(intersections.begin(), intersections.end()); + Vec2d dir = (visitor.travel_line.b - visitor.travel_line.a).cast(); + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); size_t last_border_idx_count = 0; for (const Intersection &intersection : intersections) if (int(intersection.border_idx) == possible_new_next.border_idx) @@ -576,9 +412,9 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid continue; std::vector possible_shortcut; - avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut); + avoid_perimeters_inner(boundaries, edge_grid, current.point, possible_new_next.point, possible_shortcut); double shortcut_travel = travel_length(possible_shortcut); - if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) { + if (path_length > shortcut_travel && path_length - shortcut_travel > new_path_shorter_by) { new_path_shorter_by = path_length - shortcut_travel; shortcut = possible_shortcut; new_next = possible_new_next; @@ -600,78 +436,44 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid return simplified_path; } -static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics) +// Called by AvoidCrossingPerimeters::travel_to() +static size_t avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + Polyline &result_out) { - struct Visitor + // Travel line is completely or partially inside the bounding box. + std::vector path; + size_t num_intersections = avoid_perimeters_inner(boundaries, edge_grid, start, end, path); + if (num_intersections) { + path = simplify_travel_heuristics(edge_grid, path, boundaries); + std::reverse(path.begin(), path.end()); + path = simplify_travel_heuristics(edge_grid, path, boundaries); + std::reverse(path.begin(), path.end()); + } + + result_out = to_polyline(path); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { - Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ - bool operator()(coord_t iy, coord_t ix) - { - assert(pt_current != nullptr); - assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - this->intersect = false; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { - this->intersect = true; - return false; - } - } - // Continue traversing the grid along the edge. + return num_intersections; +} + +// Check if anyone of ExPolygons contains whole travel. +// called by need_wipe() +template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) +{ + //FIXME filter by bounding boxes! + for (const ExPolygon &ex_polygon : ex_polygons) + if (ex_polygon.contains(travel)) return true; - } - - const EdgeGrid::Grid &grid; - const Slic3r::Point *pt_current = nullptr; - const Slic3r::Point *pt_next = nullptr; - bool intersect = false; - } visitor(edge_grid); - - std::vector simplified_path; - simplified_path.reserve(travel.size()); - simplified_path.emplace_back(travel.front()); - - // Try to skip some points in the path. - for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { - const Point ¤t_point = travel[point_idx - 1].point; - TravelPoint next = travel[point_idx]; - - visitor.pt_current = ¤t_point; - - for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { - if (travel[point_idx_2].point == current_point) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - continue; - } - - visitor.pt_next = &travel[point_idx_2].point; - edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); - // Check if deleting point causes crossing a boundary - if (!visitor.intersect) { - next = travel[point_idx_2]; - point_idx = point_idx_2; - } - } - - simplified_path.emplace_back(next); - } - - if(use_heuristics) { - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); - std::reverse(simplified_path.begin(),simplified_path.end()); - } - - return simplified_path; + return false; } static bool need_wipe(const GCode &gcodegen, @@ -692,11 +494,8 @@ static bool need_wipe(const GCode &gcodegen, if (any_expolygon_contains(slice, original_travel)) { // Check if original_travel and result_travel are not same. // If both are the same, then it is possible to skip testing of result_travel - if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { - wipe_needed = false; - } else { - wipe_needed = !any_expolygon_contains(slice, result_travel); - } + wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && + !any_expolygon_contains(slice, result_travel); } else { wipe_needed = true; } @@ -719,28 +518,27 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & Point end = point + scaled_origin; Polyline result_pl; size_t travel_intersection_count = 0; - if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { - result_pl = Polyline({start, end}); - travel_intersection_count = 0; + Vec2d startf = start.cast(); + Vec2d endf = end .cast(); + // Trim the travel line by the bounding box. + if (Geometry::liang_barsky_line_clipping(startf, endf, use_external ? m_bbox_external : m_bbox)) { + // Travel line is completely or partially inside the bounding box. + travel_intersection_count = use_external ? + avoid_perimeters(m_boundaries_external, m_grid_external, startf.cast(), endf.cast(), result_pl) : + avoid_perimeters(m_boundaries, m_grid, startf.cast(), endf.cast(), result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; } else { - std::vector result; - auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); - if (use_external) - travel_intersection_count = avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); - else - travel_intersection_count = avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); - - result_pl = to_polyline(result); + // Travel line is completely outside the bounding box. + result_pl = {start, end}; + travel_intersection_count = 0; } - result_pl.points.front() = start; - result_pl.points.back() = end; - Line travel(start, end); double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); - if ((max_detour_length > 0) && ((result_pl.length() - travel.length()) > max_detour_length)) { - result_pl = Polyline({start, end}); - } + if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length) + result_pl = {start, end}; + if (use_external) { result_pl.translate(-scaled_origin); *could_be_wipe_disabled = false; @@ -750,6 +548,172 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & return result_pl; } +// ************************************* AvoidCrossingPerimeters::init_layer() ***************************************** + +// called by get_perimeter_spacing() / get_perimeter_spacing_external() +static inline float get_default_perimeter_spacing(const Print &print) +{ + //FIXME better use extruders printing this PrintObject or this Print? + //FIXME maybe better use an average of printing extruders? + const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; + return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); +} + +// called by get_boundary() +static float get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + //FIXME not all regions are printing. Collect only non-empty regions? + for (const LayerRegion *layer_region : layer.regions()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +// called by get_boundary_external() +static float get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const PrintObject *object : layer.object()->print()->objects()) + //FIXME with different layering, layers on other objects will not be found at this object's print_z. + // Search an overlap of layers? + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + //FIXME not all regions are printing. Collect only non-empty regions? + for (const LayerRegion *layer_region : l->regions()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + return perimeter_spacing; +} + +// called by AvoidCrossingPerimeters::init_layer()->get_boundary()/get_boundary_external() +static std::pair split_expolygon(const ExPolygons &ex_polygons) +{ + Polygons contours, holes; + contours.reserve(ex_polygons.size()); + holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), + [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); + for (const ExPolygon &ex_poly : ex_polygons) { + contours.emplace_back(ex_poly.contour); + append(holes, ex_poly.holes); + } + return std::make_pair(std::move(contours), std::move(holes)); +} + +// called by AvoidCrossingPerimeters::init_layer() +static ExPolygons get_boundary(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + size_t polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + polygons_count += layer_region->slices.surfaces.size(); + + ExPolygons boundary; + boundary.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->slices.surfaces) + boundary.emplace_back(surface.expolygon); + + boundary = union_ex(boundary); + ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); + ExPolygons result_boundary; + if (perimeter_boundary.size() != boundary.size()) { + //FIXME ??? + // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, + offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), + perimeter_offset + float(SCALED_EPSILON)); + perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); + append(perimeter_boundary, std::move(missing_perimeter_boundary)); + // By calling intersection_ex some artifacts arose by previous operations are removed. + result_boundary = intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary); + } else { + result_boundary = std::move(perimeter_boundary); + } + + auto [contours, holes] = split_expolygon(boundary); + // Add an outer boundary to avoid crossing perimeters from supports + ExPolygons outer_boundary = union_ex( + //FIXME flip order of offset and convex_hull + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); + ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); + result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + result_boundary = union_ex(result_boundary); + + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); + } + + return result_boundary; +} + +// called by AvoidCrossingPerimeters::init_layer() +static ExPolygons get_boundary_external(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing_external(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + ExPolygons boundary; + // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". + for (const PrintObject *object : layer.object()->print()->objects()) { + ExPolygons polygons_per_obj; + //FIXME with different layering, layers on other objects will not be found at this object's print_z. + // Search an overlap of layers? + if (const Layer* l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + for (const Surface &surface : layer_region->slices.surfaces) + polygons_per_obj.emplace_back(surface.expolygon); + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift); + } + } + boundary = union_ex(boundary); + auto [contours, holes] = split_expolygon(boundary); + // Polygons in which is possible traveling without crossing perimeters of another object. + // A convex hull allows removing unnecessary detour caused by following the boundary of the object. + ExPolygons result_boundary = union_ex( + //FIXME flip order of offset and convex_hull + diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset))); + // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. + append(result_boundary, union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset)))); + return union_ex(result_boundary); +} + void AvoidCrossingPerimeters::init_layer(const Layer &layer) { m_slice.clear(); @@ -757,19 +721,25 @@ void AvoidCrossingPerimeters::init_layer(const Layer &layer) m_boundaries_external.clear(); for (const LayerRegion *layer_region : layer.regions()) + //FIXME making copies? append(m_slice, (ExPolygons) layer_region->slices); m_boundaries = to_polygons(get_boundary(layer)); m_boundaries_external = to_polygons(get_boundary_external(layer)); - m_bbox = get_extents(m_boundaries); - m_bbox.offset(SCALED_EPSILON); - m_bbox_external = get_extents(m_boundaries_external); - m_bbox_external.offset(SCALED_EPSILON); + BoundingBox bbox(get_extents(m_boundaries)); + bbox.offset(SCALED_EPSILON); + BoundingBox bbox_external = get_extents(m_boundaries_external); + bbox_external.offset(SCALED_EPSILON); - m_grid.set_bbox(m_bbox); + m_bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + m_bbox_external = BoundingBoxf(bbox_external.min.cast(), bbox_external.max.cast()); + + m_grid.set_bbox(bbox); + //FIXME 1mm grid? m_grid.create(m_boundaries, coord_t(scale_(1.))); - m_grid_external.set_bbox(m_bbox_external); + m_grid_external.set_bbox(bbox_external); + //FIXME 1mm grid? m_grid_external.create(m_boundaries_external, coord_t(scale_(1.))); } diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index d33311a90..bdae775a1 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -47,9 +47,9 @@ private: // Collection of boundaries used for detection of crossing perimetrs for travels outside object Polygons m_boundaries_external; // Bounding box of m_boundaries - BoundingBox m_bbox; + BoundingBoxf m_bbox; // Bounding box of m_boundaries_external - BoundingBox m_bbox_external; + BoundingBoxf m_bbox_external; EdgeGrid::Grid m_grid; EdgeGrid::Grid m_grid_external; }; diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index a00dd802b..03f7ff59c 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -18,13 +18,6 @@ void MultiPoint::scale(double factor_x, double factor_y) } } -void MultiPoint::translate(double x, double y) -{ - Vector v(x, y); - for (Point &pt : points) - pt += v; -} - void MultiPoint::translate(const Point &v) { for (Point &pt : points) diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 653e59cd8..b7a5ab684 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -28,7 +28,7 @@ public: MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } void scale(double factor); void scale(double factor_x, double factor_y); - void translate(double x, double y); + void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } void translate(const Point &vector); void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double cos_angle, double sin_angle); diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 75d236a54..962e2e04d 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -62,7 +62,6 @@ set(XS_XSP_FILES ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp ${XSP_DIR}/Model.xsp - ${XSP_DIR}/MotionPlanner.xsp ${XSP_DIR}/PerimeterGenerator.xsp ${XSP_DIR}/PlaceholderParser.xsp ${XSP_DIR}/Point.xsp From 060f1d48c1c5ca08fe2011a2c250b86c5d386278 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 20 Nov 2020 11:56:40 +0100 Subject: [PATCH 29/35] Little more refactoring. --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 17 +++++++++-------- src/libslic3r/libslic3r.h | 8 +++++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index f757a268e..666069014 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -297,7 +297,7 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, // Append the first intersection into the path size_t left_idx = intersection_first.line_idx; - size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1); + size_t right_idx = intersection_first.line_idx + 1 == boundaries[intersection_first.border_idx].points.size() ? 0 : intersection_first.line_idx + 1; // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the // appended point will be inside the polygon and not on the polygon border. @@ -306,7 +306,7 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, // Check if intersection line also exit the boundary polygon if (it_second_r != it_last_item) { // Transform reverse iterator to forward - auto it_second = (it_second_r.base() - 1); + auto it_second = it_second_r.base() - 1; // The exit point from the boundary polygon const Intersection &intersection_second = *it_second; Lines border_lines = boundaries[intersection_first.border_idx].lines(); @@ -315,12 +315,12 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, // Append the path around the border into the path if (shortest_direction == Direction::Forward) for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx + 1) < int(border_lines.size())) ? (line_idx + 1) : 0)) + line_idx = line_idx + 1 < int(border_lines.size()) ? line_idx + 1 : 0) result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); else for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); - line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(border_lines.size()) - 1))) + line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(border_lines.size()) - 1) result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); // Append the farthest intersection into the path @@ -523,6 +523,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & // Trim the travel line by the bounding box. if (Geometry::liang_barsky_line_clipping(startf, endf, use_external ? m_bbox_external : m_bbox)) { // Travel line is completely or partially inside the bounding box. + //FIXME initialize m_boundaries / m_boundaries_external on demand? travel_intersection_count = use_external ? avoid_perimeters(m_boundaries_external, m_grid_external, startf.cast(), endf.cast(), result_pl) : avoid_perimeters(m_boundaries, m_grid, startf.cast(), endf.cast(), result_pl); @@ -705,12 +706,12 @@ static ExPolygons get_boundary_external(const Layer &layer) auto [contours, holes] = split_expolygon(boundary); // Polygons in which is possible traveling without crossing perimeters of another object. // A convex hull allows removing unnecessary detour caused by following the boundary of the object. - ExPolygons result_boundary = union_ex( + ExPolygons result_boundary = //FIXME flip order of offset and convex_hull - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); + diff_ex(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), + offset(contours, perimeter_spacing + perimeter_offset)); // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. - append(result_boundary, union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset)))); + append(result_boundary, diff_ex(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); return union_ex(result_boundary); } diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index a404d230d..e74005993 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -123,8 +123,10 @@ inline void append(std::vector& dest, std::vector&& src) { if (dest.empty()) dest = std::move(src); - else + else { + dest.resize(dest.size() + src.size()); std::move(std::begin(src), std::end(src), std::back_inserter(dest)); + } src.clear(); src.shrink_to_fit(); } @@ -164,7 +166,7 @@ inline std::unique_ptr make_unique(Args&&... args) { // Variant of std::lower_bound() with compare predicate, but without the key. // This variant is very useful in case that the T type is large or it does not even have a public constructor. template -ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key) +ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_than_key) { ForwardIt it; typename std::iterator_traits::difference_type count, step; @@ -174,7 +176,7 @@ ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKey it = first; step = count / 2; std::advance(it, step); - if (lower_thank_key(*it)) { + if (lower_than_key(*it)) { first = ++it; count -= step + 1; } From 606db666fc6ff25db5cf04b080824e83480d5927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 20 Nov 2020 14:22:24 +0100 Subject: [PATCH 30/35] Fix missing include --- src/libslic3r/GCode.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 47da80b6c..5c7370e0b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -10,6 +10,7 @@ #include "ShortestPath.hpp" #include "Print.hpp" #include "Utils.hpp" +#include "ClipperUtils.hpp" #include "libslic3r.h" #include From 55c282d85dc0f1f9fc0ede41bccda7cf7d692799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 29 Nov 2020 13:58:36 +0100 Subject: [PATCH 31/35] Revamp of implementation of the avoid crossing perimeters algorithm. The strategy for the avoid crossing perimeters algorithm has been redesigned. But external travels (travel between objects or supports) have not been solved yet. For these travels is used a direct path between two points. Much of the code has been reworked, which leads to significant speedup compared to the previous implementation. Also, several potential bugs have been fixed. --- .../GCode/AvoidCrossingPerimeters.cpp | 957 ++++++++++++++---- .../GCode/AvoidCrossingPerimeters.hpp | 31 +- src/libslic3r/libslic3r.h | 2 +- 3 files changed, 771 insertions(+), 219 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 666069014..ff33813cf 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -29,20 +29,23 @@ struct Intersection size_t line_idx; // Point of intersection. Point point; + // Distance from the first point in the corresponding boundary + float distance; }; // Finding all intersections of a set of contours with a line segment. struct AllIntersectionsVisitor { - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) - : grid(grid), intersections(intersections) - {} + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) : grid(grid), intersections(intersections) + { + intersection_set.reserve(intersections.capacity()); + } - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, - std::vector &intersections, - const Line &travel_line) + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections, const Line &travel_line) : grid(grid), intersections(intersections), travel_line(travel_line) - {} + { + intersection_set.reserve(intersections.capacity()); + } void reset() { intersection_set.clear(); @@ -70,16 +73,52 @@ struct AllIntersectionsVisitor std::unordered_set, boost::hash>> intersection_set; }; +// Visitor to check for any collision of a line segment with any contour stored inside the edge_grid. +struct FirstIntersectionVisitor +{ + explicit FirstIntersectionVisitor(const EdgeGrid::Grid &grid) : grid(grid) {} + + bool operator()(coord_t iy, coord_t ix) + { + assert(pt_current != nullptr); + assert(pt_next != nullptr); + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + this->intersect = false; + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { + this->intersect = true; + return false; + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_current = nullptr; + const Slic3r::Point *pt_next = nullptr; + bool intersect = false; +}; + +// point_idx is the index from which is different vertex is searched. template static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) { assert(point_idx < polygon.size()); - auto line_idx = int(point_idx); - //FIXME endless loop if all points are equal to point? + // Solve case when vertex on passed index point_idx is different that pass point. This helps the following code keep simple. + if (point != polygon.points[point_idx]) + return polygon.points[point_idx]; + + auto line_idx = (int(point_idx) + 1) % int(polygon.points.size()); + assert(line_idx != int(point_idx)); if constexpr (forward) - for (; point == polygon.points[line_idx]; line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0); + for (; point == polygon.points[line_idx] && line_idx != int(point_idx); line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0); else - for (; point == polygon.points[line_idx]; line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1); + for (; point == polygon.points[line_idx] && line_idx != int(point_idx); line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1); + assert(point != polygon.points[line_idx]); return polygon.points[line_idx]; } @@ -97,8 +136,8 @@ static Vec2d three_points_inward_normal(const Point &left, const Point &middle, // Compute normal of the polygon's vertex in an inward direction static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) { - const size_t left_idx = point_idx == 0 ? polygon.size() - 1 : point_idx - 1; - const size_t right_idx = point_idx + 1 == polygon.size() ? 0 : point_idx + 1; + const size_t left_idx = prev_idx_modulo(point_idx, polygon.points); + const size_t right_idx = next_idx_modulo(point_idx, polygon.points); const Point &middle = polygon.points[point_idx]; const Point &left = find_first_different_vertex(polygon, left_idx, middle); const Point &right = find_first_different_vertex(polygon, right_idx, middle); @@ -128,14 +167,6 @@ static Polyline to_polyline(const std::vector &travel) return result; } -static double travel_length(const std::vector &travel) { - double total_length = 0; - for (size_t idx = 1; idx < travel.size(); ++idx) - total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); - - return total_length; -} - // #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT @@ -169,69 +200,43 @@ static void export_travel_to_svg(const Polygons &boundary, // Returns a direction of the shortest path along the polygon boundary enum class Direction { Forward, Backward }; -static Direction get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last) +// Returns a direction of the shortest path along the polygon boundary +static Direction get_shortest_direction(const AvoidCrossingPerimeters::Boundary &boundary, + const Intersection &intersection_first, + const Intersection &intersection_second, + float contour_length) { - double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); - double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); + assert(intersection_first.border_idx == intersection_second.border_idx); + const Polygon &poly = boundary.boundaries[intersection_first.border_idx]; + float dist_first = intersection_first.distance; + float dist_second = intersection_second.distance; - auto cyclic_index = [&lines](int index) { - if (index >= int(lines.size())) - index = 0; - else if (index < 0) - index = int(lines.size()) - 1; + assert(dist_first >= 0.f && dist_first <= contour_length); + assert(dist_second >= 0.f && dist_second <= contour_length); - return index; - }; + bool reversed = false; + if (dist_first > dist_second) { + std::swap(dist_first, dist_second); + reversed = true; + } + float total_length_forward = dist_second - dist_first; + float total_length_backward = dist_first + contour_length - dist_second; + if (reversed) std::swap(total_length_forward, total_length_backward); - for (int line_idx = cyclic_index(int(start_idx) + 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx + 1)) - total_length_forward += lines[line_idx].length(); + total_length_forward -= (intersection_first.point - poly[intersection_first.line_idx]).cast().norm(); + total_length_backward -= (poly[(intersection_first.line_idx + 1) % poly.size()] - intersection_first.point).cast().norm(); - for (int line_idx = cyclic_index(int(start_idx) - 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx - 1)) - total_length_backward += lines[line_idx].length(); + total_length_forward -= (poly[(intersection_second.line_idx + 1) % poly.size()] - intersection_second.point).cast().norm(); + total_length_backward -= (intersection_second.point - poly[intersection_second.line_idx]).cast().norm(); - total_length_forward += (lines[end_idx].a - intersection_last).cast().norm(); - total_length_backward += (lines[end_idx].b - intersection_last).cast().norm(); - - return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; + if (total_length_forward < total_length_backward) return Direction::Forward; + return Direction::Backward; } // Straighten the travel path as long as it does not collide with the contours stored in edge_grid. -static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, const std::vector &travel) +static std::vector simplify_travel(const AvoidCrossingPerimeters::Boundary &boundary, const std::vector &travel) { - // Visitor to check for a collision of a line segment with any contour stored inside the edge_grid. - struct Visitor - { - Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} - - bool operator()(coord_t iy, coord_t ix) - { - assert(pt_current != nullptr); - assert(pt_next != nullptr); - // Called with a row and colum of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - this->intersect = false; - for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - // End points of the line segment and their vector. - auto segment = grid.segment(*it_contour_and_segment); - if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) { - this->intersect = true; - return false; - } - } - // Continue traversing the grid along the edge. - return true; - } - - const EdgeGrid::Grid &grid; - const Slic3r::Point *pt_current = nullptr; - const Slic3r::Point *pt_next = nullptr; - bool intersect = false; - } visitor(edge_grid); - + FirstIntersectionVisitor visitor(boundary.grid); std::vector simplified_path; simplified_path.reserve(travel.size()); simplified_path.emplace_back(travel.front()); @@ -253,7 +258,7 @@ static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, } visitor.pt_next = &travel[point_idx_2].point; - edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + boundary.grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); // Check if deleting point causes crossing a boundary if (!visitor.intersect) { next = travel[point_idx_2]; @@ -268,18 +273,634 @@ static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, } // Called by avoid_perimeters() and by simplify_travel_heuristics(). -static size_t avoid_perimeters_inner(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, +static size_t avoid_perimeters_inner(const GCode &gcodegen, const AvoidCrossingPerimeters::Boundary &boundary, const Point &start, const Point &end, std::vector &result_out) { + const Polygons &boundaries = boundary.boundaries; + const EdgeGrid::Grid &edge_grid = boundary.grid; // Find all intersections between boundaries and the line segment, sort them along the line segment. std::vector intersections; { + intersections.reserve(boundaries.size()); AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); edge_grid.visit_cells_intersecting_line(start, end, visitor); Vec2d dir = (end - start).cast(); + for (Intersection &intersection : intersections) + intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx]; + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); + } + + std::vector result; + result.push_back({start, -1}); + + auto crossing_boundary_from_inside = [&boundary](const Point &start, const Intersection &intersection) { + const Polygon &poly = boundary.boundaries[intersection.border_idx]; + Vec2d poly_line = Line(poly[intersection.line_idx], poly[(intersection.line_idx + 1) % poly.size()]).normal().cast(); + Vec2d intersection_vec = (intersection.point - start).cast(); + return poly_line.normalized().dot(intersection_vec.normalized()) >= 0; + }; + + for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { + // The entry point to the boundary polygon + const Intersection &intersection_first = *it_first; + if(!crossing_boundary_from_inside(start, intersection_first)) + continue; + // Skip the it_first from the search for the farthest exit point from the boundary polygon + auto it_last_item = std::make_reverse_iterator(it_first) - 1; + // Search for the farthest intersection different from it_first but with the same border_idx + auto it_second_r = std::find_if(intersections.rbegin(), it_last_item, [&intersection_first](const Intersection &intersection) { + return intersection_first.border_idx == intersection.border_idx; + }); + + // Append the first intersection into the path + size_t left_idx = intersection_first.line_idx; + size_t right_idx = intersection_first.line_idx + 1 == boundaries[intersection_first.border_idx].points.size() ? 0 : intersection_first.line_idx + 1; + // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the + // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the + // appended point will be inside the polygon and not on the polygon border. + result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + + // Check if intersection line also exit the boundary polygon + if (it_second_r != it_last_item) { + // Transform reverse iterator to forward + auto it_second = it_second_r.base() - 1; + // The exit point from the boundary polygon + const Intersection &intersection_second = *it_second; + Direction shortest_direction = get_shortest_direction(boundary, intersection_first, intersection_second, + boundary.boundaries_params[intersection_first.border_idx].back()); + // Append the path around the border into the path + if (shortest_direction == Direction::Forward) + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); + line_idx = line_idx + 1 < int(boundaries[intersection_first.border_idx].size()) ? line_idx + 1 : 0) + result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + else + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); + line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(boundaries[intersection_first.border_idx].size()) - 1) + result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + + // Append the farthest intersection into the path + left_idx = intersection_second.line_idx; + right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); + result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)), int(intersection_second.border_idx)}); + // Skip intersections in between + it_first = it_second; + } + } + + result.push_back({end, -1}); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + if (! intersections.empty()) + result = simplify_travel(boundary, result); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + append(result_out, std::move(result)); + return intersections.size(); +} + +// Called by AvoidCrossingPerimeters::travel_to() +static size_t avoid_perimeters(const GCode &gcodegen, const AvoidCrossingPerimeters::Boundary &boundary, + const Point &start, + const Point &end, + Polyline &result_out) +{ + // Travel line is completely or partially inside the bounding box. + std::vector path; + size_t num_intersections = avoid_perimeters_inner(gcodegen, boundary, start, end, path); + result_out = to_polyline(path); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + return num_intersections; +} + +// Check if anyone of ExPolygons contains whole travel. +// called by need_wipe() and AvoidCrossingPerimeters::travel_to() +// FIXME Lukas H.: Maybe similar approach could also be used for ExPolygon::contains() +static bool any_expolygon_contains(const ExPolygons &ex_polygons, + const std::vector &ex_polygons_bboxes, + const EdgeGrid::Grid &grid_lslice, + const Line &travel) +{ + assert(ex_polygons.size() == ex_polygons_bboxes.size()); + if(!grid_lslice.bbox().contains(travel.a) || !grid_lslice.bbox().contains(travel.b)) + return false; + + FirstIntersectionVisitor visitor(grid_lslice); + visitor.pt_current = &travel.a; + visitor.pt_next = &travel.b; + grid_lslice.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + if (!visitor.intersect) { + for (const ExPolygon &ex_polygon : ex_polygons) { + const BoundingBox &bbox = ex_polygons_bboxes[&ex_polygon - &ex_polygons.front()]; + if (bbox.contains(travel.a) && bbox.contains(travel.b) && ex_polygon.contains(travel.a)) + return true; + } + } + return false; +} + +// Check if anyone of ExPolygons contains whole travel. +// called by need_wipe() +static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vector &ex_polygons_bboxes, const EdgeGrid::Grid &grid_lslice, const Polyline &travel) +{ + assert(ex_polygons.size() == ex_polygons_bboxes.size()); + if(std::any_of(travel.points.begin(), travel.points.end(), [&grid_lslice](const Point &point) { return !grid_lslice.bbox().contains(point); })) + return false; + + FirstIntersectionVisitor visitor(grid_lslice); + bool any_intersection = false; + for (size_t line_idx = 1; line_idx < travel.size(); ++line_idx) { + visitor.pt_current = &travel.points[line_idx - 1]; + visitor.pt_next = &travel.points[line_idx]; + grid_lslice.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + any_intersection = visitor.intersect; + if (any_intersection) break; + } + + if (!any_intersection) { + for (const ExPolygon &ex_polygon : ex_polygons) { + const BoundingBox &bbox = ex_polygons_bboxes[&ex_polygon - &ex_polygons.front()]; + if (std::all_of(travel.points.begin(), travel.points.end(), [&bbox](const Point &point) { return bbox.contains(point); }) && + ex_polygon.contains(travel.points.front())) + return true; + } + } + return false; +} + +static bool need_wipe(const GCode &gcodegen, + const EdgeGrid::Grid &grid_lslice, + const Line &original_travel, + const Polyline &result_travel, + const size_t intersection_count) +{ + const ExPolygons &lslices = gcodegen.layer()->lslices; + const std::vector &lslices_bboxes = gcodegen.layer()->lslices_bboxes; + bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; + bool wipe_needed = false; + + // If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely + // outside the object. + if (intersection_count > 0) { + // The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test. + // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. + if (z_lift_enabled) { + if (any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, original_travel)) { + // Check if original_travel and result_travel are not same. + // If both are the same, then it is possible to skip testing of result_travel + wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && + !any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, result_travel); + } else { + wipe_needed = true; + } + } else { + wipe_needed = !any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, result_travel); + } + } + + return wipe_needed; +} + +// called by get_perimeter_spacing() / get_perimeter_spacing_external() +static inline float get_default_perimeter_spacing(const PrintObject &print_object) +{ + std::vector printing_extruders = print_object.object_extruders(); + assert(!printing_extruders.empty()); + float avg_extruder = 0; + for(unsigned int extruder_id : printing_extruders) + avg_extruder += scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id)); + avg_extruder /= printing_extruders.size(); + return avg_extruder; +} + +// called by get_boundary() +static float get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const LayerRegion *layer_region : layer.regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); + return perimeter_spacing; +} + +// Adds points around all vertices so that the offset affects only small sections around these vertices. +static void resample_polygon(Polygon &polygon, double dist_from_vertex) +{ + Points resampled_poly; + resampled_poly.reserve(3 * polygon.size()); + resampled_poly.emplace_back(polygon.first_point()); + for (size_t pt_idx = 1; pt_idx < polygon.size(); ++pt_idx) { + const Point &p1 = polygon[pt_idx - 1]; + const Point &p2 = polygon[pt_idx]; + double line_length = (p2 - p1).cast().norm(); + Vector line_vec = ((p2 - p1).cast().normalized() * dist_from_vertex).cast(); + if (line_length > 2 * dist_from_vertex) { + resampled_poly.emplace_back(p1 + line_vec); + resampled_poly.emplace_back(p2 - line_vec); + } + resampled_poly.emplace_back(polygon[pt_idx]); + } + polygon.points = std::move(resampled_poly); +} + +static void resample_expolygon(ExPolygon &ex_polygon, double dist_from_vertex) +{ + resample_polygon(ex_polygon.contour, dist_from_vertex); + for (Polygon &polygon : ex_polygon.holes) resample_polygon(polygon, dist_from_vertex); +} + +static void resample_expolygons(ExPolygons &ex_polygons, double dist_from_vertex) +{ + for (ExPolygon &ex_poly : ex_polygons) resample_expolygon(ex_poly, dist_from_vertex); +} + +static void precompute_polygon_distances(const Polygon &polygon, std::vector &polygon_distances_out) +{ + polygon_distances_out.assign(polygon.size() + 1, 0.f); + for (size_t point_idx = 1; point_idx < polygon.size(); ++point_idx) + polygon_distances_out[point_idx] = polygon_distances_out[point_idx - 1] + (polygon[point_idx].cast() - polygon[point_idx - 1].cast()).norm(); + polygon_distances_out.back() = polygon_distances_out[polygon.size() - 1] + (polygon.last_point().cast() - polygon.first_point().cast()).norm(); +} + +static void precompute_expolygon_distances(const ExPolygon &ex_polygon, std::vector> &expolygon_distances_out) +{ + expolygon_distances_out.assign(ex_polygon.holes.size() + 1, std::vector()); + precompute_polygon_distances(ex_polygon.contour, expolygon_distances_out.front()); + for (size_t hole_idx = 0; hole_idx < ex_polygon.holes.size(); ++hole_idx) + precompute_polygon_distances(ex_polygon.holes[hole_idx], expolygon_distances_out[hole_idx + 1]); +} + +// It is highly based on the function contour_distance2 from the ElephantFootCompensation.cpp +static std::vector contour_distance(const EdgeGrid::Grid &grid, + const std::vector &poly_distances, + const size_t contour_idx, + const Polygon &polygon, + double compensation, + double search_radius) +{ + assert(! polygon.empty()); + assert(polygon.size() >= 2); + + std::vector out; + + if (polygon.size() > 2) + { + struct Visitor { + Visitor(const EdgeGrid::Grid &grid, const size_t contour_idx, const std::vector &polygon_distances, double dist_same_contour_accept, double dist_same_contour_reject) : + grid(grid), idx_contour(contour_idx), contour(*grid.contours()[contour_idx]), boundary_parameters(polygon_distances), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {} + + void init(const Points &contour, const Point &apoint) + { + this->idx_point = &apoint - contour.data(); + this->point = apoint; + this->found = false; + this->dir_inside = this->dir_inside_at_point(contour, this->idx_point); + this->distance = std::numeric_limits::max(); + } + + bool operator()(coord_t iy, coord_t ix) + { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = this->grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; + ++it_contour_and_segment) { + // End points of the line segment and their vector. + std::pair segment = this->grid.segment(*it_contour_and_segment); + const Vec2d v = (segment.second - segment.first).cast(); + const Vec2d va = (this->point - segment.first).cast(); + const double l2 = v.squaredNorm(); // avoid a sqrt + const double t = (l2 == 0.0) ? 0. : clamp(0., 1., va.dot(v) / l2); + // Closest point from this->point to the segment. + const Vec2d foot = segment.first.cast() + t * v; + const Vec2d bisector = foot - this->point.cast(); + const double dist = bisector.norm(); + + if ((!this->found || dist < this->distance) && this->dir_inside.dot(bisector) > 0) { + bool accept = true; + if (it_contour_and_segment->first == idx_contour) { + // Complex case: The closest segment originates from the same contour as the starting point. + // Reject the closest point if its distance along the contour is reasonable compared to the current contour bisector + // (this->pt, foot). + const Slic3r::Points &ipts = *grid.contours()[it_contour_and_segment->first]; + double param_lo = boundary_parameters[this->idx_point]; + double param_hi = t * sqrt(l2); + double param_end = boundary_parameters.back(); + const size_t ipt = it_contour_and_segment->second; + if (ipt + 1 < ipts.size()) + param_hi += boundary_parameters[ipt > 0 ? ipt - 1 : 0]; + if (param_lo > param_hi) + std::swap(param_lo, param_hi); + assert(param_lo > -SCALED_EPSILON && param_lo <= param_end + SCALED_EPSILON); + assert(param_hi > -SCALED_EPSILON && param_hi <= param_end + SCALED_EPSILON); + double dist_along_contour = std::min(param_hi - param_lo, param_lo + param_end - param_hi); + if (dist_along_contour < dist_same_contour_accept) + accept = false; + else if (dist < dist_same_contour_reject + SCALED_EPSILON) { + // this->point is close to foot. This point will only be accepted if the path along the contour is significantly + // longer than the bisector. That is, the path shall not bulge away from the bisector too much. + // Bulge is estimated by 0.6 of the circle circumference drawn around the bisector. + // Test whether the contour is convex or concave. + bool inside = (t == 0.) ? this->inside_corner(ipts, ipt, this->point) : + (t == 1.) ? this->inside_corner(ipts, ipt + 1 == ipts.size() ? 0 : ipt + 1, this->point) : + this->left_of_segment(ipts, ipt, this->point); + accept = inside && dist_along_contour > 0.6 * M_PI * dist; + } + } + if (accept && (!this->found || dist < this->distance)) { + // Simple case: Just measure the shortest distance. + this->distance = dist; + this->found = true; + } + } + } + // Continue traversing the grid. + return true; + } + + const EdgeGrid::Grid &grid; + const size_t idx_contour; + const Points &contour; + + const std::vector &boundary_parameters; + const double dist_same_contour_accept; + const double dist_same_contour_reject; + + size_t idx_point; + Point point; + // Direction inside the contour from idx_point, not normalized. + Vec2d dir_inside; + bool found; + double distance; + + private: + static Vec2d dir_inside_at_point(const Points &contour, size_t i) + { + size_t iprev = prev_idx_modulo(i, contour); + size_t inext = next_idx_modulo(i, contour); + Vec2d v1 = (contour[i] - contour[iprev]).cast(); + Vec2d v2 = (contour[inext] - contour[i]).cast(); + return Vec2d(-v1.y() - v2.y(), v1.x() + v2.x()); + } + + static bool inside_corner(const Slic3r::Points &contour, size_t i, const Point &pt_oposite) + { + const Vec2d pt = pt_oposite.cast(); + size_t iprev = prev_idx_modulo(i, contour); + size_t inext = next_idx_modulo(i, contour); + Vec2d v1 = (contour[i] - contour[iprev]).cast(); + Vec2d v2 = (contour[inext] - contour[i]).cast(); + bool left_of_v1 = cross2(v1, pt - contour[iprev].cast()) > 0.; + bool left_of_v2 = cross2(v2, pt - contour[i].cast()) > 0.; + return cross2(v1, v2) > 0 ? left_of_v1 && left_of_v2 : // convex corner + left_of_v1 || left_of_v2; // concave corner + } + + static bool left_of_segment(const Slic3r::Points &contour, size_t i, const Point &pt_oposite) + { + const Vec2d pt = pt_oposite.cast(); + size_t inext = next_idx_modulo(i, contour); + Vec2d v = (contour[inext] - contour[i]).cast(); + return cross2(v, pt - contour[i].cast()) > 0.; + } + } visitor(grid, contour_idx, poly_distances, 0.5 * compensation * M_PI, search_radius); + + out.reserve(polygon.size()); + Point radius_vector(search_radius, search_radius); + for (const Point &pt : polygon.points) { + visitor.init(polygon.points, pt); + grid.visit_cells_intersecting_box(BoundingBox(pt - radius_vector, pt + radius_vector), visitor); + out.emplace_back(float(visitor.found ? std::min(visitor.distance, search_radius) : search_radius)); + } + } + + return out; +} + +// Polygon offset which ensures that if a polygon breaks up into several separate parts, the original polygon will be used in these places. +static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset, double min_contour_width = scale_(0.001)) +{ + double search_radius = 2. * (offset + min_contour_width); + ExPolygons ex_poly_result = ex_polygons; + resample_expolygons(ex_poly_result, offset / 2); + + for (ExPolygon &ex_poly : ex_poly_result) { + BoundingBox bbox(get_extents(ex_poly)); + bbox.offset(SCALED_EPSILON); + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(ex_poly, coord_t(0.7 * search_radius)); + + std::vector> ex_poly_distances; + precompute_expolygon_distances(ex_poly, ex_poly_distances); + + std::vector> offsets; + offsets.reserve(ex_poly.holes.size() + 1); + for (size_t idx_contour = 0; idx_contour <= ex_poly.holes.size(); ++idx_contour) { + const Polygon &poly = (idx_contour == 0) ? ex_poly.contour : ex_poly.holes[idx_contour - 1]; + assert(poly.is_counter_clockwise() == (idx_contour == 0)); + std::vector distances = contour_distance(grid, ex_poly_distances[idx_contour], idx_contour, poly, offset, search_radius); + for (float &distance : distances) { + if (distance < min_contour_width) + distance = 0.f; + else if (distance > min_contour_width + 2. * offset) + distance = - float(offset); + else + distance = - (distance - float(min_contour_width)) / 2.f; + } + offsets.emplace_back(distances); + } + + ExPolygons offset_ex_poly = variable_offset_inner_ex(ex_poly, offsets); + // If variable_offset_inner_ex produces empty result, then original ex_polygon is used + if (offset_ex_poly.size() == 1) { + ex_poly = std::move(offset_ex_poly.front()); + } else if (offset_ex_poly.size() > 1) { + // fix_after_inner_offset called inside variable_offset_inner_ex sometimes produces + // tiny artefacts polygons, so these artefacts are removed. + double max_area = offset_ex_poly.front().area(); + size_t max_area_idx = 0; + for (size_t poly_idx = 1; poly_idx < offset_ex_poly.size(); ++poly_idx) { + double area = offset_ex_poly[poly_idx].area(); + if (max_area < area) { + max_area = area; + max_area_idx = poly_idx; + } + } + ex_poly = std::move(offset_ex_poly[max_area_idx]); + } + } + return ex_poly_result; +} + +// called by AvoidCrossingPerimeters::travel_to() +static ExPolygons get_boundary(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + size_t polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + polygons_count += layer_region->slices.surfaces.size(); + + ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(boundary, offset_ex(top_layer_polygons, -perimeter_offset)); + } + + return boundary; +} + +static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary) +{ + boundary->boundaries_params.assign(boundary->boundaries.size(), std::vector()); + for (size_t poly_idx = 0; poly_idx < boundary->boundaries.size(); ++poly_idx) + precompute_polygon_distances(boundary->boundaries[poly_idx], boundary->boundaries_params[poly_idx]); +} + +// Plan travel, which avoids perimeter crossings by following the boundaries of the layer. +Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) +{ + // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). + // Otherwise perform the path planning in the coordinate system of the active object. + bool use_external = m_use_external_mp || m_use_external_mp_once; + Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); + const Point start = gcodegen.last_pos() + scaled_origin; + const Point end = point + scaled_origin; + const Line travel(start, end); + + Polyline result_pl; + size_t travel_intersection_count = 0; + Vec2d startf = start.cast(); + Vec2d endf = end .cast(); + + if (!use_external && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) { + // Initialize m_internal only when it is necessary. + if (m_internal.boundaries.empty()) { + m_internal.boundaries_params.clear(); + m_internal.boundaries = to_polygons(get_boundary(*gcodegen.layer())); + + BoundingBox bbox(get_extents(m_internal.boundaries)); + bbox.offset(SCALED_EPSILON); + m_internal.bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + m_internal.grid.set_bbox(bbox); + // FIXME 1mm grid? + m_internal.grid.create(m_internal.boundaries, coord_t(scale_(1.))); + init_boundary_distances(&m_internal); + } + + // Trim the travel line by the bounding box. + if (Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { + travel_intersection_count = avoid_perimeters(gcodegen, m_internal, startf.cast(), endf.cast(), result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; + } else { + // Travel line is completely outside the bounding box. + result_pl = {start, end}; + travel_intersection_count = 0; + } + } else { + // Travel line is completely outside the bounding box. + result_pl = {start, end}; + travel_intersection_count = 0; + } + + double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); + if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length) + result_pl = {start, end}; + + if (use_external) { + result_pl.translate(-scaled_origin); + *could_be_wipe_disabled = false; + } else + *could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, travel, result_pl, travel_intersection_count); + + return result_pl; +} + +// ************************************* AvoidCrossingPerimeters::init_layer() ***************************************** + +void AvoidCrossingPerimeters::init_layer(const Layer &layer) +{ + m_internal.boundaries.clear(); + m_internal.boundaries_params.clear(); + BoundingBox bbox_slice(get_extents(layer.lslices)); + bbox_slice.offset(SCALED_EPSILON); + + m_grid_lslice.set_bbox(bbox_slice); + //FIXME 1mm grid? + m_grid_lslice.create(layer.lslices, coord_t(scale_(1.))); +} + +#if 0 +static double travel_length(const std::vector &travel) { + double total_length = 0; + for (size_t idx = 1; idx < travel.size(); ++idx) + total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); + + return total_length; +} + +// Called by avoid_perimeters() and by simplify_travel_heuristics(). +static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary, + const Point &start, + const Point &end, + std::vector &result_out) +{ + const Polygons &boundaries = boundary.boundaries; + const EdgeGrid::Grid &edge_grid = boundary.grid; + // Find all intersections between boundaries and the line segment, sort them along the line segment. + std::vector intersections; + { + intersections.reserve(boundaries.size()); + AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); + edge_grid.visit_cells_intersecting_line(start, end, visitor); + Vec2d dir = (end - start).cast(); + for (Intersection &intersection : intersections) + intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx]; std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); } @@ -309,18 +930,17 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, auto it_second = it_second_r.base() - 1; // The exit point from the boundary polygon const Intersection &intersection_second = *it_second; - Lines border_lines = boundaries[intersection_first.border_idx].lines(); - - Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point); + Direction shortest_direction = get_shortest_direction(boundary, intersection_first, intersection_second, + boundary.boundaries_params[intersection_first.border_idx].back()); // Append the path around the border into the path if (shortest_direction == Direction::Forward) for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); - line_idx = line_idx + 1 < int(border_lines.size()) ? line_idx + 1 : 0) + line_idx = line_idx + 1 < int(boundaries[intersection_first.border_idx].size()) ? line_idx + 1 : 0) result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); else for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); - line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(border_lines.size()) - 1) + line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(boundaries[intersection_first.border_idx].size()) - 1) result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); // Append the farthest intersection into the path @@ -343,7 +963,7 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ if (! intersections.empty()) - result = simplify_travel(edge_grid, result); + result = simplify_travel(boundary, result); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { @@ -357,13 +977,12 @@ static size_t avoid_perimeters_inner(const Polygons &boundaries, return intersections.size(); } -static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries) +static std::vector simplify_travel_heuristics(const AvoidCrossingPerimeters::Boundary &boundary, + const std::vector &travel) { std::vector simplified_path; std::vector intersections; - AllIntersectionsVisitor visitor(edge_grid, intersections); + AllIntersectionsVisitor visitor(boundary.grid, intersections); simplified_path.reserve(travel.size()); simplified_path.emplace_back(travel.front()); for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { @@ -399,7 +1018,7 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid visitor.reset(); visitor.travel_line.a = current.point; visitor.travel_line.b = possible_new_next.point; - edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); + boundary.grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); if (!intersections.empty()) { Vec2d dir = (visitor.travel_line.b - visitor.travel_line.a).cast(); std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); @@ -412,7 +1031,7 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid continue; std::vector possible_shortcut; - avoid_perimeters_inner(boundaries, edge_grid, current.point, possible_new_next.point, possible_shortcut); + avoid_perimeters_inner(boundary, current.point, possible_new_next.point, possible_shortcut); double shortcut_travel = travel_length(possible_shortcut); if (path_length > shortcut_travel && path_length - shortcut_travel > new_path_shorter_by) { new_path_shorter_by = path_length - shortcut_travel; @@ -437,19 +1056,18 @@ static std::vector simplify_travel_heuristics(const EdgeGrid::Grid } // Called by AvoidCrossingPerimeters::travel_to() -static size_t avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, +static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary, const Point &start, const Point &end, Polyline &result_out) { // Travel line is completely or partially inside the bounding box. std::vector path; - size_t num_intersections = avoid_perimeters_inner(boundaries, edge_grid, start, end, path); + size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path); if (num_intersections) { - path = simplify_travel_heuristics(edge_grid, path, boundaries); + path = simplify_travel_heuristics(boundary, path); std::reverse(path.begin(), path.end()); - path = simplify_travel_heuristics(edge_grid, path, boundaries); + path = simplify_travel_heuristics(boundary, path); std::reverse(path.begin(), path.end()); } @@ -465,48 +1083,6 @@ static size_t avoid_perimeters(const Polygons &boundaries, return num_intersections; } -// Check if anyone of ExPolygons contains whole travel. -// called by need_wipe() -template static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) -{ - //FIXME filter by bounding boxes! - for (const ExPolygon &ex_polygon : ex_polygons) - if (ex_polygon.contains(travel)) - return true; - return false; -} - -static bool need_wipe(const GCode &gcodegen, - const ExPolygons &slice, - const Line &original_travel, - const Polyline &result_travel, - const size_t intersection_count) -{ - bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; - bool wipe_needed = false; - - // If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely - // outside the object. - if (intersection_count > 0) { - // The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test. - // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. - if (z_lift_enabled) { - if (any_expolygon_contains(slice, original_travel)) { - // Check if original_travel and result_travel are not same. - // If both are the same, then it is possible to skip testing of result_travel - wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && - !any_expolygon_contains(slice, result_travel); - } else { - wipe_needed = true; - } - } else { - wipe_needed = !any_expolygon_contains(slice, result_travel); - } - } - - return wipe_needed; -} - // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) { @@ -521,14 +1097,13 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & Vec2d startf = start.cast(); Vec2d endf = end .cast(); // Trim the travel line by the bounding box. - if (Geometry::liang_barsky_line_clipping(startf, endf, use_external ? m_bbox_external : m_bbox)) { + if (Geometry::liang_barsky_line_clipping(startf, endf, (use_external ? m_external : m_internal).bbox)) { // Travel line is completely or partially inside the bounding box. //FIXME initialize m_boundaries / m_boundaries_external on demand? - travel_intersection_count = use_external ? - avoid_perimeters(m_boundaries_external, m_grid_external, startf.cast(), endf.cast(), result_pl) : - avoid_perimeters(m_boundaries, m_grid, startf.cast(), endf.cast(), result_pl); - result_pl.points.front() = start; - result_pl.points.back() = end; + travel_intersection_count = avoid_perimeters((use_external ? m_external : m_internal), startf.cast(), endf.cast(), + result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; } else { // Travel line is completely outside the bounding box. result_pl = {start, end}; @@ -544,41 +1119,11 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & result_pl.translate(-scaled_origin); *could_be_wipe_disabled = false; } else - *could_be_wipe_disabled = !need_wipe(gcodegen, m_slice, travel, result_pl, travel_intersection_count); + *could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, travel, result_pl, travel_intersection_count); return result_pl; } -// ************************************* AvoidCrossingPerimeters::init_layer() ***************************************** - -// called by get_perimeter_spacing() / get_perimeter_spacing_external() -static inline float get_default_perimeter_spacing(const Print &print) -{ - //FIXME better use extruders printing this PrintObject or this Print? - //FIXME maybe better use an average of printing extruders? - const std::vector &nozzle_diameters = print.config().nozzle_diameter.values; - return float(scale_(*std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()))); -} - -// called by get_boundary() -static float get_perimeter_spacing(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - //FIXME not all regions are printing. Collect only non-empty regions? - for (const LayerRegion *layer_region : layer.regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++ regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); - return perimeter_spacing; -} - // called by get_boundary_external() static float get_perimeter_spacing_external(const Layer &layer) { @@ -587,18 +1132,18 @@ static float get_perimeter_spacing_external(const Layer &layer) for (const PrintObject *object : layer.object()->print()->objects()) //FIXME with different layering, layers on other objects will not be found at this object's print_z. // Search an overlap of layers? - if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) - //FIXME not all regions are printing. Collect only non-empty regions? - for (const LayerRegion *layer_region : l->regions()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++ regions_count; - } + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } assert(perimeter_spacing >= 0.f); if (regions_count != 0) perimeter_spacing /= float(regions_count); else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()->print()); + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); return perimeter_spacing; } @@ -651,9 +1196,7 @@ static ExPolygons get_boundary(const Layer &layer) auto [contours, holes] = split_expolygon(boundary); // Add an outer boundary to avoid crossing perimeters from supports ExPolygons outer_boundary = union_ex( - //FIXME flip order of offset and convex_hull - diff(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset))); + diff(offset(Geometry::convex_hull(contours), 2.f * perimeter_spacing), offset(contours, perimeter_spacing + perimeter_offset))); result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); @@ -690,7 +1233,7 @@ static ExPolygons get_boundary_external(const Layer &layer) ExPolygons polygons_per_obj; //FIXME with different layering, layers on other objects will not be found at this object's print_z. // Search an overlap of layers? - if (const Layer* l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + if (const Layer* l = object->get_layer_at_printz(layer.print_z, EPSILON); l) for (const LayerRegion *layer_region : l->regions()) for (const Surface &surface : layer_region->slices.surfaces) polygons_per_obj.emplace_back(surface.expolygon); @@ -707,9 +1250,7 @@ static ExPolygons get_boundary_external(const Layer &layer) // Polygons in which is possible traveling without crossing perimeters of another object. // A convex hull allows removing unnecessary detour caused by following the boundary of the object. ExPolygons result_boundary = - //FIXME flip order of offset and convex_hull - diff_ex(static_cast(Geometry::convex_hull(offset(contours, 2.f * perimeter_spacing))), - offset(contours, perimeter_spacing + perimeter_offset)); + diff_ex(offset(Geometry::convex_hull(contours), 2.f * perimeter_spacing),offset(contours, perimeter_spacing + perimeter_offset)); // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. append(result_boundary, diff_ex(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); return union_ex(result_boundary); @@ -717,31 +1258,35 @@ static ExPolygons get_boundary_external(const Layer &layer) void AvoidCrossingPerimeters::init_layer(const Layer &layer) { - m_slice.clear(); - m_boundaries.clear(); - m_boundaries_external.clear(); + m_internal.boundaries.clear(); + m_external.boundaries.clear(); - for (const LayerRegion *layer_region : layer.regions()) - //FIXME making copies? - append(m_slice, (ExPolygons) layer_region->slices); + m_internal.boundaries = to_polygons(get_boundary(layer)); + m_external.boundaries = to_polygons(get_boundary_external(layer)); - m_boundaries = to_polygons(get_boundary(layer)); - m_boundaries_external = to_polygons(get_boundary_external(layer)); - - BoundingBox bbox(get_extents(m_boundaries)); + BoundingBox bbox(get_extents(m_internal.boundaries)); bbox.offset(SCALED_EPSILON); - BoundingBox bbox_external = get_extents(m_boundaries_external); + BoundingBox bbox_external = get_extents(m_external.boundaries); bbox_external.offset(SCALED_EPSILON); + BoundingBox bbox_slice(get_extents(layer.lslices)); + bbox_slice.offset(SCALED_EPSILON); - m_bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); - m_bbox_external = BoundingBoxf(bbox_external.min.cast(), bbox_external.max.cast()); + m_internal.bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + m_external.bbox = BoundingBoxf(bbox_external.min.cast(), bbox_external.max.cast()); - m_grid.set_bbox(bbox); - //FIXME 1mm grid? - m_grid.create(m_boundaries, coord_t(scale_(1.))); - m_grid_external.set_bbox(bbox_external); - //FIXME 1mm grid? - m_grid_external.create(m_boundaries_external, coord_t(scale_(1.))); + m_internal.grid.set_bbox(bbox); + //FIX1ME 1mm grid? + m_internal.grid.create(m_internal.boundaries, coord_t(scale_(1.))); + m_external.grid.set_bbox(bbox_external); + //FIX1ME 1mm grid? + m_external.grid.create(m_external.boundaries, coord_t(scale_(1.))); + m_grid_lslice.set_bbox(bbox_slice); + //FIX1ME 1mm grid? + m_grid_lslice.create(layer.lslices, coord_t(scale_(1.))); + + init_boundary_distances(&m_internal); + init_boundary_distances(&m_external); } +#endif } // namespace Slic3r diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index bdae775a1..1ed1f0026 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -32,6 +32,17 @@ public: Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled); + struct Boundary { + // Collection of boundaries used for detection of crossing perimeters for travels + Polygons boundaries; + // Bounding box of boundaries + BoundingBoxf bbox; + // Precomputed distances of all points in boundaries + std::vector> boundaries_params; + // Used for detection of intersection between line and any polygon from boundaries + EdgeGrid::Grid grid; + }; + private: bool m_use_external_mp { false }; // just for the next travel move @@ -40,18 +51,14 @@ private: // we enable it by default for the first travel move in print bool m_disabled_once { true }; - // Slice of layer with elephant foot compensation - ExPolygons m_slice; - // Collection of boundaries used for detection of crossing perimetrs for travels inside object - Polygons m_boundaries; - // Collection of boundaries used for detection of crossing perimetrs for travels outside object - Polygons m_boundaries_external; - // Bounding box of m_boundaries - BoundingBoxf m_bbox; - // Bounding box of m_boundaries_external - BoundingBoxf m_bbox_external; - EdgeGrid::Grid m_grid; - EdgeGrid::Grid m_grid_external; + // Used for detection of line or polyline is inside of any polygon. + EdgeGrid::Grid m_grid_lslice; + // Store all needed data for travels inside object + Boundary m_internal; +#if 0 + // Store all needed data for travels outside object + Boundary m_external; +#endif }; } // namespace Slic3r diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index e74005993..fce401a93 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -124,7 +124,7 @@ inline void append(std::vector& dest, std::vector&& src) if (dest.empty()) dest = std::move(src); else { - dest.resize(dest.size() + src.size()); + dest.reserve(dest.size() + src.size()); std::move(std::begin(src), std::end(src), std::back_inserter(dest)); } src.clear(); From f16e8f1a722962c37f88a0cb0fde36580accc6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 29 Nov 2020 14:00:34 +0100 Subject: [PATCH 32/35] Fixed uninitialized variable in ElephantFootCompensation --- src/libslic3r/ElephantFootCompensation.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp index c11157627..1e50ade5a 100644 --- a/src/libslic3r/ElephantFootCompensation.cpp +++ b/src/libslic3r/ElephantFootCompensation.cpp @@ -254,11 +254,12 @@ std::vector contour_distance2(const EdgeGrid::Grid &grid, const size_t id grid(grid), idx_contour(idx_contour), contour(*grid.contours()[idx_contour]), resampled_point_parameters(resampled_point_parameters), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {} void init(const Points &contour, const Point &apoint) { - this->idx_point = &apoint - contour.data(); - this->point = apoint; - this->found = false; - this->dir_inside = this->dir_inside_at_point(contour, this->idx_point); - } + this->idx_point = &apoint - contour.data(); + this->point = apoint; + this->found = false; + this->dir_inside = this->dir_inside_at_point(contour, this->idx_point); + this->distance = std::numeric_limits::max(); + } bool operator()(coord_t iy, coord_t ix) { // Called with a row and colum of the grid cell, which is intersected by a line. From dd97eaa8125220f81fdd8a41c6ccddb1a743b068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 29 Nov 2020 17:26:02 +0100 Subject: [PATCH 33/35] Fixed case when lslices in Layer is empty --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index ff33813cf..ec5418348 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -491,7 +491,7 @@ static inline float get_default_perimeter_spacing(const PrintObject &print_objec assert(!printing_extruders.empty()); float avg_extruder = 0; for(unsigned int extruder_id : printing_extruders) - avg_extruder += scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id)); + avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id))); avg_extruder /= printing_extruders.size(); return avg_extruder; } @@ -817,7 +817,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & Vec2d startf = start.cast(); Vec2d endf = end .cast(); - if (!use_external && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) { + if (!use_external && !gcodegen.layer()->lslices.empty() && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) { // Initialize m_internal only when it is necessary. if (m_internal.boundaries.empty()) { m_internal.boundaries_params.clear(); From f917b83706cff2c3ccaedde222542242aefd731f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Sun, 29 Nov 2020 17:29:11 +0100 Subject: [PATCH 34/35] Fixed compiler warnings --- src/libslic3r/GCode/AvoidCrossingPerimeters.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index ec5418348..7ce3cebc3 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -122,10 +122,6 @@ static Point find_first_different_vertex(const Polygon &polygon, const size_t po return polygon.points[line_idx]; } -//FIXME will be in Point.h in the master -template -inline Eigen::Matrix perp(const Eigen::MatrixBase>& v) { return Eigen::Matrix(-v.y(), v.x()); } - static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { assert(left != middle); @@ -223,11 +219,11 @@ static Direction get_shortest_direction(const AvoidCrossingPerimeters::Boundary float total_length_backward = dist_first + contour_length - dist_second; if (reversed) std::swap(total_length_forward, total_length_backward); - total_length_forward -= (intersection_first.point - poly[intersection_first.line_idx]).cast().norm(); - total_length_backward -= (poly[(intersection_first.line_idx + 1) % poly.size()] - intersection_first.point).cast().norm(); + total_length_forward -= (intersection_first.point - poly[intersection_first.line_idx]).cast().norm(); + total_length_backward -= (poly[(intersection_first.line_idx + 1) % poly.size()] - intersection_first.point).cast().norm(); - total_length_forward -= (poly[(intersection_second.line_idx + 1) % poly.size()] - intersection_second.point).cast().norm(); - total_length_backward -= (intersection_second.point - poly[intersection_second.line_idx]).cast().norm(); + total_length_forward -= (poly[(intersection_second.line_idx + 1) % poly.size()] - intersection_second.point).cast().norm(); + total_length_backward -= (intersection_second.point - poly[intersection_second.line_idx]).cast().norm(); if (total_length_forward < total_length_backward) return Direction::Forward; return Direction::Backward; From 98055de28c29a1e572163f601b6cea335ebeb2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 3 Dec 2020 09:43:21 +0100 Subject: [PATCH 35/35] External paths avoid crossing perimeters of holes --- .../GCode/AvoidCrossingPerimeters.cpp | 133 +++++++++++------- .../GCode/AvoidCrossingPerimeters.hpp | 8 +- 2 files changed, 91 insertions(+), 50 deletions(-) diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 7ce3cebc3..8262c3857 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -511,6 +511,27 @@ static float get_perimeter_spacing(const Layer &layer) return perimeter_spacing; } +// called by get_boundary_external() +static float get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const PrintObject *object : layer.object()->print()->objects()) + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); + return perimeter_spacing; +} + // Adds points around all vertices so that the offset affects only small sections around these vertices. static void resample_polygon(Polygon &polygon, double dist_from_vertex) { @@ -765,13 +786,9 @@ static ExPolygons get_boundary(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; - size_t polygons_count = 0; - for (const LayerRegion *layer_region : layer.regions()) - polygons_count += layer_region->slices.surfaces.size(); - - ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); + ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); // Collect all top layers that will not be crossed. - polygons_count = 0; + size_t polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) for (const Surface &surface : layer_region->fill_surfaces.surfaces) if (surface.is_top()) ++polygons_count; @@ -790,6 +807,35 @@ static ExPolygons get_boundary(const Layer &layer) return boundary; } +// called by AvoidCrossingPerimeters::travel_to() +static Polygons get_boundary_external(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + Polygons boundary; + // Collect all holes for all printed objects and their instances, which will be printed at the same time as passed "layer". + for (const PrintObject *object : layer.object()->print()->objects()) { + Polygons polygons_per_obj; + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const ExPolygon &island : l->lslices) append(polygons_per_obj, island.holes); + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + append(boundary, polygons_per_obj); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift); + } + } + + // Used offset_ex for cases when another object will be in the hole of another polygon + boundary = to_polygons(offset_ex(boundary, perimeter_offset)); + // Reverse all polygons for making normals point from the polygon out. + for (Polygon &poly : boundary) + poly.reverse(); + + return boundary; +} + static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary) { boundary->boundaries_params.assign(boundary->boundaries.size(), std::vector()); @@ -797,6 +843,20 @@ static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary) precompute_polygon_distances(boundary->boundaries[poly_idx], boundary->boundaries_params[poly_idx]); } +static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons) +{ + boundary->clear(); + boundary->boundaries = std::move(boundary_polygons); + + BoundingBox bbox(get_extents(boundary->boundaries)); + bbox.offset(SCALED_EPSILON); + boundary->bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + boundary->grid.set_bbox(bbox); + // FIXME 1mm grid? + boundary->grid.create(boundary->boundaries, coord_t(scale_(1.))); + init_boundary_distances(boundary); +} + // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) { @@ -815,30 +875,29 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & if (!use_external && !gcodegen.layer()->lslices.empty() && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) { // Initialize m_internal only when it is necessary. - if (m_internal.boundaries.empty()) { - m_internal.boundaries_params.clear(); - m_internal.boundaries = to_polygons(get_boundary(*gcodegen.layer())); - - BoundingBox bbox(get_extents(m_internal.boundaries)); - bbox.offset(SCALED_EPSILON); - m_internal.bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); - m_internal.grid.set_bbox(bbox); - // FIXME 1mm grid? - m_internal.grid.create(m_internal.boundaries, coord_t(scale_(1.))); - init_boundary_distances(&m_internal); - } + if (m_internal.boundaries.empty()) + init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer()))); // Trim the travel line by the bounding box. if (Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { travel_intersection_count = avoid_perimeters(gcodegen, m_internal, startf.cast(), endf.cast(), result_pl); result_pl.points.front() = start; result_pl.points.back() = end; - } else { - // Travel line is completely outside the bounding box. - result_pl = {start, end}; - travel_intersection_count = 0; } - } else { + } else if(use_external) { + // Initialize m_external only when exist any external travel for the current layer. + if (m_external.boundaries.empty()) + init_boundary(&m_external, get_boundary_external(*gcodegen.layer())); + + // Trim the travel line by the bounding box. + if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) { + travel_intersection_count = avoid_perimeters(gcodegen, m_external, startf.cast(), endf.cast(), result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; + } + } + + if(result_pl.empty()) { // Travel line is completely outside the bounding box. result_pl = {start, end}; travel_intersection_count = 0; @@ -861,8 +920,9 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & void AvoidCrossingPerimeters::init_layer(const Layer &layer) { - m_internal.boundaries.clear(); - m_internal.boundaries_params.clear(); + m_internal.clear(); + m_external.clear(); + BoundingBox bbox_slice(get_extents(layer.lslices)); bbox_slice.offset(SCALED_EPSILON); @@ -1120,29 +1180,6 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & return result_pl; } -// called by get_boundary_external() -static float get_perimeter_spacing_external(const Layer &layer) -{ - size_t regions_count = 0; - float perimeter_spacing = 0.f; - for (const PrintObject *object : layer.object()->print()->objects()) - //FIXME with different layering, layers on other objects will not be found at this object's print_z. - // Search an overlap of layers? - if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) - for (const LayerRegion *layer_region : l->regions()) - if (layer_region != nullptr && !layer_region->slices.empty()) { - perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); - ++ regions_count; - } - - assert(perimeter_spacing >= 0.f); - if (regions_count != 0) - perimeter_spacing /= float(regions_count); - else - perimeter_spacing = get_default_perimeter_spacing(*layer.object()); - return perimeter_spacing; -} - // called by AvoidCrossingPerimeters::init_layer()->get_boundary()/get_boundary_external() static std::pair split_expolygon(const ExPolygons &ex_polygons) { diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index 1ed1f0026..03c420a32 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -41,6 +41,12 @@ public: std::vector> boundaries_params; // Used for detection of intersection between line and any polygon from boundaries EdgeGrid::Grid grid; + + void clear() + { + boundaries.clear(); + boundaries_params.clear(); + } }; private: @@ -55,10 +61,8 @@ private: EdgeGrid::Grid m_grid_lslice; // Store all needed data for travels inside object Boundary m_internal; -#if 0 // Store all needed data for travels outside object Boundary m_external; -#endif }; } // namespace Slic3r