#include "../Layer.hpp" #include "../GCode.hpp" #include "../EdgeGrid.hpp" #include "../Print.hpp" #include "../Polygon.hpp" #include "../ExPolygon.hpp" #include "../Geometry.hpp" #include "../ClipperUtils.hpp" #include "../SVG.hpp" #include "AvoidCrossingPerimeters.hpp" #include #include namespace Slic3r { struct TravelPoint { Point point; // Index of the polygon containing this point. A negative value indicates that the point is not on any border. int border_idx; }; struct Intersection { // Index of the polygon containing this point of intersection. size_t border_idx; // Index of the line on the polygon containing this point of intersection. size_t line_idx; // Point of intersection. 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) { intersection_set.reserve(intersections.capacity()); } 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(); } bool operator()(coord_t iy, coord_t ix) { // Called with a row and colum of the grid cell, which is intersected by a line. auto cell_data_range = grid.cell_data_range(iy, ix); for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { Point 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.push_back({ it_contour_and_segment->first, it_contour_and_segment->second, intersection_point }); intersection_set.insert(*it_contour_and_segment); } } // Continue traversing the grid along the edge. return true; } const EdgeGrid::Grid &grid; std::vector &intersections; Line travel_line; 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()); // 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 != 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 != 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]; } static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) { assert(left != middle); assert(middle != right); 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 = 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); return three_points_inward_normal(left, middle, right); } // Compute offset of point_idx of the polygon in a direction of inward normal static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) { return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); } // Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx). static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const coord_t offset) { 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 Polyline to_polyline(const std::vector &travel) { Polyline result; result.points.reserve(travel.size()); for (const TravelPoint &t_point : travel) result.append(t_point.point); return result; } // #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, const Polyline &result_travel, const std::vector &intersections, const std::string &path) { BoundingBox bbox = get_extents(boundary); ::Slic3r::SVG svg(path, bbox); svg.draw_outline(boundary, "green"); svg.draw(original_travel, "blue"); svg.draw(result_travel, "red"); svg.draw(original_travel.a, "black"); svg.draw(original_travel.b, "grey"); for (const Intersection &intersection : intersections) svg.draw(intersection.point, "lightseagreen"); } static void export_travel_to_svg(const Polygons &boundary, const Line &original_travel, const std::vector &result_travel, const std::vector &intersections, const std::string &path) { export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path); } #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ // Returns a direction of the shortest path along the polygon boundary enum class Direction { Forward, Backward }; // 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) { 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; assert(dist_first >= 0.f && dist_first <= contour_length); assert(dist_second >= 0.f && dist_second <= contour_length); 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); total_length_forward -= (intersection_first.point - poly[intersection_first.line_idx]).cast().norm(); total_length_backward -= (poly[(intersection_first.line_idx + 1) % poly.size()] - intersection_first.point).cast().norm(); total_length_forward -= (poly[(intersection_second.line_idx + 1) % poly.size()] - intersection_second.point).cast().norm(); total_length_backward -= (intersection_second.point - poly[intersection_second.line_idx]).cast().norm(); 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 AvoidCrossingPerimeters::Boundary &boundary, const std::vector &travel) { FirstIntersectionVisitor visitor(boundary.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; 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]; point_idx = point_idx_2; } } simplified_path.emplace_back(next); } return simplified_path; } // Called by avoid_perimeters() and by simplify_travel_heuristics(). 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 += float(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; } // called by get_boundary_external() static float get_perimeter_spacing_external(const Layer &layer) { size_t regions_count = 0; float perimeter_spacing = 0.f; for (const PrintObject *object : layer.object()->print()->objects()) if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) for (const LayerRegion *layer_region : l->regions()) if (layer_region != nullptr && !layer_region->slices.empty()) { perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); ++ regions_count; } assert(perimeter_spacing >= 0.f); if (regions_count != 0) perimeter_spacing /= float(regions_count); else perimeter_spacing = get_default_perimeter_spacing(*layer.object()); return perimeter_spacing; } // Adds points around all vertices so that the offset affects only small sections around these vertices. static void resample_polygon(Polygon &polygon, double dist_from_vertex) { 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; ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); // Collect all top layers that will not be crossed. size_t polygons_count = 0; for (const LayerRegion *layer_region : layer.regions()) for (const Surface &surface : layer_region->fill_surfaces.surfaces) if (surface.is_top()) ++polygons_count; 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; } // called by AvoidCrossingPerimeters::travel_to() static Polygons get_boundary_external(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; Polygons boundary; // Collect all holes for all printed objects and their instances, which will be printed at the same time as passed "layer". for (const PrintObject *object : layer.object()->print()->objects()) { Polygons polygons_per_obj; if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) for (const ExPolygon &island : l->lslices) append(polygons_per_obj, island.holes); for (const PrintInstance &instance : object->instances()) { size_t boundary_idx = boundary.size(); append(boundary, polygons_per_obj); for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift); } } // Used offset_ex for cases when another object will be in the hole of another polygon boundary = to_polygons(offset_ex(boundary, perimeter_offset)); // Reverse all polygons for making normals point from the polygon out. for (Polygon &poly : boundary) poly.reverse(); return boundary; } static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary) { boundary->boundaries_params.assign(boundary->boundaries.size(), std::vector()); 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]); } static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons) { boundary->clear(); boundary->boundaries = std::move(boundary_polygons); BoundingBox bbox(get_extents(boundary->boundaries)); bbox.offset(SCALED_EPSILON); boundary->bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); boundary->grid.set_bbox(bbox); // FIXME 1mm grid? boundary->grid.create(boundary->boundaries, coord_t(scale_(1.))); init_boundary_distances(boundary); } // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) { // 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 && !gcodegen.layer()->lslices.empty() && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) { // Initialize m_internal only when it is necessary. if (m_internal.boundaries.empty()) init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer()))); // Trim the travel line by the bounding box. if (Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { travel_intersection_count = avoid_perimeters(gcodegen, m_internal, startf.cast(), endf.cast(), result_pl); result_pl.points.front() = start; result_pl.points.back() = end; } } else if(use_external) { // Initialize m_external only when exist any external travel for the current layer. if (m_external.boundaries.empty()) init_boundary(&m_external, get_boundary_external(*gcodegen.layer())); // Trim the travel line by the bounding box. if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) { travel_intersection_count = avoid_perimeters(gcodegen, m_external, startf.cast(), endf.cast(), result_pl); result_pl.points.front() = start; result_pl.points.back() = end; } } if(result_pl.empty()) { // Travel line is completely outside the bounding box. result_pl = {start, end}; travel_intersection_count = 0; } 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.clear(); m_external.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.; }); } std::vector result; result.push_back({start, -1}); for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { // The entry point to the boundary polygon const Intersection &intersection_first = *it_first; // 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(); } static std::vector simplify_travel_heuristics(const AvoidCrossingPerimeters::Boundary &boundary, const std::vector &travel) { std::vector simplified_path; std::vector 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) { // Skip all indexes on the same polygon while (point_idx < travel.size() && travel[point_idx - 1].border_idx == travel[point_idx].border_idx) { simplified_path.emplace_back(travel[point_idx]); point_idx++; } if (point_idx < travel.size()) { const TravelPoint ¤t = travel[point_idx - 1]; const TravelPoint &next = travel[point_idx]; TravelPoint new_next = next; size_t new_point_idx = point_idx; double path_length = (next.point - current.point).cast().norm(); double new_path_shorter_by = 0.; size_t border_idx_change_count = 0; std::vector shortcut; for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { const TravelPoint &possible_new_next = travel[point_idx_2]; if (travel[point_idx_2 - 1].border_idx != travel[point_idx_2].border_idx) border_idx_change_count++; if (border_idx_change_count >= 2) break; path_length += (possible_new_next.point - travel[point_idx_2 - 1].point).cast().norm(); double shortcut_length = (possible_new_next.point - current.point).cast().norm(); if ((path_length - shortcut_length) <= scale_(10.0)) continue; intersections.clear(); visitor.reset(); visitor.travel_line.a = current.point; visitor.travel_line.b = possible_new_next.point; 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.; }); size_t last_border_idx_count = 0; for (const Intersection &intersection : intersections) if (int(intersection.border_idx) == possible_new_next.border_idx) ++last_border_idx_count; if (last_border_idx_count > 0) continue; std::vector possible_shortcut; avoid_perimeters_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; shortcut = possible_shortcut; new_next = possible_new_next; new_point_idx = point_idx_2; } } } if (!shortcut.empty()) { assert(shortcut.size() >= 2); simplified_path.insert(simplified_path.end(), shortcut.begin() + 1, shortcut.end() - 1); point_idx = new_point_idx; } simplified_path.emplace_back(new_next); } } return simplified_path; } // Called by AvoidCrossingPerimeters::travel_to() 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(boundary, start, end, path); if (num_intersections) { path = simplify_travel_heuristics(boundary, path); std::reverse(path.begin(), path.end()); path = simplify_travel_heuristics(boundary, path); std::reverse(path.begin(), path.end()); } result_out = to_polyline(path); #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT { 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 */ return num_intersections; } // 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); Point start = gcodegen.last_pos() + scaled_origin; Point end = point + scaled_origin; Polyline result_pl; size_t 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_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 = 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}; travel_intersection_count = 0; } 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 = {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; } // 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( 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()); 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 = 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); } void AvoidCrossingPerimeters::init_layer(const Layer &layer) { m_internal.boundaries.clear(); m_external.boundaries.clear(); m_internal.boundaries = to_polygons(get_boundary(layer)); m_external.boundaries = to_polygons(get_boundary_external(layer)); BoundingBox bbox(get_extents(m_internal.boundaries)); bbox.offset(SCALED_EPSILON); 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_internal.bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); m_external.bbox = BoundingBoxf(bbox_external.min.cast(), bbox_external.max.cast()); 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