test_thin.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. //#define CATCH_CONFIG_DISABLE
  2. #include <catch_main.hpp>
  3. #include "test_data.hpp"
  4. #include <libslic3r/ClipperUtils.hpp>
  5. #include <libslic3r/Geometry/MedialAxis.hpp>
  6. #include <libslic3r/SVG.hpp>
  7. #include <libslic3r/GCode.hpp>
  8. #include <libslic3r/Format/3mf.hpp>
  9. using namespace Slic3r;
  10. using namespace Slic3r::Geometry;
  11. using namespace Slic3r::Test;
  12. class ExtrusionVolumeVisitor : public ExtrusionVisitorConst {
  13. double volume = 0;
  14. public:
  15. virtual void use(const ExtrusionPath &path) override {
  16. for (int i = 0; i < path.polyline.size() - 1; i++) volume += unscaled(path.polyline.get_points()[i].distance_to(path.polyline.get_points()[i + 1])) * path.mm3_per_mm;
  17. };
  18. virtual void use(const ExtrusionPath3D &path3D) override { std::cout << "error, not supported"; };
  19. virtual void use(const ExtrusionMultiPath &multipath) override {
  20. for (const ExtrusionPath &path : multipath.paths) use(path);
  21. }
  22. virtual void use(const ExtrusionMultiPath3D &multipath) override { std::cout << "error, not supported"; };
  23. virtual void use(const ExtrusionLoop &loop) override {
  24. for (const ExtrusionEntity &path : loop.paths) path.visit(*this);
  25. }
  26. virtual void use(const ExtrusionEntityCollection &collection) override {
  27. for (const ExtrusionEntity *path : collection.entities()) path->visit(*this);
  28. }
  29. double compute(const ExtrusionEntity &entity) && {
  30. entity.visit(*this);
  31. return volume;
  32. }
  33. };
  34. SCENARIO("extrude_thinwalls") {
  35. GIVEN("ThickLine") {
  36. ExPolygon expolygon;
  37. expolygon.contour = Slic3r::Polygon{ Points{
  38. Point::new_scale(-0.5, 0),
  39. Point::new_scale(0.5, 0),
  40. Point::new_scale(0.3, 10),
  41. Point::new_scale(-0.3, 10) } };
  42. ThickPolylines res;
  43. MedialAxis{ expolygon, scale_t(1.1), scale_t(0.5), scale_t(0.2) }.build(res);
  44. Flow periflow = Flow::new_from_width(1.1f, 0.4f, 0.2f, 1.f, false);
  45. ExtrusionEntityCollection gap_fill;
  46. gap_fill.append(thin_variable_width(res, erGapFill, periflow, SCALED_EPSILON*2, true));
  47. //Flow gapfill_max_flow = Flow::new_from_spacing(1.f, 0.4f, 0.2f, 1.f, false);
  48. //std::string gcode = gcodegen.get_visitor_gcode();
  49. THEN("analyse extrusion.") {
  50. ExtrusionVolumeVisitor vis;
  51. std::cout << " volume is " << ExtrusionVolumeVisitor{}.compute(gap_fill) << "\n";
  52. std::cout << " wanted volume is " << ((0.6*0.2 * 10) + (0.2*0.2 * 10)) << "\n";
  53. REQUIRE(std::abs(ExtrusionVolumeVisitor{}.compute(gap_fill) - ((0.6*0.2 * 10) + (0.2*0.2 * 10)))<0.01);
  54. }
  55. }
  56. }
  57. SCENARIO("thin walls: ")
  58. {
  59. GIVEN("Square")
  60. {
  61. Points test_set;
  62. test_set.reserve(4);
  63. Points square {Point::new_scale(100, 100),
  64. Point::new_scale(200, 100),
  65. Point::new_scale(200, 200),
  66. Point::new_scale(100, 200)};
  67. Slic3r::Polygon hole_in_square{ Points{
  68. Point::new_scale(140, 140),
  69. Point::new_scale(140, 160),
  70. Point::new_scale(160, 160),
  71. Point::new_scale(160, 140) } };
  72. ExPolygon expolygon;
  73. expolygon.contour = Slic3r::Polygon{ square };
  74. expolygon.holes = Slic3r::Polygons{ hole_in_square };
  75. WHEN("creating the medial axis"){
  76. Polylines res;
  77. expolygon.medial_axis(scale_(40), scale_(0.5), &res);
  78. THEN("medial axis of a square shape is a single path"){
  79. REQUIRE(res.size() == 1);
  80. }
  81. THEN("polyline forms a closed loop"){
  82. REQUIRE(res[0].first_point().coincides_with(res[0].last_point()) == true);
  83. }
  84. THEN("medial axis loop has reasonable length"){
  85. REQUIRE(res[0].length() > expolygon.holes[0].length());
  86. REQUIRE(res[0].length() < expolygon.contour.length());
  87. }
  88. }
  89. }
  90. GIVEN("narrow rectangle") {
  91. ExPolygon expolygon;
  92. expolygon.contour = Slic3r::Polygon{ Points{
  93. Point::new_scale(100, 100),
  94. Point::new_scale(120, 100),
  95. Point::new_scale(120, 200),
  96. Point::new_scale(100, 200) } };
  97. Polylines res;
  98. expolygon.medial_axis(scale_(20), scale_(0.5), &res);
  99. ExPolygon expolygon2;
  100. expolygon2.contour = Slic3r::Polygon{ Points{
  101. Point::new_scale(100, 100),
  102. Point::new_scale(120, 100),
  103. Point::new_scale(120, 200),
  104. Point::new_scale(105, 200), // extra point in the short side
  105. Point::new_scale(100, 200) } };
  106. Polylines res2;
  107. expolygon.medial_axis(scale_(20), scale_(0.5), &res2);
  108. WHEN("creating the medial axis") {
  109. THEN("medial axis of a narrow rectangle is a single line") {
  110. REQUIRE(res.size() == 1);
  111. THEN("medial axis has reasonable length") {
  112. REQUIRE(res[0].length() >= scale_(200 - 100 - (120 - 100)) - SCALED_EPSILON);
  113. }
  114. }
  115. THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") {
  116. REQUIRE(res2.size() == 1);
  117. THEN("medial axis of a narrow rectangle with an extra vertex has reasonable length") {
  118. REQUIRE(res2[0].length() >= scale_(200 - 100 - (120 - 100)) - SCALED_EPSILON);
  119. }
  120. THEN("extra vertices don't influence medial axis") {
  121. REQUIRE(res2[0].length() - res[0].length() < SCALED_EPSILON);
  122. }
  123. }
  124. }
  125. }
  126. //TODO: compare with mainline slic3r
  127. GIVEN("semicicumference") {
  128. ExPolygon expolygon;
  129. expolygon.contour = Slic3r::Polygon{ Points{
  130. Point{ 1185881, 829367 }, Point{ 1421988, 1578184 }, Point{ 1722442, 2303558 }, Point{ 2084981, 2999998 }, Point{ 2506843, 3662186 }, Point{ 2984809, 4285086 }, Point{ 3515250, 4863959 }, Point{ 4094122, 5394400 }, Point{ 4717018, 5872368 },
  131. Point{ 5379210, 6294226 }, Point{ 6075653, 6656769 }, Point{ 6801033, 6957229 }, Point{ 7549842, 7193328 }, Point{ 8316383, 7363266 }, Point{ 9094809, 7465751 }, Point{ 9879211, 7500000 }, Point{ 10663611, 7465750 }, Point{ 11442038, 7363265 },
  132. Point{ 12208580, 7193327 }, Point{ 12957389, 6957228 }, Point{ 13682769, 6656768 }, Point{ 14379209, 6294227 }, Point{ 15041405, 5872366 }, Point{ 15664297, 5394401 }, Point{ 16243171, 4863960 }, Point{ 16758641, 4301424 }, Point{ 17251579, 3662185 },
  133. Point{ 17673439, 3000000 }, Point{ 18035980, 2303556 }, Point{ 18336441, 1578177 }, Point{ 18572539, 829368 }, Point{ 18750748, 0 }, Point{ 19758422, 0 }, Point{ 19727293, 236479 }, Point{ 19538467, 1088188 }, Point{ 19276136, 1920196 },
  134. Point{ 18942292, 2726179 }, Point{ 18539460, 3499999 }, Point{ 18070731, 4235755 }, Point{ 17539650, 4927877 }, Point{ 16950279, 5571067 }, Point{ 16307090, 6160437 }, Point{ 15614974, 6691519 }, Point{ 14879209, 7160248 }, Point{ 14105392, 7563079 },
  135. Point{ 13299407, 7896927 }, Point{ 12467399, 8159255 }, Point{ 11615691, 8348082 }, Point{ 10750769, 8461952 }, Point{ 9879211, 8500000 }, Point{ 9007652, 8461952 }, Point{ 8142729, 8348082 }, Point{ 7291022, 8159255 }, Point{ 6459015, 7896927 },
  136. Point{ 5653029, 7563079 }, Point{ 4879210, 7160247 }, Point{ 4143447, 6691519 }, Point{ 3451331, 6160437 }, Point{ 2808141, 5571066 }, Point{ 2218773, 4927878 }, Point{ 1687689, 4235755 }, Point{ 1218962, 3499999 }, Point{ 827499, 2748020 },
  137. Point{ 482284, 1920196 }, Point{ 219954, 1088186 }, Point{ 31126, 236479 }, Point{ 0, 0 }, Point{ 1005754, 0 }
  138. } };
  139. WHEN("creating the medial axis") {
  140. Polylines res;
  141. expolygon.medial_axis(scale_(1.324888), scale_(0.25), &res);
  142. THEN("medial axis of a semicircumference is a single line") {
  143. REQUIRE(res.size() == 1);
  144. }
  145. THEN("all medial axis segments of a semicircumference have the same orientation (but the 2 end points)") {
  146. Lines lines = res[0].lines();
  147. double min_angle = PI*4, max_angle = -PI*4;
  148. //std::cout << "first angle=" << lines[0].ccw(lines[1].b) << "\n";
  149. for (int idx = 1; idx < lines.size() - 1; idx++) {
  150. assert(lines[idx].a== lines[idx - 1].b);
  151. double angle = lines[idx].a.ccw_angle(lines[idx - 1].a, lines[idx].b);
  152. if (std::abs(angle) - EPSILON < 0) angle = 0;
  153. //if (angle < 0) std::cout << unscale_(lines[idx - 1].a.x()) << ":" << unscale_(lines[idx - 1].a.y()) << " -> " << unscale_(lines[idx - 1].b.x()) << ":" << unscale_(lines[idx - 1].b.y()) << " -> " << unscale_(lines[idx].b.x()) << ":" << unscale_(lines[idx].b.y()) << "\n";
  154. std::cout << "angle=" << 180*angle/PI << "\n";
  155. min_angle = std::min(min_angle, angle);
  156. max_angle = std::max(max_angle, angle);
  157. }
  158. //std::cout << "last angle=" << lines[lines.size() - 2].ccw(lines[lines.size() - 1].b) << "\n";
  159. // check whether turns are all CCW or all CW
  160. bool allccw = (min_angle <= 0 && max_angle <= 0);
  161. bool allcw = (min_angle >= 0 && max_angle >= 0);
  162. bool allsame_orientation = allccw || allcw;
  163. REQUIRE(allsame_orientation);
  164. }
  165. }
  166. }
  167. GIVEN("round with large and very small distance between points"){
  168. ExPolygon expolygon;
  169. expolygon.contour = Slic3r::Polygon{ Points{
  170. Point::new_scale(15.181601,-2.389639), Point::new_scale(15.112616,-1.320034), Point::new_scale(14.024491,-0.644338), Point::new_scale(13.978982,-0.624495), Point::new_scale(9.993299,0.855584), Point::new_scale(9.941970,0.871195), Point::new_scale(5.796743,1.872643),
  171. Point::new_scale(5.743826,1.882168), Point::new_scale(1.509170,2.386464), Point::new_scale(1.455460,2.389639), Point::new_scale(-2.809359,2.389639), Point::new_scale(-2.862805,2.386464), Point::new_scale(-7.097726,1.882168), Point::new_scale(-7.150378,1.872643), Point::new_scale(-11.286344,0.873576),
  172. Point::new_scale(-11.335028,0.858759), Point::new_scale(-14.348632,-0.237938), Point::new_scale(-14.360538,-0.242436), Point::new_scale(-15.181601,-0.737570), Point::new_scale(-15.171309,-2.388509)
  173. } };
  174. expolygon.holes.push_back(Slic3r::Polygon{ Points{
  175. Point::new_scale( -11.023311,-1.034226 ), Point::new_scale( -6.920984,-0.042941 ), Point::new_scale( -2.768613,0.463207 ), Point::new_scale( 1.414714,0.463207 ), Point::new_scale( 5.567085,-0.042941 ), Point::new_scale( 9.627910,-1.047563 )
  176. } });
  177. WHEN("creating the medial axis"){
  178. Polylines res;
  179. expolygon.medial_axis(scale_(2.5), scale_(0.5), &res);
  180. THEN("medial axis of it is two line"){
  181. REQUIRE(res.size() == 2);
  182. }
  183. }
  184. }
  185. GIVEN("french cross")
  186. {
  187. ExPolygon expolygon;
  188. expolygon.contour = Slic3r::Polygon{ Points{
  189. Point::new_scale(4.3, 4), Point::new_scale(4.3, 0), Point::new_scale(4, 0), Point::new_scale(4, 4), Point::new_scale(0, 4), Point::new_scale(0, 4.5), Point::new_scale(4, 4.5), Point::new_scale(4, 10), Point::new_scale(4.3, 10), Point::new_scale(4.3, 4.5),
  190. Point::new_scale(6, 4.5), Point::new_scale(6, 10), Point::new_scale(6.2, 10), Point::new_scale(6.2, 4.5), Point::new_scale(10, 4.5), Point::new_scale(10, 4), Point::new_scale(6.2, 4), Point::new_scale(6.2, 0), Point::new_scale(6, 0), Point::new_scale(6, 4),
  191. } };
  192. expolygon.contour.make_counter_clockwise();
  193. WHEN("creating the medial axis"){
  194. Polylines res;
  195. expolygon.medial_axis(scale_(0.55), scale_(0.25), &res);
  196. THEN("medial axis of a (bit too narrow) french cross is two lines"){
  197. REQUIRE(res.size() == 2);
  198. }
  199. THEN("medial axis has reasonable length"){
  200. REQUIRE(res[0].length() >= scale_(9.9) - SCALED_EPSILON);
  201. REQUIRE(res[1].length() >= scale_(9.9) - SCALED_EPSILON);
  202. }
  203. THEN("medial axis of a (bit too narrow) french cross is two lines has only strait lines (first line)"){
  204. Lines lines = res[0].lines();
  205. double min_angle = 1, max_angle = -1;
  206. for (int idx = 1; idx < lines.size(); idx++){
  207. double angle = lines[idx - 1].ccw(lines[idx].b);
  208. min_angle = std::min(min_angle, angle);
  209. max_angle = std::max(max_angle, angle);
  210. }
  211. REQUIRE(min_angle == max_angle);
  212. REQUIRE(min_angle == 0);
  213. }
  214. THEN("medial axis of a (bit too narrow) french cross is two lines has only strait lines (second line)"){
  215. Lines lines = res[1].lines();
  216. double min_angle = 1, max_angle = -1;
  217. for (int idx = 1; idx < lines.size(); idx++){
  218. double angle = lines[idx - 1].ccw(lines[idx].b);
  219. min_angle = std::min(min_angle, angle);
  220. max_angle = std::max(max_angle, angle);
  221. }
  222. REQUIRE(min_angle == max_angle);
  223. REQUIRE(min_angle == 0);
  224. }
  225. }
  226. }
  227. //TODO: compare with mainline slic3r
  228. //GIVEN("tooth")
  229. //{
  230. // ExPolygon expolygon;
  231. // expolygon.contour = Slic3r::Polygon{ Points{
  232. // Point::new_scale(0.86526705, 1.4509841), Point::new_scale(0.57696039, 1.8637021),
  233. // Point::new_scale(0.4502297, 2.5569978), Point::new_scale(0.45626199, 3.2965596),
  234. // Point::new_scale(1.1218851, 3.3049455), Point::new_scale(0.96681072, 2.8243202),
  235. // Point::new_scale(0.86328971, 2.2056997), Point::new_scale(0.85367905, 1.7790778)
  236. // } };
  237. // expolygon.contour.make_counter_clockwise();
  238. // WHEN("creating the medial axis"){
  239. // Polylines res;
  240. // expolygon.medial_axis(scale_(1), scale_(0.25), &res);
  241. // THEN("medial axis of a tooth is two lines"){
  242. // REQUIRE(res.size() == 2);
  243. // THEN("medial axis has reasonable length") {
  244. // REQUIRE(res[0].length() >= scale_(1.4) - SCALED_EPSILON);
  245. // REQUIRE(res[1].length() >= scale_(1.4) - SCALED_EPSILON);
  246. // // TODO: check if min width is < 0.3 and max width is > 0.6 (min($res->[0]->width.front, $res->[0]->width.back) # problem: can't have access to width
  247. // //TODO: now i have access! correct it!
  248. // }
  249. // }
  250. // }
  251. //}
  252. GIVEN("Anchor & Tapers")
  253. {
  254. ExPolygon tooth;
  255. tooth.contour = Slic3r::Polygon{ Points{
  256. Point::new_scale(0,0), Point::new_scale(10,0), Point::new_scale(10,1.2), Point::new_scale(0,1.2)
  257. } };
  258. tooth.contour.make_counter_clockwise();
  259. ExPolygon base_part;
  260. base_part.contour = Slic3r::Polygon{ Points{
  261. Point::new_scale(0,-3), Point::new_scale(0,3), Point::new_scale(-2,3), Point::new_scale(-2,-3)
  262. } };
  263. base_part.contour.make_counter_clockwise();
  264. //expolygon.contour = Slic3r::Polygon{ Points{
  265. // //Point::new_scale(0, 13), Point::new_scale(-1, 13), Point::new_scale(-1, 0), Point::new_scale(0.0,0.0),
  266. // Point::new_scale(0,0.2), Point::new_scale(3,0.2), Point::new_scale(3,0.4), Point::new_scale(0,0.4),
  267. // Point::new_scale(0,1), Point::new_scale(3,1), Point::new_scale(3,1.3), Point::new_scale(0,1.3),
  268. // Point::new_scale(0,2), Point::new_scale(3,2), Point::new_scale(3,2.4), Point::new_scale(0,2.4),
  269. // Point::new_scale(0,3), Point::new_scale(3,3), Point::new_scale(3,3.5), Point::new_scale(0,3.5),
  270. // Point::new_scale(0,4), Point::new_scale(3,4), Point::new_scale(3,4.6), Point::new_scale(0,4.6),
  271. // Point::new_scale(0,5), Point::new_scale(3,5), Point::new_scale(3,5.7), Point::new_scale(0,5.7),
  272. // Point::new_scale(0,6), Point::new_scale(3,6), Point::new_scale(3,6.8), Point::new_scale(0,6.8),
  273. // Point::new_scale(0,7.5), Point::new_scale(3,7.5), Point::new_scale(3,8.4), Point::new_scale(0,8.4),
  274. // Point::new_scale(0,9), Point::new_scale(3,9), Point::new_scale(3,10), Point::new_scale(0,10),
  275. // Point::new_scale(0,11), Point::new_scale(3,11), Point::new_scale(3,12.2), Point::new_scale(0,12.2),
  276. //} };
  277. WHEN("1 nozzle, 0.2 layer height") {
  278. const coord_t nozzle_diam = scale_(1);
  279. ExPolygon anchor = union_ex(ExPolygons{ tooth }, intersection_ex(ExPolygons{ base_part }, offset_ex(tooth, nozzle_diam / 2)), ApplySafetyOffset::Yes)[0];
  280. ThickPolylines res;
  281. //expolygon.medial_axis(scale_(1), scale_(0.5), &res);
  282. Slic3r::Geometry::MedialAxis ma(tooth, /*min_width*/nozzle_diam * 2, /*max_width*/nozzle_diam/3, /*height*/scale_(0.2));
  283. ma.use_bounds(anchor)
  284. .use_min_real_width(nozzle_diam)
  285. .use_tapers(0.25*nozzle_diam);
  286. ma.build(res);
  287. THEN("medial axis of a simple line is one line") {
  288. REQUIRE(res.size() == 1);
  289. THEN("medial axis has the length of the line + the length of the anchor") {
  290. std::cout << res[0].length() << "\n";
  291. REQUIRE(std::abs(res[0].length() - scale_(10.5)) < SCALED_EPSILON);
  292. }
  293. THEN("medial axis has the line width as max width") {
  294. double max_width = 0;
  295. for (coordf_t width : res[0].points_width) max_width = std::max(max_width, width);
  296. REQUIRE(std::abs(max_width - scale_(1.2)) < SCALED_EPSILON);
  297. }
  298. //compute the length of the tapers
  299. THEN("medial axis has good tapers length") {
  300. int l1 = 0;
  301. for (size_t idx = 0; idx < res[0].points_width.size() - 1 && res[0].points_width[idx] - nozzle_diam < SCALED_EPSILON; ++idx)
  302. l1 += res[0].lines()[idx].length();
  303. int l2 = 0;
  304. for (size_t idx = res[0].points_width.size() - 1; idx > 0 && res[0].points_width[idx] - nozzle_diam < SCALED_EPSILON; --idx)
  305. l2 += res[0].lines()[idx - 1].length();
  306. REQUIRE(std::abs(l1 - l2) < SCALED_EPSILON);
  307. REQUIRE(std::abs(l1 - scale_(0.25 - 0.1)) < SCALED_EPSILON);
  308. }
  309. }
  310. }
  311. WHEN("1.2 nozzle, 0.6 layer height") {
  312. const coord_t nozzle_diam = scale_(1.2);
  313. ExPolygon anchor = union_ex(ExPolygons{ tooth }, intersection_ex(ExPolygons{ base_part }, offset_ex(tooth, nozzle_diam / 4)), ApplySafetyOffset::Yes)[0];
  314. ThickPolylines res;
  315. //expolygon.medial_axis(scale_(1), scale_(0.5), &res);
  316. Slic3r::Geometry::MedialAxis ma(tooth, /*min_width*/nozzle_diam * 2, /*max_width*/nozzle_diam/3, /*height*/scale_(0.6));
  317. ma.use_bounds(anchor)
  318. .use_min_real_width(nozzle_diam)
  319. .use_tapers(1.0*nozzle_diam);
  320. ma.build(res);
  321. THEN("medial axis of a simple line is one line") {
  322. REQUIRE(res.size() == 1);
  323. THEN("medial axis has the length of the line + the length of the anchor") {
  324. //0.3 because it's offseted by nozzle_diam / 4
  325. REQUIRE(std::abs(res[0].length() - scale_(10.3)) < SCALED_EPSILON);
  326. }
  327. THEN("medial axis can'ty have a line width below Flow::new_from_spacing(nozzle_diam).width") {
  328. double max_width = 0;
  329. for (coordf_t width : res[0].points_width) max_width = std::max(max_width, width);
  330. double min_width = Flow::new_from_spacing(float(unscaled(nozzle_diam)), float(unscaled(nozzle_diam)), 0.6f, 1.f, false).scaled_width();
  331. REQUIRE(std::abs(max_width - min_width) < SCALED_EPSILON);
  332. REQUIRE(std::abs(max_width - nozzle_diam) > SCALED_EPSILON);
  333. }
  334. //compute the length of the tapers
  335. THEN("medial axis has a 45� taper and a shorter one") {
  336. coord_t l1 = 0;
  337. for (size_t idx = 0; idx < res[0].points_width.size() - 1 && res[0].points_width[idx] - scale_(1.2) < SCALED_EPSILON; ++idx)
  338. l1 += coord_t(res[0].lines()[idx].length());
  339. coord_t l2 = 0;
  340. for (size_t idx = res[0].points_width.size() - 1; idx > 0 && res[0].points_width[idx] - scale_(1.2) < SCALED_EPSILON; --idx)
  341. l2 += coord_t(res[0].lines()[idx - 1].length());
  342. //here the taper is limited by the 0-width spacing
  343. double min_width = Flow::new_from_spacing(float(unscaled(nozzle_diam)), float(unscaled(nozzle_diam)), 0.6f, 1.f, false).scaled_width();
  344. REQUIRE(std::abs(l1 - l2) < SCALED_EPSILON);
  345. REQUIRE(l1 < scale_t(0.6));
  346. REQUIRE(l1 > scale_t(0.4));
  347. }
  348. }
  349. }
  350. }
  351. GIVEN("1� rotated tooths")
  352. {
  353. }
  354. GIVEN("narrow trapezoid")
  355. {
  356. ExPolygon expolygon;
  357. expolygon.contour = Slic3r::Polygon{ Points{
  358. Point::new_scale(100, 100),
  359. Point::new_scale(120, 100),
  360. Point::new_scale(112, 200),
  361. Point::new_scale(108, 200)
  362. } };
  363. WHEN("creating the medial axis"){
  364. Polylines res;
  365. expolygon.medial_axis(scale_(20), scale_(0.5), &res);
  366. THEN("medial axis of a narrow trapezoid is a single line"){
  367. REQUIRE(res.size() == 1);
  368. THEN("medial axis has reasonable length") {
  369. REQUIRE(res[0].length() >= scale_(200 - 100 - (120 - 100)) - SCALED_EPSILON);
  370. }
  371. }
  372. }
  373. }
  374. GIVEN("L shape")
  375. {
  376. ExPolygon expolygon;
  377. expolygon.contour = Slic3r::Polygon{ Points{
  378. Point::new_scale(100, 100),
  379. Point::new_scale(120, 100),
  380. Point::new_scale(120, 180),
  381. Point::new_scale(200, 180),
  382. Point::new_scale(200, 200),
  383. Point::new_scale(100, 200)
  384. } };
  385. WHEN("creating the medial axis"){
  386. Polylines res;
  387. expolygon.medial_axis(scale_(20), scale_(0.5), &res);
  388. THEN("medial axis of a L shape is a single line"){
  389. REQUIRE(res.size() == 1);
  390. THEN("medial axis has reasonable length") {
  391. // 20 is the thickness of the expolygon, which is subtracted from the ends
  392. REQUIRE(res[0].length() + 20 > scale_(80 * 2) - SCALED_EPSILON);
  393. REQUIRE(res[0].length() + 20 < scale_(100 * 2) + SCALED_EPSILON);
  394. }
  395. }
  396. }
  397. }
  398. GIVEN("shape"){
  399. ExPolygon expolygon;
  400. expolygon.contour = Slic3r::Polygon{ Points{
  401. Point{ -203064906, -51459966 }, Point{ -219312231, -51459966 }, Point{ -219335477, -51459962 }, Point{ -219376095, -51459962 }, Point{ -219412047, -51459966 },
  402. Point{ -219572094, -51459966 }, Point{ -219624814, -51459962 }, Point{ -219642183, -51459962 }, Point{ -219656665, -51459966 }, Point{ -220815482, -51459966 },
  403. Point{ -220815482, -37738966 }, Point{ -221117540, -37738966 }, Point{ -221117540, -51762024 }, Point{ -203064906, -51762024 },
  404. } };
  405. WHEN("creating the medial axis"){
  406. Polylines polylines;
  407. expolygon.medial_axis(819998, 102499.75, &polylines);
  408. double perimeter_len = expolygon.contour.split_at_first_point().length();
  409. THEN("medial axis has reasonable length"){
  410. double polyline_length = 0;
  411. for (Slic3r::Polyline &poly : polylines) polyline_length += poly.length();
  412. REQUIRE(polyline_length > perimeter_len * 3. / 8. - SCALED_EPSILON);
  413. }
  414. }
  415. }
  416. GIVEN("narrow triangle")
  417. {
  418. ExPolygon expolygon;
  419. expolygon.contour = Slic3r::Polygon{ Points{
  420. Point::new_scale(50, 100),
  421. Point::new_scale(1000, 102),
  422. Point::new_scale(50, 104)
  423. } };
  424. WHEN("creating the medial axis"){
  425. Polylines res;
  426. expolygon.medial_axis(scale_(4), scale_(0.5), &res);
  427. THEN("medial axis of a narrow triangle is a single line"){
  428. REQUIRE(res.size() == 1);
  429. THEN("medial axis has reasonable length") {
  430. REQUIRE(res[0].length() > scale_(200 - 100 - (120 - 100)) - SCALED_EPSILON);
  431. }
  432. }
  433. }
  434. }
  435. GIVEN("GH #2474")
  436. {
  437. ExPolygon expolygon;
  438. expolygon.contour = Slic3r::Polygon{ Points{
  439. Point{91294454, 31032190},
  440. Point{11294481, 31032190},
  441. Point{11294481, 29967810},
  442. Point{44969182, 29967810},
  443. Point{89909960, 29967808},
  444. Point{91294454, 29967808}
  445. } };
  446. WHEN("creating the medial axis") {
  447. Polylines res;
  448. expolygon.medial_axis(1871238, 500000, &res);
  449. THEN("medial axis is a single polyline") {
  450. REQUIRE(res.size() == 1);
  451. Slic3r::Polyline polyline = res[0];
  452. THEN("medial axis is horizontal and is centered") {
  453. double sum = 0;
  454. for (Line &l : polyline.lines()) sum += std::abs(l.b.y() - l.a.y());
  455. coord_t expected_y = expolygon.contour.bounding_box().center().y();
  456. REQUIRE((sum / polyline.size()) - expected_y < SCALED_EPSILON);
  457. }
  458. // order polyline from left to right
  459. if (polyline.first_point().x() > polyline.last_point().x()) polyline.reverse();
  460. THEN("expected x_min & x_max") {
  461. BoundingBox polyline_bb = polyline.bounding_box();
  462. REQUIRE(polyline.first_point().x() == polyline_bb.min.x());
  463. REQUIRE(polyline.last_point().x() == polyline_bb.max.x());
  464. }
  465. THEN("medial axis is not self-overlapping") {
  466. //TODO
  467. //REQUIRE(polyline.first_point().x() == polyline_bb.x_min());
  468. std::vector<coord_t> all_x;
  469. for (Point &p : polyline.points) all_x.push_back(p.x());
  470. std::vector<coord_t> sorted_x{ all_x };
  471. std::sort(sorted_x.begin(), sorted_x.end());
  472. for (size_t i = 0; i < all_x.size(); i++) {
  473. REQUIRE(all_x[i] == sorted_x[i]);
  474. }
  475. }
  476. }
  477. }
  478. }
  479. }