libnest2d_tests_main.cpp 28 KB

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