mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	Avoid crossing perimeters: Further refactoring for clarity, code review.
This commit is contained in:
		
							parent
							
								
									62ab17bf6e
								
							
						
					
					
						commit
						656b90dbe5
					
				
					 8 changed files with 363 additions and 393 deletions
				
			
		|  | @ -238,7 +238,14 @@ public: | ||||||
| 	{ | 	{ | ||||||
| 		const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; | 		const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; | ||||||
| 		size_t ipt = contour_and_segment_idx.second; | 		size_t ipt = contour_and_segment_idx.second; | ||||||
| 		return std::pair<const Slic3r::Point&, const Slic3r::Point&>(ipts[ipt], ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]); | 		return std::pair<const Slic3r::Point&, const Slic3r::Point&>(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	Line line(const std::pair<size_t, size_t> &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: | protected: | ||||||
|  |  | ||||||
|  | @ -42,11 +42,11 @@ void ExPolygon::scale(double factor) | ||||||
|         hole.scale(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) |     for (Polygon &hole : holes) | ||||||
|         hole.translate(x, y); |         hole.translate(p); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ExPolygon::rotate(double angle) | void ExPolygon::rotate(double angle) | ||||||
|  |  | ||||||
|  | @ -42,7 +42,8 @@ public: | ||||||
|     operator Polylines() const; |     operator Polylines() const; | ||||||
|     void clear() { contour.points.clear(); holes.clear(); } |     void clear() { contour.points.clear(); holes.clear(); } | ||||||
|     void scale(double factor); |     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); | ||||||
|     void rotate(double angle, const Point ¢er); |     void rotate(double angle, const Point ¢er); | ||||||
|     double area() const; |     double area() const; | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| #include "../Print.hpp" | #include "../Print.hpp" | ||||||
| #include "../Polygon.hpp" | #include "../Polygon.hpp" | ||||||
| #include "../ExPolygon.hpp" | #include "../ExPolygon.hpp" | ||||||
|  | #include "../Geometry.hpp" | ||||||
| #include "../ClipperUtils.hpp" | #include "../ClipperUtils.hpp" | ||||||
| #include "../SVG.hpp" | #include "../SVG.hpp" | ||||||
| #include "AvoidCrossingPerimeters.hpp" | #include "AvoidCrossingPerimeters.hpp" | ||||||
|  | @ -16,7 +17,7 @@ namespace Slic3r { | ||||||
| struct TravelPoint | struct TravelPoint | ||||||
| { | { | ||||||
|     Point point; |     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; |     int   border_idx; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -26,17 +27,11 @@ struct Intersection | ||||||
|     size_t border_idx; |     size_t border_idx; | ||||||
|     // Index of the line on the polygon containing this point of intersection.
 |     // Index of the line on the polygon containing this point of intersection.
 | ||||||
|     size_t line_idx; |     size_t line_idx; | ||||||
|     // Point of intersection projected on the travel path.
 |  | ||||||
|     Point  point_transformed; |  | ||||||
|     // Point of intersection.
 |     // Point of intersection.
 | ||||||
|     Point  point; |     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 | struct AllIntersectionsVisitor | ||||||
| { | { | ||||||
|     AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector<Intersection> &intersections) |     AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector<Intersection> &intersections) | ||||||
|  | @ -45,9 +40,8 @@ struct AllIntersectionsVisitor | ||||||
| 
 | 
 | ||||||
|     AllIntersectionsVisitor(const EdgeGrid::Grid      &grid, |     AllIntersectionsVisitor(const EdgeGrid::Grid      &grid, | ||||||
|                             std::vector<Intersection> &intersections, |                             std::vector<Intersection> &intersections, | ||||||
|                             const Matrix2d            &transform_to_x_axis, |  | ||||||
|                             const Line                &travel_line) |                             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() { |     void reset() { | ||||||
|  | @ -58,16 +52,11 @@ struct AllIntersectionsVisitor | ||||||
|     { |     { | ||||||
|         // Called with a row and colum of the grid cell, which is intersected by a line.
 |         // 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); |         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; |         for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { | ||||||
|              ++it_contour_and_segment) { |  | ||||||
|             // End points of the line segment and their vector.
 |  | ||||||
|             auto segment = grid.segment(*it_contour_and_segment); |  | ||||||
| 
 |  | ||||||
|             Point intersection_point; |             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()) { |                 intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { | ||||||
|                 intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second, |                 intersections.push_back({ it_contour_and_segment->first, it_contour_and_segment->second, intersection_point }); | ||||||
|                                            (transform_to_x_axis * intersection_point.cast<double>()).cast<coord_t>(), intersection_point); |  | ||||||
|                 intersection_set.insert(*it_contour_and_segment); |                 intersection_set.insert(*it_contour_and_segment); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -77,57 +66,42 @@ struct AllIntersectionsVisitor | ||||||
| 
 | 
 | ||||||
|     const EdgeGrid::Grid                                                                 &grid; |     const EdgeGrid::Grid                                                                 &grid; | ||||||
|     std::vector<Intersection>                                                            &intersections; |     std::vector<Intersection>                                                            &intersections; | ||||||
|     Matrix2d                                                                              transform_to_x_axis; |  | ||||||
|     Line                                                                                  travel_line; |     Line                                                                                  travel_line; | ||||||
|     std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> intersection_set; |     std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> intersection_set; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Create a rotation matrix for projection on the given vector
 | template<bool forward> | ||||||
| static Matrix2d rotation_by_direction(const Point &direction) | static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) | ||||||
| { |  | ||||||
|     Matrix2d rotation; |  | ||||||
|     rotation.block<1, 2>(0, 0) = direction.cast<double>() / direction.cast<double>().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) |  | ||||||
| { | { | ||||||
|     assert(point_idx < polygon.size()); |     assert(point_idx < polygon.size()); | ||||||
|     if (point != polygon.points[point_idx]) |     auto line_idx = int(point_idx); | ||||||
|         return polygon.points[point_idx]; |     //FIXME endless loop if all points are equal to point?
 | ||||||
| 
 |     if constexpr (forward) | ||||||
|     int line_idx = int(point_idx); |         for (; point == polygon.points[line_idx]; line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0); | ||||||
|     if (forward) |  | ||||||
|         for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0)); |  | ||||||
|     else |     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]; |     return polygon.points[line_idx]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | //FIXME will be in Point.h in the master
 | ||||||
|  | template<typename T, int Options> | ||||||
|  | inline Eigen::Matrix<T, 2, 1, Eigen::DontAlign> perp(const Eigen::MatrixBase<Eigen::Matrix<T, 2, 1, Options>>& v) { return Eigen::Matrix<T, 2, 1, Eigen::DontAlign>(-v.y(), v.x()); } | ||||||
|  | 
 | ||||||
| static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) | static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) | ||||||
| { | { | ||||||
|     assert(left != middle); |     assert(left != middle); | ||||||
|     assert(middle != right); |     assert(middle != right); | ||||||
| 
 |     return (perp(Point(middle - left)).cast<double>().normalized() + perp(Point(right - middle)).cast<double>().normalized()).normalized(); | ||||||
|     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(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Compute normal of the polygon's vertex in an inward direction
 | // 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) | 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 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 right_idx = point_idx + 1 == polygon.size() ? 0 : point_idx + 1; | ||||||
|     const Point &middle    = polygon.points[point_idx]; |     const Point &middle    = polygon.points[point_idx]; | ||||||
|     const Point &left      = find_first_different_vertex(polygon, left_idx, middle, false); |     const Point &left      = find_first_different_vertex<false>(polygon, left_idx, middle); | ||||||
|     const Point &right     = find_first_different_vertex(polygon, right_idx, middle, true); |     const Point &right     = find_first_different_vertex<true>(polygon, right_idx, middle); | ||||||
|     return three_points_inward_normal(left, middle, right); |     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).
 | // 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) | 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 &left  = find_first_different_vertex<false>(polygon, left_idx, middle); | ||||||
|     const Point &right = find_first_different_vertex(polygon, right_idx, middle, true); |     const Point &right = find_first_different_vertex<true>(polygon, right_idx, middle); | ||||||
|     return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast<coord_t>(); |     return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast<coord_t>(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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<Point, Point> 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<double>().norm() < (intersections[1] - start).cast<double>().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<double> &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<class T> 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<Polygons, Polygons> 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<TravelPoint> &travel) | static Polyline to_polyline(const std::vector<TravelPoint> &travel) | ||||||
| { | { | ||||||
|     Polyline result; |     Polyline result; | ||||||
|  | @ -265,6 +136,8 @@ static double travel_length(const std::vector<TravelPoint> &travel) { | ||||||
|     return total_length; |     return total_length; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
 | ||||||
|  | 
 | ||||||
| #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT | #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT | ||||||
| static void export_travel_to_svg(const Polygons                  &boundary, | static void export_travel_to_svg(const Polygons                  &boundary, | ||||||
|                                  const Line                      &original_travel, |                                  const Line                      &original_travel, | ||||||
|  | @ -294,102 +167,6 @@ static void export_travel_to_svg(const Polygons                  &boundary, | ||||||
| } | } | ||||||
| #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ | #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<Polygons>(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<Polygons>(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
 | // Returns a direction of the shortest path along the polygon boundary
 | ||||||
| enum class Direction { Forward, Backward }; | enum class Direction { Forward, Backward }; | ||||||
| static Direction get_shortest_direction(const Lines &lines, | static Direction get_shortest_direction(const Lines &lines, | ||||||
|  | @ -422,30 +199,90 @@ static Direction get_shortest_direction(const Lines &lines, | ||||||
|     return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; |     return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static std::vector<TravelPoint> simplify_travel(const EdgeGrid::Grid& edge_grid, const std::vector<TravelPoint>& travel, const Polygons& boundaries, const bool use_heuristics); | // Straighten the travel path as long as it does not collide with the contours stored in edge_grid.
 | ||||||
|  | static std::vector<TravelPoint> simplify_travel(const EdgeGrid::Grid &edge_grid, const std::vector<TravelPoint> &travel) | ||||||
|  | { | ||||||
|  |     // Visitor to check for a collision of a line segment with any contour stored inside the edge_grid.
 | ||||||
|  |     struct Visitor | ||||||
|  |     { | ||||||
|  |         Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} | ||||||
| 
 | 
 | ||||||
| static size_t avoid_perimeters(const Polygons           &boundaries, |         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<TravelPoint> 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); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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 EdgeGrid::Grid     &edge_grid, | ||||||
|                                      const Point              &start, |                                      const Point              &start, | ||||||
|                                      const Point              &end, |                                      const Point              &end, | ||||||
|                                const bool                use_heuristics, |                                      std::vector<TravelPoint> &result_out) | ||||||
|                                std::vector<TravelPoint> *result_out) |  | ||||||
| { | { | ||||||
|     const Point direction           = end - start; |     // Find all intersections between boundaries and the line segment, sort them along the line segment.
 | ||||||
|     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<double>()).cast<coord_t>(), |  | ||||||
|                            (transform_to_x_axis * end.cast<double>()).cast<coord_t>()); |  | ||||||
| 
 |  | ||||||
|     std::vector<Intersection> intersections; |     std::vector<Intersection> intersections; | ||||||
|     { |     { | ||||||
|         AllIntersectionsVisitor visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig); |         AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); | ||||||
|         edge_grid.visit_cells_intersecting_line(start, end, visitor); |         edge_grid.visit_cells_intersecting_line(start, end, visitor); | ||||||
|  |         Vec2d dir = (end - start).cast<double>(); | ||||||
|  |         std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).cast<double>().dot(dir) > 0.; }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::sort(intersections.begin(), intersections.end()); |  | ||||||
| 
 |  | ||||||
|     std::vector<TravelPoint> result; |     std::vector<TravelPoint> result; | ||||||
|     result.push_back({start, -1}); |     result.push_back({start, -1}); | ||||||
|     for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { |     for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { | ||||||
|  | @ -500,24 +337,23 @@ static size_t avoid_perimeters(const Polygons           &boundaries, | ||||||
| #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT | #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT | ||||||
|     { |     { | ||||||
|         static int iRun = 0; |         static int iRun = 0; | ||||||
|         export_travel_to_svg(boundaries, travel_line_orig, result, intersections, |         export_travel_to_svg(boundaries, Line(start, end), result, intersections, | ||||||
|                              debug_out_path("AvoidCrossingPerimeters-initial-%d.svg", iRun++)); |                              debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); | ||||||
|     } |     } | ||||||
| #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ | #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ | ||||||
| 
 | 
 | ||||||
|     if (! intersections.empty()) |     if (! intersections.empty()) | ||||||
|         result = simplify_travel(edge_grid, result, boundaries, use_heuristics); |         result = simplify_travel(edge_grid, result); | ||||||
| 
 | 
 | ||||||
| #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT | #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT | ||||||
|     { |     { | ||||||
|         static int iRun = 0; |         static int iRun = 0; | ||||||
|         export_travel_to_svg(boundaries, travel_line_orig, result, intersections, |         export_travel_to_svg(boundaries, Line(start, end), result, intersections, | ||||||
|                              debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun++)); |                              debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); | ||||||
|     } |     } | ||||||
| #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ | #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ | ||||||
| 
 | 
 | ||||||
|     result_out->reserve(result_out->size() + result.size()); |     append(result_out, std::move(result)); | ||||||
|     result_out->insert(result_out->end(), result.begin(), result.end()); |  | ||||||
|     return intersections.size(); |     return intersections.size(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -563,10 +399,10 @@ static std::vector<TravelPoint> simplify_travel_heuristics(const EdgeGrid::Grid | ||||||
|                 visitor.reset(); |                 visitor.reset(); | ||||||
|                 visitor.travel_line.a       = current.point; |                 visitor.travel_line.a       = current.point; | ||||||
|                 visitor.travel_line.b       = possible_new_next.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); |                 edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); | ||||||
|                 if (!intersections.empty()) { |                 if (!intersections.empty()) { | ||||||
|                     std::sort(intersections.begin(), intersections.end()); |                     Vec2d dir = (visitor.travel_line.b - visitor.travel_line.a).cast<double>(); | ||||||
|  |                     std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).cast<double>().dot(dir) > 0.; }); | ||||||
|                     size_t last_border_idx_count = 0; |                     size_t last_border_idx_count = 0; | ||||||
|                     for (const Intersection &intersection : intersections) |                     for (const Intersection &intersection : intersections) | ||||||
|                         if (int(intersection.border_idx) == possible_new_next.border_idx) |                         if (int(intersection.border_idx) == possible_new_next.border_idx) | ||||||
|  | @ -576,9 +412,9 @@ static std::vector<TravelPoint> simplify_travel_heuristics(const EdgeGrid::Grid | ||||||
|                         continue; |                         continue; | ||||||
| 
 | 
 | ||||||
|                     std::vector<TravelPoint> possible_shortcut; |                     std::vector<TravelPoint> 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); |                     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; |                         new_path_shorter_by = path_length - shortcut_travel; | ||||||
|                         shortcut            = possible_shortcut; |                         shortcut            = possible_shortcut; | ||||||
|                         new_next            = possible_new_next; |                         new_next            = possible_new_next; | ||||||
|  | @ -600,78 +436,44 @@ static std::vector<TravelPoint> simplify_travel_heuristics(const EdgeGrid::Grid | ||||||
|     return simplified_path; |     return simplified_path; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static std::vector<TravelPoint> simplify_travel(const EdgeGrid::Grid           &edge_grid, | // Called by AvoidCrossingPerimeters::travel_to()
 | ||||||
|                                                 const std::vector<TravelPoint> &travel, | static size_t avoid_perimeters(const Polygons           &boundaries, | ||||||
|                                                 const Polygons                 &boundaries, |                                const EdgeGrid::Grid     &edge_grid, | ||||||
|                                                 const bool                      use_heuristics) |                                const Point              &start, | ||||||
|  |                                const Point              &end, | ||||||
|  |                                Polyline                 &result_out) | ||||||
| { | { | ||||||
|     struct Visitor |     // Travel line is completely or partially inside the bounding box.
 | ||||||
|     { |     std::vector<TravelPoint> path; | ||||||
|         Visitor(const EdgeGrid::Grid &grid) : grid(grid) {} |     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()); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         bool operator()(coord_t iy, coord_t ix) |     result_out = to_polyline(path); | ||||||
|  | 
 | ||||||
|  | #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT | ||||||
|     { |     { | ||||||
|             assert(pt_current != nullptr); |         static int iRun = 0; | ||||||
|             assert(pt_next != nullptr); |         export_travel_to_svg(boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); | ||||||
|             // 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; |  | ||||||
|     } |     } | ||||||
|  | #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ | ||||||
|  | 
 | ||||||
|  |     return num_intersections; | ||||||
| } | } | ||||||
|             // Continue traversing the grid along the edge.
 | 
 | ||||||
|  | // Check if anyone of ExPolygons contains whole travel.
 | ||||||
|  | // called by need_wipe()
 | ||||||
|  | template<class T> static bool any_expolygon_contains(const ExPolygons &ex_polygons, const T &travel) | ||||||
|  | { | ||||||
|  |     //FIXME filter by bounding boxes!
 | ||||||
|  |     for (const ExPolygon &ex_polygon : ex_polygons) | ||||||
|  |         if (ex_polygon.contains(travel)) | ||||||
|             return true; |             return true; | ||||||
|         } |     return false; | ||||||
| 
 |  | ||||||
|         const EdgeGrid::Grid &grid; |  | ||||||
|         const Slic3r::Point  *pt_current = nullptr; |  | ||||||
|         const Slic3r::Point  *pt_next    = nullptr; |  | ||||||
|         bool                  intersect  = false; |  | ||||||
|     } visitor(edge_grid); |  | ||||||
| 
 |  | ||||||
|     std::vector<TravelPoint> simplified_path; |  | ||||||
|     simplified_path.reserve(travel.size()); |  | ||||||
|     simplified_path.emplace_back(travel.front()); |  | ||||||
| 
 |  | ||||||
|     // Try to skip some points in the path.
 |  | ||||||
|     for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { |  | ||||||
|         const Point ¤t_point = travel[point_idx - 1].point; |  | ||||||
|         TravelPoint  next          = travel[point_idx]; |  | ||||||
| 
 |  | ||||||
|         visitor.pt_current = ¤t_point; |  | ||||||
| 
 |  | ||||||
|         for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) { |  | ||||||
|             if (travel[point_idx_2].point == current_point) { |  | ||||||
|                 next      = travel[point_idx_2]; |  | ||||||
|                 point_idx = point_idx_2; |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             visitor.pt_next = &travel[point_idx_2].point; |  | ||||||
|             edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); |  | ||||||
|             // Check if deleting point causes crossing a boundary
 |  | ||||||
|             if (!visitor.intersect) { |  | ||||||
|                 next      = travel[point_idx_2]; |  | ||||||
|                 point_idx = point_idx_2; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         simplified_path.emplace_back(next); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(use_heuristics) { |  | ||||||
|         simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); |  | ||||||
|         std::reverse(simplified_path.begin(),simplified_path.end()); |  | ||||||
|         simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries); |  | ||||||
|         std::reverse(simplified_path.begin(),simplified_path.end()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return simplified_path; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool need_wipe(const GCode      &gcodegen, | 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)) { |             if (any_expolygon_contains(slice, original_travel)) { | ||||||
|                 // Check if original_travel and result_travel are not same.
 |                 // 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 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 = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && | ||||||
|                     wipe_needed = false; |                               !any_expolygon_contains(slice, result_travel); | ||||||
|                 } else { |  | ||||||
|                     wipe_needed = !any_expolygon_contains(slice, result_travel); |  | ||||||
|                 } |  | ||||||
|             } else { |             } else { | ||||||
|                 wipe_needed = true; |                 wipe_needed = true; | ||||||
|             } |             } | ||||||
|  | @ -719,28 +518,27 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & | ||||||
|     Point    end           = point + scaled_origin; |     Point    end           = point + scaled_origin; | ||||||
|     Polyline result_pl; |     Polyline result_pl; | ||||||
|     size_t   travel_intersection_count = 0; |     size_t   travel_intersection_count = 0; | ||||||
|     if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) { |     Vec2d startf = start.cast<double>(); | ||||||
|         result_pl                    = Polyline({start, end}); |     Vec2d endf   = end  .cast<double>(); | ||||||
|         travel_intersection_count = 0; |     // Trim the travel line by the bounding box.
 | ||||||
|     } else { |     if (Geometry::liang_barsky_line_clipping(startf, endf, use_external ? m_bbox_external : m_bbox)) { | ||||||
|         std::vector<TravelPoint> result; |         // Travel line is completely or partially inside the bounding box.
 | ||||||
|         auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); |         travel_intersection_count = use_external ?  | ||||||
|         if (use_external) |             avoid_perimeters(m_boundaries_external, m_grid_external, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl) : | ||||||
|             travel_intersection_count = avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); |             avoid_perimeters(m_boundaries,          m_grid,          startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl); | ||||||
|         else |  | ||||||
|             travel_intersection_count = avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); |  | ||||||
| 
 |  | ||||||
|         result_pl = to_polyline(result); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|         result_pl.points.front() = start; |         result_pl.points.front() = start; | ||||||
|         result_pl.points.back()  = end; |         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); |     Line travel(start, end); | ||||||
|     double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); |     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)) { |     if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length) | ||||||
|         result_pl = Polyline({start, end}); |         result_pl = {start, end}; | ||||||
|     } | 
 | ||||||
|     if (use_external) { |     if (use_external) { | ||||||
|         result_pl.translate(-scaled_origin); |         result_pl.translate(-scaled_origin); | ||||||
|         *could_be_wipe_disabled = false; |         *could_be_wipe_disabled = false; | ||||||
|  | @ -750,6 +548,172 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point & | ||||||
|     return result_pl; |     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<double> &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<Polygons, Polygons> 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<Polygons>(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<Polygons>(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) | void AvoidCrossingPerimeters::init_layer(const Layer &layer) | ||||||
| { | { | ||||||
|     m_slice.clear(); |     m_slice.clear(); | ||||||
|  | @ -757,19 +721,25 @@ void AvoidCrossingPerimeters::init_layer(const Layer &layer) | ||||||
|     m_boundaries_external.clear(); |     m_boundaries_external.clear(); | ||||||
| 
 | 
 | ||||||
|     for (const LayerRegion *layer_region : layer.regions()) |     for (const LayerRegion *layer_region : layer.regions()) | ||||||
|  |         //FIXME making copies?
 | ||||||
|         append(m_slice, (ExPolygons) layer_region->slices); |         append(m_slice, (ExPolygons) layer_region->slices); | ||||||
| 
 | 
 | ||||||
|     m_boundaries = to_polygons(get_boundary(layer)); |     m_boundaries = to_polygons(get_boundary(layer)); | ||||||
|     m_boundaries_external = to_polygons(get_boundary_external(layer)); |     m_boundaries_external = to_polygons(get_boundary_external(layer)); | ||||||
| 
 | 
 | ||||||
|     m_bbox = get_extents(m_boundaries); |     BoundingBox bbox(get_extents(m_boundaries)); | ||||||
|     m_bbox.offset(SCALED_EPSILON); |     bbox.offset(SCALED_EPSILON); | ||||||
|     m_bbox_external = get_extents(m_boundaries_external); |     BoundingBox bbox_external = get_extents(m_boundaries_external); | ||||||
|     m_bbox_external.offset(SCALED_EPSILON); |     bbox_external.offset(SCALED_EPSILON); | ||||||
| 
 | 
 | ||||||
|     m_grid.set_bbox(m_bbox); |     m_bbox = BoundingBoxf(bbox.min.cast<double>(), bbox.max.cast<double>()); | ||||||
|  |     m_bbox_external = BoundingBoxf(bbox_external.min.cast<double>(), bbox_external.max.cast<double>()); | ||||||
|  | 
 | ||||||
|  |     m_grid.set_bbox(bbox); | ||||||
|  |     //FIXME 1mm grid?
 | ||||||
|     m_grid.create(m_boundaries, coord_t(scale_(1.))); |     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.))); |     m_grid_external.create(m_boundaries_external, coord_t(scale_(1.))); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -47,9 +47,9 @@ private: | ||||||
|     // Collection of boundaries used for detection of crossing perimetrs for travels outside object
 |     // Collection of boundaries used for detection of crossing perimetrs for travels outside object
 | ||||||
|     Polygons       m_boundaries_external; |     Polygons       m_boundaries_external; | ||||||
|     // Bounding box of m_boundaries
 |     // Bounding box of m_boundaries
 | ||||||
|     BoundingBox    m_bbox; |     BoundingBoxf   m_bbox; | ||||||
|     // Bounding box of m_boundaries_external
 |     // Bounding box of m_boundaries_external
 | ||||||
|     BoundingBox    m_bbox_external; |     BoundingBoxf   m_bbox_external; | ||||||
|     EdgeGrid::Grid m_grid; |     EdgeGrid::Grid m_grid; | ||||||
|     EdgeGrid::Grid m_grid_external; |     EdgeGrid::Grid m_grid_external; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -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) | void MultiPoint::translate(const Point &v) | ||||||
| { | { | ||||||
|     for (Point &pt : points) |     for (Point &pt : points) | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ public: | ||||||
|     MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } |     MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } | ||||||
|     void scale(double factor); |     void scale(double factor); | ||||||
|     void scale(double factor_x, double factor_y); |     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 translate(const Point &vector); | ||||||
|     void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } |     void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } | ||||||
|     void rotate(double cos_angle, double sin_angle); |     void rotate(double cos_angle, double sin_angle); | ||||||
|  |  | ||||||
|  | @ -62,7 +62,6 @@ set(XS_XSP_FILES | ||||||
|     ${XSP_DIR}/Layer.xsp |     ${XSP_DIR}/Layer.xsp | ||||||
|     ${XSP_DIR}/Line.xsp |     ${XSP_DIR}/Line.xsp | ||||||
|     ${XSP_DIR}/Model.xsp |     ${XSP_DIR}/Model.xsp | ||||||
|     ${XSP_DIR}/MotionPlanner.xsp |  | ||||||
|     ${XSP_DIR}/PerimeterGenerator.xsp |     ${XSP_DIR}/PerimeterGenerator.xsp | ||||||
|     ${XSP_DIR}/PlaceholderParser.xsp |     ${XSP_DIR}/PlaceholderParser.xsp | ||||||
|     ${XSP_DIR}/Point.xsp |     ${XSP_DIR}/Point.xsp | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vojtech Bubnik
						Vojtech Bubnik