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);