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] 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 666069014c..ff33813cfc 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 bdae775a1f..1ed1f00268 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 e740059937..fce401a93e 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();