test_bridges.cpp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. #include <catch2/catch.hpp>
  2. #include <libslic3r/BridgeDetector.hpp>
  3. #include <libslic3r/Geometry.hpp>
  4. #include "test_data.hpp"
  5. using namespace Slic3r;
  6. SCENARIO("Bridge detector", "[Bridging]")
  7. {
  8. auto check_angle = [](const ExPolygons &lower, const ExPolygon &bridge, double expected, double tolerance = -1, double expected_coverage = -1)
  9. {
  10. if (expected_coverage < 0)
  11. expected_coverage = bridge.area();
  12. BridgeDetector bridge_detector(bridge, lower, scaled<coord_t>(0.5)); // extrusion width
  13. if (tolerance < 0)
  14. tolerance = Geometry::rad2deg(bridge_detector.resolution) + EPSILON;
  15. bridge_detector.detect_angle();
  16. double result = bridge_detector.angle;
  17. Polygons coverage = bridge_detector.coverage();
  18. THEN("correct coverage area") {
  19. REQUIRE(is_approx(area(coverage), expected_coverage));
  20. }
  21. // our epsilon is equal to the steps used by the bridge detection algorithm
  22. //##use XXX; YYY [ rad2deg($result), $expected ];
  23. // returned value must be non-negative, check for that too
  24. double delta = Geometry::rad2deg(result) - expected;
  25. if (delta >= 180. - EPSILON)
  26. delta -= 180;
  27. return result >= 0. && std::abs(delta) < tolerance;
  28. };
  29. GIVEN("O-shaped overhang") {
  30. auto test = [&check_angle](const Point &size, double rotate, double expected_angle, double tolerance = -1) {
  31. ExPolygon lower{
  32. Polygon::new_scale({ {-2,-2}, {size.x()+2,-2}, {size.x()+2,size.y()+2}, {-2,size.y()+2} }),
  33. Polygon::new_scale({ {0,0}, {0,size.y()}, {size.x(),size.y()}, {size.x(),0} } )
  34. };
  35. lower.rotate(Geometry::deg2rad(rotate), size / 2);
  36. ExPolygon bridge_expoly(lower.holes.front());
  37. bridge_expoly.contour.reverse();
  38. return check_angle({ lower }, bridge_expoly, expected_angle, tolerance);
  39. };
  40. WHEN("Bridge size 20x10") {
  41. bool valid = test({20,10}, 0., 90.);
  42. THEN("bridging angle is 90 degrees") {
  43. REQUIRE(valid);
  44. }
  45. }
  46. WHEN("Bridge size 10x20") {
  47. bool valid = test({10,20}, 0., 0.);
  48. THEN("bridging angle is 0 degrees") {
  49. REQUIRE(valid);
  50. }
  51. }
  52. WHEN("Bridge size 20x10, rotated by 45 degrees") {
  53. bool valid = test({20,10}, 45., 135., 20.);
  54. THEN("bridging angle is 135 degrees") {
  55. REQUIRE(valid);
  56. }
  57. }
  58. WHEN("Bridge size 20x10, rotated by 135 degrees") {
  59. bool valid = test({20,10}, 135., 45., 20.);
  60. THEN("bridging angle is 45 degrees") {
  61. REQUIRE(valid);
  62. }
  63. }
  64. }
  65. GIVEN("two-sided bridge") {
  66. ExPolygon bridge{ Polygon::new_scale({ {0,0}, {20,0}, {20,10}, {0,10} }) };
  67. ExPolygons lower { ExPolygon{ Polygon::new_scale({ {-2,0}, {0,0}, {0,10}, {-2,10} }) } };
  68. lower.emplace_back(lower.front());
  69. lower.back().translate(Point::new_scale(22, 0));
  70. THEN("Bridging angle 0 degrees") {
  71. REQUIRE(check_angle(lower, bridge, 0));
  72. }
  73. }
  74. GIVEN("for C-shaped overhang") {
  75. ExPolygon bridge{ Polygon::new_scale({ {0,0}, {20,0}, {10,10}, {0,10} }) };
  76. ExPolygon lower{ Polygon::new_scale({ {0,0}, {0,10}, {10,10}, {10,12}, {-2,12}, {-2,-2}, {22,-2}, {22,0} }) };
  77. bool valid = check_angle({ lower }, bridge, 135);
  78. THEN("Bridging angle is 135 degrees") {
  79. REQUIRE(valid);
  80. }
  81. }
  82. GIVEN("square overhang with L-shaped anchors") {
  83. ExPolygon bridge{ Polygon::new_scale({ {10,10}, {20,10}, {20,20}, {10,20} }) };
  84. ExPolygon lower{ Polygon::new_scale({ {10,10}, {10,20}, {20,20}, {30,30}, {0,30}, {0,0} }) };
  85. bool valid = check_angle({ lower }, bridge, 45., -1., bridge.area() / 2.);
  86. THEN("Bridging angle is 45 degrees") {
  87. REQUIRE(valid);
  88. }
  89. }
  90. }
  91. SCENARIO("Bridging integration", "[Bridging]") {
  92. DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config_with({
  93. { "top_solid_layers", 0 },
  94. // to prevent bridging on sparse infill
  95. { "bridge_speed", 99 }
  96. });
  97. std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::bridge }, config);
  98. GCodeReader parser;
  99. const double bridge_speed = config.opt_float("bridge_speed") * 60.;
  100. // angle => length
  101. std::map<coord_t, double> extrusions;
  102. parser.parse_buffer(gcode, [&extrusions, bridge_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
  103. {
  104. // if the command is a T command, set the the current tool
  105. if (line.cmd() == "G1" && is_approx<double>(bridge_speed, line.new_F(self))) {
  106. // Accumulate lengths of bridging extrusions according to bridging angle.
  107. Line l{ self.xy_scaled(), line.new_XY_scaled(self) };
  108. size_t angle = scaled<coord_t>(l.direction());
  109. auto it = extrusions.find(angle);
  110. if (it == extrusions.end())
  111. it = extrusions.insert(std::make_pair(angle, 0.)).first;
  112. it->second += l.length();
  113. }
  114. });
  115. THEN("bridge is generated") {
  116. REQUIRE(! extrusions.empty());
  117. }
  118. THEN("bridge has the expected direction 0 degrees") {
  119. // Bridging with the longest extrusion.
  120. auto it_longest_extrusion = std::max_element(extrusions.begin(), extrusions.end(),
  121. [](const auto &e1, const auto &e2){ return e1.second < e2.second; });
  122. REQUIRE(it_longest_extrusion->first == 0);
  123. }
  124. }