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.
This commit is contained in:
ExPikaPaka
2026-09-22 19:19:54 +02:00
parent 69be77d859
commit 71c13f0546
3 changed files with 81 additions and 43 deletions
+35 -29
View File
@@ -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<BoundingBox> 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<std::vector<size_t>> members(size_t(tiles * tiles));
std::vector<BoundingBox> tile_bboxes(members.size());
if (extent.defined) {
namespace ClipperUtils {
std::vector<ExPolygonsTile> tile_expolygons(const ExPolygons &expolygons, size_t per_tile)
{
BoundingBox extent;
std::vector<BoundingBox> 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<size_t>(per_tile, 1)))), 1, 32);
const Point size = extent.size();
const coord_t tile_w = std::max<coord_t>(1, size.x() / tiles + 1), tile_h = std::max<coord_t>(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<ExPolygonsTile> 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<ClipperUtils::ExPolygonsTile> tiles = ClipperUtils::tile_expolygons(subject, 32);
std::vector<BoundingBox> clip_bboxes;
clip_bboxes.reserve(clip.size());
for (const Polygon &polygon : clip)
clip_bboxes.emplace_back(get_extents(polygon));
std::vector<Slic3r::ExPolygons> out_tiles(members.size());
tbb::parallel_for(size_t(0), members.size(), [&](size_t tile) {
if (members[tile].empty())
return;
std::vector<Slic3r::ExPolygons> 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)
+10
View File
@@ -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<size_t> members;
};
[[nodiscard]] std::vector<ExPolygonsTile> tile_expolygons(const ExPolygons &expolygons, size_t per_tile);
}
// Perform union of input polygons using the non-zero rule, convert to ExPolygons.
+36 -14
View File
@@ -1386,27 +1386,49 @@ static inline std::vector<std::vector<ExPolygons>> 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<size_t> &shell_layers,
const LayerColorStat &stat, std::vector<ExPolygons> &dst, size_t dst_offset) {
std::vector<ExPolygons> trimmed(shell_layers.size());
std::vector<float> offsets(shell_layers.size());
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
float offset = 0.f;
std::vector<float> 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<ExPolygons> 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<ClipperUtils::ExPolygonsTile> tiles = ClipperUtils::tile_expolygons(ex, 16);
// [shell layer][tile]
std::vector<std::vector<ExPolygons>> shells(shell_layers.size(), std::vector<ExPolygons>(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<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,