Files
OrcaSlicer/tests/libnest2d/test_nfp_placer.cpp
T
Hanif Koh e374956d03 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.
2026-10-04 14:53:22 +08:00

159 lines
6.0 KiB
C++

#include <catch2/catch_all.hpp>
#include "libnest2d/libnest2d.hpp"
#include <vector>
#include <cstddef>
#include "libnest2d/geometry_traits.hpp"
#include "libnest2d/common.hpp"
#include <catch2/catch_message.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include "libnest2d_test_utils.hpp"
#include "libnest2d/backends/libslic3r/geometries.hpp"
#include "libnest2d/nester.hpp"
#include "libnest2d/placers/nfpplacer.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Point.hpp"
using namespace libnest2d;
// NfpPlacer is the No-Fit-Polygon placement engine that Orca's arranger drives
// (via _Nester/FirstFitSelection in Arrange.cpp). These exercise the placer
// directly: pack()/accept() are the core geometric placement primitives.
namespace {
struct NfpPlacerFixture {
using Cfg = NfpPlacer::Config;
Box bin{250000000, 210000000}; // 250 x 210 mm bed at 1e6 scale
NfpPlacer placer_with(Cfg cfg = {}) const {
cfg.parallel = false; // deterministic, single-threaded for tests
NfpPlacer p{bin};
p.configure(cfg);
return p;
}
// pack + accept; returns whether the item was placed.
static bool place(NfpPlacer &p, Item &item) {
auto res = p.pack(item);
if (res) p.accept(res);
return bool(res);
}
// Place every item and REQUIRE each one is packed.
static void place_all(NfpPlacer &p, std::vector<RectangleItem> &items) {
for (size_t i = 0; i < items.size(); ++i) {
INFO("packing item " << i);
REQUIRE(place(p, items[i]));
}
}
// No two items overlap (a shared edge is allowed) and each stays in the bin.
void require_disjoint_in_bin(std::vector<RectangleItem> &items) const {
for (size_t i = 0; i < items.size(); ++i) {
REQUIRE(sl::isInside(items[i].boundingBox(), bin));
for (size_t j = i + 1; j < items.size(); ++j) {
const bool overlaps = Item::intersects(items[i], items[j]) &&
!Item::touches(items[i], items[j]);
INFO("items " << i << " and " << j);
REQUIRE_FALSE(overlaps);
}
}
}
static std::vector<RectangleItem> squares(size_t n, Coord side) {
return std::vector<RectangleItem>(n, RectangleItem{side, side});
}
};
} // namespace
TEST_CASE_METHOD(NfpPlacerFixture, "NfpPlacer places a single item inside the bin", "[Nesting][Placer]") {
// The placer only keeps references to the items it packs and re-reads them
// from finalAlign() in its destructor, so the item must outlive the placer.
RectangleItem item{100000000, 100000000};
NfpPlacer placer = placer_with();
REQUIRE(place(placer, item));
REQUIRE(placer.getItems().size() == 1u);
REQUIRE(sl::isInside(item.boundingBox(), bin));
}
TEST_CASE_METHOD(NfpPlacerFixture, "NfpPlacer rejects an item larger than the bin", "[Nesting][Placer]") {
NfpPlacer placer = placer_with();
RectangleItem big{300000000, 300000000}; // wider and taller than the bin
auto res = placer.pack(big);
REQUIRE_FALSE(bool(res));
REQUIRE(placer.getItems().empty());
}
TEST_CASE_METHOD(NfpPlacerFixture, "NfpPlacer positions the first item for any starting point", "[Nesting][Placer]") {
// setInitialPosition() seeds the first item from the configured starting
// corner; pack() (without accept()) drives that switch for every value.
using A = Cfg::Alignment;
auto start = GENERATE(A::CENTER, A::BOTTOM_LEFT, A::BOTTOM_RIGHT,
A::TOP_LEFT, A::TOP_RIGHT, A::USER_DEFINED, A::DONT_ALIGN);
CAPTURE(int(start));
Cfg cfg;
cfg.starting_point = start;
cfg.best_object_pos = bin.center();
NfpPlacer placer = placer_with(cfg);
RectangleItem item{100000000, 100000000};
auto res = placer.pack(item);
REQUIRE(bool(res));
REQUIRE(sl::isInside(item.boundingBox(), bin));
}
TEST_CASE_METHOD(NfpPlacerFixture, "NfpPlacer packs many items without overlap", "[Nesting][Placer]") {
// Each item is placed against the no-fit polygon of the growing pile.
auto items = squares(GENERATE(2u, 6u, 9u), 60000000);
NfpPlacer placer = placer_with();
place_all(placer, items);
REQUIRE(placer.getItems().size() == items.size());
require_disjoint_in_bin(items);
}
TEST_CASE_METHOD(NfpPlacerFixture, "NfpPlacer evaluates the rotation candidates", "[Nesting][Placer]") {
// The placer re-reads its packed items from finalAlign() in its destructor,
// so the items must outlive the placer — declare them first.
std::vector<RectangleItem> rects = {
{180000000, 40000000}, {180000000, 40000000}, {180000000, 40000000}};
Cfg cfg;
cfg.rotations = {0.0, Pi / 2.0}; // exercise the rotation search loop
NfpPlacer placer = placer_with(cfg);
place_all(placer, rects);
require_disjoint_in_bin(rects);
}
TEST_CASE_METHOD(NfpPlacerFixture, "NfpPlacer's final alignment keeps the pile clear of a fixed obstacle", "[Nesting][Placer]") {
// A preloaded fixed item makes finalAlign's recentring keep the pile clear of
// it instead of dropping it straight onto the bin centre. Box{w,h} centres on
// the origin, so the obstacle sits there too; virtual keeps it in place.
RectangleItem obstacle{80000000, 80000000};
obstacle.translation({-40000000, -40000000}); // 80x80 mm centred in the bin (origin)
obstacle.markAsFixedInBin(0);
obstacle.is_virt_object = true;
auto items = squares(4, 30000000);
{
NfpPlacer placer = placer_with();
NfpPlacer::ItemGroup fixed;
fixed.emplace_back(obstacle);
placer.preload(fixed);
place_all(placer, items);
} // the placer's destructor runs finalAlign, translating the packed items
for (size_t i = 0; i < items.size(); ++i) {
INFO("item " << i);
const bool overlaps = Item::intersects(items[i], obstacle) &&
!Item::touches(items[i], obstacle);
REQUIRE_FALSE(overlaps);
}
}