Modified variable offset in the avoid crossing perimeters to not cause scars on thin objects (#7699).

Previously, the minimum contour width was chosen too conservative and, on some thin objects, only allowed minimal (or non) offset. This could result in travels being planned along the outer perimeter.

Now, the minimum contour width is chosen much smaller at the start and tested if the variable offset wasn't failed (the outer contour broke up into more parts, more or fewer holes, etc.).
If any problem is detected, the variable offset is recalculated with a larger minimum contour width.
This commit is contained in:
Lukáš Hejl 2022-01-12 09:55:18 +01:00
parent 7ab39f6255
commit dc00f0bf98

View File

@ -20,6 +20,8 @@ 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;
// simplify_travel() doesn't remove this point.
bool do_not_remove = false;
};
struct Intersection
@ -32,6 +34,8 @@ struct Intersection
Point point;
// Distance from the first point in the corresponding boundary
float distance;
// simplify_travel() doesn't remove this point.
bool do_not_remove = false;
};
struct ClosestLine
@ -207,8 +211,8 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
const ClosestLine &cl_start = start_lines[cl_indices.first];
const ClosestLine &cl_end = end_lines[cl_indices.second];
std::vector<Intersection> new_intersections;
new_intersections.push_back({cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)});
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)});
new_intersections.push_back({cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start), true});
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end), true});
return new_intersections;
}
}
@ -259,7 +263,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
if (cl_start_idx != std::numeric_limits<size_t>::max()) {
// If there is any ClosestLine around the start point closer to the Intersection, then replace this Intersection with ClosestLine.
const ClosestLine &cl_start = start_lines[cl_start_idx];
new_intersections.front() = {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)};
new_intersections.front() = {cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start), true};
} else {
// Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the
// vector of intersections. This allows in some cases when it is more than one around ClosestLine start point chose that one which
@ -267,7 +271,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
// use the first one, which is the closest one to the start point.
size_t start_closest_lines_idx = find_closest_line_with_same_boundary_idx(start_lines, new_intersections, true);
const ClosestLine &cl_start = (start_closest_lines_idx != std::numeric_limits<size_t>::max()) ? start_lines[start_closest_lines_idx] : start_lines.front();
new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)});
new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start), true});
}
}
@ -276,7 +280,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
if (cl_end_idx != std::numeric_limits<size_t>::max()) {
// If there is any ClosestLine around the end point closer to the Intersection, then replace this Intersection with ClosestLine.
const ClosestLine &cl_end = end_lines[cl_end_idx];
new_intersections.back() = {cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)};
new_intersections.back() = {cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end), true};
} else {
// Check if there is any ClosestLine with the same boundary_idx as any Intersection. If this ClosestLine exists, then add it to the
// vector of intersections. This allows in some cases when it is more than one around ClosestLine end point chose that one which
@ -284,7 +288,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
// use the first one, which is the closest one to the end point.
size_t end_closest_lines_idx = find_closest_line_with_same_boundary_idx(end_lines, new_intersections, false);
const ClosestLine &cl_end = (end_closest_lines_idx != std::numeric_limits<size_t>::max()) ? end_lines[end_closest_lines_idx] : end_lines.front();
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)});
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end), true});
}
}
return new_intersections;
@ -359,16 +363,17 @@ static void export_travel_to_svg(const Polygons &boundary,
const std::vector<Intersection> &intersections,
const std::string &path)
{
BoundingBox bbox = get_extents(boundary);
coordf_t stroke_width = scale_(0.05);
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");
svg.draw_outline(boundary, "green", stroke_width);
svg.draw(original_travel, "blue", stroke_width);
svg.draw(result_travel, "red", stroke_width);
svg.draw(original_travel.a, "black", stroke_width);
svg.draw(original_travel.b, "grey", stroke_width);
for (const Intersection &intersection : intersections)
svg.draw(intersection.point, "lightseagreen");
svg.draw(intersection.point, "lightseagreen", stroke_width);
}
static void export_travel_to_svg(const Polygons &boundary,
@ -433,21 +438,22 @@ static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::B
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;
}
if (!next.do_not_remove)
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size() && !travel[point_idx_2].do_not_remove; ++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;
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);
}
@ -566,7 +572,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
// 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)});
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), intersection_first.do_not_remove});
// Check if intersection line also exit the boundary polygon
if (it_second_r != it_last_item) {
@ -590,7 +596,7 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
// 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)});
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), intersection_second.do_not_remove});
// Skip intersections in between
it_first = it_second;
}
@ -945,55 +951,71 @@ static std::vector<float> contour_distance(const EdgeGrid::Grid &grid,
// ExPolygons are handled one by one so returned ExPolygons could intersect.
static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset)
{
double min_contour_width = 2. * offset + SCALED_EPSILON;
double search_radius = 2. * (offset + min_contour_width);
ExPolygons ex_poly_result = ex_polygons;
const std::vector<double> min_contour_width_values = {offset / 2., offset, 2. * offset + SCALED_EPSILON};
ExPolygons ex_poly_result = ex_polygons;
resample_expolygons(ex_poly_result, offset / 2, scaled<double>(0.5));
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);
// Filter out expolygons smaller than 0.1mm^2
if (Vec2d bbox_size = bbox.size().cast<double>(); bbox_size.x() * bbox_size.y() < Slic3r::sqr(scale_(0.1f)))
continue;
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);
}
for (const double &min_contour_width : min_contour_width_values) {
const size_t min_contour_width_idx = &min_contour_width - &min_contour_width_values.front();
const double search_radius = 2. * (offset + min_contour_width);
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;
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 && offset_ex_poly.front().holes.size() == ex_poly.holes.size()) {
ex_poly = std::move(offset_ex_poly.front());
break;
} else if ((min_contour_width_idx + 1) < min_contour_width_values.size()) {
continue; // Try the next round with bigger min_contour_width.
} else if (offset_ex_poly.size() == 1) {
ex_poly = std::move(offset_ex_poly.front());
break;
} 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]);
break;
}
ex_poly = std::move(offset_ex_poly[max_area_idx]);
}
}
return ex_poly_result;