test_clipper_offset.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. #include <catch2/catch.hpp>
  2. #include <iostream>
  3. #include <boost/filesystem.hpp>
  4. #include "libslic3r/ClipperUtils.hpp"
  5. #include "libslic3r/ExPolygon.hpp"
  6. #include "libslic3r/SVG.hpp"
  7. using namespace Slic3r;
  8. // #define TESTS_EXPORT_SVGS
  9. SCENARIO("Constant offset", "[ClipperUtils]") {
  10. int32_t s = 1000000;
  11. GIVEN("20mm box") {
  12. ExPolygon box20mm;
  13. box20mm.contour.points = { Vec2crd{ 0, 0 }, Vec2crd{ 20 * s, 0 }, Vec2crd{ 20 * s, 20 * s}, Vec2crd{ 0, 20 * s} };
  14. std::vector<float> deltas_plus(box20mm.contour.points.size(), 1. * s);
  15. std::vector<float> deltas_minus(box20mm.contour.points.size(), - 1. * s);
  16. Polygons output;
  17. WHEN("Slic3r::offset()") {
  18. for (double miter : { 2.0, 1.5, 1.2 }) {
  19. DYNAMIC_SECTION("plus 1mm, miter " << miter << "x") {
  20. output = Slic3r::offset(box20mm, 1. * s, ClipperLib::jtMiter, miter);
  21. #ifdef TESTS_EXPORT_SVGS
  22. {
  23. SVG svg(debug_out_path("constant_offset_box20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
  24. svg.draw(box20mm, "blue");
  25. svg.draw_outline(output, "black", coord_t(scale_(0.01)));
  26. }
  27. #endif
  28. THEN("Area is 22^2mm2") {
  29. REQUIRE(output.size() == 1);
  30. REQUIRE(output.front().area() == Approx(22. * 22. * s * s));
  31. }
  32. }
  33. DYNAMIC_SECTION("minus 1mm, miter " << miter << "x") {
  34. output = Slic3r::offset(box20mm, - 1. * s, ClipperLib::jtMiter, miter);
  35. #ifdef TESTS_EXPORT_SVGS
  36. {
  37. SVG svg(debug_out_path("constant_offset_box20mm_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
  38. svg.draw(box20mm, "blue");
  39. svg.draw_outline(output, "black", coord_t(scale_(0.01)));
  40. }
  41. #endif
  42. THEN("Area is 18^2mm2") {
  43. REQUIRE(output.size() == 1);
  44. REQUIRE(output.front().area() == Approx(18. * 18. * s * s));
  45. }
  46. }
  47. }
  48. }
  49. WHEN("Slic3r::variable_offset_outer/inner") {
  50. for (double miter : { 2.0, 1.5, 1.2 }) {
  51. DYNAMIC_SECTION("plus 1mm, miter " << miter << "x") {
  52. output = Slic3r::variable_offset_outer(box20mm, { deltas_plus }, miter);
  53. #ifdef TESTS_EXPORT_SVGS
  54. {
  55. SVG svg(debug_out_path("variable_offset_box20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
  56. svg.draw(box20mm, "blue");
  57. svg.draw_outline(output, "black", coord_t(scale_(0.01)));
  58. }
  59. #endif
  60. THEN("Area is 22^2mm2") {
  61. REQUIRE(output.size() == 1);
  62. REQUIRE(output.front().area() == Approx(22. * 22. * s * s));
  63. }
  64. }
  65. DYNAMIC_SECTION("minus 1mm, miter " << miter << "x") {
  66. output = Slic3r::variable_offset_inner(box20mm, { deltas_minus }, miter);
  67. #ifdef TESTS_EXPORT_SVGS
  68. {
  69. SVG svg(debug_out_path("variable_offset_box20mm_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
  70. svg.draw(box20mm, "blue");
  71. svg.draw_outline(output, "black", coord_t(scale_(0.01)));
  72. }
  73. #endif
  74. THEN("Area is 18^2mm2") {
  75. REQUIRE(output.size() == 1);
  76. REQUIRE(output.front().area() == Approx(18. * 18. * s * s));
  77. }
  78. }
  79. }
  80. }
  81. }
  82. GIVEN("20mm box with 10mm hole") {
  83. ExPolygon box20mm;
  84. box20mm.contour.points = { Vec2crd{ 0, 0 }, Vec2crd{ 20 * s, 0 }, Vec2crd{ 20 * s, 20 * s}, Vec2crd{ 0, 20 * s} };
  85. box20mm.holes.emplace_back(Slic3r::Polygon({ Vec2crd{ 5 * s, 5 * s }, Vec2crd{ 5 * s, 15 * s}, Vec2crd{ 15 * s, 15 * s}, Vec2crd{ 15 * s, 5 * s } }));
  86. std::vector<float> deltas_plus(box20mm.contour.points.size(), 1. * s);
  87. std::vector<float> deltas_minus(box20mm.contour.points.size(), -1. * s);
  88. ExPolygons output;
  89. SECTION("Slic3r::offset()") {
  90. for (double miter : { 2.0, 1.5, 1.2 }) {
  91. DYNAMIC_SECTION("miter " << miter << "x") {
  92. WHEN("plus 1mm") {
  93. output = Slic3r::offset_ex(box20mm, 1. * s, ClipperLib::jtMiter, miter);
  94. #ifdef TESTS_EXPORT_SVGS
  95. {
  96. SVG svg(debug_out_path("constant_offset_box20mm_10mm_hole_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
  97. svg.draw(box20mm, "blue");
  98. svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01)));
  99. }
  100. #endif
  101. THEN("Area is 22^2-8^2 mm2") {
  102. REQUIRE(output.size() == 1);
  103. REQUIRE(output.front().area() == Approx((22. * 22. - 8. * 8.) * s * s));
  104. }
  105. }
  106. WHEN("minus 1mm") {
  107. output = Slic3r::offset_ex(box20mm, - 1. * s, ClipperLib::jtMiter, miter);
  108. #ifdef TESTS_EXPORT_SVGS
  109. {
  110. SVG svg(debug_out_path("constant_offset_box20mm_10mm_hole_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
  111. svg.draw(box20mm, "blue");
  112. svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01)));
  113. }
  114. #endif
  115. THEN("Area is 18^2-12^2 mm2") {
  116. REQUIRE(output.size() == 1);
  117. REQUIRE(output.front().area() == Approx((18. * 18. - 12. * 12.) * s * s));
  118. }
  119. }
  120. }
  121. }
  122. }
  123. SECTION("Slic3r::variable_offset_outer()") {
  124. for (double miter : { 2.0, 1.5, 1.2 }) {
  125. DYNAMIC_SECTION("miter " << miter << "x") {
  126. WHEN("plus 1mm") {
  127. output = Slic3r::variable_offset_outer_ex(box20mm, { deltas_plus, deltas_plus }, miter);
  128. #ifdef TESTS_EXPORT_SVGS
  129. {
  130. SVG svg(debug_out_path("variable_offset_box20mm_10mm_hole_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
  131. svg.draw(box20mm, "blue");
  132. svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01)));
  133. }
  134. #endif
  135. THEN("Area is 22^2-8^2 mm2") {
  136. REQUIRE(output.size() == 1);
  137. REQUIRE(output.front().area() == Approx((22. * 22. - 8. * 8.) * s * s));
  138. }
  139. }
  140. WHEN("minus 1mm") {
  141. output = Slic3r::variable_offset_inner_ex(box20mm, { deltas_minus, deltas_minus }, miter);
  142. #ifdef TESTS_EXPORT_SVGS
  143. {
  144. SVG svg(debug_out_path("variable_offset_box20mm_10mm_hole_minus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
  145. svg.draw(box20mm, "blue");
  146. svg.draw_outline(to_polygons(output), "black", coord_t(scale_(0.01)));
  147. }
  148. #endif
  149. THEN("Area is 18^2-12^2 mm2") {
  150. REQUIRE(output.size() == 1);
  151. REQUIRE(output.front().area() == Approx((18. * 18. - 12. * 12.) * s * s));
  152. }
  153. }
  154. }
  155. }
  156. }
  157. }
  158. GIVEN("20mm right angle triangle") {
  159. ExPolygon triangle20mm;
  160. triangle20mm.contour.points = { Vec2crd{ 0, 0 }, Vec2crd{ 20 * s, 0 }, Vec2crd{ 0, 20 * s } };
  161. Polygons output;
  162. double offset = 1.;
  163. // Angle of the sharp corner bisector.
  164. double angle_bisector = M_PI / 8.;
  165. // Area tapered by mitering one sharp corner.
  166. double area_tapered = pow(offset * (1. / sin(angle_bisector) - 1.), 2.) * tan(angle_bisector);
  167. double l_triangle_side_offsetted = 20. + offset * (1. + 1. / tan(angle_bisector));
  168. double area_offsetted = (0.5 * l_triangle_side_offsetted * l_triangle_side_offsetted - 2. * area_tapered) * s * s;
  169. SECTION("Slic3r::offset()") {
  170. for (double miter : { 2.0, 1.5, 1.2 }) {
  171. DYNAMIC_SECTION("Outer offset 1mm, miter " << miter << "x") {
  172. output = Slic3r::offset(triangle20mm, offset * s, ClipperLib::jtMiter, 2.0);
  173. #ifdef TESTS_EXPORT_SVGS
  174. {
  175. SVG svg(debug_out_path("constant_offset_triangle20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
  176. svg.draw(triangle20mm, "blue");
  177. svg.draw_outline(output, "black", coord_t(scale_(0.01)));
  178. }
  179. #endif
  180. THEN("Area matches") {
  181. REQUIRE(output.size() == 1);
  182. REQUIRE(output.front().area() == Approx(area_offsetted));
  183. }
  184. }
  185. }
  186. }
  187. SECTION("Slic3r::variable_offset_outer()") {
  188. std::vector<float> deltas(triangle20mm.contour.points.size(), 1. * s);
  189. for (double miter : { 2.0, 1.5, 1.2 }) {
  190. DYNAMIC_SECTION("Outer offset 1mm, miter " << miter << "x") {
  191. output = Slic3r::variable_offset_outer(triangle20mm, { deltas }, 2.0);
  192. #ifdef TESTS_EXPORT_SVGS
  193. {
  194. SVG svg(debug_out_path("variable_offset_triangle20mm_plus1mm_miter%lf.svg", miter).c_str(), get_extents(output));
  195. svg.draw(triangle20mm, "blue");
  196. svg.draw_outline(output, "black", coord_t(scale_(0.01)));
  197. }
  198. #endif
  199. THEN("Area matches") {
  200. REQUIRE(output.size() == 1);
  201. REQUIRE(output.front().area() == Approx(area_offsetted));
  202. }
  203. }
  204. }
  205. }
  206. }
  207. }