Extend libnest tests, remove some warnings, faster catch2 compilation.

Also, improve libnest2d::nest interface.
This commit is contained in:
tamasmeszaros 2019-10-07 17:16:40 +02:00
parent 555fcc151d
commit 72ac8d68f0
7 changed files with 175 additions and 129 deletions

View File

@ -49,91 +49,84 @@ using BottomLeftPlacer = placers::_BottomLeftPlacer<PolygonImpl>;
extern template class Nester<NfpPlacer, FirstFitSelection>; extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>; extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template PackGroup Nester<NfpPlacer, FirstFitSelection>::execute( extern template std::size_t Nester<NfpPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator); std::vector<Item>::iterator, std::vector<Item>::iterator);
extern template PackGroup Nester<BottomLeftPlacer, FirstFitSelection>::execute( extern template std::size_t Nester<BottomLeftPlacer, FirstFitSelection>::execute(
std::vector<Item>::iterator, std::vector<Item>::iterator); std::vector<Item>::iterator, std::vector<Item>::iterator);
#endif #endif
template<class Placer = NfpPlacer, template<class Placer = NfpPlacer, class Selector = FirstFitSelection>
class Selector = FirstFitSelection, struct NestConfig {
class Iterator = std::vector<Item>::iterator> typename Placer::Config placer_config;
void nest(Iterator from, Iterator to, typename Selector::Config selector_config;
const typename Placer::BinType& bin, using Placement = typename Placer::Config;
Coord dist = 0, using Selection = typename Selector::Config;
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {}) NestConfig() = default;
{ NestConfig(const typename Placer::Config &cfg) : placer_config{cfg} {}
_Nester<Placer, Selector> nester(bin, dist, pconf, sconf); NestConfig(const typename Selector::Config &cfg) : selector_config{cfg} {}
nester.execute(from, to); NestConfig(const typename Placer::Config & pcfg,
} const typename Selector::Config &scfg)
: placer_config{pcfg}, selector_config{scfg} {}
};
struct NestControl {
ProgressFunction progressfn;
StopCondition stopcond = []{ return false; };
NestControl() = default;
NestControl(ProgressFunction pr) : progressfn{std::move(pr)} {}
NestControl(StopCondition sc) : stopcond{std::move(sc)} {}
NestControl(ProgressFunction pr, StopCondition sc)
: progressfn{std::move(pr)}, stopcond{std::move(sc)}
{}
};
template<class Placer = NfpPlacer, template<class Placer = NfpPlacer,
class Selector = FirstFitSelection, class Selector = FirstFitSelection,
class Iterator = std::vector<Item>::iterator> class Iterator = std::vector<Item>::iterator>
void nest(Iterator from, Iterator to, std::size_t nest(Iterator from, Iterator to,
const typename Placer::BinType& bin, const typename Placer::BinType & bin,
ProgressFunction prg,
StopCondition scond = []() { return false; },
Coord dist = 0, Coord dist = 0,
const typename Placer::Config& pconf = {}, const NestConfig<Placer, Selector> &cfg = {},
const typename Selector::Config& sconf = {}) NestControl ctl = {})
{ {
_Nester<Placer, Selector> nester(bin, dist, pconf, sconf); _Nester<Placer, Selector> nester{bin, dist, cfg.placer_config, cfg.selector_config};
if(prg) nester.progressIndicator(prg); if(ctl.progressfn) nester.progressIndicator(ctl.progressfn);
if(scond) nester.stopCondition(scond); if(ctl.stopcond) nester.stopCondition(ctl.stopcond);
nester.execute(from, to); return nester.execute(from, to);
} }
#ifdef LIBNEST2D_STATIC #ifdef LIBNEST2D_STATIC
extern template class Nester<NfpPlacer, FirstFitSelection>; extern template class Nester<NfpPlacer, FirstFitSelection>;
extern template class Nester<BottomLeftPlacer, FirstFitSelection>; extern template class Nester<BottomLeftPlacer, FirstFitSelection>;
extern template std::size_t nest(std::vector<Item>::iterator from,
extern template void nest(std::vector<Item>::iterator from, std::vector<Item>::iterator from to,
std::vector<Item>::iterator to, const Box & bin,
const Box& bin, Coord dist,
Coord dist = 0, const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
const NfpPlacer::Config& pconf, NestControl ctl);
const FirstFitSelection::Config& sconf); extern template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator from to,
extern template void nest(std::vector<Item>::iterator from, const Box & bin,
std::vector<Item>::iterator to, Coord dist,
const Box& bin, const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,
ProgressFunction prg, NestControl ctl);
StopCondition scond,
Coord dist = 0,
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
#endif #endif
template<class Placer = NfpPlacer, template<class Placer = NfpPlacer,
class Selector = FirstFitSelection, class Selector = FirstFitSelection,
class Container = std::vector<Item>> class Container = std::vector<Item>>
void nest(Container&& cont, std::size_t nest(Container&& cont,
const typename Placer::BinType& bin, const typename Placer::BinType & bin,
Coord dist = 0, Coord dist = 0,
const typename Placer::Config& pconf = {}, const NestConfig<Placer, Selector> &cfg = {},
const typename Selector::Config& sconf = {}) NestControl ctl = {})
{ {
nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, pconf, sconf); return nest<Placer, Selector>(cont.begin(), cont.end(), bin, dist, cfg, ctl);
}
template<class Placer = NfpPlacer,
class Selector = FirstFitSelection,
class Container = std::vector<Item>>
void nest(Container&& cont,
const typename Placer::BinType& bin,
ProgressFunction prg,
StopCondition scond = []() { return false; },
Coord dist = 0,
const typename Placer::Config& pconf = {},
const typename Selector::Config& sconf = {})
{
nest<Placer, Selector>(cont.begin(), cont.end(), bin, prg, scond, dist,
pconf, sconf);
} }
} }

View File

@ -129,7 +129,11 @@ public:
sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {} sh_(sl::create<RawShape>(std::move(contour), std::move(holes))) {}
inline bool isFixed() const noexcept { return fixed_; } inline bool isFixed() const noexcept { return fixed_; }
inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } inline void markAsFixedInBin(int binid)
{
fixed_ = binid >= 0;
binid_ = binid;
}
inline void binId(int idx) { binid_ = idx; } inline void binId(int idx) { binid_ = idx; }
inline int binId() const noexcept { return binid_; } inline int binId() const noexcept { return binid_; }
@ -748,6 +752,7 @@ template<class PlacementStrategy, class SelectionStrategy >
class _Nester { class _Nester {
using TSel = SelectionStrategyLike<SelectionStrategy>; using TSel = SelectionStrategyLike<SelectionStrategy>;
TSel selector_; TSel selector_;
public: public:
using Item = typename PlacementStrategy::Item; using Item = typename PlacementStrategy::Item;
using ShapeType = typename Item::ShapeType; using ShapeType = typename Item::ShapeType;
@ -824,7 +829,7 @@ public:
* the selection algorithm. * the selection algorithm.
*/ */
template<class It> template<class It>
inline ItemIteratorOnly<It, void> execute(It from, It to) inline ItemIteratorOnly<It, size_t> execute(It from, It to)
{ {
auto infl = static_cast<Coord>(std::ceil(min_obj_distance_/2.0)); auto infl = static_cast<Coord>(std::ceil(min_obj_distance_/2.0));
if(infl > 0) std::for_each(from, to, [this, infl](Item& item) { if(infl > 0) std::for_each(from, to, [this, infl](Item& item) {
@ -837,6 +842,8 @@ public:
if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) { if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) {
item.inflate(-infl); item.inflate(-infl);
}); });
return selector_.getResult().size();
} }
/// Set a progress indicator function object for the selector. /// Set a progress indicator function object for the selector.

View File

@ -5,19 +5,17 @@ namespace libnest2d {
template class Nester<NfpPlacer, FirstFitSelection>; template class Nester<NfpPlacer, FirstFitSelection>;
template class Nester<BottomLeftPlacer, FirstFitSelection>; template class Nester<BottomLeftPlacer, FirstFitSelection>;
template PackGroup nest(std::vector<Item>::iterator from, template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to, std::vector<Item>::iterator from to,
const Box& bin, const Box & bin,
Coord dist = 0, Coord dist,
const NfpPlacer::Config& pconf, const NestConfig<NfpPlacer, FirstFitSelection> &cfg,
const FirstFitSelection::Config& sconf); NestControl ctl);
template PackGroup nest(std::vector<Item>::iterator from, template std::size_t nest(std::vector<Item>::iterator from,
std::vector<Item>::iterator to, std::vector<Item>::iterator from to,
const Box& bin, const Box & bin,
ProgressFunction prg, Coord dist,
StopCondition scond, const NestConfig<BottomLeftPlacer, FirstFitSelection> &cfg,
Coord dist = 0, NestControl ctl);
const NfpPlacer::Config& pconf,
const FirstFitSelection::Config& sconf);
} }

View File

@ -375,7 +375,7 @@ public:
for(unsigned idx = 0; idx < fixeditems.size(); ++idx) { for(unsigned idx = 0; idx < fixeditems.size(); ++idx) {
Item& itm = fixeditems[idx]; Item& itm = fixeditems[idx];
itm.markAsFixed(); itm.markAsFixedInBin(0);
} }
m_pck.configure(m_pconf); m_pck.configure(m_pconf);

View File

@ -1571,7 +1571,8 @@ struct Plater::priv
size_t count = 0; // To know how much space to reserve size_t count = 0; // To know how much space to reserve
for (auto obj : model.objects) count += obj->instances.size(); for (auto obj : model.objects) count += obj->instances.size();
m_selected.clear(), m_unselected.clear(); m_selected.clear();
m_unselected.clear();
m_selected.reserve(count + 1 /* for optional wti */); m_selected.reserve(count + 1 /* for optional wti */);
m_unselected.reserve(count + 1 /* for optional wti */); m_unselected.reserve(count + 1 /* for optional wti */);
} }
@ -1589,7 +1590,8 @@ struct Plater::priv
ap.bed_idx = ap.translation.x() / bed_stride(); ap.bed_idx = ap.translation.x() / bed_stride();
ap.setter = [obj, this](const ArrangePolygon &p) { ap.setter = [obj, this](const ArrangePolygon &p) {
if (p.is_arranged()) { if (p.is_arranged()) {
auto t = p.translation; t.x() += p.bed_idx * bed_stride(); auto t = p.translation;
t.x() += p.bed_idx * bed_stride();
obj->apply_arrange_result(t, p.rotation); obj->apply_arrange_result(t, p.rotation);
} }
}; };
@ -1620,7 +1622,8 @@ struct Plater::priv
obj_sel(model.objects.size(), nullptr); obj_sel(model.objects.size(), nullptr);
for (auto &s : plater().get_selection().get_content()) for (auto &s : plater().get_selection().get_content())
if (s.first < int(obj_sel.size())) obj_sel[s.first] = &s.second; if (s.first < int(obj_sel.size()))
obj_sel[size_t(s.first)] = &s.second;
// Go through the objects and check if inside the selection // Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
@ -1630,7 +1633,8 @@ struct Plater::priv
std::vector<bool> inst_sel(mo->instances.size(), false); std::vector<bool> inst_sel(mo->instances.size(), false);
if (instlist) if (instlist)
for (auto inst_id : *instlist) inst_sel[inst_id] = true; for (auto inst_id : *instlist)
inst_sel[size_t(inst_id)] = true;
for (size_t i = 0; i < inst_sel.size(); ++i) { for (size_t i = 0; i < inst_sel.size(); ++i) {
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
@ -2759,9 +2763,8 @@ void Plater::priv::ArrangeJob::process() {
try { try {
arrangement::arrange(m_selected, m_unselected, min_d, bedshape, arrangement::arrange(m_selected, m_unselected, min_d, bedshape,
[this, count](unsigned st) { [this, count](unsigned st) {
if (st > if (st > 0) // will not finalize after last one
0) // will not finalize after last one update_status(int(count - st), arrangestr);
update_status(count - st, arrangestr);
}, },
[this]() { return was_canceled(); }); [this]() { return was_canceled(); });
} catch (std::exception & /*e*/) { } catch (std::exception & /*e*/) {

View File

@ -11,7 +11,7 @@ add_library(Catch2::Catch2 ALIAS Catch2)
include(Catch) include(Catch)
add_library(test_catch2_common INTERFACE) add_library(test_catch2_common INTERFACE)
target_compile_definitions(test_catch2_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)") target_compile_definitions(test_catch2_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)" CATCH_CONFIG_FAST_COMPILE)
target_link_libraries(test_catch2_common INTERFACE Catch2::Catch2) target_link_libraries(test_catch2_common INTERFACE Catch2::Catch2)
add_library(test_common INTERFACE) add_library(test_common INTERFACE)

View File

@ -1,5 +1,7 @@
#define CATCH_CONFIG_MAIN #define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp> #include <catch2/catch.hpp>
#include <catch2/catch_reporter_teamcity.hpp>
#include <fstream> #include <fstream>
#include <libnest2d.h> #include <libnest2d.h>
@ -225,11 +227,11 @@ TEST_CASE("Area", "[Geometry]") {
RectangleItem rect(10, 10); RectangleItem rect(10, 10);
REQUIRE(rect.area() == 100); REQUIRE(rect.area() == Approx(100));
RectangleItem rect2 = {100, 100}; RectangleItem rect2 = {100, 100};
REQUIRE(rect2.area() == 10000); REQUIRE(rect2.area() == Approx(10000));
Item item = { Item item = {
{61, 97}, {61, 97},
@ -288,11 +290,9 @@ TEST_CASE("IsPointInsidePolygon", "[Geometry]") {
// REQUIRE_FALSE(ShapeLike::intersects(s1, s2)); // REQUIRE_FALSE(ShapeLike::intersects(s1, s2));
//} //}
// Simple TEST_CASE, does not use gmock
TEST_CASE("LeftAndDownPolygon", "[Geometry]") TEST_CASE("LeftAndDownPolygon", "[Geometry]")
{ {
using namespace libnest2d; using namespace libnest2d;
using namespace libnest2d;
Box bin(100, 100); Box bin(100, 100);
BottomLeftPlacer placer(bin); BottomLeftPlacer placer(bin);
@ -339,7 +339,6 @@ TEST_CASE("LeftAndDownPolygon", "[Geometry]")
} }
} }
// Simple TEST_CASE, does not use gmock
TEST_CASE("ArrangeRectanglesTight", "[Nesting]") TEST_CASE("ArrangeRectanglesTight", "[Nesting]")
{ {
using namespace libnest2d; using namespace libnest2d;
@ -450,7 +449,7 @@ TEST_CASE("ArrangeRectanglesLoose", "[Nesting]")
return i1.binId() < i2.binId(); return i1.binId() < i2.binId();
}); });
size_t groups = max_group == rects.end() ? 0 : max_group->binId() + 1; auto groups = size_t(max_group == rects.end() ? 0 : max_group->binId() + 1);
REQUIRE(groups == 1u); REQUIRE(groups == 1u);
REQUIRE( REQUIRE(
@ -477,8 +476,6 @@ using namespace libnest2d;
template<long long SCALE = 1, class Bin> template<long long SCALE = 1, class Bin>
void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) { void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin, int idx = 0) {
std::string loc = "out"; std::string loc = "out";
static std::string svg_header = static std::string svg_header =
@ -494,20 +491,20 @@ void exportSVG(std::vector<std::reference_wrapper<Item>>& result, const Bin& bin
if(out.is_open()) { if(out.is_open()) {
out << svg_header; out << svg_header;
Item rbin( RectangleItem(bin.width(), bin.height()) ); Item rbin( RectangleItem(bin.width(), bin.height()) );
for(unsigned i = 0; i < rbin.vertexCount(); i++) { for(unsigned j = 0; j < rbin.vertexCount(); j++) {
auto v = rbin.vertex(i); auto v = rbin.vertex(j);
setY(v, -getY(v)/SCALE + 500 ); setY(v, -getY(v)/SCALE + 500 );
setX(v, getX(v)/SCALE); setX(v, getX(v)/SCALE);
rbin.setVertex(i, v); rbin.setVertex(j, v);
} }
out << shapelike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl; out << shapelike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
for(Item& sh : r) { for(Item& sh : r) {
Item tsh(sh.transformedShape()); Item tsh(sh.transformedShape());
for(unsigned i = 0; i < tsh.vertexCount(); i++) { for(unsigned j = 0; j < tsh.vertexCount(); j++) {
auto v = tsh.vertex(i); auto v = tsh.vertex(j);
setY(v, -getY(v)/SCALE + 500); setY(v, -getY(v)/SCALE + 500);
setX(v, getX(v)/SCALE); setX(v, getX(v)/SCALE);
tsh.setVertex(i, v); tsh.setVertex(j, v);
} }
out << shapelike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl; out << shapelike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
} }
@ -570,7 +567,7 @@ TEST_CASE("convexHull", "[Geometry]") {
REQUIRE(chull.size() == poly.size()); REQUIRE(chull.size() == poly.size());
} }
TEST_CASE("NestPrusaPartsShouldFitIntoTwoBins", "[Nesting]") { TEST_CASE("PrusaPartsShouldFitIntoTwoBins", "[Nesting]") {
// Get the input items and define the bin. // Get the input items and define the bin.
std::vector<Item> input = prusaParts(); std::vector<Item> input = prusaParts();
@ -579,21 +576,15 @@ TEST_CASE("NestPrusaPartsShouldFitIntoTwoBins", "[Nesting]") {
// Do the nesting. Check in each step if the remaining items are less than // Do the nesting. Check in each step if the remaining items are less than
// in the previous step. (Some algorithms can place more items in one step) // in the previous step. (Some algorithms can place more items in one step)
size_t pcount = input.size(); size_t pcount = input.size();
libnest2d::nest(input, bin, [&pcount](unsigned cnt) {
size_t bins = libnest2d::nest(input, bin, 0, {},
ProgressFunction{[&pcount](unsigned cnt) {
REQUIRE(cnt < pcount); REQUIRE(cnt < pcount);
pcount = cnt; pcount = cnt;
}); }});
// Get the number of logical bins: search for the max binId...
auto max_binid_it = std::max_element(input.begin(), input.end(),
[](const Item &i1, const Item &i2) {
return i1.binId() < i2.binId();
});
auto bins = size_t(max_binid_it == input.end() ? 0 :
max_binid_it->binId() + 1);
// For prusa parts, 2 bins should be enough... // For prusa parts, 2 bins should be enough...
REQUIRE(bins > 0u);
REQUIRE(bins <= 2u); REQUIRE(bins <= 2u);
// All parts should be processed by the algorithm // All parts should be processed by the algorithm
@ -617,29 +608,83 @@ TEST_CASE("NestPrusaPartsShouldFitIntoTwoBins", "[Nesting]") {
} }
} }
TEST_CASE("NestEmptyItemShouldBeUntouched", "[Nesting]") { TEST_CASE("EmptyItemShouldBeUntouched", "[Nesting]") {
auto bin = Box(250000000, 210000000); // dummy bin auto bin = Box(250000000, 210000000); // dummy bin
std::vector<Item> items; std::vector<Item> items;
items.emplace_back(Item{}); // Emplace empty item items.emplace_back(Item{}); // Emplace empty item
items.emplace_back(Item{0, 200, 0}); // Emplace zero area item items.emplace_back(Item{0, 200, 0}); // Emplace zero area item
libnest2d::nest(items, bin); size_t bins = libnest2d::nest(items, bin);
REQUIRE(bins == 0u);
for (auto &itm : items) REQUIRE(itm.binId() == BIN_ID_UNSET); for (auto &itm : items) REQUIRE(itm.binId() == BIN_ID_UNSET);
} }
TEST_CASE("NestLargeItemShouldBeUntouched", "[Nesting]") { TEST_CASE("LargeItemShouldBeUntouched", "[Nesting]") {
auto bin = Box(250000000, 210000000); // dummy bin auto bin = Box(250000000, 210000000); // dummy bin
std::vector<Item> items; std::vector<Item> items;
items.emplace_back(RectangleItem{250000001, 210000001}); // Emplace large item items.emplace_back(RectangleItem{250000001, 210000001}); // Emplace large item
libnest2d::nest(items, bin); size_t bins = libnest2d::nest(items, bin);
REQUIRE(bins == 0u);
REQUIRE(items.front().binId() == BIN_ID_UNSET); REQUIRE(items.front().binId() == BIN_ID_UNSET);
} }
TEST_CASE("Items can be preloaded", "[Nesting]") {
auto bin = Box({0, 0}, {250000000, 210000000}); // dummy bin
std::vector<Item> items;
items.reserve(2);
NestConfig<> cfg;
cfg.placer_config.alignment = NestConfig<>::Placement::Alignment::DONT_ALIGN;
items.emplace_back(RectangleItem{10000000, 10000000});
Item &fixed_rect = items.back();
fixed_rect.translate(bin.center());
items.emplace_back(RectangleItem{20000000, 20000000});
Item &movable_rect = items.back();
movable_rect.translate(bin.center());
SECTION("Preloaded Item should be untouched") {
fixed_rect.markAsFixedInBin(0);
size_t bins = libnest2d::nest(items, bin, 0, cfg);
REQUIRE(bins == 1);
REQUIRE(fixed_rect.binId() == 0);
REQUIRE(fixed_rect.translation().X == bin.center().X);
REQUIRE(fixed_rect.translation().Y == bin.center().Y);
REQUIRE(movable_rect.binId() == 0);
REQUIRE(movable_rect.translation().X != bin.center().X);
REQUIRE(movable_rect.translation().Y != bin.center().Y);
}
SECTION("Preloaded Item should not affect free bins") {
fixed_rect.markAsFixedInBin(1);
size_t bins = libnest2d::nest(items, bin, 0, cfg);
REQUIRE(bins == 2);
REQUIRE(fixed_rect.binId() == 1);
REQUIRE(fixed_rect.translation().X == bin.center().X);
REQUIRE(fixed_rect.translation().Y == bin.center().Y);
REQUIRE(movable_rect.binId() == 0);
auto bb = movable_rect.boundingBox();
REQUIRE(bb.center().X == bin.center().X);
REQUIRE(bb.center().Y == bin.center().Y);
}
}
namespace { namespace {
struct ItemPair { struct ItemPair {
@ -970,7 +1015,7 @@ TEST_CASE("mergePileWithPolygon", "[Geometry]") {
RectangleItem ref(45, 15); RectangleItem ref(45, 15);
REQUIRE(shapelike::area(result.front()) == ref.area()); REQUIRE(shapelike::area(result.front()) == Approx(ref.area()));
} }
namespace { namespace {