Files
OrcaSlicer-bambulab/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp
2020-11-20 11:56:40 +01:00

748 lines
36 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;
};
// 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)
{}
AllIntersectionsVisitor(const EdgeGrid::Grid &grid,
std::vector<Intersection> &intersections,
const Line &travel_line)
: grid(grid), intersections(intersections), travel_line(travel_line)
{}
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;
};
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());
auto line_idx = int(point_idx);
//FIXME endless loop if all points are equal to point?
if constexpr (forward)
for (; point == polygon.points[line_idx]; line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0);
else
for (; point == polygon.points[line_idx]; line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1);
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 = point_idx == 0 ? polygon.size() - 1 : point_idx - 1;
const size_t right_idx = point_idx + 1 == polygon.size() ? 0 : point_idx + 1;
const Point &middle = polygon.points[point_idx];
const Point &left = find_first_different_vertex<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;
}
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;
}
// #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 };
static Direction get_shortest_direction(const Lines &lines,
const size_t start_idx,
const size_t end_idx,
const Point &intersection_first,
const Point &intersection_last)
{
double total_length_forward = (lines[start_idx].b - intersection_first).cast<double>().norm();
double total_length_backward = (lines[start_idx].a - intersection_first).cast<double>().norm();
auto cyclic_index = [&lines](int index) {
if (index >= int(lines.size()))
index = 0;
else if (index < 0)
index = int(lines.size()) - 1;
return index;
};
for (int line_idx = cyclic_index(int(start_idx) + 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx + 1))
total_length_forward += lines[line_idx].length();
for (int line_idx = cyclic_index(int(start_idx) - 1); line_idx != int(end_idx); line_idx = cyclic_index(line_idx - 1))
total_length_backward += lines[line_idx].length();
total_length_forward += (lines[end_idx].a - intersection_last).cast<double>().norm();
total_length_backward += (lines[end_idx].b - intersection_last).cast<double>().norm();
return (total_length_forward < total_length_backward) ? Direction::Forward : 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 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) {}
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 &current_point = travel[point_idx - 1].point;
TravelPoint next = travel[point_idx];
visitor.pt_current = &current_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 Point &start,
const Point &end,
std::vector<TravelPoint> &result_out)
{
// Find all intersections between boundaries and the line segment, sort them along the line segment.
std::vector<Intersection> intersections;
{
AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end));
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).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;
Lines border_lines = boundaries[intersection_first.border_idx].lines();
Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx, intersection_first.point, intersection_second.point);
// Append the path around the border into the path
if (shortest_direction == Direction::Forward)
for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx);
line_idx = line_idx + 1 < int(border_lines.size()) ? line_idx + 1 : 0)
result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx],
(line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)});
else
for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx);
line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(border_lines.size()) - 1)
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(edge_grid, 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 EdgeGrid::Grid &edge_grid,
const std::vector<TravelPoint> &travel,
const Polygons &boundaries)
{
std::vector<TravelPoint> simplified_path;
std::vector<Intersection> intersections;
AllIntersectionsVisitor visitor(edge_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 &current = 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;
edge_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(boundaries, edge_grid, current.point, possible_new_next.point, possible_shortcut);
double shortcut_travel = travel_length(possible_shortcut);
if (path_length > shortcut_travel && path_length - shortcut_travel > new_path_shorter_by) {
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 Polygons &boundaries,
const EdgeGrid::Grid &edge_grid,
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(boundaries, edge_grid, start, end, path);
if (num_intersections) {
path = simplify_travel_heuristics(edge_grid, path, boundaries);
std::reverse(path.begin(), path.end());
path = simplify_travel_heuristics(edge_grid, path, boundaries);
std::reverse(path.begin(), path.end());
}
result_out = to_polyline(path);
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
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;
}
// 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 false;
}
static bool need_wipe(const GCode &gcodegen,
const ExPolygons &slice,
const Line &original_travel,
const Polyline &result_travel,
const size_t intersection_count)
{
bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.;
bool wipe_needed = false;
// If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely
// outside the object.
if (intersection_count > 0) {
// The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test.
// If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes.
if (z_lift_enabled) {
if (any_expolygon_contains(slice, original_travel)) {
// Check if original_travel and result_travel are not same.
// If both are the same, then it is possible to skip testing of result_travel
wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) &&
!any_expolygon_contains(slice, result_travel);
} else {
wipe_needed = true;
}
} else {
wipe_needed = !any_expolygon_contains(slice, result_travel);
}
}
return wipe_needed;
}
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
{
// 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_bbox_external : m_bbox)) {
// Travel line is completely or partially inside the bounding box.
//FIXME initialize m_boundaries / m_boundaries_external on demand?
travel_intersection_count = use_external ?
avoid_perimeters(m_boundaries_external, m_grid_external, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl) :
avoid_perimeters(m_boundaries, m_grid, 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_slice, travel, result_pl, travel_intersection_count);
return result_pl;
}
// ************************************* AvoidCrossingPerimeters::init_layer() *****************************************
// called by get_perimeter_spacing() / get_perimeter_spacing_external()
static inline float get_default_perimeter_spacing(const Print &print)
{
//FIXME better use extruders printing this PrintObject or this Print?
//FIXME maybe better use an average of printing extruders?
const std::vector<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 =
//FIXME flip order of offset and convex_hull
diff_ex(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, diff_ex(offset(holes, perimeter_spacing), offset(holes, perimeter_offset)));
return union_ex(result_boundary);
}
void AvoidCrossingPerimeters::init_layer(const Layer &layer)
{
m_slice.clear();
m_boundaries.clear();
m_boundaries_external.clear();
for (const LayerRegion *layer_region : layer.regions())
//FIXME making copies?
append(m_slice, (ExPolygons) layer_region->slices);
m_boundaries = to_polygons(get_boundary(layer));
m_boundaries_external = to_polygons(get_boundary_external(layer));
BoundingBox bbox(get_extents(m_boundaries));
bbox.offset(SCALED_EPSILON);
BoundingBox bbox_external = get_extents(m_boundaries_external);
bbox_external.offset(SCALED_EPSILON);
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_external.set_bbox(bbox_external);
//FIXME 1mm grid?
m_grid_external.create(m_boundaries_external, coord_t(scale_(1.)));
}
} // namespace Slic3r