From 7c5e96a43a6b118af04cfda5f5f86a14046c8ca9 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 27 Apr 2023 14:28:59 +0800 Subject: [PATCH] NEW: add boolean in export stl Change-Id: Ibeab33f27ad7a2531bb256edd0e3f853cf8def7c (cherry picked from commit 6cfb4371a1b9053b54569d91a9c29af97a753c0e) --- .gitignore | 1 + src/libslic3r/AABBMesh.cpp | 322 ++++++++++++++++++ src/libslic3r/AABBMesh.hpp | 142 ++++++++ src/libslic3r/AnyPtr.hpp | 130 +++++++ src/libslic3r/CMakeLists.txt | 3 + src/libslic3r/CSGMesh/CSGMesh.hpp | 86 +++++ src/libslic3r/CSGMesh/CSGMeshCopy.hpp | 80 +++++ src/libslic3r/CSGMesh/ModelToCSGMesh.hpp | 88 +++++ .../CSGMesh/PerformCSGMeshBooleans.hpp | 205 +++++++++++ src/libslic3r/CSGMesh/SliceCSGMesh.hpp | 131 +++++++ src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp | 95 ++++++ src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp | 116 +++++++ src/libslic3r/I18N.hpp | 19 ++ src/libslic3r/MeshBoolean.cpp | 66 +++- src/libslic3r/MeshBoolean.hpp | 22 +- src/libslic3r/MeshSplitImpl.hpp | 18 +- src/libslic3r/SLAPrint.hpp | 5 + src/libslic3r/libslic3r.h | 52 ++- src/slic3r/CMakeLists.txt | 3 + src/slic3r/GUI/GUI_App.cpp | 10 +- src/slic3r/GUI/Plater.cpp | 204 ++++++----- 21 files changed, 1664 insertions(+), 134 deletions(-) create mode 100644 src/libslic3r/AABBMesh.cpp create mode 100644 src/libslic3r/AABBMesh.hpp create mode 100644 src/libslic3r/AnyPtr.hpp create mode 100644 src/libslic3r/CSGMesh/CSGMesh.hpp create mode 100644 src/libslic3r/CSGMesh/CSGMeshCopy.hpp create mode 100644 src/libslic3r/CSGMesh/ModelToCSGMesh.hpp create mode 100644 src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp create mode 100644 src/libslic3r/CSGMesh/SliceCSGMesh.hpp create mode 100644 src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp create mode 100644 src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp diff --git a/.gitignore b/.gitignore index 77ed8b50b..758650f5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ Build Build.bat /build/ +/build2022/ deps/build MYMETA.json MYMETA.yml diff --git a/src/libslic3r/AABBMesh.cpp b/src/libslic3r/AABBMesh.cpp new file mode 100644 index 000000000..ca7042c60 --- /dev/null +++ b/src/libslic3r/AABBMesh.cpp @@ -0,0 +1,322 @@ +#include "AABBMesh.hpp" +#include + +#include +#include + +#include + +#ifdef SLIC3R_HOLE_RAYCASTER +#include +#endif + +namespace Slic3r { + +class AABBMesh::AABBImpl { +private: + AABBTreeIndirect::Tree3f m_tree; + double m_triangle_ray_epsilon; + +public: + void init(const indexed_triangle_set &its, bool calculate_epsilon) + { + m_triangle_ray_epsilon = 0.000001; + if (calculate_epsilon) { + // Calculate epsilon from average triangle edge length. + double l = its_average_edge_length(its); + if (l > 0) + m_triangle_ray_epsilon = 0.000001 * l * l; + } + m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + its.vertices, its.indices); + } + + void intersect_ray(const indexed_triangle_set &its, + const Vec3d & s, + const Vec3d & dir, + igl::Hit & hit) + { + AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices, + m_tree, s, dir, hit, m_triangle_ray_epsilon); + } + + void intersect_ray(const indexed_triangle_set &its, + const Vec3d & s, + const Vec3d & dir, + std::vector & hits) + { + AABBTreeIndirect::intersect_ray_all_hits(its.vertices, its.indices, + m_tree, s, dir, hits, m_triangle_ray_epsilon); + } + + double squared_distance(const indexed_triangle_set & its, + const Vec3d & point, + int & i, + Eigen::Matrix &closest) + { + size_t idx_unsigned = 0; + Vec3d closest_vec3d(closest); + double dist = + AABBTreeIndirect::squared_distance_to_indexed_triangle_set( + its.vertices, its.indices, m_tree, point, idx_unsigned, + closest_vec3d); + i = int(idx_unsigned); + closest = closest_vec3d; + return dist; + } +}; + +template void AABBMesh::init(const M &mesh, bool calculate_epsilon) +{ + // Build the AABB accelaration tree + m_aabb->init(*m_tm, calculate_epsilon); +} + +AABBMesh::AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon) + : m_tm(&tmesh) + , m_aabb(new AABBImpl()) + , m_vfidx{tmesh} + , m_fnidx{its_face_neighbors(tmesh)} +{ + init(tmesh, calculate_epsilon); +} + +AABBMesh::AABBMesh(const TriangleMesh &mesh, bool calculate_epsilon) + : m_tm(&mesh.its) + , m_aabb(new AABBImpl()) + , m_vfidx{mesh.its} + , m_fnidx{its_face_neighbors(mesh.its)} +{ + init(mesh, calculate_epsilon); +} + +AABBMesh::~AABBMesh() {} + +AABBMesh::AABBMesh(const AABBMesh &other) + : m_tm(other.m_tm) + , m_aabb(new AABBImpl(*other.m_aabb)) + , m_vfidx{other.m_vfidx} + , m_fnidx{other.m_fnidx} +{} + +AABBMesh &AABBMesh::operator=(const AABBMesh &other) +{ + m_tm = other.m_tm; + m_aabb.reset(new AABBImpl(*other.m_aabb)); + m_vfidx = other.m_vfidx; + m_fnidx = other.m_fnidx; + + return *this; +} + +AABBMesh &AABBMesh::operator=(AABBMesh &&other) = default; + +AABBMesh::AABBMesh(AABBMesh &&other) = default; + + + +const std::vector& AABBMesh::vertices() const +{ + return m_tm->vertices; +} + + + +const std::vector& AABBMesh::indices() const +{ + return m_tm->indices; +} + + + +const Vec3f& AABBMesh::vertices(size_t idx) const +{ + return m_tm->vertices[idx]; +} + + + +const Vec3i& AABBMesh::indices(size_t idx) const +{ + return m_tm->indices[idx]; +} + + +Vec3d AABBMesh::normal_by_face_id(int face_id) const { + + return its_unnormalized_normal(*m_tm, face_id).cast().normalized(); +} + + +AABBMesh::hit_result +AABBMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const +{ + assert(is_approx(dir.norm(), 1.)); + igl::Hit hit{-1, -1, 0.f, 0.f, 0.f}; + hit.t = std::numeric_limits::infinity(); + +#ifdef SLIC3R_HOLE_RAYCASTER + if (! m_holes.empty()) { + + // If there are holes, the hit_results will be made by + // query_ray_hits (object) and filter_hits (holes): + return filter_hits(query_ray_hits(s, dir)); + } +#endif + + m_aabb->intersect_ray(*m_tm, s, dir, hit); + hit_result ret(*this); + ret.m_t = double(hit.t); + ret.m_dir = dir; + ret.m_source = s; + if(!std::isinf(hit.t) && !std::isnan(hit.t)) { + ret.m_normal = this->normal_by_face_id(hit.id); + ret.m_face_id = hit.id; + } + + return ret; +} + +std::vector +AABBMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const +{ + std::vector outs; + std::vector hits; + m_aabb->intersect_ray(*m_tm, s, dir, hits); + + // The sort is necessary, the hits are not always sorted. + std::sort(hits.begin(), hits.end(), + [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); + + // Remove duplicates. They sometimes appear, for example when the ray is cast + // along an axis of a cube due to floating-point approximations in igl (?) + hits.erase(std::unique(hits.begin(), hits.end(), + [](const igl::Hit& a, const igl::Hit& b) + { return a.t == b.t; }), + hits.end()); + + // Convert the igl::Hit into hit_result + outs.reserve(hits.size()); + for (const igl::Hit& hit : hits) { + outs.emplace_back(AABBMesh::hit_result(*this)); + outs.back().m_t = double(hit.t); + outs.back().m_dir = dir; + outs.back().m_source = s; + if(!std::isinf(hit.t) && !std::isnan(hit.t)) { + outs.back().m_normal = this->normal_by_face_id(hit.id); + outs.back().m_face_id = hit.id; + } + } + + return outs; +} + + +#ifdef SLIC3R_HOLE_RAYCASTER +AABBMesh::hit_result IndexedMesh::filter_hits( + const std::vector& object_hits) const +{ + assert(! m_holes.empty()); + hit_result out(*this); + + if (object_hits.empty()) + return out; + + const Vec3d& s = object_hits.front().source(); + const Vec3d& dir = object_hits.front().direction(); + + // A helper struct to save an intersetion with a hole + struct HoleHit { + HoleHit(float t_p, const Vec3d& normal_p, bool entry_p) : + t(t_p), normal(normal_p), entry(entry_p) {} + float t; + Vec3d normal; + bool entry; + }; + std::vector hole_isects; + hole_isects.reserve(m_holes.size()); + + auto sf = s.cast(); + auto dirf = dir.cast(); + + // Collect hits on all holes, preserve information about entry/exit + for (const sla::DrainHole& hole : m_holes) { + std::array, 2> isects; + if (hole.get_intersections(sf, dirf, isects)) { + // Ignore hole hits behind the source + if (isects[0].first > 0.f) hole_isects.emplace_back(isects[0].first, isects[0].second, true); + if (isects[1].first > 0.f) hole_isects.emplace_back(isects[1].first, isects[1].second, false); + } + } + + // Holes can intersect each other, sort the hits by t + std::sort(hole_isects.begin(), hole_isects.end(), + [](const HoleHit& a, const HoleHit& b) { return a.t < b.t; }); + + // Now inspect the intersections with object and holes, in the order of + // increasing distance. Keep track how deep are we nested in mesh/holes and + // pick the correct intersection. + // This needs to be done twice - first to find out how deep in the structure + // the source is, then to pick the correct intersection. + int hole_nested = 0; + int object_nested = 0; + for (int dry_run=1; dry_run>=0; --dry_run) { + hole_nested = -hole_nested; + object_nested = -object_nested; + + bool is_hole = false; + bool is_entry = false; + const HoleHit* next_hole_hit = hole_isects.empty() ? nullptr : &hole_isects.front(); + const hit_result* next_mesh_hit = &object_hits.front(); + + while (next_hole_hit || next_mesh_hit) { + if (next_hole_hit && next_mesh_hit) // still have hole and obj hits + is_hole = (next_hole_hit->t < next_mesh_hit->m_t); + else + is_hole = next_hole_hit; // one or the other ran out + + // Is this entry or exit hit? + is_entry = is_hole ? next_hole_hit->entry : ! next_mesh_hit->is_inside(); + + if (! dry_run) { + if (! is_hole && hole_nested == 0) { + // This is a valid object hit + return *next_mesh_hit; + } + if (is_hole && ! is_entry && object_nested != 0) { + // This holehit is the one we seek + out.m_t = next_hole_hit->t; + out.m_normal = next_hole_hit->normal; + out.m_source = s; + out.m_dir = dir; + return out; + } + } + + // Increase/decrease the counter + (is_hole ? hole_nested : object_nested) += (is_entry ? 1 : -1); + + // Advance the respective pointer + if (is_hole && next_hole_hit++ == &hole_isects.back()) + next_hole_hit = nullptr; + if (! is_hole && next_mesh_hit++ == &object_hits.back()) + next_mesh_hit = nullptr; + } + } + + // if we got here, the ray ended up in infinity + return out; +} +#endif + + +double AABBMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { + double sqdst = 0; + Eigen::Matrix pp = p; + Eigen::Matrix cc; + sqdst = m_aabb->squared_distance(*m_tm, pp, i, cc); + c = cc; + return sqdst; +} + +} // namespace Slic3r diff --git a/src/libslic3r/AABBMesh.hpp b/src/libslic3r/AABBMesh.hpp new file mode 100644 index 000000000..3ef25977b --- /dev/null +++ b/src/libslic3r/AABBMesh.hpp @@ -0,0 +1,142 @@ +#ifndef PRUSASLICER_AABBMESH_H +#define PRUSASLICER_AABBMESH_H + +#include +#include + +#include +#include + +// There is an implementation of a hole-aware raycaster that was eventually +// not used in production version. It is now hidden under following define +// for possible future use. +// #define SLIC3R_HOLE_RAYCASTER + +#ifdef SLIC3R_HOLE_RAYCASTER + #include "libslic3r/SLA/Hollowing.hpp" +#endif + +struct indexed_triangle_set; + +namespace Slic3r { + +class TriangleMesh; + +// An index-triangle structure coupled with an AABB index to support ray +// casting and other higher level operations. +class AABBMesh { + class AABBImpl; + + const indexed_triangle_set* m_tm; + + std::unique_ptr m_aabb; + VertexFaceIndex m_vfidx; // vertex-face index + std::vector m_fnidx; // face-neighbor index + +#ifdef SLIC3R_HOLE_RAYCASTER + // This holds a copy of holes in the mesh. Initialized externally + // by load_mesh setter. + std::vector m_holes; +#endif + + template void init(const M &mesh, bool calculate_epsilon); + +public: + + // calculate_epsilon ... calculate epsilon for triangle-ray intersection from an average triangle edge length. + // If set to false, a default epsilon is used, which works for "reasonable" meshes. + explicit AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon = false); + explicit AABBMesh(const TriangleMesh &mesh, bool calculate_epsilon = false); + + AABBMesh(const AABBMesh& other); + AABBMesh& operator=(const AABBMesh&); + + AABBMesh(AABBMesh &&other); + AABBMesh& operator=(AABBMesh &&other); + + ~AABBMesh(); + + const std::vector& vertices() const; + const std::vector& indices() const; + const Vec3f& vertices(size_t idx) const; + const Vec3i& indices(size_t idx) const; + + // Result of a raycast + class hit_result { + // m_t holds a distance from m_source to the intersection. + double m_t = infty(); + int m_face_id = -1; + const AABBMesh *m_mesh = nullptr; + Vec3d m_dir = Vec3d::Zero(); + Vec3d m_source = Vec3d::Zero(); + Vec3d m_normal = Vec3d::Zero(); + friend class AABBMesh; + + // A valid object of this class can only be obtained from + // IndexedMesh::query_ray_hit method. + explicit inline hit_result(const AABBMesh& em): m_mesh(&em) {} + public: + // This denotes no hit on the mesh. + static inline constexpr double infty() { return std::numeric_limits::infinity(); } + + explicit inline hit_result(double val = infty()) : m_t(val) {} + + inline double distance() const { return m_t; } + inline const Vec3d& direction() const { return m_dir; } + inline const Vec3d& source() const { return m_source; } + inline Vec3d position() const { return m_source + m_dir * m_t; } + inline int face() const { return m_face_id; } + inline bool is_valid() const { return m_mesh != nullptr; } + inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); } + + inline const Vec3d& normal() const { + assert(is_valid()); + return m_normal; + } + + inline bool is_inside() const { + return is_hit() && normal().dot(m_dir) > 0; + } + }; + +#ifdef SLIC3R_HOLE_RAYCASTER + // Inform the object about location of holes + // creates internal copy of the vector + void load_holes(const std::vector& holes) { + m_holes = holes; + } + + // Iterates over hits and holes and returns the true hit, possibly + // on the inside of a hole. + // This function is currently not used anywhere, it was written when the + // holes were subtracted on slices, that is, before we started using CGAL + // to actually cut the holes into the mesh. + hit_result filter_hits(const std::vector& obj_hits) const; +#endif + + // Casting a ray on the mesh, returns the distance where the hit occures. + hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const; + + // Casts a ray on the mesh and returns all hits + std::vector query_ray_hits(const Vec3d &s, const Vec3d &dir) const; + + double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; + inline double squared_distance(const Vec3d &p) const + { + int i; + Vec3d c; + return squared_distance(p, i, c); + } + + Vec3d normal_by_face_id(int face_id) const; + + const indexed_triangle_set * get_triangle_mesh() const { return m_tm; } + + const VertexFaceIndex &vertex_face_index() const { return m_vfidx; } + const std::vector &face_neighbor_index() const { return m_fnidx; } +}; + + +} // namespace Slic3r::sla + +#endif // INDEXEDMESH_H diff --git a/src/libslic3r/AnyPtr.hpp b/src/libslic3r/AnyPtr.hpp new file mode 100644 index 000000000..c40d10093 --- /dev/null +++ b/src/libslic3r/AnyPtr.hpp @@ -0,0 +1,130 @@ +#ifndef ANYPTR_HPP +#define ANYPTR_HPP + +#include +#include +#include + +namespace Slic3r { + +// A general purpose pointer holder that can hold any type of smart pointer +// or raw pointer which can own or not own any object they point to. +// In case a raw pointer is stored, it is not destructed so ownership is +// assumed to be foreign. +// +// The stored pointer is not checked for being null when dereferenced. +// +// This is a movable only object due to the fact that it can possibly hold +// a unique_ptr which a non-copy. +template +class AnyPtr { + enum { RawPtr, UPtr, ShPtr, WkPtr }; + + boost::variant, std::shared_ptr, std::weak_ptr> ptr; + + template static T *get_ptr(Self &&s) + { + switch (s.ptr.which()) { + case RawPtr: return boost::get(s.ptr); + case UPtr: return boost::get>(s.ptr).get(); + case ShPtr: return boost::get>(s.ptr).get(); + case WkPtr: { + auto shptr = boost::get>(s.ptr).lock(); + return shptr.get(); + } + } + + return nullptr; + } + +public: + template>> + AnyPtr(TT *p = nullptr) : ptr{p} + {} + template>> + AnyPtr(std::unique_ptr p) : ptr{std::unique_ptr(std::move(p))} + {} + template>> + AnyPtr(std::shared_ptr p) : ptr{std::shared_ptr(std::move(p))} + {} + template>> + AnyPtr(std::weak_ptr p) : ptr{std::weak_ptr(std::move(p))} + {} + + ~AnyPtr() = default; + + AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {} + AnyPtr(const AnyPtr &other) = delete; + + AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; } + AnyPtr &operator=(const AnyPtr &other) = delete; + + template>> + AnyPtr &operator=(TT *p) { ptr = p; return *this; } + + template>> + AnyPtr &operator=(std::unique_ptr p) { ptr = std::move(p); return *this; } + + template>> + AnyPtr &operator=(std::shared_ptr p) { ptr = p; return *this; } + + template>> + AnyPtr &operator=(std::weak_ptr p) { ptr = std::move(p); return *this; } + + const T &operator*() const { return *get_ptr(*this); } + T &operator*() { return *get_ptr(*this); } + + T *operator->() { return get_ptr(*this); } + const T *operator->() const { return get_ptr(*this); } + + T *get() { return get_ptr(*this); } + const T *get() const { return get_ptr(*this); } + + operator bool() const + { + switch (ptr.which()) { + case RawPtr: return bool(boost::get(ptr)); + case UPtr: return bool(boost::get>(ptr)); + case ShPtr: return bool(boost::get>(ptr)); + case WkPtr: { + auto shptr = boost::get>(ptr).lock(); + return bool(shptr); + } + } + + return false; + } + + // If the stored pointer is a shared or weak pointer, returns a reference + // counted copy. Empty shared pointer is returned otherwise. + std::shared_ptr get_shared_cpy() const + { + std::shared_ptr ret; + + switch (ptr.which()) { + case ShPtr: ret = boost::get>(ptr); break; + case WkPtr: ret = boost::get>(ptr).lock(); break; + default: + ; + } + + return ret; + } + + // If the underlying pointer is unique, convert to shared pointer + void convert_unique_to_shared() + { + if (ptr.which() == UPtr) + ptr = std::shared_ptr{std::move(boost::get>(ptr))}; + } + + // Returns true if the data is owned by this AnyPtr instance + bool is_owned() const noexcept + { + return ptr.which() == UPtr || ptr.which() == ShPtr; + } +}; + +} // namespace Slic3r + +#endif // ANYPTR_HPP diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2f3b9b90e..09987d96c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -24,6 +24,9 @@ set(lisbslic3r_sources pchheader.hpp AABBTreeIndirect.hpp AABBTreeLines.hpp + AABBMesh.hpp + AABBMesh.cpp + AnyPtr.hpp BoundingBox.cpp BoundingBox.hpp BridgeDetector.cpp diff --git a/src/libslic3r/CSGMesh/CSGMesh.hpp b/src/libslic3r/CSGMesh/CSGMesh.hpp new file mode 100644 index 000000000..d14ed7659 --- /dev/null +++ b/src/libslic3r/CSGMesh/CSGMesh.hpp @@ -0,0 +1,86 @@ +#ifndef CSGMESH_HPP +#define CSGMESH_HPP + +#include +#include + +namespace Slic3r { namespace csg { + +// A CSGPartT should be an object that can provide at least a mesh + trafo and an +// associated csg operation. A collection of CSGPartT objects can then +// be interpreted as one model and used in various contexts. It can be assembled +// with CGAL or OpenVDB, rendered with OpenCSG or provided to a ray-tracer to +// deal with various parts of it according to the supported CSG types... +// +// A few simple templated interface functions are provided here and a default +// CSGPart class that implements the necessary means to be usable as a +// CSGPartT object. + +// Supported CSG operation types +enum class CSGType { Union, Difference, Intersection }; + +// A CSG part can instruct the processing to push the sub-result until a new +// csg part with a pop instruction appears. This can be used to implement +// parentheses in a CSG expression represented by the collection of csg parts. +// A CSG part can not contain another CSG collection, only a mesh, this is why +// its easier to do this stacking instead of recursion in the data definition. +// CSGStackOp::Continue means no stack operation required. +// When a CSG part contains a Push instruction, it is expected that the CSG +// operation it contains refers to the whole collection spanning to the nearest +// part with a Pop instruction. +// e.g.: +// { +// CUBE1: { mesh: cube, op: Union, stack op: Continue }, +// CUBE2: { mesh: cube, op: Difference, stack op: Push}, +// CUBE3: { mesh: cube, op: Union, stack op: Pop} +// } +// is a collection of csg parts representing the expression CUBE1 - (CUBE2 + CUBE3) +enum class CSGStackOp { Push, Continue, Pop }; + +// Get the CSG operation of the part. Can be overriden for any type +template CSGType get_operation(const CSGPartT &part) +{ + return part.operation; +} + +// Get the stack operation required by the CSG part. +template CSGStackOp get_stack_operation(const CSGPartT &part) +{ + return part.stack_operation; +} + +// Get the mesh for the part. Can be overriden for any type +template +const indexed_triangle_set *get_mesh(const CSGPartT &part) +{ + return part.its_ptr.get(); +} + +// Get the transformation associated with the mesh inside a CSGPartT object. +// Can be overriden for any type. +template +Transform3f get_transform(const CSGPartT &part) +{ + return part.trafo; +} + +// Default implementation +struct CSGPart { + AnyPtr its_ptr; + Transform3f trafo; + CSGType operation; + CSGStackOp stack_operation; + + CSGPart(AnyPtr ptr = {}, + CSGType op = CSGType::Union, + const Transform3f &tr = Transform3f::Identity()) + : its_ptr{std::move(ptr)} + , operation{op} + , stack_operation{CSGStackOp::Continue} + , trafo{tr} + {} +}; + +}} // namespace Slic3r::csg + +#endif // CSGMESH_HPP diff --git a/src/libslic3r/CSGMesh/CSGMeshCopy.hpp b/src/libslic3r/CSGMesh/CSGMeshCopy.hpp new file mode 100644 index 000000000..78800f9bb --- /dev/null +++ b/src/libslic3r/CSGMesh/CSGMeshCopy.hpp @@ -0,0 +1,80 @@ +#ifndef CSGMESHCOPY_HPP +#define CSGMESHCOPY_HPP + +#include "CSGMesh.hpp" + +namespace Slic3r { namespace csg { + +// Copy a csg range but for the meshes, only copy the pointers. If the copy +// is made from a CSGPart compatible object, and the pointer is a shared one, +// it will be copied with reference counting. +template +void copy_csgrange_shallow(const Range &csgrange, OutIt out) +{ + for (const auto &part : csgrange) { + CSGPart cpy{{}, + get_operation(part), + get_transform(part)}; + + cpy.stack_operation = get_stack_operation(part); + + if constexpr (std::is_convertible_v) { + if (auto shptr = part.its_ptr.get_shared_cpy()) { + cpy.its_ptr = shptr; + } + } + + if (!cpy.its_ptr) + cpy.its_ptr = AnyPtr{get_mesh(part)}; + + *out = std::move(cpy); + ++out; + } +} + +// Copy the csg range, allocating new meshes +template +void copy_csgrange_deep(const Range &csgrange, OutIt out) +{ + for (const auto &part : csgrange) { + + CSGPart cpy{{}, get_operation(part), get_transform(part)}; + + if (auto meshptr = get_mesh(part)) { + cpy.its_ptr = std::make_unique(*meshptr); + } + + cpy.stack_operation = get_stack_operation(part); + + *out = std::move(cpy); + ++out; + } +} + +template +bool is_same(const Range &A, const Range &B) +{ + bool ret = true; + + size_t s = A.size(); + + if (B.size() != s) + ret = false; + + size_t i = 0; + auto itA = A.begin(); + auto itB = B.begin(); + for (; ret && i < s; ++itA, ++itB, ++i) { + ret = ret && + get_mesh(*itA) == get_mesh(*itB) && + get_operation(*itA) == get_operation(*itB) && + get_stack_operation(*itA) == get_stack_operation(*itB) && + get_transform(*itA).isApprox(get_transform(*itB)); + } + + return ret; +} + +}} // namespace Slic3r::csg + +#endif // CSGCOPY_HPP diff --git a/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp new file mode 100644 index 000000000..b0d32710e --- /dev/null +++ b/src/libslic3r/CSGMesh/ModelToCSGMesh.hpp @@ -0,0 +1,88 @@ +#ifndef MODELTOCSGMESH_HPP +#define MODELTOCSGMESH_HPP + +#include "CSGMesh.hpp" + +#include "libslic3r/Model.hpp" +#include "libslic3r/SLA/Hollowing.hpp" +#include "libslic3r/MeshSplitImpl.hpp" + +namespace Slic3r { namespace csg { + +// Flags to select which parts to export from Model into a csg part collection. +// These flags can be chained with the | operator +enum ModelParts { + mpartsPositive = 1, // Include positive parts + mpartsNegative = 2, // Include negative parts + mpartsDrillHoles = 4, // Include drill holes + mpartsDoSplits = 8, // Split each splitable mesh and export as a union of csg parts +}; + +template +void model_to_csgmesh(const ModelObject &mo, + const Transform3d &trafo, // Applies to all exported parts + OutIt out, // Output iterator + // values of ModelParts OR-ed + int parts_to_include = mpartsPositive + ) +{ + bool do_positives = parts_to_include & mpartsPositive; + bool do_negatives = parts_to_include & mpartsNegative; + bool do_drillholes = parts_to_include & mpartsDrillHoles; + bool do_splits = parts_to_include & mpartsDoSplits; + + for (const ModelVolume *vol : mo.volumes) { + if (vol && vol->mesh_ptr() && + ((do_positives && vol->is_model_part()) || + (do_negatives && vol->is_negative_volume()))) { + + if (do_splits && its_is_splittable(vol->mesh().its)) { + CSGPart part_begin{{}, vol->is_model_part() ? CSGType::Union : CSGType::Difference}; + part_begin.stack_operation = CSGStackOp::Push; + *out = std::move(part_begin); + ++out; + + its_split(vol->mesh().its, SplitOutputFn{[&out, &vol, &trafo](indexed_triangle_set &&its) { + if (its.empty()) + return; + + CSGPart part{std::make_unique(std::move(its)), + CSGType::Union, + (trafo * vol->get_matrix()).cast()}; + + *out = std::move(part); + ++out; + }}); + + CSGPart part_end{{}}; + part_end.stack_operation = CSGStackOp::Pop; + *out = std::move(part_end); + ++out; + } else { + CSGPart part{&(vol->mesh().its), + vol->is_model_part() ? CSGType::Union : CSGType::Difference, + (trafo * vol->get_matrix()).cast()}; + + *out = std::move(part); + ++out; + } + } + } + + //if (do_drillholes) { + // sla::DrainHoles drainholes = sla::transformed_drainhole_points(mo, trafo); + + // for (const sla::DrainHole &dhole : drainholes) { + // CSGPart part{std::make_unique( + // dhole.to_mesh()), + // CSGType::Difference}; + + // *out = std::move(part); + // ++out; + // } + //} +} + +}} // namespace Slic3r::csg + +#endif // MODELTOCSGMESH_HPP diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp new file mode 100644 index 000000000..dd98e431a --- /dev/null +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -0,0 +1,205 @@ +#ifndef PERFORMCSGMESHBOOLEANS_HPP +#define PERFORMCSGMESHBOOLEANS_HPP + +#include +#include + +#include "CSGMesh.hpp" + +#include "libslic3r/Execution/ExecutionTBB.hpp" +//#include "libslic3r/Execution/ExecutionSeq.hpp" +#include "libslic3r/MeshBoolean.hpp" + +namespace Slic3r { namespace csg { + +// This method can be overriden when a specific CSGPart type supports caching +// of the voxel grid +template +MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart) +{ + const indexed_triangle_set *its = csg::get_mesh(csgpart); + indexed_triangle_set dummy; + + if (!its) + its = &dummy; + + MeshBoolean::cgal::CGALMeshPtr ret; + + indexed_triangle_set m = *its; + its_transform(m, get_transform(csgpart), true); + + try { + ret = MeshBoolean::cgal::triangle_mesh_to_cgal(m); + } catch (...) { + // errors are ignored, simply return null + ret = nullptr; + } + + return ret; +} + + +namespace detail_cgal { + +using MeshBoolean::cgal::CGALMeshPtr; + +inline void perform_csg(CSGType op, CGALMeshPtr &dst, CGALMeshPtr &src) +{ + if (!dst && op == CSGType::Union && src) { + dst = std::move(src); + return; + } + + if (!dst || !src) + return; + + switch (op) { + case CSGType::Union: + MeshBoolean::cgal::plus(*dst, *src); + break; + case CSGType::Difference: + MeshBoolean::cgal::minus(*dst, *src); + break; + case CSGType::Intersection: + MeshBoolean::cgal::intersect(*dst, *src); + break; + } +} + +template +std::vector get_cgalptrs(Ex policy, const Range &csgrange) +{ + std::vector ret(csgrange.size()); + execution::for_each(policy, size_t(0), csgrange.size(), + [&csgrange, &ret](size_t i) { + auto it = csgrange.begin(); + std::advance(it, i); + auto &csgpart = *it; + ret[i] = get_cgalmesh(csgpart); + }); + + return ret; +} + +} // namespace detail + +// Process the sequence of CSG parts with CGAL. +template +void perform_csgmesh_booleans_cgal(MeshBoolean::cgal::CGALMeshPtr &cgalm, + const Range &csgrange) +{ + using MeshBoolean::cgal::CGALMesh; + using MeshBoolean::cgal::CGALMeshPtr; + using namespace detail_cgal; + + struct Frame { + CSGType op; CGALMeshPtr cgalptr; + explicit Frame(CSGType csgop = CSGType::Union) + : op{ csgop } + , cgalptr{ MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{}) } + {} + }; + + std::stack opstack{ std::vector{} }; + + opstack.push(Frame{}); + + std::vector cgalmeshes = get_cgalptrs(ex_tbb, csgrange); + + size_t csgidx = 0; + for (auto& csgpart : csgrange) { + + auto op = get_operation(csgpart); + CGALMeshPtr& cgalptr = cgalmeshes[csgidx++]; + + if (get_stack_operation(csgpart) == CSGStackOp::Push) { + opstack.push(Frame{ op }); + op = CSGType::Union; + } + + Frame* top = &opstack.top(); + + perform_csg(get_operation(csgpart), top->cgalptr, cgalptr); + + if (get_stack_operation(csgpart) == CSGStackOp::Pop) { + CGALMeshPtr src = std::move(top->cgalptr); + auto popop = opstack.top().op; + opstack.pop(); + CGALMeshPtr& dst = opstack.top().cgalptr; + perform_csg(popop, dst, src); + } + } + + cgalm = std::move(opstack.top().cgalptr); +} + +template +It check_csgmesh_booleans(const Range &csgrange, Visitor &&vfn) +{ + using namespace detail_cgal; + + std::vector cgalmeshes(csgrange.size()); + auto check_part = [&csgrange, &cgalmeshes](size_t i) + { + auto it = csgrange.begin(); + std::advance(it, i); + auto &csgpart = *it; + auto m = get_cgalmesh(csgpart); + + // mesh can be nullptr if this is a stack push or pull + if (!get_mesh(csgpart) && get_stack_operation(csgpart) != CSGStackOp::Continue) { + cgalmeshes[i] = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{}); + return; + } + + try { + if (!m || MeshBoolean::cgal::empty(*m)) + return; + + if (!MeshBoolean::cgal::does_bound_a_volume(*m)) + return; + + if (MeshBoolean::cgal::does_self_intersect(*m)) + return; + } + catch (...) { return; } + + cgalmeshes[i] = std::move(m); + }; + execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part); + + It ret = csgrange.end(); + for (size_t i = 0; i < csgrange.size(); ++i) { + if (!cgalmeshes[i]) { + auto it = csgrange.begin(); + std::advance(it, i); + vfn(it); + + if (ret == csgrange.end()) + ret = it; + } + } + + return ret; +} + +template +It check_csgmesh_booleans(const Range &csgrange) +{ + return check_csgmesh_booleans(csgrange, [](auto &) {}); +} + +template +MeshBoolean::cgal::CGALMeshPtr perform_csgmesh_booleans(const Range &csgparts) +{ + auto ret = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{}); + if (ret) { + perform_csgmesh_booleans_cgal(ret, csgparts); + } + return ret; +} + +} // namespace csg +} // namespace Slic3r + +#endif // PERFORMCSGMESHBOOLEANS_HPP diff --git a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp new file mode 100644 index 000000000..9d7b9a077 --- /dev/null +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -0,0 +1,131 @@ +#ifndef SLICECSGMESH_HPP +#define SLICECSGMESH_HPP + +#include "CSGMesh.hpp" + +#include + +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" + +namespace Slic3r { namespace csg { + +namespace detail { + +inline void merge_slices(csg::CSGType op, size_t i, + std::vector &target, + std::vector &source) +{ + switch(op) { + case CSGType::Union: + for (ExPolygon &expoly : source[i]) + target[i].emplace_back(std::move(expoly)); + break; + case CSGType::Difference: + target[i] = diff_ex(target[i], source[i]); + break; + case CSGType::Intersection: + target[i] = intersection_ex(target[i], source[i]); + break; + } +} + +inline void collect_nonempty_indices(csg::CSGType op, + const std::vector &slicegrid, + const std::vector &slices, + std::vector &indices) +{ + indices.clear(); + for (size_t i = 0; i < slicegrid.size(); ++i) { + if (op == CSGType::Intersection || !slices[i].empty()) + indices.emplace_back(i); + } +} + +} // namespace detail + +template +std::vector slice_csgmesh_ex( + const Range &csgrange, + const std::vector &slicegrid, + const MeshSlicingParamsEx ¶ms, + const std::function &throw_on_cancel = [] {}) +{ + using namespace detail; + + struct Frame { CSGType op; std::vector slices; }; + + std::stack opstack{std::vector{}}; + + MeshSlicingParamsEx params_cpy = params; + auto trafo = params.trafo; + auto nonempty_indices = reserve_vector(slicegrid.size()); + + opstack.push({CSGType::Union, std::vector(slicegrid.size())}); + + for (const auto &csgpart : csgrange) { + const indexed_triangle_set *its = csg::get_mesh(csgpart); + + auto op = get_operation(csgpart); + + if (get_stack_operation(csgpart) == CSGStackOp::Push) { + opstack.push({op, std::vector(slicegrid.size())}); + op = CSGType::Union; + } + + Frame *top = &opstack.top(); + + if (its) { + params_cpy.trafo = trafo * csg::get_transform(csgpart).template cast(); + std::vector slices = slice_mesh_ex(*its, + slicegrid, params_cpy, + throw_on_cancel); + + assert(slices.size() == slicegrid.size()); + + collect_nonempty_indices(op, slicegrid, slices, nonempty_indices); + + execution::for_each( + ex_tbb, nonempty_indices.begin(), nonempty_indices.end(), + [op, &slices, &top](size_t i) { + merge_slices(op, i, top->slices, slices); + }, execution::max_concurrency(ex_tbb)); + } + + if (get_stack_operation(csgpart) == CSGStackOp::Pop) { + std::vector popslices = std::move(top->slices); + auto popop = opstack.top().op; + opstack.pop(); + std::vector &prev_slices = opstack.top().slices; + + collect_nonempty_indices(popop, slicegrid, popslices, nonempty_indices); + + execution::for_each( + ex_tbb, nonempty_indices.begin(), nonempty_indices.end(), + [&popslices, &prev_slices, popop](size_t i) { + merge_slices(popop, i, prev_slices, popslices); + }, execution::max_concurrency(ex_tbb)); + } + } + + std::vector ret = std::move(opstack.top().slices); + + // TODO: verify if this part can be omitted or not. + execution::for_each(ex_tbb, ret.begin(), ret.end(), [](ExPolygons &slice) { + auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){ + return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON); + }); + + // Hopefully, ExPolygons are moved, not copied to new positions + // and that is cheap for expolygons + slice.erase(it, slice.end()); + slice = union_ex(slice); + }, execution::max_concurrency(ex_tbb)); + + return ret; +} + +}} // namespace Slic3r::csg + +#endif // SLICECSGMESH_HPP diff --git a/src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp b/src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp new file mode 100644 index 000000000..81b05b046 --- /dev/null +++ b/src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp @@ -0,0 +1,95 @@ +#ifndef TRIANGLEMESHADAPTER_HPP +#define TRIANGLEMESHADAPTER_HPP + +#include "CSGMesh.hpp" + +#include "libslic3r/TriangleMesh.hpp" + +namespace Slic3r { namespace csg { + +// Provide default overloads for indexed_triangle_set to be usable as a plain +// CSGPart with an implicit union operation + +inline CSGType get_operation(const indexed_triangle_set &part) +{ + return CSGType::Union; +} + +inline CSGStackOp get_stack_operation(const indexed_triangle_set &part) +{ + return CSGStackOp::Continue; +} + +inline const indexed_triangle_set * get_mesh(const indexed_triangle_set &part) +{ + return ∂ +} + +inline Transform3f get_transform(const indexed_triangle_set &part) +{ + return Transform3f::Identity(); +} + +inline CSGType get_operation(const indexed_triangle_set *const part) +{ + return CSGType::Union; +} + +inline CSGStackOp get_stack_operation(const indexed_triangle_set *const part) +{ + return CSGStackOp::Continue; +} + +inline const indexed_triangle_set * get_mesh(const indexed_triangle_set *const part) +{ + return part; +} + +inline Transform3f get_transform(const indexed_triangle_set *const part) +{ + return Transform3f::Identity(); +} + +inline CSGType get_operation(const TriangleMesh &part) +{ + return CSGType::Union; +} + +inline CSGStackOp get_stack_operation(const TriangleMesh &part) +{ + return CSGStackOp::Continue; +} + +inline const indexed_triangle_set * get_mesh(const TriangleMesh &part) +{ + return &part.its; +} + +inline Transform3f get_transform(const TriangleMesh &part) +{ + return Transform3f::Identity(); +} + +inline CSGType get_operation(const TriangleMesh * const part) +{ + return CSGType::Union; +} + +inline CSGStackOp get_stack_operation(const TriangleMesh * const part) +{ + return CSGStackOp::Continue; +} + +inline const indexed_triangle_set * get_mesh(const TriangleMesh * const part) +{ + return &part->its; +} + +inline Transform3f get_transform(const TriangleMesh * const part) +{ + return Transform3f::Identity(); +} + +}} // namespace Slic3r::csg + +#endif // TRIANGLEMESHADAPTER_HPP diff --git a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp new file mode 100644 index 000000000..f64d17b9a --- /dev/null +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -0,0 +1,116 @@ +#ifndef VOXELIZECSGMESH_HPP +#define VOXELIZECSGMESH_HPP + +#include +#include + +#include "CSGMesh.hpp" +#include "libslic3r/OpenVDBUtils.hpp" +#include "libslic3r/Execution/ExecutionTBB.hpp" + +namespace Slic3r { namespace csg { + +using VoxelizeParams = MeshToGridParams; + +// This method can be overriden when a specific CSGPart type supports caching +// of the voxel grid +template +VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, VoxelizeParams params) +{ + const indexed_triangle_set *its = csg::get_mesh(csgpart); + VoxelGridPtr ret; + + params.trafo(params.trafo() * csg::get_transform(csgpart)); + + if (its) + ret = mesh_to_grid(*its, params); + + return ret; +} + +namespace detail { + +inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src) +{ + if (!dst || !src) + return; + + switch (op) { + case CSGType::Union: + if (is_grid_empty(*dst) && !is_grid_empty(*src)) + dst = clone(*src); + else + grid_union(*dst, *src); + + break; + case CSGType::Difference: + grid_difference(*dst, *src); + break; + case CSGType::Intersection: + grid_intersection(*dst, *src); + break; + } +} + +} // namespace detail + +template +VoxelGridPtr voxelize_csgmesh(const Range &csgrange, + const VoxelizeParams ¶ms = {}) +{ + using namespace detail; + + VoxelGridPtr ret; + + std::vector grids (csgrange.size()); + + execution::for_each(ex_tbb, size_t(0), csgrange.size(), [&](size_t csgidx) { + if (params.statusfn() && params.statusfn()(-1)) + return; + + auto it = csgrange.begin(); + std::advance(it, csgidx); + auto &csgpart = *it; + grids[csgidx] = get_voxelgrid(csgpart, params); + }, execution::max_concurrency(ex_tbb)); + + size_t csgidx = 0; + struct Frame { CSGType op = CSGType::Union; VoxelGridPtr grid; }; + std::stack opstack{std::vector{}}; + + opstack.push({CSGType::Union, mesh_to_grid({}, params)}); + + for (auto &csgpart : csgrange) { + if (params.statusfn() && params.statusfn()(-1)) + break; + + auto &partgrid = grids[csgidx++]; + + auto op = get_operation(csgpart); + + if (get_stack_operation(csgpart) == CSGStackOp::Push) { + opstack.push({op, mesh_to_grid({}, params)}); + op = CSGType::Union; + } + + Frame *top = &opstack.top(); + + perform_csg(get_operation(csgpart), top->grid, partgrid); + + if (get_stack_operation(csgpart) == CSGStackOp::Pop) { + VoxelGridPtr popgrid = std::move(top->grid); + auto popop = opstack.top().op; + opstack.pop(); + VoxelGridPtr &grid = opstack.top().grid; + perform_csg(popop, grid, popgrid); + } + } + + ret = std::move(opstack.top().grid); + + return ret; +} + +}} // namespace Slic3r::csg + +#endif // VOXELIZECSGMESH_HPP diff --git a/src/libslic3r/I18N.hpp b/src/libslic3r/I18N.hpp index db4fd22df..5d2068a3d 100644 --- a/src/libslic3r/I18N.hpp +++ b/src/libslic3r/I18N.hpp @@ -3,6 +3,12 @@ #include +#ifdef SLIC3R_CURRENTLY_COMPILING_GUI_MODULE + #ifndef SLIC3R_ALLOW_LIBSLIC3R_I18N_IN_SLIC3R + #error You included libslic3r/I18N.hpp into a file belonging to slic3r module. + #endif +#endif + namespace Slic3r { namespace I18N { @@ -15,4 +21,17 @@ namespace I18N { } // namespace Slic3r +// When this is included from slic3r, better do not define the translation functions. +// Macros from slic3r/GUI/I18N.hpp should be used there. +#ifndef SLIC3R_CURRENTLY_COMPILING_GUI_MODULE + #ifdef L + #error L macro is defined where it shouldn't be. Didn't you include slic3r/GUI/I18N.hpp in libslic3r by mistake? + #endif + namespace { + [[maybe_unused]] const char* L(const char* s) { return s; } + [[maybe_unused]] const char* L_CONTEXT(const char* s, const char* context) { return s; } + [[maybe_unused]] std::string _u8L(const char* s) { return Slic3r::I18N::translate(s); } + } +#endif + #endif /* slic3r_I18N_hpp_ */ diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 7e3e57dba..6bf60f105 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -114,6 +115,11 @@ struct CGALMesh { CGALMesh(const _EpicMesh& _m) :m(_m) {} }; +void save_CGALMesh(const std::string& fname, const CGALMesh& cgal_mesh) { + std::ofstream os(fname); + os << cgal_mesh.m; + os.close(); +} // ///////////////////////////////////////////////////////////////////////////// // Converions from and to CGAL mesh // ///////////////////////////////////////////////////////////////////////////// @@ -179,7 +185,8 @@ inline Vec3f to_vec3f(const _EpecMesh::Point& v) return { float(iv.x()), float(iv.y()), float(iv.z()) }; } -template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) +template +indexed_triangle_set cgal_to_indexed_triangle_set(const _Mesh &cgalmesh) { indexed_triangle_set its; its.vertices.reserve(cgalmesh.num_vertices()); @@ -209,7 +216,7 @@ template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) its.indices.emplace_back(facet); } - return TriangleMesh(std::move(its)); + return its; } std::unique_ptr @@ -223,7 +230,12 @@ triangle_mesh_to_cgal(const std::vector &V, TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh) { - return cgal_to_triangle_mesh(cgalmesh.m); + return TriangleMesh{cgal_to_indexed_triangle_set(cgalmesh.m)}; +} + +indexed_triangle_set cgal_to_indexed_triangle_set(const CGALMesh &cgalmesh) +{ + return cgal_to_indexed_triangle_set(cgalmesh.m); } // ///////////////////////////////////////////////////////////////////////////// @@ -303,10 +315,7 @@ void segment(CGALMesh& src, std::vector& dst, double smoothing_alpha = _EpicMesh out; CGAL::copy_face_graph(segment_mesh, out); - //std::ostringstream oss; - //oss << "Segment_" << id << ".off"; - //std::ofstream os(oss.str().data()); - //os << out; + // save_CGALMesh("out.off", out); // fill holes typedef boost::graph_traits<_EpicMesh>::halfedge_descriptor halfedge_descriptor; @@ -382,9 +391,17 @@ TriangleMesh merge(std::vector meshes) return cgal_to_triangle_mesh(dst); } -// ///////////////////////////////////////////////////////////////////////////// -// Now the public functions for TriangleMesh input: -// ///////////////////////////////////////////////////////////////////////////// +template void _mesh_boolean_do(Op &&op, indexed_triangle_set &A, const indexed_triangle_set &B) +{ + CGALMesh meshA; + CGALMesh meshB; + triangle_mesh_to_cgal(A.vertices, A.indices, meshA.m); + triangle_mesh_to_cgal(B.vertices, B.indices, meshB.m); + + _cgal_do(op, meshA, meshB); + + A = cgal_to_indexed_triangle_set(meshA.m); +} template void _mesh_boolean_do(Op &&op, TriangleMesh &A, const TriangleMesh &B) { @@ -392,10 +409,10 @@ template void _mesh_boolean_do(Op &&op, TriangleMesh &A, const Triangl CGALMesh meshB; triangle_mesh_to_cgal(A.its.vertices, A.its.indices, meshA.m); triangle_mesh_to_cgal(B.its.vertices, B.its.indices, meshB.m); - + _cgal_do(op, meshA, meshB); - - A = cgal_to_triangle_mesh(meshA.m); + + A = cgal_to_triangle_mesh(meshA); } void minus(TriangleMesh &A, const TriangleMesh &B) @@ -413,6 +430,21 @@ void intersect(TriangleMesh &A, const TriangleMesh &B) _mesh_boolean_do(_cgal_intersection, A, B); } +void minus(indexed_triangle_set &A, const indexed_triangle_set &B) +{ + _mesh_boolean_do(_cgal_diff, A, B); +} + +void plus(indexed_triangle_set &A, const indexed_triangle_set &B) +{ + _mesh_boolean_do(_cgal_union, A, B); +} + +void intersect(indexed_triangle_set &A, const indexed_triangle_set &B) +{ + _mesh_boolean_do(_cgal_intersection, A, B); +} + bool does_self_intersect(const TriangleMesh &mesh) { CGALMesh cgalm; @@ -424,7 +456,7 @@ void CGALMeshDeleter::operator()(CGALMesh *ptr) { delete ptr; } bool does_bound_a_volume(const CGALMesh &mesh) { - return CGALProc::does_bound_a_volume(mesh.m); + return CGAL::is_closed(mesh.m) && CGALProc::does_bound_a_volume(mesh.m); } bool empty(const CGALMesh &mesh) @@ -432,7 +464,11 @@ bool empty(const CGALMesh &mesh) return mesh.m.is_empty(); } -} // namespace cgal +CGALMeshPtr clone(const CGALMesh &m) +{ + return CGALMeshPtr{new CGALMesh{m}}; +} +} // namespace cgal } // namespace MeshBoolean } // namespace Slic3r diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index 8fe0545f6..b778af0bb 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -26,27 +26,37 @@ namespace cgal { struct CGALMesh; struct CGALMeshDeleter { void operator()(CGALMesh *ptr); }; +using CGALMeshPtr = std::unique_ptr; -std::unique_ptr -triangle_mesh_to_cgal(const std::vector &V, - const std::vector &F); +CGALMeshPtr clone(const CGALMesh &m); -inline std::unique_ptr triangle_mesh_to_cgal(const indexed_triangle_set &M) +void save_CGALMesh(const std::string& fname, const CGALMesh& cgal_mesh); + +CGALMeshPtr triangle_mesh_to_cgal( + const std::vector &V, + const std::vector &F); + +inline CGALMeshPtr triangle_mesh_to_cgal(const indexed_triangle_set &M) { return triangle_mesh_to_cgal(M.vertices, M.indices); } -inline std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M) +inline CGALMeshPtr triangle_mesh_to_cgal(const TriangleMesh &M) { return triangle_mesh_to_cgal(M.its); } TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh); - +indexed_triangle_set cgal_to_indexed_triangle_set(const CGALMesh &cgalmesh); + // Do boolean mesh difference with CGAL bypassing igl. void minus(TriangleMesh &A, const TriangleMesh &B); void plus(TriangleMesh &A, const TriangleMesh &B); void intersect(TriangleMesh &A, const TriangleMesh &B); +void minus(indexed_triangle_set &A, const indexed_triangle_set &B); +void plus(indexed_triangle_set &A, const indexed_triangle_set &B); +void intersect(indexed_triangle_set &A, const indexed_triangle_set &B); + void minus(CGALMesh &A, CGALMesh &B); void plus(CGALMesh &A, CGALMesh &B); void intersect(CGALMesh &A, CGALMesh &B); diff --git a/src/libslic3r/MeshSplitImpl.hpp b/src/libslic3r/MeshSplitImpl.hpp index 9a7f74529..f226a2276 100644 --- a/src/libslic3r/MeshSplitImpl.hpp +++ b/src/libslic3r/MeshSplitImpl.hpp @@ -108,6 +108,21 @@ template struct ItsNeighborsWrapper const auto& get_index() const noexcept { return index_ref; } }; +// Can be used as the second argument to its_split to apply a functor on each +// part, instead of collecting them into a container. +template +struct SplitOutputFn { + + Fn fn; + + SplitOutputFn(Fn f): fn{std::move(f)} {} + + SplitOutputFn &operator *() { return *this; } + void operator=(indexed_triangle_set &&its) { fn(std::move(its)); } + void operator=(indexed_triangle_set &its) { fn(its); } + SplitOutputFn& operator++() { return *this; }; +}; + // Splits a mesh into multiple meshes when possible. template void its_split(const Its &m, OutputIt out_it) @@ -155,7 +170,8 @@ void its_split(const Its &m, OutputIt out_it) mesh.indices.emplace_back(new_face); } - out_it = std::move(mesh); + *out_it = std::move(mesh); + ++out_it; } } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 0c5ea4a60..5c838b367 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -461,6 +461,11 @@ public: const PrintObjects& objects() const { return m_objects; } // PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects // in the notification center. + const SLAPrintObject* get_print_object_by_model_object_id(ObjectID object_id) const { + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [object_id](const SLAPrintObject* obj) { return obj->model_object()->id() == object_id; }); + return (it == m_objects.end()) ? nullptr : *it; + } const SLAPrintObject* get_object(ObjectID object_id) const { auto it = std::find_if(m_objects.begin(), m_objects.end(), [object_id](const SLAPrintObject *obj) { return obj->id() == object_id; }); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 8589d705f..a92b8f644 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef _WIN32 // On MSVC, std::deque degenerates to a list of pointers, which defeats its purpose of reducing allocator load and memory fragmentation. @@ -150,6 +151,15 @@ inline void append(std::vector& dest, std::vector&& src) src.shrink_to_fit(); } +template // Arbitrary allocator can be used +void clear_and_shrink(std::vector& vec) +{ + // shrink_to_fit does not garantee the release of memory nor does it clear() + std::vector tmp; + vec.swap(tmp); + assert(vec.capacity() == 0); +} + // Append the source in reverse. template inline void append_reversed(std::vector& dest, const std::vector& src) @@ -277,9 +287,17 @@ constexpr inline T lerp(const T& a, const T& b, Number t) } template -constexpr inline bool is_approx(Number value, Number test_value) +constexpr inline bool is_approx(Number value, Number test_value, Number precision = EPSILON) { - return std::fabs(double(value) - double(test_value)) < double(EPSILON); + return std::fabs(double(value) - double(test_value)) < double(precision); +} + +template +constexpr inline bool is_approx(const std::optional &value, + const std::optional &test_value) +{ + return (!value.has_value() && !test_value.has_value()) || + (value.has_value() && test_value.has_value() && is_approx(*value, *test_value)); } // A meta-predicate which is true for integers wider than or equal to coord_t @@ -350,16 +368,42 @@ public: Range(It b, It e) : from(std::move(b)), to(std::move(e)) {} // Some useful container-like methods... - inline size_t size() const { return end() - begin(); } - inline bool empty() const { return size() == 0; } + inline size_t size() const { return std::distance(from, to); } + inline bool empty() const { return from == to; } }; +template auto range(Cont &&cont) +{ + return Range{std::begin(cont), std::end(cont)}; +} + template> constexpr T NaN = std::numeric_limits::quiet_NaN(); constexpr float NaNf = NaN; constexpr double NaNd = NaN; +// Rounding up. +// 1.5 is rounded to 2 +// 1.49 is rounded to 1 +// 0.5 is rounded to 1, +// 0.49 is rounded to 0 +// -0.5 is rounded to 0, +// -0.51 is rounded to -1, +// -1.5 is rounded to -1. +// -1.51 is rounded to -2. +// If input is not a valid float (it is infinity NaN or if it does not fit) +// the float to int conversion produces a max int on Intel and +-max int on ARM. +template +inline IntegerOnly fast_round_up(double a) +{ + // Why does Java Math.round(0.49999999999999994) return 1? + // https://stackoverflow.com/questions/9902968/why-does-math-round0-49999999999999994-return-1 + return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5)); +} + +template using SamePair = std::pair; + } // namespace Slic3r #endif diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 4ff280a56..097923c50 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -522,3 +522,6 @@ if (UNIX AND NOT APPLE) target_link_libraries(libslic3r_gui ${GSTREAMER_LIBRARIES} ${GST_BASE_LIBRARIES}) target_include_directories(libslic3r_gui PRIVATE ${GSTREAMER_INCLUDE_DIRS} ${GST_BASE_INCLUDE_DIRS}) endif () + +# Add a definition so that we can tell we are compiling slic3r. +target_compile_definitions(libslic3r_gui PRIVATE SLIC3R_CURRENTLY_COMPILING_GUI_MODULE) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 8f829f589..a84be1d9c 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -4,7 +4,15 @@ #include "GUI_ObjectList.hpp" #include "GUI_Factories.hpp" #include "format.hpp" -#include "I18N.hpp" + +// Localization headers: include libslic3r version first so everything in this file +// uses the slic3r/GUI version (the macros will take precedence over the functions). +// Also, there is a check that the former is not included from slic3r module. +// This is the only place where we want to allow that, so define an override macro. +#define SLIC3R_ALLOW_LIBSLIC3R_I18N_IN_SLIC3R +#include "libslic3r/I18N.hpp" +#undef SLIC3R_ALLOW_LIBSLIC3R_I18N_IN_SLIC3R +#include "slic3r/GUI/I18N.hpp" #include #include diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3c4a6fa06..a951cc084 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -57,6 +57,10 @@ #include "libslic3r/PresetBundle.hpp" #include "libslic3r/ClipperUtils.hpp" +// For stl export +#include "libslic3r/CSGMesh/ModelToCSGMesh.hpp" +#include "libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp" + #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" @@ -9499,39 +9503,46 @@ void Plater::export_stl(bool extended, bool selection_only) { if (p->model.objects.empty()) { return; } - wxBusyCursor wait; - - const auto &selection = p->get_selection(); - - // BBS support mulity objects - // const auto obj_idx = selection.get_object_idx(); - // if (selection_only && (obj_idx == -1 || selection.is_wipe_tower())) - // return; - - if (selection_only && selection.is_wipe_tower()) - return; - - //BBS - if (selection_only) { - // only support selection single full object and mulitiple full object - if (!selection.is_single_full_object() && !selection.is_multiple_full_object()) - return; - } - wxString path = p->get_export_file(FT_STL); if (path.empty()) { return; } const std::string path_u8 = into_u8(path); + wxBusyCursor wait; + + const auto &selection = p->get_selection(); + const auto obj_idx = selection.get_object_idx(); + if (selection_only && (obj_idx == -1 || selection.is_wipe_tower())) + return; // Following lambda generates a combined mesh for export with normals pointing outwards. - auto mesh_to_export = [](const ModelObject& mo, int instance_id) { + auto mesh_to_export_fff = [this](const ModelObject& mo, int instance_id) { TriangleMesh mesh; - for (const ModelVolume* v : mo.volumes) - if (v->is_model_part()) { - TriangleMesh vol_mesh(v->mesh()); - vol_mesh.transform(v->get_matrix(), true); - mesh.merge(vol_mesh); - } + + std::vector csgmesh; + csgmesh.reserve(2 * mo.volumes.size()); + csg::model_to_csgmesh(mo, Transform3d::Identity(), std::back_inserter(csgmesh), + csg::mpartsPositive | csg::mpartsNegative | csg::mpartsDoSplits); + + if (csg::check_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }) == csgmesh.end()) { + try { + auto meshPtr = csg::perform_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }); + mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*meshPtr); + } catch (...) {} + } + + if (mesh.empty()) { + get_notification_manager()->push_plater_error_notification( + _u8L("Unable to perform boolean operation on model meshes. " + "Only positive parts will be exported.")); + + for (const ModelVolume* v : mo.volumes) + if (v->is_model_part()) { + TriangleMesh vol_mesh(v->mesh()); + vol_mesh.transform(v->get_matrix(), true); + mesh.merge(vol_mesh); + } + } + if (instance_id == -1) { TriangleMesh vols_mesh(mesh); mesh = TriangleMesh(); @@ -9546,114 +9557,62 @@ void Plater::export_stl(bool extended, bool selection_only) return mesh; }; - TriangleMesh mesh; - if (p->printer_technology == ptFFF) { - if (selection_only) { - if (selection.is_single_full_object()) { - const auto obj_idx = selection.get_object_idx(); - const ModelObject* model_object = p->model.objects[obj_idx]; - if (selection.get_mode() == Selection::Instance) - { - mesh = std::move(mesh_to_export(*model_object, ( model_object->instances.size() > 1) ? -1 : selection.get_instance_idx())); - } - else - { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - mesh = model_object->volumes[volume->volume_idx()]->mesh(); - mesh.transform(volume->get_volume_transformation().get_matrix(), true); - } + auto mesh_to_export_sla = [&, this](const ModelObject& mo, int instance_id) { + TriangleMesh mesh; - if (model_object->instances.size() == 1) - mesh.translate(-model_object->origin_translation.cast()); - } - else if (selection.is_multiple_full_object()) { - const std::set>& instances_idxs = p->get_selection().get_selected_object_instances(); - for (const std::pair& i : instances_idxs) - { - ModelObject* object = p->model.objects[i.first]; - mesh.merge(mesh_to_export(*object, i.second)); - } - } - } + const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id()); + + if (auto m = object->get_mesh_to_print(); m.empty()) + mesh = mesh_to_export_fff(mo, instance_id); else { - for (const ModelObject *o : p->model.objects) - mesh.merge(mesh_to_export(*o, -1)); - } - } - else - { - // This is SLA mode, all objects have only one volume. - // However, we must have a look at the backend to load - // hollowed mesh and/or supports - const auto obj_idx = selection.get_object_idx(); - const PrintObjects& objects = p->sla_print.objects(); - for (const SLAPrintObject* object : objects) - { - const ModelObject* model_object = object->model_object(); - if (selection_only) { - if (model_object->id() != p->model.objects[obj_idx]->id()) - continue; - } - Transform3d mesh_trafo_inv = object->trafo().inverse(); - bool is_left_handed = object->is_left_handed(); + const Transform3d mesh_trafo_inv = object->trafo().inverse(); + const bool is_left_handed = object->is_left_handed(); - TriangleMesh pad_mesh; - bool has_pad_mesh = extended && object->has_mesh(slaposPad); - if (has_pad_mesh) - { - pad_mesh = object->get_mesh(slaposPad); - pad_mesh.transform(mesh_trafo_inv); - } + auto pad_mesh = extended? object->pad_mesh() : TriangleMesh{}; + pad_mesh.transform(mesh_trafo_inv); + + auto supports_mesh = extended ? object->support_mesh() : TriangleMesh{}; + supports_mesh.transform(mesh_trafo_inv); - TriangleMesh supports_mesh; - bool has_supports_mesh = extended && object->has_mesh(slaposSupportTree); - if (has_supports_mesh) - { - supports_mesh = object->get_mesh(slaposSupportTree); - supports_mesh.transform(mesh_trafo_inv); - } const std::vector& obj_instances = object->instances(); - for (const SLAPrintObject::Instance& obj_instance : obj_instances) - { - auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), - [&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; }); - assert(it != model_object->instances.end()); + for (const SLAPrintObject::Instance& obj_instance : obj_instances) { + auto it = std::find_if(object->model_object()->instances.begin(), object->model_object()->instances.end(), + [&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; }); + assert(it != object->model_object()->instances.end()); - if (it != model_object->instances.end()) - { - bool one_inst_only = selection_only && ! selection.is_single_full_object(); + if (it != object->model_object()->instances.end()) { + const bool one_inst_only = selection_only && ! selection.is_single_full_object(); - int instance_idx = it - model_object->instances.begin(); + const int instance_idx = it - object->model_object()->instances.begin(); const Transform3d& inst_transform = one_inst_only - ? Transform3d::Identity() - : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); + ? Transform3d::Identity() + : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); TriangleMesh inst_mesh; - if (has_pad_mesh) - { + if (!pad_mesh.empty()) { TriangleMesh inst_pad_mesh = pad_mesh; inst_pad_mesh.transform(inst_transform, is_left_handed); inst_mesh.merge(inst_pad_mesh); } - if (has_supports_mesh) - { + if (!supports_mesh.empty()) { TriangleMesh inst_supports_mesh = supports_mesh; inst_supports_mesh.transform(inst_transform, is_left_handed); inst_mesh.merge(inst_supports_mesh); } - TriangleMesh inst_object_mesh = object->get_mesh_to_slice(); + TriangleMesh inst_object_mesh = object->get_mesh_to_print(); + inst_object_mesh.transform(mesh_trafo_inv); inst_object_mesh.transform(inst_transform, is_left_handed); inst_mesh.merge(inst_object_mesh); - // ensure that the instance lays on the bed - inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min[2]); + // ensure that the instance lays on the bed + inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min.z()); - // merge instance with global mesh + // merge instance with global mesh mesh.merge(inst_mesh); if (one_inst_only) @@ -9661,9 +9620,40 @@ void Plater::export_stl(bool extended, bool selection_only) } } } + + return mesh; + }; + + std::function + mesh_to_export; + + if (p->printer_technology == ptFFF ) + mesh_to_export = mesh_to_export_fff; + else + mesh_to_export = mesh_to_export_sla; + + TriangleMesh mesh; + if (selection_only) { + const ModelObject* model_object = p->model.objects[obj_idx]; + if (selection.get_mode() == Selection::Instance) + mesh = mesh_to_export(*model_object, (selection.is_single_full_object() && model_object->instances.size() > 1) ? -1 : selection.get_instance_idx()); + else { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + mesh = model_object->volumes[volume->volume_idx()]->mesh(); + mesh.transform(volume->get_volume_transformation().get_matrix(), true); + } + + if (!selection.is_single_full_object() || model_object->instances.size() == 1) + mesh.translate(-model_object->origin_translation.cast()); + } + else { + for (const ModelObject* o : p->model.objects) { + mesh.merge(mesh_to_export(*o, -1)); + } } Slic3r::store_stl(path_u8.c_str(), &mesh, true); +// p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path)); } //BBS: remove amf export