libnest2d_tests_main.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227
  1. #include <catch_main.hpp>
  2. #include <fstream>
  3. #include <cstdint>
  4. #include <libnest2d/libnest2d.hpp>
  5. #include "printer_parts.hpp"
  6. //#include <libnest2d/geometry_traits_nfp.hpp>
  7. #include "../tools/svgtools.hpp"
  8. #include <libnest2d/utils/rotcalipers.hpp>
  9. #if defined(_MSC_VER) && defined(__clang__)
  10. #define BOOST_NO_CXX17_HDR_STRING_VIEW
  11. #endif
  12. #include "boost/multiprecision/integer.hpp"
  13. #include "boost/rational.hpp"
  14. //#include "../tools/libnfpglue.hpp"
  15. //#include "../tools/nfp_svgnest_glue.hpp"
  16. namespace libnest2d {
  17. #if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
  18. using LargeInt = __int128;
  19. #else
  20. using LargeInt = boost::multiprecision::int128_t;
  21. template<> struct _NumTag<LargeInt> { using Type = ScalarTag; };
  22. #endif
  23. template<class T> struct _NumTag<boost::rational<T>> { using Type = RationalTag; };
  24. using RectangleItem = libnest2d::Rectangle;
  25. namespace nfp {
  26. template<class S>
  27. struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
  28. {
  29. NfpResult<S> operator()(const S &sh, const S &other)
  30. {
  31. return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
  32. }
  33. };
  34. }
  35. }
  36. namespace {
  37. using namespace libnest2d;
  38. template<int64_t SCALE = 1, class It>
  39. void exportSVG(const char *loc, It from, It to) {
  40. static const char* svg_header =
  41. R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  42. <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
  43. <svg height="500" width="500" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  44. )raw";
  45. // for(auto r : result) {
  46. std::fstream out(loc, std::fstream::out);
  47. if(out.is_open()) {
  48. out << svg_header;
  49. // Item rbin( RectangleItem(bin.width(), bin.height()) );
  50. // for(unsigned j = 0; j < rbin.vertexCount(); j++) {
  51. // auto v = rbin.vertex(j);
  52. // setY(v, -getY(v)/SCALE + 500 );
  53. // setX(v, getX(v)/SCALE);
  54. // rbin.setVertex(j, v);
  55. // }
  56. // out << shapelike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
  57. for(auto it = from; it != to; ++it) {
  58. const Item &itm = *it;
  59. Item tsh(itm.transformedShape());
  60. for(unsigned j = 0; j < tsh.vertexCount(); j++) {
  61. auto v = tsh.vertex(j);
  62. setY(v, -getY(v)/SCALE + 500);
  63. setX(v, getX(v)/SCALE);
  64. tsh.setVertex(j, v);
  65. }
  66. out << shapelike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
  67. }
  68. out << "\n</svg>" << std::endl;
  69. }
  70. out.close();
  71. // i++;
  72. // }
  73. }
  74. template<int64_t SCALE = 1>
  75. void exportSVG(std::vector<std::reference_wrapper<Item>>& result, int idx = 0) {
  76. exportSVG<SCALE>((std::string("out") + std::to_string(idx) + ".svg").c_str(),
  77. result.begin(), result.end());
  78. }
  79. }
  80. static std::vector<libnest2d::Item>& prusaParts() {
  81. using namespace libnest2d;
  82. static std::vector<Item> ret;
  83. if(ret.empty()) {
  84. ret.reserve(PRINTER_PART_POLYGONS.size());
  85. for(auto& inp : PRINTER_PART_POLYGONS) {
  86. auto inp_cpy = inp;
  87. if (ClosureTypeV<PathImpl> == Closure::OPEN)
  88. inp_cpy.points.pop_back();
  89. if constexpr (!libnest2d::is_clockwise<libnest2d::PathImpl>())
  90. std::reverse(inp_cpy.begin(), inp_cpy.end());
  91. ret.emplace_back(inp_cpy);
  92. }
  93. }
  94. return ret;
  95. }
  96. TEST_CASE("Angles", "[Geometry]")
  97. {
  98. using namespace libnest2d;
  99. Degrees deg(180);
  100. Radians rad(deg);
  101. Degrees deg2(rad);
  102. REQUIRE(Approx(rad) == Pi);
  103. REQUIRE(Approx(deg) == 180);
  104. REQUIRE(Approx(deg2) == 180);
  105. REQUIRE(Approx(rad) == Radians(deg));
  106. REQUIRE(Approx(Degrees(rad)) == deg);
  107. REQUIRE(rad == deg);
  108. Segment seg = {{0, 0}, {12, -10}};
  109. REQUIRE(Degrees(seg.angleToXaxis()) > 270);
  110. REQUIRE(Degrees(seg.angleToXaxis()) < 360);
  111. seg = {{0, 0}, {12, 10}};
  112. REQUIRE(Degrees(seg.angleToXaxis()) > 0);
  113. REQUIRE(Degrees(seg.angleToXaxis()) < 90);
  114. seg = {{0, 0}, {-12, 10}};
  115. REQUIRE(Degrees(seg.angleToXaxis()) > 90);
  116. REQUIRE(Degrees(seg.angleToXaxis()) < 180);
  117. seg = {{0, 0}, {-12, -10}};
  118. REQUIRE(Degrees(seg.angleToXaxis()) > 180);
  119. REQUIRE(Degrees(seg.angleToXaxis()) < 270);
  120. seg = {{0, 0}, {1, 0}};
  121. REQUIRE(Degrees(seg.angleToXaxis()) == Approx(0.));
  122. seg = {{0, 0}, {0, 1}};
  123. REQUIRE(Degrees(seg.angleToXaxis()) == Approx(90.));
  124. seg = {{0, 0}, {-1, 0}};
  125. REQUIRE(Degrees(seg.angleToXaxis()) == Approx(180.));
  126. seg = {{0, 0}, {0, -1}};
  127. REQUIRE(Degrees(seg.angleToXaxis()) == Approx(270.));
  128. }
  129. // Simple TEST_CASE, does not use gmock
  130. TEST_CASE("ItemCreationAndDestruction", "[Nesting]")
  131. {
  132. using namespace libnest2d;
  133. Item sh = { {0, 0}, {1, 0}, {1, 1}, {0, 1} };
  134. REQUIRE(sh.vertexCount() == 4u);
  135. Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} });
  136. REQUIRE(sh2.vertexCount() == 4u);
  137. // copy
  138. Item sh3 = sh2;
  139. REQUIRE(sh3.vertexCount() == 4u);
  140. sh2 = {};
  141. REQUIRE(sh2.vertexCount() == 0u);
  142. REQUIRE(sh3.vertexCount() == 4u);
  143. }
  144. TEST_CASE("boundingCircle", "[Geometry]") {
  145. using namespace libnest2d;
  146. using placers::boundingCircle;
  147. PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}};
  148. Circle c = boundingCircle(p);
  149. REQUIRE(getX(c.center()) == 0);
  150. REQUIRE(getY(c.center()) == 0);
  151. REQUIRE(c.radius() == Approx(10));
  152. shapelike::translate(p, PointImpl{10, 10});
  153. c = boundingCircle(p);
  154. REQUIRE(getX(c.center()) == 10);
  155. REQUIRE(getY(c.center()) == 10);
  156. REQUIRE(c.radius() == Approx(10));
  157. auto parts = prusaParts();
  158. int i = 0;
  159. for(auto& part : parts) {
  160. c = boundingCircle(part.transformedShape());
  161. if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl;
  162. else for(auto v : shapelike::contour(part.transformedShape()) ) {
  163. auto d = pointlike::distance(v, c.center());
  164. if(d > c.radius() ) {
  165. auto e = std::abs( 1.0 - d/c.radius());
  166. REQUIRE(e <= 1e-3);
  167. }
  168. }
  169. i++;
  170. }
  171. }
  172. TEST_CASE("Distance", "[Geometry]") {
  173. using namespace libnest2d;
  174. Point p1 = {0, 0};
  175. Point p2 = {10, 0};
  176. Point p3 = {10, 10};
  177. REQUIRE(pointlike::distance(p1, p2) == Approx(10));
  178. REQUIRE(pointlike::distance(p1, p3) == Approx(sqrt(200)));
  179. Segment seg(p1, p3);
  180. // REQUIRE(pointlike::distance(p2, seg) == Approx(7.0710678118654755));
  181. auto result = pointlike::horizontalDistance(p2, seg);
  182. auto check = [](TCompute<Coord> val, TCompute<Coord> expected) {
  183. if(std::is_floating_point<TCompute<Coord>>::value)
  184. REQUIRE(static_cast<double>(val) ==
  185. Approx(static_cast<double>(expected)));
  186. else
  187. REQUIRE(val == expected);
  188. };
  189. REQUIRE(result.second);
  190. check(result.first, 10);
  191. result = pointlike::verticalDistance(p2, seg);
  192. REQUIRE(result.second);
  193. check(result.first, -10);
  194. result = pointlike::verticalDistance(Point{10, 20}, seg);
  195. REQUIRE(result.second);
  196. check(result.first, 10);
  197. Point p4 = {80, 0};
  198. Segment seg2 = { {0, 0}, {0, 40} };
  199. result = pointlike::horizontalDistance(p4, seg2);
  200. REQUIRE(result.second);
  201. check(result.first, 80);
  202. result = pointlike::verticalDistance(p4, seg2);
  203. // Point should not be related to the segment
  204. REQUIRE_FALSE(result.second);
  205. }
  206. TEST_CASE("Area", "[Geometry]") {
  207. using namespace libnest2d;
  208. RectangleItem rect(10, 10);
  209. REQUIRE(rect.area() == Approx(100));
  210. RectangleItem rect2 = {100, 100};
  211. REQUIRE(rect2.area() == Approx(10000));
  212. Item item = {
  213. {61, 97},
  214. {70, 151},
  215. {176, 151},
  216. {189, 138},
  217. {189, 59},
  218. {70, 59},
  219. {61, 77},
  220. {61, 97}
  221. };
  222. REQUIRE(std::abs(shapelike::area(item.transformedShape())) > 0 );
  223. }
  224. TEST_CASE("IsPointInsidePolygon", "[Geometry]") {
  225. using namespace libnest2d;
  226. RectangleItem rect(10, 10);
  227. Point p = {1, 1};
  228. REQUIRE(rect.isInside(p));
  229. p = {11, 11};
  230. REQUIRE_FALSE(rect.isInside(p));
  231. p = {11, 12};
  232. REQUIRE_FALSE(rect.isInside(p));
  233. p = {3, 3};
  234. REQUIRE(rect.isInside(p));
  235. }
  236. //TEST_CASE(GeometryAlgorithms, Intersections) {
  237. // using namespace binpack2d;
  238. // RectangleItem rect(70, 30);
  239. // rect.translate({80, 60});
  240. // RectangleItem rect2(80, 60);
  241. // rect2.translate({80, 0});
  242. //// REQUIRE_FALSE(Item::intersects(rect, rect2));
  243. // Segment s1({0, 0}, {10, 10});
  244. // Segment s2({1, 1}, {11, 11});
  245. // REQUIRE_FALSE(ShapeLike::intersects(s1, s1));
  246. // REQUIRE_FALSE(ShapeLike::intersects(s1, s2));
  247. //}
  248. TEST_CASE("LeftAndDownPolygon", "[Geometry]")
  249. {
  250. using namespace libnest2d;
  251. Box bin(100, 100);
  252. BottomLeftPlacer placer(bin);
  253. PathImpl pitem = {{70, 75}, {88, 60}, {65, 50}, {60, 30}, {80, 20},
  254. {42, 20}, {35, 35}, {35, 55}, {40, 75}};
  255. PathImpl pleftControl = {{40, 75}, {35, 55}, {35, 35},
  256. {42, 20}, {0, 20}, {0, 75}};
  257. PathImpl pdownControl = {{88, 60}, {88, 0}, {35, 0}, {35, 35},
  258. {42, 20}, {80, 20}, {60, 30}, {65, 50}};
  259. if constexpr (!is_clockwise<PathImpl>()) {
  260. std::reverse(sl::begin(pitem), sl::end(pitem));
  261. std::reverse(sl::begin(pleftControl), sl::end(pleftControl));
  262. std::reverse(sl::begin(pdownControl), sl::end(pdownControl));
  263. }
  264. if constexpr (ClosureTypeV<PathImpl> == Closure::CLOSED) {
  265. sl::addVertex(pitem, sl::front(pitem));
  266. sl::addVertex(pleftControl, sl::front(pleftControl));
  267. sl::addVertex(pdownControl, sl::front(pdownControl));
  268. }
  269. Item item{pitem}, leftControl{pleftControl}, downControl{pdownControl};
  270. Item leftp(placer.leftPoly(item));
  271. auto valid = sl::isValid(leftp.rawShape());
  272. std::vector<std::reference_wrapper<Item>> to_export{ leftp, leftControl };
  273. exportSVG<1>("leftp.svg", to_export.begin(), to_export.end());
  274. REQUIRE(valid.first);
  275. REQUIRE(leftp.vertexCount() == leftControl.vertexCount());
  276. for(unsigned long i = 0; i < leftControl.vertexCount(); i++) {
  277. REQUIRE(getX(leftp.vertex(i)) == getX(leftControl.vertex(i)));
  278. REQUIRE(getY(leftp.vertex(i)) == getY(leftControl.vertex(i)));
  279. }
  280. Item downp(placer.downPoly(item));
  281. REQUIRE(shapelike::isValid(downp.rawShape()).first);
  282. REQUIRE(downp.vertexCount() == downControl.vertexCount());
  283. for(unsigned long i = 0; i < downControl.vertexCount(); i++) {
  284. REQUIRE(getX(downp.vertex(i)) == getX(downControl.vertex(i)));
  285. REQUIRE(getY(downp.vertex(i)) == getY(downControl.vertex(i)));
  286. }
  287. }
  288. TEST_CASE("ArrangeRectanglesTight", "[Nesting][NotWorking]")
  289. {
  290. using namespace libnest2d;
  291. std::vector<RectangleItem> rects = {
  292. {80, 80},
  293. {60, 90},
  294. {70, 30},
  295. {80, 60},
  296. {60, 60},
  297. {60, 40},
  298. {40, 40},
  299. {10, 10},
  300. {10, 10},
  301. {10, 10},
  302. {10, 10},
  303. {10, 10},
  304. {5, 5},
  305. {5, 5},
  306. {5, 5},
  307. {5, 5},
  308. {5, 5},
  309. {5, 5},
  310. {5, 5},
  311. {20, 20} };
  312. Box bin(210, 250, {105, 125});
  313. REQUIRE(bin.width() == 210);
  314. REQUIRE(bin.height() == 250);
  315. REQUIRE(getX(bin.center()) == 105);
  316. REQUIRE(getY(bin.center()) == 125);
  317. _Nester<BottomLeftPlacer, FirstFitSelection> arrange(bin);
  318. arrange.execute(rects.begin(), rects.end());
  319. auto max_group = std::max_element(rects.begin(), rects.end(),
  320. [](const Item &i1, const Item &i2) {
  321. return i1.binId() < i2.binId();
  322. });
  323. int groups = max_group == rects.end() ? 0 : max_group->binId() + 1;
  324. REQUIRE(groups == 1u);
  325. REQUIRE(
  326. std::all_of(rects.begin(), rects.end(), [](const RectangleItem &itm) {
  327. return itm.binId() != BIN_ID_UNSET;
  328. }));
  329. // check for no intersections, no containment:
  330. // exportSVG<1>("arrangeRectanglesTight.svg", rects.begin(), rects.end());
  331. bool valid = true;
  332. for(Item& r1 : rects) {
  333. for(Item& r2 : rects) {
  334. if(&r1 != &r2 ) {
  335. valid = !Item::intersects(r1, r2) || Item::touches(r1, r2);
  336. REQUIRE(valid);
  337. valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
  338. REQUIRE(valid);
  339. }
  340. }
  341. }
  342. }
  343. TEST_CASE("ArrangeRectanglesLoose", "[Nesting]")
  344. {
  345. using namespace libnest2d;
  346. // std::vector<Rectangle> rects = { {40, 40}, {10, 10}, {20, 20} };
  347. std::vector<RectangleItem> rects = {
  348. {80, 80},
  349. {60, 90},
  350. {70, 30},
  351. {80, 60},
  352. {60, 60},
  353. {60, 40},
  354. {40, 40},
  355. {10, 10},
  356. {10, 10},
  357. {10, 10},
  358. {10, 10},
  359. {10, 10},
  360. {5, 5},
  361. {5, 5},
  362. {5, 5},
  363. {5, 5},
  364. {5, 5},
  365. {5, 5},
  366. {5, 5},
  367. {20, 20} };
  368. Box bin(210, 250, {105, 125});
  369. REQUIRE(bin.width() == 210);
  370. REQUIRE(bin.height() == 250);
  371. REQUIRE(getX(bin.center()) == 105);
  372. REQUIRE(getY(bin.center()) == 125);
  373. Coord min_obj_distance = 5;
  374. _Nester<BottomLeftPlacer, FirstFitSelection> arrange(bin, min_obj_distance);
  375. arrange.execute(rects.begin(), rects.end());
  376. auto max_group = std::max_element(rects.begin(), rects.end(),
  377. [](const Item &i1, const Item &i2) {
  378. return i1.binId() < i2.binId();
  379. });
  380. auto groups = size_t(max_group == rects.end() ? 0 : max_group->binId() + 1);
  381. REQUIRE(groups == 1u);
  382. REQUIRE(
  383. std::all_of(rects.begin(), rects.end(), [](const RectangleItem &itm) {
  384. return itm.binId() != BIN_ID_UNSET;
  385. }));
  386. // check for no intersections, no containment:
  387. bool valid = true;
  388. for(Item& r1 : rects) {
  389. for(Item& r2 : rects) {
  390. if(&r1 != &r2 ) {
  391. valid = !Item::intersects(r1, r2);
  392. valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
  393. REQUIRE(valid);
  394. }
  395. }
  396. }
  397. }
  398. TEST_CASE("BottomLeftStressTest", "[Geometry][NotWorking]") {
  399. using namespace libnest2d;
  400. const Coord SCALE = 1000000;
  401. auto& input = prusaParts();
  402. Box bin(210*SCALE, 250*SCALE);
  403. BottomLeftPlacer placer(bin);
  404. auto it = input.begin();
  405. auto next = it;
  406. int i = 0;
  407. while(it != input.end() && ++next != input.end()) {
  408. placer.pack(*it);
  409. placer.pack(*next);
  410. auto result = placer.getItems();
  411. bool valid = true;
  412. if(result.size() == 2) {
  413. Item& r1 = result[0];
  414. Item& r2 = result[1];
  415. valid = !Item::intersects(r1, r2) || Item::touches(r1, r2);
  416. valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
  417. if(!valid) {
  418. std::cout << "error index: " << i << std::endl;
  419. exportSVG<SCALE>(result, i);
  420. }
  421. REQUIRE(valid);
  422. } else {
  423. std::cout << "something went terribly wrong!" << std::endl;
  424. FAIL();
  425. }
  426. placer.clearItems();
  427. it++;
  428. i++;
  429. }
  430. }
  431. TEST_CASE("convexHull", "[Geometry]") {
  432. using namespace libnest2d;
  433. PathImpl poly = PRINTER_PART_POLYGONS[0];
  434. auto chull = sl::convexHull(poly);
  435. REQUIRE(chull.size() == poly.size());
  436. }
  437. TEST_CASE("PrusaPartsShouldFitIntoTwoBins", "[Nesting]") {
  438. // Get the input items and define the bin.
  439. std::vector<Item> input = prusaParts();
  440. auto bin = Box(250000000, 210000000);
  441. // Do the nesting. Check in each step if the remaining items are less than
  442. // in the previous step. (Some algorithms can place more items in one step)
  443. size_t pcount = input.size();
  444. size_t bins = libnest2d::nest(input, bin, 0, {},
  445. ProgressFunction{[&pcount](unsigned cnt) {
  446. REQUIRE(cnt < pcount);
  447. pcount = cnt;
  448. }});
  449. // For prusa parts, 2 bins should be enough...
  450. REQUIRE(bins > 0u);
  451. REQUIRE(bins <= 2u);
  452. // All parts should be processed by the algorithm
  453. REQUIRE(
  454. std::all_of(input.begin(), input.end(), [](const Item &itm) {
  455. return itm.binId() != BIN_ID_UNSET;
  456. }));
  457. // Gather the items into piles of arranged polygons...
  458. using Pile = TMultiShape<PolygonImpl>;
  459. std::vector<Pile> piles(bins);
  460. for (auto &itm : input)
  461. piles[size_t(itm.binId())].emplace_back(itm.transformedShape());
  462. // Now check all the piles, the bounding box of each pile should be inside
  463. // the defined bin.
  464. for (auto &pile : piles) {
  465. auto bb = sl::boundingBox(pile);
  466. REQUIRE(sl::isInside(bb, bin));
  467. }
  468. // Check the area of merged pile vs the sum of area of all the parts
  469. // They should match, otherwise there is an overlap which should not happen.
  470. for (auto &pile : piles) {
  471. double area_sum = 0.;
  472. for (auto &obj : pile)
  473. area_sum += sl::area(obj);
  474. auto pile_m = nfp::merge(pile);
  475. double area_merge = sl::area(pile_m);
  476. REQUIRE(area_sum == Approx(area_merge));
  477. }
  478. }
  479. TEST_CASE("EmptyItemShouldBeUntouched", "[Nesting]") {
  480. auto bin = Box(250000000, 210000000); // dummy bin
  481. std::vector<Item> items;
  482. items.emplace_back(Item{}); // Emplace empty item
  483. items.emplace_back(Item{ {0, 200} }); // Emplace zero area item
  484. size_t bins = libnest2d::nest(items, bin);
  485. REQUIRE(bins == 0u);
  486. for (auto &itm : items) REQUIRE(itm.binId() == BIN_ID_UNSET);
  487. }
  488. TEST_CASE("LargeItemShouldBeUntouched", "[Nesting]") {
  489. auto bin = Box(250000000, 210000000); // dummy bin
  490. std::vector<Item> items;
  491. items.emplace_back(RectangleItem{250000001, 210000001}); // Emplace large item
  492. size_t bins = libnest2d::nest(items, bin);
  493. REQUIRE(bins == 0u);
  494. REQUIRE(items.front().binId() == BIN_ID_UNSET);
  495. }
  496. TEST_CASE("Items can be preloaded", "[Nesting]") {
  497. auto bin = Box({0, 0}, {250000000, 210000000}); // dummy bin
  498. std::vector<Item> items;
  499. items.reserve(2);
  500. NestConfig<> cfg;
  501. cfg.placer_config.alignment = NestConfig<>::Placement::Alignment::DONT_ALIGN;
  502. items.emplace_back(RectangleItem{10000000, 10000000});
  503. Item &fixed_rect = items.back();
  504. fixed_rect.translate(bin.center());
  505. items.emplace_back(RectangleItem{20000000, 20000000});
  506. Item &movable_rect = items.back();
  507. movable_rect.translate(bin.center());
  508. SECTION("Preloaded Item should be untouched") {
  509. fixed_rect.markAsFixedInBin(0);
  510. size_t bins = libnest2d::nest(items, bin, 0, cfg);
  511. REQUIRE(bins == 1);
  512. REQUIRE(fixed_rect.binId() == 0);
  513. REQUIRE(getX(fixed_rect.translation()) == getX(bin.center()));
  514. REQUIRE(getY(fixed_rect.translation()) == getY(bin.center()));
  515. REQUIRE(movable_rect.binId() == 0);
  516. REQUIRE(getX(movable_rect.translation()) != getX(bin.center()));
  517. REQUIRE(getY(movable_rect.translation()) != getY(bin.center()));
  518. }
  519. SECTION("Preloaded Item should not affect free bins") {
  520. fixed_rect.markAsFixedInBin(1);
  521. size_t bins = libnest2d::nest(items, bin, 0, cfg);
  522. REQUIRE(bins == 2);
  523. REQUIRE(fixed_rect.binId() == 1);
  524. REQUIRE(getX(fixed_rect.translation()) == getX(bin.center()));
  525. REQUIRE(getY(fixed_rect.translation()) == getY(bin.center()));
  526. REQUIRE(movable_rect.binId() == 0);
  527. auto bb = movable_rect.boundingBox();
  528. REQUIRE(getX(bb.center()) == getX(bin.center()));
  529. REQUIRE(getY(bb.center()) == getY(bin.center()));
  530. }
  531. }
  532. namespace {
  533. struct ItemPair {
  534. Item orbiter;
  535. Item stationary;
  536. };
  537. std::vector<ItemPair> nfp_testdata = {
  538. {
  539. {
  540. {80, 50},
  541. {100, 70},
  542. {120, 50}
  543. },
  544. {
  545. {10, 10},
  546. {10, 40},
  547. {40, 40},
  548. {40, 10}
  549. }
  550. },
  551. {
  552. {
  553. {80, 50},
  554. {60, 70},
  555. {80, 90},
  556. {120, 90},
  557. {140, 70},
  558. {120, 50}
  559. },
  560. {
  561. {10, 10},
  562. {10, 40},
  563. {40, 40},
  564. {40, 10}
  565. }
  566. },
  567. {
  568. {
  569. {40, 10},
  570. {30, 10},
  571. {20, 20},
  572. {20, 30},
  573. {30, 40},
  574. {40, 40},
  575. {50, 30},
  576. {50, 20}
  577. },
  578. {
  579. {80, 0},
  580. {80, 30},
  581. {110, 30},
  582. {110, 0}
  583. }
  584. },
  585. {
  586. {
  587. {117, 107},
  588. {118, 109},
  589. {120, 112},
  590. {122, 113},
  591. {128, 113},
  592. {130, 112},
  593. {132, 109},
  594. {133, 107},
  595. {133, 103},
  596. {132, 101},
  597. {130, 98},
  598. {128, 97},
  599. {122, 97},
  600. {120, 98},
  601. {118, 101},
  602. {117, 103}
  603. },
  604. {
  605. {102, 116},
  606. {111, 126},
  607. {114, 126},
  608. {144, 106},
  609. {148, 100},
  610. {148, 85},
  611. {147, 84},
  612. {102, 84}
  613. }
  614. },
  615. {
  616. {
  617. {99, 122},
  618. {108, 140},
  619. {110, 142},
  620. {139, 142},
  621. {151, 122},
  622. {151, 102},
  623. {142, 70},
  624. {139, 68},
  625. {111, 68},
  626. {108, 70},
  627. {99, 102}
  628. },
  629. {
  630. {107, 124},
  631. {128, 125},
  632. {133, 125},
  633. {136, 124},
  634. {140, 121},
  635. {142, 119},
  636. {143, 116},
  637. {143, 109},
  638. {141, 93},
  639. {139, 89},
  640. {136, 86},
  641. {134, 85},
  642. {108, 85},
  643. {107, 86}
  644. }
  645. },
  646. {
  647. {
  648. {91, 100},
  649. {94, 144},
  650. {117, 153},
  651. {118, 153},
  652. {159, 112},
  653. {159, 110},
  654. {156, 66},
  655. {133, 57},
  656. {132, 57},
  657. {91, 98}
  658. },
  659. {
  660. {101, 90},
  661. {103, 98},
  662. {107, 113},
  663. {114, 125},
  664. {115, 126},
  665. {135, 126},
  666. {136, 125},
  667. {144, 114},
  668. {149, 90},
  669. {149, 89},
  670. {148, 87},
  671. {145, 84},
  672. {105, 84},
  673. {102, 87},
  674. {101, 89}
  675. }
  676. }
  677. };
  678. std::vector<ItemPair> nfp_concave_testdata = {
  679. { // ItemPair
  680. {
  681. {
  682. {533726, 142141},
  683. {532359, 143386},
  684. {530141, 142155},
  685. {528649, 160091},
  686. {533659, 157607},
  687. {538669, 160091},
  688. {537178, 142155},
  689. {534959, 143386}
  690. }
  691. },
  692. {
  693. {
  694. {118305, 11603},
  695. {118311, 26616},
  696. {113311, 26611},
  697. {109311, 29604},
  698. {109300, 44608},
  699. {109311, 49631},
  700. {113300, 52636},
  701. {118311, 52636},
  702. {118308, 103636},
  703. {223830, 103636},
  704. {236845, 90642},
  705. {236832, 11630},
  706. {232825, 11616},
  707. {210149, 11616},
  708. {211308, 13625},
  709. {209315, 17080},
  710. {205326, 17080},
  711. {203334, 13629},
  712. {204493, 11616}
  713. }
  714. },
  715. }
  716. };
  717. template<nfp::NfpLevel lvl, Coord SCALE>
  718. void testNfp(const std::vector<ItemPair>& testdata) {
  719. using namespace libnest2d;
  720. Box bin(210*SCALE, 250*SCALE);
  721. int TEST_CASEcase = 0;
  722. auto& exportfun = exportSVG<SCALE>;
  723. auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){
  724. TEST_CASEcase++;
  725. orbiter.translate({210*SCALE, 0});
  726. auto&& nfp = nfp::noFitPolygon<lvl>(stationary.rawShape(),
  727. orbiter.transformedShape());
  728. placers::correctNfpPosition(nfp, stationary, orbiter);
  729. auto valid = shapelike::isValid(nfp.first);
  730. /*Item infp(nfp.first);
  731. if(!valid.first) {
  732. std::cout << "TEST_CASE instance: " << TEST_CASEidx << " "
  733. << valid.second << std::endl;
  734. std::vector<std::reference_wrapper<Item>> inp = {std::ref(infp)};
  735. exportfun(inp, bin, TEST_CASEidx);
  736. }*/
  737. REQUIRE(valid.first);
  738. Item infp(nfp.first);
  739. int i = 0;
  740. auto rorbiter = orbiter.transformedShape();
  741. auto vo = nfp::referenceVertex(rorbiter);
  742. REQUIRE(stationary.isInside(infp));
  743. for(auto v : infp) {
  744. auto dx = getX(v) - getX(vo);
  745. auto dy = getY(v) - getY(vo);
  746. Item tmp = orbiter;
  747. tmp.translate({dx, dy});
  748. bool touching = Item::touches(tmp, stationary);
  749. if(!touching || !valid.first) {
  750. std::vector<std::reference_wrapper<Item>> inp = {
  751. std::ref(stationary), std::ref(tmp), std::ref(infp)
  752. };
  753. exportfun(inp, TEST_CASEcase*i++);
  754. }
  755. REQUIRE(touching);
  756. }
  757. };
  758. unsigned tidx = 0;
  759. for(auto& td : testdata) {
  760. auto orbiter = td.orbiter;
  761. auto stationary = td.stationary;
  762. if (!libnest2d::is_clockwise<PolygonImpl>()) {
  763. auto porb = orbiter.rawShape();
  764. auto pstat = stationary.rawShape();
  765. std::reverse(sl::begin(porb), sl::end(porb));
  766. std::reverse(sl::begin(pstat), sl::end(pstat));
  767. orbiter = Item{porb};
  768. stationary = Item{pstat};
  769. }
  770. onetest(orbiter, stationary, tidx++);
  771. }
  772. tidx = 0;
  773. for(auto& td : testdata) {
  774. auto orbiter = td.stationary;
  775. auto stationary = td.orbiter;
  776. if (!libnest2d::is_clockwise<PolygonImpl>()) {
  777. auto porb = orbiter.rawShape();
  778. auto pstat = stationary.rawShape();
  779. std::reverse(sl::begin(porb), sl::end(porb));
  780. std::reverse(sl::begin(pstat), sl::end(pstat));
  781. orbiter = Item{porb};
  782. stationary = Item{pstat};
  783. }
  784. onetest(orbiter, stationary, tidx++);
  785. }
  786. }
  787. }
  788. TEST_CASE("nfpConvexConvex", "[Geometry]") {
  789. testNfp<nfp::NfpLevel::CONVEX_ONLY, 1>(nfp_testdata);
  790. }
  791. //TEST_CASE(GeometryAlgorithms, nfpConcaveConcave) {
  792. // TEST_CASENfp<NfpLevel::BOTH_CONCAVE, 1000>(nfp_concave_TEST_CASEdata);
  793. //}
  794. TEST_CASE("pointOnPolygonContour", "[Geometry]") {
  795. using namespace libnest2d;
  796. RectangleItem input(10, 10);
  797. placers::EdgeCache<PolygonImpl> ecache(input);
  798. auto first = *input.begin();
  799. REQUIRE(getX(first) == getX(ecache.coords(0)));
  800. REQUIRE(getY(first) == getY(ecache.coords(0)));
  801. auto last = *std::prev(input.end());
  802. REQUIRE(getX(last) == getX(ecache.coords(1.0)));
  803. REQUIRE(getY(last) == getY(ecache.coords(1.0)));
  804. for(int i = 0; i <= 100; i++) {
  805. auto v = ecache.coords(i*(0.01));
  806. REQUIRE(shapelike::touches(v, input.transformedShape()));
  807. }
  808. }
  809. TEST_CASE("mergePileWithPolygon", "[Geometry]") {
  810. using namespace libnest2d;
  811. RectangleItem rect1(10, 15);
  812. RectangleItem rect2(15, 15);
  813. RectangleItem rect3(20, 15);
  814. rect2.translate({10, 0});
  815. rect3.translate({25, 0});
  816. TMultiShape<PolygonImpl> pile;
  817. pile.push_back(rect1.transformedShape());
  818. pile.push_back(rect2.transformedShape());
  819. auto result = nfp::merge(pile, rect3.transformedShape());
  820. REQUIRE(result.size() == 1);
  821. RectangleItem ref(45, 15);
  822. REQUIRE(shapelike::area(result.front()) == Approx(ref.area()));
  823. }
  824. namespace {
  825. long double refMinAreaBox(const PolygonImpl& p) {
  826. auto it = sl::cbegin(p), itx = std::next(it);
  827. long double min_area = std::numeric_limits<long double>::max();
  828. auto update_min = [&min_area, &it, &itx, &p]() {
  829. Segment s(*it, *itx);
  830. PolygonImpl rotated = p;
  831. sl::rotate(rotated, -s.angleToXaxis());
  832. auto bb = sl::boundingBox(rotated);
  833. auto area = cast<long double>(sl::area(bb));
  834. if(min_area > area) min_area = area;
  835. };
  836. while(itx != sl::cend(p)) {
  837. update_min();
  838. ++it; ++itx;
  839. }
  840. it = std::prev(sl::cend(p)); itx = sl::cbegin(p);
  841. update_min();
  842. return min_area;
  843. }
  844. template<class T> struct BoostGCD {
  845. T operator()(const T &a, const T &b) { return boost::gcd(a, b); }
  846. };
  847. using Unit = int64_t;
  848. using Ratio = boost::rational<boost::multiprecision::int128_t>;
  849. }
  850. //TEST_CASE(GeometryAlgorithms, MinAreaBBCClk) {
  851. // auto u = [](ClipperLib::cInt n) { return n*1000000; };
  852. // PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}});
  853. // long double arearef = refMinAreaBox(poly);
  854. // long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area();
  855. // REQUIRE(std::abs(area - arearef) <= 500e6 );
  856. //}
  857. TEST_CASE("MinAreaBBWithRotatingCalipers", "[Geometry]") {
  858. long double err_epsilon = 500e6l;
  859. for(PathImpl rinput : PRINTER_PART_POLYGONS) {
  860. PolygonImpl poly(rinput);
  861. long double arearef = refMinAreaBox(poly);
  862. auto bb = minAreaBoundingBox<PathImpl, Unit, Ratio>(rinput);
  863. long double area = cast<long double>(bb.area());
  864. bool succ = std::abs(arearef - area) < err_epsilon;
  865. REQUIRE(succ);
  866. }
  867. for(PathImpl rinput : STEGOSAUR_POLYGONS) {
  868. // rinput.pop_back();
  869. std::reverse(rinput.begin(), rinput.end());
  870. PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
  871. long double arearef = refMinAreaBox(poly);
  872. auto bb = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly);
  873. long double area = cast<long double>(bb.area());
  874. bool succ = std::abs(arearef - area) < err_epsilon;
  875. REQUIRE(succ);
  876. }
  877. }
  878. template<class It> MultiPolygon merged_pile(It from, It to, int bin_id)
  879. {
  880. MultiPolygon pile;
  881. pile.reserve(size_t(to - from));
  882. for (auto it = from; it != to; ++it) {
  883. if (it->binId() == bin_id) pile.emplace_back(it->transformedShape());
  884. }
  885. return nfp::merge(pile);
  886. }
  887. TEST_CASE("Test for bed center distance optimization", "[Nesting], [NestKernels]")
  888. {
  889. static const constexpr Slic3r::ClipperLib::cInt W = 10000000;
  890. // Get the input items and define the bin.
  891. std::vector<RectangleItem> input(9, {W, W});
  892. auto bin = Box::infinite();
  893. NfpPlacer::Config pconfig;
  894. pconfig.object_function = [](const Item &item) -> double {
  895. return pl::magnsq<PointImpl, double>(item.boundingBox().center());
  896. };
  897. size_t bins = nest(input, bin, 0, NestConfig{pconfig});
  898. REQUIRE(bins == 1);
  899. // Gather the items into piles of arranged polygons...
  900. MultiPolygon pile;
  901. pile.reserve(input.size());
  902. for (auto &itm : input) {
  903. REQUIRE(itm.binId() == 0);
  904. pile.emplace_back(itm.transformedShape());
  905. }
  906. MultiPolygon m = merged_pile(input.begin(), input.end(), 0);
  907. REQUIRE(m.size() == 1);
  908. REQUIRE(sl::area(m) == Approx(9. * W * W));
  909. }
  910. TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]")
  911. {
  912. static const constexpr Slic3r::ClipperLib::cInt W = 10000000;
  913. static const constexpr size_t N = 100;
  914. // Get the input items and define the bin.
  915. std::vector<RectangleItem> input(N, {W, W});
  916. auto bin = Box::infinite();
  917. NfpPlacer::Config pconfig;
  918. pconfig.rotations = {0.};
  919. Box pile_box;
  920. pconfig.before_packing =
  921. [&pile_box](const MultiPolygon &pile,
  922. const _ItemGroup<PolygonImpl> &/*packed_items*/,
  923. const _ItemGroup<PolygonImpl> &/*remaining_items*/) {
  924. pile_box = sl::boundingBox(pile);
  925. };
  926. pconfig.object_function = [&pile_box](const Item &item) -> double {
  927. Box b = sl::boundingBox(item.boundingBox(), pile_box);
  928. double area = b.area<double>() / (double(W) * W);
  929. return -area;
  930. };
  931. size_t bins = nest(input, bin, 0, NestConfig{pconfig});
  932. // To debug:
  933. exportSVG<1000000>("out", input.begin(), input.end());
  934. REQUIRE(bins == 1);
  935. MultiPolygon pile = merged_pile(input.begin(), input.end(), 0);
  936. Box bb = sl::boundingBox(pile);
  937. // Here the result shall be a stairway of boxes
  938. REQUIRE(pile.size() == N);
  939. REQUIRE(bb.area() == double(N) * N * W * W);
  940. }