diff --git a/src/libnest2d/include/libnest2d.h b/src/libnest2d/include/libnest2d.h index 4661b4574..76b133f4b 100644 --- a/src/libnest2d/include/libnest2d.h +++ b/src/libnest2d/include/libnest2d.h @@ -49,91 +49,84 @@ using BottomLeftPlacer = placers::_BottomLeftPlacer; extern template class Nester; extern template class Nester; -extern template PackGroup Nester::execute( +extern template std::size_t Nester::execute( std::vector::iterator, std::vector::iterator); -extern template PackGroup Nester::execute( +extern template std::size_t Nester::execute( std::vector::iterator, std::vector::iterator); #endif -template::iterator> -void nest(Iterator from, Iterator to, - const typename Placer::BinType& bin, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) -{ - _Nester nester(bin, dist, pconf, sconf); - nester.execute(from, to); -} +template +struct NestConfig { + typename Placer::Config placer_config; + typename Selector::Config selector_config; + using Placement = typename Placer::Config; + using Selection = typename Selector::Config; + + NestConfig() = default; + NestConfig(const typename Placer::Config &cfg) : placer_config{cfg} {} + NestConfig(const typename Selector::Config &cfg) : selector_config{cfg} {} + 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::iterator> -void nest(Iterator from, Iterator to, - const typename Placer::BinType& bin, - ProgressFunction prg, - StopCondition scond = []() { return false; }, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) +std::size_t nest(Iterator from, Iterator to, + const typename Placer::BinType & bin, + Coord dist = 0, + const NestConfig &cfg = {}, + NestControl ctl = {}) { - _Nester nester(bin, dist, pconf, sconf); - if(prg) nester.progressIndicator(prg); - if(scond) nester.stopCondition(scond); - nester.execute(from, to); + _Nester nester{bin, dist, cfg.placer_config, cfg.selector_config}; + if(ctl.progressfn) nester.progressIndicator(ctl.progressfn); + if(ctl.stopcond) nester.stopCondition(ctl.stopcond); + return nester.execute(from, to); } #ifdef LIBNEST2D_STATIC extern template class Nester; extern template class Nester; - -extern template void nest(std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - Coord dist = 0, - const NfpPlacer::Config& pconf, - const FirstFitSelection::Config& sconf); - -extern template void nest(std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist = 0, - const NfpPlacer::Config& pconf, - const FirstFitSelection::Config& sconf); +extern template std::size_t nest(std::vector::iterator from, + std::vector::iterator from to, + const Box & bin, + Coord dist, + const NestConfig &cfg, + NestControl ctl); +extern template std::size_t nest(std::vector::iterator from, + std::vector::iterator from to, + const Box & bin, + Coord dist, + const NestConfig &cfg, + NestControl ctl); #endif template> -void nest(Container&& cont, - const typename Placer::BinType& bin, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) +std::size_t nest(Container&& cont, + const typename Placer::BinType & bin, + Coord dist = 0, + const NestConfig &cfg = {}, + NestControl ctl = {}) { - nest(cont.begin(), cont.end(), bin, dist, pconf, sconf); -} - -template> -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(cont.begin(), cont.end(), bin, prg, scond, dist, - pconf, sconf); + return nest(cont.begin(), cont.end(), bin, dist, cfg, ctl); } } diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 29d52c10f..91c98c62a 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -129,8 +129,12 @@ public: sh_(sl::create(std::move(contour), std::move(holes))) {} 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 int binId() const noexcept { return binid_; } @@ -748,6 +752,7 @@ template class _Nester { using TSel = SelectionStrategyLike; TSel selector_; + public: using Item = typename PlacementStrategy::Item; using ShapeType = typename Item::ShapeType; @@ -824,7 +829,7 @@ public: * the selection algorithm. */ template - inline ItemIteratorOnly execute(It from, It to) + inline ItemIteratorOnly execute(It from, It to) { auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); 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) { item.inflate(-infl); }); + + return selector_.getResult().size(); } /// Set a progress indicator function object for the selector. diff --git a/src/libnest2d/src/libnest2d.cpp b/src/libnest2d/src/libnest2d.cpp index 021458787..740f6e18d 100644 --- a/src/libnest2d/src/libnest2d.cpp +++ b/src/libnest2d/src/libnest2d.cpp @@ -5,19 +5,17 @@ namespace libnest2d { template class Nester; template class Nester; -template PackGroup nest(std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - Coord dist = 0, - const NfpPlacer::Config& pconf, - const FirstFitSelection::Config& sconf); +template std::size_t nest(std::vector::iterator from, + std::vector::iterator from to, + const Box & bin, + Coord dist, + const NestConfig &cfg, + NestControl ctl); -template PackGroup nest(std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist = 0, - const NfpPlacer::Config& pconf, - const FirstFitSelection::Config& sconf); +template std::size_t nest(std::vector::iterator from, + std::vector::iterator from to, + const Box & bin, + Coord dist, + const NestConfig &cfg, + NestControl ctl); } diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 20dfd8926..aed6e41f7 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -375,7 +375,7 @@ public: for(unsigned idx = 0; idx < fixeditems.size(); ++idx) { Item& itm = fixeditems[idx]; - itm.markAsFixed(); + itm.markAsFixedInBin(0); } m_pck.configure(m_pconf); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e16687cd9..acf7195b1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1571,7 +1571,8 @@ struct Plater::priv size_t count = 0; // To know how much space to reserve 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_unselected.reserve(count + 1 /* for optional wti */); } @@ -1585,11 +1586,12 @@ struct Plater::priv // Set up arrange polygon for a ModelInstance and Wipe tower template ArrangePolygon get_arrange_poly(T *obj) const { ArrangePolygon ap = obj->get_arrange_polygon(); - ap.priority = 0; - ap.bed_idx = ap.translation.x() / bed_stride(); - ap.setter = [obj, this](const ArrangePolygon &p) { + ap.priority = 0; + ap.bed_idx = ap.translation.x() / bed_stride(); + ap.setter = [obj, this](const ArrangePolygon &p) { 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); } }; @@ -1620,7 +1622,8 @@ struct Plater::priv obj_sel(model.objects.size(), nullptr); 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 for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { @@ -1630,7 +1633,8 @@ struct Plater::priv std::vector inst_sel(mo->instances.size(), false); 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) { ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); @@ -2759,9 +2763,8 @@ void Plater::priv::ArrangeJob::process() { try { arrangement::arrange(m_selected, m_unselected, min_d, bedshape, [this, count](unsigned st) { - if (st > - 0) // will not finalize after last one - update_status(count - st, arrangestr); + if (st > 0) // will not finalize after last one + update_status(int(count - st), arrangestr); }, [this]() { return was_canceled(); }); } catch (std::exception & /*e*/) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cc3fd1de7..e2eaebfb8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,7 +11,7 @@ add_library(Catch2::Catch2 ALIAS Catch2) include(Catch) 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) add_library(test_common INTERFACE) diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp index 56f2f59c1..298ff33af 100644 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ b/tests/libnest2d/libnest2d_tests_main.cpp @@ -1,5 +1,7 @@ #define CATCH_CONFIG_MAIN #include +#include + #include #include @@ -224,12 +226,12 @@ TEST_CASE("Area", "[Geometry]") { using namespace libnest2d; RectangleItem rect(10, 10); - - REQUIRE(rect.area() == 100); + + REQUIRE(rect.area() == Approx(100)); RectangleItem rect2 = {100, 100}; - - REQUIRE(rect2.area() == 10000); + + REQUIRE(rect2.area() == Approx(10000)); Item item = { {61, 97}, @@ -288,11 +290,9 @@ TEST_CASE("IsPointInsidePolygon", "[Geometry]") { // REQUIRE_FALSE(ShapeLike::intersects(s1, s2)); //} -// Simple TEST_CASE, does not use gmock TEST_CASE("LeftAndDownPolygon", "[Geometry]") { using namespace libnest2d; - using namespace libnest2d; Box bin(100, 100); BottomLeftPlacer placer(bin); @@ -339,7 +339,6 @@ TEST_CASE("LeftAndDownPolygon", "[Geometry]") } } -// Simple TEST_CASE, does not use gmock TEST_CASE("ArrangeRectanglesTight", "[Nesting]") { using namespace libnest2d; @@ -449,8 +448,8 @@ TEST_CASE("ArrangeRectanglesLoose", "[Nesting]") [](const Item &i1, const Item &i2) { 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( @@ -477,8 +476,6 @@ using namespace libnest2d; template void exportSVG(std::vector>& result, const Bin& bin, int idx = 0) { - - std::string loc = "out"; static std::string svg_header = @@ -494,20 +491,20 @@ void exportSVG(std::vector>& result, const Bin& bin if(out.is_open()) { out << svg_header; Item rbin( RectangleItem(bin.width(), bin.height()) ); - for(unsigned i = 0; i < rbin.vertexCount(); i++) { - auto v = rbin.vertex(i); + for(unsigned j = 0; j < rbin.vertexCount(); j++) { + auto v = rbin.vertex(j); setY(v, -getY(v)/SCALE + 500 ); setX(v, getX(v)/SCALE); - rbin.setVertex(i, v); + rbin.setVertex(j, v); } out << shapelike::serialize(rbin.rawShape()) << std::endl; for(Item& sh : r) { Item tsh(sh.transformedShape()); - for(unsigned i = 0; i < tsh.vertexCount(); i++) { - auto v = tsh.vertex(i); + for(unsigned j = 0; j < tsh.vertexCount(); j++) { + auto v = tsh.vertex(j); setY(v, -getY(v)/SCALE + 500); setX(v, getX(v)/SCALE); - tsh.setVertex(i, v); + tsh.setVertex(j, v); } out << shapelike::serialize(tsh.rawShape()) << std::endl; } @@ -570,7 +567,7 @@ TEST_CASE("convexHull", "[Geometry]") { REQUIRE(chull.size() == poly.size()); } -TEST_CASE("NestPrusaPartsShouldFitIntoTwoBins", "[Nesting]") { +TEST_CASE("PrusaPartsShouldFitIntoTwoBins", "[Nesting]") { // Get the input items and define the bin. std::vector input = prusaParts(); @@ -579,21 +576,15 @@ TEST_CASE("NestPrusaPartsShouldFitIntoTwoBins", "[Nesting]") { // 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) size_t pcount = input.size(); - libnest2d::nest(input, bin, [&pcount](unsigned cnt) { - REQUIRE(cnt < pcount); - 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); + size_t bins = libnest2d::nest(input, bin, 0, {}, + ProgressFunction{[&pcount](unsigned cnt) { + REQUIRE(cnt < pcount); + pcount = cnt; + }}); // For prusa parts, 2 bins should be enough... + REQUIRE(bins > 0u); REQUIRE(bins <= 2u); // 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 std::vector items; items.emplace_back(Item{}); // Emplace empty 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); } -TEST_CASE("NestLargeItemShouldBeUntouched", "[Nesting]") { +TEST_CASE("LargeItemShouldBeUntouched", "[Nesting]") { auto bin = Box(250000000, 210000000); // dummy bin std::vector items; 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); } +TEST_CASE("Items can be preloaded", "[Nesting]") { + auto bin = Box({0, 0}, {250000000, 210000000}); // dummy bin + + std::vector 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 { struct ItemPair { @@ -969,8 +1014,8 @@ TEST_CASE("mergePileWithPolygon", "[Geometry]") { REQUIRE(result.size() == 1); RectangleItem ref(45, 15); - - REQUIRE(shapelike::area(result.front()) == ref.area()); + + REQUIRE(shapelike::area(result.front()) == Approx(ref.area())); } namespace {