Fix G-code export stalling for hours on layers with thousands of islands

This commit is contained in:
SoftFever committed 2026-10-20 11:41:05 +08:00
1 parent c9d897b4d0
commit beae8aa516
3 files changed
+62 -6

No files matched your search

+11 -4
View File
@@ -6,6 +6,7 @@
#include "../ShortestPath.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/libslic3r.h"
#include <algorithm>
#include <cassert>
@@ -120,7 +121,7 @@ bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
// TSP helpers (2-opt, closing-edge rotation) that operate on the full cycle.
size_t n_edges = pn;
// Scan for first crossing; returns {i, j} or {npos, npos} if none.
// Scan for the first pair of intersecting edges worth reversing; returns {i, j} or {npos, npos} if none.
auto find_crossing = [&]() -> std::pair<size_t, size_t> {
for (size_t i = 0; i < n_edges; ++i) {
const Point& ai = centers[path[i]];
@@ -133,8 +134,13 @@ bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
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))
if (!bboxes_overlap(ai, bi, aj, bj) || !Geometry::segments_intersect(ai, bi, aj, bj))
continue;
// The reversal replaces edges (ai, bi) and (aj, bj) with (ai, aj) and (bi, bj). Edges that only touch
// or overlap collinearly, as at points sharing a position, may gain nothing from it, and reversing
// them would find the same pair again on every scan. A gain within SCALED_EPSILON, which nearly
// collinear crossings can have too, is not worth a reversal either.
if (ai.distance_to(bi) + aj.distance_to(bj) - ai.distance_to(aj) - bi.distance_to(bj) > SCALED_EPSILON)
return {i, j};
}
}
@@ -142,7 +148,8 @@ 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.
// Every reversal shortens the cycle, so no tour repeats and the scan ends once no pair is worth
// reversing; the cap is a safety net.
int max_iters = static_cast<int>(pn * pn);
bool improved = false;
while (max_iters-- > 0) {
+2 -2
View File
@@ -25,8 +25,8 @@ namespace Slic3r {
// Returns true if any improvement was made.
bool tsp_2opt_improve(std::vector<size_t>& path, const Points& centers, int max_passes = 10);
// Crossing removal: reverse any segment pair whose edges geometrically cross.
// Returns true if any crossing was removed.
// Crossing removal: reverse each pair of intersecting edges whose reversal shortens the cycle by more
// than SCALED_EPSILON. Returns true if any pair was reversed.
bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers);
// The shortest cycle through the points, starting at point 0, by dynamic programming over subsets; for a few points only.
@@ -1,6 +1,8 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/catch_all.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <vector>
#include <cstddef>
#include <math.h>
@@ -136,6 +138,53 @@ TEST_CASE("tsp_remove_crossings eliminates crossings", "[TSPPostProcessing]") {
REQUIRE(is_permutation(path, centers.size()));
}
TEST_CASE("tsp_remove_crossings leaves a tour alone when no reversal shortens it", "[TSPPostProcessing]") {
// Three points on a diagonal, the middle one doubled as island centroids snapped to the same 1 mm cell are,
// and a corner closing the tour. Edges 0-1 and 2-3 touch at the doubled point, and reversing between them
// only swaps the two copies.
Points centers;
centers.emplace_back(0, 0);
centers.emplace_back(3000000, 3000000);
centers.emplace_back(3000000, 3000000);
centers.emplace_back(6000000, 6000000);
centers.emplace_back(6000000, 0);
std::vector<size_t> path = {0, 1, 2, 3, 4};
REQUIRE(Geometry::segments_intersect(centers[0], centers[1], centers[2], centers[3]));
CHECK(!tsp_remove_crossings(path, centers));
CHECK(path == std::vector<size_t>{0, 1, 2, 3, 4});
}
TEST_CASE("tsp_remove_crossings keeps a crossing whose reversal gains less than SCALED_EPSILON", "[TSPPostProcessing]") {
// Two 100 mm edges crossing at a 10 nm offset: uncrossing them saves about 1e-6 scaled units of travel.
Points centers;
centers.emplace_back(0, 0);
centers.emplace_back(100000000, 10);
centers.emplace_back(100000000, 0);
centers.emplace_back(0, 10);
std::vector<size_t> path = {0, 1, 2, 3};
REQUIRE(Geometry::segments_intersect(centers[0], centers[1], centers[2], centers[3]));
CHECK(!tsp_remove_crossings(path, centers));
CHECK(path == std::vector<size_t>{0, 1, 2, 3});
}
TEST_CASE("tsp_remove_crossings reverses overlapping collinear edges when that shortens the tour", "[TSPPostProcessing]") {
// Points at x = 0, 3, 1 and 2 mm on one line. Tour 0-3-1-2 is 8 mm long, and its edges [0,3] and [1,2] overlap.
// The shortest tour through points on a line is twice their 3 mm span.
Points centers;
centers.emplace_back(0, 0);
centers.emplace_back(3000000, 0);
centers.emplace_back(1000000, 0);
centers.emplace_back(2000000, 0);
std::vector<size_t> path = {0, 1, 2, 3};
REQUIRE_THAT(tsp_cycle_path_length(path, centers), Catch::Matchers::WithinAbs(8000000., 1.));
CHECK(tsp_remove_crossings(path, centers));
REQUIRE(is_permutation(path, centers.size()));
CHECK_THAT(tsp_cycle_path_length(path, centers), Catch::Matchers::WithinAbs(6000000., 1.));
}
TEST_CASE("tsp_rotate_minimize_closing shortens closing edge", "[TSPPostProcessing]") {