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)) {} | ||||
|     BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &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; | ||||
|             // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
 | ||||
|         } else { | ||||
|             typename std::vector<PointClass>::const_iterator it = points.begin(); | ||||
|             this->min = *it; | ||||
|             this->max = *it; | ||||
|             for (++ it; it != points.end(); ++ it) { | ||||
|                 this->min = this->min.cwiseMin(*it); | ||||
|                 this->max = this->max.cwiseMax(*it); | ||||
|             auto it = from; | ||||
|             this->min = it->template cast<typename PointClass::Scalar>(); | ||||
|             this->max = this->min; | ||||
|             for (++ it; it != to; ++ 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)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     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 merge(const PointClass &point); | ||||
|     void merge(const std::vector<PointClass> &points); | ||||
|  | @ -74,19 +82,27 @@ public: | |||
|         { if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; } | ||||
|     BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &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"); | ||||
|         typename std::vector<PointClass>::const_iterator it = points.begin(); | ||||
|         this->min = *it; | ||||
|         this->max = *it; | ||||
|         for (++ it; it != points.end(); ++ it) { | ||||
|             this->min = this->min.cwiseMin(*it); | ||||
|             this->max = this->max.cwiseMax(*it); | ||||
| 
 | ||||
|         auto it = from; | ||||
|         this->min = it->template cast<typename PointClass::Scalar>(); | ||||
|         this->max = this->min; | ||||
|         for (++ it; it != to; ++ 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)); | ||||
|     } | ||||
| 
 | ||||
|     BoundingBox3Base(const std::vector<PointClass> &points) | ||||
|         : BoundingBox3Base(points.begin(), points.end()) | ||||
|     {} | ||||
| 
 | ||||
|     void merge(const PointClass &point); | ||||
|     void merge(const std::vector<PointClass> &points); | ||||
|     void merge(const BoundingBox3Base<PointClass> &bb); | ||||
|  | @ -188,9 +204,7 @@ public: | |||
| class BoundingBoxf3 : public BoundingBox3Base<Vec3d>  | ||||
| { | ||||
| public: | ||||
|     BoundingBoxf3() : BoundingBox3Base<Vec3d>() {} | ||||
|     BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {} | ||||
|     BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {} | ||||
|     using BoundingBox3Base::BoundingBox3Base; | ||||
| 
 | ||||
|     BoundingBoxf3 transformed(const Transform3d& matrix) const; | ||||
| }; | ||||
|  |  | |||
|  | @ -22,74 +22,54 @@ namespace Slic3r { | |||
| class TriangleMeshDataAdapter { | ||||
| public: | ||||
|     const TriangleMesh &mesh; | ||||
|     float voxel_scale; | ||||
| 
 | ||||
|     size_t polygonCount() const { return mesh.its.indices.size(); } | ||||
|     size_t pointCount() const   { return mesh.its.vertices.size(); } | ||||
|     size_t vertexCount(size_t) const { return 3; } | ||||
| 
 | ||||
|     // 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; | ||||
| }; | ||||
| 
 | ||||
| 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 | ||||
|     // 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>(); | ||||
|     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]; | ||||
|         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} {}; | ||||
| }; | ||||
| 
 | ||||
| // 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
 | ||||
| // even if was called previously.
 | ||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &            mesh, | ||||
|                                      const openvdb::math::Transform &tr, | ||||
|                                      float voxel_scale, | ||||
|                                      float exteriorBandWidth, | ||||
|                                      float interiorBandWidth, | ||||
|                                      int   flags) | ||||
| { | ||||
|     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(), | ||||
|     [](TriangleMesh *m){ | ||||
|     auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) { | ||||
|          m->require_shared_vertices(); | ||||
|         return !m->is_manifold() || m->volume() < EPSILON; | ||||
|          return m->volume() < EPSILON; | ||||
|      }); | ||||
| 
 | ||||
|     meshparts.erase(it, meshparts.end()); | ||||
| 
 | ||||
|     openvdb::FloatGrid::Ptr grid; | ||||
|     for (TriangleMesh *m : meshparts) { | ||||
|     for (auto &m : meshparts) { | ||||
|         auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>( | ||||
|             TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth, | ||||
|             TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth, | ||||
|             interiorBandWidth, flags); | ||||
| 
 | ||||
|         if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); | ||||
|  | @ -106,19 +86,9 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, | |||
|             interiorBandWidth, flags); | ||||
|     } | ||||
| 
 | ||||
|     return grid; | ||||
| } | ||||
|     grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale)); | ||||
| 
 | ||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh, | ||||
|                                      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); | ||||
|     return grid; | ||||
| } | ||||
| 
 | ||||
| template<class Grid> | ||||
|  | @ -136,12 +106,17 @@ sla::Contour3D _volumeToMesh(const Grid &grid, | |||
|     openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue, | ||||
|                                  adaptivity, relaxDisorientedTriangles); | ||||
| 
 | ||||
|     float scale = 1.; | ||||
|     try { | ||||
|         scale = grid.template metaValue<float>("voxel_scale"); | ||||
|     }  catch (...) { } | ||||
| 
 | ||||
|     sla::Contour3D ret; | ||||
|     ret.points.reserve(points.size()); | ||||
|     ret.faces3.reserve(triangles.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 : quads) ret.faces4.emplace_back(to_vec4i(v)); | ||||
| 
 | ||||
|  | @ -166,9 +141,18 @@ sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid, | |||
|                          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
 | ||||
|  |  | |||
|  | @ -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 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, | ||||
|                                      const openvdb::math::Transform &tr = {}, | ||||
|                                      float exteriorBandWidth = 3.0f, | ||||
|                                      float interiorBandWidth = 3.0f, | ||||
|                                      int   flags             = 0); | ||||
| 
 | ||||
| openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &          mesh, | ||||
|                                      const openvdb::math::Transform &tr = {}, | ||||
|                                      float voxel_scale = 1.f, | ||||
|                                      float exteriorBandWidth = 3.0f, | ||||
|                                      float interiorBandWidth = 3.0f, | ||||
|                                      int   flags             = 0); | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #include <tbb/mutex.h> | ||||
| #include <tbb/parallel_for.h> | ||||
| #include <tbb/parallel_reduce.h> | ||||
| #include <tbb/task_arena.h> | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <numeric> | ||||
|  | @ -76,6 +77,11 @@ template<> struct _ccr<true> | |||
|             from, to, init, std::forward<MergeFn>(mergefn), | ||||
|             [](typename I::value_type &i) { return i; }, granularity); | ||||
|     } | ||||
| 
 | ||||
|     static size_t max_concurreny() | ||||
|     { | ||||
|         return tbb::this_task_arena::max_concurrency(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| template<> struct _ccr<false> | ||||
|  | @ -133,6 +139,8 @@ public: | |||
|         return reduce(from, to, init, std::forward<MergeFn>(mergefn), | ||||
|                       [](typename I::value_type &i) { return i; }); | ||||
|     } | ||||
| 
 | ||||
|     static size_t max_concurreny() { return 1; } | ||||
| }; | ||||
| 
 | ||||
| using ccr = _ccr<USE_FULL_CONCURRENCY>; | ||||
|  |  | |||
|  | @ -26,16 +26,47 @@ inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); } | |||
| template<class S, class = FloatingOnly<S>> | ||||
| inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; } | ||||
| 
 | ||||
| static TriangleMesh _generate_interior(const TriangleMesh  &mesh, | ||||
| struct Interior { | ||||
|     TriangleMesh mesh; | ||||
|     openvdb::FloatGrid::Ptr gridptr; | ||||
|     mutable std::optional<openvdb::FloatGrid::ConstAccessor> accessor; | ||||
| 
 | ||||
|     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 D = voxel_scale * closing_dist; | ||||
|     float  out_range = 0.1f * float(offset); | ||||
|  | @ -44,7 +75,7 @@ static TriangleMesh _generate_interior(const TriangleMesh  &mesh, | |||
|     if (ctl.stopcondition()) return {}; | ||||
|     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); | ||||
| 
 | ||||
|  | @ -56,28 +87,32 @@ static TriangleMesh _generate_interior(const TriangleMesh  &mesh, | |||
|     if (ctl.stopcondition()) return {}; | ||||
|     else ctl.statuscb(30, L("Hollowing")); | ||||
| 
 | ||||
|     if (closing_dist > .0) { | ||||
|         gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range)); | ||||
|     } else { | ||||
|         D = -offset; | ||||
|     } | ||||
|     double iso_surface = D; | ||||
|     auto   narrowb = double(in_range); | ||||
|     gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); | ||||
| 
 | ||||
|     if (ctl.stopcondition()) return {}; | ||||
|     else ctl.statuscb(70, L("Hollowing")); | ||||
| 
 | ||||
|     double iso_surface = D; | ||||
|     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 {}; | ||||
|     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 JobController &  ctl) | ||||
| { | ||||
|  | @ -92,15 +127,16 @@ std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &   mesh, | |||
|     //
 | ||||
|     // max 8x upscale, min is native voxel size
 | ||||
|     auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality; | ||||
|     auto meshptr = std::make_unique<TriangleMesh>( | ||||
|         _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, | ||||
|                            hc.closing_distance)); | ||||
| 
 | ||||
|     if (meshptr && !meshptr->empty()) { | ||||
|     InteriorPtr interior = | ||||
|         generate_interior_verbose(mesh, ctl, hc.min_thickness, voxel_scale, | ||||
|                                   hc.closing_distance); | ||||
| 
 | ||||
|     if (interior && !interior->mesh.empty()) { | ||||
| 
 | ||||
|         // This flips the normals to be outward facing...
 | ||||
|         meshptr->require_shared_vertices(); | ||||
|         indexed_triangle_set its = std::move(meshptr->its); | ||||
|         interior->mesh.require_shared_vertices(); | ||||
|         indexed_triangle_set its = std::move(interior->mesh.its); | ||||
| 
 | ||||
|         Slic3r::simplify_mesh(its); | ||||
| 
 | ||||
|  | @ -108,10 +144,12 @@ std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &   mesh, | |||
|         for (stl_triangle_vertex_indices &ind : its.indices) | ||||
|             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 | ||||
|  | @ -273,12 +311,264 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices, | |||
|         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 = | ||||
|             Slic3r::sla::generate_interior(mesh); | ||||
|     InteriorPtr interior = generate_interior(mesh, cfg, JobController{}); | ||||
|     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(); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,6 +19,17 @@ struct HollowingConfig | |||
|     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 | ||||
| { | ||||
|     Vec3f pos; | ||||
|  | @ -60,11 +71,26 @@ using DrainHoles = std::vector<DrainHole>; | |||
| 
 | ||||
| constexpr float HoleStickOutLength = 1.f; | ||||
| 
 | ||||
| std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh, | ||||
| InteriorPtr generate_interior(const TriangleMesh &mesh, | ||||
|                               const HollowingConfig &  = {}, | ||||
|                               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, | ||||
|                     const std::vector<float> &slicegrid, | ||||
|  |  | |||
|  | @ -1120,7 +1120,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const | |||
|         return this->pad_mesh(); | ||||
|     case slaposDrillHoles: | ||||
|         if (m_hollowing_data) | ||||
|             return m_hollowing_data->hollow_mesh_with_holes; | ||||
|             return get_mesh_to_print(); | ||||
|         [[fallthrough]]; | ||||
|     default: | ||||
|         return TriangleMesh(); | ||||
|  | @ -1149,8 +1149,9 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const | |||
| 
 | ||||
| const TriangleMesh &SLAPrintObject::hollowed_interior_mesh() const | ||||
| { | ||||
|     if (m_hollowing_data && m_config.hollowing_enable.getBool()) | ||||
|         return m_hollowing_data->interior; | ||||
|     if (m_hollowing_data && m_hollowing_data->interior && | ||||
|         m_config.hollowing_enable.getBool()) | ||||
|         return sla::get_mesh(*m_hollowing_data->interior); | ||||
|      | ||||
|     return EMPTY_MESH; | ||||
| } | ||||
|  |  | |||
|  | @ -85,6 +85,10 @@ public: | |||
|     // Get the mesh that is going to be printed with all the modifications
 | ||||
|     // like hollowing and drilled holes.
 | ||||
|     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(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -328,8 +332,9 @@ private: | |||
|     { | ||||
|     public: | ||||
| 
 | ||||
|         TriangleMesh interior; | ||||
|         sla::InteriorPtr interior; | ||||
|         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; | ||||
|  |  | |||
|  | @ -1,3 +1,5 @@ | |||
| #include <unordered_set> | ||||
| 
 | ||||
| #include <libslic3r/Exception.hpp> | ||||
| #include <libslic3r/SLAPrintSteps.hpp> | ||||
| #include <libslic3r/MeshBoolean.hpp> | ||||
|  | @ -131,21 +133,150 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) | |||
|     double quality  = po.m_config.hollowing_quality.getFloat(); | ||||
|     double closing_d = po.m_config.hollowing_closing_distance.getFloat(); | ||||
|     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!"; | ||||
|     else { | ||||
|         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.
 | ||||
| void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | ||||
| { | ||||
|     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) { | ||||
|         // In this case we can dump any data that might have been
 | ||||
|  | @ -163,12 +294,18 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | |||
|     // holes that are no longer on the frontend.
 | ||||
|     TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes; | ||||
|     hollowed_mesh = po.transformed_mesh(); | ||||
|     if (! po.m_hollowing_data->interior.empty()) { | ||||
|         hollowed_mesh.merge(po.m_hollowing_data->interior); | ||||
|         hollowed_mesh.require_shared_vertices(); | ||||
|     } | ||||
|     if (is_hollowed) | ||||
|         sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); | ||||
| 
 | ||||
|     TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; | ||||
| 
 | ||||
|     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)."; | ||||
|         return; | ||||
|     } | ||||
|  | @ -196,6 +333,16 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | |||
|     try { | ||||
|         MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_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 &) { | ||||
|         throw Slic3r::SlicingError(L( | ||||
|             "Drilling holes into the mesh failed. " | ||||
|  | @ -213,7 +360,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) | |||
| // same imaginary grid (the height vector argument to TriangleMeshSlicer).
 | ||||
| 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...
 | ||||
| 
 | ||||
|  | @ -260,9 +407,15 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) | |||
|     auto &slice_grid = po.m_model_height_levels; | ||||
|     slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr); | ||||
| 
 | ||||
|     if (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()) { | ||||
|         po.m_hollowing_data->interior.repair(true); | ||||
|         TriangleMeshSlicer interior_slicer(&po.m_hollowing_data->interior); | ||||
|     sla::Interior *interior = po.m_hollowing_data ? | ||||
|                                   po.m_hollowing_data->interior.get() : | ||||
|                                   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; | ||||
|         interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); | ||||
| 
 | ||||
|  | @ -297,7 +450,7 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) | |||
|     // If supports are disabled, we can skip the model scan.
 | ||||
|     if(!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) | ||||
|         po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh)); | ||||
|  |  | |||
|  | @ -2063,4 +2063,22 @@ TriangleMesh make_sphere(double radius, double fa) | |||
| 	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; | ||||
| }; | ||||
| 
 | ||||
| // 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 {  | ||||
|     // A general case, the cutting plane intersect a face at two different edges.
 | ||||
|     feGeneral, | ||||
|  |  | |||
|  | @ -200,12 +200,20 @@ void HollowedMesh::on_update() | |||
|         if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { | ||||
|             size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).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()) { | ||||
|                     m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); | ||||
|                     Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse(); | ||||
|                     m_hollowed_mesh_transformed->transform(trafo_inv); | ||||
|                     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 | ||||
|                     m_hollowed_mesh_transformed.reset(nullptr); | ||||
|  | @ -230,6 +238,10 @@ const TriangleMesh* HollowedMesh::get_hollowed_mesh() const | |||
|     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_old_meshes = meshes; | ||||
| 
 | ||||
|         if (has_hollowed) | ||||
|             m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); | ||||
| 
 | ||||
|         m_active_inst_bb_radius = | ||||
|             mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); | ||||
|         //if (has_hollowed && m_clp_ratio != 0.)
 | ||||
|  |  | |||
|  | @ -199,6 +199,7 @@ public: | |||
| #endif // NDEBUG
 | ||||
| 
 | ||||
|     const TriangleMesh* get_hollowed_mesh() const; | ||||
|     const TriangleMesh* get_hollowed_interior() const; | ||||
| 
 | ||||
| protected: | ||||
|     void on_update() override; | ||||
|  | @ -206,6 +207,7 @@ protected: | |||
| 
 | ||||
| private: | ||||
|     std::unique_ptr<TriangleMesh> m_hollowed_mesh_transformed; | ||||
|     std::unique_ptr<TriangleMesh> m_hollowed_interior_transformed; | ||||
|     size_t m_old_hollowing_timestamp = 0; | ||||
|     int m_print_object_idx = -1; | ||||
|     int m_print_objects_count = 0; | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| #include "libslic3r/Tesselate.hpp" | ||||
| #include "libslic3r/TriangleMesh.hpp" | ||||
| #include "libslic3r/ClipperUtils.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) | ||||
|  | @ -74,6 +84,15 @@ void MeshClipper::recalculate_triangles() | |||
|     std::vector<ExPolygons> list_of_expolys; | ||||
|     m_tms->set_up_direction(up.cast<float>()); | ||||
|     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.); | ||||
| 
 | ||||
|     // Rotate the cut into world coords:
 | ||||
|  |  | |||
|  | @ -78,6 +78,8 @@ public: | |||
|     // must make sure that it stays valid.
 | ||||
|     void set_mesh(const TriangleMesh& mesh); | ||||
| 
 | ||||
|     void set_negative_mesh(const TriangleMesh &mesh); | ||||
| 
 | ||||
|     // Inform the MeshClipper about the transformation that transforms the mesh
 | ||||
|     // into world coordinates.
 | ||||
|     void set_transformation(const Geometry::Transformation& trafo); | ||||
|  | @ -91,6 +93,7 @@ private: | |||
| 
 | ||||
|     Geometry::Transformation m_trafo; | ||||
|     const TriangleMesh* m_mesh = nullptr; | ||||
|     const TriangleMesh* m_negative_mesh = nullptr; | ||||
|     ClippingPlane m_plane; | ||||
|     std::vector<Vec2f> m_triangles2d; | ||||
|     GLIndexedVertexArray m_vertex_array; | ||||
|  |  | |||
|  | @ -5363,7 +5363,7 @@ void Plater::export_stl(bool extended, bool selection_only) | |||
|                         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(inst_transform, is_left_handed); | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,45 +2,21 @@ | |||
| #include <fstream> | ||||
| #include <catch2/catch.hpp> | ||||
| 
 | ||||
| #include <libslic3r/TriangleMesh.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) | ||||
| #define PATH_SEPARATOR R"(\)" | ||||
| #else | ||||
| #define PATH_SEPARATOR R"(/)" | ||||
| #endif | ||||
|     sphere1.translate(-5.f, 0.f, 0.f); | ||||
|     sphere2.translate( 5.f, 0.f, 0.f); | ||||
| 
 | ||||
| static Slic3r::TriangleMesh load_model(const std::string &obj_filename) | ||||
| { | ||||
|     Slic3r::TriangleMesh mesh; | ||||
|     auto fpath = TEST_DATA_DIR PATH_SEPARATOR + obj_filename; | ||||
|     Slic3r::load_obj(fpath.c_str(), &mesh); | ||||
|     return mesh; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 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"); | ||||
|     sphere1.merge(sphere2); | ||||
|     sphere1.require_shared_vertices(); | ||||
| 
 | ||||
|     sla::hollow_mesh(sphere1, sla::HollowingConfig{}, sla::HollowingFlags::hfRemoveInsideTriangles); | ||||
| 
 | ||||
|     sphere1.WriteOBJFile("twospheres.obj"); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -88,9 +88,9 @@ void test_supports(const std::string          &obj_filename, | |||
|     REQUIRE_FALSE(mesh.empty()); | ||||
|      | ||||
|     if (hollowingcfg.enabled) { | ||||
|         auto inside = sla::generate_interior(mesh, hollowingcfg); | ||||
|         REQUIRE(inside); | ||||
|         mesh.merge(*inside); | ||||
|         sla::InteriorPtr interior = sla::generate_interior(mesh, hollowingcfg); | ||||
|         REQUIRE(interior); | ||||
|         mesh.merge(sla::get_mesh(*interior)); | ||||
|         mesh.require_shared_vertices(); | ||||
|     } | ||||
|      | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 tamasmeszaros
						tamasmeszaros