Improve pathing on extra perimeters - should not print in air if possible (#29)

Fix issue 9545 - make extra perimeters print first anchored then rest by
sorting paths - breadth first search from anchored first
This commit is contained in:
Pavel Mikuš 2023-03-08 14:29:55 +01:00 committed by GitHub
parent 7e51f85394
commit 5885a47698
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -29,10 +29,12 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cassert> #include <cassert>
#include <cstddef>
#include <cstdlib> #include <cstdlib>
#include <functional> #include <functional>
#include <iterator> #include <iterator>
#include <limits> #include <limits>
#include <list>
#include <math.h> #include <math.h>
#include <ostream> #include <ostream>
#include <stack> #include <stack>
@ -663,187 +665,176 @@ static void export_perimeters_to_svg(const std::string &path, const Polygons &co
// find out if paths touch - at least one point of one path is within limit distance of second path // find out if paths touch - at least one point of one path is within limit distance of second path
bool paths_touch(const ExtrusionPath &path_one, const ExtrusionPath &path_two, double limit_distance) bool paths_touch(const ExtrusionPath &path_one, const ExtrusionPath &path_two, double limit_distance)
{ {
AABBTreeLines::LinesDistancer<Line> lines_one{path_one.as_polyline().lines()};
AABBTreeLines::LinesDistancer<Line> lines_two{path_two.as_polyline().lines()}; AABBTreeLines::LinesDistancer<Line> lines_two{path_two.as_polyline().lines()};
for (size_t pt_idx = 0; pt_idx < path_one.polyline.size(); pt_idx++) { for (size_t pt_idx = 0; pt_idx < path_one.polyline.size(); pt_idx++) {
if (lines_two.distance_from_lines<false>(path_one.polyline.points[pt_idx]) < limit_distance) { return true; } if (lines_two.distance_from_lines<false>(path_one.polyline.points[pt_idx]) < limit_distance) { return true; }
} }
AABBTreeLines::LinesDistancer<Line> lines_one{path_one.as_polyline().lines()};
for (size_t pt_idx = 0; pt_idx < path_two.polyline.size(); pt_idx++) { for (size_t pt_idx = 0; pt_idx < path_two.polyline.size(); pt_idx++) {
if (lines_one.distance_from_lines<false>(path_two.polyline.points[pt_idx]) < limit_distance) { return true; } if (lines_one.distance_from_lines<false>(path_two.polyline.points[pt_idx]) < limit_distance) { return true; }
} }
return false; return false;
} }
ExtrusionPaths reconnect_extrusion_paths(const ExtrusionPaths& paths, double limit_distance) { Polylines reconnect_polylines(const Polylines &polylines, double limit_distance)
if (paths.empty()) return paths; {
if (polylines.empty())
return polylines;
std::unordered_map<size_t, ExtrusionPath> connected; std::unordered_map<size_t, Polyline> connected;
connected.reserve(paths.size()); connected.reserve(polylines.size());
for (size_t i = 0; i < paths.size(); i++) { for (size_t i = 0; i < polylines.size(); i++) {
if (!paths[i].empty()) { if (!polylines[i].empty()) {
connected.emplace(i, paths[i]); connected.emplace(i, polylines[i]);
} }
} }
for (size_t a = 0; a < paths.size(); a++) { for (size_t a = 0; a < polylines.size(); a++) {
if (connected.find(a) == connected.end()) { if (connected.find(a) == connected.end()) {
continue; continue;
} }
ExtrusionPath &base = connected.at(a); Polyline &base = connected.at(a);
for (size_t b = a + 1; b < paths.size(); b++) { for (size_t b = a + 1; b < polylines.size(); b++) {
if (connected.find(b) == connected.end()) { if (connected.find(b) == connected.end()) {
continue; continue;
} }
ExtrusionPath &next = connected.at(b); Polyline &next = connected.at(b);
if ((base.last_point() - next.first_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) { if ((base.last_point() - next.first_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) {
base.polyline.append(std::move(next.polyline)); base.append(std::move(next));
connected.erase(b); connected.erase(b);
} else if ((base.last_point() - next.last_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) { } else if ((base.last_point() - next.last_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) {
base.polyline.points.insert(base.polyline.points.end(), next.polyline.points.rbegin(), next.polyline.points.rend()); base.points.insert(base.points.end(), next.points.rbegin(), next.points.rend());
connected.erase(b); connected.erase(b);
} else if ((base.first_point() - next.last_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) { } else if ((base.first_point() - next.last_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) {
next.polyline.append(std::move(base.polyline)); next.append(std::move(base));
base = std::move(next); base = std::move(next);
base.reverse(); base.reverse();
connected.erase(b); connected.erase(b);
} else if ((base.first_point() - next.first_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) { } else if ((base.first_point() - next.first_point()).cast<double>().squaredNorm() < limit_distance * limit_distance) {
base.reverse(); base.reverse();
base.polyline.append(std::move(next.polyline)); base.append(std::move(next));
base.reverse(); base.reverse();
connected.erase(b); connected.erase(b);
} }
} }
} }
ExtrusionPaths result; Polylines result;
for (auto& ext : connected) { for (auto &ext : connected) {
result.push_back(std::move(ext.second)); result.push_back(std::move(ext.second));
} }
return result; return result;
} }
ExtrusionPaths sort_and_connect_extra_perimeters(const std::vector<ExtrusionPaths> &extra_perims, double extrusion_spacing) ExtrusionPaths sort_extra_perimeters(ExtrusionPaths extra_perims, int index_of_first_unanchored, double extrusion_spacing)
{ {
std::vector<ExtrusionPaths> connected_shells; if (extra_perims.empty()) return {};
connected_shells.reserve(extra_perims.size());
for (const ExtrusionPaths &ps : extra_perims) { std::vector<std::unordered_set<size_t>> dependencies(extra_perims.size());
// this will also filter away empty paths for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) {
connected_shells.push_back(reconnect_extrusion_paths(ps, 1.0 * extrusion_spacing)); for (size_t prev_path_idx = 0; prev_path_idx < path_idx; prev_path_idx++) {
if (paths_touch(extra_perims[path_idx], extra_perims[prev_path_idx], extrusion_spacing * 1.5f)) {
dependencies[path_idx].insert(prev_path_idx);
}
}
} }
struct Pidx std::vector<bool> processed(extra_perims.size(), false);
{ for (size_t path_idx = 0; path_idx < index_of_first_unanchored; path_idx++) {
size_t shell; processed[path_idx] = true;
size_t path; }
bool operator==(const Pidx &rhs) const { return shell == rhs.shell && path == rhs.path; }
};
struct PidxHash
{
size_t operator()(const Pidx &i) const { return std::hash<size_t>{}(i.shell) ^ std::hash<size_t>{}(i.path); }
};
auto get_path = [&](Pidx i) { return connected_shells[i.shell][i.path]; }; for (size_t i = index_of_first_unanchored; i < extra_perims.size(); i++) {
bool change = false;
std::vector<std::unordered_map<Pidx, std::unordered_set<Pidx, PidxHash>, PidxHash>> dependencies; for (size_t path_idx = index_of_first_unanchored; path_idx < extra_perims.size(); path_idx++) {
for (size_t shell = 0; shell < connected_shells.size(); shell++) { if (processed[path_idx])
dependencies.push_back({}); continue;
auto &current_shell = dependencies[shell]; auto processed_dep = std::find_if(dependencies[path_idx].begin(), dependencies[path_idx].end(),
for (size_t path = 0; path < connected_shells[shell].size(); path++) { [&](size_t dep) { return processed[dep]; });
Pidx current_path{shell, path}; if (processed_dep != dependencies[path_idx].end()) {
std::unordered_set<Pidx, PidxHash> current_dependencies{}; for (auto it = dependencies[path_idx].begin(); it != dependencies[path_idx].end();) {
if (shell > 0) { if (!processed[*it]) {
for (const auto &prev_path : dependencies[shell - 1]) { dependencies[*it].insert(path_idx);
if (paths_touch(get_path(current_path), get_path(prev_path.first), extrusion_spacing * 1.5f)) { dependencies[path_idx].erase(it++);
current_dependencies.insert(prev_path.first); } else {
}; ++it;
}
} }
processed[path_idx] = true;
change = true;
} }
current_shell[current_path] = current_dependencies; }
if (!change) {
break;
} }
} }
Point current_point{}; Point current_point = extra_perims.begin()->first_point();
for (const ExtrusionPaths &ps : connected_shells) {
for (const ExtrusionPath &p : ps) {
if (!p.empty()) {
current_point = p.first_point();
goto first_point_found;
}
}
}
first_point_found:
ExtrusionPaths sorted_paths{}; ExtrusionPaths sorted_paths{};
Pidx npidx = Pidx{size_t(-1), 0}; size_t null_idx = size_t(-1);
Pidx next_pidx = npidx; size_t next_idx = null_idx;
bool reverse = false; bool reverse = false;
while (true) { while (true) {
if (next_pidx == npidx) { // find next pidx to print if (next_idx == null_idx) { // find next pidx to print
double dist = std::numeric_limits<double>::max(); double dist = std::numeric_limits<double>::max();
for (size_t shell = 0; shell < dependencies.size(); shell++) { for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) {
for (const auto &p : dependencies[shell]) { if (!dependencies[path_idx].empty())
if (!p.second.empty()) continue;
continue; const auto &path = extra_perims[path_idx];
const auto &path = get_path(p.first); double dist_a = (path.first_point() - current_point).cast<double>().squaredNorm();
double dist_a = (path.first_point() - current_point).cast<double>().squaredNorm(); if (dist_a < dist) {
if (dist_a < dist) { dist = dist_a;
dist = dist_a; next_idx = path_idx;
next_pidx = p.first; reverse = false;
reverse = false; }
} double dist_b = (path.last_point() - current_point).cast<double>().squaredNorm();
double dist_b = (path.last_point() - current_point).cast<double>().squaredNorm(); if (dist_b < dist) {
if (dist_b < dist) { dist = dist_b;
dist = dist_b; next_idx = path_idx;
next_pidx = p.first; reverse = true;
reverse = true;
}
} }
} }
if (next_pidx == npidx) { if (next_idx == null_idx) {
break; break;
} }
} else { } else {
// we have valid next_pidx, add it to the sorted paths, update dependencies, update current point and potentialy set new next_pidx // we have valid next_idx, add it to the sorted paths, update dependencies, update current point and potentialy set new next_idx
ExtrusionPath path = get_path(next_pidx); ExtrusionPath path = extra_perims[next_idx];
if (reverse) { if (reverse) {
path.reverse(); path.reverse();
} }
sorted_paths.push_back(path); sorted_paths.push_back(path);
assert(dependencies[next_idx].empty());
dependencies[next_idx].insert(null_idx);
current_point = sorted_paths.back().last_point(); current_point = sorted_paths.back().last_point();
if (next_pidx.shell < dependencies.size() - 1) { for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) {
for (auto &p : dependencies[next_pidx.shell + 1]) { dependencies[path_idx].erase(next_idx);
p.second.erase(next_pidx);
}
} }
dependencies[next_pidx.shell].erase(next_pidx); double dist = std::numeric_limits<double>::max();
// check current and next shell for next pidx next_idx = null_idx;
double dist = std::numeric_limits<double>::max();
size_t current_shell = next_pidx.shell; for (size_t path_idx = next_idx + 1; path_idx < extra_perims.size(); path_idx++) {
next_pidx = npidx; if (!dependencies[path_idx].empty()) {
for (size_t shell = current_shell; shell < std::min(current_shell + 2, dependencies.size()); shell++) { continue;
for (const auto &p : dependencies[shell]) { }
if (!p.second.empty()) const ExtrusionPath &next_path = extra_perims[path_idx];
continue; double dist_a = (next_path.first_point() - current_point).cast<double>().squaredNorm();
const ExtrusionPath &next_path = get_path(p.first); if (dist_a < dist) {
double dist_a = (next_path.first_point() - current_point).cast<double>().squaredNorm(); dist = dist_a;
if (dist_a < dist) { next_idx = path_idx;
dist = dist_a; reverse = false;
next_pidx = p.first; }
reverse = false; double dist_b = (next_path.last_point() - current_point).cast<double>().squaredNorm();
} if (dist_b < dist) {
double dist_b = (next_path.last_point() - current_point).cast<double>().squaredNorm(); dist = dist_b;
if (dist_b < dist) { next_idx = path_idx;
dist = dist_b; reverse = true;
next_pidx = p.first;
reverse = true;
}
} }
} }
if (dist > scaled(5.0)) { if (dist > scaled(5.0)) {
next_pidx = npidx; next_idx = null_idx;
} }
} }
} }
@ -875,61 +866,27 @@ first_point_found:
// #define EXTRA_PERIM_DEBUG_FILES // #define EXTRA_PERIM_DEBUG_FILES
// Function will generate extra perimeters clipped over nonbridgeable areas of the provided surface and returns both the new perimeters and // Function will generate extra perimeters clipped over nonbridgeable areas of the provided surface and returns both the new perimeters and
// Polygons filled by those clipped perimeters // Polygons filled by those clipped perimeters
std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over_overhangs(ExPolygons infill_area, std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over_overhangs(ExPolygons infill_area,
const Polygons &lower_slices_polygons, const Polygons &lower_slices_polygons,
const Flow &overhang_flow, int perimeter_count,
double scaled_resolution, const Flow &overhang_flow,
double scaled_resolution,
const PrintObjectConfig &object_config, const PrintObjectConfig &object_config,
const PrintConfig &print_config) const PrintConfig &print_config)
{ {
coord_t anchors_size = scale_(EXTERNAL_INFILL_MARGIN); coord_t anchors_size = std::min(coord_t(scale_(EXTERNAL_INFILL_MARGIN)), overhang_flow.scaled_spacing() * (perimeter_count + 1));
BoundingBox infill_area_bb = get_extents(infill_area).inflated(SCALED_EPSILON); BoundingBox infill_area_bb = get_extents(infill_area).inflated(SCALED_EPSILON);
Polygons optimized_lower_slices = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons, infill_area_bb); Polygons optimized_lower_slices = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons, infill_area_bb);
Polygons overhangs = diff(infill_area, optimized_lower_slices); Polygons overhangs = diff(infill_area, optimized_lower_slices);
if (overhangs.empty()) { return {}; } if (overhangs.empty()) { return {}; }
Polygons anchors = intersection(infill_area, optimized_lower_slices);
Polygons inset_anchors; // anchored area inset by the anchor length
{
std::vector<double> deltas{anchors_size * 0.15 + 0.5 * overhang_flow.scaled_spacing(),
anchors_size * 0.33 + 0.5 * overhang_flow.scaled_spacing(),
anchors_size * 0.66 + 0.5 * overhang_flow.scaled_spacing(), anchors_size * 1.00};
std::vector<Polygons> anchor_areas_w_delta_anchor_size{}; AABBTreeLines::LinesDistancer<Line> lower_layer_aabb_tree{to_lines(optimized_lower_slices)};
for (double delta : deltas) { Polygons anchors = intersection(infill_area, optimized_lower_slices);
// for each delta, store anchors without the delta region around overhangs Polygons inset_anchors = diff(anchors,
anchor_areas_w_delta_anchor_size.push_back(diff(anchors, expand(overhangs, delta, EXTRA_PERIMETER_OFFSET_PARAMETERS))); expand(overhangs, anchors_size + 0.1 * overhang_flow.scaled_width(), EXTRA_PERIMETER_OFFSET_PARAMETERS));
} Polygons inset_overhang_area = diff(infill_area, inset_anchors);
for (size_t i = 0; i < anchor_areas_w_delta_anchor_size.size() - 1; i++) {
// Then, clip off each anchor area by the next area expanded back to original size, so that this smaller anchor region is only where larger wouldnt fit
anchor_areas_w_delta_anchor_size[i] = diff(anchor_areas_w_delta_anchor_size[i], expand(anchor_areas_w_delta_anchor_size[i + 1],
deltas[i + 1], EXTRA_PERIMETER_OFFSET_PARAMETERS));
}
for (size_t i = 0; i < anchor_areas_w_delta_anchor_size.size(); i++) {
inset_anchors = union_(inset_anchors, anchor_areas_w_delta_anchor_size[i]);
}
inset_anchors = expand(inset_anchors, 0.1*overhang_flow.scaled_width());
#ifdef EXTRA_PERIM_DEBUG_FILES
{
std::vector<std::string> colors = {"blue", "purple", "orange", "red"};
BoundingBox bbox = get_extents(anchors);
bbox.offset(scale_(1.));
::Slic3r::SVG svg(debug_out_path("anchored").c_str(), bbox);
for (const Line &line : to_lines(inset_anchors)) svg.draw(line, "green", scale_(0.2));
for (size_t i = 0; i < anchor_areas_w_delta_anchor_size.size(); i++) {
for (const Line &line : to_lines(anchor_areas_w_delta_anchor_size[i])) svg.draw(line, colors[i], scale_(0.1));
}
svg.Close();
}
#endif
}
Polygons inset_overhang_area = diff(infill_area, inset_anchors);
#ifdef EXTRA_PERIM_DEBUG_FILES #ifdef EXTRA_PERIM_DEBUG_FILES
{ {
@ -944,7 +901,7 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
Polygons inset_overhang_area_left_unfilled; Polygons inset_overhang_area_left_unfilled;
std::vector<std::vector<ExtrusionPaths>> extra_perims; // overhang region -> shell -> shell parts std::vector<ExtrusionPaths> extra_perims; // overhang region -> extrusion paths
for (const ExPolygon &overhang : union_ex(to_expolygons(inset_overhang_area))) { for (const ExPolygon &overhang : union_ex(to_expolygons(inset_overhang_area))) {
Polygons overhang_to_cover = to_polygons(overhang); Polygons overhang_to_cover = to_polygons(overhang);
Polygons expanded_overhang_to_cover = expand(overhang_to_cover, 1.1 * overhang_flow.scaled_spacing()); Polygons expanded_overhang_to_cover = expand(overhang_to_cover, 1.1 * overhang_flow.scaled_spacing());
@ -956,9 +913,7 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
overhang_to_cover.end()); overhang_to_cover.end());
continue; continue;
} }
ExtrusionPaths &overhang_region = extra_perims.emplace_back();
extra_perims.emplace_back();
std::vector<ExtrusionPaths> &overhang_region = extra_perims.back();
Polygons anchoring = intersection(expanded_overhang_to_cover, inset_anchors); Polygons anchoring = intersection(expanded_overhang_to_cover, inset_anchors);
Polygons perimeter_polygon = offset(union_(expand(overhang_to_cover, 0.1 * overhang_flow.scaled_spacing()), anchoring), Polygons perimeter_polygon = offset(union_(expand(overhang_to_cover, 0.1 * overhang_flow.scaled_spacing()), anchoring),
@ -1004,9 +959,9 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
if (perimeter_polygon.empty()) { // fill possible gaps of single extrusion width if (perimeter_polygon.empty()) { // fill possible gaps of single extrusion width
Polygons shrinked = intersection(offset(prev, -0.3 * overhang_flow.scaled_spacing()), expanded_overhang_to_cover); Polygons shrinked = intersection(offset(prev, -0.3 * overhang_flow.scaled_spacing()), expanded_overhang_to_cover);
if (!shrinked.empty()) { if (!shrinked.empty()) {
overhang_region.emplace_back(); extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
extrusion_paths_append(overhang_region.back(), perimeter, ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
overhang_flow.width(), overhang_flow.height()); overhang_flow.height());
} }
Polylines fills; Polylines fills;
@ -1017,15 +972,15 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
} }
if (!fills.empty()) { if (!fills.empty()) {
fills = intersection_pl(fills, shrinked_overhang_to_cover); fills = intersection_pl(fills, shrinked_overhang_to_cover);
overhang_region.emplace_back(); extrusion_paths_append(overhang_region, reconnect_polylines(fills, overhang_flow.scaled_spacing()),
extrusion_paths_append(overhang_region.back(), fills, ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
overhang_flow.width(), overhang_flow.height()); overhang_flow.height());
} }
break; break;
} else { } else {
overhang_region.emplace_back(); extrusion_paths_append(overhang_region, reconnect_polylines(perimeter, overhang_flow.scaled_spacing()),
extrusion_paths_append(overhang_region.back(), perimeter, ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), ExtrusionRole::OverhangPerimeter, overhang_flow.mm3_per_mm(), overhang_flow.width(),
overhang_flow.width(), overhang_flow.height()); overhang_flow.height());
} }
if (intersection(perimeter_polygon, real_overhang).empty()) { continuation_loops--; } if (intersection(perimeter_polygon, real_overhang).empty()) { continuation_loops--; }
@ -1059,16 +1014,23 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
for (const Line &line : to_lines(inset_overhang_area_left_unfilled)) svg.draw(line, "red", scale_(0.05)); for (const Line &line : to_lines(inset_overhang_area_left_unfilled)) svg.draw(line, "red", scale_(0.05));
svg.Close(); svg.Close();
#endif #endif
overhang_region.erase(std::remove_if(overhang_region.begin(), overhang_region.end(),
[](const ExtrusionPath &p) { return p.empty(); }),
overhang_region.end());
std::reverse(overhang_region.begin(), overhang_region.end()); // reverse the order, It shall be printed from inside out if (!overhang_region.empty()) {
auto is_anchored = [&lower_layer_aabb_tree](const ExtrusionPath &path) {
return lower_layer_aabb_tree.distance_from_lines<true>(path.first_point()) <= 0 ||
lower_layer_aabb_tree.distance_from_lines<true>(path.last_point()) <= 0;
};
std::reverse(overhang_region.begin(), overhang_region.end());
auto first_unanchored = std::stable_partition(overhang_region.begin(), overhang_region.end(), is_anchored);
int index_of_first_unanchored = first_unanchored - overhang_region.begin();
overhang_region = sort_extra_perimeters(overhang_region, index_of_first_unanchored, overhang_flow.scaled_spacing());
}
} }
} }
std::vector<ExtrusionPaths> result{};
for (const std::vector<ExtrusionPaths> &paths : extra_perims) {
result.push_back(sort_and_connect_extra_perimeters(paths, overhang_flow.scaled_spacing()));
}
#ifdef EXTRA_PERIM_DEBUG_FILES #ifdef EXTRA_PERIM_DEBUG_FILES
BoundingBox bbox = get_extents(inset_overhang_area); BoundingBox bbox = get_extents(inset_overhang_area);
bbox.offset(scale_(2.)); bbox.offset(scale_(2.));
@ -1081,7 +1043,7 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
inset_overhang_area_left_unfilled = union_(inset_overhang_area_left_unfilled); inset_overhang_area_left_unfilled = union_(inset_overhang_area_left_unfilled);
return {result, diff(inset_overhang_area, inset_overhang_area_left_unfilled)}; return {extra_perims, diff(inset_overhang_area, inset_overhang_area_left_unfilled)};
} }
// Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper // Thanks, Cura developers, for implementing an algorithm for generating perimeters with variable width (Arachne) that is based on the paper
@ -1315,6 +1277,7 @@ void PerimeterGenerator::process_arachne(
// Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material
auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas,
lower_slices_polygons_cache, lower_slices_polygons_cache,
loop_number + 1,
params.overhang_flow, params.scaled_resolution, params.overhang_flow, params.scaled_resolution,
params.object_config, params.print_config); params.object_config, params.print_config);
if (!extra_perimeters.empty()) { if (!extra_perimeters.empty()) {
@ -1609,6 +1572,7 @@ void PerimeterGenerator::process_classic(
// Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material
auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas, auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_areas,
lower_slices_polygons_cache, lower_slices_polygons_cache,
loop_number + 1,
params.overhang_flow, params.scaled_resolution, params.overhang_flow, params.scaled_resolution,
params.object_config, params.print_config); params.object_config, params.print_config);
if (!extra_perimeters.empty()) { if (!extra_perimeters.empty()) {