test_trianglemesh.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. #include <catch2/catch.hpp>
  2. #include "libslic3r/TriangleMesh.hpp"
  3. #include "libslic3r/Point.hpp"
  4. #include "libslic3r/Config.hpp"
  5. #include "libslic3r/Model.hpp"
  6. #include "libslic3r/libslic3r.h"
  7. #include <algorithm>
  8. #include <future>
  9. #include <chrono>
  10. //#include "test_options.hpp"
  11. #include "test_data.hpp"
  12. using namespace Slic3r;
  13. using namespace std;
  14. SCENARIO( "TriangleMesh: Basic mesh statistics") {
  15. GIVEN( "A 20mm cube, built from constexpr std::array" ) {
  16. std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
  17. std::vector<Vec3i32> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
  18. TriangleMesh cube(vertices, facets);
  19. cube.repair();
  20. THEN( "Volume is appropriate for 20mm square cube.") {
  21. REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
  22. }
  23. THEN( "Vertices array matches input.") {
  24. for (size_t i = 0U; i < cube.its.vertices.size(); i++) {
  25. REQUIRE(cube.its.vertices.at(i) == vertices.at(i).cast<float>());
  26. }
  27. for (size_t i = 0U; i < vertices.size(); i++) {
  28. REQUIRE(vertices.at(i).cast<float>() == cube.its.vertices.at(i));
  29. }
  30. }
  31. THEN( "Vertex count matches vertex array size.") {
  32. REQUIRE(cube.facets_count() == facets.size());
  33. }
  34. THEN( "Facet array matches input.") {
  35. for (size_t i = 0U; i < cube.its.indices.size(); i++) {
  36. REQUIRE(cube.its.indices.at(i) == facets.at(i));
  37. }
  38. for (size_t i = 0U; i < facets.size(); i++) {
  39. REQUIRE(facets.at(i) == cube.its.indices.at(i));
  40. }
  41. }
  42. THEN( "Facet count matches facet array size.") {
  43. REQUIRE(cube.facets_count() == facets.size());
  44. }
  45. #if 0
  46. THEN( "Number of normals is equal to the number of facets.") {
  47. REQUIRE(cube.normals().size() == facets.size());
  48. }
  49. #endif
  50. THEN( "center() returns the center of the object.") {
  51. REQUIRE(cube.center() == Vec3d(10.0,10.0,10.0));
  52. }
  53. THEN( "Size of cube is (20,20,20)") {
  54. REQUIRE(cube.size() == Vec3d(20,20,20));
  55. }
  56. }
  57. GIVEN( "A 20mm cube with one corner on the origin") {
  58. const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
  59. const std::vector<Vec3i32> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
  60. TriangleMesh cube(vertices, facets);
  61. cube.repair();
  62. THEN( "Volume is appropriate for 20mm square cube.") {
  63. REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
  64. }
  65. THEN( "Vertices array matches input.") {
  66. for (size_t i = 0U; i < cube.its.vertices.size(); i++) {
  67. REQUIRE(cube.its.vertices.at(i) == vertices.at(i).cast<float>());
  68. }
  69. for (size_t i = 0U; i < vertices.size(); i++) {
  70. REQUIRE(vertices.at(i).cast<float>() == cube.its.vertices.at(i));
  71. }
  72. }
  73. THEN( "Vertex count matches vertex array size.") {
  74. REQUIRE(cube.facets_count() == facets.size());
  75. }
  76. THEN( "Facet array matches input.") {
  77. for (size_t i = 0U; i < cube.its.indices.size(); i++) {
  78. REQUIRE(cube.its.indices.at(i) == facets.at(i));
  79. }
  80. for (size_t i = 0U; i < facets.size(); i++) {
  81. REQUIRE(facets.at(i) == cube.its.indices.at(i));
  82. }
  83. }
  84. THEN( "Facet count matches facet array size.") {
  85. REQUIRE(cube.facets_count() == facets.size());
  86. }
  87. #if 0
  88. THEN( "Number of normals is equal to the number of facets.") {
  89. REQUIRE(cube.normals().size() == facets.size());
  90. }
  91. #endif
  92. THEN( "center() returns the center of the object.") {
  93. REQUIRE(cube.center() == Vec3d(10.0,10.0,10.0));
  94. }
  95. THEN( "Size of cube is (20,20,20)") {
  96. REQUIRE(cube.size() == Vec3d(20,20,20));
  97. }
  98. }
  99. }
  100. SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") {
  101. GIVEN( "A 20mm cube with one corner on the origin") {
  102. const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
  103. const std::vector<Vec3i32> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
  104. TriangleMesh cube(vertices, facets);
  105. cube.repair();
  106. WHEN( "The cube is scaled 200% uniformly") {
  107. cube.scale(2.0);
  108. THEN( "The volume is equivalent to 40x40x40 (all dimensions increased by 200%") {
  109. REQUIRE(abs(cube.volume() - 40.0*40.0*40.0) < 1e-2);
  110. }
  111. }
  112. WHEN( "The resulting cube is scaled 200% in the X direction") {
  113. cube.scale(Vec3d(2.0, 1, 1));
  114. THEN( "The volume is doubled.") {
  115. REQUIRE(abs(cube.volume() - 2*20.0*20.0*20.0) < 1e-2);
  116. }
  117. THEN( "The X coordinate size is 200%.") {
  118. REQUIRE(cube.its.vertices.at(0).x() == 40.0);
  119. }
  120. }
  121. WHEN( "The cube is scaled 25% in the X direction") {
  122. cube.scale(Vec3d(0.25, 1, 1));
  123. THEN( "The volume is 25% of the previous volume.") {
  124. REQUIRE(abs(cube.volume() - 0.25*20.0*20.0*20.0) < 1e-2);
  125. }
  126. THEN( "The X coordinate size is 25% from previous.") {
  127. REQUIRE(cube.its.vertices.at(0).x() == 5.0);
  128. }
  129. }
  130. WHEN( "The cube is rotated 45 degrees.") {
  131. cube.rotate_z(float(M_PI / 4.));
  132. THEN( "The X component of the size is sqrt(2)*20") {
  133. REQUIRE(abs(cube.size().x() - sqrt(2.0)*20) < 1e-2);
  134. }
  135. }
  136. WHEN( "The cube is translated (5, 10, 0) units with a Vec3f ") {
  137. cube.translate(Vec3f(5.0, 10.0, 0.0));
  138. THEN( "The first vertex is located at 25, 30, 0") {
  139. REQUIRE(cube.its.vertices.at(0) == Vec3f(25.0, 30.0, 0.0));
  140. }
  141. }
  142. WHEN( "The cube is translated (5, 10, 0) units with 3 doubles") {
  143. cube.translate(5.0, 10.0, 0.0);
  144. THEN( "The first vertex is located at 25, 30, 0") {
  145. REQUIRE(cube.its.vertices.at(0) == Vec3f(25.0, 30.0, 0.0));
  146. }
  147. }
  148. WHEN( "The cube is translated (5, 10, 0) units and then aligned to origin") {
  149. cube.translate(5.0, 10.0, 0.0);
  150. cube.align_to_origin();
  151. THEN( "The third vertex is located at 0,0,0") {
  152. REQUIRE(cube.its.vertices.at(2) == Vec3f(0.0, 0.0, 0.0));
  153. }
  154. }
  155. }
  156. }
  157. SCENARIO( "TriangleMesh: slice behavior.") {
  158. GIVEN( "A 20mm cube with one corner on the origin") {
  159. const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
  160. const std::vector<Vec3i32> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
  161. TriangleMesh cube(vertices, facets);
  162. cube.repair();
  163. WHEN("Cube is sliced with z = [0+EPSILON,2,4,8,6,8,10,12,14,16,18,20]") {
  164. std::vector<double> z { 0+EPSILON,2,4,8,6,8,10,12,14,16,18,20 };
  165. std::vector<ExPolygons> result = cube.slice(z);
  166. THEN( "The correct number of polygons are returned per layer.") {
  167. for (size_t i = 0U; i < z.size(); i++) {
  168. REQUIRE(result.at(i).size() == 1);
  169. }
  170. }
  171. THEN( "The area of the returned polygons is correct.") {
  172. for (size_t i = 0U; i < z.size(); i++) {
  173. REQUIRE(result.at(i).at(0).area() == 20.0*20/(std::pow(SCALING_FACTOR,2)));
  174. }
  175. }
  176. }
  177. }
  178. GIVEN( "A STL with an irregular shape.") {
  179. const std::vector<Vec3d> vertices {{0,0,0},{0,0,20},{0,5,0},{0,5,20},{50,0,0},{50,0,20},{15,5,0},{35,5,0},{15,20,0},{50,5,0},{35,20,0},{15,5,10},{50,5,20},{35,5,10},{35,20,10},{15,20,10}};
  180. const std::vector<Vec3i32> facets {{0,1,2},{2,1,3},{1,0,4},{5,1,4},{0,2,4},{4,2,6},{7,6,8},{4,6,7},{9,4,7},{7,8,10},{2,3,6},{11,3,12},{7,12,9},{13,12,7},{6,3,11},{11,12,13},{3,1,5},{12,3,5},{5,4,9},{12,5,9},{13,7,10},{14,13,10},{8,15,10},{10,15,14},{6,11,8},{8,11,15},{15,11,13},{14,15,13}};
  181. TriangleMesh cube(vertices, facets);
  182. cube.repair();
  183. WHEN(" a top tangent plane is sliced") {
  184. std::vector<ExPolygons> slices = cube.slice({5.0, 10.0});
  185. THEN( "its area is included") {
  186. REQUIRE(slices.at(0).at(0).area() > 0);
  187. REQUIRE(slices.at(1).at(0).area() > 0);
  188. }
  189. }
  190. WHEN(" a model that has been transformed is sliced") {
  191. cube.mirror_z();
  192. std::vector<ExPolygons> slices = cube.slice({-5.0, -10.0});
  193. THEN( "it is sliced properly (mirrored bottom plane area is included)") {
  194. REQUIRE(slices.at(0).at(0).area() > 0);
  195. REQUIRE(slices.at(1).at(0).area() > 0);
  196. }
  197. }
  198. }
  199. }
  200. SCENARIO( "make_xxx functions produce meshes.") {
  201. GIVEN("make_cube() function") {
  202. WHEN("make_cube() is called with arguments 20,20,20") {
  203. TriangleMesh cube = make_cube(20,20,20);
  204. THEN("The resulting mesh has one and only one vertex at 0,0,0") {
  205. const std::vector<Vec3f> &verts = cube.its.vertices;
  206. REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return t.x() == 0 && t.y() == 0 && t.z() == 0; } ) == 1);
  207. }
  208. THEN("The mesh volume is 20*20*20") {
  209. REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2);
  210. }
  211. THEN("The resulting mesh is in the repaired state.") {
  212. REQUIRE(cube.repaired == true);
  213. }
  214. THEN("There are 12 facets.") {
  215. REQUIRE(cube.its.indices.size() == 12);
  216. }
  217. }
  218. }
  219. GIVEN("make_cylinder() function") {
  220. WHEN("make_cylinder() is called with arguments 10,10, PI / 3") {
  221. TriangleMesh cyl = make_cylinder(10, 10, PI / 243.0);
  222. double angle = (2*PI / floor(2*PI / (PI / 243.0)));
  223. THEN("The resulting mesh has one and only one vertex at 0,0,0") {
  224. const std::vector<Vec3f> &verts = cyl.its.vertices;
  225. REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return t.x() == 0 && t.y() == 0 && t.z() == 0; } ) == 1);
  226. }
  227. THEN("The resulting mesh has one and only one vertex at 0,0,10") {
  228. const std::vector<Vec3f> &verts = cyl.its.vertices;
  229. REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return t.x() == 0 && t.y() == 0 && t.z() == 10; } ) == 1);
  230. }
  231. THEN("Resulting mesh has 2 + (2*PI/angle * 2) vertices.") {
  232. REQUIRE(cyl.its.vertices.size() == (2 + ((2*PI/angle)*2)));
  233. }
  234. THEN("Resulting mesh has 2*PI/angle * 4 facets") {
  235. REQUIRE(cyl.its.indices.size() == (2*PI/angle)*4);
  236. }
  237. THEN("The resulting mesh is in the repaired state.") {
  238. REQUIRE(cyl.repaired == true);
  239. }
  240. THEN( "The mesh volume is approximately 10pi * 10^2") {
  241. REQUIRE(abs(cyl.volume() - (10.0 * M_PI * std::pow(10,2))) < 1);
  242. }
  243. }
  244. }
  245. GIVEN("make_sphere() function") {
  246. WHEN("make_sphere() is called with arguments 10, PI / 3") {
  247. TriangleMesh sph = make_sphere(10, PI / 243.0);
  248. THEN("Resulting mesh has one point at 0,0,-10 and one at 0,0,10") {
  249. const std::vector<stl_vertex> &verts = sph.its.vertices;
  250. REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, 10.f)); } ) == 1);
  251. REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, -10.f)); } ) == 1);
  252. }
  253. THEN("The resulting mesh is in the repaired state.") {
  254. REQUIRE(sph.repaired == true);
  255. }
  256. THEN( "The mesh volume is approximately 4/3 * pi * 10^3") {
  257. REQUIRE(abs(sph.volume() - (4.0/3.0 * M_PI * std::pow(10,3))) < 1); // 1% tolerance?
  258. }
  259. }
  260. }
  261. }
  262. SCENARIO( "TriangleMesh: split functionality.") {
  263. GIVEN( "A 20mm cube with one corner on the origin") {
  264. const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
  265. const std::vector<Vec3i32> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
  266. TriangleMesh cube(vertices, facets);
  267. cube.repair();
  268. WHEN( "The mesh is split into its component parts.") {
  269. std::vector<TriangleMesh*> meshes = cube.split();
  270. THEN(" The bounding box statistics are propagated to the split copies") {
  271. REQUIRE(meshes.size() == 1);
  272. REQUIRE((meshes.at(0)->bounding_box() == cube.bounding_box()));
  273. }
  274. }
  275. }
  276. GIVEN( "Two 20mm cubes, each with one corner on the origin, merged into a single TriangleMesh") {
  277. const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
  278. const std::vector<Vec3i32> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
  279. TriangleMesh cube(vertices, facets);
  280. cube.repair();
  281. TriangleMesh cube2(vertices, facets);
  282. cube2.repair();
  283. cube.merge(cube2);
  284. cube.repair();
  285. WHEN( "The combined mesh is split") {
  286. std::vector<TriangleMesh*> meshes = cube.split();
  287. THEN( "Two meshes are in the output vector.") {
  288. REQUIRE(meshes.size() == 2);
  289. }
  290. }
  291. }
  292. }
  293. SCENARIO( "TriangleMesh: Mesh merge functions") {
  294. GIVEN( "Two 20mm cubes, each with one corner on the origin") {
  295. const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
  296. const std::vector<Vec3i32> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
  297. TriangleMesh cube(vertices, facets);
  298. cube.repair();
  299. TriangleMesh cube2(vertices, facets);
  300. cube2.repair();
  301. WHEN( "The two meshes are merged") {
  302. cube.merge(cube2);
  303. cube.repair();
  304. THEN( "There are twice as many facets in the merged mesh as the original.") {
  305. REQUIRE(cube.stl.stats.number_of_facets == 2 * cube2.stl.stats.number_of_facets);
  306. }
  307. }
  308. }
  309. }
  310. SCENARIO( "TriangleMeshSlicer: Cut behavior.") {
  311. GIVEN( "A 20mm cube with one corner on the origin") {
  312. const std::vector<Vec3d> vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} };
  313. const std::vector<Vec3i32> facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} };
  314. TriangleMesh cube(vertices, facets);
  315. cube.repair();
  316. WHEN( "Object is cut at the bottom") {
  317. TriangleMesh upper {};
  318. TriangleMesh lower {};
  319. TriangleMeshSlicer slicer(&cube);
  320. slicer.cut(0, &upper, &lower);
  321. THEN("Upper mesh has all facets except those belonging to the slicing plane.") {
  322. REQUIRE(upper.facets_count() == 12);
  323. }
  324. THEN("Lower mesh has no facets.") {
  325. REQUIRE(lower.facets_count() == 0);
  326. }
  327. }
  328. WHEN( "Object is cut at the center") {
  329. TriangleMesh upper {};
  330. TriangleMesh lower {};
  331. TriangleMeshSlicer slicer(&cube);
  332. slicer.cut(10, &upper, &lower);
  333. THEN("Upper mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") {
  334. REQUIRE(upper.facets_count() == 2+12+6);
  335. }
  336. THEN("Lower mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") {
  337. REQUIRE(lower.facets_count() == 2+12+6);
  338. }
  339. }
  340. }
  341. }
  342. #ifdef TEST_PERFORMANCE
  343. TEST_CASE("Regression test for issue #4486 - files take forever to slice") {
  344. TriangleMesh mesh;
  345. DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
  346. mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/100_000.stl");
  347. mesh.repair();
  348. config.set("layer_height", 500);
  349. config.set("first_layer_height", 250);
  350. config.set("nozzle_diameter", 500);
  351. Slic3r::Print print;
  352. Slic3r::Model model;
  353. Slic3r::Test::init_print({mesh}, print, model, config);
  354. print.status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
  355. std::future<void> fut = std::async([&print] () { print.process(); });
  356. std::chrono::milliseconds span {120000};
  357. bool timedout {false};
  358. if(fut.wait_for(span) == std::future_status::timeout) {
  359. timedout = true;
  360. }
  361. REQUIRE(timedout == false);
  362. }
  363. #endif // TEST_PERFORMANCE
  364. #ifdef BUILD_PROFILE
  365. TEST_CASE("Profile test for issue #4486 - files take forever to slice") {
  366. TriangleMesh mesh;
  367. DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
  368. mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/10_000.stl");
  369. mesh.repair();
  370. config.set("layer_height", 500);
  371. config.set("first_layer_height", 250);
  372. config.set("nozzle_diameter", 500);
  373. config.set("fill_density", "5%");
  374. Slic3r::Print print;
  375. Slic3r::Model model;
  376. Slic3r::Test::init_print({mesh}, print, model, config);
  377. print.status_cb = [] (int ln, const std::string& msg) { Slic3r::Log::info("Print") << ln << " " << msg << "\n";};
  378. print.process();
  379. REQUIRE(true);
  380. }
  381. #endif //BUILD_PROFILE