libnest2d_tests_main.cpp 31 KB

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