test_arrange.cpp 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122
  1. #include <catch2/catch.hpp>
  2. #include "test_utils.hpp"
  3. #include <libslic3r/Execution/ExecutionSeq.hpp>
  4. #include <libslic3r/Arrange/Core/ArrangeBase.hpp>
  5. #include <libslic3r/Arrange/Core/ArrangeFirstFit.hpp>
  6. #include <libslic3r/Arrange/Core/NFP/PackStrategyNFP.hpp>
  7. #include <libslic3r/Arrange/Core/NFP/RectangleOverfitPackingStrategy.hpp>
  8. #include <libslic3r/Arrange/Core/NFP/Kernels/GravityKernel.hpp>
  9. #include <libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp>
  10. #include <libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp>
  11. #include <libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp>
  12. #include <libslic3r/Arrange/Core/NFP/CircularEdgeIterator.hpp>
  13. #include <libslic3r/Arrange/Items/SimpleArrangeItem.hpp>
  14. #include <libslic3r/Arrange/Items/ArrangeItem.hpp>
  15. #include <libslic3r/Arrange/Items/TrafoOnlyArrangeItem.hpp>
  16. #include <libslic3r/Model.hpp>
  17. #include <libslic3r/Optimize/BruteforceOptimizer.hpp>
  18. #include <libslic3r/Geometry/ConvexHull.hpp>
  19. #include <libslic3r/ClipperUtils.hpp>
  20. #include "../data/prusaparts.hpp"
  21. #include <libslic3r/SVG.hpp>
  22. #include <libslic3r/BoostAdapter.hpp>
  23. #include <boost/log/trivial.hpp>
  24. #include <boost/geometry/geometries/polygon.hpp>
  25. #include <boost/geometry/geometries/point_xy.hpp>
  26. #include <boost/geometry/geometries/multi_polygon.hpp>
  27. #include <boost/geometry/algorithms/convert.hpp>
  28. #include <random>
  29. template<class ArrItem = Slic3r::arr2::ArrangeItem>
  30. static std::vector<ArrItem> prusa_parts(double infl = 0.) {
  31. using namespace Slic3r;
  32. std::vector<ArrItem> ret;
  33. if(ret.empty()) {
  34. ret.reserve(PRUSA_PART_POLYGONS.size());
  35. for(auto& inp : PRUSA_PART_POLYGONS) {
  36. ExPolygons inp_cpy{ExPolygon(inp)};
  37. inp_cpy.back().contour.points.pop_back();
  38. std::reverse(inp_cpy.back().contour.begin(),
  39. inp_cpy.back().contour.end());
  40. REQUIRE(inp_cpy.back().contour.is_counter_clockwise());
  41. if (infl > 0.)
  42. inp_cpy = offset_ex(inp_cpy, scaled(std::ceil(infl / 2.)));
  43. ArrItem item{Geometry::convex_hull(inp_cpy)};
  44. ret.emplace_back(std::move(item));
  45. }
  46. }
  47. return ret;
  48. }
  49. static std::vector<Slic3r::arr2::ArrangeItem> prusa_parts_ex(double infl = 0.)
  50. {
  51. using namespace Slic3r;
  52. std::vector<arr2::ArrangeItem> ret;
  53. if (ret.empty()) {
  54. ret.reserve(PRUSA_PART_POLYGONS_EX.size());
  55. for (auto &inp : PRUSA_PART_POLYGONS_EX) {
  56. ExPolygons inp_cpy{inp};
  57. REQUIRE(std::all_of(inp_cpy.begin(), inp_cpy.end(),
  58. [](const ExPolygon &p) {
  59. return p.contour.is_counter_clockwise();
  60. }));
  61. if (infl > 0.)
  62. inp_cpy = offset_ex(inp_cpy, scaled(std::ceil(infl / 2.)));
  63. Point c = get_extents(inp_cpy).center();
  64. for (auto &p : inp_cpy)
  65. p.translate(-c);
  66. arr2::ArrangeItem item{inp_cpy};
  67. ret.emplace_back(std::move(item));
  68. }
  69. }
  70. return ret;
  71. }
  72. using Slic3r::arr2::ArrangeItem;
  73. using Slic3r::arr2::DecomposedShape;
  74. struct ItemPair {
  75. ArrangeItem orbiter;
  76. ArrangeItem stationary;
  77. };
  78. using Slic3r::scaled;
  79. using Slic3r::Vec2f;
  80. std::vector<ItemPair> nfp_testdata = {
  81. {
  82. ArrangeItem { DecomposedShape {
  83. scaled(Vec2f{80, 50}) ,
  84. scaled(Vec2f{120, 50}),
  85. scaled(Vec2f{100, 70}),
  86. }},
  87. ArrangeItem { DecomposedShape {
  88. scaled(Vec2f{40, 10}),
  89. scaled(Vec2f{40, 40}),
  90. scaled(Vec2f{10, 40}),
  91. scaled(Vec2f{10, 10}),
  92. }}
  93. },
  94. {
  95. ArrangeItem {
  96. scaled(Vec2f{120, 50}),
  97. scaled(Vec2f{140, 70}),
  98. scaled(Vec2f{120, 90}),
  99. scaled(Vec2f{80, 90}) ,
  100. scaled(Vec2f{60, 70}) ,
  101. scaled(Vec2f{80, 50}) ,
  102. },
  103. ArrangeItem {
  104. scaled(Vec2f{40, 10}),
  105. scaled(Vec2f{40, 40}),
  106. scaled(Vec2f{10, 40}),
  107. scaled(Vec2f{10, 10}),
  108. }
  109. },
  110. };
  111. struct PolyPair { Slic3r::ExPolygon orbiter; Slic3r::ExPolygon stationary; };
  112. std::vector<PolyPair> nfp_concave_testdata = {
  113. { // ItemPair
  114. {
  115. {
  116. scaled(Vec2f{53.3726f, 14.2141f}),
  117. scaled(Vec2f{53.2359f, 14.3386f}),
  118. scaled(Vec2f{53.0141f, 14.2155f}),
  119. scaled(Vec2f{52.8649f, 16.0091f}),
  120. scaled(Vec2f{53.3659f, 15.7607f}),
  121. scaled(Vec2f{53.8669f, 16.0091f}),
  122. scaled(Vec2f{53.7178f, 14.2155f}),
  123. scaled(Vec2f{53.4959f, 14.3386f})
  124. }
  125. },
  126. {
  127. {
  128. scaled(Vec2f{11.8305f, 1.1603f}),
  129. scaled(Vec2f{11.8311f, 2.6616f}),
  130. scaled(Vec2f{11.3311f, 2.6611f}),
  131. scaled(Vec2f{10.9311f, 2.9604f}),
  132. scaled(Vec2f{10.9300f, 4.4608f}),
  133. scaled(Vec2f{10.9311f, 4.9631f}),
  134. scaled(Vec2f{11.3300f, 5.2636f}),
  135. scaled(Vec2f{11.8311f, 5.2636f}),
  136. scaled(Vec2f{11.8308f, 10.3636f}),
  137. scaled(Vec2f{22.3830f, 10.3636f}),
  138. scaled(Vec2f{23.6845f, 9.0642f}),
  139. scaled(Vec2f{23.6832f, 1.1630f}),
  140. scaled(Vec2f{23.2825f, 1.1616f}),
  141. scaled(Vec2f{21.0149f, 1.1616f}),
  142. scaled(Vec2f{21.1308f, 1.3625f}),
  143. scaled(Vec2f{20.9315f, 1.7080f}),
  144. scaled(Vec2f{20.5326f, 1.7080f}),
  145. scaled(Vec2f{20.3334f, 1.3629f}),
  146. scaled(Vec2f{20.4493f, 1.1616f})
  147. }
  148. },
  149. }
  150. };
  151. static void check_nfp(const std::string & outfile_prefix,
  152. const Slic3r::Polygons &stationary,
  153. const Slic3r::Polygons &orbiter,
  154. const Slic3r::ExPolygons &bedpoly,
  155. const Slic3r::ExPolygons &nfp)
  156. {
  157. using namespace Slic3r;
  158. auto stationary_ex = to_expolygons(stationary);
  159. auto bedbb = get_extents(bedpoly);
  160. bedbb.offset(scaled(1.));
  161. auto bedrect = arr2::to_rectangle(bedbb);
  162. ExPolygons bed_negative = diff_ex(bedrect, bedpoly);
  163. ExPolygons orb_ex_r = to_expolygons(orbiter);
  164. ExPolygons orb_ex_r_ch = {ExPolygon(Geometry::convex_hull(orb_ex_r))};
  165. auto orb_ex_offs_pos_r = offset_ex(orb_ex_r, scaled<float>(EPSILON));
  166. auto orb_ex_offs_neg_r = offset_ex(orb_ex_r, -scaled<float>(EPSILON));
  167. auto orb_ex_offs_pos_r_ch = offset_ex(orb_ex_r_ch, scaled<float>(EPSILON));
  168. auto orb_ex_offs_neg_r_ch = offset_ex(orb_ex_r_ch, -scaled<float>(EPSILON));
  169. auto bedpoly_offs = offset_ex(bedpoly, SCALED_EPSILON);
  170. auto check_at_nfppos = [&](const Point &pos) {
  171. ExPolygons orb_ex = orb_ex_r;
  172. Point d = pos - reference_vertex(orbiter);
  173. for (ExPolygon &poly : orb_ex)
  174. poly.translate(d);
  175. bool touching = false;
  176. bool check_failed = false;
  177. bool within_bed = false;
  178. bool touches_fixed = false;
  179. bool touches_bedwall = false;
  180. try {
  181. auto beddiff = diff_ex(orb_ex, bedpoly_offs);
  182. if (beddiff.empty())
  183. within_bed = true;
  184. auto orb_ex_offs_pos = orb_ex_offs_pos_r;
  185. for (ExPolygon &poly: orb_ex_offs_pos)
  186. poly.translate(d);
  187. auto orb_ex_offs_neg = orb_ex_offs_neg_r;
  188. for (ExPolygon &poly: orb_ex_offs_neg)
  189. poly.translate(d);
  190. auto orb_ex_offs_pos_ch = orb_ex_offs_pos_r_ch;
  191. for (ExPolygon &poly: orb_ex_offs_pos_ch)
  192. poly.translate(d);
  193. auto orb_ex_offs_neg_ch = orb_ex_offs_neg_r_ch;
  194. for (ExPolygon &poly: orb_ex_offs_neg_ch)
  195. poly.translate(d);
  196. if (!touches_bedwall) {
  197. auto inters_pos = intersection_ex(bed_negative, orb_ex_offs_pos_ch);
  198. auto inters_neg = intersection_ex(bed_negative, orb_ex_offs_neg_ch);
  199. if (!inters_pos.empty() && inters_neg.empty())
  200. touches_bedwall = true;
  201. }
  202. if (!touches_fixed) {
  203. auto inters_pos = intersection_ex(stationary_ex, orb_ex_offs_pos);
  204. auto inters_neg = intersection_ex(stationary_ex, orb_ex_offs_neg);
  205. if (!inters_pos.empty() && inters_neg.empty())
  206. touches_fixed = true;
  207. }
  208. touching = within_bed && (touches_fixed || touches_bedwall);
  209. } catch (...) {
  210. check_failed = true;
  211. touching = false;
  212. }
  213. #ifndef NDEBUG
  214. if (!touching || check_failed) {
  215. auto bb = get_extents(bedpoly);
  216. SVG svg(outfile_prefix + ".svg", bb, 0, true);
  217. svg.draw(orbiter, "orange");
  218. svg.draw(stationary, "yellow");
  219. svg.draw(bed_negative, "blue", 0.5f);
  220. svg.draw(nfp, "green", 0.5f);
  221. svg.draw(orb_ex, "red");
  222. svg.Close();
  223. }
  224. #endif
  225. REQUIRE(!check_failed);
  226. REQUIRE(touching);
  227. };
  228. if (nfp.empty()) {
  229. auto bb = get_extents(bedpoly);
  230. SVG svg(outfile_prefix + ".svg", bb, 0, true);
  231. svg.draw(orbiter, "orange");
  232. svg.draw(stationary, "yellow");
  233. svg.draw(bedpoly, "blue", 0.5f);
  234. svg.Close();
  235. }
  236. REQUIRE(!nfp.empty());
  237. for (const ExPolygon &nfp_part : nfp) {
  238. for (const Point &nfp_pos : nfp_part.contour) {
  239. check_at_nfppos(nfp_pos);
  240. }
  241. for (const Polygon &h : nfp_part.holes)
  242. for (const Point &nfp_pos : h) {
  243. check_at_nfppos(nfp_pos);
  244. }
  245. }
  246. }
  247. template<class Bed, class PairType>
  248. void test_itempairs(const std::vector<PairType> &testdata,
  249. const Bed &bed,
  250. const std::string &outfile_prefix = "")
  251. {
  252. using namespace Slic3r;
  253. size_t testnum = 0;
  254. ExPolygons bedshape = arr2::to_expolygons(bed);
  255. for(auto td : testdata) {
  256. Polygons orbiter = td.orbiter.envelope().transformed_outline();
  257. Polygons stationary = td.stationary.shape().transformed_outline();
  258. Point center = bounding_box(bed).center();
  259. Point stat_c = get_extents(stationary).center();
  260. Point d = center - stat_c;
  261. arr2::translate(td.stationary, d);
  262. stationary = td.stationary.shape().transformed_outline();
  263. std::array<std::reference_wrapper<const decltype(td.stationary)>, 1> fixed = {{td.stationary}};
  264. auto nfp = arr2::calculate_nfp(td.orbiter, arr2::default_context(fixed), bed);
  265. check_nfp(outfile_prefix + "nfp_test_" + std::to_string(testnum),
  266. stationary,
  267. orbiter,
  268. bedshape,
  269. nfp);
  270. testnum++;
  271. }
  272. }
  273. template<class It, class Fn>
  274. static void foreach_combo(const Slic3r::Range<It> &range, const Fn &fn)
  275. {
  276. std::vector<bool> pairs(range.size(), false);
  277. assert(range.size() >= 2);
  278. pairs[range.size() - 1] = true;
  279. pairs[range.size() - 2] = true;
  280. do {
  281. std::vector<typename std::iterator_traits<It>::value_type> items;
  282. for (size_t i = 0; i < pairs.size(); i++) {
  283. if (pairs[i]) {
  284. auto it = range.begin();
  285. std::advance(it, i);
  286. items.emplace_back(*it);
  287. }
  288. }
  289. fn (items[0], items[1]);
  290. } while (std::next_permutation(pairs.begin(), pairs.end()));
  291. }
  292. TEST_CASE("Static type tests for arrange items", "[arrange2]")
  293. {
  294. using namespace Slic3r;
  295. REQUIRE(arr2::IsDataStore<arr2::ArrangeItem>);
  296. REQUIRE(arr2::IsMutableItem<arr2::ArrangeItem>);
  297. REQUIRE(! arr2::IsDataStore<arr2::SimpleArrangeItem>);
  298. REQUIRE(arr2::IsMutableItem<arr2::SimpleArrangeItem>);
  299. REQUIRE(arr2::IsDataStore<arr2::TrafoOnlyArrangeItem>);
  300. REQUIRE(arr2::IsMutableItem<arr2::TrafoOnlyArrangeItem>);
  301. }
  302. template<class Bed> Bed init_bed() { return {}; }
  303. template<> inline Slic3r::arr2::InfiniteBed init_bed<Slic3r::arr2::InfiniteBed>()
  304. {
  305. return Slic3r::arr2::InfiniteBed{{scaled(250.) / 2., scaled(210.) / 2.}};
  306. }
  307. template<> inline Slic3r::arr2::RectangleBed init_bed<Slic3r::arr2::RectangleBed>()
  308. {
  309. return Slic3r::arr2::RectangleBed{scaled(500.), scaled(500.)};
  310. }
  311. template<> inline Slic3r::arr2::CircleBed init_bed<Slic3r::arr2::CircleBed>()
  312. {
  313. return Slic3r::arr2::CircleBed{Slic3r::Point::Zero(), scaled(300.)};
  314. }
  315. template<> inline Slic3r::arr2::IrregularBed init_bed<Slic3r::arr2::IrregularBed>()
  316. {
  317. using namespace Slic3r;
  318. BoundingBox bb_outer{Point::Zero(), {scaled(500.), scaled(500.)}};
  319. BoundingBox corner{Point::Zero(), {scaled(50.), scaled(50.)}};
  320. auto transl = [](BoundingBox bb, Point t) { bb.translate(t); return bb; };
  321. Polygons rect_outer = {arr2::to_rectangle(bb_outer)};
  322. Polygons corners = {arr2::to_rectangle(transl(corner, {scaled(10.), scaled(10.)})),
  323. arr2::to_rectangle(transl(corner, {scaled(440.), scaled(10.)})),
  324. arr2::to_rectangle(transl(corner, {scaled(440.), scaled(440.)})),
  325. arr2::to_rectangle(transl(corner, {scaled(10.), scaled(440.)})),
  326. arr2::to_rectangle(BoundingBox({scaled(80.), scaled(450.)}, {scaled(420.), scaled(510.)})),
  327. arr2::to_rectangle(BoundingBox({scaled(80.), scaled(-10.)}, {scaled(420.), scaled(50.)}))};
  328. ExPolygons bedshape = diff_ex(rect_outer, corners);
  329. return arr2::IrregularBed{bedshape};
  330. }
  331. template<class Bed> std::string bedtype_str(const Bed &bed)
  332. {
  333. return "";
  334. }
  335. inline std::string bedtype_str(const Slic3r::arr2::RectangleBed &bed)
  336. {
  337. return "RectangleBed";
  338. }
  339. inline std::string bedtype_str(const Slic3r::arr2::CircleBed &bed)
  340. {
  341. return "CircleBed";
  342. }
  343. inline std::string bedtype_str(const Slic3r::arr2::InfiniteBed &bed)
  344. {
  345. return "InfiniteBed";
  346. }
  347. inline std::string bedtype_str(const Slic3r::arr2::IrregularBed &bed)
  348. {
  349. return "IrregularBed";
  350. }
  351. TEST_CASE("NFP should be empty if item cannot fit into bed", "[arrange2]") {
  352. using namespace Slic3r;
  353. arr2::RectangleBed bed{scaled(10.), scaled(10.)};
  354. ExPolygons bedshape = arr2::to_expolygons(bed);
  355. for(auto& td : nfp_testdata) {
  356. REQUIRE(&(td.orbiter.envelope()) == &(td.orbiter.shape()));
  357. REQUIRE(&(td.stationary.envelope()) == &(td.stationary.shape()));
  358. REQUIRE(td.orbiter.envelope().reference_vertex() ==
  359. td.orbiter.shape().reference_vertex());
  360. REQUIRE(td.stationary.envelope().reference_vertex() ==
  361. td.stationary.shape().reference_vertex());
  362. ArrangeItem cpy = td.stationary;
  363. REQUIRE(&(cpy.envelope()) == &(cpy.shape()));
  364. REQUIRE(cpy.envelope().reference_vertex() ==
  365. cpy.shape().reference_vertex());
  366. std::array<std::reference_wrapper<const ArrangeItem>, 1> fixed =
  367. {{td.stationary}};
  368. auto nfp = arr2::calculate_nfp(td.orbiter, default_context(fixed), bed);
  369. REQUIRE(nfp.empty());
  370. }
  371. }
  372. #include <boost/filesystem/path.hpp>
  373. #include <boost/filesystem.hpp>
  374. TEMPLATE_TEST_CASE("NFP algorithm test", "[arrange2][Slow]",
  375. Slic3r::arr2::InfiniteBed,
  376. Slic3r::arr2::RectangleBed,
  377. Slic3r::arr2::CircleBed,
  378. Slic3r::arr2::IrregularBed)
  379. {
  380. using namespace Slic3r;
  381. auto bed = init_bed<TestType>();
  382. std::string bedtypestr = bedtype_str(bed);
  383. SECTION("Predefined simple polygons for debugging") {
  384. test_itempairs(nfp_testdata, bed, bedtypestr + "_");
  385. }
  386. SECTION("All combinations of convex prusa parts without inflation") {
  387. auto parts = prusa_parts();
  388. std::vector<ItemPair> testdata;
  389. foreach_combo(range(parts), [&testdata](auto &i1, auto &i2){
  390. testdata.emplace_back(ItemPair{i1, i2});
  391. });
  392. test_itempairs(testdata, bed, bedtypestr + "_prusacombos");
  393. }
  394. SECTION("All combinations of prusa parts with random inflation") {
  395. std::random_device rd;
  396. auto seed = rd();
  397. std::mt19937 rng{seed};
  398. std::uniform_real_distribution<double> distr{0., 50.};
  399. INFO ("Seed = " << seed);
  400. auto parts = prusa_parts(distr(rng));
  401. std::vector<ItemPair> testdata;
  402. foreach_combo(range(parts), [&testdata](auto &i1, auto &i2){
  403. testdata.emplace_back(ItemPair{i1, i2});
  404. });
  405. test_itempairs(testdata, bed, bedtypestr + "_prusacombos_infl");
  406. }
  407. SECTION("All combinations of concave-holed prusa parts without inflation")
  408. {
  409. auto parts = prusa_parts_ex();
  410. for (ArrangeItem &itm : parts) {
  411. itm.set_envelope(arr2::DecomposedShape{itm.shape().convex_hull()});
  412. }
  413. std::vector<ItemPair> testdata;
  414. foreach_combo(range(parts), [&testdata](auto &i1, auto &i2){
  415. testdata.emplace_back(ItemPair{i1, i2});
  416. });
  417. test_itempairs(testdata, bed, bedtypestr + "_prusacombos_ex");
  418. }
  419. SECTION("All combinations of concave-holed prusa parts with inflation") {
  420. std::random_device rd;
  421. auto seed = rd();
  422. std::mt19937 rng{seed};
  423. std::uniform_real_distribution<double> distr{0., 50.};
  424. INFO ("Seed = " << seed);
  425. auto parts = prusa_parts_ex(distr(rng));
  426. for (ArrangeItem &itm : parts) {
  427. itm.set_envelope(arr2::DecomposedShape{itm.shape().convex_hull()});
  428. }
  429. std::vector<ItemPair> testdata;
  430. foreach_combo(range(parts), [&testdata](auto &i1, auto &i2){
  431. testdata.emplace_back(ItemPair{i1, i2});
  432. });
  433. test_itempairs(testdata, bed, bedtypestr + "_prusacombos_ex_infl");
  434. }
  435. }
  436. TEST_CASE("EdgeCache tests", "[arrange2]") {
  437. using namespace Slic3r;
  438. SECTION ("Empty polygon should produce empty edge-cache") {
  439. ExPolygon emptypoly;
  440. arr2::EdgeCache ep {&emptypoly};
  441. std::vector<arr2::ContourLocation> samples;
  442. ep.sample_contour(1., samples);
  443. REQUIRE(samples.empty());
  444. }
  445. SECTION ("Single edge polygon should be considered as 2 lines") {
  446. ExPolygon poly{scaled(Vec2f{0.f, 0.f}), scaled(Vec2f{10., 10.})};
  447. arr2::EdgeCache ep{&poly};
  448. std::vector<arr2::ContourLocation> samples;
  449. double accuracy = 1.;
  450. ep.sample_contour(accuracy, samples);
  451. REQUIRE(samples.size() == 2);
  452. REQUIRE(ep.coords(samples[0]) == poly.contour[1]);
  453. REQUIRE(ep.coords(samples[1]) == poly.contour[0]);
  454. REQUIRE(ep.coords({0, 0.}) == ep.coords({0, 1.}));
  455. }
  456. SECTION ("Test address range") {
  457. // Single edge on the int range boundary
  458. ExPolygon poly{scaled(Vec2f{-2000.f, 0.f}), scaled(Vec2f{2000.f, 0.f})};
  459. arr2::EdgeCache ep{&poly};
  460. REQUIRE(ep.coords({0, 0.25}) == Vec2crd{0, 0});
  461. REQUIRE(ep.coords({0, 0.75}) == Vec2crd{0, 0});
  462. // Multiple edges on the int range boundary
  463. ExPolygon squ{arr2::to_rectangle(scaled(BoundingBoxf{{0., 0.}, {2000., 2000.}}))};
  464. arr2::EdgeCache ep2{&squ};
  465. REQUIRE(ep2.coords({0, 0.}) == Vec2crd{0, 0});
  466. REQUIRE(ep2.coords({0, 0.25}) == Vec2crd{2000000000, 0});
  467. REQUIRE(ep2.coords({0, 0.5}) == Vec2crd{2000000000, 2000000000});
  468. REQUIRE(ep2.coords({0, 0.75}) == Vec2crd{0, 2000000000});
  469. REQUIRE(ep2.coords({0, 1.}) == Vec2crd{0, 0});
  470. }
  471. SECTION("Accuracy argument should skip corners correctly") {
  472. ExPolygon poly{arr2::to_rectangle(scaled(BoundingBoxf{{0., 0.}, {10., 10.}}))};
  473. double accuracy = 1.;
  474. arr2::EdgeCache ep{&poly};
  475. std::vector<arr2::ContourLocation> samples;
  476. ep.sample_contour(accuracy, samples);
  477. REQUIRE(samples.size() == poly.contour.size());
  478. for (size_t i = 0; i < samples.size(); ++i) {
  479. auto &cr = samples[i];
  480. REQUIRE(ep.coords(cr) == poly.contour.points[(i + 1) % poly.contour.size()]);
  481. }
  482. accuracy = 0.;
  483. arr2::EdgeCache ep0{&poly};
  484. samples.clear();
  485. ep0.sample_contour(accuracy, samples);
  486. REQUIRE(samples.size() == 1);
  487. REQUIRE(ep0.coords(samples[0]) == poly.contour.points[1]);
  488. }
  489. }
  490. // Mock packing strategy that places N items to the center of the
  491. // bed bounding box, if the bed is larger than the item.
  492. template<int Cap>
  493. struct RectangleToCenterPackStrategy { static constexpr int Capacity = Cap; };
  494. namespace Slic3r { namespace arr2 {
  495. struct RectangleToCenterPackTag {};
  496. template<int N> struct PackStrategyTag_<RectangleToCenterPackStrategy<N>> {
  497. using Tag = RectangleToCenterPackTag;
  498. };
  499. // Dummy arrangeitem that is a rectangle
  500. struct RectangleItem {
  501. int bed_index = Unarranged;
  502. BoundingBox shape = {{0, 0}, scaled(Vec2d{10., 10.})};
  503. Vec2crd translation = {0, 0};
  504. double rotation = 0;
  505. int priority = 0;
  506. int packed_num = 0;
  507. void set_bed_index(int idx) { bed_index = idx; }
  508. int get_bed_index() const noexcept { return bed_index; }
  509. void set_translation(const Vec2crd &tr) { translation = tr; }
  510. const Vec2crd & get_translation() const noexcept { return translation; }
  511. void set_rotation(double r) { rotation = r; }
  512. double get_rotation() const noexcept { return rotation; }
  513. int get_priority() const noexcept { return priority; }
  514. };
  515. template<class Strategy, class Bed, class RemIt>
  516. bool pack(Strategy &&strategy,
  517. const Bed &bed,
  518. RectangleItem &item,
  519. const PackStrategyContext<Strategy, RectangleItem> &packing_context,
  520. const Range<RemIt> &remaining_items,
  521. const RectangleToCenterPackTag &)
  522. {
  523. bool ret = false;
  524. auto bedbb = bounding_box(bed);
  525. auto itmbb = item.shape;
  526. Vec2crd tr = bedbb.center() - itmbb.center();
  527. itmbb.translate(tr);
  528. auto fixed_items = all_items_range(packing_context);
  529. if (fixed_items.size() < Slic3r::StripCVRef<Strategy>::Capacity &&
  530. bedbb.contains(itmbb))
  531. {
  532. translate(item, tr);
  533. ret = true;
  534. }
  535. return ret;
  536. }
  537. }} // namespace Slic3r::arr2
  538. using Slic3r::arr2::RectangleItem;
  539. TEST_CASE("First fit selection strategy", "[arrange2]")
  540. {
  541. using ArrItem = RectangleItem;
  542. using Cmp = Slic3r::arr2::firstfit::DefaultItemCompareFn;
  543. auto create_items_n = [](size_t count) {
  544. INFO ("Item count = " << count);
  545. auto items = Slic3r::reserve_vector<ArrItem>(count);
  546. std::generate_n(std::back_inserter(items), count, [] { return ArrItem{}; });
  547. return items;
  548. };
  549. auto bed = Slic3r::arr2::RectangleBed(scaled(100.), scaled(100.));
  550. GIVEN("A packing strategy that does not accept any items")
  551. {
  552. using PackStrategy = RectangleToCenterPackStrategy<0>;
  553. int on_arrange_call_count = 0;
  554. auto on_arranged_fn = [&on_arrange_call_count](ArrItem &itm,
  555. auto &bed, auto &packed,
  556. auto &rem) {
  557. ++on_arrange_call_count;
  558. Slic3r::arr2::firstfit::DefaultOnArrangedFn{}(itm, bed, packed, rem);
  559. };
  560. int cancel_call_count = 0;
  561. auto stop_cond = [&cancel_call_count] {
  562. ++cancel_call_count;
  563. return false;
  564. };
  565. WHEN ("attempting to pack a single item with a valid bed index")
  566. {
  567. auto items = create_items_n(1);
  568. Slic3r::arr2::set_bed_index(items.front(), random_value(0, 1000));
  569. auto sel = Slic3r::arr2::firstfit::SelectionStrategy{Cmp{}, on_arranged_fn, stop_cond};
  570. Slic3r::arr2::arrange(sel,
  571. PackStrategy{},
  572. Slic3r::range(items),
  573. bed);
  574. THEN ("the original bed index should be ignored and set to Unarranged")
  575. {
  576. REQUIRE(Slic3r::arr2::get_bed_index(items.front()) ==
  577. Slic3r::arr2::Unarranged);
  578. }
  579. THEN("the arrange callback should not be called")
  580. {
  581. REQUIRE(on_arrange_call_count == 0);
  582. }
  583. THEN("the stop condition should be called at least once")
  584. {
  585. REQUIRE(cancel_call_count > 0);
  586. }
  587. }
  588. WHEN("attempting to pack arbitrary number > 1 of items into the bed")
  589. {
  590. auto items = create_items_n(random_value(1, 100));
  591. CHECK(cancel_call_count == 0);
  592. CHECK(on_arrange_call_count == 0);
  593. Slic3r::arr2::arrange(
  594. Slic3r::arr2::firstfit::SelectionStrategy{Cmp{}, on_arranged_fn, stop_cond},
  595. PackStrategy{}, Slic3r::range(items), bed);
  596. THEN("The item should be left unpacked")
  597. {
  598. REQUIRE(std::all_of(items.begin(), items.end(), [](const ArrItem &itm) {
  599. return !Slic3r::arr2::is_arranged(itm);
  600. }));
  601. }
  602. THEN("the arrange callback should not be called")
  603. {
  604. REQUIRE(on_arrange_call_count == 0);
  605. }
  606. THEN("the stop condition should be called at least once for each item")
  607. {
  608. INFO("items count = " << items.size());
  609. REQUIRE(cancel_call_count >= static_cast<int>(items.size()));
  610. }
  611. }
  612. }
  613. GIVEN("A pack strategy that accepts only a single item")
  614. {
  615. using PackStrategy = RectangleToCenterPackStrategy<1>;
  616. WHEN ("attempting to pack a single item with a valid bed index")
  617. {
  618. auto items = create_items_n(1);
  619. Slic3r::arr2::set_bed_index(items.front(), random_value(0, 1000));
  620. Slic3r::arr2::arrange(Slic3r::arr2::firstfit::SelectionStrategy<>{},
  621. PackStrategy{},
  622. Slic3r::range(items),
  623. bed);
  624. THEN ("the original bed index should be ignored and set to zero")
  625. {
  626. REQUIRE(Slic3r::arr2::get_bed_index(items.front()) == 0);
  627. }
  628. }
  629. WHEN("attempting to pack arbitrary number > 1 of items into bed")
  630. {
  631. auto items = create_items_n(random_value(1, 100));
  632. Slic3r::arr2::arrange(Slic3r::arr2::firstfit::SelectionStrategy<>{},
  633. PackStrategy{},
  634. Slic3r::range(items),
  635. bed);
  636. THEN ("The number of beds created should match the number of items")
  637. {
  638. auto bed_count = Slic3r::arr2::get_bed_count(Slic3r::range(items));
  639. REQUIRE(bed_count == items.size());
  640. }
  641. THEN("All items should reside on their respective beds")
  642. {
  643. for (size_t i = 0; i < items.size(); ++i)
  644. REQUIRE(Slic3r::arr2::get_bed_index(items[i]) == static_cast<int>(i));
  645. }
  646. }
  647. }
  648. GIVEN ("Two packed beds with an unpacked bed between them")
  649. {
  650. using PackStrategy = RectangleToCenterPackStrategy<1>;
  651. auto bed = Slic3r::arr2::RectangleBed(scaled(100.), scaled(100.));
  652. auto fixed = create_items_n(2);
  653. std::for_each(fixed.begin(), fixed.end(), [&bed](auto &itm){
  654. Slic3r::arr2::pack(PackStrategy{}, bed, itm);
  655. });
  656. for (auto [i, idx] : {std::make_pair(0, 0), std::make_pair(1, 2)})
  657. Slic3r::arr2::set_bed_index(fixed[i], idx);
  658. WHEN("attempting to pack a single item")
  659. {
  660. auto items = create_items_n(1);
  661. Slic3r::arr2::arrange(Slic3r::arr2::firstfit::SelectionStrategy<>{},
  662. PackStrategy{},
  663. Slic3r::range(items),
  664. Slic3r::crange(fixed),
  665. bed);
  666. THEN("the item should end up on the first free bed")
  667. {
  668. REQUIRE(Slic3r::arr2::get_bed_index(items.front()) == 1);
  669. }
  670. }
  671. }
  672. GIVEN ("A 100 items with increasing priorities and a packer that accepts 20 items")
  673. {
  674. static constexpr int Capacity = 20;
  675. static constexpr int Count = 5 * Capacity;
  676. using PackStrategy = RectangleToCenterPackStrategy<Capacity>;
  677. auto items = create_items_n(Count);
  678. for (size_t i = 0; i < items.size(); ++i)
  679. items[i].priority = static_cast<int>(i);
  680. WHEN("attempting to pack all items")
  681. {
  682. auto on_arranged_fn = [](ArrItem &itm, auto &bed, auto &packed,
  683. auto &rem) {
  684. itm.packed_num = packed.size();
  685. Slic3r::arr2::firstfit::DefaultOnArrangedFn{}(itm, bed, packed, rem);
  686. };
  687. Slic3r::arr2::arrange(Slic3r::arr2::firstfit::SelectionStrategy{Cmp{}, on_arranged_fn},
  688. PackStrategy{},
  689. Slic3r::range(items),
  690. bed);
  691. THEN("all items should fit onto the beds from index 0 to 4")
  692. {
  693. REQUIRE(std::all_of(items.begin(), items.end(), [](const ArrItem &itm) {
  694. auto bed_idx = Slic3r::arr2::get_bed_index(itm);
  695. return bed_idx >= 0 && bed_idx < Count / Capacity;
  696. }));
  697. }
  698. // Highest priority goes first
  699. THEN("all the items should be packed in reverse order of their priority value")
  700. {
  701. REQUIRE(std::all_of(items.begin(), items.end(), [](const ArrItem &itm) {
  702. return itm.packed_num == (99 - itm.priority);
  703. }));
  704. }
  705. }
  706. }
  707. }
  708. template<>
  709. Slic3r::BoundingBox Slic3r::arr2::NFPArrangeItemTraits_<
  710. RectangleItem>::envelope_bounding_box(const RectangleItem &itm)
  711. {
  712. return itm.shape;
  713. }
  714. template<>
  715. Slic3r::Vec2crd Slic3r::arr2::NFPArrangeItemTraits_<
  716. RectangleItem>::reference_vertex(const RectangleItem &itm)
  717. {
  718. return itm.shape.center();
  719. }
  720. TEST_CASE("Optimal nfp position search with GravityKernel using RectangleItem and InfiniteBed",
  721. "[arrange2]")
  722. {
  723. auto bed = Slic3r::arr2::InfiniteBed{};
  724. auto strategy = Slic3r::arr2::PackStrategyNFP{Slic3r::arr2::GravityKernel{bed.center}};
  725. GIVEN("An nfp made of a single point coincident with the bed center")
  726. {
  727. WHEN ("searching for optimal position")
  728. {
  729. THEN ("the optimum should be at the single nfp point")
  730. {
  731. Slic3r::ExPolygons nfp;
  732. nfp.emplace_back(Slic3r::ExPolygon{{bed.center}});
  733. auto item = RectangleItem{};
  734. double score = pick_best_spot_on_nfp_verts_only(item, nfp, bed, strategy);
  735. Slic3r::Vec2crd D = bed.center - item.shape.center();
  736. REQUIRE(item.translation == D);
  737. REQUIRE(score == Approx(0.).margin(EPSILON));
  738. }
  739. }
  740. }
  741. }
  742. TEMPLATE_TEST_CASE("RectangleOverfitPackingStrategy test", "[arrange2]",
  743. Slic3r::arr2::SimpleArrangeItem, Slic3r::arr2::ArrangeItem)
  744. {
  745. using Slic3r::arr2::RectangleOverfitPackingStrategy;
  746. using Slic3r::arr2::PackStrategyNFP;
  747. using Slic3r::arr2::GravityKernel;
  748. using Slic3r::arr2::get_bed_index;
  749. namespace firstfit = Slic3r::arr2::firstfit;
  750. using ArrItem = TestType;
  751. auto frontleft_align_fn = [](const Slic3r::BoundingBox &bedbb,
  752. const Slic3r::BoundingBox &pilebb) {
  753. return bedbb.min - pilebb.min;
  754. };
  755. RectangleOverfitPackingStrategy pstrategy{PackStrategyNFP{GravityKernel{}},
  756. frontleft_align_fn};
  757. auto bed = Slic3r::arr2::RectangleBed{scaled(100.), scaled(100.)};
  758. auto item_blueprint = Slic3r::arr2::to_rectangle(
  759. Slic3r::BoundingBox{{0, 0}, {scaled(20.), scaled(20.)}});
  760. auto item_gen_fn = [&item_blueprint] { return ArrItem{item_blueprint}; };
  761. GIVEN("One empty logical rectangular 100x100 mm bed ") {
  762. WHEN("attempting to pack one rectangle") {
  763. constexpr auto count = size_t{1};
  764. auto items = Slic3r::reserve_vector<ArrItem>(count);
  765. std::generate_n(std::back_inserter(items), count, item_gen_fn);
  766. Slic3r::arr2::arrange(firstfit::SelectionStrategy<>{}, pstrategy,
  767. Slic3r::range(items), bed);
  768. THEN ("Overfit kernel should take over and align the single item") {
  769. auto pilebb = bounding_box(Slic3r::range(items));
  770. Slic3r::Vec2crd D = frontleft_align_fn(bounding_box(bed), pilebb);
  771. REQUIRE(D.squaredNorm() == 0);
  772. }
  773. }
  774. WHEN("attempting to pack two rectangles") {
  775. constexpr auto count = size_t{2};
  776. auto items = Slic3r::reserve_vector<ArrItem>(count);
  777. std::generate_n(std::back_inserter(items), count, item_gen_fn);
  778. Slic3r::arr2::arrange(firstfit::SelectionStrategy<>{}, pstrategy,
  779. Slic3r::range(items), bed);
  780. THEN("Overfit kernel should take over and align the single item")
  781. {
  782. auto pilebb = bounding_box(Slic3r::range(items));
  783. Slic3r::Vec2crd D = frontleft_align_fn(bounding_box(bed), pilebb);
  784. REQUIRE(D.squaredNorm() == 0);
  785. }
  786. }
  787. }
  788. GIVEN("Two logical rectangular beds, the second having fixed items") {
  789. auto fixed_item_bb = Slic3r::BoundingBox{{0, 0}, {scaled(20.), scaled(20.)}};
  790. std::vector<ArrItem> fixed = {
  791. ArrItem{Slic3r::arr2::to_rectangle(fixed_item_bb)}};
  792. Slic3r::arr2::set_bed_index(fixed.front(), 1);
  793. WHEN("attempting to pack 3 rectangles, 1 filling the first bed") {
  794. auto items = Slic3r::reserve_vector<ArrItem>(3);
  795. // Add a big rectangle this will fill the first bed so that
  796. // smaller rectangles will fit only into the next bed
  797. items.emplace_back(ArrItem{Slic3r::arr2::to_rectangle(
  798. Slic3r::BoundingBox{{0, 0}, {scaled(90.), scaled(90.)}})});
  799. std::generate_n(std::back_inserter(items), 2, item_gen_fn);
  800. Slic3r::arr2::arrange(firstfit::SelectionStrategy<>{}, pstrategy,
  801. Slic3r::range(items), Slic3r::crange(fixed),
  802. bed);
  803. THEN("Overfit kernel should handle the 0th bed and gravity kernel handles the 1st bed")
  804. {
  805. REQUIRE(get_bed_index(items.front()) == 0);
  806. auto pilebb = bounding_box_on_bedidx(Slic3r::range(items), 0);
  807. Slic3r::Vec2crd D = frontleft_align_fn(bounding_box(bed), pilebb);
  808. REQUIRE(D.squaredNorm() == 0);
  809. REQUIRE((get_bed_index(items[1]) == get_bed_index(items[2]) == 1));
  810. auto pilebb1 = bounding_box_on_bedidx(Slic3r::range(items), 1);
  811. REQUIRE(pilebb1.overlap(fixed_item_bb));
  812. Slic3r::Vec2crd D1 = frontleft_align_fn(bounding_box(bed), pilebb1);
  813. REQUIRE(D1.squaredNorm() != 0);
  814. }
  815. }
  816. }
  817. }
  818. TEMPLATE_TEST_CASE("Test if allowed item rotations are considered", "[arrange2]",
  819. Slic3r::arr2::ArrangeItem)
  820. {
  821. using ArrItem = TestType;
  822. auto item_blueprint = Slic3r::arr2::to_rectangle(
  823. Slic3r::BoundingBox{{0, 0}, {scaled(20.), scaled(20.)}});
  824. ArrItem itm{item_blueprint};
  825. auto bed = Slic3r::arr2::RectangleBed{scaled(100.), scaled(100.)};
  826. set_allowed_rotations(itm, {PI});
  827. Slic3r::arr2::PackStrategyNFP strategy{Slic3r::arr2::GravityKernel{}};
  828. bool packed = pack(strategy, bed, itm);
  829. REQUIRE(packed);
  830. REQUIRE(get_rotation(itm) == Approx(PI));
  831. }
  832. //TEST_CASE("NFP optimizing test", "[arrange2]") {
  833. // using namespace Slic3r;
  834. // auto itemshape = arr2::to_rectangle(BoundingBox{{scaled(-25.), scaled(-25.)}, {scaled(25.), scaled(25.)}});
  835. // arr2::ArrangeItem item{arr2::DecomposedShape{itemshape}};
  836. // ExPolygons nfp = { ExPolygon {{scaled(-2000.), scaled(25.)}, {scaled(2000.), scaled(25.)}} };
  837. // struct K : public arr2::GravityKernel {
  838. // size_t &fncnt;
  839. // K(size_t &counter, Vec2crd gpos): arr2::GravityKernel{gpos}, fncnt{counter} {}
  840. // double placement_fitness(const arr2::ArrangeItem &itm, const Vec2crd &tr) const
  841. // {
  842. // ++fncnt;
  843. // return arr2::GravityKernel::placement_fitness(itm, tr);
  844. // }
  845. // };
  846. // size_t counter = 0;
  847. // K k{counter, Vec2crd{0, 0}};
  848. // opt::Optimizer<opt::AlgBruteForce> solver_ref({}, 1000);
  849. // opt::Optimizer<opt::AlgNLoptSubplex> solver (opt::StopCriteria{}
  850. // .max_iterations(1000)
  851. // /*.rel_score_diff(1e-20)*/);
  852. // double accuracy = 1.;
  853. // arr2::PackStrategyNFP strategy_ref(solver_ref, k, ex_seq, accuracy);
  854. // arr2::PackStrategyNFP strategy(solver, k, ex_seq, accuracy);
  855. // SVG svg("nfp_optimizing.svg");
  856. // svg.flipY = true;
  857. // svg.draw_outline(nfp, "green");
  858. // svg.draw_outline(item.shape().transformed_outline(), "yellow");
  859. // double score = pick_best_spot_on_nfp(item, nfp, arr2::InfiniteBed{}, strategy);
  860. // svg.draw_outline(item.shape().transformed_outline(), "red");
  861. // counter = 0;
  862. // double score_ref = pick_best_spot_on_nfp(item, nfp, arr2::InfiniteBed{}, strategy_ref);
  863. // svg.draw_outline(item.shape().transformed_outline(), "blue");
  864. //#ifndef NDEBUG
  865. // std::cout << "fitness called: " << k.fncnt << " times" << std::endl;
  866. // std::cout << "score = " << score << " score_ref = " << score_ref << std::endl;
  867. //#endif
  868. // REQUIRE(!std::isnan(score));
  869. // REQUIRE(!std::isnan(score_ref));
  870. // REQUIRE(score >= score_ref);
  871. //}