mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Merge branch 'tm_remove_inside_triangles'
This commit is contained in:
		
						commit
						6d11c50ba5
					
				
					 18 changed files with 881 additions and 358 deletions
				
			
		|  | @ -21,22 +21,30 @@ public: | ||||||
|         min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} |         min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} | ||||||
|     BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : |     BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : | ||||||
|         min(p1), max(p1), defined(false) { merge(p2); merge(p3); } |         min(p1), max(p1), defined(false) { merge(p2); merge(p3); } | ||||||
|     BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero()) | 
 | ||||||
|  |     template<class It, class = IteratorOnly<It> > | ||||||
|  |     BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero()) | ||||||
|     { |     { | ||||||
|         if (points.empty()) { |         if (from == to) { | ||||||
|             this->defined = false; |             this->defined = false; | ||||||
|             // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
 |             // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
 | ||||||
|         } else { |         } else { | ||||||
|             typename std::vector<PointClass>::const_iterator it = points.begin(); |             auto it = from; | ||||||
|             this->min = *it; |             this->min = it->template cast<typename PointClass::Scalar>(); | ||||||
|             this->max = *it; |             this->max = this->min; | ||||||
|             for (++ it; it != points.end(); ++ it) { |             for (++ it; it != to; ++ it) { | ||||||
|                 this->min = this->min.cwiseMin(*it); |                 auto vec = it->template cast<typename PointClass::Scalar>(); | ||||||
|                 this->max = this->max.cwiseMax(*it); |                 this->min = this->min.cwiseMin(vec); | ||||||
|  |                 this->max = this->max.cwiseMax(vec); | ||||||
|             } |             } | ||||||
|             this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); |             this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     BoundingBoxBase(const std::vector<PointClass> &points) | ||||||
|  |         : BoundingBoxBase(points.begin(), points.end()) | ||||||
|  |     {} | ||||||
|  | 
 | ||||||
|     void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); } |     void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); } | ||||||
|     void merge(const PointClass &point); |     void merge(const PointClass &point); | ||||||
|     void merge(const std::vector<PointClass> &points); |     void merge(const std::vector<PointClass> &points); | ||||||
|  | @ -74,19 +82,27 @@ public: | ||||||
|         { if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; } |         { if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; } | ||||||
|     BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : |     BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : | ||||||
|         BoundingBoxBase<PointClass>(p1, p1) { merge(p2); merge(p3); } |         BoundingBoxBase<PointClass>(p1, p1) { merge(p2); merge(p3); } | ||||||
|     BoundingBox3Base(const std::vector<PointClass>& points) | 
 | ||||||
|  |     template<class It, class = IteratorOnly<It> > BoundingBox3Base(It from, It to) | ||||||
|     { |     { | ||||||
|         if (points.empty()) |         if (from == to) | ||||||
|             throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor"); |             throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor"); | ||||||
|         typename std::vector<PointClass>::const_iterator it = points.begin(); | 
 | ||||||
|         this->min = *it; |         auto it = from; | ||||||
|         this->max = *it; |         this->min = it->template cast<typename PointClass::Scalar>(); | ||||||
|         for (++ it; it != points.end(); ++ it) { |         this->max = this->min; | ||||||
|             this->min = this->min.cwiseMin(*it); |         for (++ it; it != to; ++ it) { | ||||||
|             this->max = this->max.cwiseMax(*it); |             auto vec = it->template cast<typename PointClass::Scalar>(); | ||||||
|  |             this->min = this->min.cwiseMin(vec); | ||||||
|  |             this->max = this->max.cwiseMax(vec); | ||||||
|         } |         } | ||||||
|         this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2)); |         this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2)); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     BoundingBox3Base(const std::vector<PointClass> &points) | ||||||
|  |         : BoundingBox3Base(points.begin(), points.end()) | ||||||
|  |     {} | ||||||
|  | 
 | ||||||
|     void merge(const PointClass &point); |     void merge(const PointClass &point); | ||||||
|     void merge(const std::vector<PointClass> &points); |     void merge(const std::vector<PointClass> &points); | ||||||
|     void merge(const BoundingBox3Base<PointClass> &bb); |     void merge(const BoundingBox3Base<PointClass> &bb); | ||||||
|  | @ -188,9 +204,7 @@ public: | ||||||
| class BoundingBoxf3 : public BoundingBox3Base<Vec3d>  | class BoundingBoxf3 : public BoundingBox3Base<Vec3d>  | ||||||
| { | { | ||||||
| public: | public: | ||||||
|     BoundingBoxf3() : BoundingBox3Base<Vec3d>() {} |     using BoundingBox3Base::BoundingBox3Base; | ||||||
|     BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {} |  | ||||||
|     BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {} |  | ||||||
| 
 | 
 | ||||||
|     BoundingBoxf3 transformed(const Transform3d& matrix) const; |     BoundingBoxf3 transformed(const Transform3d& matrix) const; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -22,74 +22,54 @@ namespace Slic3r { | ||||||
| class TriangleMeshDataAdapter { | class TriangleMeshDataAdapter { | ||||||
| public: | public: | ||||||
|     const TriangleMesh &mesh; |     const TriangleMesh &mesh; | ||||||
|  |     float voxel_scale; | ||||||
| 
 | 
 | ||||||
|     size_t polygonCount() const { return mesh.its.indices.size(); } |     size_t polygonCount() const { return mesh.its.indices.size(); } | ||||||
|     size_t pointCount() const   { return mesh.its.vertices.size(); } |     size_t pointCount() const   { return mesh.its.vertices.size(); } | ||||||
|     size_t vertexCount(size_t) const { return 3; } |     size_t vertexCount(size_t) const { return 3; } | ||||||
| 
 | 
 | ||||||
|     // Return position pos in local grid index space for polygon n and vertex v
 |     // Return position pos in local grid index space for polygon n and vertex v
 | ||||||
|     void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const; |     // The actual mesh will appear to openvdb as scaled uniformly by voxel_size
 | ||||||
|  |     // And the voxel count per unit volume can be affected this way.
 | ||||||
|  |     void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const | ||||||
|  |     { | ||||||
|  |         auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v))); | ||||||
|  |         Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>() * voxel_scale; | ||||||
|  |         pos = {p.x(), p.y(), p.z()}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     TriangleMeshDataAdapter(const TriangleMesh &m, float voxel_sc = 1.f) | ||||||
|  |         : mesh{m}, voxel_scale{voxel_sc} {}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class Contour3DDataAdapter { |  | ||||||
| public: |  | ||||||
|     const sla::Contour3D &mesh; |  | ||||||
|      |  | ||||||
|     size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); } |  | ||||||
|     size_t pointCount() const   { return mesh.points.size(); } |  | ||||||
|     size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; } |  | ||||||
|      |  | ||||||
|     // Return position pos in local grid index space for polygon n and vertex v
 |  | ||||||
|     void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| void TriangleMeshDataAdapter::getIndexSpacePoint(size_t          n, |  | ||||||
|                                                  size_t          v, |  | ||||||
|                                                  openvdb::Vec3d &pos) const |  | ||||||
| { |  | ||||||
|     auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v))); |  | ||||||
|     Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>(); |  | ||||||
|     pos = {p.x(), p.y(), p.z()}; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void Contour3DDataAdapter::getIndexSpacePoint(size_t          n, |  | ||||||
|                                               size_t          v, |  | ||||||
|                                               openvdb::Vec3d &pos) const |  | ||||||
| { |  | ||||||
|     size_t vidx = 0; |  | ||||||
|     if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v))); |  | ||||||
|     else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v))); |  | ||||||
|      |  | ||||||
|     Slic3r::Vec3d p = mesh.points[vidx]; |  | ||||||
|     pos = {p.x(), p.y(), p.z()}; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| // TODO: Do I need to call initialize? Seems to work without it as well but the
 | // TODO: Do I need to call initialize? Seems to work without it as well but the
 | ||||||
| // docs say it should be called ones. It does a mutex lock-unlock sequence all
 | // docs say it should be called ones. It does a mutex lock-unlock sequence all
 | ||||||
| // even if was called previously.
 | // even if was called previously.
 | ||||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, | openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &            mesh, | ||||||
|                                      const openvdb::math::Transform &tr, |                                      const openvdb::math::Transform &tr, | ||||||
|                                      float               exteriorBandWidth, |                                      float voxel_scale, | ||||||
|                                      float               interiorBandWidth, |                                      float exteriorBandWidth, | ||||||
|                                      int                 flags) |                                      float interiorBandWidth, | ||||||
|  |                                      int   flags) | ||||||
| { | { | ||||||
|     openvdb::initialize(); |     openvdb::initialize(); | ||||||
| 
 | 
 | ||||||
|     TriangleMeshPtrs meshparts = mesh.split(); |     TriangleMeshPtrs meshparts_raw = mesh.split(); | ||||||
|  |     auto meshparts = reserve_vector<std::unique_ptr<TriangleMesh>>(meshparts_raw.size()); | ||||||
|  |     for (auto *p : meshparts_raw) | ||||||
|  |         meshparts.emplace_back(p); | ||||||
| 
 | 
 | ||||||
|     auto it = std::remove_if(meshparts.begin(), meshparts.end(), |     auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) { | ||||||
|     [](TriangleMesh *m){ |          m->require_shared_vertices(); | ||||||
|         m->require_shared_vertices(); |          return m->volume() < EPSILON; | ||||||
|         return !m->is_manifold() || m->volume() < EPSILON; |      }); | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     meshparts.erase(it, meshparts.end()); |     meshparts.erase(it, meshparts.end()); | ||||||
| 
 | 
 | ||||||
|     openvdb::FloatGrid::Ptr grid; |     openvdb::FloatGrid::Ptr grid; | ||||||
|     for (TriangleMesh *m : meshparts) { |     for (auto &m : meshparts) { | ||||||
|         auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>( |         auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>( | ||||||
|             TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth, |             TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth, | ||||||
|             interiorBandWidth, flags); |             interiorBandWidth, flags); | ||||||
| 
 | 
 | ||||||
|         if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); |         if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); | ||||||
|  | @ -106,19 +86,9 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, | ||||||
|             interiorBandWidth, flags); |             interiorBandWidth, flags); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return grid; |     grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale)); | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh, |     return grid; | ||||||
|                                      const openvdb::math::Transform &tr, |  | ||||||
|                                      float exteriorBandWidth, |  | ||||||
|                                      float interiorBandWidth, |  | ||||||
|                                      int flags) |  | ||||||
| { |  | ||||||
|     openvdb::initialize(); |  | ||||||
|     return openvdb::tools::meshToVolume<openvdb::FloatGrid>( |  | ||||||
|         Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth, |  | ||||||
|         flags); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<class Grid> | template<class Grid> | ||||||
|  | @ -136,12 +106,17 @@ sla::Contour3D _volumeToMesh(const Grid &grid, | ||||||
|     openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue, |     openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue, | ||||||
|                                  adaptivity, relaxDisorientedTriangles); |                                  adaptivity, relaxDisorientedTriangles); | ||||||
| 
 | 
 | ||||||
|  |     float scale = 1.; | ||||||
|  |     try { | ||||||
|  |         scale = grid.template metaValue<float>("voxel_scale"); | ||||||
|  |     }  catch (...) { } | ||||||
|  | 
 | ||||||
|     sla::Contour3D ret; |     sla::Contour3D ret; | ||||||
|     ret.points.reserve(points.size()); |     ret.points.reserve(points.size()); | ||||||
|     ret.faces3.reserve(triangles.size()); |     ret.faces3.reserve(triangles.size()); | ||||||
|     ret.faces4.reserve(quads.size()); |     ret.faces4.reserve(quads.size()); | ||||||
| 
 | 
 | ||||||
|     for (auto &v : points) ret.points.emplace_back(to_vec3d(v)); |     for (auto &v : points) ret.points.emplace_back(to_vec3d(v) / scale); | ||||||
|     for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v)); |     for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v)); | ||||||
|     for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v)); |     for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v)); | ||||||
| 
 | 
 | ||||||
|  | @ -166,9 +141,18 @@ sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid, | ||||||
|                          relaxDisorientedTriangles); |                          relaxDisorientedTriangles); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir) | openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, | ||||||
|  |                                         double                    iso, | ||||||
|  |                                         double                    er, | ||||||
|  |                                         double                    ir) | ||||||
| { | { | ||||||
|     return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir)); |     auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso), | ||||||
|  |                                                     float(er), float(ir)); | ||||||
|  | 
 | ||||||
|  |     // Copies voxel_scale metadata, if it exists.
 | ||||||
|  |     new_grid->insertMeta(*grid.deepCopyMeta()); | ||||||
|  | 
 | ||||||
|  |     return new_grid; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace Slic3r
 | } // namespace Slic3r
 | ||||||
|  |  | ||||||
|  | @ -21,14 +21,16 @@ inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast<double> | ||||||
| inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; } | inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; } | ||||||
| inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; } | inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; } | ||||||
| 
 | 
 | ||||||
|  | // Here voxel_scale defines the scaling of voxels which affects the voxel count.
 | ||||||
|  | // 1.0 value means a voxel for every unit cube. 2 means the model is scaled to
 | ||||||
|  | // be 2x larger and the voxel count is increased by the increment in the scaled
 | ||||||
|  | // volume, thus 4 times. This kind a sampling accuracy selection is not
 | ||||||
|  | // achievable through the Transform parameter. (TODO: or is it?)
 | ||||||
|  | // The resulting grid will contain the voxel_scale in its metadata under the
 | ||||||
|  | // "voxel_scale" key to be used in grid_to_mesh function.
 | ||||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &            mesh, | openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &            mesh, | ||||||
|                                      const openvdb::math::Transform &tr = {}, |                                      const openvdb::math::Transform &tr = {}, | ||||||
|                                      float exteriorBandWidth = 3.0f, |                                      float voxel_scale = 1.f, | ||||||
|                                      float interiorBandWidth = 3.0f, |  | ||||||
|                                      int   flags             = 0); |  | ||||||
| 
 |  | ||||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &          mesh, |  | ||||||
|                                      const openvdb::math::Transform &tr = {}, |  | ||||||
|                                      float exteriorBandWidth = 3.0f, |                                      float exteriorBandWidth = 3.0f, | ||||||
|                                      float interiorBandWidth = 3.0f, |                                      float interiorBandWidth = 3.0f, | ||||||
|                                      int   flags             = 0); |                                      int   flags             = 0); | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| #include <tbb/mutex.h> | #include <tbb/mutex.h> | ||||||
| #include <tbb/parallel_for.h> | #include <tbb/parallel_for.h> | ||||||
| #include <tbb/parallel_reduce.h> | #include <tbb/parallel_reduce.h> | ||||||
|  | #include <tbb/task_arena.h> | ||||||
| 
 | 
 | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <numeric> | #include <numeric> | ||||||
|  | @ -76,6 +77,11 @@ template<> struct _ccr<true> | ||||||
|             from, to, init, std::forward<MergeFn>(mergefn), |             from, to, init, std::forward<MergeFn>(mergefn), | ||||||
|             [](typename I::value_type &i) { return i; }, granularity); |             [](typename I::value_type &i) { return i; }, granularity); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     static size_t max_concurreny() | ||||||
|  |     { | ||||||
|  |         return tbb::this_task_arena::max_concurrency(); | ||||||
|  |     } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| template<> struct _ccr<false> | template<> struct _ccr<false> | ||||||
|  | @ -133,6 +139,8 @@ public: | ||||||
|         return reduce(from, to, init, std::forward<MergeFn>(mergefn), |         return reduce(from, to, init, std::forward<MergeFn>(mergefn), | ||||||
|                       [](typename I::value_type &i) { return i; }); |                       [](typename I::value_type &i) { return i; }); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     static size_t max_concurreny() { return 1; } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| using ccr = _ccr<USE_FULL_CONCURRENCY>; | using ccr = _ccr<USE_FULL_CONCURRENCY>; | ||||||
|  |  | ||||||
|  | @ -26,16 +26,47 @@ inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); } | ||||||
| template<class S, class = FloatingOnly<S>> | template<class S, class = FloatingOnly<S>> | ||||||
| inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; } | inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; } | ||||||
| 
 | 
 | ||||||
| static TriangleMesh _generate_interior(const TriangleMesh  &mesh, | struct Interior { | ||||||
|                                        const JobController &ctl, |     TriangleMesh mesh; | ||||||
|                                        double               min_thickness, |     openvdb::FloatGrid::Ptr gridptr; | ||||||
|                                        double               voxel_scale, |     mutable std::optional<openvdb::FloatGrid::ConstAccessor> accessor; | ||||||
|                                        double               closing_dist) | 
 | ||||||
|  |     double closing_distance = 0.; | ||||||
|  |     double thickness = 0.; | ||||||
|  |     double voxel_scale = 1.; | ||||||
|  |     double nb_in = 3.;  // narrow band width inwards
 | ||||||
|  |     double nb_out = 3.; // narrow band width outwards
 | ||||||
|  |     // Full narrow band is the sum of the two above values.
 | ||||||
|  | 
 | ||||||
|  |     void reset_accessor() const  // This resets the accessor and its cache
 | ||||||
|  |     // Not a thread safe call!
 | ||||||
|  |     { | ||||||
|  |         if (gridptr) | ||||||
|  |             accessor = gridptr->getConstAccessor(); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void InteriorDeleter::operator()(Interior *p) | ||||||
| { | { | ||||||
|     TriangleMesh imesh{mesh}; |     delete p; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     _scale(voxel_scale, imesh); | TriangleMesh &get_mesh(Interior &interior) | ||||||
|  | { | ||||||
|  |     return interior.mesh; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|  | const TriangleMesh &get_mesh(const Interior &interior) | ||||||
|  | { | ||||||
|  |     return interior.mesh; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh, | ||||||
|  |                                              const JobController &ctl, | ||||||
|  |                                              double min_thickness, | ||||||
|  |                                              double voxel_scale, | ||||||
|  |                                              double closing_dist) | ||||||
|  | { | ||||||
|     double offset = voxel_scale * min_thickness; |     double offset = voxel_scale * min_thickness; | ||||||
|     double D = voxel_scale * closing_dist; |     double D = voxel_scale * closing_dist; | ||||||
|     float  out_range = 0.1f * float(offset); |     float  out_range = 0.1f * float(offset); | ||||||
|  | @ -44,7 +75,7 @@ static TriangleMesh _generate_interior(const TriangleMesh  &mesh, | ||||||
|     if (ctl.stopcondition()) return {}; |     if (ctl.stopcondition()) return {}; | ||||||
|     else ctl.statuscb(0, L("Hollowing")); |     else ctl.statuscb(0, L("Hollowing")); | ||||||
| 
 | 
 | ||||||
|     auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range); |     auto gridptr = mesh_to_grid(mesh, {}, voxel_scale, out_range, in_range); | ||||||
| 
 | 
 | ||||||
|     assert(gridptr); |     assert(gridptr); | ||||||
| 
 | 
 | ||||||
|  | @ -56,30 +87,34 @@ static TriangleMesh _generate_interior(const TriangleMesh  &mesh, | ||||||
|     if (ctl.stopcondition()) return {}; |     if (ctl.stopcondition()) return {}; | ||||||
|     else ctl.statuscb(30, L("Hollowing")); |     else ctl.statuscb(30, L("Hollowing")); | ||||||
| 
 | 
 | ||||||
|     if (closing_dist > .0) { |     double iso_surface = D; | ||||||
|         gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range)); |     auto   narrowb = double(in_range); | ||||||
|     } else { |     gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); | ||||||
|         D = -offset; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if (ctl.stopcondition()) return {}; |     if (ctl.stopcondition()) return {}; | ||||||
|     else ctl.statuscb(70, L("Hollowing")); |     else ctl.statuscb(70, L("Hollowing")); | ||||||
| 
 | 
 | ||||||
|     double iso_surface = D; |  | ||||||
|     double adaptivity = 0.; |     double adaptivity = 0.; | ||||||
|     auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); |     InteriorPtr interior = InteriorPtr{new Interior{}}; | ||||||
| 
 | 
 | ||||||
|     _scale(1. / voxel_scale, omesh); |     interior->mesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); | ||||||
|  |     interior->gridptr = gridptr; | ||||||
| 
 | 
 | ||||||
|     if (ctl.stopcondition()) return {}; |     if (ctl.stopcondition()) return {}; | ||||||
|     else ctl.statuscb(100, L("Hollowing")); |     else ctl.statuscb(100, L("Hollowing")); | ||||||
| 
 | 
 | ||||||
|     return omesh; |     interior->closing_distance = D; | ||||||
|  |     interior->thickness = offset; | ||||||
|  |     interior->voxel_scale = voxel_scale; | ||||||
|  |     interior->nb_in = narrowb; | ||||||
|  |     interior->nb_out = narrowb; | ||||||
|  | 
 | ||||||
|  |     return interior; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &   mesh, | InteriorPtr generate_interior(const TriangleMesh &   mesh, | ||||||
|                                                 const HollowingConfig &hc, |                               const HollowingConfig &hc, | ||||||
|                                                 const JobController &  ctl) |                               const JobController &  ctl) | ||||||
| { | { | ||||||
|     static const double MIN_OVERSAMPL = 3.; |     static const double MIN_OVERSAMPL = 3.; | ||||||
|     static const double MAX_OVERSAMPL = 8.; |     static const double MAX_OVERSAMPL = 8.; | ||||||
|  | @ -92,15 +127,16 @@ std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &   mesh, | ||||||
|     //
 |     //
 | ||||||
|     // max 8x upscale, min is native voxel size
 |     // max 8x upscale, min is native voxel size
 | ||||||
|     auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality; |     auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality; | ||||||
|     auto meshptr = std::make_unique<TriangleMesh>( |  | ||||||
|         _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, |  | ||||||
|                            hc.closing_distance)); |  | ||||||
| 
 | 
 | ||||||
|     if (meshptr && !meshptr->empty()) { |     InteriorPtr interior = | ||||||
|  |         generate_interior_verbose(mesh, ctl, hc.min_thickness, voxel_scale, | ||||||
|  |                                   hc.closing_distance); | ||||||
|  | 
 | ||||||
|  |     if (interior && !interior->mesh.empty()) { | ||||||
| 
 | 
 | ||||||
|         // This flips the normals to be outward facing...
 |         // This flips the normals to be outward facing...
 | ||||||
|         meshptr->require_shared_vertices(); |         interior->mesh.require_shared_vertices(); | ||||||
|         indexed_triangle_set its = std::move(meshptr->its); |         indexed_triangle_set its = std::move(interior->mesh.its); | ||||||
| 
 | 
 | ||||||
|         Slic3r::simplify_mesh(its); |         Slic3r::simplify_mesh(its); | ||||||
| 
 | 
 | ||||||
|  | @ -108,10 +144,12 @@ std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &   mesh, | ||||||
|         for (stl_triangle_vertex_indices &ind : its.indices) |         for (stl_triangle_vertex_indices &ind : its.indices) | ||||||
|             std::swap(ind(0), ind(2)); |             std::swap(ind(0), ind(2)); | ||||||
| 
 | 
 | ||||||
|         *meshptr = Slic3r::TriangleMesh{its}; |         interior->mesh = Slic3r::TriangleMesh{its}; | ||||||
|  |         interior->mesh.repaired = true; | ||||||
|  |         interior->mesh.require_shared_vertices(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return meshptr; |     return interior; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Contour3D DrainHole::to_mesh() const | Contour3D DrainHole::to_mesh() const | ||||||
|  | @ -273,12 +311,264 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices, | ||||||
|         obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]); |         obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg) | void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags) | ||||||
| { | { | ||||||
|     std::unique_ptr<Slic3r::TriangleMesh> inter_ptr = |     InteriorPtr interior = generate_interior(mesh, cfg, JobController{}); | ||||||
|             Slic3r::sla::generate_interior(mesh); |     if (!interior) return; | ||||||
| 
 | 
 | ||||||
|     if (inter_ptr) mesh.merge(*inter_ptr); |     hollow_mesh(mesh, *interior, flags); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) | ||||||
|  | { | ||||||
|  |     if (mesh.empty() || interior.mesh.empty()) return; | ||||||
|  | 
 | ||||||
|  |     if (flags & hfRemoveInsideTriangles && interior.gridptr) | ||||||
|  |         remove_inside_triangles(mesh, interior); | ||||||
|  | 
 | ||||||
|  |     mesh.merge(interior.mesh); | ||||||
|  |     mesh.require_shared_vertices(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Get the distance of p to the interior's zero iso_surface. Interior should
 | ||||||
|  | // have its zero isosurface positioned at offset + closing_distance inwards form
 | ||||||
|  | // the model surface.
 | ||||||
|  | static double get_distance_raw(const Vec3f &p, const Interior &interior) | ||||||
|  | { | ||||||
|  |     assert(interior.gridptr); | ||||||
|  | 
 | ||||||
|  |     if (!interior.accessor) interior.reset_accessor(); | ||||||
|  | 
 | ||||||
|  |     auto v       = (p * interior.voxel_scale).cast<double>(); | ||||||
|  |     auto grididx = interior.gridptr->transform().worldToIndexCellCentered( | ||||||
|  |         {v.x(), v.y(), v.z()}); | ||||||
|  | 
 | ||||||
|  |     return interior.accessor->getValue(grididx) ; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | struct TriangleBubble { Vec3f center; double R; }; | ||||||
|  | 
 | ||||||
|  | // Return the distance of bubble center to the interior boundary or NaN if the
 | ||||||
|  | // triangle is too big to be measured.
 | ||||||
|  | static double get_distance(const TriangleBubble &b, const Interior &interior) | ||||||
|  | { | ||||||
|  |     double R = b.R * interior.voxel_scale; | ||||||
|  |     double D = get_distance_raw(b.center, interior); | ||||||
|  | 
 | ||||||
|  |     return (D > 0. && R >= interior.nb_out) || | ||||||
|  |            (D < 0. && R >= interior.nb_in)  || | ||||||
|  |            ((D - R) < 0. && 2 * R > interior.thickness) ? | ||||||
|  |                 std::nan("") : | ||||||
|  |                 // FIXME: Adding interior.voxel_scale is a compromise supposed
 | ||||||
|  |                 // to prevent the deletion of the triangles forming the interior
 | ||||||
|  |                 // itself. This has a side effect that a small portion of the
 | ||||||
|  |                 // bad triangles will still be visible.
 | ||||||
|  |                 D - interior.closing_distance /*+ 2 * interior.voxel_scale*/; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | double get_distance(const Vec3f &p, const Interior &interior) | ||||||
|  | { | ||||||
|  |     double d = get_distance_raw(p, interior) - interior.closing_distance; | ||||||
|  |     return d / interior.voxel_scale; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // A face that can be divided. Stores the indices into the original mesh if its
 | ||||||
|  | // part of that mesh and the vertices it consists of.
 | ||||||
|  | enum { NEW_FACE = -1}; | ||||||
|  | struct DivFace { | ||||||
|  |     Vec3i indx; | ||||||
|  |     std::array<Vec3f, 3> verts; | ||||||
|  |     long faceid = NEW_FACE; | ||||||
|  |     long parent = NEW_FACE; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // Divide a face recursively and call visitor on all the sub-faces.
 | ||||||
|  | template<class Fn> | ||||||
|  | void divide_triangle(const DivFace &face, Fn &&visitor) | ||||||
|  | { | ||||||
|  |     std::array<Vec3f, 3> edges = {(face.verts[0] - face.verts[1]), | ||||||
|  |                                   (face.verts[1] - face.verts[2]), | ||||||
|  |                                   (face.verts[2] - face.verts[0])}; | ||||||
|  | 
 | ||||||
|  |     std::array<size_t, 3> edgeidx = {0, 1, 2}; | ||||||
|  | 
 | ||||||
|  |     std::sort(edgeidx.begin(), edgeidx.end(), [&edges](size_t e1, size_t e2) { | ||||||
|  |         return edges[e1].squaredNorm() > edges[e2].squaredNorm(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     DivFace child1, child2; | ||||||
|  | 
 | ||||||
|  |     child1.parent   = face.faceid == NEW_FACE ? face.parent : face.faceid; | ||||||
|  |     child1.indx(0)  = -1; | ||||||
|  |     child1.indx(1)  = face.indx(edgeidx[1]); | ||||||
|  |     child1.indx(2)  = face.indx((edgeidx[1] + 1) % 3); | ||||||
|  |     child1.verts[0] = (face.verts[edgeidx[0]] + face.verts[(edgeidx[0] + 1) % 3]) / 2.; | ||||||
|  |     child1.verts[1] = face.verts[edgeidx[1]]; | ||||||
|  |     child1.verts[2] = face.verts[(edgeidx[1] + 1) % 3]; | ||||||
|  | 
 | ||||||
|  |     if (visitor(child1)) | ||||||
|  |         divide_triangle(child1, std::forward<Fn>(visitor)); | ||||||
|  | 
 | ||||||
|  |     child2.parent   = face.faceid == NEW_FACE ? face.parent : face.faceid; | ||||||
|  |     child2.indx(0)  = -1; | ||||||
|  |     child2.indx(1)  = face.indx(edgeidx[2]); | ||||||
|  |     child2.indx(2)  = face.indx((edgeidx[2] + 1) % 3); | ||||||
|  |     child2.verts[0] = child1.verts[0]; | ||||||
|  |     child2.verts[1] = face.verts[edgeidx[2]]; | ||||||
|  |     child2.verts[2] = face.verts[(edgeidx[2] + 1) % 3]; | ||||||
|  | 
 | ||||||
|  |     if (visitor(child2)) | ||||||
|  |         divide_triangle(child2, std::forward<Fn>(visitor)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, | ||||||
|  |                              const std::vector<bool> &exclude_mask) | ||||||
|  | { | ||||||
|  |     enum TrPos { posInside, posTouch, posOutside }; | ||||||
|  | 
 | ||||||
|  |     auto &faces       = mesh.its.indices; | ||||||
|  |     auto &vertices    = mesh.its.vertices; | ||||||
|  |     auto bb           = mesh.bounding_box(); | ||||||
|  | 
 | ||||||
|  |     bool use_exclude_mask = faces.size() == exclude_mask.size(); | ||||||
|  |     auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) { | ||||||
|  |         return use_exclude_mask && exclude_mask[face_id]; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     // TODO: Parallel mode not working yet
 | ||||||
|  |     using exec_policy = ccr_seq; | ||||||
|  | 
 | ||||||
|  |     // Info about the needed modifications on the input mesh.
 | ||||||
|  |     struct MeshMods { | ||||||
|  | 
 | ||||||
|  |         // Just a thread safe wrapper for a vector of triangles.
 | ||||||
|  |         struct { | ||||||
|  |             std::vector<std::array<Vec3f, 3>> data; | ||||||
|  |             exec_policy::SpinningMutex        mutex; | ||||||
|  | 
 | ||||||
|  |             void emplace_back(const std::array<Vec3f, 3> &pts) | ||||||
|  |             { | ||||||
|  |                 std::lock_guard lk{mutex}; | ||||||
|  |                 data.emplace_back(pts); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             size_t size() const { return data.size(); } | ||||||
|  |             const std::array<Vec3f, 3>& operator[](size_t idx) const | ||||||
|  |             { | ||||||
|  |                 return data[idx]; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } new_triangles; | ||||||
|  | 
 | ||||||
|  |         // A vector of bool for all faces signaling if it needs to be removed
 | ||||||
|  |         // or not.
 | ||||||
|  |         std::vector<bool> to_remove; | ||||||
|  | 
 | ||||||
|  |         MeshMods(const TriangleMesh &mesh): | ||||||
|  |             to_remove(mesh.its.indices.size(), false) {} | ||||||
|  | 
 | ||||||
|  |         // Number of triangles that need to be removed.
 | ||||||
|  |         size_t to_remove_cnt() const | ||||||
|  |         { | ||||||
|  |             return std::accumulate(to_remove.begin(), to_remove.end(), size_t(0)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } mesh_mods{mesh}; | ||||||
|  | 
 | ||||||
|  |     // Must return true if further division of the face is needed.
 | ||||||
|  |     auto divfn = [&interior, bb, &mesh_mods](const DivFace &f) { | ||||||
|  |         BoundingBoxf3 facebb { f.verts.begin(), f.verts.end() }; | ||||||
|  | 
 | ||||||
|  |         // Face is certainly outside the cavity
 | ||||||
|  |         if (! facebb.intersects(bb) && f.faceid != NEW_FACE) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         TriangleBubble bubble{facebb.center().cast<float>(), facebb.radius()}; | ||||||
|  | 
 | ||||||
|  |         double D = get_distance(bubble, interior); | ||||||
|  |         double R = bubble.R * interior.voxel_scale; | ||||||
|  | 
 | ||||||
|  |         if (std::isnan(D)) // The distance cannot be measured, triangle too big
 | ||||||
|  |             return true; | ||||||
|  | 
 | ||||||
|  |         // Distance of the bubble wall to the interior wall. Negative if the
 | ||||||
|  |         // bubble is overlapping with the interior
 | ||||||
|  |         double bubble_distance = D - R; | ||||||
|  | 
 | ||||||
|  |         // The face is crossing the interior or inside, it must be removed and
 | ||||||
|  |         // parts of it re-added, that are outside the interior
 | ||||||
|  |         if (bubble_distance < 0.) { | ||||||
|  |             if (f.faceid != NEW_FACE) | ||||||
|  |                 mesh_mods.to_remove[f.faceid] = true; | ||||||
|  | 
 | ||||||
|  |             if (f.parent != NEW_FACE) // Top parent needs to be removed as well
 | ||||||
|  |                 mesh_mods.to_remove[f.parent] = true; | ||||||
|  | 
 | ||||||
|  |             // If the outside part is between the interior end the exterior
 | ||||||
|  |             // (inside the wall being invisible), no further division is needed.
 | ||||||
|  |             if ((R + D) < interior.thickness) | ||||||
|  |                 return false; | ||||||
|  | 
 | ||||||
|  |             return true; | ||||||
|  |         } else if (f.faceid == NEW_FACE) { | ||||||
|  |             // New face completely outside needs to be re-added.
 | ||||||
|  |             mesh_mods.new_triangles.emplace_back(f.verts); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return false; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     interior.reset_accessor(); | ||||||
|  | 
 | ||||||
|  |     exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) { | ||||||
|  |         const Vec3i &face = faces[face_idx]; | ||||||
|  | 
 | ||||||
|  |         // If the triangle is excluded, we need to keep it.
 | ||||||
|  |         if (is_excluded(face_idx)) | ||||||
|  |             return; | ||||||
|  | 
 | ||||||
|  |         std::array<Vec3f, 3> pts = | ||||||
|  |             { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; | ||||||
|  | 
 | ||||||
|  |         BoundingBoxf3 facebb { pts.begin(), pts.end() }; | ||||||
|  | 
 | ||||||
|  |         // Face is certainly outside the cavity
 | ||||||
|  |         if (! facebb.intersects(bb)) return; | ||||||
|  | 
 | ||||||
|  |         DivFace df{face, pts, long(face_idx)}; | ||||||
|  | 
 | ||||||
|  |         if (divfn(df)) | ||||||
|  |             divide_triangle(df, divfn); | ||||||
|  | 
 | ||||||
|  |     }, exec_policy::max_concurreny()); | ||||||
|  | 
 | ||||||
|  |     auto new_faces = reserve_vector<Vec3i>(faces.size() + | ||||||
|  |                                            mesh_mods.new_triangles.size()); | ||||||
|  | 
 | ||||||
|  |     for (size_t face_idx = 0; face_idx < faces.size(); ++face_idx) { | ||||||
|  |         if (!mesh_mods.to_remove[face_idx]) | ||||||
|  |             new_faces.emplace_back(faces[face_idx]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for(size_t i = 0; i < mesh_mods.new_triangles.size(); ++i) { | ||||||
|  |         size_t o = vertices.size(); | ||||||
|  |         vertices.emplace_back(mesh_mods.new_triangles[i][0]); | ||||||
|  |         vertices.emplace_back(mesh_mods.new_triangles[i][1]); | ||||||
|  |         vertices.emplace_back(mesh_mods.new_triangles[i][2]); | ||||||
|  |         new_faces.emplace_back(int(o), int(o + 1), int(o + 2)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     BOOST_LOG_TRIVIAL(info) | ||||||
|  |             << "Trimming: " << mesh_mods.to_remove_cnt() << " triangles removed"; | ||||||
|  |     BOOST_LOG_TRIVIAL(info) | ||||||
|  |             << "Trimming: " << mesh_mods.new_triangles.size() << " triangles added"; | ||||||
|  | 
 | ||||||
|  |     faces.swap(new_faces); | ||||||
|  |     new_faces = {}; | ||||||
|  | 
 | ||||||
|  |     mesh = TriangleMesh{mesh.its}; | ||||||
|  |     mesh.repaired = true; | ||||||
|     mesh.require_shared_vertices(); |     mesh.require_shared_vertices(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -19,6 +19,17 @@ struct HollowingConfig | ||||||
|     bool enabled = true; |     bool enabled = true; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | enum HollowingFlags { hfRemoveInsideTriangles = 0x1 }; | ||||||
|  | 
 | ||||||
|  | // All data related to a generated mesh interior. Includes the 3D grid and mesh
 | ||||||
|  | // and various metadata. No need to manipulate from outside.
 | ||||||
|  | struct Interior; | ||||||
|  | struct InteriorDeleter { void operator()(Interior *p); }; | ||||||
|  | using  InteriorPtr = std::unique_ptr<Interior, InteriorDeleter>; | ||||||
|  | 
 | ||||||
|  | TriangleMesh &      get_mesh(Interior &interior); | ||||||
|  | const TriangleMesh &get_mesh(const Interior &interior); | ||||||
|  | 
 | ||||||
| struct DrainHole | struct DrainHole | ||||||
| { | { | ||||||
|     Vec3f pos; |     Vec3f pos; | ||||||
|  | @ -60,11 +71,26 @@ using DrainHoles = std::vector<DrainHole>; | ||||||
| 
 | 
 | ||||||
| constexpr float HoleStickOutLength = 1.f; | constexpr float HoleStickOutLength = 1.f; | ||||||
| 
 | 
 | ||||||
| std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh, | InteriorPtr generate_interior(const TriangleMesh &mesh, | ||||||
|                                                 const HollowingConfig &  = {}, |                               const HollowingConfig &  = {}, | ||||||
|                                                 const JobController &ctl = {}); |                               const JobController &ctl = {}); | ||||||
| 
 | 
 | ||||||
| void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg); | // Will do the hollowing
 | ||||||
|  | void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0); | ||||||
|  | 
 | ||||||
|  | // Hollowing prepared in "interior", merge with original mesh
 | ||||||
|  | void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0); | ||||||
|  | 
 | ||||||
|  | void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, | ||||||
|  |                              const std::vector<bool> &exclude_mask = {}); | ||||||
|  | 
 | ||||||
|  | double get_distance(const Vec3f &p, const Interior &interior); | ||||||
|  | 
 | ||||||
|  | template<class T> | ||||||
|  | FloatingOnly<T> get_distance(const Vec<3, T> &p, const Interior &interior) | ||||||
|  | { | ||||||
|  |     return get_distance(Vec3f(p.template cast<float>()), interior); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| void cut_drainholes(std::vector<ExPolygons> & obj_slices, | void cut_drainholes(std::vector<ExPolygons> & obj_slices, | ||||||
|                     const std::vector<float> &slicegrid, |                     const std::vector<float> &slicegrid, | ||||||
|  |  | ||||||
|  | @ -1120,7 +1120,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const | ||||||
|         return this->pad_mesh(); |         return this->pad_mesh(); | ||||||
|     case slaposDrillHoles: |     case slaposDrillHoles: | ||||||
|         if (m_hollowing_data) |         if (m_hollowing_data) | ||||||
|             return m_hollowing_data->hollow_mesh_with_holes; |             return get_mesh_to_print(); | ||||||
|         [[fallthrough]]; |         [[fallthrough]]; | ||||||
|     default: |     default: | ||||||
|         return TriangleMesh(); |         return TriangleMesh(); | ||||||
|  | @ -1149,8 +1149,9 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const | ||||||
| 
 | 
 | ||||||
| const TriangleMesh &SLAPrintObject::hollowed_interior_mesh() const | const TriangleMesh &SLAPrintObject::hollowed_interior_mesh() const | ||||||
| { | { | ||||||
|     if (m_hollowing_data && m_config.hollowing_enable.getBool()) |     if (m_hollowing_data && m_hollowing_data->interior && | ||||||
|         return m_hollowing_data->interior; |         m_config.hollowing_enable.getBool()) | ||||||
|  |         return sla::get_mesh(*m_hollowing_data->interior); | ||||||
|      |      | ||||||
|     return EMPTY_MESH; |     return EMPTY_MESH; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -85,6 +85,10 @@ public: | ||||||
|     // Get the mesh that is going to be printed with all the modifications
 |     // Get the mesh that is going to be printed with all the modifications
 | ||||||
|     // like hollowing and drilled holes.
 |     // like hollowing and drilled holes.
 | ||||||
|     const TriangleMesh & get_mesh_to_print() const { |     const TriangleMesh & get_mesh_to_print() const { | ||||||
|  |         return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const TriangleMesh & get_mesh_to_slice() const { | ||||||
|         return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh(); |         return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -328,8 +332,9 @@ private: | ||||||
|     { |     { | ||||||
|     public: |     public: | ||||||
| 
 | 
 | ||||||
|         TriangleMesh interior; |         sla::InteriorPtr interior; | ||||||
|         mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh
 |         mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh
 | ||||||
|  |         mutable TriangleMesh hollow_mesh_with_holes_trimmed; | ||||||
|     }; |     }; | ||||||
|      |      | ||||||
|     std::unique_ptr<HollowingData> m_hollowing_data; |     std::unique_ptr<HollowingData> m_hollowing_data; | ||||||
|  |  | ||||||
|  | @ -1,3 +1,5 @@ | ||||||
|  | #include <unordered_set> | ||||||
|  | 
 | ||||||
| #include <libslic3r/Exception.hpp> | #include <libslic3r/Exception.hpp> | ||||||
| #include <libslic3r/SLAPrintSteps.hpp> | #include <libslic3r/SLAPrintSteps.hpp> | ||||||
| #include <libslic3r/MeshBoolean.hpp> | #include <libslic3r/MeshBoolean.hpp> | ||||||
|  | @ -131,21 +133,150 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) | ||||||
|     double quality  = po.m_config.hollowing_quality.getFloat(); |     double quality  = po.m_config.hollowing_quality.getFloat(); | ||||||
|     double closing_d = po.m_config.hollowing_closing_distance.getFloat(); |     double closing_d = po.m_config.hollowing_closing_distance.getFloat(); | ||||||
|     sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; |     sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; | ||||||
|     auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg); |  | ||||||
| 
 | 
 | ||||||
|     if (meshptr->empty()) |     sla::InteriorPtr interior = generate_interior(po.transformed_mesh(), hlwcfg); | ||||||
|  | 
 | ||||||
|  |     if (!interior || sla::get_mesh(*interior).empty()) | ||||||
|         BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; |         BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; | ||||||
|     else { |     else { | ||||||
|         po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); |         po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); | ||||||
|         po.m_hollowing_data->interior = *meshptr; |         po.m_hollowing_data->interior = std::move(interior); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | struct FaceHash { | ||||||
|  | 
 | ||||||
|  |     // A hash is created for each triangle to be identifiable. The hash uses
 | ||||||
|  |     // only the triangle's geometric traits, not the index in a particular mesh.
 | ||||||
|  |     std::unordered_set<std::string> facehash; | ||||||
|  | 
 | ||||||
|  |     static std::string facekey(const Vec3i &face, | ||||||
|  |                                const std::vector<Vec3f> &vertices) | ||||||
|  |     { | ||||||
|  |         // Scale to integer to avoid floating points
 | ||||||
|  |         std::array<Vec<3, int64_t>, 3> pts = { | ||||||
|  |             scaled<int64_t>(vertices[face(0)]), | ||||||
|  |             scaled<int64_t>(vertices[face(1)]), | ||||||
|  |             scaled<int64_t>(vertices[face(2)]) | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // Get the first two sides of the triangle, do a cross product and move
 | ||||||
|  |         // that vector to the center of the triangle. This encodes all
 | ||||||
|  |         // information to identify an identical triangle at the same position.
 | ||||||
|  |         Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2]; | ||||||
|  |         Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3; | ||||||
|  | 
 | ||||||
|  |         // Return a concatenated string representation of the coordinates
 | ||||||
|  |         return std::to_string(c(0)) + std::to_string(c(1)) + std::to_string(c(2)); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     FaceHash(const indexed_triangle_set &its) | ||||||
|  |     { | ||||||
|  |         for (const Vec3i &face : its.indices) { | ||||||
|  |             std::string keystr = facekey(face, its.vertices); | ||||||
|  |             facehash.insert(keystr); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool find(const std::string &key) | ||||||
|  |     { | ||||||
|  |         auto it = facehash.find(key); | ||||||
|  |         return it != facehash.end(); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // Create exclude mask for triangle removal inside hollowed interiors.
 | ||||||
|  | // This is necessary when the interior is already part of the mesh which was
 | ||||||
|  | // drilled using CGAL mesh boolean operation. Excluded will be the triangles
 | ||||||
|  | // originally part of the interior mesh and triangles that make up the drilled
 | ||||||
|  | // hole walls.
 | ||||||
|  | static std::vector<bool> create_exclude_mask( | ||||||
|  |         const indexed_triangle_set &its, | ||||||
|  |         const sla::Interior &interior, | ||||||
|  |         const std::vector<sla::DrainHole> &holes) | ||||||
|  | { | ||||||
|  |     FaceHash interior_hash{sla::get_mesh(interior).its}; | ||||||
|  | 
 | ||||||
|  |     std::vector<bool> exclude_mask(its.indices.size(), false); | ||||||
|  | 
 | ||||||
|  |     std::vector< std::vector<size_t> > neighbor_index = | ||||||
|  |             create_neighbor_index(its); | ||||||
|  | 
 | ||||||
|  |     auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face) | ||||||
|  |     { | ||||||
|  |         for (int i = 0; i < 3; ++i) { | ||||||
|  |             const std::vector<size_t> &neighbors = neighbor_index[face(i)]; | ||||||
|  |             for (size_t fi_n : neighbors) exclude_mask[fi_n] = true; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     for (size_t fi = 0; fi < its.indices.size(); ++fi) { | ||||||
|  |         auto &face = its.indices[fi]; | ||||||
|  | 
 | ||||||
|  |         if (interior_hash.find(FaceHash::facekey(face, its.vertices))) { | ||||||
|  |             exclude_mask[fi] = true; | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (exclude_mask[fi]) { | ||||||
|  |             exclude_neighbors(face); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Lets deal with the holes. All the triangles of a hole and all the
 | ||||||
|  |         // neighbors of these triangles need to be kept. The neigbors were
 | ||||||
|  |         // created by CGAL mesh boolean operation that modified the original
 | ||||||
|  |         // interior inside the input mesh to contain the holes.
 | ||||||
|  |         Vec3d tr_center = ( | ||||||
|  |             its.vertices[face(0)] + | ||||||
|  |             its.vertices[face(1)] + | ||||||
|  |             its.vertices[face(2)] | ||||||
|  |         ).cast<double>() / 3.; | ||||||
|  | 
 | ||||||
|  |         // If the center is more than half a mm inside the interior,
 | ||||||
|  |         // it cannot possibly be part of a hole wall.
 | ||||||
|  |         if (sla::get_distance(tr_center, interior) < -0.5) | ||||||
|  |             continue; | ||||||
|  | 
 | ||||||
|  |         Vec3f U = its.vertices[face(1)] - its.vertices[face(0)]; | ||||||
|  |         Vec3f V = its.vertices[face(2)] - its.vertices[face(0)]; | ||||||
|  |         Vec3f C = U.cross(V); | ||||||
|  |         Vec3f face_normal = C.normalized(); | ||||||
|  | 
 | ||||||
|  |         for (const sla::DrainHole &dh : holes) { | ||||||
|  |             Vec3d dhpos = dh.pos.cast<double>(); | ||||||
|  |             Vec3d dhend = dhpos + dh.normal.cast<double>() * dh.height; | ||||||
|  | 
 | ||||||
|  |             Linef3 holeaxis{dhpos, dhend}; | ||||||
|  | 
 | ||||||
|  |             double D_hole_center = line_alg::distance_to(holeaxis, tr_center); | ||||||
|  |             double D_hole        = std::abs(D_hole_center - dh.radius); | ||||||
|  |             float dot            = dh.normal.dot(face_normal); | ||||||
|  | 
 | ||||||
|  |             // Empiric tolerances for center distance and normals angle.
 | ||||||
|  |             // For triangles that are part of a hole wall the angle of
 | ||||||
|  |             // triangle normal and the hole axis is around 90 degrees,
 | ||||||
|  |             // so the dot product is around zero.
 | ||||||
|  |             double D_tol = dh.radius / sla::DrainHole::steps; | ||||||
|  |             float normal_angle_tol = 1.f / sla::DrainHole::steps; | ||||||
|  | 
 | ||||||
|  |             if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) { | ||||||
|  |                 exclude_mask[fi] = true; | ||||||
|  |                 exclude_neighbors(face); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return exclude_mask; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Drill holes into the hollowed/original mesh.
 | // Drill holes into the hollowed/original mesh.
 | ||||||
| void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | ||||||
| { | { | ||||||
|     bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty(); |     bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty(); | ||||||
|     bool is_hollowed = (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()); |     bool is_hollowed = | ||||||
|  |         (po.m_hollowing_data && po.m_hollowing_data->interior && | ||||||
|  |          !sla::get_mesh(*po.m_hollowing_data->interior).empty()); | ||||||
| 
 | 
 | ||||||
|     if (! is_hollowed && ! needs_drilling) { |     if (! is_hollowed && ! needs_drilling) { | ||||||
|         // In this case we can dump any data that might have been
 |         // In this case we can dump any data that might have been
 | ||||||
|  | @ -163,12 +294,18 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | ||||||
|     // holes that are no longer on the frontend.
 |     // holes that are no longer on the frontend.
 | ||||||
|     TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; |     TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; | ||||||
|     hollowed_mesh = po.transformed_mesh(); |     hollowed_mesh = po.transformed_mesh(); | ||||||
|     if (! po.m_hollowing_data->interior.empty()) { |     if (is_hollowed) | ||||||
|         hollowed_mesh.merge(po.m_hollowing_data->interior); |         sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); | ||||||
|         hollowed_mesh.require_shared_vertices(); | 
 | ||||||
|     } |     TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; | ||||||
| 
 | 
 | ||||||
|     if (! needs_drilling) { |     if (! needs_drilling) { | ||||||
|  |         mesh_view = po.transformed_mesh(); | ||||||
|  | 
 | ||||||
|  |         if (is_hollowed) | ||||||
|  |             sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, | ||||||
|  |                              sla::hfRemoveInsideTriangles); | ||||||
|  | 
 | ||||||
|         BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; |         BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  | @ -196,6 +333,16 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | ||||||
|     try { |     try { | ||||||
|         MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); |         MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); | ||||||
|         hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); |         hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); | ||||||
|  |         mesh_view = hollowed_mesh; | ||||||
|  | 
 | ||||||
|  |         if (is_hollowed) { | ||||||
|  |             auto &interior = *po.m_hollowing_data->interior; | ||||||
|  |             std::vector<bool> exclude_mask = | ||||||
|  |                     create_exclude_mask(mesh_view.its, interior, drainholes); | ||||||
|  | 
 | ||||||
|  |             sla::remove_inside_triangles(mesh_view, interior, exclude_mask); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|     } catch (const std::runtime_error &) { |     } catch (const std::runtime_error &) { | ||||||
|         throw Slic3r::SlicingError(L( |         throw Slic3r::SlicingError(L( | ||||||
|             "Drilling holes into the mesh failed. " |             "Drilling holes into the mesh failed. " | ||||||
|  | @ -213,7 +360,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | ||||||
| // same imaginary grid (the height vector argument to TriangleMeshSlicer).
 | // same imaginary grid (the height vector argument to TriangleMeshSlicer).
 | ||||||
| void SLAPrint::Steps::slice_model(SLAPrintObject &po) | void SLAPrint::Steps::slice_model(SLAPrintObject &po) | ||||||
| { | { | ||||||
|     const TriangleMesh &mesh = po.get_mesh_to_print(); |     const TriangleMesh &mesh = po.get_mesh_to_slice(); | ||||||
| 
 | 
 | ||||||
|     // We need to prepare the slice index...
 |     // We need to prepare the slice index...
 | ||||||
| 
 | 
 | ||||||
|  | @ -260,9 +407,15 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) | ||||||
|     auto &slice_grid = po.m_model_height_levels; |     auto &slice_grid = po.m_model_height_levels; | ||||||
|     slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr); |     slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr); | ||||||
| 
 | 
 | ||||||
|     if (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()) { |     sla::Interior *interior = po.m_hollowing_data ? | ||||||
|         po.m_hollowing_data->interior.repair(true); |                                   po.m_hollowing_data->interior.get() : | ||||||
|         TriangleMeshSlicer interior_slicer(&po.m_hollowing_data->interior); |                                   nullptr; | ||||||
|  | 
 | ||||||
|  |     if (interior && ! sla::get_mesh(*interior).empty()) { | ||||||
|  |         TriangleMesh interiormesh = sla::get_mesh(*interior); | ||||||
|  |         interiormesh.repaired = false; | ||||||
|  |         interiormesh.repair(true); | ||||||
|  |         TriangleMeshSlicer interior_slicer(&interiormesh); | ||||||
|         std::vector<ExPolygons> interior_slices; |         std::vector<ExPolygons> interior_slices; | ||||||
|         interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); |         interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); | ||||||
| 
 | 
 | ||||||
|  | @ -297,7 +450,7 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) | ||||||
|     // If supports are disabled, we can skip the model scan.
 |     // If supports are disabled, we can skip the model scan.
 | ||||||
|     if(!po.m_config.supports_enable.getBool()) return; |     if(!po.m_config.supports_enable.getBool()) return; | ||||||
| 
 | 
 | ||||||
|     const TriangleMesh &mesh = po.get_mesh_to_print(); |     const TriangleMesh &mesh = po.get_mesh_to_slice(); | ||||||
| 
 | 
 | ||||||
|     if (!po.m_supportdata) |     if (!po.m_supportdata) | ||||||
|         po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); |         po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); | ||||||
|  |  | ||||||
|  | @ -2063,4 +2063,22 @@ TriangleMesh make_sphere(double radius, double fa) | ||||||
| 	return mesh; | 	return mesh; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | std::vector<std::vector<size_t> > create_neighbor_index(const indexed_triangle_set &its) | ||||||
|  | { | ||||||
|  |     if (its.vertices.empty()) return {}; | ||||||
|  | 
 | ||||||
|  |     size_t res = its.indices.size() / its.vertices.size(); | ||||||
|  |     std::vector< std::vector<size_t> > index(its.vertices.size(), | ||||||
|  |                                              reserve_vector<size_t>(res)); | ||||||
|  | 
 | ||||||
|  |     for (size_t fi = 0; fi < its.indices.size(); ++fi) { | ||||||
|  |         auto &face = its.indices[fi]; | ||||||
|  |         index[face(0)].emplace_back(fi); | ||||||
|  |         index[face(1)].emplace_back(fi); | ||||||
|  |         index[face(2)].emplace_back(fi); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return index; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -89,6 +89,12 @@ private: | ||||||
|     std::deque<uint32_t> find_unvisited_neighbors(std::vector<unsigned char> &facet_visited) const; |     std::deque<uint32_t> find_unvisited_neighbors(std::vector<unsigned char> &facet_visited) const; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | // Create an index of faces belonging to each vertex. The returned vector can
 | ||||||
|  | // be indexed with vertex indices and contains a list of face indices for each
 | ||||||
|  | // vertex.
 | ||||||
|  | std::vector< std::vector<size_t> > | ||||||
|  | create_neighbor_index(const indexed_triangle_set &its); | ||||||
|  | 
 | ||||||
| enum FacetEdgeType {  | enum FacetEdgeType {  | ||||||
|     // A general case, the cutting plane intersect a face at two different edges.
 |     // A general case, the cutting plane intersect a face at two different edges.
 | ||||||
|     feGeneral, |     feGeneral, | ||||||
|  |  | ||||||
|  | @ -200,12 +200,20 @@ void HollowedMesh::on_update() | ||||||
|         if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { |         if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { | ||||||
|             size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; |             size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; | ||||||
|             if (timestamp > m_old_hollowing_timestamp) { |             if (timestamp > m_old_hollowing_timestamp) { | ||||||
|                 const TriangleMesh& backend_mesh = print_object->get_mesh_to_print(); |                 const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); | ||||||
|                 if (! backend_mesh.empty()) { |                 if (! backend_mesh.empty()) { | ||||||
|                     m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); |                     m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); | ||||||
|                     Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse(); |                     Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse(); | ||||||
|                     m_hollowed_mesh_transformed->transform(trafo_inv); |                     m_hollowed_mesh_transformed->transform(trafo_inv); | ||||||
|                     m_old_hollowing_timestamp = timestamp; |                     m_old_hollowing_timestamp = timestamp; | ||||||
|  | 
 | ||||||
|  |                     const TriangleMesh &interior = print_object->hollowed_interior_mesh(); | ||||||
|  |                     if (!interior.empty()) { | ||||||
|  |                         m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(interior); | ||||||
|  |                         m_hollowed_interior_transformed->repaired = false; | ||||||
|  |                         m_hollowed_interior_transformed->repair(true); | ||||||
|  |                         m_hollowed_interior_transformed->transform(trafo_inv); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                     m_hollowed_mesh_transformed.reset(nullptr); |                     m_hollowed_mesh_transformed.reset(nullptr); | ||||||
|  | @ -230,6 +238,10 @@ const TriangleMesh* HollowedMesh::get_hollowed_mesh() const | ||||||
|     return m_hollowed_mesh_transformed.get(); |     return m_hollowed_mesh_transformed.get(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const TriangleMesh* HollowedMesh::get_hollowed_interior() const | ||||||
|  | { | ||||||
|  |     return m_hollowed_interior_transformed.get(); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -306,6 +318,10 @@ void ObjectClipper::on_update() | ||||||
|             m_clippers.back()->set_mesh(*mesh); |             m_clippers.back()->set_mesh(*mesh); | ||||||
|         } |         } | ||||||
|         m_old_meshes = meshes; |         m_old_meshes = meshes; | ||||||
|  | 
 | ||||||
|  |         if (has_hollowed) | ||||||
|  |             m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); | ||||||
|  | 
 | ||||||
|         m_active_inst_bb_radius = |         m_active_inst_bb_radius = | ||||||
|             mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); |             mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); | ||||||
|         //if (has_hollowed && m_clp_ratio != 0.)
 |         //if (has_hollowed && m_clp_ratio != 0.)
 | ||||||
|  |  | ||||||
|  | @ -199,6 +199,7 @@ public: | ||||||
| #endif // NDEBUG
 | #endif // NDEBUG
 | ||||||
| 
 | 
 | ||||||
|     const TriangleMesh* get_hollowed_mesh() const; |     const TriangleMesh* get_hollowed_mesh() const; | ||||||
|  |     const TriangleMesh* get_hollowed_interior() const; | ||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
|     void on_update() override; |     void on_update() override; | ||||||
|  | @ -206,6 +207,7 @@ protected: | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     std::unique_ptr<TriangleMesh> m_hollowed_mesh_transformed; |     std::unique_ptr<TriangleMesh> m_hollowed_mesh_transformed; | ||||||
|  |     std::unique_ptr<TriangleMesh> m_hollowed_interior_transformed; | ||||||
|     size_t m_old_hollowing_timestamp = 0; |     size_t m_old_hollowing_timestamp = 0; | ||||||
|     int m_print_object_idx = -1; |     int m_print_object_idx = -1; | ||||||
|     int m_print_objects_count = 0; |     int m_print_objects_count = 0; | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| #include "libslic3r/Tesselate.hpp" | #include "libslic3r/Tesselate.hpp" | ||||||
| #include "libslic3r/TriangleMesh.hpp" | #include "libslic3r/TriangleMesh.hpp" | ||||||
|  | #include "libslic3r/ClipperUtils.hpp" | ||||||
| 
 | 
 | ||||||
| #include "slic3r/GUI/Camera.hpp" | #include "slic3r/GUI/Camera.hpp" | ||||||
| 
 | 
 | ||||||
|  | @ -31,6 +32,15 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) | ||||||
|  | { | ||||||
|  |     if (m_negative_mesh != &mesh) { | ||||||
|  |         m_negative_mesh = &mesh; | ||||||
|  |         m_triangles_valid = false; | ||||||
|  |         m_triangles2d.resize(0); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| void MeshClipper::set_transformation(const Geometry::Transformation& trafo) | void MeshClipper::set_transformation(const Geometry::Transformation& trafo) | ||||||
|  | @ -74,6 +84,15 @@ void MeshClipper::recalculate_triangles() | ||||||
|     std::vector<ExPolygons> list_of_expolys; |     std::vector<ExPolygons> list_of_expolys; | ||||||
|     m_tms->set_up_direction(up.cast<float>()); |     m_tms->set_up_direction(up.cast<float>()); | ||||||
|     m_tms->slice(std::vector<float>{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){}); |     m_tms->slice(std::vector<float>{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){}); | ||||||
|  | 
 | ||||||
|  |     if (m_negative_mesh && !m_negative_mesh->empty()) { | ||||||
|  |         TriangleMeshSlicer negative_tms{m_negative_mesh}; | ||||||
|  |         negative_tms.set_up_direction(up.cast<float>()); | ||||||
|  | 
 | ||||||
|  |         std::vector<ExPolygons> neg_polys; | ||||||
|  |         negative_tms.slice(std::vector<float>{height_mesh}, SlicingMode::Regular, 0.f, &neg_polys, [](){}); | ||||||
|  |         list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front()); | ||||||
|  |     } | ||||||
|     m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); |     m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); | ||||||
| 
 | 
 | ||||||
|     // Rotate the cut into world coords:
 |     // Rotate the cut into world coords:
 | ||||||
|  |  | ||||||
|  | @ -78,6 +78,8 @@ public: | ||||||
|     // must make sure that it stays valid.
 |     // must make sure that it stays valid.
 | ||||||
|     void set_mesh(const TriangleMesh& mesh); |     void set_mesh(const TriangleMesh& mesh); | ||||||
| 
 | 
 | ||||||
|  |     void set_negative_mesh(const TriangleMesh &mesh); | ||||||
|  | 
 | ||||||
|     // Inform the MeshClipper about the transformation that transforms the mesh
 |     // Inform the MeshClipper about the transformation that transforms the mesh
 | ||||||
|     // into world coordinates.
 |     // into world coordinates.
 | ||||||
|     void set_transformation(const Geometry::Transformation& trafo); |     void set_transformation(const Geometry::Transformation& trafo); | ||||||
|  | @ -91,6 +93,7 @@ private: | ||||||
| 
 | 
 | ||||||
|     Geometry::Transformation m_trafo; |     Geometry::Transformation m_trafo; | ||||||
|     const TriangleMesh* m_mesh = nullptr; |     const TriangleMesh* m_mesh = nullptr; | ||||||
|  |     const TriangleMesh* m_negative_mesh = nullptr; | ||||||
|     ClippingPlane m_plane; |     ClippingPlane m_plane; | ||||||
|     std::vector<Vec2f> m_triangles2d; |     std::vector<Vec2f> m_triangles2d; | ||||||
|     GLIndexedVertexArray m_vertex_array; |     GLIndexedVertexArray m_vertex_array; | ||||||
|  |  | ||||||
|  | @ -5363,7 +5363,7 @@ void Plater::export_stl(bool extended, bool selection_only) | ||||||
|                         inst_mesh.merge(inst_supports_mesh); |                         inst_mesh.merge(inst_supports_mesh); | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     TriangleMesh inst_object_mesh = object->get_mesh_to_print(); |                     TriangleMesh inst_object_mesh = object->get_mesh_to_slice(); | ||||||
|                     inst_object_mesh.transform(mesh_trafo_inv); |                     inst_object_mesh.transform(mesh_trafo_inv); | ||||||
|                     inst_object_mesh.transform(inst_transform, is_left_handed); |                     inst_object_mesh.transform(inst_transform, is_left_handed); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,45 +2,21 @@ | ||||||
| #include <fstream> | #include <fstream> | ||||||
| #include <catch2/catch.hpp> | #include <catch2/catch.hpp> | ||||||
| 
 | 
 | ||||||
| #include <libslic3r/TriangleMesh.hpp> |  | ||||||
| #include "libslic3r/SLA/Hollowing.hpp" | #include "libslic3r/SLA/Hollowing.hpp" | ||||||
| #include <openvdb/tools/Filter.h> |  | ||||||
| #include "libslic3r/Format/OBJ.hpp" |  | ||||||
| 
 | 
 | ||||||
| #include <libnest2d/tools/benchmark.h> | TEST_CASE("Hollow two overlapping spheres") { | ||||||
|  |     using namespace Slic3r; | ||||||
| 
 | 
 | ||||||
| #include <libslic3r/SimplifyMesh.hpp> |     TriangleMesh sphere1 = make_sphere(10., 2 * PI / 20.), sphere2 = sphere1; | ||||||
| 
 | 
 | ||||||
| #if defined(WIN32) || defined(_WIN32) |     sphere1.translate(-5.f, 0.f, 0.f); | ||||||
| #define PATH_SEPARATOR R"(\)" |     sphere2.translate( 5.f, 0.f, 0.f); | ||||||
| #else |  | ||||||
| #define PATH_SEPARATOR R"(/)" |  | ||||||
| #endif |  | ||||||
| 
 | 
 | ||||||
| static Slic3r::TriangleMesh load_model(const std::string &obj_filename) |     sphere1.merge(sphere2); | ||||||
| { |     sphere1.require_shared_vertices(); | ||||||
|     Slic3r::TriangleMesh mesh; | 
 | ||||||
|     auto fpath = TEST_DATA_DIR PATH_SEPARATOR + obj_filename; |     sla::hollow_mesh(sphere1, sla::HollowingConfig{}, sla::HollowingFlags::hfRemoveInsideTriangles); | ||||||
|     Slic3r::load_obj(fpath.c_str(), &mesh); | 
 | ||||||
|     return mesh; |     sphere1.WriteOBJFile("twospheres.obj"); | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| TEST_CASE("Negative 3D offset should produce smaller object.", "[Hollowing]") |  | ||||||
| { |  | ||||||
|     Slic3r::TriangleMesh in_mesh = load_model("20mm_cube.obj"); |  | ||||||
|     Benchmark bench; |  | ||||||
|     bench.start(); |  | ||||||
|      |  | ||||||
|     std::unique_ptr<Slic3r::TriangleMesh> out_mesh_ptr = |  | ||||||
|         Slic3r::sla::generate_interior(in_mesh); |  | ||||||
|      |  | ||||||
|     bench.stop(); |  | ||||||
|      |  | ||||||
|     std::cout << "Elapsed processing time: " << bench.getElapsedSec() << std::endl; |  | ||||||
|      |  | ||||||
|     if (out_mesh_ptr) in_mesh.merge(*out_mesh_ptr); |  | ||||||
|     in_mesh.require_shared_vertices(); |  | ||||||
|     in_mesh.WriteOBJFile("merged_out.obj"); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -88,9 +88,9 @@ void test_supports(const std::string          &obj_filename, | ||||||
|     REQUIRE_FALSE(mesh.empty()); |     REQUIRE_FALSE(mesh.empty()); | ||||||
|      |      | ||||||
|     if (hollowingcfg.enabled) { |     if (hollowingcfg.enabled) { | ||||||
|         auto inside = sla::generate_interior(mesh, hollowingcfg); |         sla::InteriorPtr interior = sla::generate_interior(mesh, hollowingcfg); | ||||||
|         REQUIRE(inside); |         REQUIRE(interior); | ||||||
|         mesh.merge(*inside); |         mesh.merge(sla::get_mesh(*interior)); | ||||||
|         mesh.require_shared_vertices(); |         mesh.require_shared_vertices(); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 tamasmeszaros
						tamasmeszaros