From 0fc288b6d7804443a252b59ad361e1be230e3c91 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Tue, 10 Jul 2018 22:35:49 -0500 Subject: [PATCH] Pushed ::slice and ::stats() and ::bb3() into TriangleMesh. Added more tests to TriangleMesh. --- src/test/libslic3r/test_trianglemesh.cpp | 164 +++++++++++++++++++++-- xs/src/libslic3r/TriangleMesh.cpp | 35 +++++ xs/src/libslic3r/TriangleMesh.hpp | 23 ++++ 3 files changed, 213 insertions(+), 9 deletions(-) diff --git a/src/test/libslic3r/test_trianglemesh.cpp b/src/test/libslic3r/test_trianglemesh.cpp index 259a73acc..e5d814ad3 100644 --- a/src/test/libslic3r/test_trianglemesh.cpp +++ b/src/test/libslic3r/test_trianglemesh.cpp @@ -1,8 +1,11 @@ #include #include "TriangleMesh.hpp" +#include "libslic3r.h" #include "Point.hpp" +#include + using namespace Slic3r; SCENARIO( "TriangleMesh: Basic mesh statistics") { @@ -119,20 +122,164 @@ SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") { } } -SCENARIO( "TriangleMesh: split functionality.", "[!mayfail]") { - REQUIRE(false); // TODO +SCENARIO( "TriangleMesh: slice behavior.") { + GIVEN( "A 20mm cube with one corner on the origin") { + const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) }; + const std::vector facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) }; + auto cube {TriangleMesh(vertices, facets)}; + cube.repair(); + + WHEN("Cube is sliced with z = [0,2,4,8,6,8,10,12,14,16,18,20]") { + std::vector z { 0,2,4,8,6,8,10,12,14,16,18,20 }; + auto result {cube.slice(z)}; + THEN( "The correct number of polygons are returned per layer.") { + for (auto i = 0U; i < z.size(); i++) { + REQUIRE(result.at(i).size() == 1); + } + } + THEN( "The area of the returned polygons is correct.") { + for (auto i = 0U; i < z.size(); i++) { + REQUIRE(result.at(i).at(0).area() == 20.0*20/(std::pow(SCALING_FACTOR,2))); + } + } + } + } + GIVEN( "A STL with an irregular shape.") { + const Pointf3s vertices {Pointf3(0,0,0),Pointf3(0,0,20),Pointf3(0,5,0),Pointf3(0,5,20),Pointf3(50,0,0),Pointf3(50,0,20),Pointf3(15,5,0),Pointf3(35,5,0),Pointf3(15,20,0),Pointf3(50,5,0),Pointf3(35,20,0),Pointf3(15,5,10),Pointf3(50,5,20),Pointf3(35,5,10),Pointf3(35,20,10),Pointf3(15,20,10)}; + const Point3s facets {Point3(0,1,2),Point3(2,1,3),Point3(1,0,4),Point3(5,1,4),Point3(0,2,4),Point3(4,2,6),Point3(7,6,8),Point3(4,6,7),Point3(9,4,7),Point3(7,8,10),Point3(2,3,6),Point3(11,3,12),Point3(7,12,9),Point3(13,12,7),Point3(6,3,11),Point3(11,12,13),Point3(3,1,5),Point3(12,3,5),Point3(5,4,9),Point3(12,5,9),Point3(13,7,10),Point3(14,13,10),Point3(8,15,10),Point3(10,15,14),Point3(6,11,8),Point3(8,11,15),Point3(15,11,13),Point3(14,15,13)}; + + auto cube {TriangleMesh(vertices, facets)}; + cube.repair(); + WHEN(" a top tangent plane is sliced") { + auto slices {cube.slice({5.0, 10.0})}; + THEN( "its area is included") { + REQUIRE(slices.at(0).at(0).area() > 0); + REQUIRE(slices.at(1).at(0).area() > 0); + } + } + WHEN(" a model that has been transformed is sliced") { + cube.mirror_z(); + auto slices {cube.slice({-5.0, -10.0})}; + THEN( "it is sliced properly (mirrored bottom plane area is included)") { + REQUIRE(slices.at(0).at(0).area() > 0); + REQUIRE(slices.at(1).at(0).area() > 0); + } + } + } } -SCENARIO( "TriangleMesh: slice behavior.", "[!mayfail]") { - REQUIRE(false); // TODO +SCENARIO( "make_xxx functions produce meshes.") { + GIVEN("make_cube() function") { + WHEN("make_cube() is called with arguments 20,20,20") { + auto cube {TriangleMesh::make_cube(20,20,20)}; + THEN("The resulting mesh has one and only one vertex at 0,0,0") { + auto verts {cube.vertices()}; + REQUIRE(std::count_if(verts.begin(), verts.end(), [](Pointf3& t) { return t.x == 0 && t.y == 0 && t.z == 0; } ) == 1); + } + THEN("The mesh volume is 20*20*20") { + REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2); + } + THEN("The resulting mesh is in the repaired state.") { + REQUIRE(cube.repaired == true); + } + THEN("There are 12 facets.") { + REQUIRE(cube.facets().size() == 12); + } + } + } + GIVEN("make_cylinder() function") { + WHEN("make_cylinder() is called with arguments 10,10, PI / 3") { + auto cyl {TriangleMesh::make_cylinder(10, 10, PI / 3.0)}; + double angle = (2*PI / floor(2*PI / (PI / 3.0))); + THEN("The resulting mesh has one and only one vertex at 0,0,0") { + auto verts {cyl.vertices()}; + REQUIRE(std::count_if(verts.begin(), verts.end(), [](Pointf3& t) { return t.x == 0 && t.y == 0 && t.z == 0; } ) == 1); + } + THEN("The resulting mesh has one and only one vertex at 0,0,10") { + auto verts {cyl.vertices()}; + REQUIRE(std::count_if(verts.begin(), verts.end(), [](Pointf3& t) { return t.x == 0 && t.y == 0 && t.z == 10; } ) == 1); + } + THEN("Resulting mesh has 2 + (2*PI/angle * 2) vertices.") { + REQUIRE(cyl.vertices().size() == (2 + ((2*PI/angle)*2))); + } + THEN("Resulting mesh has 2*PI/angle * 4 facets") { + REQUIRE(cyl.facets().size() == (2*PI/angle)*4); + } + THEN("The resulting mesh is in the repaired state.") { + REQUIRE(cyl.repaired == true); + } + } + } + + GIVEN("make_sphere() function") { + WHEN("make_sphere() is called with arguments 10, PI / 3") { + auto sph {TriangleMesh::make_sphere(10, PI / 3.0)}; + double angle = (2.0*PI / floor(2.0*PI / (PI / 3.0))); + THEN("Resulting mesh has one point at 0,0,-10 and one at 0,0,10") { + auto verts {sph.vertices()}; + REQUIRE(std::count_if(verts.begin(), verts.end(), [](Pointf3& t) { return t.x == 0 && t.y == 0 && t.z == 10; } ) == 1); + REQUIRE(std::count_if(verts.begin(), verts.end(), [](Pointf3& t) { return t.x == 0 && t.y == 0 && t.z == -10; } ) == 1); + } + THEN("The resulting mesh is in the repaired state.") { + REQUIRE(sph.repaired == true); + } + } + } } -SCENARIO( "make_xxx functions produce meshes.", "[!mayfail]") { - REQUIRE(false); // TODO +SCENARIO( "TriangleMesh: split functionality.") { + GIVEN( "A 20mm cube with one corner on the origin") { + const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) }; + const Point3s facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) }; + + auto cube {TriangleMesh(vertices, facets)}; + cube.repair(); + WHEN( "The mesh is split into its component parts.") { + auto meshes {cube.split()}; + THEN(" The bounding box statistics are propagated to the split copies") { + REQUIRE(meshes.size() == 1); + REQUIRE(meshes.at(0)->bb3() == cube.bb3()); + } + } + } + GIVEN( "Two 20mm cubes, each with one corner on the origin, merged into a single TriangleMesh") { + const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) }; + const Point3s facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) }; + + auto cube {TriangleMesh(vertices, facets)}; + cube.repair(); + auto cube2 {TriangleMesh(vertices, facets)}; + cube2.repair(); + + cube.merge(cube2); + cube.repair(); + WHEN( "The combined mesh is split") { + auto meshes {cube.split()}; + THEN( "Two meshes are in the output vector.") { + REQUIRE(meshes.size() == 2); + } + } + } } -SCENARIO( "TriangleMesh: Mesh merge functions", "[!mayfail]") { - REQUIRE(false); // TODO +SCENARIO( "TriangleMesh: Mesh merge functions") { + GIVEN( "Two 20mm cubes, each with one corner on the origin") { + const Pointf3s vertices { Pointf3(20,20,0), Pointf3(20,0,0), Pointf3(0,0,0), Pointf3(0,20,0), Pointf3(20,20,20), Pointf3(0,20,20), Pointf3(0,0,20), Pointf3(20,0,20) }; + const Point3s facets { Point3(0,1,2), Point3(0,2,3), Point3(4,5,6), Point3(4,6,7), Point3(0,4,7), Point3(0,7,1), Point3(1,7,6), Point3(1,6,2), Point3(2,6,5), Point3(2,5,3), Point3(4,0,3), Point3(4,3,5) }; + + auto cube {TriangleMesh(vertices, facets)}; + cube.repair(); + auto cube2 {TriangleMesh(vertices, facets)}; + cube2.repair(); + + WHEN( "The two meshes are merged") { + cube.merge(cube2); + cube.repair(); + THEN( "There are twice as many facets in the merged mesh as the original.") { + REQUIRE(cube.stats().number_of_facets == 2 * cube2.stats().number_of_facets); + } + } + } } SCENARIO( "TriangleMeshSlicer: Cut behavior.") { @@ -165,5 +312,4 @@ SCENARIO( "TriangleMeshSlicer: Cut behavior.") { } } } - } diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index a337eeb72..383ff7602 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -432,6 +432,41 @@ TriangleMesh::center() const { return this->bounding_box().center(); } +std::vector +TriangleMesh::slice(const std::vector& z) +{ + // convert doubles to floats + std::vector z_f(z.begin(), z.end()); + TriangleMeshSlicer mslicer(this); + std::vector layers; + + mslicer.slice(z_f, &layers); + + return std::move(layers); +} + +mesh_stats +TriangleMesh::stats() const { + mesh_stats tmp_stats; + tmp_stats.number_of_facets = this->stl.stats.number_of_facets; + tmp_stats.number_of_parts = this->stl.stats.number_of_parts; + tmp_stats.volume = this->stl.stats.volume; + tmp_stats.degenerate_facets = this->stl.stats.degenerate_facets; + tmp_stats.edges_fixed = this->stl.stats.edges_fixed; + tmp_stats.facets_removed = this->stl.stats.facets_removed; + tmp_stats.facets_added = this->stl.stats.facets_added; + tmp_stats.facets_reversed = this->stl.stats.facets_reversed; + tmp_stats.backwards_edges = this->stl.stats.backwards_edges; + tmp_stats.normals_fixed = this->stl.stats.normals_fixed; + return std::move(tmp_stats); +} + +BoundingBoxf3 TriangleMesh::bb3() const { + Pointf3 min(this->stl.stats.min.x, this->stl.stats.min.y, this->stl.stats.min.z); + Pointf3 max(this->stl.stats.max.x, this->stl.stats.max.y, this->stl.stats.max.z); + return std::move(BoundingBoxf3(min, max)); +} + TriangleMeshPtrs TriangleMesh::split() const { diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index dd937fba5..0d74fe112 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -17,6 +17,21 @@ class TriangleMesh; template class TriangleMeshSlicer; typedef std::vector TriangleMeshPtrs; + +/// Interface to available statistics from the underlying mesh. +struct mesh_stats { + size_t number_of_facets {0}; + size_t number_of_parts {0}; + double volume {0}; + size_t degenerate_facets {0}; + size_t edges_fixed {0}; + size_t facets_removed {0}; + size_t facets_added {0}; + size_t facets_reversed {0}; + size_t backwards_edges {0}; + size_t normals_fixed {0}; +}; + class TriangleMesh { public: @@ -87,6 +102,14 @@ class TriangleMesh /// Return the center of the related bounding box. Pointf3 center() const; + /// Slice this mesh at the provided Z levels and return the vector + std::vector slice(const std::vector& z); + + /// Contains general statistics from underlying mesh structure. + mesh_stats stats() const; + + BoundingBoxf3 bb3() const; + /// Perform a cut of the mesh and put the output in upper and lower void cut(Axis axis, double z, TriangleMesh* upper, TriangleMesh* lower);