mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-04 22:31:02 +00:00
Keep the G-code identical to main on Clipper2
Since main moved to Clipper2, parts of this branch no longer gave the same G-code as main: - bridge_over_infill dropped expand(limiting_area, 0.3 * flow.spacing()) as a no-op. The offset is below one unit, but Clipper2 still unites its result, which splits and merges touching polygons and so changes the anchor lines. Running it on the polygons next to the bridge gives main's anchors without the whole-layer pass. - tsp_remove_crossings stopped at the first repeated ordering, where main runs on to its pn * pn cap. The loop is periodic from that point, so it now takes only the steps to the ordering main stops on. - With a single tile, the tiled booleans now make the plain call instead of cutting the clip to the tile first. The tiled boolean test compared rings exactly. With the safety offset a tile unites only the clip polygons near it, and Clipper2 can then round a crossing 1 unit differently, so that case allows 1 unit. Comments that named ClipperLib now say Clipper, and docs/HLSD/polygon-clipping.md describes the tiled booleans. G-code of the five handy models in four configurations and of a baked texture relief is byte-identical to main. Colour-painted models still differ: segmenting each island on its own splits a colour's region into different pieces than one diagram over the layer, which on one model also changes the first layer's tool order.
This commit is contained in:
@@ -62,6 +62,23 @@ Clipper2 behaves differently by default, the wrapper adjusts it.
|
||||
clip do not leave slivers.
|
||||
- Open polylines are clipped with the non-zero rule and keep their direction.
|
||||
|
||||
### Tiled booleans
|
||||
|
||||
The sweep slows down with the number of edges crossing a scan line, so a layer
|
||||
cut into thousands of pieces makes every whole-layer boolean expensive.
|
||||
`diff_ex_by_piece()` and `intersection_ex_by_piece()` take a subject of
|
||||
non-overlapping `ExPolygons`, group them into tiles with
|
||||
`ClipperUtils::tile_expolygons()`, and run each tile in parallel against only
|
||||
the clip polygons near it, cut to the tile's box. Below 128 pieces there is a
|
||||
single tile, and they are the plain `diff_ex()` / `intersection_ex()`.
|
||||
|
||||
The result covers the same area as the plain call. Without the safety offset
|
||||
the rings are the same. With it, each tile unites only the clip polygons near
|
||||
it, so a clip edge that the whole-layer union splits where it crosses a distant
|
||||
clip polygon stays whole, and a crossing with the subject can round 1 unit
|
||||
differently. The tiles' results are concatenated in tile order, so the order of
|
||||
the output `ExPolygons` differs from the plain call.
|
||||
|
||||
### Offsets
|
||||
|
||||
- Before offsetting, input vertices closer than
|
||||
|
||||
@@ -819,8 +819,11 @@ namespace ClipperUtils {
|
||||
|
||||
static Slic3r::ExPolygons clipper_ex_by_piece(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.
|
||||
// A few dozen subject ExPolygons to a tile, each tile one Clipper call with the clip cut to the tile's box.
|
||||
const std::vector<ClipperUtils::ExPolygonsTile> tiles = ClipperUtils::tile_expolygons(subject, 32);
|
||||
// One tile is the plain call: cutting the clip would only cost time.
|
||||
if (tiles.size() <= 1)
|
||||
return _clipper_ex(clipType, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset);
|
||||
std::vector<BoundingBox> clip_bboxes;
|
||||
clip_bboxes.reserve(clip.size());
|
||||
for (const Polygon &polygon : clip)
|
||||
|
||||
@@ -331,7 +331,7 @@ 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
|
||||
// Splits ExPolygons into tiles by the centres of their boxes, about `per_tile` of them to a tile, to run Clipper 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
|
||||
{
|
||||
@@ -537,7 +537,7 @@ 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 = 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.
|
||||
// subject of thousands of pieces spread over a layer: Clipper 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);
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -188,26 +187,34 @@ bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
|
||||
|
||||
// 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<int>(pn * pn);
|
||||
const int max_iters = static_cast<int>(pn * pn);
|
||||
bool improved = false;
|
||||
// Reversing between two segments that only touch or overlap along a line need not remove the intersection, so on
|
||||
// islands laid out on a regular grid (a tiled texture, an array of parts) the loop can cycle through the same
|
||||
// orderings until the pn * pn cap. Stop as soon as an ordering repeats; up to that point this is the same loop.
|
||||
std::unordered_set<uint64_t> seen_paths;
|
||||
// orderings until the pn * pn cap. Once an ordering repeats the rest of the loop is periodic, so only the steps
|
||||
// to the ordering the capped loop would have stopped on are taken.
|
||||
std::unordered_map<uint64_t, int> seen_paths; // path hash -> reversals done when it was reached
|
||||
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) {
|
||||
const auto reverse_first_crossing = [&]() {
|
||||
auto [ci, cj] = pn >= grid_min_size ? find_crossing_grid() : find_crossing();
|
||||
if (ci == std::numeric_limits<size_t>::max()) break;
|
||||
improved = true;
|
||||
if (ci == std::numeric_limits<size_t>::max())
|
||||
return false;
|
||||
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
|
||||
if (!seen_paths.insert(path_hash()).second)
|
||||
return true;
|
||||
};
|
||||
seen_paths.emplace(path_hash(), 0);
|
||||
for (int iter = 1; iter <= max_iters && reverse_first_crossing(); ++iter) {
|
||||
improved = true;
|
||||
if (auto [it, inserted] = seen_paths.emplace(path_hash(), iter); !inserted) {
|
||||
for (int steps = (max_iters - iter) % (iter - it->second); steps > 0; --steps)
|
||||
reverse_first_crossing();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return improved;
|
||||
}
|
||||
|
||||
@@ -1403,7 +1403,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
||||
// `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
|
||||
// each Clipper 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, ShellProjections &dst) {
|
||||
@@ -1965,7 +1965,7 @@ static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::ve
|
||||
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
|
||||
// cut through a fine relief every region shares thousands of hole contours with every other, and Clipper, 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<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
|
||||
|
||||
@@ -3531,7 +3531,7 @@ void PrintObject::bridge_over_infill()
|
||||
std::map<coord_t, Polylines> 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.
|
||||
// instead of being fed to Clipper 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) {
|
||||
@@ -3568,6 +3568,7 @@ void PrintObject::bridge_over_infill()
|
||||
Polygons limiting_area;
|
||||
const Polygons near_expansion = split_near(expansion_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON),
|
||||
limiting_area);
|
||||
const size_t num_far = limiting_area.size();
|
||||
append(limiting_area, union_(area_to_be_bridge, near_expansion));
|
||||
|
||||
auto boundary_it = boundary_by_spacing.find(flow.scaled_spacing());
|
||||
@@ -3577,11 +3578,9 @@ void PrintObject::bridge_over_infill()
|
||||
.first;
|
||||
Polylines boundary_plines = boundary_it->second;
|
||||
{
|
||||
// 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);
|
||||
// The sub-unit offset (spacing is in mm) still re-unites touching polygons by the bridge, which the anchors depend on.
|
||||
Polylines limiting_plines = to_polylines(Polygons(limiting_area.begin(), limiting_area.begin() + num_far));
|
||||
append(limiting_plines, to_polylines(expand(Polygons(limiting_area.begin() + num_far, limiting_area.end()), 0.3 * flow.spacing())));
|
||||
boundary_plines.insert(boundary_plines.end(), limiting_plines.begin(), limiting_plines.end());
|
||||
}
|
||||
|
||||
|
||||
@@ -325,6 +325,15 @@ static std::vector<std::vector<coord_t>> canonical_rings(const ExPolygons &expol
|
||||
return rings;
|
||||
}
|
||||
|
||||
// The same rings, every coordinate within `tolerance`.
|
||||
static bool same_rings(const ExPolygons &a, const ExPolygons &b, coord_t tolerance)
|
||||
{
|
||||
const std::vector<std::vector<coord_t>> ra = canonical_rings(a), rb = canonical_rings(b);
|
||||
return std::equal(ra.begin(), ra.end(), rb.begin(), rb.end(), [tolerance](const std::vector<coord_t> &x, const std::vector<coord_t> &y) {
|
||||
return std::equal(x.begin(), x.end(), y.begin(), y.end(), [tolerance](coord_t u, coord_t v) { return std::abs(u - v) <= tolerance; });
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("Tiled diff and intersection return the same polygons as the plain calls", "[ClipperUtils]") {
|
||||
// A grid of disjoint framed squares, enough of them to be split into several tiles.
|
||||
const int n = 40;
|
||||
@@ -358,13 +367,16 @@ TEST_CASE("Tiled diff and intersection return the same polygons as the plain cal
|
||||
// path under test is never taken.
|
||||
REQUIRE(ClipperUtils::tile_expolygons(subject, 32).size() > 1);
|
||||
|
||||
// With the safety offset a tile unites fewer clip polygons, so Clipper2 can round a crossing 1 unit differently.
|
||||
const coord_t tolerance = safety == ApplySafetyOffset::Yes ? 1 : 0;
|
||||
|
||||
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(canonical_rings(diff_tiled) == canonical_rings(diff_plain));
|
||||
CHECK(same_rings(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(canonical_rings(intersection_tiled) == canonical_rings(intersection_plain));
|
||||
CHECK(same_rings(intersection_tiled, intersection_plain, tolerance));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user