diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index a6e5e1fb4a..423329fc81 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -306,47 +306,15 @@ std::vector group_fills(const Layer &layer) } } - // Detect narrow internal solid infill area and use ipEnsuring pattern instead. - { - std::vector narrow_expolygons; - static constexpr const auto narrow_pattern = ipEnsuring; - for (size_t surface_fill_id = 0, num_old_fills = surface_fills.size(); surface_fill_id < num_old_fills; ++ surface_fill_id) - if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) { - size_t num_expolygons = fill.expolygons.size(); - narrow_expolygons.clear(); - narrow_expolygons.reserve(num_expolygons); - // Detect narrow expolygons. - int num_narrow = 0; - for (const ExPolygon &ex : fill.expolygons) { - bool narrow = offset_ex(ex, -scaled(NarrowInfillAreaThresholdMM)).empty(); - num_narrow += int(narrow); - narrow_expolygons.emplace_back(narrow); - } - if (num_narrow == num_expolygons) { - // All expolygons are narrow, change the fill pattern. - fill.params.pattern = narrow_pattern; - } else if (num_narrow > 0) { - // Some expolygons are narrow, split the fills. - params = fill.params; - params.pattern = narrow_pattern; - surface_fills.emplace_back(params); - SurfaceFill &old_fill = surface_fills[surface_fill_id]; - SurfaceFill &new_fill = surface_fills.back(); - new_fill.region_id = old_fill.region_id; - new_fill.surface.surface_type = stInternalSolid; - new_fill.surface.thickness = old_fill.surface.thickness; - new_fill.expolygons.reserve(num_narrow); - for (size_t i = 0; i < narrow_expolygons.size(); ++ i) - if (narrow_expolygons[i]) - new_fill.expolygons.emplace_back(std::move(old_fill.expolygons[i])); - old_fill.expolygons.erase(std::remove_if(old_fill.expolygons.begin(), old_fill.expolygons.end(), - [&narrow_expolygons, ex_first = old_fill.expolygons.data()](const ExPolygon& ex) { return narrow_expolygons[&ex - ex_first]; }), - old_fill.expolygons.end()); - } - } - } + // Detect narrow internal solid infill area and use ipEnsuring pattern instead. + { + for (size_t surface_fill_id = 0; surface_fill_id < surface_fills.size(); ++surface_fill_id) + if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) { + fill.params.pattern = ipEnsuring; + } + } - return surface_fills; + return surface_fills; } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING diff --git a/src/libslic3r/Fill/FillEnsuring.cpp b/src/libslic3r/Fill/FillEnsuring.cpp index 2522aee938..14b2ebedcf 100644 --- a/src/libslic3r/Fill/FillEnsuring.cpp +++ b/src/libslic3r/Fill/FillEnsuring.cpp @@ -2,9 +2,20 @@ #include "../ShortestPath.hpp" #include "../Arachne/WallToolPaths.hpp" +#include "AABBTreeLines.hpp" +#include "ExPolygon.hpp" #include "FillEnsuring.hpp" +#include "Line.hpp" +#include "Polygon.hpp" +#include "Polyline.hpp" +#include "SVG.hpp" +#include "libslic3r.h" #include +#include +#include +#include +#include namespace Slic3r { @@ -13,17 +24,225 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const assert(params.use_arachne); assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr); - const coord_t scaled_spacing = scaled(this->spacing); + auto rotate_thick_polylines = [](ThickPolylines &tpolylines, double cos_angle, double sin_angle) { + for (ThickPolyline &tp : tpolylines) { + for (auto &p : tp.points) { + double px = double(p.x()); + double py = double(p.y()); + p.x() = coord_t(round(cos_angle * px - sin_angle * py)); + p.y() = coord_t(round(cos_angle * py + sin_angle * px)); + } + } + }; + + auto segments_overlap = [](coord_t alow, coord_t ahigh, coord_t blow, coord_t bhigh) { + return (alow >= blow && alow <= bhigh) || (ahigh >= blow && ahigh <= bhigh) || (blow >= alow && blow <= ahigh) || + (bhigh >= alow && bhigh <= ahigh); + }; + + Polygons filled_area = to_polygons(surface->expolygon); + double aligning_angle = -this->angle + PI * 0.5; + polygons_rotate(filled_area, aligning_angle); + Polygons internal_area = shrink(filled_area, scale_(this->overlap)); + BoundingBox bb = get_extents(internal_area); + const coord_t scaled_spacing = scaled(this->spacing); + + const size_t n_vlines = (bb.max.x() - bb.min.x() + scaled_spacing - 1) / scaled_spacing; + std::vector vertical_lines(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + coord_t x = bb.min.x() + i * scaled_spacing; + coord_t y_min = bb.min.y(); + coord_t y_max = bb.max.y(); + vertical_lines[i].a = Point{x, y_min}; + vertical_lines[i].b = Point{x, y_max}; + } + + auto internal_area_distancer = AABBTreeLines::LinesDistancer{to_lines(internal_area)}; + + std::vector> polygon_sections(n_vlines); + for (size_t i = 0; i < n_vlines; i++) { + auto area_intersections = internal_area_distancer.intersections_with_line(vertical_lines[i]); + for (int intersection_idx = 0; intersection_idx < int(area_intersections.size()) - 1; intersection_idx++) { + if (internal_area_distancer.outside( + (area_intersections[intersection_idx].first + area_intersections[intersection_idx + 1].first) / 2) < 0) { + polygon_sections[i].emplace_back(area_intersections[intersection_idx].first, area_intersections[intersection_idx + 1].first); + } + } + + for (int section_idx = 0; section_idx < int(polygon_sections[i].size()) - 1; section_idx++) { + Line §ion_a = polygon_sections[i][section_idx]; + Line §ion_b = polygon_sections[i][section_idx + 1]; + if (segments_overlap(section_a.a.y(), section_a.b.y(), section_b.a.y(), section_b.b.y())) { + section_b.a = section_a.a.y() < section_b.a.y() ? section_a.a : section_b.a; + section_b.b = section_a.b.y() < section_b.b.y() ? section_b.b : section_a.b; + section_a.a = section_a.b; + } + } + + polygon_sections[i].erase(std::remove_if(polygon_sections[i].begin(), polygon_sections[i].end(), + [](const Line &s) { return s.a == s.b; }), + polygon_sections[i].end()); + } + + struct Node{ + int section_idx; + int line_idx; + int skips_taken = 0; + bool neighbours_explored = false; + std::vector> neighbours{}; + }; + + coord_t length_filter = scale_(3); + for (int section_idx = 0; section_idx < polygon_sections.size(); section_idx++) { + for (int line_idx = 0; line_idx < polygon_sections[section_idx].size(); line_idx++) { + if (const Line &line = polygon_sections[section_idx][line_idx]; line.a != line.b && line.length() < length_filter) { + std::set> to_remove{{section_idx, line_idx}}; + std::vector to_visit{{section_idx, line_idx}}; + while (!to_visit.empty()) { + Node curr = to_visit.back(); + const Line &curr_l = polygon_sections[curr.section_idx][curr.line_idx]; + if (curr.neighbours_explored) { + bool is_valid_for_removal = (curr_l.length() < length_filter) && ((int(to_remove.size()) - curr.skips_taken > 3) || + to_remove.size() == polygon_sections.size()); + if (!is_valid_for_removal) { + for (const auto &n : curr.neighbours) { + if (to_remove.find(n) != to_remove.end()) { + is_valid_for_removal = true; + break; + } + } + } + if (!is_valid_for_removal) { + to_remove.erase({curr.section_idx, curr.line_idx}); + } + to_visit.pop_back(); + } else { + to_visit.back().neighbours_explored = true; + int curr_index = to_visit.size() - 1; + bool can_use_skip = curr_l.length() <= length_filter && curr.skips_taken < 2; + if (curr.section_idx + 1 < polygon_sections.size()) { + for (int lidx = 0; lidx < polygon_sections[curr.section_idx + 1].size(); lidx++) { + if (const Line &nl = polygon_sections[curr.section_idx + 1][lidx]; + nl.a != nl.b && segments_overlap(curr_l.a.y(), curr_l.b.y(), nl.a.y(), nl.b.y()) && + (nl.length() < length_filter || can_use_skip)) { + to_visit[curr_index].neighbours.push_back({curr.section_idx + 1, lidx}); + to_remove.insert({curr.section_idx + 1, lidx}); + Node next_node{curr.section_idx + 1, lidx, curr.skips_taken + (nl.length() >= length_filter)}; + to_visit.push_back(next_node); + } + } + } + } + } + + for (const auto &pair : to_remove) { + Line &l = polygon_sections[pair.first][pair.second]; + l.a = l.b; + } + } + } + } + + for (size_t section_idx = 0; section_idx < polygon_sections.size(); section_idx++) { + polygon_sections[section_idx].erase(std::remove_if(polygon_sections[section_idx].begin(), polygon_sections[section_idx].end(), + [](const Line &s) { return s.a == s.b; }), + polygon_sections[section_idx].end()); + } + + Polygons reconstructed_area{}; + // reconstruct polygon from polygon sections + { + struct TracedPoly + { + std::vector lows; + std::vector highs; + }; + + std::vector current_traced_polys; + for (const auto &polygon_slice : polygon_sections) { + std::unordered_set used_segments; + for (TracedPoly &traced_poly : current_traced_polys) { + auto maybe_first_overlap = std::upper_bound(polygon_slice.begin(), polygon_slice.end(), traced_poly.lows.back(), + [](const Point &low, const Line &seg) { return seg.b.y() > low.y(); }); + + if (maybe_first_overlap != polygon_slice.end() && // segment exists + segments_overlap(traced_poly.lows.back().y(), traced_poly.highs.back().y(), maybe_first_overlap->a.y(), + maybe_first_overlap->b.y())) // segment is overlapping + { + // Overlapping segment. In that case, add it + // to the traced polygon and add segment to used segments + traced_poly.lows.push_back(traced_poly.lows.back() + Point{scaled_spacing / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a - Point{scaled_spacing / 2, 0}); + traced_poly.lows.push_back(maybe_first_overlap->a); + + traced_poly.highs.push_back(traced_poly.highs.back() + Point{scaled_spacing / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b - Point{scaled_spacing / 2, 0}); + traced_poly.highs.push_back(maybe_first_overlap->b); + used_segments.insert(&(*maybe_first_overlap)); + } else { + // Zero or multiple overlapping segments. Resolving this is nontrivial, + // so we just close this polygon and maybe open several new. This will hopefully happen much less often + traced_poly.lows.push_back(traced_poly.lows.back() + Point{scaled_spacing / 2, 0}); + traced_poly.highs.push_back(traced_poly.highs.back() + Point{scaled_spacing / 2, 0}); + Polygon &new_poly = reconstructed_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + traced_poly.lows.clear(); + traced_poly.highs.clear(); + } + } + + current_traced_polys.erase(std::remove_if(current_traced_polys.begin(), current_traced_polys.end(), + [](const TracedPoly &tp) { return tp.lows.empty(); }), + current_traced_polys.end()); + + for (const auto &segment : polygon_slice) { + if (used_segments.find(&segment) == used_segments.end()) { + TracedPoly &new_tp = current_traced_polys.emplace_back(); + new_tp.lows.push_back(segment.a - Point{scaled_spacing / 2, 0}); + new_tp.lows.push_back(segment.a); + new_tp.highs.push_back(segment.b - Point{scaled_spacing / 2, 0}); + new_tp.highs.push_back(segment.b); + } + } + } + + // add not closed polys + for (TracedPoly &traced_poly : current_traced_polys) { + Polygon &new_poly = reconstructed_area.emplace_back(std::move(traced_poly.lows)); + new_poly.points.insert(new_poly.points.end(), traced_poly.highs.rbegin(), traced_poly.highs.rend()); + } + } - // Perform offset. - Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled(this->overlap)) : ExPolygons{surface->expolygon}; - // Create the infills for each of the regions. ThickPolylines thick_polylines_out; - for (ExPolygon &ex_poly : expp) { - Point bbox_size = ex_poly.contour.bounding_box().size(); - coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1; - Polygons polygons = to_polygons(ex_poly); - Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, *this->print_object_config, *this->print_config); + for (const auto &a : polygon_sections) { + for (const auto &l : a) { + ThickPolyline tp{}; + tp.points = {l.a, l.b}; + tp.width = {double(scaled_spacing), double(scaled_spacing)}; + thick_polylines_out.push_back(tp); + } + } + + reconstructed_area = closing(reconstructed_area, float(SCALED_EPSILON), float(SCALED_EPSILON)); + ExPolygons gaps_for_additional_filling = diff_ex(filled_area, reconstructed_area); + if (this->overlap != 0) { + gaps_for_additional_filling = offset_ex(gaps_for_additional_filling, scaled(this->overlap)); + } + gaps_for_additional_filling = opening_ex(gaps_for_additional_filling, 0.3 * scaled_spacing); + + BoundingBox bbox = get_extents(filled_area); + bbox.offset(scale_(1.)); + ::Slic3r::SVG svg(debug_out_path(("surface" + std::to_string(surface->area())).c_str()).c_str(), bbox); + svg.draw(to_lines(filled_area), "red", scale_(0.3)); + svg.draw(to_lines(reconstructed_area), "blue", scale_(0.2)); + svg.draw(to_lines(gaps_for_additional_filling), "green", scale_(0.1)); + svg.Close(); + + for (const ExPolygon &expoly : gaps_for_additional_filling) { + Point bbox_size = expoly.contour.bounding_box().size(); + coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1; + Arachne::WallToolPaths wall_tool_paths(to_polygons(expoly), scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, + *this->print_object_config, *this->print_config); if (std::vector loops = wall_tool_paths.getToolPaths(); !loops.empty()) { std::vector all_extrusions; for (Arachne::VariableWidthLines &loop : loops) { @@ -76,6 +295,8 @@ ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const } } + rotate_thick_polylines(thick_polylines_out, cos(-aligning_angle), sin(-aligning_angle)); + return thick_polylines_out; }