mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-04 14:20:58 +00:00
* Ignore Clipper, libpng, mcut and Boost.Polygon Internals in clang-tidy Each only works through a wrapper or umbrella header: libslic3r/clipper.hpp or clipper_z.hpp configure Clipper before including it, png.h pulls in libpng's config headers, and Boost.Polygon's headers only compile through polygon.hpp or voronoi.hpp. * Ignore minilzo's Config Headers in clang-tidy lzoconf.h and lzodefs.h are internal to minilzo.h, which is what the code includes. * Add Missing Includes Across the Remaining Sources and Tests Covers src/slic3r/Utils, src/slic3r/plugin, src/slic3r/Config, src/libvgcode, src/dev-utils, src/OrcaSlicer.cpp and tests/, the directories left after src/slic3r/GUI and src/libslic3r. Generated with clang-tidy misc-include-cleaner. libvgcode's own headers are included by relative path as in the rest of that library, and Catch2 and pybind11 with angle brackets as elsewhere in the repo. * Make the GUI and Test Headers Compile on Their Own Each now includes, or forward-declares, what it uses instead of relying on what its includers happened to include first. Headers that only compile on one platform, or that nothing built includes, are left alone. * Keep Windows and nanosvg Setup Ahead of the Added Includes OrcaSlicer.cpp and several tests set _WIN32_WINNT, WIN32_LEAN_AND_MEAN or NOMINMAX before including Windows.h, and the profile validator defines NANOSVG_IMPLEMENTATION before any libslic3r header. The added includes had landed above those blocks, which broke the Windows build. * Add the GUI Includes the First Pass Missed Covers headers that only became editable once they compiled on their own, and wx symbols whose suggested header changed as the clang-tidy ignore list grew after the src/slic3r/GUI pass. * Keep the Added Test Includes Below the NOMINMAX Guard test_marchingsquares.cpp and test_texture_displacement.cpp had includes inside #ifndef NOMINMAX, which the tests inherit as defined on Windows from libslic3r, so those were skipped there. .clang-tidy also ignores the MSVC STL and UCRT internals, Boost.Multiprecision's fwd.hpp and CPython's Windows include directory, as in #16068.
199 lines
8.5 KiB
C++
199 lines
8.5 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"
|
|
|
|
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 },
|
|
});
|
|
}
|