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,