mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -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> | ||||||
|  | @ -128,20 +98,25 @@ sla::Contour3D _volumeToMesh(const Grid &grid, | ||||||
|                              bool        relaxDisorientedTriangles) |                              bool        relaxDisorientedTriangles) | ||||||
| { | { | ||||||
|     openvdb::initialize(); |     openvdb::initialize(); | ||||||
|      | 
 | ||||||
|     std::vector<openvdb::Vec3s> points; |     std::vector<openvdb::Vec3s> points; | ||||||
|     std::vector<openvdb::Vec3I> triangles; |     std::vector<openvdb::Vec3I> triangles; | ||||||
|     std::vector<openvdb::Vec4I> quads; |     std::vector<openvdb::Vec4I> quads; | ||||||
|      | 
 | ||||||
|     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,13 +77,18 @@ 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> | ||||||
| { | { | ||||||
| private: | private: | ||||||
|     struct _Mtx { inline void lock() {} inline void unlock() {} }; |     struct _Mtx { inline void lock() {} inline void unlock() {} }; | ||||||
|      | 
 | ||||||
| public: | public: | ||||||
|     using SpinningMutex = _Mtx; |     using SpinningMutex = _Mtx; | ||||||
|     using BlockingMutex = _Mtx; |     using BlockingMutex = _Mtx; | ||||||
|  | @ -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,64 +26,99 @@ 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) | ||||||
|  | { | ||||||
|  |     delete p; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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) | ||||||
| { | { | ||||||
|     TriangleMesh imesh{mesh}; |  | ||||||
|      |  | ||||||
|     _scale(voxel_scale, imesh); |  | ||||||
|      |  | ||||||
|     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); | ||||||
|     float  in_range = 1.1f * float(offset + D); |     float  in_range = 1.1f * float(offset + D); | ||||||
|      | 
 | ||||||
|     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); | ||||||
|      | 
 | ||||||
|     if (!gridptr) { |     if (!gridptr) { | ||||||
|         BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL"; |         BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL"; | ||||||
|         return {}; |         return {}; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     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.; | ||||||
|          | 
 | ||||||
|     // I can't figure out how to increase the grid resolution through openvdb
 |     // I can't figure out how to increase the grid resolution through openvdb
 | ||||||
|     // API so the model will be scaled up before conversion and the result
 |     // API so the model will be scaled up before conversion and the result
 | ||||||
|     // scaled down. Voxels have a unit size. If I set voxelSize smaller, it
 |     // scaled down. Voxels have a unit size. If I set voxelSize smaller, it
 | ||||||
|  | @ -92,26 +127,29 @@ 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, |     InteriorPtr interior = | ||||||
|                            hc.closing_distance)); |         generate_interior_verbose(mesh, ctl, hc.min_thickness, voxel_scale, | ||||||
|      |                                   hc.closing_distance); | ||||||
|     if (meshptr && !meshptr->empty()) { | 
 | ||||||
|          |     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); | ||||||
|          | 
 | ||||||
|         // flip normals back...
 |         // flip normals back...
 | ||||||
|         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(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -327,9 +331,10 @@ private: | ||||||
|     class HollowingData |     class HollowingData | ||||||
|     { |     { | ||||||
|     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> | ||||||
|  | @ -84,17 +86,17 @@ SLAPrint::Steps::Steps(SLAPrint *print) | ||||||
| void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin o) | void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin o) | ||||||
| { | { | ||||||
|     if (o == soSupport && !po.m_supportdata) return; |     if (o == soSupport && !po.m_supportdata) return; | ||||||
|      | 
 | ||||||
|     auto faded_lyrs = size_t(po.m_config.faded_layers.getInt()); |     auto faded_lyrs = size_t(po.m_config.faded_layers.getInt()); | ||||||
|     double min_w = m_print->m_printer_config.elefant_foot_min_width.getFloat() / 2.; |     double min_w = m_print->m_printer_config.elefant_foot_min_width.getFloat() / 2.; | ||||||
|     double start_efc = m_print->m_printer_config.elefant_foot_compensation.getFloat(); |     double start_efc = m_print->m_printer_config.elefant_foot_compensation.getFloat(); | ||||||
|      | 
 | ||||||
|     double doffs = m_print->m_printer_config.absolute_correction.getFloat(); |     double doffs = m_print->m_printer_config.absolute_correction.getFloat(); | ||||||
|     coord_t clpr_offs = scaled(doffs); |     coord_t clpr_offs = scaled(doffs); | ||||||
|      | 
 | ||||||
|     faded_lyrs = std::min(po.m_slice_index.size(), faded_lyrs); |     faded_lyrs = std::min(po.m_slice_index.size(), faded_lyrs); | ||||||
|     size_t faded_lyrs_efc = std::max(size_t(1), faded_lyrs - 1); |     size_t faded_lyrs_efc = std::max(size_t(1), faded_lyrs - 1); | ||||||
|      | 
 | ||||||
|     auto efc = [start_efc, faded_lyrs_efc](size_t pos) { |     auto efc = [start_efc, faded_lyrs_efc](size_t pos) { | ||||||
|         return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc; |         return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc; | ||||||
|     }; |     }; | ||||||
|  | @ -102,13 +104,13 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin | ||||||
|     std::vector<ExPolygons> &slices = o == soModel ? |     std::vector<ExPolygons> &slices = o == soModel ? | ||||||
|                                           po.m_model_slices : |                                           po.m_model_slices : | ||||||
|                                           po.m_supportdata->support_slices; |                                           po.m_supportdata->support_slices; | ||||||
|      | 
 | ||||||
|     if (clpr_offs != 0) for (size_t i = 0; i < po.m_slice_index.size(); ++i) { |     if (clpr_offs != 0) for (size_t i = 0; i < po.m_slice_index.size(); ++i) { | ||||||
|         size_t idx = po.m_slice_index[i].get_slice_idx(o); |         size_t idx = po.m_slice_index[i].get_slice_idx(o); | ||||||
|         if (idx < slices.size()) |         if (idx < slices.size()) | ||||||
|             slices[idx] = offset_ex(slices[idx], float(clpr_offs)); |             slices[idx] = offset_ex(slices[idx], float(clpr_offs)); | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     if (start_efc > 0.) for (size_t i = 0; i < faded_lyrs; ++i) { |     if (start_efc > 0.) for (size_t i = 0; i < faded_lyrs; ++i) { | ||||||
|         size_t idx = po.m_slice_index[i].get_slice_idx(o); |         size_t idx = po.m_slice_index[i].get_slice_idx(o); | ||||||
|         if (idx < slices.size()) |         if (idx < slices.size()) | ||||||
|  | @ -124,28 +126,157 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) | ||||||
|         BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; |         BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; |     BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; | ||||||
| 
 | 
 | ||||||
|     double thickness = po.m_config.hollowing_min_thickness.getFloat(); |     double thickness = po.m_config.hollowing_min_thickness.getFloat(); | ||||||
|     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,19 +294,25 @@ 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; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; |     BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes."; | ||||||
|     sla::DrainHoles drainholes = po.transformed_drainhole_points(); |     sla::DrainHoles drainholes = po.transformed_drainhole_points(); | ||||||
|      | 
 | ||||||
|     std::uniform_real_distribution<float> dist(0., float(EPSILON)); |     std::uniform_real_distribution<float> dist(0., float(EPSILON)); | ||||||
|     auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}); |     auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}); | ||||||
|     for (sla::DrainHole holept : drainholes) { |     for (sla::DrainHole holept : drainholes) { | ||||||
|  | @ -187,15 +324,25 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | ||||||
|         auto cgal_m = MeshBoolean::cgal::triangle_mesh_to_cgal(m); |         auto cgal_m = MeshBoolean::cgal::triangle_mesh_to_cgal(m); | ||||||
|         MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_m); |         MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_m); | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) |     if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) | ||||||
|         throw Slic3r::SlicingError(L("Too many overlapping holes.")); |         throw Slic3r::SlicingError(L("Too many overlapping holes.")); | ||||||
|      | 
 | ||||||
|     auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); |     auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); | ||||||
|      | 
 | ||||||
|     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. " | ||||||
|  | @ -212,11 +359,11 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | ||||||
| // of it. In any case, the model and the supports have to be sliced in the
 | // of it. In any case, the model and the supports have to be sliced in the
 | ||||||
| // 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...
 | ||||||
|      | 
 | ||||||
|     double  lhd  = m_print->m_objects.front()->m_config.layer_height.getFloat(); |     double  lhd  = m_print->m_objects.front()->m_config.layer_height.getFloat(); | ||||||
|     float   lh   = float(lhd); |     float   lh   = float(lhd); | ||||||
|     coord_t lhs  = scaled(lhd); |     coord_t lhs  = scaled(lhd); | ||||||
|  | @ -226,43 +373,49 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) | ||||||
|     auto    minZf = float(minZ); |     auto    minZf = float(minZ); | ||||||
|     coord_t minZs = scaled(minZ); |     coord_t minZs = scaled(minZ); | ||||||
|     coord_t maxZs = scaled(maxZ); |     coord_t maxZs = scaled(maxZ); | ||||||
|      | 
 | ||||||
|     po.m_slice_index.clear(); |     po.m_slice_index.clear(); | ||||||
|      | 
 | ||||||
|     size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs); |     size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs); | ||||||
|     po.m_slice_index.reserve(cap); |     po.m_slice_index.reserve(cap); | ||||||
|      | 
 | ||||||
|     po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh); |     po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh); | ||||||
|      | 
 | ||||||
|     for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) |     for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) | ||||||
|         po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh); |         po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh); | ||||||
|      | 
 | ||||||
|     // Just get the first record that is from the model:
 |     // Just get the first record that is from the model:
 | ||||||
|     auto slindex_it = |     auto slindex_it = | ||||||
|         po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z))); |         po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z))); | ||||||
|      | 
 | ||||||
|     if(slindex_it == po.m_slice_index.end()) |     if(slindex_it == po.m_slice_index.end()) | ||||||
|         //TRN To be shown at the status bar on SLA slicing error.
 |         //TRN To be shown at the status bar on SLA slicing error.
 | ||||||
|         throw Slic3r::RuntimeError( |         throw Slic3r::RuntimeError( | ||||||
|             L("Slicing had to be stopped due to an internal error: " |             L("Slicing had to be stopped due to an internal error: " | ||||||
|               "Inconsistent slice index.")); |               "Inconsistent slice index.")); | ||||||
|      | 
 | ||||||
|     po.m_model_height_levels.clear(); |     po.m_model_height_levels.clear(); | ||||||
|     po.m_model_height_levels.reserve(po.m_slice_index.size()); |     po.m_model_height_levels.reserve(po.m_slice_index.size()); | ||||||
|     for(auto it = slindex_it; it != po.m_slice_index.end(); ++it) |     for(auto it = slindex_it; it != po.m_slice_index.end(); ++it) | ||||||
|         po.m_model_height_levels.emplace_back(it->slice_level()); |         po.m_model_height_levels.emplace_back(it->slice_level()); | ||||||
|      | 
 | ||||||
|     TriangleMeshSlicer slicer(&mesh); |     TriangleMeshSlicer slicer(&mesh); | ||||||
|      | 
 | ||||||
|     po.m_model_slices.clear(); |     po.m_model_slices.clear(); | ||||||
|     float closing_r  = float(po.config().slice_closing_radius.value); |     float closing_r  = float(po.config().slice_closing_radius.value); | ||||||
|     auto  thr        = [this]() { m_print->throw_if_canceled(); }; |     auto  thr        = [this]() { m_print->throw_if_canceled(); }; | ||||||
|     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); | ||||||
| 
 | 
 | ||||||
|  | @ -273,17 +426,17 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) | ||||||
|                                   diff_ex(po.m_model_slices[i], slice); |                                   diff_ex(po.m_model_slices[i], slice); | ||||||
|                            }); |                            }); | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     auto mit = slindex_it; |     auto mit = slindex_it; | ||||||
|     for (size_t id = 0; |     for (size_t id = 0; | ||||||
|          id < po.m_model_slices.size() && mit != po.m_slice_index.end(); |          id < po.m_model_slices.size() && mit != po.m_slice_index.end(); | ||||||
|          id++) { |          id++) { | ||||||
|         mit->set_model_slice_idx(po, id); ++mit; |         mit->set_model_slice_idx(po, id); ++mit; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     // We apply the printer correction offset here.
 |     // We apply the printer correction offset here.
 | ||||||
|     apply_printer_corrections(po, soModel); |     apply_printer_corrections(po, soModel); | ||||||
|          | 
 | ||||||
|     if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool()) |     if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool()) | ||||||
|     { |     { | ||||||
|         po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); |         po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); | ||||||
|  | @ -296,22 +449,22 @@ 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)); | ||||||
|      | 
 | ||||||
|     const ModelObject& mo = *po.m_model_object; |     const ModelObject& mo = *po.m_model_object; | ||||||
|      | 
 | ||||||
|     BOOST_LOG_TRIVIAL(debug) << "Support point count " |     BOOST_LOG_TRIVIAL(debug) << "Support point count " | ||||||
|                              << mo.sla_support_points.size(); |                              << mo.sla_support_points.size(); | ||||||
|      | 
 | ||||||
|     // Unless the user modified the points or we already did the calculation,
 |     // Unless the user modified the points or we already did the calculation,
 | ||||||
|     // we will do the autoplacement. Otherwise we will just blindly copy the
 |     // we will do the autoplacement. Otherwise we will just blindly copy the
 | ||||||
|     // frontend data into the backend cache.
 |     // frontend data into the backend cache.
 | ||||||
|     if (mo.sla_points_status != sla::PointsStatus::UserModified) { |     if (mo.sla_points_status != sla::PointsStatus::UserModified) { | ||||||
|          | 
 | ||||||
|         // calculate heights of slices (slices are calculated already)
 |         // calculate heights of slices (slices are calculated already)
 | ||||||
|         const std::vector<float>& heights = po.m_model_height_levels; |         const std::vector<float>& heights = po.m_model_height_levels; | ||||||
| 
 | 
 | ||||||
|  | @ -319,27 +472,27 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) | ||||||
|         // calculated on slices, the algorithm then raycasts the points
 |         // calculated on slices, the algorithm then raycasts the points
 | ||||||
|         // so they actually lie on the mesh.
 |         // so they actually lie on the mesh.
 | ||||||
| //        po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
 | //        po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
 | ||||||
|          | 
 | ||||||
|         throw_if_canceled(); |         throw_if_canceled(); | ||||||
|         sla::SupportPointGenerator::Config config; |         sla::SupportPointGenerator::Config config; | ||||||
|         const SLAPrintObjectConfig& cfg = po.config(); |         const SLAPrintObjectConfig& cfg = po.config(); | ||||||
|          | 
 | ||||||
|         // the density config value is in percents:
 |         // the density config value is in percents:
 | ||||||
|         config.density_relative = float(cfg.support_points_density_relative / 100.f); |         config.density_relative = float(cfg.support_points_density_relative / 100.f); | ||||||
|         config.minimal_distance = float(cfg.support_points_minimal_distance); |         config.minimal_distance = float(cfg.support_points_minimal_distance); | ||||||
|         config.head_diameter    = float(cfg.support_head_front_diameter); |         config.head_diameter    = float(cfg.support_head_front_diameter); | ||||||
|          | 
 | ||||||
|         // scaling for the sub operations
 |         // scaling for the sub operations
 | ||||||
|         double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0; |         double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0; | ||||||
|         double init = current_status(); |         double init = current_status(); | ||||||
|          | 
 | ||||||
|         auto statuscb = [this, d, init](unsigned st) |         auto statuscb = [this, d, init](unsigned st) | ||||||
|         { |         { | ||||||
|             double current = init + st * d; |             double current = init + st * d; | ||||||
|             if(std::round(current_status()) < std::round(current)) |             if(std::round(current_status()) < std::round(current)) | ||||||
|                 report_status(current, OBJ_STEP_LABELS(slaposSupportPoints)); |                 report_status(current, OBJ_STEP_LABELS(slaposSupportPoints)); | ||||||
|         }; |         }; | ||||||
|          | 
 | ||||||
|         // Construction of this object does the calculation.
 |         // Construction of this object does the calculation.
 | ||||||
|         throw_if_canceled(); |         throw_if_canceled(); | ||||||
|         sla::SupportPointGenerator auto_supports( |         sla::SupportPointGenerator auto_supports( | ||||||
|  | @ -350,10 +503,10 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) | ||||||
|         const std::vector<sla::SupportPoint>& points = auto_supports.output(); |         const std::vector<sla::SupportPoint>& points = auto_supports.output(); | ||||||
|         throw_if_canceled(); |         throw_if_canceled(); | ||||||
|         po.m_supportdata->pts = points; |         po.m_supportdata->pts = points; | ||||||
|          | 
 | ||||||
|         BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " |         BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " | ||||||
|                                  << po.m_supportdata->pts.size(); |                                  << po.m_supportdata->pts.size(); | ||||||
|          | 
 | ||||||
|         // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
 |         // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
 | ||||||
|         // the update status to GLGizmoSlaSupports
 |         // the update status to GLGizmoSlaSupports
 | ||||||
|         report_status(-1, L("Generating support points"), |         report_status(-1, L("Generating support points"), | ||||||
|  | @ -368,9 +521,9 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) | ||||||
| void SLAPrint::Steps::support_tree(SLAPrintObject &po) | void SLAPrint::Steps::support_tree(SLAPrintObject &po) | ||||||
| { | { | ||||||
|     if(!po.m_supportdata) return; |     if(!po.m_supportdata) return; | ||||||
|      | 
 | ||||||
|     sla::PadConfig pcfg = make_pad_cfg(po.m_config); |     sla::PadConfig pcfg = make_pad_cfg(po.m_config); | ||||||
|      | 
 | ||||||
|     if (pcfg.embed_object) |     if (pcfg.embed_object) | ||||||
|         po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); |         po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); | ||||||
| 
 | 
 | ||||||
|  | @ -380,15 +533,15 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) | ||||||
|         remove_bottom_points(po.m_supportdata->pts, |         remove_bottom_points(po.m_supportdata->pts, | ||||||
|                              float(po.m_supportdata->emesh.ground_level() + EPSILON)); |                              float(po.m_supportdata->emesh.ground_level() + EPSILON)); | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     po.m_supportdata->cfg = make_support_cfg(po.m_config); |     po.m_supportdata->cfg = make_support_cfg(po.m_config); | ||||||
| //    po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
 | //    po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
 | ||||||
|      | 
 | ||||||
|     // scaling for the sub operations
 |     // scaling for the sub operations
 | ||||||
|     double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; |     double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; | ||||||
|     double init = current_status(); |     double init = current_status(); | ||||||
|     sla::JobController ctl; |     sla::JobController ctl; | ||||||
|      | 
 | ||||||
|     ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { |     ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { | ||||||
|         double current = init + st * d; |         double current = init + st * d; | ||||||
|         if (std::round(current_status()) < std::round(current)) |         if (std::round(current_status()) < std::round(current)) | ||||||
|  | @ -397,26 +550,26 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) | ||||||
|     }; |     }; | ||||||
|     ctl.stopcondition = [this]() { return canceled(); }; |     ctl.stopcondition = [this]() { return canceled(); }; | ||||||
|     ctl.cancelfn = [this]() { throw_if_canceled(); }; |     ctl.cancelfn = [this]() { throw_if_canceled(); }; | ||||||
|      | 
 | ||||||
|     po.m_supportdata->create_support_tree(ctl); |     po.m_supportdata->create_support_tree(ctl); | ||||||
|      | 
 | ||||||
|     if (!po.m_config.supports_enable.getBool()) return; |     if (!po.m_config.supports_enable.getBool()) return; | ||||||
|      | 
 | ||||||
|     throw_if_canceled(); |     throw_if_canceled(); | ||||||
|      | 
 | ||||||
|     // Create the unified mesh
 |     // Create the unified mesh
 | ||||||
|     auto rc = SlicingStatus::RELOAD_SCENE; |     auto rc = SlicingStatus::RELOAD_SCENE; | ||||||
|      | 
 | ||||||
|     // This is to prevent "Done." being displayed during merged_mesh()
 |     // This is to prevent "Done." being displayed during merged_mesh()
 | ||||||
|     report_status(-1, L("Visualizing supports")); |     report_status(-1, L("Visualizing supports")); | ||||||
|      | 
 | ||||||
|     BOOST_LOG_TRIVIAL(debug) << "Processed support point count " |     BOOST_LOG_TRIVIAL(debug) << "Processed support point count " | ||||||
|                              << po.m_supportdata->pts.size(); |                              << po.m_supportdata->pts.size(); | ||||||
|      | 
 | ||||||
|     // Check the mesh for later troubleshooting.
 |     // Check the mesh for later troubleshooting.
 | ||||||
|     if(po.support_mesh().empty()) |     if(po.support_mesh().empty()) | ||||||
|         BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; |         BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; | ||||||
|      | 
 | ||||||
|     report_status(-1, L("Visualizing supports"), rc); |     report_status(-1, L("Visualizing supports"), rc); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -424,15 +577,15 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { | ||||||
|     // this step can only go after the support tree has been created
 |     // this step can only go after the support tree has been created
 | ||||||
|     // and before the supports had been sliced. (or the slicing has to be
 |     // and before the supports had been sliced. (or the slicing has to be
 | ||||||
|     // repeated)
 |     // repeated)
 | ||||||
|      | 
 | ||||||
|     if(po.m_config.pad_enable.getBool()) { |     if(po.m_config.pad_enable.getBool()) { | ||||||
|         // Get the distilled pad configuration from the config
 |         // Get the distilled pad configuration from the config
 | ||||||
|         sla::PadConfig pcfg = make_pad_cfg(po.m_config); |         sla::PadConfig pcfg = make_pad_cfg(po.m_config); | ||||||
|          | 
 | ||||||
|         ExPolygons bp; // This will store the base plate of the pad.
 |         ExPolygons bp; // This will store the base plate of the pad.
 | ||||||
|         double   pad_h             = pcfg.full_height(); |         double   pad_h             = pcfg.full_height(); | ||||||
|         const TriangleMesh &trmesh = po.transformed_mesh(); |         const TriangleMesh &trmesh = po.transformed_mesh(); | ||||||
|          | 
 | ||||||
|         if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { |         if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { | ||||||
|             // No support (thus no elevation) or zero elevation mode
 |             // No support (thus no elevation) or zero elevation mode
 | ||||||
|             // we sometimes call it "builtin pad" is enabled so we will
 |             // we sometimes call it "builtin pad" is enabled so we will
 | ||||||
|  | @ -442,19 +595,19 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { | ||||||
|                                float(po.m_config.layer_height.getFloat()), |                                float(po.m_config.layer_height.getFloat()), | ||||||
|                                [this](){ throw_if_canceled(); }); |                                [this](){ throw_if_canceled(); }); | ||||||
|         } |         } | ||||||
|          | 
 | ||||||
|         po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); |         po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); | ||||||
|         auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad); |         auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad); | ||||||
|          | 
 | ||||||
|         if (!validate_pad(pad_mesh, pcfg)) |         if (!validate_pad(pad_mesh, pcfg)) | ||||||
|             throw Slic3r::SlicingError( |             throw Slic3r::SlicingError( | ||||||
|                     L("No pad can be generated for this model with the " |                     L("No pad can be generated for this model with the " | ||||||
|                       "current configuration")); |                       "current configuration")); | ||||||
|          | 
 | ||||||
|     } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { |     } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { | ||||||
|         po.m_supportdata->support_tree_ptr->remove_pad(); |         po.m_supportdata->support_tree_ptr->remove_pad(); | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     throw_if_canceled(); |     throw_if_canceled(); | ||||||
|     report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE); |     report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE); | ||||||
| } | } | ||||||
|  | @ -464,25 +617,25 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { | ||||||
| // be part of the slices)
 | // be part of the slices)
 | ||||||
| void SLAPrint::Steps::slice_supports(SLAPrintObject &po) { | void SLAPrint::Steps::slice_supports(SLAPrintObject &po) { | ||||||
|     auto& sd = po.m_supportdata; |     auto& sd = po.m_supportdata; | ||||||
|      | 
 | ||||||
|     if(sd) sd->support_slices.clear(); |     if(sd) sd->support_slices.clear(); | ||||||
|      | 
 | ||||||
|     // Don't bother if no supports and no pad is present.
 |     // Don't bother if no supports and no pad is present.
 | ||||||
|     if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool()) |     if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool()) | ||||||
|         return; |         return; | ||||||
|      | 
 | ||||||
|     if(sd && sd->support_tree_ptr) { |     if(sd && sd->support_tree_ptr) { | ||||||
|         auto heights = reserve_vector<float>(po.m_slice_index.size()); |         auto heights = reserve_vector<float>(po.m_slice_index.size()); | ||||||
|          | 
 | ||||||
|         for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level()); |         for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level()); | ||||||
| 
 | 
 | ||||||
|         sd->support_slices = sd->support_tree_ptr->slice( |         sd->support_slices = sd->support_tree_ptr->slice( | ||||||
|             heights, float(po.config().slice_closing_radius.value)); |             heights, float(po.config().slice_closing_radius.value)); | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i)  |     for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) | ||||||
|         po.m_slice_index[i].set_support_slice_idx(po, i); |         po.m_slice_index[i].set_support_slice_idx(po, i); | ||||||
|      | 
 | ||||||
|     apply_printer_corrections(po, soSupport); |     apply_printer_corrections(po, soSupport); | ||||||
| 
 | 
 | ||||||
|     // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
 |     // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
 | ||||||
|  | @ -497,37 +650,37 @@ using ClipperPolygons = std::vector<ClipperPolygon>; | ||||||
| static ClipperPolygons polyunion(const ClipperPolygons &subjects) | static ClipperPolygons polyunion(const ClipperPolygons &subjects) | ||||||
| { | { | ||||||
|     ClipperLib::Clipper clipper; |     ClipperLib::Clipper clipper; | ||||||
|      | 
 | ||||||
|     bool closed = true; |     bool closed = true; | ||||||
|      | 
 | ||||||
|     for(auto& path : subjects) { |     for(auto& path : subjects) { | ||||||
|         clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); |         clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); | ||||||
|         clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); |         clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     auto mode = ClipperLib::pftPositive; |     auto mode = ClipperLib::pftPositive; | ||||||
|      | 
 | ||||||
|     return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode); |     return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips) | static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips) | ||||||
| { | { | ||||||
|     ClipperLib::Clipper clipper; |     ClipperLib::Clipper clipper; | ||||||
|      | 
 | ||||||
|     bool closed = true; |     bool closed = true; | ||||||
|      | 
 | ||||||
|     for(auto& path : subjects) { |     for(auto& path : subjects) { | ||||||
|         clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); |         clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); | ||||||
|         clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); |         clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     for(auto& path : clips) { |     for(auto& path : clips) { | ||||||
|         clipper.AddPath(path.Contour, ClipperLib::ptClip, closed); |         clipper.AddPath(path.Contour, ClipperLib::ptClip, closed); | ||||||
|         clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed); |         clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed); | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     auto mode = ClipperLib::pftPositive; |     auto mode = ClipperLib::pftPositive; | ||||||
|      | 
 | ||||||
|     return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode); |     return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -535,28 +688,28 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo | ||||||
| static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o) | static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o) | ||||||
| { | { | ||||||
|     namespace sl = libnest2d::sl; |     namespace sl = libnest2d::sl; | ||||||
|      | 
 | ||||||
|     if (!record.print_obj()) return {}; |     if (!record.print_obj()) return {}; | ||||||
|      | 
 | ||||||
|     ClipperPolygons polygons; |     ClipperPolygons polygons; | ||||||
|     auto &input_polygons = record.get_slice(o); |     auto &input_polygons = record.get_slice(o); | ||||||
|     auto &instances = record.print_obj()->instances(); |     auto &instances = record.print_obj()->instances(); | ||||||
|     bool is_lefthanded = record.print_obj()->is_left_handed(); |     bool is_lefthanded = record.print_obj()->is_left_handed(); | ||||||
|     polygons.reserve(input_polygons.size() * instances.size()); |     polygons.reserve(input_polygons.size() * instances.size()); | ||||||
|      | 
 | ||||||
|     for (const ExPolygon& polygon : input_polygons) { |     for (const ExPolygon& polygon : input_polygons) { | ||||||
|         if(polygon.contour.empty()) continue; |         if(polygon.contour.empty()) continue; | ||||||
|          | 
 | ||||||
|         for (size_t i = 0; i < instances.size(); ++i) |         for (size_t i = 0; i < instances.size(); ++i) | ||||||
|         { |         { | ||||||
|             ClipperPolygon poly; |             ClipperPolygon poly; | ||||||
|              | 
 | ||||||
|             // We need to reverse if is_lefthanded is true but
 |             // We need to reverse if is_lefthanded is true but
 | ||||||
|             bool needreverse = is_lefthanded; |             bool needreverse = is_lefthanded; | ||||||
|              | 
 | ||||||
|             // should be a move
 |             // should be a move
 | ||||||
|             poly.Contour.reserve(polygon.contour.size() + 1); |             poly.Contour.reserve(polygon.contour.size() + 1); | ||||||
|              | 
 | ||||||
|             auto& cntr = polygon.contour.points; |             auto& cntr = polygon.contour.points; | ||||||
|             if(needreverse) |             if(needreverse) | ||||||
|                 for(auto it = cntr.rbegin(); it != cntr.rend(); ++it) |                 for(auto it = cntr.rbegin(); it != cntr.rend(); ++it) | ||||||
|  | @ -564,12 +717,12 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o | ||||||
|             else |             else | ||||||
|                 for(auto& p : cntr) |                 for(auto& p : cntr) | ||||||
|                     poly.Contour.emplace_back(p.x(), p.y()); |                     poly.Contour.emplace_back(p.x(), p.y()); | ||||||
|              | 
 | ||||||
|             for(auto& h : polygon.holes) { |             for(auto& h : polygon.holes) { | ||||||
|                 poly.Holes.emplace_back(); |                 poly.Holes.emplace_back(); | ||||||
|                 auto& hole = poly.Holes.back(); |                 auto& hole = poly.Holes.back(); | ||||||
|                 hole.reserve(h.points.size() + 1); |                 hole.reserve(h.points.size() + 1); | ||||||
|                  | 
 | ||||||
|                 if(needreverse) |                 if(needreverse) | ||||||
|                     for(auto it = h.points.rbegin(); it != h.points.rend(); ++it) |                     for(auto it = h.points.rbegin(); it != h.points.rend(); ++it) | ||||||
|                         hole.emplace_back(it->x(), it->y()); |                         hole.emplace_back(it->x(), it->y()); | ||||||
|  | @ -577,42 +730,42 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o | ||||||
|                     for(auto& p : h.points) |                     for(auto& p : h.points) | ||||||
|                         hole.emplace_back(p.x(), p.y()); |                         hole.emplace_back(p.x(), p.y()); | ||||||
|             } |             } | ||||||
|              | 
 | ||||||
|             if(is_lefthanded) { |             if(is_lefthanded) { | ||||||
|                 for(auto& p : poly.Contour) p.X = -p.X; |                 for(auto& p : poly.Contour) p.X = -p.X; | ||||||
|                 for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X; |                 for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X; | ||||||
|             } |             } | ||||||
|              | 
 | ||||||
|             sl::rotate(poly, double(instances[i].rotation)); |             sl::rotate(poly, double(instances[i].rotation)); | ||||||
|             sl::translate(poly, ClipperPoint{instances[i].shift.x(), |             sl::translate(poly, ClipperPoint{instances[i].shift.x(), | ||||||
|                                              instances[i].shift.y()}); |                                              instances[i].shift.y()}); | ||||||
|              | 
 | ||||||
|             polygons.emplace_back(std::move(poly)); |             polygons.emplace_back(std::move(poly)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     return polygons; |     return polygons; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SLAPrint::Steps::initialize_printer_input() | void SLAPrint::Steps::initialize_printer_input() | ||||||
| { | { | ||||||
|     auto &printer_input = m_print->m_printer_input; |     auto &printer_input = m_print->m_printer_input; | ||||||
|      | 
 | ||||||
|     // clear the rasterizer input
 |     // clear the rasterizer input
 | ||||||
|     printer_input.clear(); |     printer_input.clear(); | ||||||
|      | 
 | ||||||
|     size_t mx = 0; |     size_t mx = 0; | ||||||
|     for(SLAPrintObject * o : m_print->m_objects) { |     for(SLAPrintObject * o : m_print->m_objects) { | ||||||
|         if(auto m = o->get_slice_index().size() > mx) mx = m; |         if(auto m = o->get_slice_index().size() > mx) mx = m; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     printer_input.reserve(mx); |     printer_input.reserve(mx); | ||||||
|      | 
 | ||||||
|     auto eps = coord_t(SCALED_EPSILON); |     auto eps = coord_t(SCALED_EPSILON); | ||||||
|      | 
 | ||||||
|     for(SLAPrintObject * o : m_print->m_objects) { |     for(SLAPrintObject * o : m_print->m_objects) { | ||||||
|         coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs; |         coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs; | ||||||
|          | 
 | ||||||
|         for(const SliceRecord& slicerecord : o->get_slice_index()) { |         for(const SliceRecord& slicerecord : o->get_slice_index()) { | ||||||
|             if (!slicerecord.is_valid()) |             if (!slicerecord.is_valid()) | ||||||
|                 throw Slic3r::SlicingError( |                 throw Slic3r::SlicingError( | ||||||
|  | @ -621,7 +774,7 @@ void SLAPrint::Steps::initialize_printer_input() | ||||||
|                       "objects printable.")); |                       "objects printable.")); | ||||||
| 
 | 
 | ||||||
|             coord_t lvlid = slicerecord.print_level() - gndlvl; |             coord_t lvlid = slicerecord.print_level() - gndlvl; | ||||||
|              | 
 | ||||||
|             // Neat trick to round the layer levels to the grid.
 |             // Neat trick to round the layer levels to the grid.
 | ||||||
|             lvlid = eps * (lvlid / eps); |             lvlid = eps * (lvlid / eps); | ||||||
| 
 | 
 | ||||||
|  | @ -631,8 +784,8 @@ void SLAPrint::Steps::initialize_printer_input() | ||||||
| 
 | 
 | ||||||
|             if(it == printer_input.end() || it->level() != lvlid) |             if(it == printer_input.end() || it->level() != lvlid) | ||||||
|                 it = printer_input.insert(it, PrintLayer(lvlid)); |                 it = printer_input.insert(it, PrintLayer(lvlid)); | ||||||
|              | 
 | ||||||
|              | 
 | ||||||
|             it->add(slicerecord); |             it->add(slicerecord); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -641,53 +794,53 @@ void SLAPrint::Steps::initialize_printer_input() | ||||||
| // Merging the slices from all the print objects into one slice grid and
 | // Merging the slices from all the print objects into one slice grid and
 | ||||||
| // calculating print statistics from the merge result.
 | // calculating print statistics from the merge result.
 | ||||||
| void SLAPrint::Steps::merge_slices_and_eval_stats() { | void SLAPrint::Steps::merge_slices_and_eval_stats() { | ||||||
|      | 
 | ||||||
|     initialize_printer_input(); |     initialize_printer_input(); | ||||||
|      | 
 | ||||||
|     auto &print_statistics = m_print->m_print_statistics; |     auto &print_statistics = m_print->m_print_statistics; | ||||||
|     auto &printer_config   = m_print->m_printer_config; |     auto &printer_config   = m_print->m_printer_config; | ||||||
|     auto &material_config  = m_print->m_material_config; |     auto &material_config  = m_print->m_material_config; | ||||||
|     auto &printer_input    = m_print->m_printer_input; |     auto &printer_input    = m_print->m_printer_input; | ||||||
|      | 
 | ||||||
|     print_statistics.clear(); |     print_statistics.clear(); | ||||||
|      | 
 | ||||||
|     // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
 |     // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
 | ||||||
|     auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); }; |     auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); }; | ||||||
|      | 
 | ||||||
|     const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
 |     const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
 | ||||||
|     const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0;
 |     const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0;
 | ||||||
|     const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0;
 |     const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0;
 | ||||||
|      | 
 | ||||||
|     const double init_exp_time = material_config.initial_exposure_time.getFloat(); |     const double init_exp_time = material_config.initial_exposure_time.getFloat(); | ||||||
|     const double exp_time      = material_config.exposure_time.getFloat(); |     const double exp_time      = material_config.exposure_time.getFloat(); | ||||||
|      | 
 | ||||||
|     const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20]
 |     const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20]
 | ||||||
|      | 
 | ||||||
|     const auto width          = scaled<double>(printer_config.display_width.getFloat()); |     const auto width          = scaled<double>(printer_config.display_width.getFloat()); | ||||||
|     const auto height         = scaled<double>(printer_config.display_height.getFloat()); |     const auto height         = scaled<double>(printer_config.display_height.getFloat()); | ||||||
|     const double display_area = width*height; |     const double display_area = width*height; | ||||||
|      | 
 | ||||||
|     double supports_volume(0.0); |     double supports_volume(0.0); | ||||||
|     double models_volume(0.0); |     double models_volume(0.0); | ||||||
|      | 
 | ||||||
|     double estim_time(0.0); |     double estim_time(0.0); | ||||||
|     std::vector<double> layers_times; |     std::vector<double> layers_times; | ||||||
|     layers_times.reserve(printer_input.size()); |     layers_times.reserve(printer_input.size()); | ||||||
|      | 
 | ||||||
|     size_t slow_layers = 0; |     size_t slow_layers = 0; | ||||||
|     size_t fast_layers = 0; |     size_t fast_layers = 0; | ||||||
|      | 
 | ||||||
|     const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1); |     const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1); | ||||||
|     double fade_layer_time = init_exp_time; |     double fade_layer_time = init_exp_time; | ||||||
|      | 
 | ||||||
|     sla::ccr::SpinningMutex mutex; |     sla::ccr::SpinningMutex mutex; | ||||||
|     using Lock = std::lock_guard<sla::ccr::SpinningMutex>; |     using Lock = std::lock_guard<sla::ccr::SpinningMutex>; | ||||||
|      | 
 | ||||||
|     // Going to parallel:
 |     // Going to parallel:
 | ||||||
|     auto printlayerfn = [this, |     auto printlayerfn = [this, | ||||||
|             // functions and read only vars
 |             // functions and read only vars
 | ||||||
|             areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, |             areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, | ||||||
|              | 
 | ||||||
|             // write vars
 |             // write vars
 | ||||||
|             &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, |             &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, | ||||||
|             &fast_layers, &fade_layer_time, &layers_times](size_t sliced_layer_cnt) |             &fast_layers, &fade_layer_time, &layers_times](size_t sliced_layer_cnt) | ||||||
|  | @ -696,87 +849,87 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { | ||||||
| 
 | 
 | ||||||
|         // vector of slice record references
 |         // vector of slice record references
 | ||||||
|         auto& slicerecord_references = layer.slices(); |         auto& slicerecord_references = layer.slices(); | ||||||
|          | 
 | ||||||
|         if(slicerecord_references.empty()) return; |         if(slicerecord_references.empty()) return; | ||||||
|          | 
 | ||||||
|         // Layer height should match for all object slices for a given level.
 |         // Layer height should match for all object slices for a given level.
 | ||||||
|         const auto l_height = double(slicerecord_references.front().get().layer_height()); |         const auto l_height = double(slicerecord_references.front().get().layer_height()); | ||||||
|          | 
 | ||||||
|         // Calculation of the consumed material
 |         // Calculation of the consumed material
 | ||||||
|          | 
 | ||||||
|         ClipperPolygons model_polygons; |         ClipperPolygons model_polygons; | ||||||
|         ClipperPolygons supports_polygons; |         ClipperPolygons supports_polygons; | ||||||
|          | 
 | ||||||
|         size_t c = std::accumulate(layer.slices().begin(), |         size_t c = std::accumulate(layer.slices().begin(), | ||||||
|                                    layer.slices().end(), |                                    layer.slices().end(), | ||||||
|                                    size_t(0), |                                    size_t(0), | ||||||
|                                    [](size_t a, const SliceRecord &sr) { |                                    [](size_t a, const SliceRecord &sr) { | ||||||
|             return a + sr.get_slice(soModel).size(); |             return a + sr.get_slice(soModel).size(); | ||||||
|         }); |         }); | ||||||
|          | 
 | ||||||
|         model_polygons.reserve(c); |         model_polygons.reserve(c); | ||||||
|          | 
 | ||||||
|         c = std::accumulate(layer.slices().begin(), |         c = std::accumulate(layer.slices().begin(), | ||||||
|                             layer.slices().end(), |                             layer.slices().end(), | ||||||
|                             size_t(0), |                             size_t(0), | ||||||
|                             [](size_t a, const SliceRecord &sr) { |                             [](size_t a, const SliceRecord &sr) { | ||||||
|             return a + sr.get_slice(soModel).size(); |             return a + sr.get_slice(soModel).size(); | ||||||
|         }); |         }); | ||||||
|          | 
 | ||||||
|         supports_polygons.reserve(c); |         supports_polygons.reserve(c); | ||||||
|          | 
 | ||||||
|         for(const SliceRecord& record : layer.slices()) { |         for(const SliceRecord& record : layer.slices()) { | ||||||
|              | 
 | ||||||
|             ClipperPolygons modelslices = get_all_polygons(record, soModel); |             ClipperPolygons modelslices = get_all_polygons(record, soModel); | ||||||
|             for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp)); |             for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp)); | ||||||
|          | 
 | ||||||
|             ClipperPolygons supportslices = get_all_polygons(record, soSupport); |             ClipperPolygons supportslices = get_all_polygons(record, soSupport); | ||||||
|             for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp)); |             for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp)); | ||||||
|          | 
 | ||||||
|         } |         } | ||||||
|          | 
 | ||||||
|         model_polygons = polyunion(model_polygons); |         model_polygons = polyunion(model_polygons); | ||||||
|         double layer_model_area = 0; |         double layer_model_area = 0; | ||||||
|         for (const ClipperPolygon& polygon : model_polygons) |         for (const ClipperPolygon& polygon : model_polygons) | ||||||
|             layer_model_area += areafn(polygon); |             layer_model_area += areafn(polygon); | ||||||
|          | 
 | ||||||
|         if (layer_model_area < 0 || layer_model_area > 0) { |         if (layer_model_area < 0 || layer_model_area > 0) { | ||||||
|             Lock lck(mutex); models_volume += layer_model_area * l_height; |             Lock lck(mutex); models_volume += layer_model_area * l_height; | ||||||
|         } |         } | ||||||
|          | 
 | ||||||
|         if(!supports_polygons.empty()) { |         if(!supports_polygons.empty()) { | ||||||
|             if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons); |             if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons); | ||||||
|             else supports_polygons = polydiff(supports_polygons, model_polygons); |             else supports_polygons = polydiff(supports_polygons, model_polygons); | ||||||
|             // allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
 |             // allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
 | ||||||
|         } |         } | ||||||
|          | 
 | ||||||
|         double layer_support_area = 0; |         double layer_support_area = 0; | ||||||
|         for (const ClipperPolygon& polygon : supports_polygons) |         for (const ClipperPolygon& polygon : supports_polygons) | ||||||
|             layer_support_area += areafn(polygon); |             layer_support_area += areafn(polygon); | ||||||
|          | 
 | ||||||
|         if (layer_support_area < 0 || layer_support_area > 0) { |         if (layer_support_area < 0 || layer_support_area > 0) { | ||||||
|             Lock lck(mutex); supports_volume += layer_support_area * l_height; |             Lock lck(mutex); supports_volume += layer_support_area * l_height; | ||||||
|         } |         } | ||||||
|          | 
 | ||||||
|         // Here we can save the expensively calculated polygons for printing
 |         // Here we can save the expensively calculated polygons for printing
 | ||||||
|         ClipperPolygons trslices; |         ClipperPolygons trslices; | ||||||
|         trslices.reserve(model_polygons.size() + supports_polygons.size()); |         trslices.reserve(model_polygons.size() + supports_polygons.size()); | ||||||
|         for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); |         for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); | ||||||
|         for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); |         for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); | ||||||
|          | 
 | ||||||
|         layer.transformed_slices(polyunion(trslices)); |         layer.transformed_slices(polyunion(trslices)); | ||||||
|          | 
 | ||||||
|         // Calculation of the slow and fast layers to the future controlling those values on FW
 |         // Calculation of the slow and fast layers to the future controlling those values on FW
 | ||||||
|          | 
 | ||||||
|         const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill; |         const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill; | ||||||
|         const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt; |         const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt; | ||||||
|          | 
 | ||||||
|         { Lock lck(mutex); |         { Lock lck(mutex); | ||||||
|             if (is_fast_layer) |             if (is_fast_layer) | ||||||
|                 fast_layers++; |                 fast_layers++; | ||||||
|             else |             else | ||||||
|                 slow_layers++; |                 slow_layers++; | ||||||
|              | 
 | ||||||
|             // Calculation of the printing time
 |             // Calculation of the printing time
 | ||||||
| 
 | 
 | ||||||
|             double layer_times = 0.0; |             double layer_times = 0.0; | ||||||
|  | @ -794,15 +947,15 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { | ||||||
|             estim_time += layer_times; |             estim_time += layer_times; | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|      | 
 | ||||||
|     // sequential version for debugging:
 |     // sequential version for debugging:
 | ||||||
|     // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
 |     // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
 | ||||||
|     sla::ccr::for_each(size_t(0), printer_input.size(), printlayerfn); |     sla::ccr::for_each(size_t(0), printer_input.size(), printlayerfn); | ||||||
|      | 
 | ||||||
|     auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; |     auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; | ||||||
|     print_statistics.support_used_material = supports_volume * SCALING2; |     print_statistics.support_used_material = supports_volume * SCALING2; | ||||||
|     print_statistics.objects_used_material = models_volume  * SCALING2; |     print_statistics.objects_used_material = models_volume  * SCALING2; | ||||||
|      | 
 | ||||||
|     // Estimated printing time
 |     // Estimated printing time
 | ||||||
|     // A layers count o the highest object
 |     // A layers count o the highest object
 | ||||||
|     if (printer_input.size() == 0) |     if (printer_input.size() == 0) | ||||||
|  | @ -811,10 +964,10 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { | ||||||
|         print_statistics.estimated_print_time = estim_time; |         print_statistics.estimated_print_time = estim_time; | ||||||
|         print_statistics.layers_times = layers_times; |         print_statistics.layers_times = layers_times; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     print_statistics.fast_layers_count = fast_layers; |     print_statistics.fast_layers_count = fast_layers; | ||||||
|     print_statistics.slow_layers_count = slow_layers; |     print_statistics.slow_layers_count = slow_layers; | ||||||
|      | 
 | ||||||
|     report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); |     report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -822,23 +975,23 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { | ||||||
| void SLAPrint::Steps::rasterize() | void SLAPrint::Steps::rasterize() | ||||||
| { | { | ||||||
|     if(canceled() || !m_print->m_printer) return; |     if(canceled() || !m_print->m_printer) return; | ||||||
|      | 
 | ||||||
|     // coefficient to map the rasterization state (0-99) to the allocated
 |     // coefficient to map the rasterization state (0-99) to the allocated
 | ||||||
|     // portion (slot) of the process state
 |     // portion (slot) of the process state
 | ||||||
|     double sd = (100 - max_objstatus) / 100.0; |     double sd = (100 - max_objstatus) / 100.0; | ||||||
|      | 
 | ||||||
|     // slot is the portion of 100% that is realted to rasterization
 |     // slot is the portion of 100% that is realted to rasterization
 | ||||||
|     unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; |     unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; | ||||||
|      | 
 | ||||||
|     // pst: previous state
 |     // pst: previous state
 | ||||||
|     double pst = current_status(); |     double pst = current_status(); | ||||||
|      | 
 | ||||||
|     double increment = (slot * sd) / m_print->m_printer_input.size(); |     double increment = (slot * sd) / m_print->m_printer_input.size(); | ||||||
|     double dstatus = current_status(); |     double dstatus = current_status(); | ||||||
|      | 
 | ||||||
|     sla::ccr::SpinningMutex slck; |     sla::ccr::SpinningMutex slck; | ||||||
|     using Lock = std::lock_guard<sla::ccr::SpinningMutex>; |     using Lock = std::lock_guard<sla::ccr::SpinningMutex>; | ||||||
|      | 
 | ||||||
|     // procedure to process one height level. This will run in parallel
 |     // procedure to process one height level. This will run in parallel
 | ||||||
|     auto lvlfn = |     auto lvlfn = | ||||||
|         [this, &slck, increment, &dstatus, &pst] |         [this, &slck, increment, &dstatus, &pst] | ||||||
|  | @ -846,10 +999,10 @@ void SLAPrint::Steps::rasterize() | ||||||
|     { |     { | ||||||
|         PrintLayer& printlayer = m_print->m_printer_input[idx]; |         PrintLayer& printlayer = m_print->m_printer_input[idx]; | ||||||
|         if(canceled()) return; |         if(canceled()) return; | ||||||
|          | 
 | ||||||
|         for (const ClipperLib::Polygon& poly : printlayer.transformed_slices()) |         for (const ClipperLib::Polygon& poly : printlayer.transformed_slices()) | ||||||
|             raster.draw(poly); |             raster.draw(poly); | ||||||
|          | 
 | ||||||
|         // Status indication guarded with the spinlock
 |         // Status indication guarded with the spinlock
 | ||||||
|         { |         { | ||||||
|             Lock lck(slck); |             Lock lck(slck); | ||||||
|  | @ -861,10 +1014,10 @@ void SLAPrint::Steps::rasterize() | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|      | 
 | ||||||
|     // last minute escape
 |     // last minute escape
 | ||||||
|     if(canceled()) return; |     if(canceled()) return; | ||||||
|      | 
 | ||||||
|     // Print all the layers in parallel
 |     // Print all the layers in parallel
 | ||||||
|     m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn); |     m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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