mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-05 23:01:17 +00:00
* Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced Generated with include-what-you-use and applied conservatively. Only OrcaSlicer's own headers, the ones under src/ and tests/, are removed or forward-declared; standard-library and third-party includes are left alone. An include is removed only when both the Release and the Debug configuration leave it unused, never from inside a conditional block, and never from a file with platform-specific blocks, which only gain includes. Files whose only use of a header sits behind a feature or debug macro (libvgcode's OpenGL ES and marker code, the ARACHNE/TESTS_EXPORT_SVGS debug output) keep their includes. clonable_ptr.hpp gains #pragma once; it had no include guard and was only safe while Config.hpp was its sole includer. * Remove Unused Project Includes From Files With Platform-Specific Code A Linux include-what-you-use run cannot see the code inside _WIN32, __APPLE__ or __linux__ blocks, so its verdict is only taken where nothing the removed header declares, directly or through what it includes, is named inside those blocks. Removals also have to hold in both the Release and Debug configuration and never touch a line inside a conditional block. * Restore the libslic3r Precompiled Header and Direct Includes Lost in the Platform Pass The platform-file pass treated pchheader.hpp as an ordinary header and emptied it, and left GUI_Preview.hpp and 14 other files relying on headers they no longer reached directly. * Restore MainFrame.hpp in ParamsDialog.cpp for the Windows-Only Reparent Call * Include Headers That Files Reached Through Ones the Cleanup Removed * Drop Includes Duplicated by the Cleanup or by Main's Own Additions * Leave PreciseSeam.cpp as Main Has It After the Precise Seam Rework
442 lines
16 KiB
C++
442 lines
16 KiB
C++
// Print-object ordering strategies: implementation.
|
|
// Consolidates TSP post-processing, Snake, and Best-of-Strategies.
|
|
|
|
#include "OrderingStrategies.hpp"
|
|
#include "../Geometry.hpp"
|
|
#include "../ShortestPath.hpp"
|
|
#include "libslic3r/Point.hpp"
|
|
#include "libslic3r/PrintConfig.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <numeric>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include "libslic3r/Print.hpp"
|
|
|
|
namespace Slic3r {
|
|
|
|
/* ====================================================================
|
|
* TSP post-processing utilities
|
|
* ==================================================================== */
|
|
|
|
bool tsp_2opt_improve(std::vector<size_t>& path, const Points& centers, int max_passes)
|
|
{
|
|
size_t pn = path.size();
|
|
if (pn <= 2) return false;
|
|
|
|
// Pre-compute edge lengths once per pass to avoid redundant norm() calls.
|
|
auto recompute_edges = [&]() {
|
|
std::vector<double> el(pn);
|
|
for (size_t i = 0; i < pn; ++i) {
|
|
size_t ni = (i + 1) % pn;
|
|
el[i] = (centers[path[i]].cast<double>() - centers[path[ni]].cast<double>()).norm();
|
|
}
|
|
return el;
|
|
};
|
|
std::vector<double> el = recompute_edges();
|
|
|
|
// Pre-compute squared edge lengths for early rejection in the inner loop.
|
|
auto recompute_edges_sq = [&]() {
|
|
std::vector<double> elsq(pn);
|
|
for (size_t i = 0; i < pn; ++i) {
|
|
size_t ni = (i + 1) % pn;
|
|
elsq[i] = (centers[path[i]].cast<double>() - centers[path[ni]].cast<double>()).squaredNorm();
|
|
}
|
|
return elsq;
|
|
};
|
|
std::vector<double> elsq = recompute_edges_sq();
|
|
|
|
bool improved = false;
|
|
for (int pass = 0; max_passes <= 0 || pass < max_passes; ++pass) {
|
|
size_t best_i = pn, best_j = pn;
|
|
double best_gain = 0;
|
|
|
|
for (size_t i = 0; i < pn; ++i) {
|
|
const Vec2d& pi = centers[path[i]].cast<double>();
|
|
const Vec2d& p_in = centers[path[(i + 1) % pn]].cast<double>();
|
|
double d_i = el[i];
|
|
double d_i_sq = elsq[i];
|
|
|
|
for (size_t j = i + 2; j < pn; ++j) {
|
|
size_t j_next = (j + 1) % pn;
|
|
// Skip the swap that would reverse the entire cycle (removes both
|
|
// edges (0,1) and (pn-1,0), equivalent to traversing the cycle backwards).
|
|
if (i == 0 && j_next == 0) continue;
|
|
|
|
const Vec2d& pj = centers[path[j]].cast<double>();
|
|
const Vec2d& p_jn = centers[path[j_next]].cast<double>();
|
|
double d_j = el[j];
|
|
|
|
// Early rejection using squared distances (avoids 2 sqrt calls).
|
|
double new_a_sq = (pj - pi).squaredNorm();
|
|
double new_b_sq = (p_jn - p_in).squaredNorm();
|
|
if (new_a_sq >= d_i_sq && new_b_sq >= elsq[j]) continue;
|
|
|
|
double new_a = std::sqrt(new_a_sq);
|
|
double new_b = std::sqrt(new_b_sq);
|
|
double gain = d_i + d_j - new_a - new_b;
|
|
|
|
if (gain > best_gain) {
|
|
best_gain = gain;
|
|
best_i = i; best_j = j;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (best_i == pn) break;
|
|
improved = true;
|
|
// Reverse the best swap segment
|
|
std::reverse(path.begin() + best_i + 1, path.begin() + best_j + 1);
|
|
|
|
// Recompute edge lengths after reversal
|
|
el = recompute_edges();
|
|
elsq = recompute_edges_sq();
|
|
}
|
|
return improved;
|
|
}
|
|
|
|
// Fast bounding-box overlap test (rejects most non-intersecting pairs).
|
|
static inline bool bboxes_overlap(const Point& a, const Point& b, const Point& c, const Point& d)
|
|
{
|
|
return !(std::max(a.x(), b.x()) < std::min(c.x(), d.x()) ||
|
|
std::max(c.x(), d.x()) < std::min(a.x(), b.x()) ||
|
|
std::max(a.y(), b.y()) < std::min(c.y(), d.y()) ||
|
|
std::max(c.y(), d.y()) < std::min(a.y(), b.y()));
|
|
}
|
|
|
|
bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
|
|
{
|
|
size_t pn = path.size();
|
|
if (pn <= 3) return false;
|
|
|
|
// Treat path as a cycle: include the closing edge (pn-1 -> 0), consistent with the other
|
|
// 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.
|
|
auto find_crossing = [&]() -> std::pair<size_t, size_t> {
|
|
for (size_t i = 0; i < n_edges; ++i) {
|
|
const Point& ai = centers[path[i]];
|
|
const Point& bi = centers[path[(i + 1) % pn]];
|
|
|
|
for (size_t j = i + 2; j < n_edges; ++j) {
|
|
// Skip the (0, pn-1) pair: edges (0,1) and (pn-1,0) share node 0.
|
|
if (i == 0 && j == pn - 1) continue;
|
|
|
|
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))
|
|
return {i, j};
|
|
}
|
|
}
|
|
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
|
|
};
|
|
|
|
// 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);
|
|
bool improved = false;
|
|
while (max_iters-- > 0) {
|
|
auto [ci, cj] = find_crossing();
|
|
if (ci == std::numeric_limits<size_t>::max()) break;
|
|
improved = true;
|
|
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
|
|
}
|
|
return improved;
|
|
}
|
|
|
|
void tsp_rotate_minimize_closing(std::vector<size_t>& path, const Points& centers)
|
|
{
|
|
size_t pn = path.size();
|
|
size_t best_start = 0;
|
|
double best_closing2 = std::numeric_limits<double>::max();
|
|
for (size_t start = 0; start < pn; ++start) {
|
|
size_t last = (start + pn - 1) % pn;
|
|
double d2 = (centers[path[start]].cast<double>() - centers[path[last]].cast<double>()).squaredNorm();
|
|
if (d2 < best_closing2) { best_closing2 = d2; best_start = start; }
|
|
}
|
|
std::rotate(path.begin(), path.begin() + best_start, path.end());
|
|
}
|
|
|
|
/* ====================================================================
|
|
* Snake ordering
|
|
* ==================================================================== */
|
|
|
|
struct SnakeRow { double avg_y; std::vector<size_t> indices; };
|
|
|
|
// --- Row threshold computation ---
|
|
// Extract unique Y values and use the median gap between them to determine
|
|
// the row threshold.
|
|
static double compute_row_threshold(const std::vector<double>& sorted_ys,
|
|
double y_min, double y_max,
|
|
size_t n,
|
|
double fraction_of_y_range,
|
|
double min_threshold_um)
|
|
{
|
|
constexpr double MIN_GAP_FILTER = 1.0; // ignore sub-micron gaps (coord_t = 1/100mm)
|
|
|
|
// Extract unique Y values
|
|
std::vector<double> unique_ys;
|
|
unique_ys.reserve(sorted_ys.size());
|
|
unique_ys.push_back(sorted_ys[0]);
|
|
for (size_t i = 1; i < sorted_ys.size(); ++i) {
|
|
if (sorted_ys[i] - sorted_ys[i - 1] > MIN_GAP_FILTER)
|
|
unique_ys.push_back(sorted_ys[i]);
|
|
}
|
|
|
|
double fallback_threshold = (y_max - y_min) * fraction_of_y_range;
|
|
if (unique_ys.size() <= 1) {
|
|
return std::max(fallback_threshold, min_threshold_um);
|
|
}
|
|
|
|
// Compute gaps between consecutive unique Y values
|
|
std::vector<double> gaps;
|
|
gaps.reserve(unique_ys.size() - 1);
|
|
for (size_t i = 1; i < unique_ys.size(); ++i)
|
|
gaps.push_back(unique_ys[i] - unique_ys[i - 1]);
|
|
|
|
if (gaps.empty()) {
|
|
return std::max(fallback_threshold, min_threshold_um);
|
|
}
|
|
|
|
// Sort gaps to find the median
|
|
std::sort(gaps.begin(), gaps.end());
|
|
double median_gap = gaps[gaps.size() / 2];
|
|
double min_gap = gaps.front();
|
|
|
|
// Threshold: half the gap between consecutive unique Y values.
|
|
double threshold = (median_gap < min_gap * 1.5) ? min_gap * 0.5 : median_gap * 0.5;
|
|
|
|
bool has_row_structure;
|
|
if (unique_ys.size() * 2 <= n) {
|
|
has_row_structure = true;
|
|
} else {
|
|
// Single-column or sparse: uniform gaps indicate a deliberate grid
|
|
double max_gap = *std::max_element(gaps.begin(), gaps.end());
|
|
has_row_structure = (max_gap < min_gap * 2.0);
|
|
}
|
|
|
|
if (has_row_structure) {
|
|
// For grid-like data, use the gap-based threshold directly.
|
|
return threshold;
|
|
}
|
|
|
|
return std::max(fallback_threshold, min_threshold_um);
|
|
}
|
|
|
|
// --- Row grouping ---
|
|
// Bin points into rows by quantising Y / threshold
|
|
static std::vector<SnakeRow> group_into_rows(const Points& centers, double row_threshold)
|
|
{
|
|
size_t n = centers.size();
|
|
std::unordered_map<int64_t, std::vector<size_t>> row_map;
|
|
for (size_t i = 0; i < n; ++i) {
|
|
int64_t y_key = static_cast<int64_t>(std::floor(static_cast<double>(centers[i].y()) / row_threshold));
|
|
row_map[y_key].push_back(i);
|
|
}
|
|
|
|
std::vector<SnakeRow> rows;
|
|
rows.reserve(row_map.size());
|
|
for (auto& [key, indices] : row_map) {
|
|
double avg_y = std::accumulate(indices.begin(), indices.end(), 0.0,
|
|
[&](double acc, size_t idx) { return acc + static_cast<double>(centers[idx].y()); })
|
|
/ indices.size();
|
|
rows.push_back({avg_y, std::move(indices)});
|
|
}
|
|
|
|
std::sort(rows.begin(), rows.end(),
|
|
[](const SnakeRow& a, const SnakeRow& b) { return a.avg_y < b.avg_y; });
|
|
|
|
return rows;
|
|
}
|
|
|
|
// Sort each row by X and greedily pick the direction (left->right or right->left)
|
|
// that minimises the transition distance from the previous row's endpoint.
|
|
static std::vector<size_t> build_serpentine_path(const Points& centers,
|
|
std::vector<SnakeRow>& rows)
|
|
{
|
|
std::vector<size_t> path;
|
|
path.reserve(centers.size());
|
|
|
|
for (size_t ri = 0; ri < rows.size(); ++ri) {
|
|
auto& row = rows[ri].indices;
|
|
std::sort(row.begin(), row.end(),
|
|
[&](size_t a, size_t b) { return centers[a].x() < centers[b].x(); });
|
|
|
|
if (ri == 0) {
|
|
path.insert(path.end(), row.begin(), row.end());
|
|
} else {
|
|
const Point& prev_end = centers[path.back()];
|
|
double dist_to_left = (prev_end.cast<double>() - centers[row.front()].cast<double>()).squaredNorm();
|
|
double dist_to_right = (prev_end.cast<double>() - centers[row.back()].cast<double>()).squaredNorm();
|
|
|
|
if (dist_to_left <= dist_to_right)
|
|
path.insert(path.end(), row.begin(), row.end());
|
|
else
|
|
path.insert(path.end(), row.rbegin(), row.rend());
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
// Row-based serpentine traversal: detect rows, bin points, snake through them.
|
|
static std::vector<size_t> row_serpentine_path(const Points& centers,
|
|
double fraction_of_y_range = 0.02,
|
|
double min_threshold_um = 1e4)
|
|
{
|
|
if (centers.empty()) return {};
|
|
|
|
size_t n = centers.size();
|
|
|
|
// Collect and sort Y coordinates.
|
|
std::vector<double> sorted_ys;
|
|
sorted_ys.reserve(n);
|
|
for (const auto& p : centers) sorted_ys.push_back(static_cast<double>(p.y()));
|
|
std::sort(sorted_ys.begin(), sorted_ys.end());
|
|
|
|
auto [ymin, ymax] = std::minmax_element(sorted_ys.begin(), sorted_ys.end());
|
|
double y_min = *ymin, y_max = *ymax;
|
|
|
|
double row_threshold = compute_row_threshold(sorted_ys, y_min, y_max, n,
|
|
fraction_of_y_range, min_threshold_um);
|
|
|
|
auto rows = group_into_rows(centers, row_threshold);
|
|
return build_serpentine_path(centers, rows);
|
|
}
|
|
|
|
std::vector<size_t> snake_core(const Points& centers)
|
|
{
|
|
if (centers.empty()) return {};
|
|
|
|
std::vector<size_t> path = row_serpentine_path(centers);
|
|
|
|
for (int iter = 0; iter < 3; ++iter) {
|
|
bool improved = tsp_2opt_improve(path, centers);
|
|
improved |= tsp_remove_crossings(path, centers);
|
|
if (!improved) break;
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
std::vector<const PrintInstance*> chain_print_object_instances_snake(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
|
{
|
|
return chain_instances_with_core(print_objects, start_near, snake_core);
|
|
}
|
|
|
|
std::vector<const PrintInstance*> chain_print_object_instances_snake(const Print& print)
|
|
{
|
|
return chain_print_object_instances_snake(print.objects().vector(), nullptr);
|
|
}
|
|
|
|
/* ====================================================================
|
|
* Best-of-strategies meta-strategy
|
|
* ==================================================================== */
|
|
|
|
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const std::vector<const PrintObject*>& print_objects, const Point* start_near)
|
|
{
|
|
if (print_objects.empty())
|
|
return {};
|
|
|
|
// Run all strategies.
|
|
std::vector<std::vector<const PrintInstance*>> candidates;
|
|
candidates.push_back(chain_print_object_instances(print_objects, start_near));
|
|
candidates.push_back(chain_print_object_instances_snake(print_objects, start_near));
|
|
|
|
// Compute metrics for each candidate.
|
|
struct Candidate { double total_len; double max_edge; };
|
|
std::vector<Candidate> metrics;
|
|
metrics.reserve(candidates.size());
|
|
|
|
for (size_t i = 0; i < candidates.size(); ++i) {
|
|
double total = 0.0;
|
|
double mx = 0.0;
|
|
for (size_t j = 0; j < candidates[i].size(); ++j) {
|
|
size_t k = (j + 1) % candidates[i].size();
|
|
double d = (candidates[i][j]->shift.cast<double>() - candidates[i][k]->shift.cast<double>()).norm();
|
|
total += d;
|
|
if (d > mx) mx = d;
|
|
}
|
|
metrics.push_back({total, mx});
|
|
}
|
|
|
|
// Pick shortest total path; tiebreak on smallest max edge.
|
|
auto best_it = std::min_element(metrics.begin(), metrics.end(),
|
|
[](const Candidate& a, const Candidate& b) {
|
|
return a.total_len < b.total_len ||
|
|
(a.total_len == b.total_len && a.max_edge < b.max_edge);
|
|
});
|
|
size_t best = static_cast<size_t>(std::distance(metrics.begin(), best_it));
|
|
|
|
return candidates[best];
|
|
}
|
|
|
|
std::vector<const PrintInstance*> chain_print_object_instances_best_of(const Print& print)
|
|
{
|
|
return chain_print_object_instances_best_of(print.objects().vector(), nullptr);
|
|
}
|
|
|
|
/* ====================================================================
|
|
* Island-level ordering entry point
|
|
* ==================================================================== */
|
|
|
|
std::vector<size_t> order_points_with_strategy(const Points& points, PrintOrder print_order, const Point* start_near)
|
|
{
|
|
if (points.empty())
|
|
return {};
|
|
|
|
if (print_order != PrintOrder::Snake && print_order != PrintOrder::BestOfStrategies)
|
|
// Nearest neighbor + post-processing; honours start_near natively.
|
|
return chain_points_with_postprocessing(points, start_near);
|
|
|
|
auto run_snake = [&points, start_near]() {
|
|
std::vector<size_t> path = snake_core(points);
|
|
if (start_near != nullptr && !path.empty()) {
|
|
// Start the cycle at the point closest to start_near.
|
|
size_t best_start = 0;
|
|
double best_d2 = std::numeric_limits<double>::max();
|
|
for (size_t k = 0; k < points.size(); ++k) {
|
|
double d2 = (points[k].cast<double>() - start_near->cast<double>()).squaredNorm();
|
|
if (d2 < best_d2) { best_d2 = d2; best_start = k; }
|
|
}
|
|
auto it = std::find(path.begin(), path.end(), best_start);
|
|
if (it != path.begin() && it != path.end())
|
|
std::rotate(path.begin(), it, path.end());
|
|
} else {
|
|
tsp_rotate_minimize_closing(path, points);
|
|
}
|
|
return path;
|
|
};
|
|
|
|
if (print_order == PrintOrder::Snake)
|
|
return run_snake();
|
|
|
|
// Best-of: pick the shortest total cycle; tiebreak on smallest max edge.
|
|
std::vector<std::vector<size_t>> candidates;
|
|
candidates.emplace_back(chain_points_with_postprocessing(points, start_near));
|
|
candidates.emplace_back(run_snake());
|
|
|
|
size_t best = 0;
|
|
double best_len = std::numeric_limits<double>::max();
|
|
double best_edge = std::numeric_limits<double>::max();
|
|
for (size_t i = 0; i < candidates.size(); ++i) {
|
|
double len = tsp_cycle_path_length(candidates[i], points);
|
|
double edge = tsp_max_edge_length(candidates[i], points);
|
|
if (len < best_len || (len == best_len && edge < best_edge)) {
|
|
best_len = len; best_edge = edge; best = i;
|
|
}
|
|
}
|
|
return candidates[best];
|
|
}
|
|
|
|
} // namespace Slic3r
|