mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-05 23:01:17 +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.
280 lines
7.6 KiB
C++
280 lines
7.6 KiB
C++
#include <algorithm>
|
|
#include <catch2/catch_all.hpp>
|
|
#include "libnest2d/libnest2d.hpp"
|
|
#include <vector>
|
|
#include "libnest2d/geometry_traits.hpp"
|
|
#include "libnest2d/backends/libslic3r/geometries.hpp"
|
|
#include "libnest2d/geometry_traits_nfp.hpp"
|
|
#include "libnest2d/utils/boost_alg.hpp"
|
|
#include "libnest2d/placers/nfpplacer.hpp"
|
|
#include <iterator>
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/matchers/catch_matchers.hpp>
|
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
|
#include "libnest2d_test_utils.hpp"
|
|
|
|
using namespace libnest2d;
|
|
|
|
namespace {
|
|
|
|
struct ItemPair {
|
|
Item orbiter;
|
|
Item stationary;
|
|
};
|
|
|
|
std::vector<ItemPair> nfp_testdata = {
|
|
{
|
|
{
|
|
{80, 50},
|
|
{100, 70},
|
|
{120, 50}
|
|
},
|
|
{
|
|
{10, 10},
|
|
{10, 40},
|
|
{40, 40},
|
|
{40, 10}
|
|
}
|
|
},
|
|
{
|
|
{
|
|
{80, 50},
|
|
{60, 70},
|
|
{80, 90},
|
|
{120, 90},
|
|
{140, 70},
|
|
{120, 50}
|
|
},
|
|
{
|
|
{10, 10},
|
|
{10, 40},
|
|
{40, 40},
|
|
{40, 10}
|
|
}
|
|
},
|
|
{
|
|
{
|
|
{40, 10},
|
|
{30, 10},
|
|
{20, 20},
|
|
{20, 30},
|
|
{30, 40},
|
|
{40, 40},
|
|
{50, 30},
|
|
{50, 20}
|
|
},
|
|
{
|
|
{80, 0},
|
|
{80, 30},
|
|
{110, 30},
|
|
{110, 0}
|
|
}
|
|
},
|
|
{
|
|
{
|
|
{117, 107},
|
|
{118, 109},
|
|
{120, 112},
|
|
{122, 113},
|
|
{128, 113},
|
|
{130, 112},
|
|
{132, 109},
|
|
{133, 107},
|
|
{133, 103},
|
|
{132, 101},
|
|
{130, 98},
|
|
{128, 97},
|
|
{122, 97},
|
|
{120, 98},
|
|
{118, 101},
|
|
{117, 103}
|
|
},
|
|
{
|
|
{102, 116},
|
|
{111, 126},
|
|
{114, 126},
|
|
{144, 106},
|
|
{148, 100},
|
|
{148, 85},
|
|
{147, 84},
|
|
{102, 84}
|
|
}
|
|
},
|
|
{
|
|
{
|
|
{99, 122},
|
|
{108, 140},
|
|
{110, 142},
|
|
{139, 142},
|
|
{151, 122},
|
|
{151, 102},
|
|
{142, 70},
|
|
{139, 68},
|
|
{111, 68},
|
|
{108, 70},
|
|
{99, 102}
|
|
},
|
|
{
|
|
{107, 124},
|
|
{128, 125},
|
|
{133, 125},
|
|
{136, 124},
|
|
{140, 121},
|
|
{142, 119},
|
|
{143, 116},
|
|
{143, 109},
|
|
{141, 93},
|
|
{139, 89},
|
|
{136, 86},
|
|
{134, 85},
|
|
{108, 85},
|
|
{107, 86}
|
|
}
|
|
},
|
|
{
|
|
{
|
|
{91, 100},
|
|
{94, 144},
|
|
{117, 153},
|
|
{118, 153},
|
|
{159, 112},
|
|
{159, 110},
|
|
{156, 66},
|
|
{133, 57},
|
|
{132, 57},
|
|
{91, 98}
|
|
},
|
|
{
|
|
{101, 90},
|
|
{103, 98},
|
|
{107, 113},
|
|
{114, 125},
|
|
{115, 126},
|
|
{135, 126},
|
|
{136, 125},
|
|
{144, 114},
|
|
{149, 90},
|
|
{149, 89},
|
|
{148, 87},
|
|
{145, 84},
|
|
{105, 84},
|
|
{102, 87},
|
|
{101, 89}
|
|
}
|
|
}
|
|
};
|
|
|
|
// libnest2d's vertex order depends on the backend; normalise to clockwise.
|
|
Item reversed_if_ccw(Item it) {
|
|
if (!is_clockwise<PolygonImpl>()) {
|
|
auto raw = it.rawShape();
|
|
std::reverse(sl::begin(raw), sl::end(raw));
|
|
it = Item{raw};
|
|
}
|
|
return it;
|
|
}
|
|
|
|
// Sliding `orbiter` around `stationary` along their no-fit polygon must keep the
|
|
// two shapes touching at every NFP vertex, and `stationary` must lie inside the
|
|
// resulting inner-fit polygon.
|
|
template<nfp::NfpLevel lvl, Coord SCALE>
|
|
void check_nfp(const std::vector<ItemPair> &testdata) {
|
|
auto check_pair = [](Item orbiter, Item stationary) {
|
|
orbiter.translate({210 * SCALE, 0});
|
|
|
|
auto &&nfp = nfp::noFitPolygon<lvl>(stationary.rawShape(), orbiter.transformedShape());
|
|
placers::correctNfpPosition(nfp, stationary, orbiter);
|
|
REQUIRE(shapelike::isValid(nfp.first).first);
|
|
|
|
Item infp(nfp.first);
|
|
REQUIRE(stationary.isInside(infp));
|
|
|
|
auto vo = nfp::referenceVertex(orbiter.transformedShape());
|
|
for (auto v : infp) {
|
|
Item moved = orbiter;
|
|
moved.translate({getX(v) - getX(vo), getY(v) - getY(vo)});
|
|
REQUIRE(Item::touches(moved, stationary));
|
|
}
|
|
};
|
|
|
|
for (const ItemPair &td : testdata) {
|
|
check_pair(reversed_if_ccw(td.orbiter), reversed_if_ccw(td.stationary));
|
|
check_pair(reversed_if_ccw(td.stationary), reversed_if_ccw(td.orbiter));
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("No-fit polygon of convex shapes keeps the items touching", "[Geometry][NFP]") {
|
|
check_nfp<nfp::NfpLevel::CONVEX_ONLY, 1>(nfp_testdata);
|
|
}
|
|
|
|
TEST_CASE("BottomLeftPlacer left and down polygons", "[Geometry][NFP]") {
|
|
Box bin(100, 100);
|
|
BottomLeftPlacer placer(bin);
|
|
|
|
PathImpl pitem = {{70, 75}, {88, 60}, {65, 50}, {60, 30}, {80, 20},
|
|
{42, 20}, {35, 35}, {35, 55}, {40, 75}};
|
|
PathImpl left_control = {{40, 75}, {35, 55}, {35, 35}, {42, 20}, {0, 20}, {0, 75}};
|
|
PathImpl down_control = {{88, 60}, {88, 0}, {35, 0}, {35, 35},
|
|
{42, 20}, {80, 20}, {60, 30}, {65, 50}};
|
|
|
|
if constexpr (!is_clockwise<PathImpl>()) {
|
|
std::reverse(sl::begin(pitem), sl::end(pitem));
|
|
std::reverse(sl::begin(left_control), sl::end(left_control));
|
|
std::reverse(sl::begin(down_control), sl::end(down_control));
|
|
}
|
|
if constexpr (ClosureTypeV<PathImpl> == Closure::CLOSED) {
|
|
sl::addVertex(pitem, sl::front(pitem));
|
|
sl::addVertex(left_control, sl::front(left_control));
|
|
sl::addVertex(down_control, sl::front(down_control));
|
|
}
|
|
|
|
auto require_same_vertices = [](const Item &got, const Item &expected) {
|
|
REQUIRE(shapelike::isValid(got.rawShape()).first);
|
|
REQUIRE(got.vertexCount() == expected.vertexCount());
|
|
for (unsigned long i = 0; i < expected.vertexCount(); ++i) {
|
|
REQUIRE(getX(got.vertex(i)) == getX(expected.vertex(i)));
|
|
REQUIRE(getY(got.vertex(i)) == getY(expected.vertex(i)));
|
|
}
|
|
};
|
|
|
|
Item item{pitem};
|
|
require_same_vertices(Item(placer.leftPoly(item)), Item{left_control});
|
|
require_same_vertices(Item(placer.downPoly(item)), Item{down_control});
|
|
}
|
|
|
|
TEST_CASE("EdgeCache maps a parameter to a contour point", "[Geometry][NFP]") {
|
|
RectangleItem input(10, 10);
|
|
placers::EdgeCache<PolygonImpl> ecache(input);
|
|
|
|
auto first = *input.begin();
|
|
REQUIRE(getX(first) == getX(ecache.coords(0)));
|
|
REQUIRE(getY(first) == getY(ecache.coords(0)));
|
|
|
|
auto last = *std::prev(input.end());
|
|
REQUIRE(getX(last) == getX(ecache.coords(1.0)));
|
|
REQUIRE(getY(last) == getY(ecache.coords(1.0)));
|
|
|
|
for (int i = 0; i <= 100; ++i)
|
|
REQUIRE(shapelike::touches(ecache.coords(i * 0.01), input.transformedShape()));
|
|
}
|
|
|
|
TEST_CASE("Merging a pile with a polygon", "[Geometry][NFP]") {
|
|
RectangleItem rect1(10, 15), rect2(15, 15), rect3(20, 15);
|
|
rect2.translate({10, 0});
|
|
rect3.translate({25, 0});
|
|
|
|
TMultiShape<PolygonImpl> pile;
|
|
pile.push_back(rect1.transformedShape());
|
|
pile.push_back(rect2.transformedShape());
|
|
|
|
auto result = nfp::merge(pile, rect3.transformedShape());
|
|
REQUIRE(result.size() == 1); // the three abutting rectangles merge into one
|
|
|
|
RectangleItem ref(45, 15);
|
|
REQUIRE_THAT(shapelike::area(result.front()),
|
|
Catch::Matchers::WithinRel(ref.area(), 1e-9));
|
|
}
|