libnest2d_tests_main.cpp 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
  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 It>
  345. void exportSVG(const char *loc, It from, It to) {
  346. static const char* svg_header =
  347. R"raw(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  348. <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
  349. <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">
  350. )raw";
  351. // for(auto r : result) {
  352. std::fstream out(loc, std::fstream::out);
  353. if(out.is_open()) {
  354. out << svg_header;
  355. // Item rbin( RectangleItem(bin.width(), bin.height()) );
  356. // for(unsigned j = 0; j < rbin.vertexCount(); j++) {
  357. // auto v = rbin.vertex(j);
  358. // setY(v, -getY(v)/SCALE + 500 );
  359. // setX(v, getX(v)/SCALE);
  360. // rbin.setVertex(j, v);
  361. // }
  362. // out << shapelike::serialize<Formats::SVG>(rbin.rawShape()) << std::endl;
  363. for(auto it = from; it != to; ++it) {
  364. const Item &itm = *it;
  365. Item tsh(itm.transformedShape());
  366. for(unsigned j = 0; j < tsh.vertexCount(); j++) {
  367. auto v = tsh.vertex(j);
  368. setY(v, -getY(v)/SCALE + 500);
  369. setX(v, getX(v)/SCALE);
  370. tsh.setVertex(j, v);
  371. }
  372. out << shapelike::serialize<Formats::SVG>(tsh.rawShape()) << std::endl;
  373. }
  374. out << "\n</svg>" << std::endl;
  375. }
  376. out.close();
  377. // i++;
  378. // }
  379. }
  380. template<long long SCALE = 1>
  381. void exportSVG(std::vector<std::reference_wrapper<Item>>& result, int idx = 0) {
  382. exportSVG((std::string("out") + std::to_string(idx) + ".svg").c_str(),
  383. result.begin(), result.end());
  384. }
  385. }
  386. TEST_CASE("BottomLeftStressTest", "[Geometry]") {
  387. using namespace libnest2d;
  388. const Coord SCALE = 1000000;
  389. auto& input = prusaParts();
  390. Box bin(210*SCALE, 250*SCALE);
  391. BottomLeftPlacer placer(bin);
  392. auto it = input.begin();
  393. auto next = it;
  394. int i = 0;
  395. while(it != input.end() && ++next != input.end()) {
  396. placer.pack(*it);
  397. placer.pack(*next);
  398. auto result = placer.getItems();
  399. bool valid = true;
  400. if(result.size() == 2) {
  401. Item& r1 = result[0];
  402. Item& r2 = result[1];
  403. valid = !Item::intersects(r1, r2) || Item::touches(r1, r2);
  404. valid = (valid && !r1.isInside(r2) && !r2.isInside(r1));
  405. if(!valid) {
  406. std::cout << "error index: " << i << std::endl;
  407. exportSVG<SCALE>(result, i);
  408. }
  409. REQUIRE(valid);
  410. } else {
  411. std::cout << "something went terribly wrong!" << std::endl;
  412. FAIL();
  413. }
  414. placer.clearItems();
  415. it++;
  416. i++;
  417. }
  418. }
  419. TEST_CASE("convexHull", "[Geometry]") {
  420. using namespace libnest2d;
  421. ClipperLib::Path poly = PRINTER_PART_POLYGONS[0];
  422. auto chull = sl::convexHull(poly);
  423. REQUIRE(chull.size() == poly.size());
  424. }
  425. TEST_CASE("PrusaPartsShouldFitIntoTwoBins", "[Nesting]") {
  426. // Get the input items and define the bin.
  427. std::vector<Item> input = prusaParts();
  428. auto bin = Box(250000000, 210000000);
  429. // Do the nesting. Check in each step if the remaining items are less than
  430. // in the previous step. (Some algorithms can place more items in one step)
  431. size_t pcount = input.size();
  432. size_t bins = libnest2d::nest(input, bin, 0, {},
  433. ProgressFunction{[&pcount](unsigned cnt) {
  434. REQUIRE(cnt < pcount);
  435. pcount = cnt;
  436. }});
  437. // For prusa parts, 2 bins should be enough...
  438. REQUIRE(bins > 0u);
  439. REQUIRE(bins <= 2u);
  440. // All parts should be processed by the algorithm
  441. REQUIRE(
  442. std::all_of(input.begin(), input.end(), [](const Item &itm) {
  443. return itm.binId() != BIN_ID_UNSET;
  444. }));
  445. // Gather the items into piles of arranged polygons...
  446. using Pile = TMultiShape<ClipperLib::Polygon>;
  447. std::vector<Pile> piles(bins);
  448. for (auto &itm : input)
  449. piles[size_t(itm.binId())].emplace_back(itm.transformedShape());
  450. // Now check all the piles, the bounding box of each pile should be inside
  451. // the defined bin.
  452. for (auto &pile : piles) {
  453. auto bb = sl::boundingBox(pile);
  454. REQUIRE(sl::isInside(bb, bin));
  455. }
  456. }
  457. TEST_CASE("EmptyItemShouldBeUntouched", "[Nesting]") {
  458. auto bin = Box(250000000, 210000000); // dummy bin
  459. std::vector<Item> items;
  460. items.emplace_back(Item{}); // Emplace empty item
  461. items.emplace_back(Item{0, 200, 0}); // Emplace zero area item
  462. size_t bins = libnest2d::nest(items, bin);
  463. REQUIRE(bins == 0u);
  464. for (auto &itm : items) REQUIRE(itm.binId() == BIN_ID_UNSET);
  465. }
  466. TEST_CASE("LargeItemShouldBeUntouched", "[Nesting]") {
  467. auto bin = Box(250000000, 210000000); // dummy bin
  468. std::vector<Item> items;
  469. items.emplace_back(RectangleItem{250000001, 210000001}); // Emplace large item
  470. size_t bins = libnest2d::nest(items, bin);
  471. REQUIRE(bins == 0u);
  472. REQUIRE(items.front().binId() == BIN_ID_UNSET);
  473. }
  474. TEST_CASE("Items can be preloaded", "[Nesting]") {
  475. auto bin = Box({0, 0}, {250000000, 210000000}); // dummy bin
  476. std::vector<Item> items;
  477. items.reserve(2);
  478. NestConfig<> cfg;
  479. cfg.placer_config.alignment = NestConfig<>::Placement::Alignment::DONT_ALIGN;
  480. items.emplace_back(RectangleItem{10000000, 10000000});
  481. Item &fixed_rect = items.back();
  482. fixed_rect.translate(bin.center());
  483. items.emplace_back(RectangleItem{20000000, 20000000});
  484. Item &movable_rect = items.back();
  485. movable_rect.translate(bin.center());
  486. SECTION("Preloaded Item should be untouched") {
  487. fixed_rect.markAsFixedInBin(0);
  488. size_t bins = libnest2d::nest(items, bin, 0, cfg);
  489. REQUIRE(bins == 1);
  490. REQUIRE(fixed_rect.binId() == 0);
  491. REQUIRE(fixed_rect.translation().X == bin.center().X);
  492. REQUIRE(fixed_rect.translation().Y == bin.center().Y);
  493. REQUIRE(movable_rect.binId() == 0);
  494. REQUIRE(movable_rect.translation().X != bin.center().X);
  495. REQUIRE(movable_rect.translation().Y != bin.center().Y);
  496. }
  497. SECTION("Preloaded Item should not affect free bins") {
  498. fixed_rect.markAsFixedInBin(1);
  499. size_t bins = libnest2d::nest(items, bin, 0, cfg);
  500. REQUIRE(bins == 2);
  501. REQUIRE(fixed_rect.binId() == 1);
  502. REQUIRE(fixed_rect.translation().X == bin.center().X);
  503. REQUIRE(fixed_rect.translation().Y == bin.center().Y);
  504. REQUIRE(movable_rect.binId() == 0);
  505. auto bb = movable_rect.boundingBox();
  506. REQUIRE(bb.center().X == bin.center().X);
  507. REQUIRE(bb.center().Y == bin.center().Y);
  508. }
  509. }
  510. namespace {
  511. struct ItemPair {
  512. Item orbiter;
  513. Item stationary;
  514. };
  515. std::vector<ItemPair> nfp_testdata = {
  516. {
  517. {
  518. {80, 50},
  519. {100, 70},
  520. {120, 50},
  521. {80, 50}
  522. },
  523. {
  524. {10, 10},
  525. {10, 40},
  526. {40, 40},
  527. {40, 10},
  528. {10, 10}
  529. }
  530. },
  531. {
  532. {
  533. {80, 50},
  534. {60, 70},
  535. {80, 90},
  536. {120, 90},
  537. {140, 70},
  538. {120, 50},
  539. {80, 50}
  540. },
  541. {
  542. {10, 10},
  543. {10, 40},
  544. {40, 40},
  545. {40, 10},
  546. {10, 10}
  547. }
  548. },
  549. {
  550. {
  551. {40, 10},
  552. {30, 10},
  553. {20, 20},
  554. {20, 30},
  555. {30, 40},
  556. {40, 40},
  557. {50, 30},
  558. {50, 20},
  559. {40, 10}
  560. },
  561. {
  562. {80, 0},
  563. {80, 30},
  564. {110, 30},
  565. {110, 0},
  566. {80, 0}
  567. }
  568. },
  569. {
  570. {
  571. {117, 107},
  572. {118, 109},
  573. {120, 112},
  574. {122, 113},
  575. {128, 113},
  576. {130, 112},
  577. {132, 109},
  578. {133, 107},
  579. {133, 103},
  580. {132, 101},
  581. {130, 98},
  582. {128, 97},
  583. {122, 97},
  584. {120, 98},
  585. {118, 101},
  586. {117, 103},
  587. {117, 107}
  588. },
  589. {
  590. {102, 116},
  591. {111, 126},
  592. {114, 126},
  593. {144, 106},
  594. {148, 100},
  595. {148, 85},
  596. {147, 84},
  597. {102, 84},
  598. {102, 116},
  599. }
  600. },
  601. {
  602. {
  603. {99, 122},
  604. {108, 140},
  605. {110, 142},
  606. {139, 142},
  607. {151, 122},
  608. {151, 102},
  609. {142, 70},
  610. {139, 68},
  611. {111, 68},
  612. {108, 70},
  613. {99, 102},
  614. {99, 122},
  615. },
  616. {
  617. {107, 124},
  618. {128, 125},
  619. {133, 125},
  620. {136, 124},
  621. {140, 121},
  622. {142, 119},
  623. {143, 116},
  624. {143, 109},
  625. {141, 93},
  626. {139, 89},
  627. {136, 86},
  628. {134, 85},
  629. {108, 85},
  630. {107, 86},
  631. {107, 124},
  632. }
  633. },
  634. {
  635. {
  636. {91, 100},
  637. {94, 144},
  638. {117, 153},
  639. {118, 153},
  640. {159, 112},
  641. {159, 110},
  642. {156, 66},
  643. {133, 57},
  644. {132, 57},
  645. {91, 98},
  646. {91, 100},
  647. },
  648. {
  649. {101, 90},
  650. {103, 98},
  651. {107, 113},
  652. {114, 125},
  653. {115, 126},
  654. {135, 126},
  655. {136, 125},
  656. {144, 114},
  657. {149, 90},
  658. {149, 89},
  659. {148, 87},
  660. {145, 84},
  661. {105, 84},
  662. {102, 87},
  663. {101, 89},
  664. {101, 90},
  665. }
  666. }
  667. };
  668. std::vector<ItemPair> nfp_concave_testdata = {
  669. { // ItemPair
  670. {
  671. {
  672. {533726, 142141},
  673. {532359, 143386},
  674. {530141, 142155},
  675. {528649, 160091},
  676. {533659, 157607},
  677. {538669, 160091},
  678. {537178, 142155},
  679. {534959, 143386},
  680. {533726, 142141},
  681. }
  682. },
  683. {
  684. {
  685. {118305, 11603},
  686. {118311, 26616},
  687. {113311, 26611},
  688. {109311, 29604},
  689. {109300, 44608},
  690. {109311, 49631},
  691. {113300, 52636},
  692. {118311, 52636},
  693. {118308, 103636},
  694. {223830, 103636},
  695. {236845, 90642},
  696. {236832, 11630},
  697. {232825, 11616},
  698. {210149, 11616},
  699. {211308, 13625},
  700. {209315, 17080},
  701. {205326, 17080},
  702. {203334, 13629},
  703. {204493, 11616},
  704. {118305, 11603},
  705. }
  706. },
  707. }
  708. };
  709. template<nfp::NfpLevel lvl, Coord SCALE>
  710. void testNfp(const std::vector<ItemPair>& testdata) {
  711. using namespace libnest2d;
  712. Box bin(210*SCALE, 250*SCALE);
  713. int TEST_CASEcase = 0;
  714. auto& exportfun = exportSVG<SCALE>;
  715. auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){
  716. TEST_CASEcase++;
  717. orbiter.translate({210*SCALE, 0});
  718. auto&& nfp = nfp::noFitPolygon<lvl>(stationary.rawShape(),
  719. orbiter.transformedShape());
  720. placers::correctNfpPosition(nfp, stationary, orbiter);
  721. auto valid = shapelike::isValid(nfp.first);
  722. /*Item infp(nfp.first);
  723. if(!valid.first) {
  724. std::cout << "TEST_CASE instance: " << TEST_CASEidx << " "
  725. << valid.second << std::endl;
  726. std::vector<std::reference_wrapper<Item>> inp = {std::ref(infp)};
  727. exportfun(inp, bin, TEST_CASEidx);
  728. }*/
  729. REQUIRE(valid.first);
  730. Item infp(nfp.first);
  731. int i = 0;
  732. auto rorbiter = orbiter.transformedShape();
  733. auto vo = nfp::referenceVertex(rorbiter);
  734. REQUIRE(stationary.isInside(infp));
  735. for(auto v : infp) {
  736. auto dx = getX(v) - getX(vo);
  737. auto dy = getY(v) - getY(vo);
  738. Item tmp = orbiter;
  739. tmp.translate({dx, dy});
  740. bool touching = Item::touches(tmp, stationary);
  741. if(!touching || !valid.first) {
  742. std::vector<std::reference_wrapper<Item>> inp = {
  743. std::ref(stationary), std::ref(tmp), std::ref(infp)
  744. };
  745. exportfun(inp, TEST_CASEcase*i++);
  746. }
  747. REQUIRE(touching);
  748. }
  749. };
  750. unsigned tidx = 0;
  751. for(auto& td : testdata) {
  752. auto orbiter = td.orbiter;
  753. auto stationary = td.stationary;
  754. onetest(orbiter, stationary, tidx++);
  755. }
  756. tidx = 0;
  757. for(auto& td : testdata) {
  758. auto orbiter = td.stationary;
  759. auto stationary = td.orbiter;
  760. onetest(orbiter, stationary, tidx++);
  761. }
  762. }
  763. }
  764. TEST_CASE("nfpConvexConvex", "[Geometry]") {
  765. testNfp<nfp::NfpLevel::CONVEX_ONLY, 1>(nfp_testdata);
  766. }
  767. //TEST_CASE(GeometryAlgorithms, nfpConcaveConcave) {
  768. // TEST_CASENfp<NfpLevel::BOTH_CONCAVE, 1000>(nfp_concave_TEST_CASEdata);
  769. //}
  770. TEST_CASE("pointOnPolygonContour", "[Geometry]") {
  771. using namespace libnest2d;
  772. RectangleItem input(10, 10);
  773. placers::EdgeCache<PolygonImpl> ecache(input);
  774. auto first = *input.begin();
  775. REQUIRE(getX(first) == getX(ecache.coords(0)));
  776. REQUIRE(getY(first) == getY(ecache.coords(0)));
  777. auto last = *std::prev(input.end());
  778. REQUIRE(getX(last) == getX(ecache.coords(1.0)));
  779. REQUIRE(getY(last) == getY(ecache.coords(1.0)));
  780. for(int i = 0; i <= 100; i++) {
  781. auto v = ecache.coords(i*(0.01));
  782. REQUIRE(shapelike::touches(v, input.transformedShape()));
  783. }
  784. }
  785. TEST_CASE("mergePileWithPolygon", "[Geometry]") {
  786. using namespace libnest2d;
  787. RectangleItem rect1(10, 15);
  788. RectangleItem rect2(15, 15);
  789. RectangleItem rect3(20, 15);
  790. rect2.translate({10, 0});
  791. rect3.translate({25, 0});
  792. TMultiShape<PolygonImpl> pile;
  793. pile.push_back(rect1.transformedShape());
  794. pile.push_back(rect2.transformedShape());
  795. auto result = nfp::merge(pile, rect3.transformedShape());
  796. REQUIRE(result.size() == 1);
  797. RectangleItem ref(45, 15);
  798. REQUIRE(shapelike::area(result.front()) == Approx(ref.area()));
  799. }
  800. namespace {
  801. long double refMinAreaBox(const PolygonImpl& p) {
  802. auto it = sl::cbegin(p), itx = std::next(it);
  803. long double min_area = std::numeric_limits<long double>::max();
  804. auto update_min = [&min_area, &it, &itx, &p]() {
  805. Segment s(*it, *itx);
  806. PolygonImpl rotated = p;
  807. sl::rotate(rotated, -s.angleToXaxis());
  808. auto bb = sl::boundingBox(rotated);
  809. auto area = cast<long double>(sl::area(bb));
  810. if(min_area > area) min_area = area;
  811. };
  812. while(itx != sl::cend(p)) {
  813. update_min();
  814. ++it; ++itx;
  815. }
  816. it = std::prev(sl::cend(p)); itx = sl::cbegin(p);
  817. update_min();
  818. return min_area;
  819. }
  820. template<class T> struct BoostGCD {
  821. T operator()(const T &a, const T &b) { return boost::gcd(a, b); }
  822. };
  823. using Unit = int64_t;
  824. using Ratio = boost::rational<boost::multiprecision::int128_t>;
  825. }
  826. //TEST_CASE(GeometryAlgorithms, MinAreaBBCClk) {
  827. // auto u = [](ClipperLib::cInt n) { return n*1000000; };
  828. // PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}});
  829. // long double arearef = refMinAreaBox(poly);
  830. // long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area();
  831. // REQUIRE(std::abs(area - arearef) <= 500e6 );
  832. //}
  833. TEST_CASE("MinAreaBBWithRotatingCalipers", "[Geometry]") {
  834. long double err_epsilon = 500e6l;
  835. for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) {
  836. PolygonImpl poly(rinput);
  837. long double arearef = refMinAreaBox(poly);
  838. auto bb = minAreaBoundingBox<PathImpl, Unit, Ratio>(rinput);
  839. long double area = cast<long double>(bb.area());
  840. bool succ = std::abs(arearef - area) < err_epsilon;
  841. REQUIRE(succ);
  842. }
  843. for(ClipperLib::Path rinput : STEGOSAUR_POLYGONS) {
  844. rinput.pop_back();
  845. std::reverse(rinput.begin(), rinput.end());
  846. PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
  847. long double arearef = refMinAreaBox(poly);
  848. auto bb = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly);
  849. long double area = cast<long double>(bb.area());
  850. bool succ = std::abs(arearef - area) < err_epsilon;
  851. REQUIRE(succ);
  852. }
  853. }
  854. template<class It> MultiPolygon merged_pile(It from, It to, int bin_id)
  855. {
  856. MultiPolygon pile;
  857. pile.reserve(size_t(to - from));
  858. for (auto it = from; it != to; ++it) {
  859. if (it->binId() == bin_id) pile.emplace_back(it->transformedShape());
  860. }
  861. return nfp::merge(pile);
  862. }
  863. TEST_CASE("Test for bed center distance optimization", "[Nesting], [NestKernels]")
  864. {
  865. static const constexpr ClipperLib::cInt W = 10000000;
  866. // Get the input items and define the bin.
  867. std::vector<RectangleItem> input(9, {W, W});
  868. auto bin = Box::infinite();
  869. NfpPlacer::Config pconfig;
  870. pconfig.object_function = [](const Item &item) -> double {
  871. return pl::magnsq<PointImpl, double>(item.boundingBox().center());
  872. };
  873. size_t bins = nest(input, bin, 0, NestConfig{pconfig});
  874. REQUIRE(bins == 1);
  875. // Gather the items into piles of arranged polygons...
  876. MultiPolygon pile;
  877. pile.reserve(input.size());
  878. for (auto &itm : input) {
  879. REQUIRE(itm.binId() == 0);
  880. pile.emplace_back(itm.transformedShape());
  881. }
  882. MultiPolygon m = merged_pile(input.begin(), input.end(), 0);
  883. REQUIRE(m.size() == 1);
  884. REQUIRE(sl::area(m) == Approx(9. * W * W));
  885. }
  886. TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]")
  887. {
  888. static const constexpr ClipperLib::cInt W = 10000000;
  889. static const constexpr size_t N = 100;
  890. // Get the input items and define the bin.
  891. std::vector<RectangleItem> input(N, {W, W});
  892. auto bin = Box::infinite();
  893. NfpPlacer::Config pconfig;
  894. pconfig.rotations = {0.};
  895. Box pile_box;
  896. pconfig.before_packing =
  897. [&pile_box](const MultiPolygon &pile,
  898. const _ItemGroup<PolygonImpl> &/*packed_items*/,
  899. const _ItemGroup<PolygonImpl> &/*remaining_items*/) {
  900. pile_box = sl::boundingBox(pile);
  901. };
  902. pconfig.object_function = [&pile_box](const Item &item) -> double {
  903. Box b = sl::boundingBox(item.boundingBox(), pile_box);
  904. double area = b.area<double>() / (W * W);
  905. return -area;
  906. };
  907. size_t bins = nest(input, bin, 0, NestConfig{pconfig});
  908. // To debug:
  909. exportSVG<1000000>("out", input.begin(), input.end());
  910. REQUIRE(bins == 1);
  911. MultiPolygon pile = merged_pile(input.begin(), input.end(), 0);
  912. Box bb = sl::boundingBox(pile);
  913. // Here the result shall be a stairway of boxes
  914. REQUIRE(pile.size() == N);
  915. REQUIRE(bb.area() == N * N * W * W);
  916. }