From f206b743fd8195a41e9aa13d17f865a35fc97693 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Nov 2020 15:18:19 +0100 Subject: [PATCH] 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