From 9afe66eacfceb453bc2fa6b3e3c8fe1da7d9313d Mon Sep 17 00:00:00 2001 From: ExPikaPaka Date: Tue, 22 Sep 2026 19:19:54 +0200 Subject: [PATCH] Project painted faces onto the shell layers per tile Only the slices within the deepest shell offset decide the result, so the work is done per tile of the face. Top and bottom segmentation ~130 s at 0.1 mm / 2000k, was ~180. --- src/libslic3r/ClipperUtils.cpp | 64 +++++++++++---------- src/libslic3r/ClipperUtils.hpp | 10 ++++ src/libslic3r/MultiMaterialSegmentation.cpp | 50 +++++++++++----- 3 files changed, 81 insertions(+), 43 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 405ede60bd..51d9c0cea6 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -816,52 +816,58 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r 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); } -static Slic3r::ExPolygons clipper_ex_by_piece(ClipperLib::ClipType clipType, const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) -{ - // The subject ExPolygons are split into tiles by the centres of their boxes, a few dozen per tile, and each tile is one - // ClipperLib call with the clip cut to the box of the tile's ExPolygons. - BoundingBox extent; - std::vector bboxes; - bboxes.reserve(subject.size()); - for (const ExPolygon &expoly : subject) { - bboxes.emplace_back(get_extents(expoly)); - extent.merge(bboxes.back()); - } - const int tiles = std::clamp(int(std::sqrt(double(subject.size()) / 32.)), 1, 32); - std::vector> members(size_t(tiles * tiles)); - std::vector tile_bboxes(members.size()); - if (extent.defined) { +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); - for (size_t i = 0; i < subject.size(); ++i) { - const Point c = bboxes[i].center(); - const size_t tile = 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)); - members[tile].emplace_back(i); - tile_bboxes[tile].merge(bboxes[i]); + 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(members.size()); - tbb::parallel_for(size_t(0), members.size(), [&](size_t tile) { - if (members[tile].empty()) - return; + 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(members[tile].size()); - for (size_t i : members[tile]) + 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_bboxes[tile].inflated(SCALED_EPSILON); + 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] = _clipper_ex(clipType, ClipperUtils::ExPolygonsProvider(local_subject), ClipperUtils::PolygonsProvider(local_clip), do_safety_offset); + 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) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index a2b43ceb70..0c7b7f4294 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -2,6 +2,7 @@ #define slic3r_ClipperUtils_hpp_ #include "libslic3r.h" +#include "BoundingBox.hpp" #include "clipper.hpp" #include "ExPolygon.hpp" #include "Polygon.hpp" @@ -321,6 +322,15 @@ 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. diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 320e8fa39b..0798838b06 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1386,27 +1386,49 @@ static inline std::vector> segmentation_top_and_bottom_l // 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. - // The per-layer ClipperLib work is independent once the trimmed slices and the offsets have been walked in order, - // so it runs in parallel; the offsets are accumulated exactly as the sequential walk did. + // 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 trimmed(shell_layers.size()); - std::vector offsets(shell_layers.size()); - ExPolygons layer_slices_trimmed = input_expolygons[layer_idx]; - float offset = 0.f; + 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; - layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[shell_layers[i]]); - trimmed[i] = layer_slices_trimmed; + offsets[i] = offset; } - std::vector shells(shell_layers.size()); - tbb::parallel_for(size_t(0), shell_layers.size(), [&](size_t i) { - shells[i] = opening_ex(intersection_ex(ex, offset_ex(trimmed[i], offsets[i])), stat.small_region_threshold); + 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() && !shells[i].empty(); ++i) - append(dst[shell_layers[i] + dst_offset], std::move(shells[i])); + 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,