From 84666a94339281c206a14cabdb5d47e37347494b Mon Sep 17 00:00:00 2001 From: ExPikaPaka Date: Tue, 29 Sep 2026 10:38:48 +0200 Subject: [PATCH] Move the slicing optimizations to their own branch They stand on their own: nothing in the texture displacement feature calls them, and nothing in them knows about textures. Reviewing a slicer-wide parallelization next to a new gizmo helped neither. They now live in perf/slicing-optimizations, based on current main. --- src/libslic3r/ClipperUtils.cpp | 67 -- src/libslic3r/ClipperUtils.hpp | 15 - src/libslic3r/Fill/Fill.cpp | 43 +- src/libslic3r/GCode/OrderingStrategies.cpp | 67 +- src/libslic3r/GCode/SeamPlacer.cpp | 31 +- src/libslic3r/KDTreeIndirect.hpp | 30 - src/libslic3r/LayerRegion.cpp | 3 +- src/libslic3r/MultiMaterialSegmentation.cpp | 346 ++------- src/libslic3r/MultiPoint.hpp | 6 +- src/libslic3r/PerimeterGenerator.cpp | 750 ++++++++++---------- src/libslic3r/Polygon.hpp | 4 +- src/libslic3r/Polyline.hpp | 4 +- src/libslic3r/PrintObject.cpp | 543 +++++--------- src/libslic3r/Support/SupportMaterial.cpp | 2 +- src/libslic3r/Support/TreeSupport.cpp | 32 +- src/libslic3r/Surface.hpp | 4 +- src/libslic3r/libslic3r.h | 8 +- tests/libslic3r/CMakeLists.txt | 1 - tests/libslic3r/test_clipper_utils.cpp | 44 -- tests/libslic3r/test_kdtree.cpp | 67 -- 20 files changed, 662 insertions(+), 1405 deletions(-) delete mode 100644 tests/libslic3r/test_kdtree.cpp diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index abb0433076..ea19eeb559 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -1,11 +1,7 @@ -#include -#include #include #include #include -#include - #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "ShortestPath.hpp" @@ -817,69 +813,6 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) { return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } - -namespace ClipperUtils { - std::vector tile_expolygons(const ExPolygons &expolygons, size_t per_tile) - { - BoundingBox extent; - std::vector bboxes; - bboxes.reserve(expolygons.size()); - for (const ExPolygon &expoly : expolygons) { - bboxes.emplace_back(get_extents(expoly)); - extent.merge(bboxes.back()); - } - if (! extent.defined) - return {}; - const int tiles = std::clamp(int(std::sqrt(double(expolygons.size()) / double(std::max(per_tile, 1)))), 1, 32); - const Point size = extent.size(); - const coord_t tile_w = std::max(1, size.x() / tiles + 1), tile_h = std::max(1, size.y() / tiles + 1); - std::vector out(size_t(tiles * tiles)); - for (size_t i = 0; i < expolygons.size(); ++ i) { - const Point c = bboxes[i].center(); - ExPolygonsTile &tile = out[size_t(std::clamp(int((c.y() - extent.min.y()) / tile_h), 0, tiles - 1) * tiles + - std::clamp(int((c.x() - extent.min.x()) / tile_w), 0, tiles - 1))]; - tile.members.emplace_back(i); - tile.bbox.merge(bboxes[i]); - } - out.erase(std::remove_if(out.begin(), out.end(), [](const ExPolygonsTile &tile) { return tile.members.empty(); }), out.end()); - return out; - } -} - -static Slic3r::ExPolygons clipper_ex_by_piece(ClipperLib::ClipType clipType, const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) -{ - // A few dozen subject ExPolygons to a tile, each tile one ClipperLib call with the clip cut to the tile's box. - const std::vector tiles = ClipperUtils::tile_expolygons(subject, 32); - std::vector clip_bboxes; - clip_bboxes.reserve(clip.size()); - for (const Polygon &polygon : clip) - clip_bboxes.emplace_back(get_extents(polygon)); - - std::vector out_tiles(tiles.size()); - tbb::parallel_for(size_t(0), tiles.size(), [&](size_t tile_idx) { - const ClipperUtils::ExPolygonsTile &tile = tiles[tile_idx]; - Slic3r::ExPolygons local_subject; - local_subject.reserve(tile.members.size()); - for (size_t i : tile.members) - local_subject.emplace_back(subject[i]); - // Grown so that the cut edges of the clip stay clear of the subject, also after the safety offset. - const BoundingBox bbox = tile.bbox.inflated(SCALED_EPSILON); - Polygons local_clip; - for (size_t i = 0; i < clip.size(); ++i) - if (clip_bboxes[i].overlap(bbox)) - if (Polygon clipped = ClipperUtils::clip_clipper_polygon_with_subject_bbox(clip[i], bbox); ! clipped.empty()) - local_clip.emplace_back(std::move(clipped)); - out_tiles[tile_idx] = _clipper_ex(clipType, ClipperUtils::ExPolygonsProvider(local_subject), ClipperUtils::PolygonsProvider(local_clip), do_safety_offset); - }); - Slic3r::ExPolygons out; - for (Slic3r::ExPolygons &out_tile : out_tiles) - append(out, std::move(out_tile)); - return out; -} -Slic3r::ExPolygons diff_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) - { return clipper_ex_by_piece(ClipperLib::ctDifference, subject, clip, do_safety_offset); } -Slic3r::ExPolygons intersection_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) - { return clipper_ex_by_piece(ClipperLib::ctIntersection, subject, clip, do_safety_offset); } // May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative). Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type) { return _clipper_ex(ClipperLib::ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); } diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 0c7b7f4294..5ed161c27a 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -2,7 +2,6 @@ #define slic3r_ClipperUtils_hpp_ #include "libslic3r.h" -#include "BoundingBox.hpp" #include "clipper.hpp" #include "ExPolygon.hpp" #include "Polygon.hpp" @@ -322,15 +321,6 @@ namespace ClipperUtils { [[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox, const bool get_entire_polygons = false); [[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox, const bool get_entire_polygons = false); - // Splits ExPolygons into tiles by the centres of their boxes, about `per_tile` of them to a tile, to run ClipperLib on a - // layer of many pieces tile by tile. Returns the non-empty tiles, each with the indices of its ExPolygons and their box. - struct ExPolygonsTile - { - BoundingBox bbox; - std::vector members; - }; - [[nodiscard]] std::vector tile_expolygons(const ExPolygons &expolygons, size_t per_tile); - } // Perform union of input polygons using the non-zero rule, convert to ExPolygons. @@ -528,11 +518,6 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); -// diff_ex() / intersection_ex() of the subject split into tiles, each against only the part of the clip near it, the tiles in -// parallel. The same area as the operation on the whole subject when its ExPolygons do not overlap, and much faster for a -// subject of thousands of pieces spread over a layer: ClipperLib slows down with the number of edges crossing a scan line. -Slic3r::ExPolygons diff_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); -Slic3r::ExPolygons intersection_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip); diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 56d54ef681..28fabed8af 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -9,8 +9,6 @@ #include "../PrintConfig.hpp" #include "../Surface.hpp" -#include - #include "AABBTreeLines.hpp" #include "ExtrusionEntity.hpp" #include "Fill.hpp" @@ -632,28 +630,24 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n if (!line_based_pattern) { const coord_t scaled_spacing = scaled(fill.params.spacing); - // Each expolygon is split on its own, so they run in parallel and are collected in their original order. - std::vector> split_parts(fill.expolygons.size()); // normal, narrow - tbb::parallel_for(size_t(0), fill.expolygons.size(), [&](size_t idx) { - const ExPolygon &expolygon = fill.expolygons[idx]; + for (const ExPolygon &expolygon : fill.expolygons) { Polygons filled_area = to_polygons(expolygon); // "Core" area: open (erode+dilate) to drop thin features, then clamp back to the original polygon. Polygons inner_area = intersection(filled_area, opening(filled_area, scaled_spacing, scaled_spacing)); if (inner_area.empty()) { - split_parts[idx].second.emplace_back(expolygon); - return; + narrow_infill.emplace_back(expolygon); + continue; } ExPolygons inner_ex = union_ex(inner_area); ExPolygons expolys{expolygon}; - split_parts[idx].second = diff_ex(expolys, inner_ex); // narrow infill area - split_parts[idx].first = intersection_ex(expolys, inner_ex); // normal infill area - }); - for (auto &[normal_ex, narrow_ex] : split_parts) { - append(normal_infill, std::move(normal_ex)); - append(narrow_infill, std::move(narrow_ex)); + ExPolygons narrow_ex = diff_ex(expolys, inner_ex); + ExPolygons normal_ex = intersection_ex(expolys, inner_ex); + + append(normal_infill, normal_ex); // normal infill area + append(narrow_infill, narrow_ex); // narrow infill area } return; @@ -675,10 +669,7 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n } const double aligning_angle = -base_angle + PI; - // Each expolygon is reconstructed on its own, so they run in parallel and are collected in their original order. - std::vector split_reconstructed(fill.expolygons.size()); - tbb::parallel_for(size_t(0), fill.expolygons.size(), [&](size_t expolygon_idx) { - const ExPolygon &expolygon = fill.expolygons[expolygon_idx]; + for (const ExPolygon &expolygon : fill.expolygons) { Polygons filled_area = to_polygons(expolygon); polygons_rotate(filled_area, aligning_angle); BoundingBox bb = get_extents(filled_area); @@ -809,10 +800,8 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n } } - split_reconstructed[expolygon_idx] = std::move(reconstructed_area); - }); - for (Polygons &reconstructed_area : split_reconstructed) - polygons_append(normal_fill_areas, std::move(reconstructed_area)); + polygons_append(normal_fill_areas, reconstructed_area); + } polygons_rotate(normal_fill_areas, -aligning_angle); @@ -1420,15 +1409,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface. f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox)); - // Only the part of the layer-wide no-overlap area under this expolygon matters, so clip it to the - // expolygon's box first (padded past the safety offset, which grows the clip side). The result is - // identical; the cost is not: a layer split into many small fills, e.g. by colour painting, - // otherwise intersects every one of them with the whole layer. - BoundingBox no_overlap_bbox = get_extents(expoly); - no_overlap_bbox.offset(SCALED_EPSILON); - f->no_overlap_expolygons = intersection_ex( - ClipperUtils::clip_clipper_polygons_with_subject_bbox(surface_fill.no_overlap_expolygons, no_overlap_bbox), - ExPolygons() = {expoly}, ApplySafetyOffset::Yes); + f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes); if (params.symmetric_infill_y_axis) { params.symmetric_y_axis = f->extended_object_bounding_box().center().x(); expoly.symmetric_y(params.symmetric_y_axis); diff --git a/src/libslic3r/GCode/OrderingStrategies.cpp b/src/libslic3r/GCode/OrderingStrategies.cpp index d52a326e2e..2ba9ae5490 100644 --- a/src/libslic3r/GCode/OrderingStrategies.cpp +++ b/src/libslic3r/GCode/OrderingStrategies.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -135,79 +134,15 @@ bool tsp_remove_crossings(std::vector& path, const Points& centers) return {std::numeric_limits::max(), std::numeric_limits::max()}; }; - // For many islands, the same scan with the edges binned in a uniform grid over their boxes, so each edge is only tested against the edges sharing a - // cell with it - two edges whose boxes overlap always do. It returns the same crossing as the all-pairs scan - // (smallest i, then smallest j), so the result is unchanged; with thousands of islands on a layer the all-pairs - // scan, repeated after every reversal, never finished. Rebuilding the grid costs more than it saves on small inputs. - constexpr size_t grid_min_size = 500; - BoundingBox extent; - for (size_t idx : path) - extent.merge(centers[idx]); - const int grid_n = std::clamp(int(std::sqrt(double(pn))), 1, 256); - const coord_t cell_w = std::max(1, (extent.max.x() - extent.min.x()) / grid_n + 1); - const coord_t cell_h = std::max(1, (extent.max.y() - extent.min.y()) / grid_n + 1); - const auto for_cells = [&](const Point& a, const Point& b, auto&& fn) { - const int x0 = int((std::min(a.x(), b.x()) - extent.min.x()) / cell_w), x1 = int((std::max(a.x(), b.x()) - extent.min.x()) / cell_w); - const int y0 = int((std::min(a.y(), b.y()) - extent.min.y()) / cell_h), y1 = int((std::max(a.y(), b.y()) - extent.min.y()) / cell_h); - for (int y = y0; y <= y1; ++y) - for (int x = x0; x <= x1; ++x) - fn(y * grid_n + x); - }; - std::vector> edge_cells(size_t(grid_n) * grid_n); - - auto find_crossing_grid = [&]() -> std::pair { - for (std::vector& cell : edge_cells) - cell.clear(); - for (size_t j = 0; j < n_edges; ++j) - for_cells(centers[path[j]], centers[path[(j + 1) % pn]], [&](int cell) { edge_cells[cell].emplace_back(j); }); - - for (size_t i = 0; i < n_edges; ++i) { - const Point& ai = centers[path[i]]; - const Point& bi = centers[path[(i + 1) % pn]]; - - size_t first_j = std::numeric_limits::max(); - for_cells(ai, bi, [&](int cell) { - for (size_t j : edge_cells[cell]) { - if (j < i + 2 || j >= first_j) continue; - // Skip the (0, pn-1) pair: edges (0,1) and (pn-1,0) share node 0. - if (i == 0 && j == pn - 1) continue; - - const Point& aj = centers[path[j]]; - const Point& bj = centers[path[(j + 1) % pn]]; - - if (!bboxes_overlap(ai, bi, aj, bj)) continue; - if (Geometry::segments_intersect(ai, bi, aj, bj)) - first_j = j; - } - }); - if (first_j != std::numeric_limits::max()) - return {i, first_j}; - } - return {std::numeric_limits::max(), std::numeric_limits::max()}; - }; - // Process crossings one at a time: find first, reverse it, restart scan. // Cap iterations to prevent infinite loops on collinear/overlapping segments. int max_iters = static_cast(pn * pn); bool improved = false; - // Reversing between two segments that only touch or overlap along a line need not remove the intersection, and on - // islands laid out on a regular grid (a tiled texture, an array of parts) the loop cycled through the same orderings - // until the pn * pn cap - effectively forever. Stop as soon as an ordering repeats: until then this is the same loop. - std::unordered_set seen_paths; - const auto path_hash = [&path]() { - uint64_t h = 1469598103934665603ull; // FNV-1a - for (size_t idx : path) - h = (h ^ uint64_t(idx)) * 1099511628211ull; - return h; - }; - seen_paths.insert(path_hash()); while (max_iters-- > 0) { - auto [ci, cj] = pn >= grid_min_size ? find_crossing_grid() : find_crossing(); + auto [ci, cj] = find_crossing(); if (ci == std::numeric_limits::max()) break; improved = true; std::reverse(path.begin() + ci + 1, path.begin() + cj + 1); - if (!seen_paths.insert(path_hash()).second) - break; } return improved; } diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index 04d2360f0e..d527ec6aa9 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -1179,21 +1178,21 @@ std::optional> SeamPlacer::find_next_seam_in_layer( const size_t layer_idx, const float max_distance, const SeamPlacerImpl::SeamComparator &comparator) const { using namespace SeamPlacerImpl; - // Find the best nearby point and the nearest one. A layer of a fine relief has tens of thousands of candidates within - // the radius, so they are looked at as the search finds them rather than collected into a vector first. - constexpr size_t none = std::numeric_limits::max(); - size_t best_nearby_point_index = none; - size_t nearest_point_index = none; - visit_nearby_points(*layers[layer_idx].points_tree, projected_position, max_distance, - [&layers, &comparator, &projected_position, layer_idx, &best_nearby_point_index, &nearest_point_index] - (size_t nearby_point_index) { - if (best_nearby_point_index == none) { - // The first point found starts both, as the first of the collected ones did. - best_nearby_point_index = nearest_point_index = nearby_point_index; - } + std::vector nearby_points_indices = find_nearby_points(*layers[layer_idx].points_tree, projected_position, + max_distance); + + if (nearby_points_indices.empty()) { + return {}; + } + + size_t best_nearby_point_index = nearby_points_indices[0]; + size_t nearest_point_index = nearby_points_indices[0]; + + // Now find best nearby point, nearest point, and corresponding indices + for (const size_t &nearby_point_index : nearby_points_indices) { const SeamCandidate &point = layers[layer_idx].points[nearby_point_index]; if (point.perimeter.finalized) { - return; // skip over finalized perimeters, try to find some that is not finalized + continue; // skip over finalized perimeters, try to find some that is not finalized } if (comparator.is_first_better(point, layers[layer_idx].points[best_nearby_point_index], projected_position.head<2>()) @@ -1205,10 +1204,6 @@ std::optional> SeamPlacer::find_next_seam_in_layer( || layers[layer_idx].points[nearest_point_index].perimeter.finalized) { nearest_point_index = nearby_point_index; } - }); - - if (best_nearby_point_index == none) { - return {}; } const SeamCandidate &best_nearby_point = layers[layer_idx].points[best_nearby_point_index]; diff --git a/src/libslic3r/KDTreeIndirect.hpp b/src/libslic3r/KDTreeIndirect.hpp index 8b48ffc818..37c10827b1 100644 --- a/src/libslic3r/KDTreeIndirect.hpp +++ b/src/libslic3r/KDTreeIndirect.hpp @@ -313,36 +313,6 @@ std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const P return visitor.result; } -// Visits the points within max_distance of center, in the order find_nearby_points() would collect them, and hands -// each of them to `visitor_fn` instead of returning them all: a search over a dense set spends more on collecting the -// points into a vector than on the search itself, and its caller usually keeps only a few of them. -template -void visit_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er, - const typename KDTreeIndirectType::CoordType &max_distance, VisitorFn visitor_fn) -{ - using CoordType = typename KDTreeIndirectType::CoordType; - - struct Visitor { - const KDTreeIndirectType &kdtree; - const PointType center; - const CoordType max_distance_squared; - VisitorFn visitor_fn; - - unsigned int operator()(size_t idx, size_t dimension) { - auto dist = CoordType(0); - for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++i) { - CoordType d = center[i] - kdtree.coordinate(idx, i); - dist += d * d; - } - if (dist < max_distance_squared) - visitor_fn(idx); - return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension); - } - } visitor { kdtree, center, max_distance * max_distance, visitor_fn }; - - kdtree.visit(visitor); -} - template std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er, const typename KDTreeIndirectType::CoordType& max_distance) diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 1dbdafa292..6f868ba095 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -72,11 +72,10 @@ void LayerRegion::slices_to_fill_surfaces_clipped() by_surface[size_t(surface.surface_type)].emplace_back(&surface); // Trim surfaces by the fill_boundaries. this->fill_surfaces.surfaces.clear(); - const Polygons fill_boundaries = to_polygons(this->fill_expolygons); for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) { const SurfacesPtr &this_surfaces = by_surface[surface_type]; if (! this_surfaces.empty()) - this->fill_surfaces.append(intersection_ex_by_piece(to_expolygons(this_surfaces), fill_boundaries), SurfaceType(surface_type)); + this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type)); } } diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 0798838b06..6f80f7b759 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -8,7 +8,6 @@ #include "MutablePolygon.hpp" #include "format.hpp" -#include #include #include @@ -1312,15 +1311,10 @@ static inline std::vector> segmentation_top_and_bottom_l } #endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM - // When the upper surface of an object is occluded, it should no longer be considered the upper surface. - // Every (colour, layer) pair is trimmed on its own, so they all run at once: the painted faces of a finely - // textured part project hundreds of thousands of triangles onto one layer, which used to be trimmed serially. + // When the upper surface of an object is occluded, it should no longer be considered the upper surface { - const size_t occluded_pairs = num_facets_states * layers.size(); - tbb::parallel_for(tbb::blocked_range(0, occluded_pairs), [&](const tbb::blocked_range &range) { - for (size_t pair_idx = range.begin(); pair_idx < range.end(); ++pair_idx) { - const size_t extruder_idx = pair_idx / layers.size(); - const size_t layer_idx = pair_idx % layers.size(); + for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) { + for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_idx].empty() && layer_idx + 1 < layers.size()) { top_raw[extruder_idx][layer_idx] = diff(top_raw[extruder_idx][layer_idx], input_expolygons[layer_idx + 1]); } @@ -1328,7 +1322,7 @@ static inline std::vector> segmentation_top_and_bottom_l bottom_raw[extruder_idx][layer_idx] = diff(bottom_raw[extruder_idx][layer_idx], input_expolygons[layer_idx - 1]); } } - }); + } } std::vector> triangles_by_color_bottom(num_facets_states); @@ -1384,62 +1378,13 @@ static inline std::vector> segmentation_top_and_bottom_l return out; }; - // Projects a painted top or bottom face `ex` of layer `layer_idx` onto the shell layers below or above it (in - // `shell_layers`, nearest first), one more perimeter in on each, stopping at the first layer where nothing is left. - // Only the slices within the deepest offset of `ex` (three times that with the miter joins) decide the result, so the - // work is done per tile of `ex`'s ExPolygons on the slices cut to the tile's box grown by that much: the same result, but - // each ClipperLib call stays the size of a tile rather than of a layer cut through a fine relief, and the tiles run in - // parallel. - const auto project_to_shells = [&input_expolygons](const ExPolygons &ex, size_t layer_idx, const std::vector &shell_layers, - const LayerColorStat &stat, std::vector &dst, size_t dst_offset) { - std::vector offsets(shell_layers.size()); - float offset = 0.f; - for (size_t i = 0; i < shell_layers.size(); ++i) { - //BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line - offset -= (stat.extrusion_spacing + stat.extrusion_width); - offsets[i] = offset; - } - if (offsets.empty()) - return; - const coord_t reach = coord_t(std::ceil(DefaultMiterLimit * std::abs(offsets.back()))) + 10 * SCALED_EPSILON; - const std::vector tiles = ClipperUtils::tile_expolygons(ex, 16); - // [shell layer][tile] - std::vector> shells(shell_layers.size(), std::vector(tiles.size())); - tbb::parallel_for(size_t(0), tiles.size(), [&](size_t tile_idx) { - const ClipperUtils::ExPolygonsTile &tile = tiles[tile_idx]; - const BoundingBox bbox = tile.bbox.inflated(reach); - ExPolygons tile_ex; - tile_ex.reserve(tile.members.size()); - for (size_t i : tile.members) - tile_ex.emplace_back(ex[i]); - Polygons layer_slices_trimmed = ClipperUtils::clip_clipper_polygons_with_subject_bbox(input_expolygons[layer_idx], bbox); - for (size_t i = 0; i < shell_layers.size() && ! layer_slices_trimmed.empty(); ++i) { - const ExPolygons trimmed = intersection_ex(layer_slices_trimmed, ClipperUtils::clip_clipper_polygons_with_subject_bbox(input_expolygons[shell_layers[i]], bbox)); - shells[i][tile_idx] = opening_ex(intersection_ex(tile_ex, offset_ex(trimmed, offsets[i])), stat.small_region_threshold); - layer_slices_trimmed = to_polygons(trimmed); - } - }); - for (size_t i = 0; i < shell_layers.size(); ++i) { - bool empty = true; - for (ExPolygons &shell : shells[i]) - if (! shell.empty()) { - append(dst[shell_layers[i] + dst_offset], std::move(shell)); - empty = false; - } - if (empty) - break; - } - }; - tbb::parallel_for(tbb::blocked_range(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top, - &throw_on_cancel_callback, &bottom_raw, &triangles_by_color_bottom, &project_to_shells, + &throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom, &shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range &range) { size_t group_idx = range.begin() / granularity; size_t layer_idx_offset = (group_idx & 1) * num_layers; for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { - // Each colour writes only its own vectors, so the colours run in parallel: a painted top or bottom face - // projects onto a single layer, which otherwise did all of its colours on one thread. - tbb::parallel_for(size_t(0), size_t(num_facets_states), [&](size_t color_idx) { + for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) { throw_on_cancel_callback(); LayerColorStat stat = layer_color_stat(layer_idx, color_idx); if (std::vector &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) @@ -1448,10 +1393,18 @@ static inline std::vector> segmentation_top_and_bottom_l top_ex = opening_ex(top_ex, stat.small_region_threshold); if (! top_ex.empty()) { append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex); - std::vector shell_layers; - for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) - shell_layers.emplace_back(size_t(last_idx)); - project_to_shells(top_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_top[color_idx], layer_idx_offset); + float offset = 0.f; + ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; + for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) { + //BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line + //offset -= stat.extrusion_width ; + offset -= (stat.extrusion_spacing + stat.extrusion_width); + layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); + ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); + if (last.empty()) + break; + append(shell_triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last)); + } } } if (std::vector &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty()) @@ -1460,13 +1413,21 @@ static inline std::vector> segmentation_top_and_bottom_l bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold); if (! bottom_ex.empty()) { append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex); - std::vector shell_layers; - for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) - shell_layers.emplace_back(last_idx); - project_to_shells(bottom_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_bottom[color_idx], layer_idx_offset); + float offset = 0.f; + ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; + for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) { + //BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line + //offset -= stat.extrusion_width; + offset -= (stat.extrusion_spacing + stat.extrusion_width); + layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]); + ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold); + if (last.empty()) + break; + append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last)); + } } } - }); + } } }); @@ -1476,25 +1437,22 @@ static inline std::vector> segmentation_top_and_bottom_l &shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { throw_on_cancel_callback(); - // The per-colour unions below are independent of each other, so they run in parallel (a painted top or - // bottom face puts all of its colours on one layer); whatever combines the colours stays in colour order. - const auto merge_colour_union = [&](size_t color_idx) { + ExPolygons painted_exploys; + for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) { auto &self = triangles_by_color_merged[color_idx][layer_idx]; append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx])); append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx + num_layers])); append(self, std::move(triangles_by_color_top[color_idx][layer_idx])); append(self, std::move(triangles_by_color_top[color_idx][layer_idx + num_layers])); self = union_ex(self); - }; - tbb::parallel_for(size_t(0), triangles_by_color_merged.size(), merge_colour_union); - ExPolygons painted_exploys; - for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) - append(painted_exploys, triangles_by_color_merged[color_idx][layer_idx]); + append(painted_exploys, self); + } + painted_exploys = union_ex(painted_exploys); //BBS: merge the top and bottom shell layers - tbb::parallel_for(size_t(0), triangles_by_color_merged.size(), [&](size_t color_idx) { + for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) { auto &self = triangles_by_color_merged[color_idx][layer_idx]; auto top_area = diff_ex(union_ex(shell_triangles_by_color_top[color_idx][layer_idx], @@ -1508,7 +1466,7 @@ static inline std::vector> segmentation_top_and_bottom_l append(self, top_area); append(self, bottom_area); self = union_ex(self); - }); + } // Trim one region by the other if some of the regions overlap. ExPolygons painted_regions; for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) { @@ -1875,69 +1833,7 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto } } - -// Finds the islands (layer ExPolygons) a region piece overlaps. A top or bottom region is projected from the neighbouring -// layers and may reach past the island it belongs to, or over several islands. -class IslandLocator -{ -public: - explicit IslandLocator(const ExPolygons &islands) : m_islands(islands) - { - m_bboxes.reserve(islands.size()); - for (const ExPolygon &island : islands) { - m_bboxes.emplace_back(get_extents(island)); - m_extent.merge(m_bboxes.back()); - } - if (!m_extent.defined) - return; - const Point size = m_extent.size(); - m_cell_w = std::max(1, size.x() / GRID + 1); - m_cell_h = std::max(1, size.y() / GRID + 1); - m_grid.assign(GRID * GRID, {}); - for (size_t i = 0; i < m_bboxes.size(); ++i) - for_cells(m_bboxes[i], [&](int cell) { m_grid[cell].emplace_back(i); }); - } - - void find(const ExPolygon &piece, std::vector &out) const - { - out.clear(); - const BoundingBox bbox = get_extents(piece); - if (!m_extent.defined || !m_extent.overlap(bbox)) - return; - for_cells(bbox, [&](int cell) { - for (size_t i : m_grid[cell]) - if (m_bboxes[i].overlap(bbox)) - out.emplace_back(i); - }); - sort_remove_duplicates(out); - if (out.size() > 1) - out.erase(std::remove_if(out.begin(), out.end(), [&](size_t i) { - const BoundingBox common(m_bboxes[i].min.cwiseMax(bbox.min), m_bboxes[i].max.cwiseMin(bbox.max)); - return intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(piece, common.inflated(SCALED_EPSILON)), - ClipperUtils::clip_clipper_polygons_with_subject_bbox(m_islands[i], common.inflated(SCALED_EPSILON))).empty(); - }), out.end()); - } - -private: - static constexpr int GRID = 64; - template void for_cells(const BoundingBox &bb, Fn &&fn) const - { - const int x0 = std::clamp(int((bb.min.x() - m_extent.min.x()) / m_cell_w), 0, GRID - 1), x1 = std::clamp(int((bb.max.x() - m_extent.min.x()) / m_cell_w), 0, GRID - 1); - const int y0 = std::clamp(int((bb.min.y() - m_extent.min.y()) / m_cell_h), 0, GRID - 1), y1 = std::clamp(int((bb.max.y() - m_extent.min.y()) / m_cell_h), 0, GRID - 1); - for (int y = y0; y <= y1; ++y) - for (int x = x0; x <= x1; ++x) - fn(y * GRID + x); - } - - const ExPolygons &m_islands; - std::vector m_bboxes; - BoundingBox m_extent; - coord_t m_cell_w = 1, m_cell_h = 1; - std::vector> m_grid; -}; - -static std::vector> merge_segmented_layers(const std::vector &input_expolygons, - const std::vector> &segmented_regions, +static std::vector> merge_segmented_layers(const std::vector> &segmented_regions, std::vector> &&top_and_bottom_layers, const size_t num_facets_states, const std::function &throw_on_cancel_callback) @@ -1948,91 +1844,33 @@ static std::vector> merge_segmented_layers(const std::ve assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size()); BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin"; - // Every region of a layer is merged together with the regions of the islands it overlaps, and the islands are further - // apart than the dimple removal below reaches, so this gives the same result as merging the layer at once. On a layer - // cut through a fine relief every region shares thousands of hole contours with every other, and ClipperLib, splitting - // and re-linking one huge polygon over and over, took anything up to half an hour for a layer; per island each operation - // stays the size of the island, and the islands run in parallel. - tbb::parallel_for(tbb::blocked_range(0, num_layers), [&](const tbb::blocked_range &range) { + tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { assert(segmented_regions[layer_idx].size() == num_facets_states); - throw_on_cancel_callback(); - // Group the islands joined by a region overlapping several of them; the last group takes the regions lying - // outside every island. - const ExPolygons &islands = input_expolygons[layer_idx]; - const IslandLocator locator(islands); - std::vector parent(islands.size() + 1); - std::iota(parent.begin(), parent.end(), 0); - const auto root = [&parent](size_t i) { - while (parent[i] != i) - i = parent[i] = parent[parent[i]]; - return i; - }; - // Islands of every piece: side regions of colours 1.., then top/bottom regions of colours 0.. - std::vector pieces; - for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) - for (const ExPolygon &piece : segmented_regions[layer_idx][extruder_id]) - pieces.emplace_back(&piece); - if (!top_and_bottom_layers.empty()) - for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) - for (const ExPolygon &piece : top_and_bottom_layers[color_idx][layer_idx]) - pieces.emplace_back(&piece); - std::vector> overlapped(pieces.size()); - tbb::parallel_for(size_t(0), pieces.size(), [&](size_t i) { locator.find(*pieces[i], overlapped[i]); }); - std::vector piece_island(pieces.size()); - for (size_t i = 0; i < pieces.size(); ++i) { - piece_island[i] = overlapped[i].empty() ? islands.size() : overlapped[i].front(); - for (size_t island : overlapped[i]) - parent[root(island)] = root(piece_island[i]); - } - std::vector bucket_of(parent.size(), size_t(-1)); - size_t num_buckets = 0; - for (size_t i = 0; i < parent.size(); ++i) - if (size_t &b = bucket_of[root(i)]; b == size_t(-1)) - b = num_buckets++; - - // [bucket][colour] - std::vector> sides(num_buckets, std::vector(num_facets_states)); - std::vector> tops(num_buckets, std::vector(num_facets_states)); - size_t piece_idx = 0; - for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) - for (const ExPolygon &piece : segmented_regions[layer_idx][extruder_id]) - sides[bucket_of[root(piece_island[piece_idx++])]][extruder_id].emplace_back(piece); - if (!top_and_bottom_layers.empty()) - for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) - for (const ExPolygon &piece : top_and_bottom_layers[color_idx][layer_idx]) - tops[bucket_of[root(piece_island[piece_idx++])]][color_idx].emplace_back(piece); - - // Side regions minus the top/bottom regions of every colour. - std::vector> merged(num_buckets, std::vector(num_facets_states)); - tbb::parallel_for(size_t(0), num_buckets, [&](size_t bucket) { - Polygons tops_all; - for (const ExPolygons &t : tops[bucket]) - polygons_append(tops_all, t); - for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) - if (!sides[bucket][extruder_id].empty()) - merged[bucket][extruder_id] = tops_all.empty() ? std::move(sides[bucket][extruder_id]) : - diff_ex_by_piece(sides[bucket][extruder_id], tops_all); - }); - - // Then this colour's top/bottom regions, with the dimples removed (#7235) when the layer has side regions left. + // Zero is skipped because it is the default color of the volume for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) { - if (top_and_bottom_layers.empty() || top_and_bottom_layers[extruder_id][layer_idx].empty()) { - for (size_t bucket = 0; bucket < num_buckets; ++bucket) - append(segmented_regions_merged[layer_idx][extruder_id - 1], std::move(merged[bucket][extruder_id])); - continue; + throw_on_cancel_callback(); + if (!segmented_regions[layer_idx][extruder_id].empty()) { + ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id]; + if (!top_and_bottom_layers.empty()) { + for (const std::vector &top_and_bottom_by_extruder : top_and_bottom_layers) { + if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) { + segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]); + } + } + } + + segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed); + } + + if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) { + bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty(); + append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]); + + // Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers. + if (!was_top_and_bottom_empty) + segmented_regions_merged[layer_idx][extruder_id - 1] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id - 1]), float(SCALED_EPSILON), -float(SCALED_EPSILON)); } - bool was_top_and_bottom_empty = true; - for (size_t bucket = 0; bucket < num_buckets && was_top_and_bottom_empty; ++bucket) - was_top_and_bottom_empty = merged[bucket][extruder_id].empty(); - tbb::parallel_for(size_t(0), num_buckets, [&](size_t bucket) { - ExPolygons ®ion = merged[bucket][extruder_id]; - append(region, tops[bucket][extruder_id]); - if (!was_top_and_bottom_empty && !region.empty()) - region = offset2_ex(union_ex(region), float(SCALED_EPSILON), -float(SCALED_EPSILON)); - }); - for (size_t bucket = 0; bucket < num_buckets; ++bucket) - append(segmented_regions_merged[layer_idx][extruder_id - 1], std::move(merged[bucket][extruder_id])); } } }); // end of parallel_for @@ -2319,56 +2157,16 @@ std::vector> segmentation_by_painting(const PrintObject assert(!color_poly.empty()); assert(!color_poly.front().empty()); - // Each island (an ExPolygon with its holes) is segmented on its own. Any point of an island is closer to - // that island's contours than to any other island's - the way out crosses its own boundary first - so its - // Voronoi cells, and with them its colour regions, depend on nothing else. A layer cut through a fine relief - // has thousands of islands, and one Voronoi diagram over all of them degenerated into overlapping regions - // that every boolean afterwards had to untangle. Per island the diagrams stay small and the islands run in - // parallel; an island in a single colour needs no diagram at all. - const ExPolygons &islands = input_expolygons[layer_idx]; - std::vector> island_contours(islands.size()); // [first, last) into color_poly - { - // The same order EdgeGrid::Grid::create() lists the contours in, and so colorize_contours(). - size_t idx = 0; - for (size_t island_idx = 0; island_idx < islands.size(); ++island_idx) { - const size_t first = idx; - if (!islands[island_idx].contour.empty()) - ++idx; - for (const Polygon &hole : islands[island_idx].holes) - if (!hole.empty()) - ++idx; - island_contours[island_idx] = {first, idx}; - } - assert(idx == color_poly.size()); + if (has_layer_only_one_color(color_poly)) { + // If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer. + segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx]; + } else { + MMU_Graph graph = build_graph(layer_idx, color_poly); + remove_multiple_edges_in_vertices(graph, color_poly); + graph.remove_nodes_with_one_arc(); + segmented_regions[layer_idx] = extract_colored_segments(graph, num_facets_states); + //segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx); } - std::vector> island_regions(islands.size()); - tbb::parallel_for(size_t(0), islands.size(), [&](size_t island_idx) { - const auto [first, last] = island_contours[island_idx]; - if (first == last) - return; - const std::vector island_poly(color_poly.begin() + first, color_poly.begin() + last); - std::vector ®ions = island_regions[island_idx]; - if (has_layer_only_one_color(island_poly)) { - regions.assign(num_facets_states, ExPolygons()); - regions[size_t(island_poly.front().front().color)].emplace_back(islands[island_idx]); - } else { - MMU_Graph graph = build_graph(layer_idx, island_poly); - remove_multiple_edges_in_vertices(graph, island_poly); - graph.remove_nodes_with_one_arc(); - regions = extract_colored_segments(graph, num_facets_states); - // The faces of one colour tile it without overlapping; merged here, where an island is small, - // every later boolean gets a few regions instead of thousands of faces sharing their edges. An - // island with many holes keeps its faces: merged, each colour would be one region with thousands - // of holes, and subtracting from that is far slower than from the faces one at a time. - if (island_poly.size() <= 64) - for (ExPolygons &faces : regions) - if (faces.size() > 1) - faces = union_ex(faces); - } - }); - for (std::vector ®ions : island_regions) - for (size_t color_idx = 0; color_idx < regions.size(); ++color_idx) - append(segmented_regions[layer_idx][color_idx], std::move(regions[color_idx])); #ifdef MM_SEGMENTATION_DEBUG_REGIONS export_regions_to_svg(debug_out_path("3-mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]); @@ -2391,7 +2189,7 @@ std::vector> segmentation_by_painting(const PrintObject throw_on_cancel_callback(); } - std::vector> segmented_regions_merged = merge_segmented_layers(input_expolygons, segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback); + std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback); throw_on_cancel_callback(); #ifdef MM_SEGMENTATION_DEBUG_REGIONS diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index b4f8af0871..096230d22c 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -19,13 +19,11 @@ public: MultiPoint() {} MultiPoint(const MultiPoint &other) : points(other.points) {} - MultiPoint(MultiPoint &&other) noexcept : points(std::move(other.points)) {} + MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} MultiPoint(std::initializer_list list) : points(list) {} explicit MultiPoint(const Points &_points) : points(_points) {} - // Without it, the derived classes' move constructors passing std::move(points) here copied them. - explicit MultiPoint(Points &&_points) noexcept : points(std::move(_points)) {} MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; } - MultiPoint& operator=(MultiPoint &&other) noexcept { points = std::move(other.points); return *this; } + MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } virtual ~MultiPoint() = default; void scale(double factor); void scale(double factor_x, double factor_y); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 4fec834171..1f1f1289e5 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -17,8 +17,6 @@ #include #include #include -#include -#include #include "libslic3r/AABBTreeLines.hpp" #include "Print.hpp" static const int overhang_sampling_number = 6; @@ -2483,443 +2481,421 @@ void PerimeterGenerator::process_arachne() const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config); // we need to process each island separately because we might have different // extra perimeters for each one - // Each island is independent up to its outputs, so they are generated in parallel - a layer split into thousands - // of islands (e.g. by colour painting) otherwise ran on one thread - and the outputs are then committed in the - // original island order, which is what the extra overhang perimeters (applied to the last island's loops and to - // all fill surfaces so far) depend on. - struct ArachneSurfaceResult - { - ExtrusionEntityCollection loops; - bool has_loops = false; - ExPolygons infill; - ExPolygons no_overlap; - }; - std::vector results(all_surfaces.size()); - tbb::parallel_for(tbb::blocked_range(0, all_surfaces.size()), [&](const tbb::blocked_range &range) { - for (size_t surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) { - const Surface &surface = all_surfaces[surface_idx]; - ArachneSurfaceResult &result = results[surface_idx]; - coord_t bead_width_0 = ext_perimeter_spacing; - // detect how many perimeters must be generated for this island - int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops - int sparse_infill_density = this->config->sparse_infill_density.value; - if (this->config->alternate_extra_wall && this->layer_id % 2 == 1 && !m_spiral_vase && sparse_infill_density > 0) // add alternating extra wall - loop_number++; + for (const Surface& surface : all_surfaces) { + coord_t bead_width_0 = ext_perimeter_spacing; + // detect how many perimeters must be generated for this island + int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops + int sparse_infill_density = this->config->sparse_infill_density.value; + if (this->config->alternate_extra_wall && this->layer_id % 2 == 1 && !m_spiral_vase && sparse_infill_density > 0) // add alternating extra wall + loop_number++; - // Set the bottommost layer to be one wall - const bool is_bottom_layer = (this->layer_id == object_config->raft_layers) ? true : false; - if (is_bottom_layer && only_one_wall_first_layer) - loop_number = 0; + // Set the bottommost layer to be one wall + const bool is_bottom_layer = (this->layer_id == object_config->raft_layers) ? true : false; + if (is_bottom_layer && only_one_wall_first_layer) + loop_number = 0; - // Orca: set the topmost layer to be one wall according to the config - const bool is_topmost_layer = (this->upper_slices == nullptr) ? true : false; - if (is_topmost_layer && loop_number > 0 && only_one_wall_top) - loop_number = 0; + // Orca: set the topmost layer to be one wall according to the config + const bool is_topmost_layer = (this->upper_slices == nullptr) ? true : false; + if (is_topmost_layer && loop_number > 0 && only_one_wall_top) + loop_number = 0; - auto apply_precise_outer_wall = config->precise_outer_wall && config->wall_sequence == WallSequence::InnerOuter; - // Orca: properly adjust offset for the outer wall if precise_outer_wall is enabled. - ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution), - apply_precise_outer_wall? -float(ext_perimeter_width - ext_perimeter_spacing ) - : -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); + auto apply_precise_outer_wall = config->precise_outer_wall && config->wall_sequence == WallSequence::InnerOuter; + // Orca: properly adjust offset for the outer wall if precise_outer_wall is enabled. + ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution), + apply_precise_outer_wall? -float(ext_perimeter_width - ext_perimeter_spacing ) + : -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.)); - Arachne::WallToolPathsParams input_params = Arachne::make_paths_params(this->layer_id, *object_config, *print_config); - // Set params is_top_or_bottom_layer for adjusting short-wall removal sensitivity. - input_params.is_top_or_bottom_layer = (is_bottom_layer || is_topmost_layer) ? true : false; + Arachne::WallToolPathsParams input_params = Arachne::make_paths_params(this->layer_id, *object_config, *print_config); + // Set params is_top_or_bottom_layer for adjusting short-wall removal sensitivity. + input_params.is_top_or_bottom_layer = (is_bottom_layer || is_topmost_layer) ? true : false; - coord_t wall_0_inset = 0; - if (apply_precise_outer_wall) - wall_0_inset = -coord_t(ext_perimeter_width / 2 - ext_perimeter_spacing / 2); + coord_t wall_0_inset = 0; + if (apply_precise_outer_wall) + wall_0_inset = -coord_t(ext_perimeter_width / 2 - ext_perimeter_spacing / 2); - //PS: One wall top surface for Arachne - ExPolygons top_expolygons; - // Calculate how many inner loops remain when TopSurfaces is selected. - const int inner_loop_number = (only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1; + //PS: One wall top surface for Arachne + ExPolygons top_expolygons; + // Calculate how many inner loops remain when TopSurfaces is selected. + const int inner_loop_number = (only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1; - // Set one perimeter when TopSurfaces is selected. - if (only_one_wall_top && loop_number > 0) - loop_number = 0; + // Set one perimeter when TopSurfaces is selected. + if (only_one_wall_top && loop_number > 0) + loop_number = 0; - Arachne::WallToolPathsParams input_params_tmp = input_params; + Arachne::WallToolPathsParams input_params_tmp = input_params; - Polygons last_p = to_polygons(last); - Arachne::WallToolPaths wallToolPaths(last_p, bead_width_0, perimeter_spacing, coord_t(loop_number + 1), - wall_0_inset, layer_height, input_params_tmp); - std::vector perimeters = wallToolPaths.getToolPaths(); - ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); + Polygons last_p = to_polygons(last); + Arachne::WallToolPaths wallToolPaths(last_p, bead_width_0, perimeter_spacing, coord_t(loop_number + 1), + wall_0_inset, layer_height, input_params_tmp); + std::vector perimeters = wallToolPaths.getToolPaths(); + ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour()); - // Check if there are some remaining perimeters to generate (the number of perimeters - // is greater than one together with enabled the single perimeter on top surface feature). - if (inner_loop_number >= 0) { - assert(upper_slices != nullptr); + // Check if there are some remaining perimeters to generate (the number of perimeters + // is greater than one together with enabled the single perimeter on top surface feature). + if (inner_loop_number >= 0) { + assert(upper_slices != nullptr); - // Infill contour bounding box. - BoundingBox infill_contour_bbox = get_extents(infill_contour); - infill_contour_bbox.offset(SCALED_EPSILON); + // Infill contour bounding box. + BoundingBox infill_contour_bbox = get_extents(infill_contour); + infill_contour_bbox.offset(SCALED_EPSILON); - coord_t perimeter_width = this->perimeter_flow.scaled_width(); + coord_t perimeter_width = this->perimeter_flow.scaled_width(); - // Get top ExPolygons from current infill contour. - Polygons upper_slices_clipped; - if (object_config->interface_shells) { - auto upper_slicer_same_region = to_expolygons(this->upper_slices_same_region->surfaces); - upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_slicer_same_region, infill_contour_bbox); - } else - upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, infill_contour_bbox); + // Get top ExPolygons from current infill contour. + Polygons upper_slices_clipped; + if (object_config->interface_shells) { + auto upper_slicer_same_region = to_expolygons(this->upper_slices_same_region->surfaces); + upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_slicer_same_region, infill_contour_bbox); + } else + upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, infill_contour_bbox); - top_expolygons = diff_ex(infill_contour, upper_slices_clipped); + top_expolygons = diff_ex(infill_contour, upper_slices_clipped); - if (!top_expolygons.empty()) { - if (lower_slices != nullptr) { - const float bridge_offset = float(std::max(ext_perimeter_spacing, perimeter_width)); - const Polygons lower_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, infill_contour_bbox); - const ExPolygons current_slices_bridges = offset_ex(diff_ex(top_expolygons, lower_slices_clipped), bridge_offset); + if (!top_expolygons.empty()) { + if (lower_slices != nullptr) { + const float bridge_offset = float(std::max(ext_perimeter_spacing, perimeter_width)); + const Polygons lower_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, infill_contour_bbox); + const ExPolygons current_slices_bridges = offset_ex(diff_ex(top_expolygons, lower_slices_clipped), bridge_offset); - // Remove bridges from top surface polygons. - top_expolygons = diff_ex(top_expolygons, current_slices_bridges); - } - - // Filter out areas that are too thin and expand top surface polygons a bit to hide the wall line. - // ORCA: skip if the top surface area is smaller than "min_width_top_surface" - const float top_surface_min_width = std::max(float(ext_perimeter_spacing) / 4.f + scaled(0.00001), float(scale_(config->min_width_top_surface.get_abs_value(unscale_(perimeter_width)))) / 4.f); - // Shrink the polygon to remove the small areas, then expand it back out plus a maragin to hide the wall line a little. - // ORCA: Expand the polygon with half the perimeter width in addition to the contracted amount, - // not the full perimeter width as PS does, to enable thin lettering to print on the top surface without nozzle collisions - // due to thin lines being generated - top_expolygons = offset2_ex(top_expolygons, -top_surface_min_width, top_surface_min_width + float(perimeter_width * 0.85)); - - // Get final top ExPolygons (bridges were excluded above, so they stay walled). - top_expolygons = intersection_ex(top_expolygons, infill_contour); - - // ORCA: onion the real region (inside the outer wall) so the remaining walls follow the actual - // geometry, then cut away the parts over the top surface. Re-onioning the non-top complement - // instead - the fallback when there is no top fill - walls the top/non-top interface and rings - // top-surface islands with inner walls that don't exist when the feature is disabled. - const bool clip_walls_over_top = top_fill_replaces_inner_walls(*this->config); - const Polygons inner_region = to_polygons(offset_ex(clip_walls_over_top ? infill_contour - : diff_ex(infill_contour, top_expolygons), - wall_0_inset)); - Arachne::WallToolPaths inner_wall_tool_paths(inner_region, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp); - std::vector inner_perimeters = inner_wall_tool_paths.getToolPaths(); - - if (clip_walls_over_top) { - Polygons kept_over_top; - clip_inner_walls_over_top(inner_perimeters, top_expolygons, perimeter_width, kept_over_top); - // Route the top fill around the walls kept despite grazing the top. - if (! kept_over_top.empty()) - top_expolygons = diff_ex(top_expolygons, kept_over_top); - } - - // Recalculate indexes of inner perimeters before merging them: they come after the single outer wall. - if (!perimeters.empty()) - for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters) - for (Arachne::ExtrusionLine &el : inner_perimeter) - ++el.inset_idx; - - perimeters.insert(perimeters.end(), inner_perimeters.begin(), inner_perimeters.end()); - infill_contour = union_ex(top_expolygons, inner_wall_tool_paths.getInnerContour()); - } else { - // There is no top surface ExPolygon, so we call Arachne again with parameters - // like when the single perimeter feature is disabled. - Arachne::WallToolPaths no_single_perimeter_tool_paths(last_p, bead_width_0, perimeter_spacing, coord_t(inner_loop_number + 2), wall_0_inset, layer_height, input_params_tmp); - perimeters = no_single_perimeter_tool_paths.getToolPaths(); - infill_contour = union_ex(no_single_perimeter_tool_paths.getInnerContour()); + // Remove bridges from top surface polygons. + top_expolygons = diff_ex(top_expolygons, current_slices_bridges); } + + // Filter out areas that are too thin and expand top surface polygons a bit to hide the wall line. + // ORCA: skip if the top surface area is smaller than "min_width_top_surface" + const float top_surface_min_width = std::max(float(ext_perimeter_spacing) / 4.f + scaled(0.00001), float(scale_(config->min_width_top_surface.get_abs_value(unscale_(perimeter_width)))) / 4.f); + // Shrink the polygon to remove the small areas, then expand it back out plus a maragin to hide the wall line a little. + // ORCA: Expand the polygon with half the perimeter width in addition to the contracted amount, + // not the full perimeter width as PS does, to enable thin lettering to print on the top surface without nozzle collisions + // due to thin lines being generated + top_expolygons = offset2_ex(top_expolygons, -top_surface_min_width, top_surface_min_width + float(perimeter_width * 0.85)); + + // Get final top ExPolygons (bridges were excluded above, so they stay walled). + top_expolygons = intersection_ex(top_expolygons, infill_contour); + + // ORCA: onion the real region (inside the outer wall) so the remaining walls follow the actual + // geometry, then cut away the parts over the top surface. Re-onioning the non-top complement + // instead - the fallback when there is no top fill - walls the top/non-top interface and rings + // top-surface islands with inner walls that don't exist when the feature is disabled. + const bool clip_walls_over_top = top_fill_replaces_inner_walls(*this->config); + const Polygons inner_region = to_polygons(offset_ex(clip_walls_over_top ? infill_contour + : diff_ex(infill_contour, top_expolygons), + wall_0_inset)); + Arachne::WallToolPaths inner_wall_tool_paths(inner_region, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp); + std::vector inner_perimeters = inner_wall_tool_paths.getToolPaths(); + + if (clip_walls_over_top) { + Polygons kept_over_top; + clip_inner_walls_over_top(inner_perimeters, top_expolygons, perimeter_width, kept_over_top); + // Route the top fill around the walls kept despite grazing the top. + if (! kept_over_top.empty()) + top_expolygons = diff_ex(top_expolygons, kept_over_top); + } + + // Recalculate indexes of inner perimeters before merging them: they come after the single outer wall. + if (!perimeters.empty()) + for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters) + for (Arachne::ExtrusionLine &el : inner_perimeter) + ++el.inset_idx; + + perimeters.insert(perimeters.end(), inner_perimeters.begin(), inner_perimeters.end()); + infill_contour = union_ex(top_expolygons, inner_wall_tool_paths.getInnerContour()); + } else { + // There is no top surface ExPolygon, so we call Arachne again with parameters + // like when the single perimeter feature is disabled. + Arachne::WallToolPaths no_single_perimeter_tool_paths(last_p, bead_width_0, perimeter_spacing, coord_t(inner_loop_number + 2), wall_0_inset, layer_height, input_params_tmp); + perimeters = no_single_perimeter_tool_paths.getToolPaths(); + infill_contour = union_ex(no_single_perimeter_tool_paths.getInnerContour()); } - //PS + } + //PS - loop_number = int(perimeters.size()) - 1; + loop_number = int(perimeters.size()) - 1; - #ifdef ARACHNE_DEBUG - { - static int iRun = 0; - export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); - } - #endif + #ifdef ARACHNE_DEBUG + { + static int iRun = 0; + export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour())); + } +#endif - // All closed ExtrusionLine should have the same the first and the last point. - // But in rare cases, Arachne produce ExtrusionLine marked as closed but without - // equal the first and the last point. - assert([&perimeters = std::as_const(perimeters)]() -> bool { - for (const Arachne::VariableWidthLines& perimeter : perimeters) - for (const Arachne::ExtrusionLine& el : perimeter) - if (el.is_closed && el.junctions.front().p != el.junctions.back().p) - return false; - return true; - }()); + // All closed ExtrusionLine should have the same the first and the last point. + // But in rare cases, Arachne produce ExtrusionLine marked as closed but without + // equal the first and the last point. + assert([&perimeters = std::as_const(perimeters)]() -> bool { + for (const Arachne::VariableWidthLines& perimeter : perimeters) + for (const Arachne::ExtrusionLine& el : perimeter) + if (el.is_closed && el.junctions.front().p != el.junctions.back().p) + return false; + return true; + }()); - int start_perimeter = int(perimeters.size()) - 1; - int end_perimeter = -1; - int direction = -1; + int start_perimeter = int(perimeters.size()) - 1; + int end_perimeter = -1; + int direction = -1; - bool is_outer_wall_first = - this->config->wall_sequence == WallSequence::OuterInner || - this->config->wall_sequence == WallSequence::InnerOuterInner; + bool is_outer_wall_first = + this->config->wall_sequence == WallSequence::OuterInner || + this->config->wall_sequence == WallSequence::InnerOuterInner; - if (layer_id == 0){ // disable inner outer inner algorithm after the first layer - is_outer_wall_first = - this->config->wall_sequence == WallSequence::OuterInner; - } - if (is_outer_wall_first) { - start_perimeter = 0; - end_perimeter = int(perimeters.size()); - direction = 1; + if (layer_id == 0){ // disable inner outer inner algorithm after the first layer + is_outer_wall_first = + this->config->wall_sequence == WallSequence::OuterInner; + } + if (is_outer_wall_first) { + start_perimeter = 0; + end_perimeter = int(perimeters.size()); + direction = 1; + } + + std::vector all_extrusions; + for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { + if (perimeters[perimeter_idx].empty()) + continue; + for (Arachne::ExtrusionLine& wall : perimeters[perimeter_idx]) + all_extrusions.emplace_back(&wall); + } + + // Find topological order with constraints from extrusions_constrains. + std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. + std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. + std::unordered_map map_extrusion_to_idx; + for (size_t idx = 0; idx < all_extrusions.size(); idx++) + map_extrusion_to_idx.emplace(all_extrusions[idx], idx); + + auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, is_outer_wall_first); + for (auto [before, after] : extrusions_constrains) { + auto after_it = map_extrusion_to_idx.find(after); + ++blocked[after_it->second]; + blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); + } + + std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. + Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. + std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. + ordered_extrusions.reserve(all_extrusions.size()); + + while (ordered_extrusions.size() < all_extrusions.size()) { + size_t best_candidate = 0; + double best_distance_sqr = std::numeric_limits::max(); + bool is_best_closed = false; + + std::vector available_candidates; + for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { + if (processed[candidate] || blocked[candidate]) + continue; // Not a valid candidate. + available_candidates.push_back(candidate); } - std::vector all_extrusions; - for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) { - if (perimeters[perimeter_idx].empty()) + std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { + return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; + }); + + for (const size_t candidate_path_idx : available_candidates) { + auto& path = all_extrusions[candidate_path_idx]; + + if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. + if (best_distance_sqr == std::numeric_limits::max()) { + best_candidate = candidate_path_idx; + is_best_closed = path->is_closed; + } continue; - for (Arachne::ExtrusionLine& wall : perimeters[perimeter_idx]) - all_extrusions.emplace_back(&wall); - } - - // Find topological order with constraints from extrusions_constrains. - std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. - std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. - std::unordered_map map_extrusion_to_idx; - for (size_t idx = 0; idx < all_extrusions.size(); idx++) - map_extrusion_to_idx.emplace(all_extrusions[idx], idx); - - auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, is_outer_wall_first); - for (auto [before, after] : extrusions_constrains) { - auto after_it = map_extrusion_to_idx.find(after); - ++blocked[after_it->second]; - blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second); - } - - std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed. - Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position. - std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap. - ordered_extrusions.reserve(all_extrusions.size()); - - while (ordered_extrusions.size() < all_extrusions.size()) { - size_t best_candidate = 0; - double best_distance_sqr = std::numeric_limits::max(); - bool is_best_closed = false; - - std::vector available_candidates; - for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) { - if (processed[candidate] || blocked[candidate]) - continue; // Not a valid candidate. - available_candidates.push_back(candidate); } - std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool { - return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed; - }); - - for (const size_t candidate_path_idx : available_candidates) { - auto& path = all_extrusions[candidate_path_idx]; - - if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. - if (best_distance_sqr == std::numeric_limits::max()) { - best_candidate = candidate_path_idx; - is_best_closed = path->is_closed; - } - continue; - } - - const Point candidate_position = path->junctions.front().p; - double distance_sqr = (current_position - candidate_position).cast().norm(); - if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. - if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { - best_candidate = candidate_path_idx; - best_distance_sqr = distance_sqr; - is_best_closed = path->is_closed; - } + const Point candidate_position = path->junctions.front().p; + double distance_sqr = (current_position - candidate_position).cast().norm(); + if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far. + if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) { + best_candidate = candidate_path_idx; + best_distance_sqr = distance_sqr; + is_best_closed = path->is_closed; } } - - auto& best_path = all_extrusions[best_candidate]; - ordered_extrusions.push_back({ best_path, best_path->is_contour() }); - processed[best_candidate] = true; - for (size_t unlocked_idx : blocking[best_candidate]) - blocked[unlocked_idx]--; - - if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. - if (best_path->is_closed) - current_position = best_path->junctions[0].p; //We end where we started. - else - current_position = best_path->junctions.back().p; //Pick the other end from where we started. - } } - // printf("New Layer: Layer ID %d\n",layer_id); //debug - new layer - if (this->config->wall_sequence == WallSequence::InnerOuterInner && layer_id > 0) { // only enable inner outer inner algorithm after first layer - if (ordered_extrusions.size() > 2) { // 3 walls minimum needed to do inner outer inner ordering - int position = 0; // index to run the re-ordering for multiple external perimeters in a single island. - int arr_i, arr_j = 0; // indexes to run through the walls in the for loops - int outer, first_internal, second_internal, max_internal, current_perimeter; // allocate index values - - // To address any remaining scenarios where the outer perimeter contour is not first on the list as arachne sometimes reorders the perimeters when clustering - // for OI mode that is used the basis for IOI - bringContoursToFront(ordered_extrusions); - std::vector reordered_extrusions; - - // Debug statement to print spacing values: - //printf("External threshold - Ext perimeter: %d Ext spacing: %d Int perimeter: %d Int spacing: %d\n", this->ext_perimeter_flow.scaled_width(),this->ext_perimeter_flow.scaled_spacing(),this->perimeter_flow.scaled_width(), this->perimeter_flow.scaled_spacing()); + auto& best_path = all_extrusions[best_candidate]; + ordered_extrusions.push_back({ best_path, best_path->is_contour() }); + processed[best_candidate] = true; + for (size_t unlocked_idx : blocking[best_candidate]) + blocked[unlocked_idx]--; - // Get searching thresholds. For an external perimeter we take the external perimeter spacing/2 plus the internal perimeter spacing/2 and expand by the factor - // rounding errors. When precise wall is enabled, the external perimeter full spacing is used. - coord_t threshold_external = (apply_precise_outer_wall) - // Precise outer wall ⇒ use “full external spacing” - ? ( this->ext_perimeter_flow.scaled_spacing() - + this->perimeter_flow.scaled_spacing()/2.0 ) - // Normal ⇒ half ext spacing + half int spacing - : ( this->ext_perimeter_flow.scaled_spacing()/2.0 - + this->perimeter_flow.scaled_spacing()/2.0 ); + if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then. + if (best_path->is_closed) + current_position = best_path->junctions[0].p; //We end where we started. + else + current_position = best_path->junctions.back().p; //Pick the other end from where we started. + } + } + + // printf("New Layer: Layer ID %d\n",layer_id); //debug - new layer + if (this->config->wall_sequence == WallSequence::InnerOuterInner && layer_id > 0) { // only enable inner outer inner algorithm after first layer + if (ordered_extrusions.size() > 2) { // 3 walls minimum needed to do inner outer inner ordering + int position = 0; // index to run the re-ordering for multiple external perimeters in a single island. + int arr_i, arr_j = 0; // indexes to run through the walls in the for loops + int outer, first_internal, second_internal, max_internal, current_perimeter; // allocate index values - // For the intenal perimeter threshold, the distance is the internal perimeter spacing expanded by the factor to cover rounding errors. - coord_t threshold_internal = this->perimeter_flow.scaled_spacing(); + // To address any remaining scenarios where the outer perimeter contour is not first on the list as arachne sometimes reorders the perimeters when clustering + // for OI mode that is used the basis for IOI + bringContoursToFront(ordered_extrusions); + std::vector reordered_extrusions; - // Re-order extrusions based on distance - // Alorithm will aggresively optimise for the appearance of the outermost perimeter - ordered_extrusions = reorderPerimetersByProximity(ordered_extrusions,threshold_external,threshold_internal ); - reordered_extrusions = ordered_extrusions; // copy them into the reordered extrusions vector to allow for IOI operations to be performed below without altering the base ordered extrusions list. + // Debug statement to print spacing values: + //printf("External threshold - Ext perimeter: %d Ext spacing: %d Int perimeter: %d Int spacing: %d\n", this->ext_perimeter_flow.scaled_width(),this->ext_perimeter_flow.scaled_spacing(),this->perimeter_flow.scaled_width(), this->perimeter_flow.scaled_spacing()); + + // Get searching thresholds. For an external perimeter we take the external perimeter spacing/2 plus the internal perimeter spacing/2 and expand by the factor + // rounding errors. When precise wall is enabled, the external perimeter full spacing is used. + coord_t threshold_external = (apply_precise_outer_wall) + // Precise outer wall ⇒ use “full external spacing” + ? ( this->ext_perimeter_flow.scaled_spacing() + + this->perimeter_flow.scaled_spacing()/2.0 ) + // Normal ⇒ half ext spacing + half int spacing + : ( this->ext_perimeter_flow.scaled_spacing()/2.0 + + this->perimeter_flow.scaled_spacing()/2.0 ); - // Now start the sandwich mode wall re-ordering using the reordered_extrusions as the basis - // scan to find the external perimeter, first internal, second internal and last perimeter in the island. - // We then advance the position index to move to the second island and continue until there are no more - // perimeters left. - while (position < reordered_extrusions.size()) { - outer = first_internal = second_internal = current_perimeter = -1; // initialise all index values to -1 - max_internal = reordered_extrusions.size()-1; // initialise the maximum internal perimeter to the last perimeter on the extrusion list - // run through the walls to get the index values that need re-ordering until the first one for each - // is found. Start at "position" index to enable the for loop to iterate for multiple external - // perimeters in a single island - // printf("Reorder Loop. Position %d, extrusion list size: %d, Outer index %d, inner index %d, second inner index %d\n", position, reordered_extrusions.size(),outer,first_internal,second_internal); - for (arr_i = position; arr_i < reordered_extrusions.size(); ++arr_i) { - // printf("Perimeter: extrusion inset index %d, ordered extrusions array position %d\n",reordered_extrusions[arr_i].extrusion->inset_idx, arr_i); - switch (reordered_extrusions[arr_i].extrusion->inset_idx) { - case 0: // external perimeter - if (outer == -1) - outer = arr_i; - break; - case 1: // first internal wall - if (first_internal==-1 && arr_i>outer && outer!=-1){ - first_internal = arr_i; - } - break; - case 2: // second internal wall - if (second_internal == -1 && arr_i > first_internal && outer!=-1){ - second_internal = arr_i; - } - break; - } - if(outer >-1 && first_internal>-1 && reordered_extrusions[arr_i].extrusion->inset_idx == 0){ // found a new external perimeter after we've found at least a first internal perimeter to re-order. - // This means we entered a new island. - arr_i=arr_i-1; //step back one perimeter - max_internal = arr_i; // new maximum internal perimeter is now this as we have found a new external perimeter, hence a new island. - break; // exit the for loop - } - } - - // printf("Layer ID %d, Outer index %d, inner index %d, second inner index %d, maximum internal perimeter %d \n",layer_id,outer,first_internal,second_internal, max_internal); - if (outer > -1 && first_internal > -1 && second_internal > -1) { // found all three perimeters to re-order? If not the perimeters will be processed outside in. - std::vector inner_outer_extrusions; // temporary array to hold extrusions for reordering - inner_outer_extrusions.resize(max_internal - position + 1); // reserve array containing the number of perimeters before a new island. Variables are array indexes hence need to add +1 to convert to position allocations - // printf("Allocated array size %d, max_internal index %d, start position index %d \n",max_internal-position+1,max_internal,position); - - for (arr_j = max_internal; arr_j >=position; --arr_j){ // go inside out towards the external perimeter (perimeters in reverse order) and store all internal perimeters until the first one identified with inset index 2 - if(arr_j >= second_internal){ - //printf("Inside out loop: Mapped perimeter index %d to array position %d\n", arr_j, max_internal-arr_j); - inner_outer_extrusions[max_internal-arr_j] = reordered_extrusions[arr_j]; - current_perimeter++; + // For the intenal perimeter threshold, the distance is the internal perimeter spacing expanded by the factor to cover rounding errors. + coord_t threshold_internal = this->perimeter_flow.scaled_spacing(); + + // Re-order extrusions based on distance + // Alorithm will aggresively optimise for the appearance of the outermost perimeter + ordered_extrusions = reorderPerimetersByProximity(ordered_extrusions,threshold_external,threshold_internal ); + reordered_extrusions = ordered_extrusions; // copy them into the reordered extrusions vector to allow for IOI operations to be performed below without altering the base ordered extrusions list. + + // Now start the sandwich mode wall re-ordering using the reordered_extrusions as the basis + // scan to find the external perimeter, first internal, second internal and last perimeter in the island. + // We then advance the position index to move to the second island and continue until there are no more + // perimeters left. + while (position < reordered_extrusions.size()) { + outer = first_internal = second_internal = current_perimeter = -1; // initialise all index values to -1 + max_internal = reordered_extrusions.size()-1; // initialise the maximum internal perimeter to the last perimeter on the extrusion list + // run through the walls to get the index values that need re-ordering until the first one for each + // is found. Start at "position" index to enable the for loop to iterate for multiple external + // perimeters in a single island + // printf("Reorder Loop. Position %d, extrusion list size: %d, Outer index %d, inner index %d, second inner index %d\n", position, reordered_extrusions.size(),outer,first_internal,second_internal); + for (arr_i = position; arr_i < reordered_extrusions.size(); ++arr_i) { + // printf("Perimeter: extrusion inset index %d, ordered extrusions array position %d\n",reordered_extrusions[arr_i].extrusion->inset_idx, arr_i); + switch (reordered_extrusions[arr_i].extrusion->inset_idx) { + case 0: // external perimeter + if (outer == -1) + outer = arr_i; + break; + case 1: // first internal wall + if (first_internal==-1 && arr_i>outer && outer!=-1){ + first_internal = arr_i; } - } - - for (arr_j = position; arr_j < second_internal; ++arr_j){ // go outside in and map the remaining perimeters (external and first internal wall(s)) using the outside in wall order - // printf("Outside in loop: Mapped perimeter index %d to array position %d\n", arr_j, current_perimeter+1); - inner_outer_extrusions[++current_perimeter] = reordered_extrusions[arr_j]; - } - - for(arr_j = position; arr_j <= max_internal; ++arr_j) // replace perimeter array with the new re-ordered array - ordered_extrusions[arr_j] = inner_outer_extrusions[arr_j-position]; + break; + case 2: // second internal wall + if (second_internal == -1 && arr_i > first_internal && outer!=-1){ + second_internal = arr_i; + } + break; + } + if(outer >-1 && first_internal>-1 && reordered_extrusions[arr_i].extrusion->inset_idx == 0){ // found a new external perimeter after we've found at least a first internal perimeter to re-order. + // This means we entered a new island. + arr_i=arr_i-1; //step back one perimeter + max_internal = arr_i; // new maximum internal perimeter is now this as we have found a new external perimeter, hence a new island. + break; // exit the for loop } - // go to the next perimeter from the current position to continue scanning for external walls in the same island - position = arr_i + 1; } + + // printf("Layer ID %d, Outer index %d, inner index %d, second inner index %d, maximum internal perimeter %d \n",layer_id,outer,first_internal,second_internal, max_internal); + if (outer > -1 && first_internal > -1 && second_internal > -1) { // found all three perimeters to re-order? If not the perimeters will be processed outside in. + std::vector inner_outer_extrusions; // temporary array to hold extrusions for reordering + inner_outer_extrusions.resize(max_internal - position + 1); // reserve array containing the number of perimeters before a new island. Variables are array indexes hence need to add +1 to convert to position allocations + // printf("Allocated array size %d, max_internal index %d, start position index %d \n",max_internal-position+1,max_internal,position); + + for (arr_j = max_internal; arr_j >=position; --arr_j){ // go inside out towards the external perimeter (perimeters in reverse order) and store all internal perimeters until the first one identified with inset index 2 + if(arr_j >= second_internal){ + //printf("Inside out loop: Mapped perimeter index %d to array position %d\n", arr_j, max_internal-arr_j); + inner_outer_extrusions[max_internal-arr_j] = reordered_extrusions[arr_j]; + current_perimeter++; + } + } + + for (arr_j = position; arr_j < second_internal; ++arr_j){ // go outside in and map the remaining perimeters (external and first internal wall(s)) using the outside in wall order + // printf("Outside in loop: Mapped perimeter index %d to array position %d\n", arr_j, current_perimeter+1); + inner_outer_extrusions[++current_perimeter] = reordered_extrusions[arr_j]; + } + + for(arr_j = position; arr_j <= max_internal; ++arr_j) // replace perimeter array with the new re-ordered array + ordered_extrusions[arr_j] = inner_outer_extrusions[arr_j-position]; + } + // go to the next perimeter from the current position to continue scanning for external walls in the same island + position = arr_i + 1; } } + } - bool steep_overhang_contour = false; - bool steep_overhang_hole = false; - if (!config->overhang_reverse) { - // Skip steep overhang detection no reverse is specified - steep_overhang_contour = true; - steep_overhang_hole = true; - } - if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions, steep_overhang_contour, steep_overhang_hole); !extrusion_coll.empty()) { - if (config->overhang_reverse) { - reorient_perimeters(extrusion_coll, steep_overhang_contour, steep_overhang_hole, - this->config->overhang_reverse_internal_only); - } - defer_unsupported_loops(*this, extrusion_coll); - result.loops = std::move(extrusion_coll); - result.has_loops = true; + bool steep_overhang_contour = false; + bool steep_overhang_hole = false; + if (!config->overhang_reverse) { + // Skip steep overhang detection no reverse is specified + steep_overhang_contour = true; + steep_overhang_hole = true; + } + if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions, steep_overhang_contour, steep_overhang_hole); !extrusion_coll.empty()) { + if (config->overhang_reverse) { + reorient_perimeters(extrusion_coll, steep_overhang_contour, steep_overhang_hole, + this->config->overhang_reverse_internal_only); } + defer_unsupported_loops(*this, extrusion_coll); + this->loops->append(extrusion_coll); + } - const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; + const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; - if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) - infill_contour.clear(); // Infill region is too small, so let's filter it out. + if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) + infill_contour.clear(); // Infill region is too small, so let's filter it out. - // create one more offset to be used as boundary for fill - // we offset by half the perimeter spacing (to get to the actual infill boundary) - // and then we offset back and forth by half the infill spacing to only consider the - // non-collapsing regions - coord_t inset = - (loop_number < 0) ? 0 : - (loop_number == 0) ? - // one loop - ext_perimeter_spacing : - // two or more loops? - perimeter_spacing; - coord_t top_inset = inset; + // create one more offset to be used as boundary for fill + // we offset by half the perimeter spacing (to get to the actual infill boundary) + // and then we offset back and forth by half the infill spacing to only consider the + // non-collapsing regions + coord_t inset = + (loop_number < 0) ? 0 : + (loop_number == 0) ? + // one loop + ext_perimeter_spacing : + // two or more loops? + perimeter_spacing; + coord_t top_inset = inset; - top_inset = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale(inset)))); - if(is_topmost_layer || is_bottom_layer) - inset = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale(inset)))); - else - inset = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale(inset)))); + top_inset = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale(inset)))); + if(is_topmost_layer || is_bottom_layer) + inset = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale(inset)))); + else + inset = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale(inset)))); - // simplify infill contours according to resolution - Polygons pp; - for (ExPolygon& ex : infill_contour) - ex.simplify_p(m_scaled_resolution, &pp); - ExPolygons not_filled_exp = union_ex(pp); - // collapse too narrow infill areas - const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); + // simplify infill contours according to resolution + Polygons pp; + for (ExPolygon& ex : infill_contour) + ex.simplify_p(m_scaled_resolution, &pp); + ExPolygons not_filled_exp = union_ex(pp); + // collapse too narrow infill areas + const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE)); - ExPolygons infill_exp = offset2_ex( + ExPolygons infill_exp = offset2_ex( + not_filled_exp, + float(-min_perimeter_infill_spacing / 2.), + float(inset + min_perimeter_infill_spacing / 2.)); + // append infill areas to fill_surfaces + if (!top_expolygons.empty()) { + infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset))); + } + this->fill_surfaces->append(infill_exp, stInternal); + + apply_extra_perimeters(infill_exp); + + // BBS: get the no-overlap infill expolygons + { + ExPolygons polyWithoutOverlap; + polyWithoutOverlap = offset2_ex( not_filled_exp, float(-min_perimeter_infill_spacing / 2.), - float(inset + min_perimeter_infill_spacing / 2.)); - // append infill areas to fill_surfaces - if (!top_expolygons.empty()) { - infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset))); - } - result.infill = std::move(infill_exp); - - // BBS: get the no-overlap infill expolygons - { - ExPolygons polyWithoutOverlap; - polyWithoutOverlap = offset2_ex( - not_filled_exp, - float(-min_perimeter_infill_spacing / 2.), - float(+min_perimeter_infill_spacing / 2.)); - if (!top_expolygons.empty()) - polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons); - result.no_overlap = std::move(polyWithoutOverlap); - } - } - }); - for (ArachneSurfaceResult &result : results) { - if (result.has_loops) - this->loops->append(result.loops); - this->fill_surfaces->append(result.infill, stInternal); - apply_extra_perimeters(result.infill); - this->fill_no_overlap->insert(this->fill_no_overlap->end(), result.no_overlap.begin(), result.no_overlap.end()); + float(+min_perimeter_infill_spacing / 2.)); + if (!top_expolygons.empty()) + polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons); + this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end()); + } } } diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 7e0d88a720..34ca214cf3 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -27,7 +27,7 @@ public: explicit Polygon(const Points &points) : MultiPoint(points) {} Polygon(std::initializer_list points) : MultiPoint(points) {} Polygon(const Polygon &other) : MultiPoint(other.points) {} - Polygon(Polygon &&other) noexcept : MultiPoint(std::move(other.points)) {} + Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {} static Polygon new_scale(const std::vector &points) { Polygon pgn; pgn.points.reserve(points.size()); @@ -36,7 +36,7 @@ public: return pgn; } Polygon& operator=(const Polygon &other) { points = other.points; return *this; } - Polygon& operator=(Polygon &&other) noexcept { points = std::move(other.points); return *this; } + Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; } Point& operator[](Points::size_type idx) { return this->points[idx]; } const Point& operator[](Points::size_type idx) const { return this->points[idx]; } diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index aa9282970e..f52c3c9bbd 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -20,7 +20,7 @@ class Polyline : public MultiPoint { public: Polyline() {}; Polyline(const Polyline& other) : MultiPoint(other.points), fitting_result(other.fitting_result) {} - Polyline(Polyline &&other) noexcept : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {} + Polyline(Polyline &&other) : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {} Polyline(std::initializer_list list) : MultiPoint(list) { fitting_result.clear(); } @@ -41,7 +41,7 @@ public: fitting_result = other.fitting_result; return *this; } - Polyline& operator=(Polyline&& other) noexcept { + Polyline& operator=(Polyline&& other) { points = std::move(other.points); fitting_result = std::move(other.fitting_result); return *this; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d26bb142cb..3b7e889472 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -30,7 +30,6 @@ #include #include -#include #include #include #include @@ -43,7 +42,6 @@ #include #include -#include #include #include @@ -1666,9 +1664,7 @@ void PrintObject::detect_surfaces_type() bool interface_shells = ! spiral_mode && m_config.interface_shells.value; size_t num_layers = spiral_mode ? std::min(size_t(this->printing_region(0).config().bottom_shell_layers), m_layers.size()) : m_layers.size(); - // The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the - // others, so the regions run next to each other instead of one after another, each still over all layers. - tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " in parallel - start"; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (Layer *layer : m_layers) @@ -1726,7 +1722,7 @@ void PrintObject::detect_surfaces_type() if (upper_layer) { ExPolygons upper_slices = interface_shells ? diff_ex(layerm_slices_surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) : - diff_ex_by_piece(layerm_slices_surfaces, to_polygons(upper_layer->lslices), ApplySafetyOffset::Yes); + diff_ex(layerm_slices_surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); surfaces_append(top, opening_ex(upper_slices, offset), stTop); } else { // if no upper layer, all surfaces of this one are solid @@ -1752,7 +1748,7 @@ void PrintObject::detect_surfaces_type() surfaces_append( bottom, opening_ex( - diff_ex_by_piece(layerm_slices_surfaces, to_polygons(lower_layer->lslices), ApplySafetyOffset::Yes), + diff_ex(layerm_slices_surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces @@ -1783,44 +1779,34 @@ void PrintObject::detect_surfaces_type() // and top surfaces; let's do an intersection to discover them and consider them // as bottom surfaces (to allow for bridge detection) if (! top.empty() && ! bottom.empty()) { - const auto cracks = intersection_ex_by_piece(to_expolygons(top), to_polygons(bottom)); + const auto cracks = intersection_ex(top, bottom); if (!cracks.empty()) { if (lower_layer) { // Only detect small cracks for non-first layer, because first layer should always be bottom const float small_crack_threshold = -layerm->flow(frExternalPerimeter).scaled_width() * 1.5; - // Only the bottom surfaces near a crack can take part: one that contains it must contain its box, - // and one whose box misses the grown crack is left unchanged by removing it. A layer cut through - // a fine relief has thousands of both, which made this loop quadratic. for (const auto& crack : cracks) { if (offset_ex(crack, small_crack_threshold).empty()) { // For small cracks, if it's part of a large bottom surface, then it should be added to bottom as well - const BoundingBox crack_bbox = get_extents(crack); - if (std::any_of(bottom.begin(), bottom.end(), [&crack, &crack_bbox, small_crack_threshold](const Surface& s) { + if (std::any_of(bottom.begin(), bottom.end(), [&crack, small_crack_threshold](const Surface& s) { const auto& se = s.expolygon; - return get_extents(se).inflated(SCALED_EPSILON).contains(crack_bbox) - && diff_ex(crack, se, ApplySafetyOffset::Yes).empty() + return diff_ex(crack, se, ApplySafetyOffset::Yes).empty() && se.area() > crack.area() * 2 && !offset_ex(diff_ex(se, crack), small_crack_threshold).empty(); })) continue; // Crack too small, leave it as part of the top surface, remove it from bottom surfaces - const ExPolygons grown_crack = offset_ex(crack, -small_crack_threshold); - const BoundingBox grown_bbox = get_extents(grown_crack); Surfaces bot_tmp; for (auto& b : bottom) { - if (get_extents(b.expolygon).overlap(grown_bbox)) - surfaces_append(bot_tmp, diff_ex(b.expolygon, grown_crack), b.surface_type); - else - bot_tmp.emplace_back(std::move(b)); + surfaces_append(bot_tmp, diff_ex(b.expolygon, offset_ex(crack, -small_crack_threshold)), b.surface_type); } bottom = std::move(bot_tmp); } } } - ExPolygons top_expolygons = to_expolygons(std::move(top)); + Polygons top_polygons = to_polygons(std::move(top)); top.clear(); - surfaces_append(top, diff_ex_by_piece(top_expolygons, to_polygons(bottom)), stTop); + surfaces_append(top, diff_ex(top_polygons, bottom), stTop); } } @@ -1911,7 +1897,7 @@ void PrintObject::detect_surfaces_type() { Polygons topbottom = to_polygons(top); polygons_append(topbottom, to_polygons(bottom)); - surfaces_append(surfaces_out, diff_ex_by_piece(surfaces_prev_expolys, topbottom), stInternal); + surfaces_append(surfaces_out, diff_ex(surfaces_prev_expolys, topbottom), stInternal); } surfaces_append(surfaces_out, std::move(top)); @@ -2088,31 +2074,29 @@ void PrintObject::detect_surfaces_type() } } ); + // ============================================================================================================== + // === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ============== + // === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ========== + // === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ========== + // ============================================================================================================== + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), [this, region_id](const tbb::blocked_range &range) { + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + Surfaces &surfs = m_layers[idx_layer]->m_regions[region_id]->slices.surfaces; + for (Surface &s : surfs) { + if (s.surface_type == stInternalAfterExternalBridge) { + s.surface_type = stBottomBridge; + } + } + } + } + ); + } } // ============================================================================================================== // === ORCA: End of second external bridge layer changes ======================================================= // ============================================================================================================== - }); // for each this->print->region_count - - // ============================================================================================================== - // === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ============== - // === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ========== - // === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ========== - // ============================================================================================================== - // Once all the regions have their second bridge layer, and before their slices are trimmed into fill surfaces below. - if ((this->config().enable_extra_bridge_layer.value == eblApplyToAll) || (this->config().enable_extra_bridge_layer.value == eblExternalBridgeOnly)) { - tbb::parallel_for(tbb::blocked_range(0, m_layers.size()), [this](const tbb::blocked_range &range) { - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) - for (LayerRegion *layerm : m_layers[idx_layer]->regions()) - for (Surface &s : layerm->slices.surfaces) - if (s.surface_type == stInternalAfterExternalBridge) - s.surface_type = stBottomBridge; - }); - m_print->throw_if_canceled(); - } - - tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) { BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start"; // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces. tbb::parallel_for( @@ -2129,7 +2113,7 @@ void PrintObject::detect_surfaces_type() }); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - end"; - }); + } // for each this->print->region_count // Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.) m_typed_slices = true; @@ -2196,10 +2180,8 @@ void PrintObject::process_external_surfaces() BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end"; } - BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - start"; - // The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the - // others, so the regions run next to each other instead of one after another, each still over all layers. - tbb::parallel_for(size_t(0), this->num_printing_regions(), [this, &surfaces_covered](size_t region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) { + BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), [this, &surfaces_covered, region_id](const tbb::blocked_range& range) { @@ -2214,9 +2196,9 @@ void PrintObject::process_external_surfaces() } } ); - }); - m_print->throw_if_canceled(); - BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - end"; + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end"; + } } void PrintObject::discover_vertical_shells() @@ -2255,10 +2237,10 @@ void PrintObject::discover_vertical_shells() // The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit. return; BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom"; - // One layer per task: on a layer cut through a fine relief the unions below take far longer than elsewhere, and a - // few such layers next to each other must not end up in one task. + //FIXME Improve the heuristics for a grain size. + size_t grain_size = std::max(num_layers / 16, size_t(1)); tbb::parallel_for( - tbb::blocked_range(0, num_layers, 1), + tbb::blocked_range(0, num_layers, grain_size), [this, &cache_top_botom_regions](const tbb::blocked_range& range) { const std::initializer_list surfaces_bottom { stBottom, stBottomBridge }; const size_t num_regions = this->num_printing_regions(); @@ -2266,198 +2248,67 @@ void PrintObject::discover_vertical_shells() m_print->throw_if_canceled(); const Layer &layer = *m_layers[idx_layer]; DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer]; - const auto top_bottom_expansion = [&layer](size_t region_id) { - return float(layer.m_regions[region_id]->flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; - }; + // Simulate single set of perimeters over all merged regions. + float perimeter_offset = 0.f; + float perimeter_min_spacing = FLT_MAX; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING static size_t debug_idx = 0; ++ debug_idx; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // The top surfaces, the bottom surfaces and the holes are independent of each other. - tbb::parallel_invoke( - [&]() { - for (size_t region_id = 0; region_id < num_regions; ++ region_id) - append(cache.top_surfaces, offset(layer.m_regions[region_id]->slices.filter_by_type(stTop), top_bottom_expansion(region_id))); -// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion)); - // Save some computing time by reducing the number of polygons. - cache.top_surfaces = union_(cache.top_surfaces); - }, - [&]() { - for (size_t region_id = 0; region_id < num_regions; ++ region_id) - append(cache.bottom_surfaces, offset(layer.m_regions[region_id]->slices.filter_by_types(surfaces_bottom), top_bottom_expansion(region_id))); -// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion)); - cache.bottom_surfaces = union_(cache.bottom_surfaces); - }, - [&]() { - // Simulate single set of perimeters over all merged regions. - float perimeter_offset = 0.f; - float perimeter_min_spacing = FLT_MAX; - for (size_t region_id = 0; region_id < num_regions; ++ region_id) { - const LayerRegion &layerm = *layer.m_regions[region_id]; - // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. - // First find the maxium number of perimeters per region slice. - unsigned int perimeters = 0; - for (const Surface &s : layerm.slices.surfaces) - perimeters = std::max(perimeters, s.extra_perimeters); - perimeters += layerm.region().config().wall_loops.value; - // Then calculate the infill offset. - if (perimeters > 0) { - Flow extflow = layerm.flow(frExternalPerimeter); - Flow flow = layerm.flow(frPerimeter); - perimeter_offset = std::max(perimeter_offset, - 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing()); - perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing()))); - } - polygons_append(cache.holes, to_polygons(layerm.fill_expolygons)); - } - // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. - if (perimeter_offset > 0.) { - // The layer.lslices are forced to merge by expanding them first. - polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing)); + for (size_t region_id = 0; region_id < num_regions; ++ region_id) { + LayerRegion &layerm = *layer.m_regions[region_id]; + float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; + // Top surfaces. + append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion)); +// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion)); + // Bottom surfaces. + append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion)); +// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion)); + // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only. + // First find the maxium number of perimeters per region slice. + unsigned int perimeters = 0; + for (Surface &s : layerm.slices.surfaces) + perimeters = std::max(perimeters, s.extra_perimeters); + perimeters += layerm.region().config().wall_loops.value; + // Then calculate the infill offset. + if (perimeters > 0) { + Flow extflow = layerm.flow(frExternalPerimeter); + Flow flow = layerm.flow(frPerimeter); + perimeter_offset = std::max(perimeter_offset, + 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing()); + perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing()))); + } + polygons_append(cache.holes, to_polygons(layerm.fill_expolygons)); + } + // Save some computing time by reducing the number of polygons. + cache.top_surfaces = union_(cache.top_surfaces); + cache.bottom_surfaces = union_(cache.bottom_surfaces); + // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print. + if (perimeter_offset > 0.) { + // The layer.lslices are forced to merge by expanding them first. + polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing)); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices)); - svg.draw(layer.lslices, "blue"); - svg.draw(union_ex(cache.holes), "red"); - svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05)); - svg.Close(); - } + { + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices)); + svg.draw(layer.lslices, "blue"); + svg.draw(union_ex(cache.holes), "red"); + svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05)); + svg.Close(); + } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - } - cache.holes = union_(cache.holes); - }); + } + cache.holes = union_(cache.holes); } }); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom"; } - // With one top/bottom cache for all regions, the shell and hole accumulation in the loop below depends on nothing - // region-specific but the shell settings and the external perimeter spacing, so a region sharing them with an earlier - // one reuses its result instead of repeating it: that accumulation is a union over several layers of top/bottom - // surfaces, and a multi-material print has a region per filament. - using AccumulationKey = std::array; - struct ShellAccumulation - { - AccumulationKey key; - Polygons shell; - Polygons holes; - }; - const auto accumulation_key = [](const PrintRegionConfig ®ion_config, const LayerRegion *layerm) { - return AccumulationKey{ double(region_config.top_shell_layers.value), region_config.top_shell_thickness.value, - double(region_config.bottom_shell_layers.value), region_config.bottom_shell_thickness.value, - double(layerm->flow(frExternalPerimeter).scaled_spacing()) }; - }; - const auto accumulate_shell = [this, &cache_top_botom_regions](size_t idx_layer, const PrintRegionConfig ®ion_config, - const LayerRegion *layerm, Polygons &shell, Polygons &holes) { - const Layer *layer = m_layers[idx_layer]; - polygons_append(holes, cache_top_botom_regions[idx_layer].holes); - auto combine_holes = [&holes](const Polygons &holes2) { - if (holes.empty() || holes2.empty()) - holes.clear(); - else - holes = intersection(holes, holes2); - }; - auto combine_shells = [&shell](const Polygons &shells2) { - if (shell.empty()) - shell = std::move(shells2); - else if (! shells2.empty()) { - polygons_append(shell, shells2); - // Running the union_ using the Clipper library piece by piece is cheaper - // than running the union_ all at once. - shell = union_(shell); - } - }; - static constexpr const bool one_more_layer_below_top_bottom_surfaces = false; - if (int n_top_layers = region_config.top_shell_layers.value; n_top_layers > 0) { - // Gather top regions projected to this layer. - coordf_t print_z = layer->print_z; - int i = int(idx_layer) + 1; - int itop = int(idx_layer) + n_top_layers; - bool at_least_one_top_projected = false; - for (; i < int(cache_top_botom_regions.size()) && - (i < itop || m_layers[i]->print_z - print_z < region_config.top_shell_thickness - EPSILON); - ++ i) { - at_least_one_top_projected = true; - const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; - combine_holes(cache.holes); - combine_shells(cache.top_surfaces); - } - if (!at_least_one_top_projected && i < int(cache_top_botom_regions.size())) { - // Lets consider this a special case - with only 1 top solid and minimal shell thickness settings, the - // boundaries of solid layers are not anchored over/under perimeters, so lets fix it by adding at least one - // perimeter width of area - Polygons anchor_area = intersection(expand(cache_top_botom_regions[idx_layer].top_surfaces, - layerm->flow(frExternalPerimeter).scaled_spacing()), - to_polygons(m_layers[i]->lslices)); - combine_shells(anchor_area); - } - - if (one_more_layer_below_top_bottom_surfaces) - if (i < int(cache_top_botom_regions.size()) && - (i <= itop || m_layers[i]->bottom_z() - print_z < region_config.top_shell_thickness - EPSILON)) - combine_holes(cache_top_botom_regions[i].holes); - } - if (int n_bottom_layers = region_config.bottom_shell_layers.value; n_bottom_layers > 0) { - // Gather bottom regions projected to this layer. - coordf_t bottom_z = layer->bottom_z(); - int i = int(idx_layer) - 1; - int ibottom = int(idx_layer) - n_bottom_layers; - bool at_least_one_bottom_projected = false; - for (; i >= 0 && - (i > ibottom || bottom_z - m_layers[i]->bottom_z() < region_config.bottom_shell_thickness - EPSILON); - -- i) { - at_least_one_bottom_projected = true; - const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; - combine_holes(cache.holes); - combine_shells(cache.bottom_surfaces); - } - - if (!at_least_one_bottom_projected && i >= 0) { - Polygons anchor_area = intersection(expand(cache_top_botom_regions[idx_layer].bottom_surfaces, - layerm->flow(frExternalPerimeter).scaled_spacing()), - to_polygons(m_layers[i]->lslices)); - combine_shells(anchor_area); - } - - if (one_more_layer_below_top_bottom_surfaces) - if (i >= 0 && - (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON)) - combine_holes(cache_top_botom_regions[i].holes); - } - }; - std::vector> shell_accumulations(top_bottom_surfaces_all_regions ? num_layers : 0); - if (! shell_accumulations.empty()) { - // Every (layer, key) pair is accumulated once, before the regions, so that nothing in the loop below is shared - // between them and they can run next to each other. - std::vector> todo; // layer, its slot, a region holding the key - for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer) { - std::vector &accumulations = shell_accumulations[idx_layer]; - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { - if (this->printing_region(region_id).config().ensure_vertical_shell_thickness.value != evstAll) - continue; - const LayerRegion *layerm = m_layers[idx_layer]->m_regions[region_id]; - const AccumulationKey key = accumulation_key(layerm->region().config(), layerm); - if (std::none_of(accumulations.begin(), accumulations.end(), [&key](const ShellAccumulation &a) { return a.key == key; })) { - todo.push_back({ idx_layer, accumulations.size(), region_id }); - accumulations.push_back({ key, {}, {} }); - } - } - } - tbb::parallel_for(size_t(0), todo.size(), [this, &todo, &shell_accumulations, &accumulate_shell](size_t i) { - m_print->throw_if_canceled(); - const LayerRegion *layerm = m_layers[todo[i][0]]->m_regions[todo[i][2]]; - ShellAccumulation &out = shell_accumulations[todo[i][0]][todo[i][1]]; - accumulate_shell(todo[i][0], layerm->region().config(), layerm, out.shell, out.holes); - }); - m_print->throw_if_canceled(); - } - - const auto process_region = [&](size_t region_id) { + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { const PrintRegion ®ion = this->printing_region(region_id); if (region.config().ensure_vertical_shell_thickness.value != evstAll ) // This region will be handled by discover_horizontal_shells(). - return; + continue; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); @@ -2497,7 +2348,7 @@ void PrintObject::discover_vertical_shells() grain_size = 1; tbb::parallel_for( tbb::blocked_range(0, num_layers, grain_size), - [this, region_id, &shell_accumulations, &accumulation_key, &accumulate_shell] + [this, region_id, &cache_top_botom_regions] (const tbb::blocked_range& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { @@ -2547,19 +2398,80 @@ void PrintObject::discover_vertical_shells() } } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - const AccumulationKey key = accumulation_key(region_config, layerm); - const ShellAccumulation *reused = shell_accumulations.empty() ? nullptr : - [&]() -> const ShellAccumulation * { - for (const ShellAccumulation &a : shell_accumulations[idx_layer]) - if (a.key == key) - return &a; - return nullptr; - }(); - if (reused != nullptr) { - shell = reused->shell; - holes = reused->holes; - } else - accumulate_shell(idx_layer, region_config, layerm, shell, holes); + polygons_append(holes, cache_top_botom_regions[idx_layer].holes); + auto combine_holes = [&holes](const Polygons &holes2) { + if (holes.empty() || holes2.empty()) + holes.clear(); + else + holes = intersection(holes, holes2); + }; + auto combine_shells = [&shell](const Polygons &shells2) { + if (shell.empty()) + shell = std::move(shells2); + else if (! shells2.empty()) { + polygons_append(shell, shells2); + // Running the union_ using the Clipper library piece by piece is cheaper + // than running the union_ all at once. + shell = union_(shell); + } + }; + static constexpr const bool one_more_layer_below_top_bottom_surfaces = false; + if (int n_top_layers = region_config.top_shell_layers.value; n_top_layers > 0) { + // Gather top regions projected to this layer. + coordf_t print_z = layer->print_z; + int i = int(idx_layer) + 1; + int itop = int(idx_layer) + n_top_layers; + bool at_least_one_top_projected = false; + for (; i < int(cache_top_botom_regions.size()) && + (i < itop || m_layers[i]->print_z - print_z < region_config.top_shell_thickness - EPSILON); + ++ i) { + at_least_one_top_projected = true; + const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; + combine_holes(cache.holes); + combine_shells(cache.top_surfaces); + } + if (!at_least_one_top_projected && i < int(cache_top_botom_regions.size())) { + // Lets consider this a special case - with only 1 top solid and minimal shell thickness settings, the + // boundaries of solid layers are not anchored over/under perimeters, so lets fix it by adding at least one + // perimeter width of area + Polygons anchor_area = intersection(expand(cache_top_botom_regions[idx_layer].top_surfaces, + layerm->flow(frExternalPerimeter).scaled_spacing()), + to_polygons(m_layers[i]->lslices)); + combine_shells(anchor_area); + } + + if (one_more_layer_below_top_bottom_surfaces) + if (i < int(cache_top_botom_regions.size()) && + (i <= itop || m_layers[i]->bottom_z() - print_z < region_config.top_shell_thickness - EPSILON)) + combine_holes(cache_top_botom_regions[i].holes); + } + if (int n_bottom_layers = region_config.bottom_shell_layers.value; n_bottom_layers > 0) { + // Gather bottom regions projected to this layer. + coordf_t bottom_z = layer->bottom_z(); + int i = int(idx_layer) - 1; + int ibottom = int(idx_layer) - n_bottom_layers; + bool at_least_one_bottom_projected = false; + for (; i >= 0 && + (i > ibottom || bottom_z - m_layers[i]->bottom_z() < region_config.bottom_shell_thickness - EPSILON); + -- i) { + at_least_one_bottom_projected = true; + const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; + combine_holes(cache.holes); + combine_shells(cache.bottom_surfaces); + } + + if (!at_least_one_bottom_projected && i >= 0) { + Polygons anchor_area = intersection(expand(cache_top_botom_regions[idx_layer].bottom_surfaces, + layerm->flow(frExternalPerimeter).scaled_spacing()), + to_polygons(m_layers[i]->lslices)); + combine_shells(anchor_area); + } + + if (one_more_layer_below_top_bottom_surfaces) + if (i >= 0 && + (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON)) + combine_holes(cache_top_botom_regions[i].holes); + } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); @@ -2653,8 +2565,11 @@ void PrintObject::discover_vertical_shells() Polygons object_volume; Polygons internal_volume; { - if (idx_layer > 0 && idx_layer + 1 < m_layers.size()) - object_volume = to_polygons(intersection_ex_by_piece(m_layers[idx_layer - 1]->lslices, to_polygons(m_layers[idx_layer + 1]->lslices))); + Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{}; + Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ? + to_polygons(m_layers[idx_layer + 1]->lslices) : + Polygons{}; + object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice); internal_volume = closing(polygonsInternal, SCALED_EPSILON); } @@ -2665,34 +2580,15 @@ void PrintObject::discover_vertical_shells() // the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy // 2. the area does not fully cover an internal polygon // This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small - // Both tests below compare a small piece against the whole layer. Done literally, that is - // quadratic in the number of pieces, which is what a layer split up by colour painting has, - // so each is restricted to the part of the layer near the piece with an identical result: - // object_volume is clipped to the piece's box, and only the internal polygons whose box meets - // the expanded piece take part in the count, since the others pass through the difference - // unchanged and add the same number to both sides of it. - std::vector internal_bboxes; - internal_bboxes.reserve(internal_volume.size()); - for (const Polygon &poly : internal_volume) - internal_bboxes.emplace_back(get_extents(poly)); regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(), - [&internal_volume, &internal_bboxes, &min_perimeter_infill_spacing, + [&internal_volume, &min_perimeter_infill_spacing, &object_volume](const ExPolygon &p) { - const bool small = p.area() < min_perimeter_infill_spacing * scaled(1.5) || - (p.area() < min_perimeter_infill_spacing * scaled(8.0) && - diff(to_polygons(p), - ClipperUtils::clip_clipper_polygons_with_subject_bbox( - object_volume, get_extents(p).inflated(SCALED_EPSILON))) - .empty()); - if (!small) - return false; - const Polygons expanded = expand(to_polygons(p), min_perimeter_infill_spacing); - const BoundingBox bbox = get_extents(expanded); - Polygons nearby; - for (size_t i = 0; i < internal_volume.size(); ++i) - if (internal_bboxes[i].overlap(bbox)) - nearby.emplace_back(internal_volume[i]); - return diff(nearby, expanded).size() >= nearby.size(); + return (p.area() < min_perimeter_infill_spacing * scaled(1.5) || + (p.area() < min_perimeter_infill_spacing * scaled(8.0) && + diff(to_polygons(p), object_volume).empty())) && + diff(internal_volume, + expand(to_polygons(p), min_perimeter_infill_spacing)) + .size() >= internal_volume.size(); }), regularized_shell.end()); } @@ -2714,9 +2610,8 @@ void PrintObject::discover_vertical_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Trim the internal & internalvoid by the shell. - const Polygons regularized_shell_polygons = to_polygons(regularized_shell); - Slic3r::ExPolygons new_internal = diff_ex_by_piece(to_expolygons(layerm->fill_surfaces.filter_by_type(stInternal)), regularized_shell_polygons); - Slic3r::ExPolygons new_internal_void = diff_ex_by_piece(to_expolygons(layerm->fill_surfaces.filter_by_type(stInternalVoid)), regularized_shell_polygons); + Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), regularized_shell); + Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), regularized_shell); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -2743,15 +2638,7 @@ void PrintObject::discover_vertical_shells() layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final"); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - }; // for each region - if (top_bottom_surfaces_all_regions) - // Nothing is shared between the regions, and a layer cut through a fine relief takes far longer than the others, - // so they run next to each other instead of one after another. - tbb::parallel_for(size_t(0), this->num_printing_regions(), process_region); - else - // Here every region fills the one top/bottom cache with its own surfaces first. - for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) - process_region(region_id); + } // for each region } // void PrintObject::discover_vertical_shells() // #define DEBUG_BRIDGE_OVER_INFILL @@ -3272,16 +3159,6 @@ void PrintObject::bridge_over_infill() vertical_lines[i].b = Point{x, y_max}; } - // The vertical lines only span the bridged area's x range, so anchors entirely outside it can never be - // hit. Leaving them out gives the same intersections without building a tree over the whole layer's - // boundary for every bridge. - const coord_t scan_x_min = bb_x.min.x(); - const coord_t scan_x_max = bb_x.min.x() + coord_t(n_vlines) * scan_spacing; - anchors.erase(std::remove_if(anchors.begin(), anchors.end(), - [scan_x_min, scan_x_max](const Line &l) { - return std::max(l.a.x(), l.b.x()) < scan_x_min || std::min(l.a.x(), l.b.x()) > scan_x_max; - }), - anchors.end()); auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer{std::move(anchors)}; auto bridged_area_tree = AABBTreeLines::LinesDistancer{to_lines(bridged_area)}; @@ -3526,63 +3403,28 @@ void PrintObject::bridge_over_infill() std::vector expanded_surfaces; expanded_surfaces.reserve(surfaces_by_layer[lidx].size()); - // The expanded fill boundary depends only on the bridging flow, and total_fill_area is not - // modified below, so build it once per spacing rather than once per candidate. A layer split - // into many candidates (e.g. by colour painting) otherwise repeats a layer-wide offset for each. - std::map boundary_by_spacing; - // expansion_area is a clean, non-overlapping set, so uniting it with a bridge or cutting a bridge - // out of it only changes the polygons near that bridge. The rest are passed through untouched - // instead of being fed to ClipperLib with the whole layer again for every candidate. - // Not `near`/`far`: the Windows headers still define those as macros, and they expand to - // nothing, which turns the declaration below into an empty one. - const auto split_near = [](const Polygons &polys, const BoundingBox &bbox, Polygons &rest) { - Polygons nearby; - for (const Polygon &p : polys) - (get_extents(p).overlap(bbox) ? nearby : rest).emplace_back(p); - return nearby; - }; for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) { const auto ®ion_config = candidate.region->region().config(); const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve || region_config.sparse_infill_pattern == ipOctagramSpiral; const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); - // deep_infill_area and internal_unsupported_area cover the whole layer; only their part under - // this candidate can change the results, so they are clipped to its box first. - if (!area_to_be_bridge.empty()) - area_to_be_bridge = intersection(area_to_be_bridge, - ClipperUtils::clip_clipper_polygons_with_subject_bbox( - deep_infill_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON))); + area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(), - [&internal_unsupported_area](const Polygon &p) { - return intersection({p}, ClipperUtils::clip_clipper_polygons_with_subject_bbox( - internal_unsupported_area, - get_extents(p).inflated(SCALED_EPSILON))) - .empty(); + [internal_unsupported_area](const Polygon &p) { + return intersection({p}, internal_unsupported_area).empty(); }), area_to_be_bridge.end()); + Polygons limiting_area = union_(area_to_be_bridge, expansion_area); + if (area_to_be_bridge.empty()) continue; - Polygons limiting_area; - const Polygons near_expansion = split_near(expansion_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON), - limiting_area); - append(limiting_area, union_(area_to_be_bridge, near_expansion)); - - auto boundary_it = boundary_by_spacing.find(flow.scaled_spacing()); - if (boundary_it == boundary_by_spacing.end()) - boundary_it = boundary_by_spacing - .emplace(flow.scaled_spacing(), to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing()))) - .first; - Polylines boundary_plines = boundary_it->second; + Polylines boundary_plines = to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing())); { - // No offset here: flow.spacing() is in mm, so the expand(limiting_area, 0.3 * flow.spacing()) - // this used to be moved the outline by 0.135 scaled units - nothing beyond rounding - while - // costing a whole-layer ClipperLib pass for every candidate. limiting_area is already a clean - // union, so its own outline is the same boundary. - Polylines limiting_plines = to_polylines(limiting_area); + Polylines limiting_plines = to_polylines(expand(limiting_area, 0.3*flow.spacing())); boundary_plines.insert(boundary_plines.end(), limiting_plines.begin(), limiting_plines.end()); } @@ -3656,12 +3498,9 @@ void PrintObject::bridge_over_infill() // Check collision with other expanded surfaces { bool reconstruct = false; - Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing()); - const BoundingBox tmp_expanded_bbox = get_extents(tmp_expanded_area); + Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing()); for (const CandidateSurface &s : expanded_surfaces) { - // Surfaces whose boxes miss each other cannot intersect, which is most pairs on a busy layer. - if (get_extents(s.new_polys).overlap(tmp_expanded_bbox) && - !intersection(s.new_polys, tmp_expanded_area).empty()) { + if (!intersection(s.new_polys, tmp_expanded_area).empty()) { bridging_angle = s.bridge_angle; reconstruct = true; break; @@ -3685,20 +3524,10 @@ void PrintObject::bridge_over_infill() bridging_area = union_(bridging_area, construct_anchored_polygon(bridging_area, to_lines(boundary_plines), flow, bridging_angle, scan_spacing, true)); } - // Each of these meets one bridge with the whole layer, so the layer side is first cut down to the - // bridge's box (and expansion_area split as above); the result is the same. - if (!bridging_area.empty()) { - const BoundingBox bridging_bbox = get_extents(bridging_area).inflated(SCALED_EPSILON); - bridging_area = intersection(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(limiting_area, bridging_bbox)); - bridging_area = intersection(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(total_fill_area, bridging_bbox)); - bridging_area = diff(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(total_top_area, bridging_bbox)); - } - if (!bridging_area.empty()) { - Polygons kept; - const Polygons cut = split_near(expansion_area, get_extents(bridging_area).inflated(SCALED_EPSILON), kept); - append(kept, diff(cut, bridging_area)); - expansion_area = std::move(kept); - } + bridging_area = intersection(bridging_area, limiting_area); + bridging_area = intersection(bridging_area, total_fill_area); + bridging_area = diff(bridging_area, total_top_area); + expansion_area = diff(expansion_area, bridging_area); #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_expanded_bridging" + std::to_string(r), diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index ef18ed363f..a955eb5ca7 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -929,9 +929,9 @@ public: ::fread(&y, sizeof(coord_t), 1, file); poly.points.emplace_back(Point(x * scale, y * scale)); } - printf("Polygon %d, area: %lf\n", i, area(poly.points)); if (which == -1 || which == i) m_support_polygons_deserialized.emplace_back(std::move(poly)); + printf("Polygon %d, area: %lf\n", i, area(poly.points)); } ::fread(&n_polygons, 4, 1, file); m_trimming_polygons_deserialized.reserve(n_polygons); diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 174c5e4fce..519b6e826e 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -854,41 +854,11 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/) if (is_auto(stype) && config_detect_sharp_tails) { // BBS detect sharp tail - // Each island is tested only against the lower islands whose box meets its own: overlaps() tries every - // pair, which on a layer cut through a fine relief (thousands of islands above thousands) never ends. - std::vector lower_bboxes; - lower_bboxes.reserve(lower_polys.size()); - for (const ExPolygon &lower : lower_polys) - lower_bboxes.emplace_back(get_extents(lower)); for (const ExPolygon& expoly : curr_polys) { bool is_sharp_tail = false; // 1. nothing below // this is a sharp tail region if it's floating and non-ignorable - const ExPolygons expanded = offset_ex(expoly, 0.1 * extrusion_width_scaled); - const BoundingBox bbox = get_extents(expanded); - ExPolygons lower_nearby; - for (size_t i = 0; i < lower_polys.size(); ++i) - if (lower_bboxes[i].overlap(bbox)) - lower_nearby.emplace_back(lower_polys[i]); - // As overlaps(expanded, lower_nearby), with each lower island cut to the island's box first: below - // a fine relief the lower layer is a few islands with thousands of holes, whose whole boundary - // was otherwise intersected again for every island above. - const auto overlaps_nearby = [&]() { - for (const ExPolygon &a : expanded) { - if (a.empty()) - continue; - const BoundingBox a_bbox = get_extents(a); - for (const ExPolygon &b : lower_nearby) { - if (b.empty() || !get_extents(b).overlap(a_bbox)) - continue; - const Polygons b_near = ClipperUtils::clip_clipper_polygons_with_subject_bbox(b, a_bbox.inflated(SCALED_EPSILON)); - if (!intersection_pl(to_polylines(b_near), a).empty() || b.contains(a.contour.points.front())) - return true; - } - } - return false; - }; - if (!overlaps_nearby()) { + if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_polys)) { is_sharp_tail = !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty(); } diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index f9737f8cf5..b63c283251 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -61,7 +61,7 @@ public: thickness(other.thickness), thickness_layers(other.thickness_layers), bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters) {}; - Surface(Surface &&rhs) noexcept + Surface(Surface &&rhs) : surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)), thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) @@ -87,7 +87,7 @@ public: return *this; } - Surface& operator=(Surface &&rhs) noexcept + Surface& operator=(Surface &&rhs) { surface_type = rhs.surface_type; expolygon = std::move(rhs.expolygon); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 06fce9796d..c339da566a 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -162,10 +162,10 @@ inline void append(std::vector &dest, std::vector &&src) { if (dest.empty()) dest = std::move(src); - else - // insert() grows the capacity geometrically; reserving exactly the new size reallocated on every call, which - // made appending piece by piece quadratic. - dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end())); + else { + dest.reserve(dest.size() + src.size()); + std::move(std::begin(src), std::end(src), std::back_inserter(dest)); + } src.clear(); src.shrink_to_fit(); } diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 1c6f14bc8e..fcc257f14d 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -28,7 +28,6 @@ add_executable(${_TEST_NAME}_tests test_filament_mixer.cpp test_fill_plane_path.cpp test_geometry.cpp - test_kdtree.cpp test_multimaterial_segmentation.cpp test_placeholder_parser.cpp test_polygon.cpp diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index c69c686521..fe9961a771 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -299,47 +299,3 @@ TEST_CASE("Traversing Clipper PolyTree", "[ClipperUtils]") { REQUIRE(count_polys(output) == reference.size()); } } - -TEST_CASE("Tiled diff and intersection cover the same area as the plain calls", "[ClipperUtils]") { - // A grid of disjoint framed squares, enough of them to be split into several tiles. - const int n = 40; - const coord_t cell = scaled(2.), side = scaled(1.5), frame = scaled(0.3); - ExPolygons subject; - for (int y = 0; y < n; ++ y) - for (int x = 0; x < n; ++ x) { - const Point o(x * cell, y * cell); - ExPolygon square(Polygon({ o, o + Point(side, 0), o + Point(side, side), o + Point(0, side) })); - Polygon hole({ o + Point(frame, frame), o + Point(frame, side - frame), o + Point(side - frame, side - frame), o + Point(side - frame, frame) }); - square.holes.emplace_back(std::move(hole)); - subject.emplace_back(std::move(square)); - } - // Clip polygons crossing many squares, one of them large with holes of its own. - Polygons clip; - const coord_t span = n * cell; - for (int i = 0; i < 8; ++ i) { - const coord_t y0 = coord_t(i) * span / 8, y1 = y0 + scaled(0.9); - clip.emplace_back(Polygon({ Point(- cell, y0), Point(span, y0 + cell * 3), Point(span, y1 + cell * 3), Point(- cell, y1) })); - } - ExPolygon big(Polygon({ Point(span / 4, span / 4), Point(3 * span / 4, span / 4), Point(3 * span / 4, 3 * span / 4), Point(span / 4, 3 * span / 4) })); - for (int i = 0; i < 4; ++ i) { - const Point o(span / 4 + scaled(3.1) + i * scaled(9.7), span / 4 + scaled(5.3)); - big.holes.emplace_back(Polygon({ o, o + Point(0, scaled(20.)), o + Point(scaled(5.), scaled(20.)), o + Point(scaled(5.), 0) })); - } - polygons_append(clip, to_polygons(big)); - - const auto xor_area = [](const ExPolygons &a, const ExPolygons &b) { return area(diff_ex(a, b)) + area(diff_ex(b, a)); }; - const ApplySafetyOffset safety = GENERATE(ApplySafetyOffset::No, ApplySafetyOffset::Yes); - const double tolerance = double(scaled(0.001)) * double(span); - - const ExPolygons diff_plain = diff_ex(subject, clip, safety); - const ExPolygons diff_tiled = diff_ex_by_piece(subject, clip, safety); - REQUIRE(area(diff_plain) > 0.); - CHECK_THAT(area(diff_tiled), Catch::Matchers::WithinRel(area(diff_plain), 1e-9)); - CHECK(xor_area(diff_tiled, diff_plain) < tolerance); - - const ExPolygons intersection_plain = intersection_ex(subject, clip, safety); - const ExPolygons intersection_tiled = intersection_ex_by_piece(subject, clip, safety); - REQUIRE(area(intersection_plain) > 0.); - CHECK_THAT(area(intersection_tiled), Catch::Matchers::WithinRel(area(intersection_plain), 1e-9)); - CHECK(xor_area(intersection_tiled, intersection_plain) < tolerance); -} diff --git a/tests/libslic3r/test_kdtree.cpp b/tests/libslic3r/test_kdtree.cpp deleted file mode 100644 index 679a1465a7..0000000000 --- a/tests/libslic3r/test_kdtree.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include - -#include -#include -#include - -#include "libslic3r/KDTreeIndirect.hpp" -#include "libslic3r/Point.hpp" - -using namespace Slic3r; - -TEST_CASE("Visiting the nearby points gives what collecting them gives", "[KDTree]") { - std::mt19937 rng(19937); - std::uniform_real_distribution coord(-50.f, 50.f); - // Points in a box, so that a radius search returns anything from none of them to all of them. - std::vector points(2000); - for (Vec3f &p : points) - p = Vec3f(coord(rng), coord(rng), coord(rng)); - - auto coordinate = [&points](size_t idx, size_t dimension) { return points[idx](int(dimension)); }; - KDTreeIndirect<3, float, decltype(coordinate)> tree(coordinate); - std::vector indices(points.size()); - std::iota(indices.begin(), indices.end(), 0); - tree.build(indices); - - const float radius = GENERATE(0.5f, 5.f, 25.f, 200.f); - for (int i = 0; i < 20; ++ i) { - const Vec3f center(coord(rng), coord(rng), coord(rng)); - - const std::vector collected = find_nearby_points(tree, center, radius); - std::vector visited; - visit_nearby_points(tree, center, radius, [&visited](size_t idx) { visited.emplace_back(idx); }); - - // Same points, and in the same order: a caller that keeps the first of several equally good ones - // must get the same answer either way. - REQUIRE(visited == collected); - } -} - -TEST_CASE("A radius search returns every point within the radius and no other", "[KDTree]") { - std::mt19937 rng(2024); - std::uniform_real_distribution coord(-20.f, 20.f); - std::vector points(500); - for (Vec3f &p : points) - p = Vec3f(coord(rng), coord(rng), coord(rng)); - - auto coordinate = [&points](size_t idx, size_t dimension) { return points[idx](int(dimension)); }; - KDTreeIndirect<3, float, decltype(coordinate)> tree(coordinate); - std::vector indices(points.size()); - std::iota(indices.begin(), indices.end(), 0); - tree.build(indices); - - const Vec3f center(1.f, -2.f, 3.f); - const float radius = 7.f; - - std::vector expected; - for (size_t i = 0; i < points.size(); ++ i) - if ((points[i] - center).squaredNorm() < radius * radius) - expected.emplace_back(i); - - std::vector visited; - visit_nearby_points(tree, center, radius, [&visited](size_t idx) { visited.emplace_back(idx); }); - std::sort(visited.begin(), visited.end()); - - REQUIRE(! expected.empty()); - REQUIRE(visited == expected); -}