test_gcode_travels.cpp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #include <catch2/catch.hpp>
  2. #include <libslic3r/GCode/Travels.hpp>
  3. #include <libslic3r/ExPolygon.hpp>
  4. #include <libslic3r/GCode.hpp>
  5. #include <boost/math/special_functions/pow.hpp>
  6. using namespace Slic3r;
  7. using namespace Slic3r::GCode::Impl::Travels;
  8. struct ApproxEqualsPoints : public Catch::MatcherBase<Points> {
  9. ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {}
  10. bool match(const Points& points) const override {
  11. if (points.size() != expected.size()) {
  12. return false;
  13. }
  14. for (auto i = 0u; i < points.size(); ++i) {
  15. const Point& point = points[i];
  16. const Point& expected_point = this->expected[i];
  17. if (
  18. std::abs(point.x() - expected_point.x()) > int(this->tolerance)
  19. || std::abs(point.y() - expected_point.y()) > int(this->tolerance)
  20. ) {
  21. return false;
  22. }
  23. }
  24. return true;
  25. }
  26. std::string describe() const override {
  27. std::stringstream ss;
  28. ss << std::endl;
  29. for (const Point& point : expected) {
  30. ss << "(" << point.x() << ", " << point.y() << ")" << std::endl;
  31. }
  32. ss << "With tolerance: " << this->tolerance;
  33. return "Equals " + ss.str();
  34. }
  35. private:
  36. Points expected;
  37. unsigned tolerance;
  38. };
  39. Points get_points(const std::vector<DistancedPoint>& result) {
  40. Points result_points;
  41. std::transform(
  42. result.begin(),
  43. result.end(),
  44. std::back_inserter(result_points),
  45. [](const DistancedPoint& point){
  46. return point.point;
  47. }
  48. );
  49. return result_points;
  50. }
  51. std::vector<double> get_distances(const std::vector<DistancedPoint>& result) {
  52. std::vector<double> result_distances;
  53. std::transform(
  54. result.begin(),
  55. result.end(),
  56. std::back_inserter(result_distances),
  57. [](const DistancedPoint& point){
  58. return point.distance_from_start;
  59. }
  60. );
  61. return result_distances;
  62. }
  63. TEST_CASE("Place points at distances - expected use", "[GCode]") {
  64. std::vector<Point> line{
  65. scaled(Vec2f{0, 0}),
  66. scaled(Vec2f{1, 0}),
  67. scaled(Vec2f{2, 1}),
  68. scaled(Vec2f{2, 2})
  69. };
  70. std::vector<double> distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0};
  71. std::vector<DistancedPoint> result = slice_xy_path(line, distances);
  72. REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{
  73. scaled(Vec2f{0, 0}),
  74. scaled(Vec2f{0.2, 0}),
  75. scaled(Vec2f{0.5, 0}),
  76. scaled(Vec2f{1, 0}),
  77. scaled(Vec2f{1.5, 0.5}),
  78. scaled(Vec2f{2, 1}),
  79. scaled(Vec2f{2, 1.5}),
  80. scaled(Vec2f{2, 2})
  81. }, 5));
  82. REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector<double>{
  83. distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2)
  84. }));
  85. }
  86. TEST_CASE("Place points at distances - edge case", "[GCode]") {
  87. std::vector<Point> line{
  88. scaled(Vec2f{0, 0}),
  89. scaled(Vec2f{1, 0}),
  90. scaled(Vec2f{2, 0})
  91. };
  92. std::vector<double> distances{0, 1, 1.5, 2};
  93. Points result{get_points(slice_xy_path(line, distances))};
  94. CHECK(result == Points{
  95. scaled(Vec2f{0, 0}),
  96. scaled(Vec2f{1, 0}),
  97. scaled(Vec2f{1.5, 0}),
  98. scaled(Vec2f{2, 0})
  99. });
  100. }
  101. TEST_CASE("Generate elevated travel", "[GCode]") {
  102. std::vector<Point> xy_path{
  103. scaled(Vec2f{0, 0}),
  104. scaled(Vec2f{1, 0}),
  105. };
  106. std::vector<double> ensure_points_at_distances{0.2, 0.5};
  107. Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})};
  108. CHECK(result == Points3{
  109. scaled(Vec3f{ 0.f, 0.f, 3.f}),
  110. scaled(Vec3f{0.2f, 0.f, 3.2f}),
  111. scaled(Vec3f{0.5f, 0.f, 3.5f}),
  112. scaled(Vec3f{ 1.f, 0.f, 4.f})
  113. });
  114. }
  115. TEST_CASE("Get first crossed line distance", "[GCode]") {
  116. // A 2x2 square at 0, 0, with 1x1 square hole in its center.
  117. ExPolygon square_with_hole{
  118. {
  119. scaled(Vec2f{-1, -1}),
  120. scaled(Vec2f{1, -1}),
  121. scaled(Vec2f{1, 1}),
  122. scaled(Vec2f{-1, 1})
  123. },
  124. {
  125. scaled(Vec2f{-0.5, -0.5}),
  126. scaled(Vec2f{0.5, -0.5}),
  127. scaled(Vec2f{0.5, 0.5}),
  128. scaled(Vec2f{-0.5, 0.5})
  129. }
  130. };
  131. // A 2x2 square above the previous square at (0, 3).
  132. ExPolygon square_above{
  133. {
  134. scaled(Vec2f{-1, 2}),
  135. scaled(Vec2f{1, 2}),
  136. scaled(Vec2f{1, 4}),
  137. scaled(Vec2f{-1, 4})
  138. }
  139. };
  140. // Bottom-up travel intersecting the squares.
  141. Lines travel{Polyline{
  142. scaled(Vec2f{0, -2}),
  143. scaled(Vec2f{0, -0.7}),
  144. scaled(Vec2f{0, 0}),
  145. scaled(Vec2f{0, 1}),
  146. scaled(Vec2f{0, 1.3}),
  147. scaled(Vec2f{0, 2.4}),
  148. scaled(Vec2f{0, 4.5}),
  149. scaled(Vec2f{0, 5}),
  150. }.lines()};
  151. std::vector<GCode::ObjectOrExtrusionLinef> lines;
  152. for (const ExPolygon& polygon : {square_with_hole, square_above}) {
  153. for (const Line& line : polygon.lines()) {
  154. lines.emplace_back(unscale(line.a), unscale(line.b));
  155. }
  156. }
  157. // Try different cases by skipping lines in the travel.
  158. AABBTreeLines::LinesDistancer<GCode::ObjectOrExtrusionLinef> distancer{std::move(lines)};
  159. CHECK(get_first_crossed_line_distance(travel, distancer) == Approx(1));
  160. CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));
  161. CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5));
  162. CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case
  163. CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7));
  164. CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6));
  165. CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer) == std::numeric_limits<double>::max());
  166. }
  167. TEST_CASE("Elevated travel formula", "[GCode]") {
  168. const double lift_height{10};
  169. const double slope_end{10};
  170. const double blend_width{10};
  171. const ElevatedTravelParams params{lift_height, slope_end, blend_width};
  172. ElevatedTravelFormula f{params};
  173. const double distance = slope_end - blend_width / 2;
  174. const double slope = (f(distance) - f(0)) / distance;
  175. // At the begining it has given slope.
  176. CHECK(slope == lift_height / slope_end);
  177. // At the end it is flat.
  178. CHECK(f(slope_end + blend_width / 2) == f(slope_end + blend_width));
  179. // Should be smoothed.
  180. CHECK(f(slope_end) < lift_height);
  181. }