NEW: add boolean in export stl
Change-Id: Ibeab33f27ad7a2531bb256edd0e3f853cf8def7c (cherry picked from commit 6cfb4371a1b9053b54569d91a9c29af97a753c0e)
This commit is contained in:
parent
2c620aa855
commit
7c5e96a43a
21 changed files with 1664 additions and 134 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
||||||
Build
|
Build
|
||||||
Build.bat
|
Build.bat
|
||||||
/build/
|
/build/
|
||||||
|
/build2022/
|
||||||
deps/build
|
deps/build
|
||||||
MYMETA.json
|
MYMETA.json
|
||||||
MYMETA.yml
|
MYMETA.yml
|
||||||
|
|
322
src/libslic3r/AABBMesh.cpp
Normal file
322
src/libslic3r/AABBMesh.cpp
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
#include "AABBMesh.hpp"
|
||||||
|
#include <Execution/ExecutionTBB.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||||
|
#include <libslic3r/TriangleMesh.hpp>
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||||
|
#include <libslic3r/SLA/Hollowing.hpp>
|
||||||
|
#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<igl::Hit> & 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<double, 1, 3> &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<class M> 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<Vec3f>& AABBMesh::vertices() const
|
||||||
|
{
|
||||||
|
return m_tm->vertices;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const std::vector<Vec3i>& 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<double>().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<float>::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::hit_result>
|
||||||
|
AABBMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||||
|
{
|
||||||
|
std::vector<AABBMesh::hit_result> outs;
|
||||||
|
std::vector<igl::Hit> 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<AABBMesh::hit_result>& 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<HoleHit> hole_isects;
|
||||||
|
hole_isects.reserve(m_holes.size());
|
||||||
|
|
||||||
|
auto sf = s.cast<float>();
|
||||||
|
auto dirf = dir.cast<float>();
|
||||||
|
|
||||||
|
// Collect hits on all holes, preserve information about entry/exit
|
||||||
|
for (const sla::DrainHole& hole : m_holes) {
|
||||||
|
std::array<std::pair<float, Vec3d>, 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<double, 1, 3> pp = p;
|
||||||
|
Eigen::Matrix<double, 1, 3> cc;
|
||||||
|
sqdst = m_aabb->squared_distance(*m_tm, pp, i, cc);
|
||||||
|
c = cc;
|
||||||
|
return sqdst;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
142
src/libslic3r/AABBMesh.hpp
Normal file
142
src/libslic3r/AABBMesh.hpp
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
#ifndef PRUSASLICER_AABBMESH_H
|
||||||
|
#define PRUSASLICER_AABBMESH_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <libslic3r/Point.hpp>
|
||||||
|
#include <libslic3r/TriangleMesh.hpp>
|
||||||
|
|
||||||
|
// 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<AABBImpl> m_aabb;
|
||||||
|
VertexFaceIndex m_vfidx; // vertex-face index
|
||||||
|
std::vector<Vec3i> 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<sla::DrainHole> m_holes;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<class M> 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<Vec3f>& vertices() const;
|
||||||
|
const std::vector<Vec3i>& 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<double>::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<sla::DrainHole>& 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<AABBMesh::hit_result>& 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<hit_result> 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<Vec3i> &face_neighbor_index() const { return m_fnidx; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace Slic3r::sla
|
||||||
|
|
||||||
|
#endif // INDEXEDMESH_H
|
130
src/libslic3r/AnyPtr.hpp
Normal file
130
src/libslic3r/AnyPtr.hpp
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
#ifndef ANYPTR_HPP
|
||||||
|
#define ANYPTR_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <boost/variant.hpp>
|
||||||
|
|
||||||
|
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 T>
|
||||||
|
class AnyPtr {
|
||||||
|
enum { RawPtr, UPtr, ShPtr, WkPtr };
|
||||||
|
|
||||||
|
boost::variant<T*, std::unique_ptr<T>, std::shared_ptr<T>, std::weak_ptr<T>> ptr;
|
||||||
|
|
||||||
|
template<class Self> static T *get_ptr(Self &&s)
|
||||||
|
{
|
||||||
|
switch (s.ptr.which()) {
|
||||||
|
case RawPtr: return boost::get<T *>(s.ptr);
|
||||||
|
case UPtr: return boost::get<std::unique_ptr<T>>(s.ptr).get();
|
||||||
|
case ShPtr: return boost::get<std::shared_ptr<T>>(s.ptr).get();
|
||||||
|
case WkPtr: {
|
||||||
|
auto shptr = boost::get<std::weak_ptr<T>>(s.ptr).lock();
|
||||||
|
return shptr.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||||
|
AnyPtr(TT *p = nullptr) : ptr{p}
|
||||||
|
{}
|
||||||
|
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||||
|
AnyPtr(std::unique_ptr<TT> p) : ptr{std::unique_ptr<T>(std::move(p))}
|
||||||
|
{}
|
||||||
|
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||||
|
AnyPtr(std::shared_ptr<TT> p) : ptr{std::shared_ptr<T>(std::move(p))}
|
||||||
|
{}
|
||||||
|
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||||
|
AnyPtr(std::weak_ptr<TT> p) : ptr{std::weak_ptr<T>(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<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||||
|
AnyPtr &operator=(TT *p) { ptr = p; return *this; }
|
||||||
|
|
||||||
|
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||||
|
AnyPtr &operator=(std::unique_ptr<TT> p) { ptr = std::move(p); return *this; }
|
||||||
|
|
||||||
|
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||||
|
AnyPtr &operator=(std::shared_ptr<TT> p) { ptr = p; return *this; }
|
||||||
|
|
||||||
|
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||||
|
AnyPtr &operator=(std::weak_ptr<TT> 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<T *>(ptr));
|
||||||
|
case UPtr: return bool(boost::get<std::unique_ptr<T>>(ptr));
|
||||||
|
case ShPtr: return bool(boost::get<std::shared_ptr<T>>(ptr));
|
||||||
|
case WkPtr: {
|
||||||
|
auto shptr = boost::get<std::weak_ptr<T>>(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<T> get_shared_cpy() const
|
||||||
|
{
|
||||||
|
std::shared_ptr<T> ret;
|
||||||
|
|
||||||
|
switch (ptr.which()) {
|
||||||
|
case ShPtr: ret = boost::get<std::shared_ptr<T>>(ptr); break;
|
||||||
|
case WkPtr: ret = boost::get<std::weak_ptr<T>>(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<T>{std::move(boost::get<std::unique_ptr<T>>(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
|
|
@ -24,6 +24,9 @@ set(lisbslic3r_sources
|
||||||
pchheader.hpp
|
pchheader.hpp
|
||||||
AABBTreeIndirect.hpp
|
AABBTreeIndirect.hpp
|
||||||
AABBTreeLines.hpp
|
AABBTreeLines.hpp
|
||||||
|
AABBMesh.hpp
|
||||||
|
AABBMesh.cpp
|
||||||
|
AnyPtr.hpp
|
||||||
BoundingBox.cpp
|
BoundingBox.cpp
|
||||||
BoundingBox.hpp
|
BoundingBox.hpp
|
||||||
BridgeDetector.cpp
|
BridgeDetector.cpp
|
||||||
|
|
86
src/libslic3r/CSGMesh/CSGMesh.hpp
Normal file
86
src/libslic3r/CSGMesh/CSGMesh.hpp
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
#ifndef CSGMESH_HPP
|
||||||
|
#define CSGMESH_HPP
|
||||||
|
|
||||||
|
#include <libslic3r/AnyPtr.hpp>
|
||||||
|
#include <admesh/stl.h>
|
||||||
|
|
||||||
|
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<class CSGPartT> CSGType get_operation(const CSGPartT &part)
|
||||||
|
{
|
||||||
|
return part.operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the stack operation required by the CSG part.
|
||||||
|
template<class CSGPartT> CSGStackOp get_stack_operation(const CSGPartT &part)
|
||||||
|
{
|
||||||
|
return part.stack_operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the mesh for the part. Can be overriden for any type
|
||||||
|
template<class CSGPartT>
|
||||||
|
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<class CSGPartT>
|
||||||
|
Transform3f get_transform(const CSGPartT &part)
|
||||||
|
{
|
||||||
|
return part.trafo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default implementation
|
||||||
|
struct CSGPart {
|
||||||
|
AnyPtr<const indexed_triangle_set> its_ptr;
|
||||||
|
Transform3f trafo;
|
||||||
|
CSGType operation;
|
||||||
|
CSGStackOp stack_operation;
|
||||||
|
|
||||||
|
CSGPart(AnyPtr<const indexed_triangle_set> 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
|
80
src/libslic3r/CSGMesh/CSGMeshCopy.hpp
Normal file
80
src/libslic3r/CSGMesh/CSGMeshCopy.hpp
Normal file
|
@ -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<class It, class OutIt>
|
||||||
|
void copy_csgrange_shallow(const Range<It> &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<decltype(part), const CSGPart&>) {
|
||||||
|
if (auto shptr = part.its_ptr.get_shared_cpy()) {
|
||||||
|
cpy.its_ptr = shptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cpy.its_ptr)
|
||||||
|
cpy.its_ptr = AnyPtr<const indexed_triangle_set>{get_mesh(part)};
|
||||||
|
|
||||||
|
*out = std::move(cpy);
|
||||||
|
++out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the csg range, allocating new meshes
|
||||||
|
template<class It, class OutIt>
|
||||||
|
void copy_csgrange_deep(const Range<It> &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<const indexed_triangle_set>(*meshptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
cpy.stack_operation = get_stack_operation(part);
|
||||||
|
|
||||||
|
*out = std::move(cpy);
|
||||||
|
++out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ItA, class ItB>
|
||||||
|
bool is_same(const Range<ItA> &A, const Range<ItB> &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
|
88
src/libslic3r/CSGMesh/ModelToCSGMesh.hpp
Normal file
88
src/libslic3r/CSGMesh/ModelToCSGMesh.hpp
Normal file
|
@ -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<class OutIt>
|
||||||
|
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<indexed_triangle_set>(std::move(its)),
|
||||||
|
CSGType::Union,
|
||||||
|
(trafo * vol->get_matrix()).cast<float>()};
|
||||||
|
|
||||||
|
*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<float>()};
|
||||||
|
|
||||||
|
*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<const indexed_triangle_set>(
|
||||||
|
// dhole.to_mesh()),
|
||||||
|
// CSGType::Difference};
|
||||||
|
|
||||||
|
// *out = std::move(part);
|
||||||
|
// ++out;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::csg
|
||||||
|
|
||||||
|
#endif // MODELTOCSGMESH_HPP
|
205
src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp
Normal file
205
src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
#ifndef PERFORMCSGMESHBOOLEANS_HPP
|
||||||
|
#define PERFORMCSGMESHBOOLEANS_HPP
|
||||||
|
|
||||||
|
#include <stack>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<class CSGPartT>
|
||||||
|
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<class Ex, class It>
|
||||||
|
std::vector<CGALMeshPtr> get_cgalptrs(Ex policy, const Range<It> &csgrange)
|
||||||
|
{
|
||||||
|
std::vector<CGALMeshPtr> 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<class It>
|
||||||
|
void perform_csgmesh_booleans_cgal(MeshBoolean::cgal::CGALMeshPtr &cgalm,
|
||||||
|
const Range<It> &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<Frame>{} };
|
||||||
|
|
||||||
|
opstack.push(Frame{});
|
||||||
|
|
||||||
|
std::vector<CGALMeshPtr> 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<class It, class Visitor>
|
||||||
|
It check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn)
|
||||||
|
{
|
||||||
|
using namespace detail_cgal;
|
||||||
|
|
||||||
|
std::vector<CGALMeshPtr> 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<class It>
|
||||||
|
It check_csgmesh_booleans(const Range<It> &csgrange)
|
||||||
|
{
|
||||||
|
return check_csgmesh_booleans(csgrange, [](auto &) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class It>
|
||||||
|
MeshBoolean::cgal::CGALMeshPtr perform_csgmesh_booleans(const Range<It> &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
|
131
src/libslic3r/CSGMesh/SliceCSGMesh.hpp
Normal file
131
src/libslic3r/CSGMesh/SliceCSGMesh.hpp
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
#ifndef SLICECSGMESH_HPP
|
||||||
|
#define SLICECSGMESH_HPP
|
||||||
|
|
||||||
|
#include "CSGMesh.hpp"
|
||||||
|
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
#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<ExPolygons> &target,
|
||||||
|
std::vector<ExPolygons> &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<float> &slicegrid,
|
||||||
|
const std::vector<ExPolygons> &slices,
|
||||||
|
std::vector<size_t> &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<class ItCSG>
|
||||||
|
std::vector<ExPolygons> slice_csgmesh_ex(
|
||||||
|
const Range<ItCSG> &csgrange,
|
||||||
|
const std::vector<float> &slicegrid,
|
||||||
|
const MeshSlicingParamsEx ¶ms,
|
||||||
|
const std::function<void()> &throw_on_cancel = [] {})
|
||||||
|
{
|
||||||
|
using namespace detail;
|
||||||
|
|
||||||
|
struct Frame { CSGType op; std::vector<ExPolygons> slices; };
|
||||||
|
|
||||||
|
std::stack opstack{std::vector<Frame>{}};
|
||||||
|
|
||||||
|
MeshSlicingParamsEx params_cpy = params;
|
||||||
|
auto trafo = params.trafo;
|
||||||
|
auto nonempty_indices = reserve_vector<size_t>(slicegrid.size());
|
||||||
|
|
||||||
|
opstack.push({CSGType::Union, std::vector<ExPolygons>(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<ExPolygons>(slicegrid.size())});
|
||||||
|
op = CSGType::Union;
|
||||||
|
}
|
||||||
|
|
||||||
|
Frame *top = &opstack.top();
|
||||||
|
|
||||||
|
if (its) {
|
||||||
|
params_cpy.trafo = trafo * csg::get_transform(csgpart).template cast<double>();
|
||||||
|
std::vector<ExPolygons> 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<ExPolygons> popslices = std::move(top->slices);
|
||||||
|
auto popop = opstack.top().op;
|
||||||
|
opstack.pop();
|
||||||
|
std::vector<ExPolygons> &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<ExPolygons> 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
|
95
src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp
Normal file
95
src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp
Normal file
|
@ -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
|
116
src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp
Normal file
116
src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
#ifndef VOXELIZECSGMESH_HPP
|
||||||
|
#define VOXELIZECSGMESH_HPP
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
#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<class CSGPartT>
|
||||||
|
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<class It>
|
||||||
|
VoxelGridPtr voxelize_csgmesh(const Range<It> &csgrange,
|
||||||
|
const VoxelizeParams ¶ms = {})
|
||||||
|
{
|
||||||
|
using namespace detail;
|
||||||
|
|
||||||
|
VoxelGridPtr ret;
|
||||||
|
|
||||||
|
std::vector<VoxelGridPtr> 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<Frame>{}};
|
||||||
|
|
||||||
|
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
|
|
@ -3,6 +3,12 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#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 Slic3r {
|
||||||
|
|
||||||
namespace I18N {
|
namespace I18N {
|
||||||
|
@ -15,4 +21,17 @@ namespace I18N {
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // 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_ */
|
#endif /* slic3r_I18N_hpp_ */
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <CGAL/Polygon_mesh_processing/corefinement.h>
|
#include <CGAL/Polygon_mesh_processing/corefinement.h>
|
||||||
#include <CGAL/Exact_integer.h>
|
#include <CGAL/Exact_integer.h>
|
||||||
#include <CGAL/Surface_mesh.h>
|
#include <CGAL/Surface_mesh.h>
|
||||||
|
#include <CGAL/Cartesian_converter.h>
|
||||||
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
|
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
|
||||||
#include <CGAL/Polygon_mesh_processing/repair.h>
|
#include <CGAL/Polygon_mesh_processing/repair.h>
|
||||||
#include <CGAL/Polygon_mesh_processing/remesh.h>
|
#include <CGAL/Polygon_mesh_processing/remesh.h>
|
||||||
|
@ -114,6 +115,11 @@ struct CGALMesh {
|
||||||
CGALMesh(const _EpicMesh& _m) :m(_m) {}
|
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
|
// 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()) };
|
return { float(iv.x()), float(iv.y()), float(iv.z()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
template<class _Mesh>
|
||||||
|
indexed_triangle_set cgal_to_indexed_triangle_set(const _Mesh &cgalmesh)
|
||||||
{
|
{
|
||||||
indexed_triangle_set its;
|
indexed_triangle_set its;
|
||||||
its.vertices.reserve(cgalmesh.num_vertices());
|
its.vertices.reserve(cgalmesh.num_vertices());
|
||||||
|
@ -209,7 +216,7 @@ template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
||||||
its.indices.emplace_back(facet);
|
its.indices.emplace_back(facet);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TriangleMesh(std::move(its));
|
return its;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<CGALMesh, CGALMeshDeleter>
|
std::unique_ptr<CGALMesh, CGALMeshDeleter>
|
||||||
|
@ -223,7 +230,12 @@ triangle_mesh_to_cgal(const std::vector<stl_vertex> &V,
|
||||||
|
|
||||||
TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh)
|
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<CGALMesh>& dst, double smoothing_alpha =
|
||||||
_EpicMesh out;
|
_EpicMesh out;
|
||||||
CGAL::copy_face_graph(segment_mesh, out);
|
CGAL::copy_face_graph(segment_mesh, out);
|
||||||
|
|
||||||
//std::ostringstream oss;
|
// save_CGALMesh("out.off", out);
|
||||||
//oss << "Segment_" << id << ".off";
|
|
||||||
//std::ofstream os(oss.str().data());
|
|
||||||
//os << out;
|
|
||||||
|
|
||||||
// fill holes
|
// fill holes
|
||||||
typedef boost::graph_traits<_EpicMesh>::halfedge_descriptor halfedge_descriptor;
|
typedef boost::graph_traits<_EpicMesh>::halfedge_descriptor halfedge_descriptor;
|
||||||
|
@ -382,9 +391,17 @@ TriangleMesh merge(std::vector<TriangleMesh> meshes)
|
||||||
return cgal_to_triangle_mesh(dst);
|
return cgal_to_triangle_mesh(dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////////
|
template<class Op> void _mesh_boolean_do(Op &&op, indexed_triangle_set &A, const indexed_triangle_set &B)
|
||||||
// Now the public functions for TriangleMesh input:
|
{
|
||||||
// /////////////////////////////////////////////////////////////////////////////
|
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<class Op> void _mesh_boolean_do(Op &&op, TriangleMesh &A, const TriangleMesh &B)
|
template<class Op> void _mesh_boolean_do(Op &&op, TriangleMesh &A, const TriangleMesh &B)
|
||||||
{
|
{
|
||||||
|
@ -392,10 +409,10 @@ template<class Op> void _mesh_boolean_do(Op &&op, TriangleMesh &A, const Triangl
|
||||||
CGALMesh meshB;
|
CGALMesh meshB;
|
||||||
triangle_mesh_to_cgal(A.its.vertices, A.its.indices, meshA.m);
|
triangle_mesh_to_cgal(A.its.vertices, A.its.indices, meshA.m);
|
||||||
triangle_mesh_to_cgal(B.its.vertices, B.its.indices, meshB.m);
|
triangle_mesh_to_cgal(B.its.vertices, B.its.indices, meshB.m);
|
||||||
|
|
||||||
_cgal_do(op, meshA, meshB);
|
_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)
|
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);
|
_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)
|
bool does_self_intersect(const TriangleMesh &mesh)
|
||||||
{
|
{
|
||||||
CGALMesh cgalm;
|
CGALMesh cgalm;
|
||||||
|
@ -424,7 +456,7 @@ void CGALMeshDeleter::operator()(CGALMesh *ptr) { delete ptr; }
|
||||||
|
|
||||||
bool does_bound_a_volume(const CGALMesh &mesh)
|
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)
|
bool empty(const CGALMesh &mesh)
|
||||||
|
@ -432,7 +464,11 @@ bool empty(const CGALMesh &mesh)
|
||||||
return mesh.m.is_empty();
|
return mesh.m.is_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cgal
|
CGALMeshPtr clone(const CGALMesh &m)
|
||||||
|
{
|
||||||
|
return CGALMeshPtr{new CGALMesh{m}};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cgal
|
||||||
} // namespace MeshBoolean
|
} // namespace MeshBoolean
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
|
@ -26,27 +26,37 @@ namespace cgal {
|
||||||
|
|
||||||
struct CGALMesh;
|
struct CGALMesh;
|
||||||
struct CGALMeshDeleter { void operator()(CGALMesh *ptr); };
|
struct CGALMeshDeleter { void operator()(CGALMesh *ptr); };
|
||||||
|
using CGALMeshPtr = std::unique_ptr<CGALMesh, CGALMeshDeleter>;
|
||||||
|
|
||||||
std::unique_ptr<CGALMesh, CGALMeshDeleter>
|
CGALMeshPtr clone(const CGALMesh &m);
|
||||||
triangle_mesh_to_cgal(const std::vector<stl_vertex> &V,
|
|
||||||
const std::vector<stl_triangle_vertex_indices> &F);
|
|
||||||
|
|
||||||
inline std::unique_ptr<CGALMesh, CGALMeshDeleter> 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<stl_vertex> &V,
|
||||||
|
const std::vector<stl_triangle_vertex_indices> &F);
|
||||||
|
|
||||||
|
inline CGALMeshPtr triangle_mesh_to_cgal(const indexed_triangle_set &M)
|
||||||
{
|
{
|
||||||
return triangle_mesh_to_cgal(M.vertices, M.indices);
|
return triangle_mesh_to_cgal(M.vertices, M.indices);
|
||||||
}
|
}
|
||||||
inline std::unique_ptr<CGALMesh, CGALMeshDeleter> triangle_mesh_to_cgal(const TriangleMesh &M)
|
inline CGALMeshPtr triangle_mesh_to_cgal(const TriangleMesh &M)
|
||||||
{
|
{
|
||||||
return triangle_mesh_to_cgal(M.its);
|
return triangle_mesh_to_cgal(M.its);
|
||||||
}
|
}
|
||||||
|
|
||||||
TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh);
|
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.
|
// Do boolean mesh difference with CGAL bypassing igl.
|
||||||
void minus(TriangleMesh &A, const TriangleMesh &B);
|
void minus(TriangleMesh &A, const TriangleMesh &B);
|
||||||
void plus(TriangleMesh &A, const TriangleMesh &B);
|
void plus(TriangleMesh &A, const TriangleMesh &B);
|
||||||
void intersect(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 minus(CGALMesh &A, CGALMesh &B);
|
||||||
void plus(CGALMesh &A, CGALMesh &B);
|
void plus(CGALMesh &A, CGALMesh &B);
|
||||||
void intersect(CGALMesh &A, CGALMesh &B);
|
void intersect(CGALMesh &A, CGALMesh &B);
|
||||||
|
|
|
@ -108,6 +108,21 @@ template<class IndexT> struct ItsNeighborsWrapper
|
||||||
const auto& get_index() const noexcept { return index_ref; }
|
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<class Fn>
|
||||||
|
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.
|
// Splits a mesh into multiple meshes when possible.
|
||||||
template<class Its, class OutputIt>
|
template<class Its, class OutputIt>
|
||||||
void its_split(const Its &m, OutputIt out_it)
|
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);
|
mesh.indices.emplace_back(new_face);
|
||||||
}
|
}
|
||||||
|
|
||||||
out_it = std::move(mesh);
|
*out_it = std::move(mesh);
|
||||||
|
++out_it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -461,6 +461,11 @@ public:
|
||||||
const PrintObjects& objects() const { return m_objects; }
|
const PrintObjects& objects() const { return m_objects; }
|
||||||
// PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects
|
// PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects
|
||||||
// in the notification center.
|
// 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 {
|
const SLAPrintObject* get_object(ObjectID object_id) const {
|
||||||
auto it = std::find_if(m_objects.begin(), m_objects.end(),
|
auto it = std::find_if(m_objects.begin(), m_objects.end(),
|
||||||
[object_id](const SLAPrintObject *obj) { return obj->id() == object_id; });
|
[object_id](const SLAPrintObject *obj) { return obj->id() == object_id; });
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// On MSVC, std::deque degenerates to a list of pointers, which defeats its purpose of reducing allocator load and memory fragmentation.
|
// 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<T>& dest, std::vector<T>&& src)
|
||||||
src.shrink_to_fit();
|
src.shrink_to_fit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class T, class... Args> // Arbitrary allocator can be used
|
||||||
|
void clear_and_shrink(std::vector<T, Args...>& vec)
|
||||||
|
{
|
||||||
|
// shrink_to_fit does not garantee the release of memory nor does it clear()
|
||||||
|
std::vector<T, Args...> tmp;
|
||||||
|
vec.swap(tmp);
|
||||||
|
assert(vec.capacity() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Append the source in reverse.
|
// Append the source in reverse.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline void append_reversed(std::vector<T>& dest, const std::vector<T>& src)
|
inline void append_reversed(std::vector<T>& dest, const std::vector<T>& src)
|
||||||
|
@ -277,9 +287,17 @@ constexpr inline T lerp(const T& a, const T& b, Number t)
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Number>
|
template <typename Number>
|
||||||
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<typename Number>
|
||||||
|
constexpr inline bool is_approx(const std::optional<Number> &value,
|
||||||
|
const std::optional<Number> &test_value)
|
||||||
|
{
|
||||||
|
return (!value.has_value() && !test_value.has_value()) ||
|
||||||
|
(value.has_value() && test_value.has_value() && is_approx<Number>(*value, *test_value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// A meta-predicate which is true for integers wider than or equal to coord_t
|
// 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)) {}
|
Range(It b, It e) : from(std::move(b)), to(std::move(e)) {}
|
||||||
|
|
||||||
// Some useful container-like methods...
|
// Some useful container-like methods...
|
||||||
inline size_t size() const { return end() - begin(); }
|
inline size_t size() const { return std::distance(from, to); }
|
||||||
inline bool empty() const { return size() == 0; }
|
inline bool empty() const { return from == to; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<class Cont> auto range(Cont &&cont)
|
||||||
|
{
|
||||||
|
return Range{std::begin(cont), std::end(cont)};
|
||||||
|
}
|
||||||
|
|
||||||
template<class T, class = FloatingOnly<T>>
|
template<class T, class = FloatingOnly<T>>
|
||||||
constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
|
constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
|
||||||
|
|
||||||
constexpr float NaNf = NaN<float>;
|
constexpr float NaNf = NaN<float>;
|
||||||
constexpr double NaNd = NaN<double>;
|
constexpr double NaNd = NaN<double>;
|
||||||
|
|
||||||
|
// 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<typename I>
|
||||||
|
inline IntegerOnly<I, I> 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<class T> using SamePair = std::pair<T, T>;
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -522,3 +522,6 @@ if (UNIX AND NOT APPLE)
|
||||||
target_link_libraries(libslic3r_gui ${GSTREAMER_LIBRARIES} ${GST_BASE_LIBRARIES})
|
target_link_libraries(libslic3r_gui ${GSTREAMER_LIBRARIES} ${GST_BASE_LIBRARIES})
|
||||||
target_include_directories(libslic3r_gui PRIVATE ${GSTREAMER_INCLUDE_DIRS} ${GST_BASE_INCLUDE_DIRS})
|
target_include_directories(libslic3r_gui PRIVATE ${GSTREAMER_INCLUDE_DIRS} ${GST_BASE_INCLUDE_DIRS})
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
# Add a definition so that we can tell we are compiling slic3r.
|
||||||
|
target_compile_definitions(libslic3r_gui PRIVATE SLIC3R_CURRENTLY_COMPILING_GUI_MODULE)
|
||||||
|
|
|
@ -4,7 +4,15 @@
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
#include "GUI_Factories.hpp"
|
#include "GUI_Factories.hpp"
|
||||||
#include "format.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 <algorithm>
|
#include <algorithm>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
|
@ -57,6 +57,10 @@
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "libslic3r/ClipperUtils.hpp"
|
#include "libslic3r/ClipperUtils.hpp"
|
||||||
|
|
||||||
|
// For stl export
|
||||||
|
#include "libslic3r/CSGMesh/ModelToCSGMesh.hpp"
|
||||||
|
#include "libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp"
|
||||||
|
|
||||||
#include "GUI.hpp"
|
#include "GUI.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
|
@ -9499,39 +9503,46 @@ void Plater::export_stl(bool extended, bool selection_only)
|
||||||
{
|
{
|
||||||
if (p->model.objects.empty()) { return; }
|
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);
|
wxString path = p->get_export_file(FT_STL);
|
||||||
if (path.empty()) { return; }
|
if (path.empty()) { return; }
|
||||||
const std::string path_u8 = into_u8(path);
|
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.
|
// 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;
|
TriangleMesh mesh;
|
||||||
for (const ModelVolume* v : mo.volumes)
|
|
||||||
if (v->is_model_part()) {
|
std::vector<csg::CSGPart> csgmesh;
|
||||||
TriangleMesh vol_mesh(v->mesh());
|
csgmesh.reserve(2 * mo.volumes.size());
|
||||||
vol_mesh.transform(v->get_matrix(), true);
|
csg::model_to_csgmesh(mo, Transform3d::Identity(), std::back_inserter(csgmesh),
|
||||||
mesh.merge(vol_mesh);
|
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) {
|
if (instance_id == -1) {
|
||||||
TriangleMesh vols_mesh(mesh);
|
TriangleMesh vols_mesh(mesh);
|
||||||
mesh = TriangleMesh();
|
mesh = TriangleMesh();
|
||||||
|
@ -9546,114 +9557,62 @@ void Plater::export_stl(bool extended, bool selection_only)
|
||||||
return mesh;
|
return mesh;
|
||||||
};
|
};
|
||||||
|
|
||||||
TriangleMesh mesh;
|
auto mesh_to_export_sla = [&, this](const ModelObject& mo, int instance_id) {
|
||||||
if (p->printer_technology == ptFFF) {
|
TriangleMesh mesh;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (model_object->instances.size() == 1)
|
const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id());
|
||||||
mesh.translate(-model_object->origin_translation.cast<float>());
|
|
||||||
}
|
if (auto m = object->get_mesh_to_print(); m.empty())
|
||||||
else if (selection.is_multiple_full_object()) {
|
mesh = mesh_to_export_fff(mo, instance_id);
|
||||||
const std::set<std::pair<int, int>>& instances_idxs = p->get_selection().get_selected_object_instances();
|
|
||||||
for (const std::pair<int, int>& i : instances_idxs)
|
|
||||||
{
|
|
||||||
ModelObject* object = p->model.objects[i.first];
|
|
||||||
mesh.merge(mesh_to_export(*object, i.second));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
for (const ModelObject *o : p->model.objects)
|
const Transform3d mesh_trafo_inv = object->trafo().inverse();
|
||||||
mesh.merge(mesh_to_export(*o, -1));
|
const bool is_left_handed = object->is_left_handed();
|
||||||
}
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
|
|
||||||
TriangleMesh pad_mesh;
|
auto pad_mesh = extended? object->pad_mesh() : TriangleMesh{};
|
||||||
bool has_pad_mesh = extended && object->has_mesh(slaposPad);
|
pad_mesh.transform(mesh_trafo_inv);
|
||||||
if (has_pad_mesh)
|
|
||||||
{
|
auto supports_mesh = extended ? object->support_mesh() : TriangleMesh{};
|
||||||
pad_mesh = object->get_mesh(slaposPad);
|
supports_mesh.transform(mesh_trafo_inv);
|
||||||
pad_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<SLAPrintObject::Instance>& obj_instances = object->instances();
|
const std::vector<SLAPrintObject::Instance>& obj_instances = object->instances();
|
||||||
for (const SLAPrintObject::Instance& obj_instance : obj_instances)
|
for (const SLAPrintObject::Instance& obj_instance : obj_instances) {
|
||||||
{
|
auto it = std::find_if(object->model_object()->instances.begin(), object->model_object()->instances.end(),
|
||||||
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; });
|
||||||
[&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; });
|
assert(it != object->model_object()->instances.end());
|
||||||
assert(it != model_object->instances.end());
|
|
||||||
|
|
||||||
if (it != model_object->instances.end())
|
if (it != object->model_object()->instances.end()) {
|
||||||
{
|
const bool one_inst_only = selection_only && ! selection.is_single_full_object();
|
||||||
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
|
const Transform3d& inst_transform = one_inst_only
|
||||||
? Transform3d::Identity()
|
? Transform3d::Identity()
|
||||||
: object->model_object()->instances[instance_idx]->get_transformation().get_matrix();
|
: object->model_object()->instances[instance_idx]->get_transformation().get_matrix();
|
||||||
|
|
||||||
TriangleMesh inst_mesh;
|
TriangleMesh inst_mesh;
|
||||||
|
|
||||||
if (has_pad_mesh)
|
if (!pad_mesh.empty()) {
|
||||||
{
|
|
||||||
TriangleMesh inst_pad_mesh = pad_mesh;
|
TriangleMesh inst_pad_mesh = pad_mesh;
|
||||||
inst_pad_mesh.transform(inst_transform, is_left_handed);
|
inst_pad_mesh.transform(inst_transform, is_left_handed);
|
||||||
inst_mesh.merge(inst_pad_mesh);
|
inst_mesh.merge(inst_pad_mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_supports_mesh)
|
if (!supports_mesh.empty()) {
|
||||||
{
|
|
||||||
TriangleMesh inst_supports_mesh = supports_mesh;
|
TriangleMesh inst_supports_mesh = supports_mesh;
|
||||||
inst_supports_mesh.transform(inst_transform, is_left_handed);
|
inst_supports_mesh.transform(inst_transform, is_left_handed);
|
||||||
inst_mesh.merge(inst_supports_mesh);
|
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(mesh_trafo_inv);
|
||||||
inst_object_mesh.transform(inst_transform, is_left_handed);
|
inst_object_mesh.transform(inst_transform, is_left_handed);
|
||||||
|
|
||||||
inst_mesh.merge(inst_object_mesh);
|
inst_mesh.merge(inst_object_mesh);
|
||||||
|
|
||||||
// ensure that the instance lays on the bed
|
// ensure that the instance lays on the bed
|
||||||
inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min[2]);
|
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);
|
mesh.merge(inst_mesh);
|
||||||
|
|
||||||
if (one_inst_only)
|
if (one_inst_only)
|
||||||
|
@ -9661,9 +9620,40 @@ void Plater::export_stl(bool extended, bool selection_only)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::function<TriangleMesh(const ModelObject& mo, int instance_id)>
|
||||||
|
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<float>());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (const ModelObject* o : p->model.objects) {
|
||||||
|
mesh.merge(mesh_to_export(*o, -1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Slic3r::store_stl(path_u8.c_str(), &mesh, true);
|
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
|
//BBS: remove amf export
|
||||||
|
|
Loading…
Reference in a new issue