test_region_expansion.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. #include <catch2/catch.hpp>
  2. #include <libslic3r/libslic3r.h>
  3. #include <libslic3r/Algorithm/RegionExpansion.hpp>
  4. #include <libslic3r/ClipperUtils.hpp>
  5. #include <libslic3r/ExPolygon.hpp>
  6. #include <libslic3r/Polygon.hpp>
  7. #include <libslic3r/SVG.cpp>
  8. using namespace Slic3r;
  9. //#define DEBUG_TEMP_DIR "d:\\temp\\"
  10. SCENARIO("Region expansion basics", "[RegionExpansion]") {
  11. static constexpr const coord_t ten = scaled<coord_t>(10.);
  12. GIVEN("two touching squares") {
  13. Polygon square1{ { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 2 * ten }, { 1 * ten, 2 * ten } };
  14. Polygon square2{ { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } };
  15. Polygon square3{ { 1 * ten, 2 * ten }, { 2 * ten, 2 * ten }, { 2 * ten, 3 * ten }, { 1 * ten, 3 * ten } };
  16. static constexpr const float expansion = scaled<float>(1.);
  17. auto test_expansion = [](const Polygon &src, const Polygon &boundary) {
  18. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{src} }, { ExPolygon{boundary} },
  19. expansion,
  20. scaled<float>(0.3), // expansion step
  21. 5); // max num steps
  22. THEN("Single anchor is produced") {
  23. REQUIRE(expanded.size() == 1);
  24. }
  25. THEN("The area of the anchor is 10mm2") {
  26. REQUIRE(area(expanded.front()) == Approx(expansion * ten));
  27. }
  28. };
  29. WHEN("second square expanded into the first square (to left)") {
  30. test_expansion(square2, square1);
  31. }
  32. WHEN("first square expanded into the second square (to right)") {
  33. test_expansion(square1, square2);
  34. }
  35. WHEN("third square expanded into the first square (down)") {
  36. test_expansion(square3, square1);
  37. }
  38. WHEN("first square expanded into the third square (up)") {
  39. test_expansion(square1, square3);
  40. }
  41. }
  42. GIVEN("simple bridge") {
  43. Polygon square1{ { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 2 * ten }, { 1 * ten, 2 * ten } };
  44. Polygon square2{ { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } };
  45. Polygon square3{ { 3 * ten, 1 * ten }, { 4 * ten, 1 * ten }, { 4 * ten, 2 * ten }, { 3 * ten, 2 * ten } };
  46. WHEN("expanded") {
  47. static constexpr const float expansion = scaled<float>(1.);
  48. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{square2} }, { ExPolygon{square1}, ExPolygon{square3} },
  49. expansion,
  50. scaled<float>(0.3), // expansion step
  51. 5); // max num steps
  52. THEN("Two anchors are produced") {
  53. REQUIRE(expanded.size() == 1);
  54. REQUIRE(expanded.front().size() == 2);
  55. }
  56. THEN("The area of each anchor is 10mm2") {
  57. REQUIRE(area(expanded.front().front()) == Approx(expansion * ten));
  58. REQUIRE(area(expanded.front().back()) == Approx(expansion * ten));
  59. }
  60. }
  61. WHEN("fully expanded") {
  62. static constexpr const float expansion = scaled<float>(10.1);
  63. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{square2} }, { ExPolygon{square1}, ExPolygon{square3} },
  64. expansion,
  65. scaled<float>(2.3), // expansion step
  66. 5); // max num steps
  67. THEN("Two anchors are produced") {
  68. REQUIRE(expanded.size() == 1);
  69. REQUIRE(expanded.front().size() == 2);
  70. }
  71. THEN("The area of each anchor is 100mm2") {
  72. REQUIRE(area(expanded.front().front()) == Approx(sqr<double>(ten)));
  73. REQUIRE(area(expanded.front().back()) == Approx(sqr<double>(ten)));
  74. }
  75. }
  76. }
  77. GIVEN("two bridges") {
  78. Polygon left_support { { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 4 * ten }, { 1 * ten, 4 * ten } };
  79. Polygon right_support { { 3 * ten, 1 * ten }, { 4 * ten, 1 * ten }, { 4 * ten, 4 * ten }, { 3 * ten, 4 * ten } };
  80. Polygon bottom_bridge { { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } };
  81. Polygon top_bridge { { 2 * ten, 3 * ten }, { 3 * ten, 3 * ten }, { 3 * ten, 4 * ten }, { 2 * ten, 4 * ten } };
  82. WHEN("expanded") {
  83. static constexpr const float expansion = scaled<float>(1.);
  84. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{bottom_bridge}, ExPolygon{top_bridge} }, { ExPolygon{left_support}, ExPolygon{right_support} },
  85. expansion,
  86. scaled<float>(0.3), // expansion step
  87. 5); // max num steps
  88. #if 0
  89. SVG::export_expolygons(DEBUG_TEMP_DIR "two_bridges-out.svg",
  90. { { { { ExPolygon{left_support}, ExPolygon{right_support} } }, { "supports", "orange", 0.5f } },
  91. { { { ExPolygon{bottom_bridge}, ExPolygon{top_bridge} } }, { "bridges", "blue", 0.5f } },
  92. { { union_ex(union_(expanded.front(), expanded.back())) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
  93. #endif
  94. THEN("Two anchors are produced for each bridge") {
  95. REQUIRE(expanded.size() == 2);
  96. REQUIRE(expanded.front().size() == 2);
  97. REQUIRE(expanded.back().size() == 2);
  98. }
  99. THEN("The area of each anchor is 10mm2") {
  100. double a = expansion * ten + M_PI * sqr(expansion) / 4;
  101. double eps = sqr(scaled<double>(0.1));
  102. REQUIRE(is_approx(area(expanded.front().front()), a, eps));
  103. REQUIRE(is_approx(area(expanded.front().back()), a, eps));
  104. REQUIRE(is_approx(area(expanded.back().front()), a, eps));
  105. REQUIRE(is_approx(area(expanded.back().back()), a, eps));
  106. }
  107. }
  108. }
  109. GIVEN("rectangle with rhombic cut-out") {
  110. double diag = 1 * ten * sqrt(2.) / 4.;
  111. Polygon square_with_rhombic_cutout{ { 0, 0 }, { 1 * ten, 0 }, { ten / 2, ten / 2 }, { 1 * ten, 1 * ten }, { 0, 1 * ten } };
  112. Polygon rhombic { { ten / 2, ten / 2 }, { 3 * ten / 4, ten / 4 }, { 1 * ten, ten / 2 }, { 3 * ten / 4, 3 * ten / 4 } };
  113. WHEN("expanded") {
  114. static constexpr const float expansion = scaled<float>(1.);
  115. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{rhombic} }, { ExPolygon{square_with_rhombic_cutout} },
  116. expansion,
  117. scaled<float>(0.1), // expansion step
  118. 11); // max num steps
  119. #if 0
  120. SVG::export_expolygons(DEBUG_TEMP_DIR "rectangle_with_rhombic_cut-out.svg",
  121. { { { { ExPolygon{square_with_rhombic_cutout} } }, { "square_with_rhombic_cutout", "orange", 0.5f } },
  122. { { { ExPolygon{rhombic} } }, { "rhombic", "blue", 0.5f } },
  123. { { union_ex(expanded.front()) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
  124. #endif
  125. THEN("Single anchor is produced") {
  126. REQUIRE(expanded.size() == 1);
  127. }
  128. THEN("The area of anchor is correct") {
  129. double area_calculated = area(expanded.front());
  130. double area_expected = 2. * diag * expansion + M_PI * sqr(expansion) * 0.75;
  131. REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.2))));
  132. }
  133. }
  134. WHEN("extra expanded") {
  135. static constexpr const float expansion = scaled<float>(2.5);
  136. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{rhombic} }, { ExPolygon{square_with_rhombic_cutout} },
  137. expansion,
  138. scaled<float>(0.25), // expansion step
  139. 11); // max num steps
  140. #if 0
  141. SVG::export_expolygons(DEBUG_TEMP_DIR "rectangle_with_rhombic_cut-out2.svg",
  142. { { { { ExPolygon{square_with_rhombic_cutout} } }, { "square_with_rhombic_cutout", "orange", 0.5f } },
  143. { { { ExPolygon{rhombic} } }, { "rhombic", "blue", 0.5f } },
  144. { { union_ex(expanded.front()) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
  145. #endif
  146. THEN("Single anchor is produced") {
  147. REQUIRE(expanded.size() == 1);
  148. }
  149. THEN("The area of anchor is correct") {
  150. double area_calculated = area(expanded.front());
  151. double area_expected = 2. * diag * expansion + M_PI * sqr(expansion) * 0.75;
  152. REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.3))));
  153. }
  154. }
  155. }
  156. GIVEN("square with two holes") {
  157. Polygon outer{ { 0, 0 }, { 3 * ten, 0 }, { 3 * ten, 5 * ten }, { 0, 5 * ten } };
  158. Polygon hole1{ { 1 * ten, 1 * ten }, { 1 * ten, 2 * ten }, { 2 * ten, 2 * ten }, { 2 * ten, 1 * ten } };
  159. Polygon hole2{ { 1 * ten, 3 * ten }, { 1 * ten, 4 * ten }, { 2 * ten, 4 * ten }, { 2 * ten, 3 * ten } };
  160. ExPolygon boundary(outer);
  161. boundary.holes = { hole1, hole2 };
  162. Polygon anchor{ { -1 * ten, coord_t(1.5 * ten) }, { 0 * ten, coord_t(1.5 * ten) }, { 0, coord_t(3.5 * ten) }, { -1 * ten, coord_t(3.5 * ten) } };
  163. WHEN("expanded") {
  164. static constexpr const float expansion = scaled<float>(5.);
  165. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
  166. expansion,
  167. scaled<float>(0.4), // expansion step
  168. 15); // max num steps
  169. #if 0
  170. SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-out.svg",
  171. { { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
  172. { { { boundary } }, { "boundary", "blue", 0.5f } },
  173. { { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
  174. #endif
  175. THEN("The anchor expands into a single region") {
  176. REQUIRE(expanded.size() == 1);
  177. REQUIRE(expanded.front().size() == 1);
  178. }
  179. THEN("The area of anchor is correct") {
  180. double area_calculated = area(expanded.front());
  181. double area_expected = double(expansion) * 2. * double(ten) + M_PI * sqr(expansion) * 0.5;
  182. REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.45))));
  183. }
  184. }
  185. WHEN("expanded even more") {
  186. static constexpr const float expansion = scaled<float>(25.);
  187. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
  188. expansion,
  189. scaled<float>(2.), // expansion step
  190. 15); // max num steps
  191. #if 0
  192. SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded2-out.svg",
  193. { { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
  194. { { { boundary } }, { "boundary", "blue", 0.5f } },
  195. { { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
  196. #endif
  197. THEN("The anchor expands into a single region") {
  198. REQUIRE(expanded.size() == 1);
  199. REQUIRE(expanded.front().size() == 1);
  200. }
  201. }
  202. WHEN("expanded yet even more") {
  203. static constexpr const float expansion = scaled<float>(28.);
  204. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
  205. expansion,
  206. scaled<float>(2.), // expansion step
  207. 20); // max num steps
  208. #if 0
  209. SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded3-out.svg",
  210. { { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
  211. { { { boundary } }, { "boundary", "blue", 0.5f } },
  212. { { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
  213. #endif
  214. THEN("The anchor expands into a single region with two holes") {
  215. REQUIRE(expanded.size() == 1);
  216. REQUIRE(expanded.front().size() == 3);
  217. }
  218. }
  219. WHEN("expanded fully") {
  220. static constexpr const float expansion = scaled<float>(35.);
  221. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
  222. expansion,
  223. scaled<float>(2.), // expansion step
  224. 25); // max num steps
  225. #if 0
  226. SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded_fully-out.svg",
  227. { { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
  228. { { { boundary } }, { "boundary", "blue", 0.5f } },
  229. { { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
  230. #endif
  231. THEN("The anchor expands into a single region with two holes, fully covering the boundary") {
  232. REQUIRE(expanded.size() == 1);
  233. REQUIRE(expanded.front().size() == 3);
  234. REQUIRE(area(expanded.front()) == Approx(area(boundary)));
  235. }
  236. }
  237. }
  238. GIVEN("square with hole, hole edge anchored") {
  239. Polygon outer{ { -1 * ten, -1 * ten }, { 2 * ten, -1 * ten }, { 2 * ten, 2 * ten }, { -1 * ten, 2 * ten } };
  240. Polygon hole { { 0, ten }, { ten, ten }, { ten, 0 }, { 0, 0 } };
  241. Polygon anchor{ { 0, 0 }, { ten, 0 }, { ten, ten }, { 0, ten } };
  242. ExPolygon boundary(outer);
  243. boundary.holes = { hole };
  244. WHEN("expanded") {
  245. static constexpr const float expansion = scaled<float>(5.);
  246. std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
  247. expansion,
  248. scaled<float>(0.4), // expansion step
  249. 15); // max num steps
  250. #if 0
  251. SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_hole_anchored-out.svg",
  252. { { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
  253. { { { boundary } }, { "boundary", "blue", 0.5f } },
  254. { { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
  255. #endif
  256. THEN("The anchor expands into a single region with a hole") {
  257. REQUIRE(expanded.size() == 1);
  258. REQUIRE(expanded.front().size() == 2);
  259. }
  260. THEN("The area of anchor is correct") {
  261. double area_calculated = area(expanded.front());
  262. double area_expected = double(expansion) * 4. * double(ten) + M_PI * sqr(expansion);
  263. REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.6))));
  264. }
  265. }
  266. }
  267. }