test_support_spots_generator.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #include "libslic3r/Point.hpp"
  2. #include <catch2/catch.hpp>
  3. #include <libslic3r/SupportSpotsGenerator.hpp>
  4. using namespace Slic3r;
  5. using namespace SupportSpotsGenerator;
  6. namespace Rectangle {
  7. const float width = 10;
  8. const float height = 20;
  9. const Polygon polygon = {
  10. scaled(Vec2f{-width / 2, -height / 2}),
  11. scaled(Vec2f{width / 2, -height / 2}),
  12. scaled(Vec2f{width / 2, height / 2}),
  13. scaled(Vec2f{-width / 2, height / 2})
  14. };
  15. }
  16. TEST_CASE("Numerical integral over polygon calculation compared with exact solution.", "[SupportSpotsGenerator]") {
  17. const Integrals integrals{Rectangle::polygon};
  18. CHECK(integrals.area == Approx(Rectangle::width * Rectangle::height));
  19. CHECK(integrals.x_i.x() == Approx(0));
  20. CHECK(integrals.x_i.y() == Approx(0));
  21. CHECK(integrals.x_i_squared.x() == Approx(std::pow(Rectangle::width, 3) * Rectangle::height / 12));
  22. CHECK(integrals.x_i_squared.y() == Approx(Rectangle::width * std::pow(Rectangle::height, 3) / 12));
  23. }
  24. TEST_CASE("Integrals over multiple polygons", "[SupportSpotsGenerator]") {
  25. const Integrals integrals{{Rectangle::polygon, Rectangle::polygon}};
  26. CHECK(integrals.area == Approx(2 * Rectangle::width * Rectangle::height));
  27. }
  28. TEST_CASE("Numerical integral over line calculation compared with exact solution.", "[SupportSpotsGenerator]") {
  29. const float length = 10;
  30. const float width = 20;
  31. const Polyline polyline{scaled(Vec2f{-length/2.0f, 0.0f}), scaled(Vec2f{length/2.0f, 0.0f})};
  32. const Integrals integrals{{polyline}, {width}};
  33. CHECK(integrals.area == Approx(length * width));
  34. CHECK(integrals.x_i.x() == Approx(0));
  35. CHECK(integrals.x_i.y() == Approx(0));
  36. CHECK(integrals.x_i_squared.x() == Approx(std::pow(length, 3) * width / 12));
  37. CHECK(integrals.x_i_squared.y() == Approx(length * std::pow(width, 3) / 12));
  38. }
  39. TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") {
  40. const float width = 40;
  41. const float height = 2;
  42. // Moments are calculated at centroid.
  43. // Polygon centroid must not be (0, 0).
  44. const Polygon polygon = {
  45. scaled(Vec2f{0, 0}),
  46. scaled(Vec2f{width, 0}),
  47. scaled(Vec2f{width, height}),
  48. scaled(Vec2f{0, height})
  49. };
  50. const Integrals integrals{polygon};
  51. const Vec2f x_axis{1, 0};
  52. const float x_axis_moment = compute_second_moment(integrals, x_axis);
  53. const Vec2f y_axis{0, 1};
  54. const float y_axis_moment = compute_second_moment(integrals, y_axis);
  55. const float moment_ratio = std::pow(width / height, 2);
  56. // Ensure the object transaltion has no effect.
  57. CHECK(x_axis_moment == Approx(width * std::pow(height, 3) / 12));
  58. CHECK(y_axis_moment == Approx(std::pow(width, 3) * height / 12));
  59. // If the object is "wide" the y axis moments should be large compared to x axis moment.
  60. CHECK(y_axis_moment / x_axis_moment == Approx(moment_ratio));
  61. }
  62. TEST_CASE("Moments calculation for rotated axis.", "[SupportSpotsGenerator]") {
  63. Polygon polygon = {
  64. scaled(Vec2f{6.362284076172198, 138.9674202217155}),
  65. scaled(Vec2f{97.48779843751677, 106.08136606617076}),
  66. scaled(Vec2f{135.75221821532384, 66.84428834668765}),
  67. scaled(Vec2f{191.5308049852741, 45.77905628725614}),
  68. scaled(Vec2f{182.7525148049201, 74.01799041087513}),
  69. scaled(Vec2f{296.83210979283473, 196.80022572637228}),
  70. scaled(Vec2f{215.16434429179148, 187.45715418834143}),
  71. scaled(Vec2f{64.64574271229334, 284.293883209721}),
  72. scaled(Vec2f{110.76507036894843, 174.35633141113783}),
  73. scaled(Vec2f{77.56229640885199, 189.33057746591336})
  74. };
  75. Integrals integrals{polygon};
  76. // Meassured counterclockwise from (1, 0)
  77. const float angle = 1.432f;
  78. Vec2f axis{std::cos(angle), std::sin(angle)};
  79. float moment_calculated_then_rotated = compute_second_moment(
  80. integrals,
  81. axis
  82. );
  83. // We want to rotate the object clockwise by angle to align the axis with (1, 0)
  84. // Method .rotate is counterclockwise for positive angle
  85. polygon.rotate(-angle);
  86. Integrals integrals_rotated{{polygon}};
  87. float moment_rotated_polygon = compute_second_moment(
  88. integrals_rotated,
  89. Vec2f{1, 0}
  90. );
  91. // Up to 0.1% accuracy
  92. CHECK_THAT(moment_calculated_then_rotated, Catch::Matchers::WithinRel(moment_rotated_polygon, 0.001f));
  93. }
  94. struct ObjectPartFixture {
  95. const Polyline polyline{
  96. Point{scaled(Vec2f{0, 0})},
  97. Point{scaled(Vec2f{1, 0})},
  98. };
  99. const float width = 0.1f;
  100. bool connected_to_bed = true;
  101. coordf_t print_head_z = 0.2;
  102. coordf_t layer_height = 0.2;
  103. ExtrusionAttributes attributes;
  104. ExtrusionEntityCollection collection;
  105. std::vector<const ExtrusionEntityCollection*> extrusions{};
  106. Polygon expected_polygon{
  107. Point{scaled(Vec2f{0, -width / 2})},
  108. Point{scaled(Vec2f{1, -width / 2})},
  109. Point{scaled(Vec2f{1, width / 2})},
  110. Point{scaled(Vec2f{0, width / 2})}
  111. };
  112. ObjectPartFixture() {
  113. attributes.width = width;
  114. const ExtrusionPath path{polyline, attributes};
  115. collection.append(path);
  116. extrusions.push_back(&collection);
  117. }
  118. };
  119. TEST_CASE_METHOD(ObjectPartFixture, "Constructing ObjectPart using extrusion collections", "[SupportSpotsGenerator]") {
  120. ObjectPart part{
  121. extrusions,
  122. connected_to_bed,
  123. print_head_z,
  124. layer_height,
  125. std::nullopt
  126. };
  127. Integrals expected{expected_polygon};
  128. CHECK(part.connected_to_bed == true);
  129. Vec3f volume_centroid{part.volume_centroid_accumulator / part.volume};
  130. CHECK(volume_centroid.x() == Approx(0.5));
  131. CHECK(volume_centroid.y() == Approx(0));
  132. CHECK(volume_centroid.z() == Approx(layer_height / 2));
  133. CHECK(part.sticking_area == Approx(expected.area));
  134. CHECK(part.sticking_centroid_accumulator.x() == Approx(expected.x_i.x()));
  135. CHECK(part.sticking_centroid_accumulator.y() == Approx(expected.x_i.y()));
  136. CHECK(part.sticking_second_moment_of_area_accumulator.x() == Approx(expected.x_i_squared.x()));
  137. CHECK(part.sticking_second_moment_of_area_accumulator.y() == Approx(expected.x_i_squared.y()));
  138. CHECK(part.sticking_second_moment_of_area_covariance_accumulator == Approx(expected.xy).margin(1e-6));
  139. CHECK(part.volume == Approx(layer_height * width));
  140. }
  141. TEST_CASE_METHOD(ObjectPartFixture, "Constructing ObjectPart with brim", "[SupportSpotsGenerator]") {
  142. float brim_width = 1;
  143. Polygons brim = get_brim(ExPolygon{expected_polygon}, BrimType::btOuterOnly, brim_width);
  144. ObjectPart part{
  145. extrusions,
  146. connected_to_bed,
  147. print_head_z,
  148. layer_height,
  149. brim
  150. };
  151. CHECK(part.sticking_area == Approx((1 + 2*brim_width) * (width + 2*brim_width)));
  152. }