|
|
|
@ -4,6 +4,7 @@
|
|
|
|
|
#include "../Print.hpp"
|
|
|
|
|
#include "../Polygon.hpp"
|
|
|
|
|
#include "../ExPolygon.hpp"
|
|
|
|
|
#include "../Geometry.hpp"
|
|
|
|
|
#include "../ClipperUtils.hpp"
|
|
|
|
|
#include "../SVG.hpp"
|
|
|
|
|
#include "AvoidCrossingPerimeters.hpp"
|
|
|
|
@ -16,7 +17,7 @@ namespace Slic3r {
|
|
|
|
|
struct TravelPoint
|
|
|
|
|
{
|
|
|
|
|
Point point;
|
|
|
|
|
// Index of the polygon containing this point. A negative value indicates that the point is not on any border
|
|
|
|
|
// Index of the polygon containing this point. A negative value indicates that the point is not on any border.
|
|
|
|
|
int border_idx;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -26,17 +27,11 @@ struct Intersection
|
|
|
|
|
size_t border_idx;
|
|
|
|
|
// Index of the line on the polygon containing this point of intersection.
|
|
|
|
|
size_t line_idx;
|
|
|
|
|
// Point of intersection projected on the travel path.
|
|
|
|
|
Point point_transformed;
|
|
|
|
|
// Point of intersection.
|
|
|
|
|
Point point;
|
|
|
|
|
|
|
|
|
|
Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point)
|
|
|
|
|
: border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){};
|
|
|
|
|
|
|
|
|
|
inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Finding all intersections of a set of contours with a line segment.
|
|
|
|
|
struct AllIntersectionsVisitor
|
|
|
|
|
{
|
|
|
|
|
AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector<Intersection> &intersections)
|
|
|
|
@ -45,9 +40,8 @@ struct AllIntersectionsVisitor
|
|
|
|
|
|
|
|
|
|
AllIntersectionsVisitor(const EdgeGrid::Grid &grid,
|
|
|
|
|
std::vector<Intersection> &intersections,
|
|
|
|
|
const Matrix2d &transform_to_x_axis,
|
|
|
|
|
const Line &travel_line)
|
|
|
|
|
: grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line)
|
|
|
|
|
: grid(grid), intersections(intersections), travel_line(travel_line)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
void reset() {
|
|
|
|
@ -58,16 +52,11 @@ struct AllIntersectionsVisitor
|
|
|
|
|
{
|
|
|
|
|
// Called with a row and colum of the grid cell, which is intersected by a line.
|
|
|
|
|
auto cell_data_range = grid.cell_data_range(iy, ix);
|
|
|
|
|
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second;
|
|
|
|
|
++it_contour_and_segment) {
|
|
|
|
|
// End points of the line segment and their vector.
|
|
|
|
|
auto segment = grid.segment(*it_contour_and_segment);
|
|
|
|
|
|
|
|
|
|
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
|
|
|
|
Point intersection_point;
|
|
|
|
|
if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) &&
|
|
|
|
|
if (travel_line.intersection(grid.line(*it_contour_and_segment), &intersection_point) &&
|
|
|
|
|
intersection_set.find(*it_contour_and_segment) == intersection_set.end()) {
|
|
|
|
|
intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second,
|
|
|
|
|
(transform_to_x_axis * intersection_point.cast<double>()).cast<coord_t>(), intersection_point);
|
|
|
|
|
intersections.push_back({ it_contour_and_segment->first, it_contour_and_segment->second, intersection_point });
|
|
|
|
|
intersection_set.insert(*it_contour_and_segment);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -77,57 +66,42 @@ struct AllIntersectionsVisitor
|
|
|
|
|
|
|
|
|
|
const EdgeGrid::Grid &grid;
|
|
|
|
|
std::vector<Intersection> &intersections;
|
|
|
|
|
Matrix2d transform_to_x_axis;
|
|
|
|
|
Line travel_line;
|
|
|
|
|
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
|
|
|
|
|
static Matrix2d rotation_by_direction(const Point &direction)
|
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
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());
|
|
|
|
|
if (point != polygon.points[point_idx])
|
|
|
|
|
return polygon.points[point_idx];
|
|
|
|
|
|
|
|
|
|
int line_idx = int(point_idx);
|
|
|
|
|
if (forward)
|
|
|
|
|
for (; point == polygon.points[line_idx]; line_idx = (((line_idx + 1) < int(polygon.points.size())) ? (line_idx + 1) : 0));
|
|
|
|
|
auto line_idx = int(point_idx);
|
|
|
|
|
//FIXME endless loop if all points are equal to point?
|
|
|
|
|
if constexpr (forward)
|
|
|
|
|
for (; point == polygon.points[line_idx]; line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0);
|
|
|
|
|
else
|
|
|
|
|
for (; point == polygon.points[line_idx]; line_idx = (((line_idx - 1) >= 0) ? (line_idx - 1) : (int(polygon.points.size()) - 1)));
|
|
|
|
|
for (; point == polygon.points[line_idx]; line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1);
|
|
|
|
|
return polygon.points[line_idx];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//FIXME will be in Point.h in the master
|
|
|
|
|
template<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);
|
|
|
|
|
|
|
|
|
|
Vec2d normal_1(-1 * (middle.y() - left.y()), middle.x() - left.x());
|
|
|
|
|
Vec2d normal_2(-1 * (right.y() - middle.y()), right.x() - middle.x());
|
|
|
|
|
normal_1.normalize();
|
|
|
|
|
normal_2.normalize();
|
|
|
|
|
|
|
|
|
|
return (normal_1 + normal_2).normalized();
|
|
|
|
|
return (perp(Point(middle - left)).cast<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 >= (polygon.size() - 1)) ? 0 : (point_idx + 1);
|
|
|
|
|
const size_t left_idx = point_idx == 0 ? polygon.size() - 1 : point_idx - 1;
|
|
|
|
|
const size_t right_idx = point_idx + 1 == polygon.size() ? 0 : point_idx + 1;
|
|
|
|
|
const Point &middle = polygon.points[point_idx];
|
|
|
|
|
const Point &left = find_first_different_vertex(polygon, left_idx, middle, false);
|
|
|
|
|
const Point &right = find_first_different_vertex(polygon, right_idx, middle, true);
|
|
|
|
|
const Point &left = find_first_different_vertex<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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -140,114 +114,11 @@ static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t poin
|
|
|
|
|
// Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx).
|
|
|
|
|
static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const coord_t offset)
|
|
|
|
|
{
|
|
|
|
|
const Point &left = find_first_different_vertex(polygon, left_idx, middle, false);
|
|
|
|
|
const Point &right = find_first_different_vertex(polygon, right_idx, middle, true);
|
|
|
|
|
const Point &left = find_first_different_vertex<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 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)
|
|
|
|
|
{
|
|
|
|
|
Polyline result;
|
|
|
|
@ -265,6 +136,8 @@ static double travel_length(const std::vector<TravelPoint> &travel) {
|
|
|
|
|
return total_length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
|
|
|
|
|
|
|
|
|
|
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
|
|
|
|
|
static void export_travel_to_svg(const Polygons &boundary,
|
|
|
|
|
const Line &original_travel,
|
|
|
|
@ -294,102 +167,6 @@ static void export_travel_to_svg(const Polygons &boundary,
|
|
|
|
|
}
|
|
|
|
|
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
|
|
|
|
|
|
|
|
|
|
static ExPolygons get_boundary(const Layer &layer)
|
|
|
|
|
{
|
|
|
|
|
const float perimeter_spacing = get_perimeter_spacing(layer);
|
|
|
|
|
const float perimeter_offset = perimeter_spacing / 2.f;
|
|
|
|
|
size_t polygons_count = 0;
|
|
|
|
|
for (const LayerRegion *layer_region : layer.regions())
|
|
|
|
|
polygons_count += layer_region->slices.surfaces.size();
|
|
|
|
|
|
|
|
|
|
ExPolygons boundary;
|
|
|
|
|
boundary.reserve(polygons_count);
|
|
|
|
|
for (const LayerRegion *layer_region : layer.regions())
|
|
|
|
|
for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon);
|
|
|
|
|
|
|
|
|
|
boundary = union_ex(boundary);
|
|
|
|
|
ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset);
|
|
|
|
|
ExPolygons result_boundary;
|
|
|
|
|
if (perimeter_boundary.size() != boundary.size()) {
|
|
|
|
|
// If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice.
|
|
|
|
|
ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary,
|
|
|
|
|
offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)),
|
|
|
|
|
perimeter_offset + float(SCALED_EPSILON));
|
|
|
|
|
perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset);
|
|
|
|
|
perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size());
|
|
|
|
|
perimeter_boundary.insert(perimeter_boundary.end(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end());
|
|
|
|
|
// By calling intersection_ex some artifacts arose by previous operations are removed.
|
|
|
|
|
result_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary));
|
|
|
|
|
} else {
|
|
|
|
|
result_boundary = std::move(perimeter_boundary);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto [contours, holes] = split_expolygon(boundary);
|
|
|
|
|
// Add an outer boundary to avoid crossing perimeters from supports
|
|
|
|
|
ExPolygons outer_boundary = union_ex(
|
|
|
|
|
diff(static_cast<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
|
|
|
|
|
enum class Direction { Forward, Backward };
|
|
|
|
|
static Direction get_shortest_direction(const Lines &lines,
|
|
|
|
@ -422,29 +199,89 @@ static Direction get_shortest_direction(const Lines &lines,
|
|
|
|
|
return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::vector<TravelPoint> simplify_travel(const EdgeGrid::Grid& edge_grid, const std::vector<TravelPoint>& travel, const Polygons& boundaries, const bool use_heuristics);
|
|
|
|
|
|
|
|
|
|
static size_t avoid_perimeters(const Polygons &boundaries,
|
|
|
|
|
const EdgeGrid::Grid &edge_grid,
|
|
|
|
|
const Point &start,
|
|
|
|
|
const Point &end,
|
|
|
|
|
const bool use_heuristics,
|
|
|
|
|
std::vector<TravelPoint> *result_out)
|
|
|
|
|
// 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)
|
|
|
|
|
{
|
|
|
|
|
const Point direction = end - start;
|
|
|
|
|
Matrix2d transform_to_x_axis = rotation_by_direction(direction);
|
|
|
|
|
|
|
|
|
|
const Line travel_line_orig(start, end);
|
|
|
|
|
const Line travel_line((transform_to_x_axis * start.cast<double>()).cast<coord_t>(),
|
|
|
|
|
(transform_to_x_axis * end.cast<double>()).cast<coord_t>());
|
|
|
|
|
|
|
|
|
|
std::vector<Intersection> intersections;
|
|
|
|
|
// Visitor to check for a collision of a line segment with any contour stored inside the edge_grid.
|
|
|
|
|
struct Visitor
|
|
|
|
|
{
|
|
|
|
|
AllIntersectionsVisitor visitor(edge_grid, intersections, transform_to_x_axis, travel_line_orig);
|
|
|
|
|
edge_grid.visit_cells_intersecting_line(start, end, visitor);
|
|
|
|
|
Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
|
|
|
|
|
|
|
|
|
|
bool operator()(coord_t iy, coord_t ix)
|
|
|
|
|
{
|
|
|
|
|
assert(pt_current != nullptr);
|
|
|
|
|
assert(pt_next != nullptr);
|
|
|
|
|
// Called with a row and colum of the grid cell, which is intersected by a line.
|
|
|
|
|
auto cell_data_range = grid.cell_data_range(iy, ix);
|
|
|
|
|
this->intersect = false;
|
|
|
|
|
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
|
|
|
|
// End points of the line segment and their vector.
|
|
|
|
|
auto segment = grid.segment(*it_contour_and_segment);
|
|
|
|
|
if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) {
|
|
|
|
|
this->intersect = true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Continue traversing the grid along the edge.
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const EdgeGrid::Grid &grid;
|
|
|
|
|
const Slic3r::Point *pt_current = nullptr;
|
|
|
|
|
const Slic3r::Point *pt_next = nullptr;
|
|
|
|
|
bool intersect = false;
|
|
|
|
|
} visitor(edge_grid);
|
|
|
|
|
|
|
|
|
|
std::vector<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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::sort(intersections.begin(), intersections.end());
|
|
|
|
|
return simplified_path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called by avoid_perimeters() and by simplify_travel_heuristics().
|
|
|
|
|
static size_t avoid_perimeters_inner(const Polygons &boundaries,
|
|
|
|
|
const EdgeGrid::Grid &edge_grid,
|
|
|
|
|
const Point &start,
|
|
|
|
|
const Point &end,
|
|
|
|
|
std::vector<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});
|
|
|
|
@ -500,24 +337,23 @@ static size_t avoid_perimeters(const Polygons &boundaries,
|
|
|
|
|
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
|
|
|
|
|
{
|
|
|
|
|
static int iRun = 0;
|
|
|
|
|
export_travel_to_svg(boundaries, travel_line_orig, result, intersections,
|
|
|
|
|
debug_out_path("AvoidCrossingPerimeters-initial-%d.svg", iRun++));
|
|
|
|
|
export_travel_to_svg(boundaries, Line(start, end), result, intersections,
|
|
|
|
|
debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++));
|
|
|
|
|
}
|
|
|
|
|
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
|
|
|
|
|
|
|
|
|
|
if(!intersections.empty())
|
|
|
|
|
result = simplify_travel(edge_grid, result, boundaries, use_heuristics);
|
|
|
|
|
if (! intersections.empty())
|
|
|
|
|
result = simplify_travel(edge_grid, result);
|
|
|
|
|
|
|
|
|
|
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
|
|
|
|
|
{
|
|
|
|
|
static int iRun = 0;
|
|
|
|
|
export_travel_to_svg(boundaries, travel_line_orig, result, intersections,
|
|
|
|
|
debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun++));
|
|
|
|
|
export_travel_to_svg(boundaries, Line(start, end), result, intersections,
|
|
|
|
|
debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++));
|
|
|
|
|
}
|
|
|
|
|
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
|
|
|
|
|
|
|
|
|
|
result_out->reserve(result_out->size() + result.size());
|
|
|
|
|
result_out->insert(result_out->end(), result.begin(), result.end());
|
|
|
|
|
append(result_out, std::move(result));
|
|
|
|
|
return intersections.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -563,10 +399,10 @@ static std::vector<TravelPoint> simplify_travel_heuristics(const EdgeGrid::Grid
|
|
|
|
|
visitor.reset();
|
|
|
|
|
visitor.travel_line.a = current.point;
|
|
|
|
|
visitor.travel_line.b = possible_new_next.point;
|
|
|
|
|
visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector());
|
|
|
|
|
edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor);
|
|
|
|
|
if (!intersections.empty()) {
|
|
|
|
|
std::sort(intersections.begin(), intersections.end());
|
|
|
|
|
Vec2d dir = (visitor.travel_line.b - visitor.travel_line.a).cast<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)
|
|
|
|
@ -576,9 +412,9 @@ static std::vector<TravelPoint> simplify_travel_heuristics(const EdgeGrid::Grid
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) {
|
|
|
|
|
if (path_length > shortcut_travel && path_length - shortcut_travel > new_path_shorter_by) {
|
|
|
|
|
new_path_shorter_by = path_length - shortcut_travel;
|
|
|
|
|
shortcut = possible_shortcut;
|
|
|
|
|
new_next = possible_new_next;
|
|
|
|
@ -600,78 +436,44 @@ static std::vector<TravelPoint> simplify_travel_heuristics(const EdgeGrid::Grid
|
|
|
|
|
return simplified_path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::vector<TravelPoint> simplify_travel(const EdgeGrid::Grid &edge_grid,
|
|
|
|
|
const std::vector<TravelPoint> &travel,
|
|
|
|
|
const Polygons &boundaries,
|
|
|
|
|
const bool use_heuristics)
|
|
|
|
|
// Called by AvoidCrossingPerimeters::travel_to()
|
|
|
|
|
static size_t avoid_perimeters(const Polygons &boundaries,
|
|
|
|
|
const EdgeGrid::Grid &edge_grid,
|
|
|
|
|
const Point &start,
|
|
|
|
|
const Point &end,
|
|
|
|
|
Polyline &result_out)
|
|
|
|
|
{
|
|
|
|
|
struct Visitor
|
|
|
|
|
// Travel line is completely or partially inside the bounding box.
|
|
|
|
|
std::vector<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
|
|
|
|
|
{
|
|
|
|
|
Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
|
|
|
|
|
static int iRun = 0;
|
|
|
|
|
export_travel_to_svg(boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++));
|
|
|
|
|
}
|
|
|
|
|
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
|
|
|
|
|
|
|
|
|
|
bool operator()(coord_t iy, coord_t ix)
|
|
|
|
|
{
|
|
|
|
|
assert(pt_current != nullptr);
|
|
|
|
|
assert(pt_next != nullptr);
|
|
|
|
|
// Called with a row and colum of the grid cell, which is intersected by a line.
|
|
|
|
|
auto cell_data_range = grid.cell_data_range(iy, ix);
|
|
|
|
|
this->intersect = false;
|
|
|
|
|
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
|
|
|
|
// End points of the line segment and their vector.
|
|
|
|
|
auto segment = grid.segment(*it_contour_and_segment);
|
|
|
|
|
if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) {
|
|
|
|
|
this->intersect = true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Continue traversing the grid along the edge.
|
|
|
|
|
return num_intersections;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if anyone of ExPolygons contains whole travel.
|
|
|
|
|
// called by need_wipe()
|
|
|
|
|
template<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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool need_wipe(const GCode &gcodegen,
|
|
|
|
@ -692,11 +494,8 @@ static bool need_wipe(const GCode &gcodegen,
|
|
|
|
|
if (any_expolygon_contains(slice, original_travel)) {
|
|
|
|
|
// Check if original_travel and result_travel are not same.
|
|
|
|
|
// If both are the same, then it is possible to skip testing of result_travel
|
|
|
|
|
if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) {
|
|
|
|
|
wipe_needed = false;
|
|
|
|
|
} else {
|
|
|
|
|
wipe_needed = !any_expolygon_contains(slice, result_travel);
|
|
|
|
|
}
|
|
|
|
|
wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) &&
|
|
|
|
|
!any_expolygon_contains(slice, result_travel);
|
|
|
|
|
} else {
|
|
|
|
|
wipe_needed = true;
|
|
|
|
|
}
|
|
|
|
@ -719,28 +518,27 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
|
|
|
|
|
Point end = point + scaled_origin;
|
|
|
|
|
Polyline result_pl;
|
|
|
|
|
size_t travel_intersection_count = 0;
|
|
|
|
|
if (!check_if_could_cross_perimeters(use_external ? m_bbox_external : m_bbox, start, end)) {
|
|
|
|
|
result_pl = Polyline({start, end});
|
|
|
|
|
travel_intersection_count = 0;
|
|
|
|
|
Vec2d startf = start.cast<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.
|
|
|
|
|
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 {
|
|
|
|
|
std::vector<TravelPoint> result;
|
|
|
|
|
auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end);
|
|
|
|
|
if (use_external)
|
|
|
|
|
travel_intersection_count = avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result);
|
|
|
|
|
else
|
|
|
|
|
travel_intersection_count = avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result);
|
|
|
|
|
|
|
|
|
|
result_pl = to_polyline(result);
|
|
|
|
|
// Travel line is completely outside the bounding box.
|
|
|
|
|
result_pl = {start, end};
|
|
|
|
|
travel_intersection_count = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result_pl.points.front() = start;
|
|
|
|
|
result_pl.points.back() = end;
|
|
|
|
|
|
|
|
|
|
Line travel(start, end);
|
|
|
|
|
double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour);
|
|
|
|
|
if ((max_detour_length > 0) && ((result_pl.length() - travel.length()) > max_detour_length)) {
|
|
|
|
|
result_pl = Polyline({start, end});
|
|
|
|
|
}
|
|
|
|
|
if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length)
|
|
|
|
|
result_pl = {start, end};
|
|
|
|
|
|
|
|
|
|
if (use_external) {
|
|
|
|
|
result_pl.translate(-scaled_origin);
|
|
|
|
|
*could_be_wipe_disabled = false;
|
|
|
|
@ -750,6 +548,172 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
|
|
|
|
|
return result_pl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ************************************* AvoidCrossingPerimeters::init_layer() *****************************************
|
|
|
|
|
|
|
|
|
|
// called by get_perimeter_spacing() / get_perimeter_spacing_external()
|
|
|
|
|
static inline float get_default_perimeter_spacing(const Print &print)
|
|
|
|
|
{
|
|
|
|
|
//FIXME better use extruders printing this PrintObject or this Print?
|
|
|
|
|
//FIXME maybe better use an average of printing extruders?
|
|
|
|
|
const std::vector<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)
|
|
|
|
|
{
|
|
|
|
|
m_slice.clear();
|
|
|
|
@ -757,19 +721,25 @@ void AvoidCrossingPerimeters::init_layer(const Layer &layer)
|
|
|
|
|
m_boundaries_external.clear();
|
|
|
|
|
|
|
|
|
|
for (const LayerRegion *layer_region : layer.regions())
|
|
|
|
|
//FIXME making copies?
|
|
|
|
|
append(m_slice, (ExPolygons) layer_region->slices);
|
|
|
|
|
|
|
|
|
|
m_boundaries = to_polygons(get_boundary(layer));
|
|
|
|
|
m_boundaries_external = to_polygons(get_boundary_external(layer));
|
|
|
|
|
|
|
|
|
|
m_bbox = get_extents(m_boundaries);
|
|
|
|
|
m_bbox.offset(SCALED_EPSILON);
|
|
|
|
|
m_bbox_external = get_extents(m_boundaries_external);
|
|
|
|
|
m_bbox_external.offset(SCALED_EPSILON);
|
|
|
|
|
BoundingBox bbox(get_extents(m_boundaries));
|
|
|
|
|
bbox.offset(SCALED_EPSILON);
|
|
|
|
|
BoundingBox bbox_external = get_extents(m_boundaries_external);
|
|
|
|
|
bbox_external.offset(SCALED_EPSILON);
|
|
|
|
|
|
|
|
|
|
m_grid.set_bbox(m_bbox);
|
|
|
|
|
m_bbox = BoundingBoxf(bbox.min.cast<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(m_bbox_external);
|
|
|
|
|
m_grid_external.set_bbox(bbox_external);
|
|
|
|
|
//FIXME 1mm grid?
|
|
|
|
|
m_grid_external.create(m_boundaries_external, coord_t(scale_(1.)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|