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
201 lines
8.6 KiB
C++
201 lines
8.6 KiB
C++
#include <catch2/catch_all.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <initializer_list>
|
|
#include "libslic3r/Config.hpp"
|
|
#include "libslic3r/PrintConfig.hpp"
|
|
#include "libslic3r/Point.hpp"
|
|
#include <cstddef>
|
|
#include "libslic3r/libslic3r.h"
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/catch_message.hpp>
|
|
#include "libslic3r/Layer.hpp"
|
|
#include "libslic3r/TriangleMesh.hpp"
|
|
|
|
#include "test_helpers.hpp"
|
|
#include "libslic3r/ExtrusionEntityCollection.hpp"
|
|
#include "libslic3r/Print.hpp"
|
|
|
|
using namespace Slic3r::Test;
|
|
using namespace Slic3r;
|
|
|
|
namespace {
|
|
|
|
// The upper plate overhangs both the lower plate and open air, so branches land on the model and on
|
|
// the bed in the same slice.
|
|
TriangleMesh two_tier_mesh()
|
|
{
|
|
TriangleMesh lower = make_cube(30, 30, 3);
|
|
TriangleMesh column = make_cube(8, 8, 15);
|
|
TriangleMesh upper = make_cube(50, 50, 3);
|
|
// Each part overlaps the one below rather than resting on it; a coplanar join slices ambiguously.
|
|
column.translate(11.f, 11.f, 2.f);
|
|
upper.translate(-10.f, -10.f, 16.f);
|
|
TriangleMesh mesh = lower;
|
|
mesh.merge(column);
|
|
mesh.merge(upper);
|
|
return mesh;
|
|
}
|
|
|
|
TriangleMesh scaled(TestMesh id, float scale)
|
|
{
|
|
TriangleMesh mesh = Slic3r::Test::mesh(id);
|
|
mesh.scale(scale);
|
|
return mesh;
|
|
}
|
|
|
|
// `extra` is applied last, so a caller can add or override any key.
|
|
void slice_with_tree_support(const TriangleMesh &mesh, Slic3r::Print &print, const char *style,
|
|
int threshold_angle = 30, int build_plate_only = 0, int raft_layers = 0,
|
|
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> extra = {})
|
|
{
|
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
config.set_deserialize_strict({
|
|
{ "enable_support", 1 },
|
|
{ "support_type", "tree(auto)" },
|
|
{ "support_style", style },
|
|
{ "support_on_build_plate_only", build_plate_only },
|
|
{ "support_threshold_angle", threshold_angle },
|
|
{ "raft_layers", raft_layers },
|
|
{ "layer_height", 0.2 },
|
|
});
|
|
config.set_deserialize_strict(extra);
|
|
Slic3r::Test::init_and_process_print({ mesh }, print, config);
|
|
}
|
|
|
|
Points support_points(const Slic3r::Print &print)
|
|
{
|
|
Points points;
|
|
for (const SupportLayer *layer : print.objects().front()->support_layers())
|
|
layer->support_fills.collect_points(points);
|
|
return points;
|
|
}
|
|
|
|
size_t support_point_count(const TriangleMesh &mesh, const char *style, int threshold_angle = 30,
|
|
int build_plate_only = 0)
|
|
{
|
|
Slic3r::Print print;
|
|
slice_with_tree_support(mesh, print, style, threshold_angle, build_plate_only);
|
|
return support_points(print).size();
|
|
}
|
|
|
|
// Index of the first differing point, or the common length when they match. An index keeps a
|
|
// failure readable; comparing the vectors themselves dumps thousands of points.
|
|
size_t first_difference(const Points &a, const Points &b)
|
|
{
|
|
const size_t common = std::min(a.size(), b.size());
|
|
for (size_t i = 0; i < common; ++i)
|
|
if (a[i] != b[i])
|
|
return i;
|
|
return common;
|
|
}
|
|
|
|
// Slice `mesh` twice and require an identical support point sequence. Point counts and total
|
|
// length are order insensitive, so the sequence is what a reordering shows up in.
|
|
void sliced_twice_matches(const TriangleMesh &mesh, int build_plate_only, const char *style = "tree_slim",
|
|
std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> extra = {})
|
|
{
|
|
Slic3r::Print first_print, second_print;
|
|
slice_with_tree_support(mesh, first_print, style, 30, build_plate_only, 0, extra);
|
|
slice_with_tree_support(mesh, second_print, style, 30, build_plate_only, 0, extra);
|
|
const Points first = support_points(first_print);
|
|
const Points second = support_points(second_print);
|
|
REQUIRE(first.size() > 1000); // without support the comparison below passes vacuously
|
|
REQUIRE(second.size() == first.size());
|
|
REQUIRE(first_difference(first, second) == first.size());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Tree support is generated for an overhang and not for a plain cube", "[TreeSupport]")
|
|
{
|
|
REQUIRE(support_point_count(scaled(TestMesh::overhang, 2.f), "tree_slim") > 1000);
|
|
REQUIRE(support_point_count(Slic3r::Test::cube(20), "tree_slim") == 0);
|
|
}
|
|
|
|
TEST_CASE("Restricting tree support to the build plate changes what is generated", "[TreeSupport]")
|
|
{
|
|
const TriangleMesh mesh = two_tier_mesh();
|
|
const size_t anywhere = support_point_count(mesh, "tree_slim", 30, 0);
|
|
const size_t plate_only = support_point_count(mesh, "tree_slim", 30, 1);
|
|
REQUIRE(anywhere > 1000);
|
|
REQUIRE(plate_only > 1000);
|
|
// The upper plate overhangs the lower one, so some branches would land on the model.
|
|
REQUIRE(plate_only != anywhere);
|
|
}
|
|
|
|
TEST_CASE("Tree support layers rise monotonically within the layer height limits", "[TreeSupport]")
|
|
{
|
|
Slic3r::Print print;
|
|
slice_with_tree_support(scaled(TestMesh::overhang, 2.f), print, "tree_slim");
|
|
const double nozzle = print.config().nozzle_diameter.values.front();
|
|
|
|
size_t checked = 0;
|
|
double previous = 0;
|
|
bool previous_was_adjacent = false;
|
|
for (const SupportLayer *layer : print.objects().front()->support_layers()) {
|
|
if (layer->print_z <= 0 || layer->height <= 0) {
|
|
// Layers with no nodes are left at zero. Skipping one leaves a hole, so the next pair
|
|
// spans more than one layer and its gap says nothing about the layer height limit.
|
|
previous_was_adjacent = false;
|
|
continue;
|
|
}
|
|
if (previous > 0) {
|
|
CAPTURE(previous, layer->print_z);
|
|
REQUIRE(layer->print_z > previous);
|
|
if (previous_was_adjacent)
|
|
REQUIRE(layer->print_z - previous <= nozzle + EPSILON);
|
|
}
|
|
previous = layer->print_z;
|
|
previous_was_adjacent = true;
|
|
++checked;
|
|
}
|
|
REQUIRE(checked > 10);
|
|
}
|
|
|
|
TEST_CASE("A raft is still generated under tree support", "[TreeSupport]")
|
|
{
|
|
// The mesh supports itself, so a layer count alone passes with no raft at all.
|
|
Slic3r::Print rafted, unrafted;
|
|
slice_with_tree_support(scaled(TestMesh::overhang, 2.f), rafted, "tree_slim", 30, 0, 3);
|
|
slice_with_tree_support(scaled(TestMesh::overhang, 2.f), unrafted, "tree_slim", 30, 0, 0);
|
|
const PrintObject *rafted_object = rafted.objects().front();
|
|
const PrintObject *unrafted_object = unrafted.objects().front();
|
|
REQUIRE(rafted_object->support_layers().size() > unrafted_object->support_layers().size());
|
|
// The raft goes under the object.
|
|
REQUIRE(rafted_object->layers().front()->print_z > unrafted_object->layers().front()->print_z);
|
|
}
|
|
|
|
// drop_nodes() decides the node merges and spawns the next layer's nodes in parallel. Every one of
|
|
// those decisions has to be applied in a fixed order, or the same model gives different branches on
|
|
// each slice.
|
|
TEST_CASE("Tree support toolpaths do not depend on thread scheduling", "[TreeSupport][Regression]")
|
|
{
|
|
// Scaled up so that a layer holds enough nodes for the parallel range to be split. At stock
|
|
// size it stays in one chunk and the order never varies.
|
|
SECTION("overhang") { sliced_twice_matches(scaled(TestMesh::overhang, 2.f), 0); }
|
|
SECTION("bridge with hole") { sliced_twice_matches(scaled(TestMesh::bridge_with_hole, 3.f), 0); }
|
|
// Dropping every branch that cannot reach the bed leaves the survivors dense enough that the
|
|
// neighbour merge fires in bulk.
|
|
SECTION("on the build plate") { sliced_twice_matches(scaled(TestMesh::overhang, 4.f), 1); }
|
|
// Branches resting on the model are what put nodes in a part group other than 0, which is the
|
|
// only way to reach the prune in the second pass. tree_hybrid additionally builds polygon
|
|
// nodes, so it is the only style that exercises the overhang merge.
|
|
SECTION("resting on the model") { sliced_twice_matches(two_tier_mesh(), 0); }
|
|
SECTION("hybrid on the model") { sliced_twice_matches(two_tier_mesh(), 0, "tree_hybrid"); }
|
|
}
|
|
|
|
// Prim breaks equal-distance ties by heap address. A 1 mm branch diameter puts neighbours close
|
|
// enough to tie, and an explicit line width pins max_move_dist, so the moved tie winner reaches
|
|
// the support toolpaths.
|
|
TEST_CASE("Tree support toolpaths do not depend on the MST tie order", "[TreeSupport][Regression]")
|
|
{
|
|
sliced_twice_matches(two_tier_mesh(), 0, "tree_hybrid", {
|
|
{ "tree_support_branch_diameter", 1.0 },
|
|
{ "tree_support_branch_distance", 5.0 },
|
|
{ "tree_support_branch_angle", 40 },
|
|
{ "support_line_width", 0.4 },
|
|
});
|
|
}
|