diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 72e239a70..c447cfcd9 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -982,6 +982,9 @@ template inline double area(const S& poly, const PolygonTag& ) }); } +template +inline double area(const RawShapes& shapes, const MultiPolygonTag&); + template // Dispatching function inline double area(const S& sh) { diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index b6d7fcdcf..5eef28c40 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -27,6 +27,7 @@ using Coord = TCoord; using Box = _Box; using Segment = _Segment; using Circle = _Circle; +using MultiPolygon = TMultiShape; using Item = _Item; using Rectangle = _Rectangle; diff --git a/src/libnest2d/tools/svgtools.hpp b/src/libnest2d/tools/svgtools.hpp index e1ed1ad05..2a05b551d 100644 --- a/src/libnest2d/tools/svgtools.hpp +++ b/src/libnest2d/tools/svgtools.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include namespace libnest2d { namespace svg { @@ -48,23 +48,28 @@ public: conf_.width = static_cast(box.width()) / conf_.mm_in_coord_units; } - - void writeItem(const Item& item) { + + void writeShape(RawShape tsh) { if(svg_layers_.empty()) addLayer(); - auto tsh = item.transformedShape(); if(conf_.origo_location == BOTTOMLEFT) { auto d = static_cast( - std::round(conf_.height*conf_.mm_in_coord_units) ); - + std::round(conf_.height*conf_.mm_in_coord_units) ); + auto& contour = shapelike::contour(tsh); for(auto& v : contour) setY(v, -getY(v) + d); - + auto& holes = shapelike::holes(tsh); for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d); - + } - currentLayer() += shapelike::serialize(tsh, - 1.0/conf_.mm_in_coord_units) + "\n"; + currentLayer() += + shapelike::serialize(tsh, + 1.0 / conf_.mm_in_coord_units) + + "\n"; + } + + void writeItem(const Item& item) { + writeShape(item.transformedShape()); } void writePackGroup(const PackGroup& result) { diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp index c7259ae53..e0692f4e7 100644 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ b/tests/libnest2d/libnest2d_tests_main.cpp @@ -472,32 +472,30 @@ TEST_CASE("ArrangeRectanglesLoose", "[Nesting]") namespace { using namespace libnest2d; -template -void exportSVG(std::vector>& result, const Bin& bin, int idx = 0) { - std::string loc = "out"; +template +void exportSVG(const char *loc, It from, It to) { - static std::string svg_header = - R"raw( + static const char* svg_header = +R"raw( )raw"; - int i = idx; - auto r = result; // for(auto r : result) { - std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out); + std::fstream out(loc, std::fstream::out); if(out.is_open()) { out << svg_header; - Item rbin( RectangleItem(bin.width(), bin.height()) ); - 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(j, v); - } - out << shapelike::serialize(rbin.rawShape()) << std::endl; - for(Item& sh : r) { - Item tsh(sh.transformedShape()); +// Item rbin( RectangleItem(bin.width(), bin.height()) ); +// 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(j, v); +// } +// out << shapelike::serialize(rbin.rawShape()) << std::endl; + for(auto it = from; it != to; ++it) { + const Item &itm = *it; + Item tsh(itm.transformedShape()); for(unsigned j = 0; j < tsh.vertexCount(); j++) { auto v = tsh.vertex(j); setY(v, -getY(v)/SCALE + 500); @@ -509,10 +507,16 @@ void exportSVG(std::vector>& result, const Bin& bin out << "\n" << std::endl; } out.close(); - + // i++; // } } + +template +void exportSVG(std::vector>& result, int idx = 0) { + exportSVG((std::string("out") + std::to_string(idx) + ".svg").c_str(), + result.begin(), result.end()); +} } TEST_CASE("BottomLeftStressTest", "[Geometry]") { @@ -541,7 +545,7 @@ TEST_CASE("BottomLeftStressTest", "[Geometry]") { valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); if(!valid) { std::cout << "error index: " << i << std::endl; - exportSVG(result, bin, i); + exportSVG(result, i); } REQUIRE(valid); } else { @@ -894,7 +898,7 @@ void testNfp(const std::vector& testdata) { int TEST_CASEcase = 0; - auto& exportfun = exportSVG; + auto& exportfun = exportSVG; auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){ TEST_CASEcase++; @@ -941,7 +945,7 @@ void testNfp(const std::vector& testdata) { std::ref(stationary), std::ref(tmp), std::ref(infp) }; - exportfun(inp, bin, TEST_CASEcase*i++); + exportfun(inp, TEST_CASEcase*i++); } REQUIRE(touching); @@ -1096,3 +1100,91 @@ TEST_CASE("MinAreaBBWithRotatingCalipers", "[Geometry]") { REQUIRE(succ); } } + +template MultiPolygon merged_pile(It from, It to, int bin_id) +{ + MultiPolygon pile; + pile.reserve(size_t(to - from)); + + for (auto it = from; it != to; ++it) { + if (it->binId() == bin_id) pile.emplace_back(it->transformedShape()); + } + + return nfp::merge(pile); +} + +TEST_CASE("Test for bed center distance optimization", "[Nesting], [NestKernels]") +{ + static const constexpr ClipperLib::cInt W = 10000000; + + // Get the input items and define the bin. + std::vector input(9, {W, W}); + + auto bin = Box::infinite(); + + NfpPlacer::Config pconfig; + + pconfig.object_function = [](const Item &item) -> double { + return pl::magnsq(item.boundingBox().center()); + }; + + size_t bins = nest(input, bin, 0, NestConfig{pconfig}); + + REQUIRE(bins == 1); + + // Gather the items into piles of arranged polygons... + MultiPolygon pile; + pile.reserve(input.size()); + + for (auto &itm : input) { + REQUIRE(itm.binId() == 0); + pile.emplace_back(itm.transformedShape()); + } + + MultiPolygon m = merged_pile(input.begin(), input.end(), 0); + + REQUIRE(m.size() == 1); + + REQUIRE(sl::area(m) == Approx(9. * W * W)); +} + +TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]") +{ + static const constexpr ClipperLib::cInt W = 10000000; + static const constexpr size_t N = 100; + + // Get the input items and define the bin. + std::vector input(N, {W, W}); + + auto bin = Box::infinite(); + + NfpPlacer::Config pconfig; + pconfig.rotations = {0.}; + Box pile_box; + pconfig.before_packing = + [&pile_box](const MultiPolygon &pile, + const _ItemGroup &/*packed_items*/, + const _ItemGroup &/*remaining_items*/) { + pile_box = sl::boundingBox(pile); + }; + + pconfig.object_function = [&pile_box](const Item &item) -> double { + Box b = sl::boundingBox(item.boundingBox(), pile_box); + double area = b.area() / (W * W); + return -area; + }; + + size_t bins = nest(input, bin, 0, NestConfig{pconfig}); + + // To debug: + exportSVG<1000000>("out", input.begin(), input.end()); + + REQUIRE(bins == 1); + + MultiPolygon pile = merged_pile(input.begin(), input.end(), 0); + Box bb = sl::boundingBox(pile); + + // Here the result shall be a stairway of boxes + REQUIRE(pile.size() == N); + REQUIRE(bb.area() == N * N * W * W); +}