mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-14 02:07:54 -06:00
Rework of outer borders to reduce unnecessary detours along the border.
The resulting path now contains all intersection with borders, which allows eliminating more unnecessary detours and more simplify the path.
This commit is contained in:
parent
ef9de07740
commit
c00c7eaed3
2 changed files with 135 additions and 71 deletions
|
@ -255,10 +255,43 @@ template<class T> static bool any_expolygon_contains(const ExPolygons &ex_polygo
|
||||||
return false;
|
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(), 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
#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<AvoidCrossingPerimeters2::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 AvoidCrossingPerimeters2::Intersection &intersection : intersections)
|
||||||
|
svg.draw(intersection.point, "lightseagreen");
|
||||||
|
}
|
||||||
|
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
|
||||||
|
|
||||||
ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer)
|
ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer)
|
||||||
{
|
{
|
||||||
const coord_t perimeter_spacing = get_perimeter_spacing(layer);
|
const coord_t perimeter_spacing = get_perimeter_spacing(layer);
|
||||||
const coord_t offset = perimeter_spacing / 2;
|
const coord_t perimeter_offset = perimeter_spacing / 2;
|
||||||
size_t polygons_count = 0;
|
size_t polygons_count = 0;
|
||||||
for (const LayerRegion *layer_region : layer.regions())
|
for (const LayerRegion *layer_region : layer.regions())
|
||||||
polygons_count += layer_region->slices.surfaces.size();
|
polygons_count += layer_region->slices.surfaces.size();
|
||||||
|
@ -269,25 +302,31 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer)
|
||||||
for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon);
|
for (const Surface &surface : layer_region->slices.surfaces) boundary.emplace_back(surface.expolygon);
|
||||||
|
|
||||||
boundary = union_ex(boundary);
|
boundary = union_ex(boundary);
|
||||||
ExPolygons perimeter_boundary = offset_ex(boundary, -offset);
|
ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset);
|
||||||
ExPolygons final_boundary;
|
ExPolygons result_boundary;
|
||||||
if (perimeter_boundary.size() != boundary.size()) {
|
if (perimeter_boundary.size() != boundary.size()) {
|
||||||
// If any part of the polygon is missing after shrinking, the boundary of slice is used instead.
|
// 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, offset + SCALED_EPSILON / 2)),
|
ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary,
|
||||||
offset + SCALED_EPSILON);
|
offset_ex(perimeter_boundary, perimeter_offset + SCALED_EPSILON / 2)),
|
||||||
perimeter_boundary = offset_ex(perimeter_boundary, offset);
|
perimeter_offset + SCALED_EPSILON);
|
||||||
|
perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset);
|
||||||
perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size());
|
perimeter_boundary.reserve(perimeter_boundary.size() + missing_perimeter_boundary.size());
|
||||||
perimeter_boundary.insert(perimeter_boundary.begin(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end());
|
perimeter_boundary.insert(perimeter_boundary.end(), missing_perimeter_boundary.begin(), missing_perimeter_boundary.end());
|
||||||
final_boundary = union_ex(intersection_ex(offset_ex(perimeter_boundary, -offset), boundary));
|
// 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 {
|
} else {
|
||||||
final_boundary = std::move(perimeter_boundary);
|
result_boundary = std::move(perimeter_boundary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto [contours, holes] = split_expolygon(boundary);
|
||||||
// Add an outer boundary to avoid crossing perimeters from supports
|
// Add an outer boundary to avoid crossing perimeters from supports
|
||||||
ExPolygons outer_boundary = diff_ex(offset_ex(boundary, 2 * perimeter_spacing), offset_ex(boundary, 2 * perimeter_spacing - offset));
|
ExPolygons outer_boundary = union_ex(
|
||||||
final_boundary.reserve(final_boundary.size() + outer_boundary.size());
|
diff(static_cast<Polygons>(Geometry::convex_hull(offset(contours, 2 * perimeter_spacing))),
|
||||||
final_boundary.insert(final_boundary.begin(), outer_boundary.begin(), outer_boundary.end());
|
offset(contours, perimeter_spacing + perimeter_offset)));
|
||||||
final_boundary = union_ex(final_boundary);
|
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.
|
// Collect all top layers that will not be crossed.
|
||||||
polygons_count = 0;
|
polygons_count = 0;
|
||||||
|
@ -303,15 +342,18 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer)
|
||||||
if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon);
|
if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon);
|
||||||
|
|
||||||
top_layer_polygons = union_ex(top_layer_polygons);
|
top_layer_polygons = union_ex(top_layer_polygons);
|
||||||
return diff_ex(final_boundary, offset_ex(top_layer_polygons, -offset));
|
return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
return final_boundary;
|
return result_boundary;
|
||||||
}
|
}
|
||||||
|
|
||||||
ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer)
|
ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer)
|
||||||
{
|
{
|
||||||
|
const coord_t perimeter_spacing = get_perimeter_spacing_external(layer);
|
||||||
|
const coord_t perimeter_offset = perimeter_spacing / 2;
|
||||||
ExPolygons boundary;
|
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()) {
|
for (const PrintObject *object : layer.object()->print()->objects()) {
|
||||||
ExPolygons polygons_per_obj;
|
ExPolygons polygons_per_obj;
|
||||||
for (Layer *l : object->layers())
|
for (Layer *l : object->layers())
|
||||||
|
@ -327,24 +369,19 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer)
|
||||||
for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y());
|
for (; boundary_idx < boundary.size(); ++boundary_idx) boundary[boundary_idx].translate(instance.shift.x(), instance.shift.y());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
boundary = union_ex(boundary);
|
||||||
const coord_t perimeter_spacing = get_perimeter_spacing_external(layer);
|
auto [contours, holes] = split_expolygon(boundary);
|
||||||
const coord_t perimeter_offset = perimeter_spacing / 2;
|
// 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.
|
||||||
Polygons contours;
|
ExPolygons result_boundary = union_ex(
|
||||||
Polygons holes;
|
diff(static_cast<Polygons>(Geometry::convex_hull(offset(contours, 2 * perimeter_spacing))),
|
||||||
contours.reserve(boundary.size());
|
offset(contours, perimeter_spacing + perimeter_offset)));
|
||||||
for (ExPolygon &poly : boundary) {
|
// All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed.
|
||||||
contours.emplace_back(poly.contour);
|
|
||||||
append(holes, poly.holes);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExPolygons final_boundary = union_ex(diff(offset(contours, perimeter_spacing * 3), offset(contours, 3 * perimeter_spacing - perimeter_offset)));
|
|
||||||
ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset)));
|
ExPolygons holes_boundary = union_ex(diff(offset(holes, perimeter_spacing), offset(holes, perimeter_offset)));
|
||||||
final_boundary.reserve(final_boundary.size() + holes_boundary.size());
|
result_boundary.reserve(result_boundary.size() + holes_boundary.size());
|
||||||
final_boundary.insert(final_boundary.end(), holes_boundary.begin(), holes_boundary.end());
|
result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end());
|
||||||
final_boundary = union_ex(final_boundary);
|
result_boundary = union_ex(result_boundary);
|
||||||
return final_boundary;
|
return result_boundary;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a direction of the shortest path along the polygon boundary
|
// Returns a direction of the shortest path along the polygon boundary
|
||||||
|
@ -409,12 +446,12 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr
|
||||||
bool intersect = false;
|
bool intersect = false;
|
||||||
} visitor(edge_grid);
|
} visitor(edge_grid);
|
||||||
|
|
||||||
Polyline optimized_comb_path;
|
Polyline simplified_path;
|
||||||
optimized_comb_path.points.reserve(travel.points.size());
|
simplified_path.points.reserve(travel.points.size());
|
||||||
optimized_comb_path.points.emplace_back(travel.points.front());
|
simplified_path.points.emplace_back(travel.points.front());
|
||||||
|
|
||||||
// Try to skip some points in the path.
|
// Try to skip some points in the path.
|
||||||
for (size_t point_idx = 1; point_idx < travel.size(); point_idx++) {
|
for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) {
|
||||||
const Point ¤t_point = travel.points[point_idx - 1];
|
const Point ¤t_point = travel.points[point_idx - 1];
|
||||||
Point next = travel.points[point_idx];
|
Point next = travel.points[point_idx];
|
||||||
|
|
||||||
|
@ -436,10 +473,10 @@ Polyline AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_gr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
optimized_comb_path.append(next);
|
simplified_path.append(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
return optimized_comb_path;
|
return simplified_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries,
|
size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries,
|
||||||
|
@ -502,19 +539,32 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari
|
||||||
Polyline result;
|
Polyline result;
|
||||||
result.append(start);
|
result.append(start);
|
||||||
for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) {
|
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;
|
const Intersection &intersection_first = *it_first;
|
||||||
for (auto it_second = it_first + 1; it_second != intersections.end(); ++it_second) {
|
// Skip the it_first from the search for the farthest exit point from the boundary polygon
|
||||||
const Intersection &intersection_second = *it_second;
|
auto it_last_item = std::make_reverse_iterator(it_first) - 1;
|
||||||
if (intersection_first.border_idx == intersection_second.border_idx) {
|
// Search for the farthest intersection different from it_first but with the same border_idx
|
||||||
Lines border_lines = boundaries[intersection_first.border_idx].lines();
|
auto it_second_r = std::find_if(intersections.rbegin(), it_last_item, [&intersection_first](const Intersection &intersection) {
|
||||||
// Append the nearest intersection into the path
|
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 left_idx = intersection_first.line_idx;
|
||||||
size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1);
|
size_t right_idx = (intersection_first.line_idx >= (boundaries[intersection_first.border_idx].points.size() - 1)) ? 0 : (intersection_first.line_idx + 1);
|
||||||
// Offset of the polygon's point using get_middle_point_offset is used to simplify calculation of intersection between boundary
|
// 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.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON));
|
result.append(get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, SCALED_EPSILON));
|
||||||
|
|
||||||
Direction shortest_direction = get_shortest_direction(border_lines, intersection_first.line_idx, intersection_second.line_idx,
|
// Check if intersection line also exit the boundary polygon
|
||||||
intersection_first.point, intersection_second.point);
|
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
|
// Append the path around the border into the path
|
||||||
if (shortest_direction == Direction::Forward)
|
if (shortest_direction == Direction::Forward)
|
||||||
for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx);
|
for (int line_idx = intersection_first.line_idx; line_idx != int(intersection_second.line_idx);
|
||||||
|
@ -531,16 +581,30 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundari
|
||||||
right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1);
|
right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1);
|
||||||
result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON));
|
result.append(get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, SCALED_EPSILON));
|
||||||
// Skip intersections in between
|
// Skip intersections in between
|
||||||
it_first = (it_second - 1);
|
it_first = it_second;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.append(end);
|
result.append(end);
|
||||||
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-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, travel_line_orig, result, intersections,
|
||||||
|
debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun++));
|
||||||
|
}
|
||||||
|
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
|
||||||
|
|
||||||
append(result_out->points, result.points);
|
append(result_out->points, result.points);
|
||||||
return intersections.size();
|
return intersections.size();
|
||||||
|
|
|
@ -58,7 +58,7 @@ protected:
|
||||||
|
|
||||||
class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters
|
class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters
|
||||||
{
|
{
|
||||||
protected:
|
public:
|
||||||
struct Intersection
|
struct Intersection
|
||||||
{
|
{
|
||||||
// Index of the polygon containing this point of intersection.
|
// Index of the polygon containing this point of intersection.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue