test_seam_perimeters.cpp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. #include "libslic3r/ClipperUtils.hpp"
  2. #include "libslic3r/GCode/SeamPerimeters.hpp"
  3. #include "libslic3r/Layer.hpp"
  4. #include "libslic3r/Point.hpp"
  5. #include <catch2/catch.hpp>
  6. #include <libslic3r/GCode/SeamGeometry.hpp>
  7. #include <libslic3r/Geometry.hpp>
  8. #include <fstream>
  9. #include "test_data.hpp"
  10. using namespace Slic3r;
  11. using namespace Slic3r::Seams;
  12. constexpr bool debug_files{false};
  13. const ExPolygon square{
  14. scaled(Vec2d{0.0, 0.0}), scaled(Vec2d{1.0, 0.0}), scaled(Vec2d{1.0, 1.0}),
  15. scaled(Vec2d{0.0, 1.0})};
  16. TEST_CASE("Oversample painted", "[Seams][SeamPerimeters]") {
  17. auto is_painted{[](const Vec3f &position, float radius) {
  18. return (position - Vec3f{0.5, 0.0, 1.0}).norm() < radius;
  19. }};
  20. std::vector<Vec2d> points{Perimeters::Impl::oversample_painted(
  21. Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.2
  22. )};
  23. REQUIRE(points.size() == 8);
  24. CHECK((points[1] - Vec2d{0.2, 0.0}).norm() == Approx(0.0));
  25. points = Perimeters::Impl::oversample_painted(
  26. Seams::Geometry::unscaled(square.contour.points), is_painted, 1.0, 0.199
  27. );
  28. CHECK(points.size() == 9);
  29. }
  30. TEST_CASE("Remove redundant points", "[Seams][SeamPerimeters]") {
  31. using Perimeters::PointType;
  32. using Perimeters::PointClassification;
  33. std::vector<Vec2d> points{{0.0, 0.0}, {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0},
  34. {3.0, 1.0}, {3.0, 2.0}, {0.0, 2.0}};
  35. std::vector<PointType> point_types{PointType::common,
  36. PointType::enforcer, // Should keep this.
  37. PointType::enforcer, // Should keep this.
  38. PointType::blocker,
  39. PointType::blocker, // Should remove this.
  40. PointType::blocker, PointType::common};
  41. const auto [resulting_points, resulting_point_types]{
  42. Perimeters::Impl::remove_redundant_points(points, point_types, 0.1)};
  43. REQUIRE(resulting_points.size() == 6);
  44. REQUIRE(resulting_point_types.size() == 6);
  45. CHECK((resulting_points[3] - Vec2d{3.0, 0.0}).norm() == Approx(0.0));
  46. CHECK((resulting_points[4] - Vec2d{3.0, 2.0}).norm() == Approx(0.0));
  47. CHECK(resulting_point_types[3] == PointType::blocker);
  48. CHECK(resulting_point_types[4] == PointType::blocker);
  49. }
  50. TEST_CASE("Perimeter constructs KD trees", "[Seams][SeamPerimeters]") {
  51. using Perimeters::PointType;
  52. using Perimeters::PointClassification;
  53. using Perimeters::AngleType;
  54. std::vector<Vec2d> positions{Vec2d{0.0, 0.0}, Vec2d{1.0, 0.0}, Vec2d{1.0, 1.0}, Vec2d{0.0, 1.0}};
  55. std::vector<double> angles(4, -M_PI / 2.0);
  56. std::vector<PointType>
  57. point_types{PointType::enforcer, PointType::blocker, PointType::common, PointType::common};
  58. std::vector<PointClassification> point_classifications{
  59. PointClassification::overhang, PointClassification::embedded, PointClassification::embedded,
  60. PointClassification::common};
  61. std::vector<AngleType>
  62. angle_types{AngleType::convex, AngleType::concave, AngleType::smooth, AngleType::smooth};
  63. Perimeters::Perimeter perimeter{
  64. 3.0,
  65. 2,
  66. false,
  67. std::move(positions),
  68. std::move(angles),
  69. std::move(point_types),
  70. std::move(point_classifications),
  71. std::move(angle_types)};
  72. CHECK(perimeter.enforced_points.overhanging_points);
  73. CHECK(perimeter.blocked_points.embedded_points);
  74. CHECK(perimeter.common_points.common_points);
  75. CHECK(perimeter.common_points.embedded_points);
  76. }
  77. using std::filesystem::path;
  78. constexpr const char *to_string(Perimeters::PointType point_type) {
  79. using Perimeters::PointType;
  80. switch (point_type) {
  81. case PointType::enforcer: return "enforcer";
  82. case PointType::blocker: return "blocker";
  83. case PointType::common: return "common";
  84. }
  85. throw std::runtime_error("Unreachable");
  86. }
  87. constexpr const char *to_string(Perimeters::PointClassification point_classification) {
  88. using Perimeters::PointClassification;
  89. switch (point_classification) {
  90. case PointClassification::embedded: return "embedded";
  91. case PointClassification::overhang: return "overhang";
  92. case PointClassification::common: return "common";
  93. }
  94. throw std::runtime_error("Unreachable");
  95. }
  96. constexpr const char *to_string(Perimeters::AngleType angle_type) {
  97. using Perimeters::AngleType;
  98. switch (angle_type) {
  99. case AngleType::convex: return "convex";
  100. case AngleType::concave: return "concave";
  101. case AngleType::smooth: return "smooth";
  102. }
  103. throw std::runtime_error("Unreachable");
  104. }
  105. void serialize_shell(std::ostream &output, const Shells::Shell<Perimeters::Perimeter> &shell) {
  106. output << "x,y,z,point_type,point_classification,angle_type,layer_index,"
  107. "point_index,distance,distance_to_previous,is_degenerate"
  108. << std::endl;
  109. for (std::size_t perimeter_index{0}; perimeter_index < shell.size(); ++perimeter_index) {
  110. const Shells::Slice<> &slice{shell[perimeter_index]};
  111. const Perimeters::Perimeter &perimeter{slice.boundary};
  112. const std::vector<Vec2d> &points{perimeter.positions};
  113. double total_distance{0.0};
  114. for (std::size_t point_index{0}; point_index < perimeter.point_types.size(); ++point_index) {
  115. const Vec3d point{to_3d(points[point_index], perimeter.slice_z)};
  116. const Perimeters::PointType point_type{perimeter.point_types[point_index]};
  117. const Perimeters::PointClassification point_classification{
  118. perimeter.point_classifications[point_index]};
  119. const Perimeters::AngleType angle_type{perimeter.angle_types[point_index]};
  120. const std::size_t layer_index{slice.layer_index};
  121. const std::size_t previous_index{point_index == 0 ? points.size() - 1 : point_index - 1};
  122. const double distance_to_previous{(points[point_index] - points[previous_index]).norm()};
  123. total_distance += point_index == 0 ? 0.0 : distance_to_previous;
  124. const double distance{total_distance};
  125. const bool is_degenerate{perimeter.is_degenerate};
  126. // clang-format off
  127. output
  128. << point.x() << ","
  129. << point.y() << ","
  130. << point.z() << ","
  131. << to_string(point_type) << ","
  132. << to_string(point_classification) << ","
  133. << to_string(angle_type) << ","
  134. << layer_index << ","
  135. << point_index << ","
  136. << distance << ","
  137. << distance_to_previous << ","
  138. << is_degenerate << std::endl;
  139. // clang-format on
  140. }
  141. }
  142. }
  143. TEST_CASE_METHOD(Test::SeamsFixture, "Create perimeters", "[Seams][SeamPerimeters][Integration]") {
  144. Seams::Perimeters::LayerPerimeters perimeters{
  145. Seams::Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)};
  146. Seams::Shells::Shells<> shells{
  147. Seams::Shells::create_shells(std::move(perimeters), params.max_distance)};
  148. if constexpr (debug_files) {
  149. std::ofstream csv{"perimeters.csv"};
  150. serialize_shell(csv, shells[0]);
  151. }
  152. }