mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-13 09:47:58 -06:00

The strategy for the avoid crossing perimeters algorithm has been redesigned. But external travels (travel between objects or supports) have not been solved yet. For these travels is used a direct path between two points. Much of the code has been reworked, which leads to significant speedup compared to the previous implementation. Also, several potential bugs have been fixed.
1292 lines
65 KiB
C++
1292 lines
65 KiB
C++
#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 <numeric>
|
|
#include <unordered_set>
|
|
|
|
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<Intersection> &intersections) : grid(grid), intersections(intersections)
|
|
{
|
|
intersection_set.reserve(intersections.capacity());
|
|
}
|
|
|
|
AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector<Intersection> &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<Intersection> &intersections;
|
|
Line travel_line;
|
|
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> 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<bool forward>
|
|
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];
|
|
}
|
|
|
|
//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)
|
|
{
|
|
assert(left != middle);
|
|
assert(middle != right);
|
|
return (perp(Point(middle - left)).cast<double>().normalized() + perp(Point(right - middle)).cast<double>().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<false>(polygon, left_idx, middle);
|
|
const Point &right = find_first_different_vertex<true>(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<coord_t>();
|
|
}
|
|
|
|
// 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<false>(polygon, left_idx, middle);
|
|
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>();
|
|
}
|
|
|
|
static Polyline to_polyline(const std::vector<TravelPoint> &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<Intersection> &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<TravelPoint> &result_travel,
|
|
const std::vector<Intersection> &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<double>().norm();
|
|
total_length_backward -= (poly[(intersection_first.line_idx + 1) % poly.size()] - intersection_first.point).cast<double>().norm();
|
|
|
|
total_length_forward -= (poly[(intersection_second.line_idx + 1) % poly.size()] - intersection_second.point).cast<double>().norm();
|
|
total_length_backward -= (intersection_second.point - poly[intersection_second.line_idx]).cast<double>().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<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::Boundary &boundary, const std::vector<TravelPoint> &travel)
|
|
{
|
|
FirstIntersectionVisitor visitor(boundary.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;
|
|
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<TravelPoint> &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<Intersection> 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<double>();
|
|
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<double>().dot(dir) > 0.; });
|
|
}
|
|
|
|
std::vector<TravelPoint> 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<double>();
|
|
Vec2d intersection_vec = (intersection.point - start).cast<double>();
|
|
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<TravelPoint> 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<BoundingBox> &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<BoundingBox> &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<BoundingBox> &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<unsigned int> printing_extruders = print_object.object_extruders();
|
|
assert(!printing_extruders.empty());
|
|
float avg_extruder = 0;
|
|
for(unsigned int extruder_id : printing_extruders)
|
|
avg_extruder += scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id));
|
|
avg_extruder /= printing_extruders.size();
|
|
return avg_extruder;
|
|
}
|
|
|
|
// called by get_boundary()
|
|
static float get_perimeter_spacing(const Layer &layer)
|
|
{
|
|
size_t regions_count = 0;
|
|
float perimeter_spacing = 0.f;
|
|
for (const LayerRegion *layer_region : layer.regions())
|
|
if (layer_region != nullptr && !layer_region->slices.empty()) {
|
|
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
|
|
++regions_count;
|
|
}
|
|
|
|
assert(perimeter_spacing >= 0.f);
|
|
if (regions_count != 0)
|
|
perimeter_spacing /= float(regions_count);
|
|
else
|
|
perimeter_spacing = get_default_perimeter_spacing(*layer.object());
|
|
return perimeter_spacing;
|
|
}
|
|
|
|
// Adds points around all vertices so that the offset affects only small sections around these vertices.
|
|
static void resample_polygon(Polygon &polygon, double dist_from_vertex)
|
|
{
|
|
Points resampled_poly;
|
|
resampled_poly.reserve(3 * polygon.size());
|
|
resampled_poly.emplace_back(polygon.first_point());
|
|
for (size_t pt_idx = 1; pt_idx < polygon.size(); ++pt_idx) {
|
|
const Point &p1 = polygon[pt_idx - 1];
|
|
const Point &p2 = polygon[pt_idx];
|
|
double line_length = (p2 - p1).cast<double>().norm();
|
|
Vector line_vec = ((p2 - p1).cast<double>().normalized() * dist_from_vertex).cast<coord_t>();
|
|
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<float> &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<float>() - polygon[point_idx - 1].cast<float>()).norm();
|
|
polygon_distances_out.back() = polygon_distances_out[polygon.size() - 1] + (polygon.last_point().cast<float>() - polygon.first_point().cast<float>()).norm();
|
|
}
|
|
|
|
static void precompute_expolygon_distances(const ExPolygon &ex_polygon, std::vector<std::vector<float>> &expolygon_distances_out)
|
|
{
|
|
expolygon_distances_out.assign(ex_polygon.holes.size() + 1, std::vector<float>());
|
|
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<float> contour_distance(const EdgeGrid::Grid &grid,
|
|
const std::vector<float> &poly_distances,
|
|
const size_t contour_idx,
|
|
const Polygon &polygon,
|
|
double compensation,
|
|
double search_radius)
|
|
{
|
|
assert(! polygon.empty());
|
|
assert(polygon.size() >= 2);
|
|
|
|
std::vector<float> out;
|
|
|
|
if (polygon.size() > 2)
|
|
{
|
|
struct Visitor {
|
|
Visitor(const EdgeGrid::Grid &grid, const size_t contour_idx, const std::vector<float> &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<double>::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<const Point &, const Point &> segment = this->grid.segment(*it_contour_and_segment);
|
|
const Vec2d v = (segment.second - segment.first).cast<double>();
|
|
const Vec2d va = (this->point - segment.first).cast<double>();
|
|
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<double>() + t * v;
|
|
const Vec2d bisector = foot - this->point.cast<double>();
|
|
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<float> &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<double>();
|
|
Vec2d v2 = (contour[inext] - contour[i]).cast<double>();
|
|
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<double>();
|
|
size_t iprev = prev_idx_modulo(i, contour);
|
|
size_t inext = next_idx_modulo(i, contour);
|
|
Vec2d v1 = (contour[i] - contour[iprev]).cast<double>();
|
|
Vec2d v2 = (contour[inext] - contour[i]).cast<double>();
|
|
bool left_of_v1 = cross2(v1, pt - contour[iprev].cast<double>()) > 0.;
|
|
bool left_of_v2 = cross2(v2, pt - contour[i].cast<double>()) > 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<double>();
|
|
size_t inext = next_idx_modulo(i, contour);
|
|
Vec2d v = (contour[inext] - contour[i]).cast<double>();
|
|
return cross2(v, pt - contour[i].cast<double>()) > 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<std::vector<float>> ex_poly_distances;
|
|
precompute_expolygon_distances(ex_poly, ex_poly_distances);
|
|
|
|
std::vector<std::vector<float>> 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<float> distances = contour_distance(grid, ex_poly_distances[idx_contour], idx_contour, poly, offset, search_radius);
|
|
for (float &distance : distances) {
|
|
if (distance < min_contour_width)
|
|
distance = 0.f;
|
|
else if (distance > min_contour_width + 2. * offset)
|
|
distance = - float(offset);
|
|
else
|
|
distance = - (distance - float(min_contour_width)) / 2.f;
|
|
}
|
|
offsets.emplace_back(distances);
|
|
}
|
|
|
|
ExPolygons offset_ex_poly = variable_offset_inner_ex(ex_poly, offsets);
|
|
// If variable_offset_inner_ex produces empty result, then original ex_polygon is used
|
|
if (offset_ex_poly.size() == 1) {
|
|
ex_poly = std::move(offset_ex_poly.front());
|
|
} else if (offset_ex_poly.size() > 1) {
|
|
// fix_after_inner_offset called inside variable_offset_inner_ex sometimes produces
|
|
// tiny artefacts polygons, so these artefacts are removed.
|
|
double max_area = offset_ex_poly.front().area();
|
|
size_t max_area_idx = 0;
|
|
for (size_t poly_idx = 1; poly_idx < offset_ex_poly.size(); ++poly_idx) {
|
|
double area = offset_ex_poly[poly_idx].area();
|
|
if (max_area < area) {
|
|
max_area = area;
|
|
max_area_idx = poly_idx;
|
|
}
|
|
}
|
|
ex_poly = std::move(offset_ex_poly[max_area_idx]);
|
|
}
|
|
}
|
|
return ex_poly_result;
|
|
}
|
|
|
|
// called by AvoidCrossingPerimeters::travel_to()
|
|
static ExPolygons get_boundary(const Layer &layer)
|
|
{
|
|
const float perimeter_spacing = get_perimeter_spacing(layer);
|
|
const float perimeter_offset = perimeter_spacing / 2.f;
|
|
size_t polygons_count = 0;
|
|
for (const LayerRegion *layer_region : layer.regions())
|
|
polygons_count += layer_region->slices.surfaces.size();
|
|
|
|
ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset));
|
|
// Collect all top layers that will not be crossed.
|
|
polygons_count = 0;
|
|
for (const LayerRegion *layer_region : layer.regions())
|
|
for (const Surface &surface : layer_region->fill_surfaces.surfaces)
|
|
if (surface.is_top()) ++polygons_count;
|
|
|
|
if (polygons_count > 0) {
|
|
ExPolygons top_layer_polygons;
|
|
top_layer_polygons.reserve(polygons_count);
|
|
for (const LayerRegion *layer_region : layer.regions())
|
|
for (const Surface &surface : layer_region->fill_surfaces.surfaces)
|
|
if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon);
|
|
|
|
top_layer_polygons = union_ex(top_layer_polygons);
|
|
return diff_ex(boundary, offset_ex(top_layer_polygons, -perimeter_offset));
|
|
}
|
|
|
|
return boundary;
|
|
}
|
|
|
|
static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary)
|
|
{
|
|
boundary->boundaries_params.assign(boundary->boundaries.size(), std::vector<float>());
|
|
for (size_t poly_idx = 0; poly_idx < boundary->boundaries.size(); ++poly_idx)
|
|
precompute_polygon_distances(boundary->boundaries[poly_idx], boundary->boundaries_params[poly_idx]);
|
|
}
|
|
|
|
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
|
|
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
|
|
{
|
|
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
|
|
// Otherwise perform the path planning in the coordinate system of the active object.
|
|
bool use_external = m_use_external_mp || m_use_external_mp_once;
|
|
Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
|
|
const Point start = gcodegen.last_pos() + scaled_origin;
|
|
const Point end = point + scaled_origin;
|
|
const Line travel(start, end);
|
|
|
|
Polyline result_pl;
|
|
size_t travel_intersection_count = 0;
|
|
Vec2d startf = start.cast<double>();
|
|
Vec2d endf = end .cast<double>();
|
|
|
|
if (!use_external && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) {
|
|
// Initialize m_internal only when it is necessary.
|
|
if (m_internal.boundaries.empty()) {
|
|
m_internal.boundaries_params.clear();
|
|
m_internal.boundaries = to_polygons(get_boundary(*gcodegen.layer()));
|
|
|
|
BoundingBox bbox(get_extents(m_internal.boundaries));
|
|
bbox.offset(SCALED_EPSILON);
|
|
m_internal.bbox = BoundingBoxf(bbox.min.cast<double>(), bbox.max.cast<double>());
|
|
m_internal.grid.set_bbox(bbox);
|
|
// FIXME 1mm grid?
|
|
m_internal.grid.create(m_internal.boundaries, coord_t(scale_(1.)));
|
|
init_boundary_distances(&m_internal);
|
|
}
|
|
|
|
// Trim the travel line by the bounding box.
|
|
if (Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) {
|
|
travel_intersection_count = avoid_perimeters(gcodegen, m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl);
|
|
result_pl.points.front() = start;
|
|
result_pl.points.back() = end;
|
|
} else {
|
|
// Travel line is completely outside the bounding box.
|
|
result_pl = {start, end};
|
|
travel_intersection_count = 0;
|
|
}
|
|
} else {
|
|
// Travel line is completely outside the bounding box.
|
|
result_pl = {start, end};
|
|
travel_intersection_count = 0;
|
|
}
|
|
|
|
double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour);
|
|
if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length)
|
|
result_pl = {start, end};
|
|
|
|
if (use_external) {
|
|
result_pl.translate(-scaled_origin);
|
|
*could_be_wipe_disabled = false;
|
|
} else
|
|
*could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, travel, result_pl, travel_intersection_count);
|
|
|
|
return result_pl;
|
|
}
|
|
|
|
// ************************************* AvoidCrossingPerimeters::init_layer() *****************************************
|
|
|
|
void AvoidCrossingPerimeters::init_layer(const Layer &layer)
|
|
{
|
|
m_internal.boundaries.clear();
|
|
m_internal.boundaries_params.clear();
|
|
BoundingBox bbox_slice(get_extents(layer.lslices));
|
|
bbox_slice.offset(SCALED_EPSILON);
|
|
|
|
m_grid_lslice.set_bbox(bbox_slice);
|
|
//FIXME 1mm grid?
|
|
m_grid_lslice.create(layer.lslices, coord_t(scale_(1.)));
|
|
}
|
|
|
|
#if 0
|
|
static double travel_length(const std::vector<TravelPoint> &travel) {
|
|
double total_length = 0;
|
|
for (size_t idx = 1; idx < travel.size(); ++idx)
|
|
total_length += (travel[idx].point - travel[idx - 1].point).cast<double>().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<TravelPoint> &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<Intersection> 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<double>();
|
|
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<double>().dot(dir) > 0.; });
|
|
}
|
|
|
|
std::vector<TravelPoint> 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<TravelPoint> simplify_travel_heuristics(const AvoidCrossingPerimeters::Boundary &boundary,
|
|
const std::vector<TravelPoint> &travel)
|
|
{
|
|
std::vector<TravelPoint> simplified_path;
|
|
std::vector<Intersection> 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<double>().norm();
|
|
double new_path_shorter_by = 0.;
|
|
size_t border_idx_change_count = 0;
|
|
std::vector<TravelPoint> 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<double>().norm();
|
|
double shortcut_length = (possible_new_next.point - current.point).cast<double>().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<double>();
|
|
std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast<double>().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<TravelPoint> 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<TravelPoint> 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<double>();
|
|
Vec2d endf = end .cast<double>();
|
|
// 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<coord_t>(), endf.cast<coord_t>(),
|
|
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 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)
|
|
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;
|
|
}
|
|
|
|
// 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(
|
|
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<double>(), bbox.max.cast<double>());
|
|
m_external.bbox = BoundingBoxf(bbox_external.min.cast<double>(), bbox_external.max.cast<double>());
|
|
|
|
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
|