Merge branch 'tm_remove_inside_triangles'
This commit is contained in:
commit
6d11c50ba5
18 changed files with 881 additions and 358 deletions
|
@ -21,22 +21,30 @@ public:
|
|||
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
|
||||
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
|
||||
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
|
||||
BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero())
|
||||
|
||||
template<class It, class = IteratorOnly<It> >
|
||||
BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero())
|
||||
{
|
||||
if (points.empty()) {
|
||||
if (from == to) {
|
||||
this->defined = false;
|
||||
// throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
|
||||
} else {
|
||||
typename std::vector<PointClass>::const_iterator it = points.begin();
|
||||
this->min = *it;
|
||||
this->max = *it;
|
||||
for (++ it; it != points.end(); ++ it) {
|
||||
this->min = this->min.cwiseMin(*it);
|
||||
this->max = this->max.cwiseMax(*it);
|
||||
auto it = from;
|
||||
this->min = it->template cast<typename PointClass::Scalar>();
|
||||
this->max = this->min;
|
||||
for (++ it; it != to; ++ it) {
|
||||
auto vec = it->template cast<typename PointClass::Scalar>();
|
||||
this->min = this->min.cwiseMin(vec);
|
||||
this->max = this->max.cwiseMax(vec);
|
||||
}
|
||||
this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1));
|
||||
}
|
||||
}
|
||||
|
||||
BoundingBoxBase(const std::vector<PointClass> &points)
|
||||
: BoundingBoxBase(points.begin(), points.end())
|
||||
{}
|
||||
|
||||
void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); }
|
||||
void merge(const PointClass &point);
|
||||
void merge(const std::vector<PointClass> &points);
|
||||
|
@ -74,19 +82,27 @@ public:
|
|||
{ if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; }
|
||||
BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
|
||||
BoundingBoxBase<PointClass>(p1, p1) { merge(p2); merge(p3); }
|
||||
BoundingBox3Base(const std::vector<PointClass>& points)
|
||||
|
||||
template<class It, class = IteratorOnly<It> > BoundingBox3Base(It from, It to)
|
||||
{
|
||||
if (points.empty())
|
||||
if (from == to)
|
||||
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
|
||||
typename std::vector<PointClass>::const_iterator it = points.begin();
|
||||
this->min = *it;
|
||||
this->max = *it;
|
||||
for (++ it; it != points.end(); ++ it) {
|
||||
this->min = this->min.cwiseMin(*it);
|
||||
this->max = this->max.cwiseMax(*it);
|
||||
|
||||
auto it = from;
|
||||
this->min = it->template cast<typename PointClass::Scalar>();
|
||||
this->max = this->min;
|
||||
for (++ it; it != to; ++ it) {
|
||||
auto vec = it->template cast<typename PointClass::Scalar>();
|
||||
this->min = this->min.cwiseMin(vec);
|
||||
this->max = this->max.cwiseMax(vec);
|
||||
}
|
||||
this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2));
|
||||
}
|
||||
|
||||
BoundingBox3Base(const std::vector<PointClass> &points)
|
||||
: BoundingBox3Base(points.begin(), points.end())
|
||||
{}
|
||||
|
||||
void merge(const PointClass &point);
|
||||
void merge(const std::vector<PointClass> &points);
|
||||
void merge(const BoundingBox3Base<PointClass> &bb);
|
||||
|
@ -188,9 +204,7 @@ public:
|
|||
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
|
||||
{
|
||||
public:
|
||||
BoundingBoxf3() : BoundingBox3Base<Vec3d>() {}
|
||||
BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {}
|
||||
BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {}
|
||||
using BoundingBox3Base::BoundingBox3Base;
|
||||
|
||||
BoundingBoxf3 transformed(const Transform3d& matrix) const;
|
||||
};
|
||||
|
|
|
@ -22,74 +22,54 @@ namespace Slic3r {
|
|||
class TriangleMeshDataAdapter {
|
||||
public:
|
||||
const TriangleMesh &mesh;
|
||||
|
||||
float voxel_scale;
|
||||
|
||||
size_t polygonCount() const { return mesh.its.indices.size(); }
|
||||
size_t pointCount() const { return mesh.its.vertices.size(); }
|
||||
size_t vertexCount(size_t) const { return 3; }
|
||||
|
||||
|
||||
// Return position pos in local grid index space for polygon n and vertex v
|
||||
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
|
||||
// The actual mesh will appear to openvdb as scaled uniformly by voxel_size
|
||||
// And the voxel count per unit volume can be affected this way.
|
||||
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const
|
||||
{
|
||||
auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v)));
|
||||
Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>() * voxel_scale;
|
||||
pos = {p.x(), p.y(), p.z()};
|
||||
}
|
||||
|
||||
TriangleMeshDataAdapter(const TriangleMesh &m, float voxel_sc = 1.f)
|
||||
: mesh{m}, voxel_scale{voxel_sc} {};
|
||||
};
|
||||
|
||||
class Contour3DDataAdapter {
|
||||
public:
|
||||
const sla::Contour3D &mesh;
|
||||
|
||||
size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); }
|
||||
size_t pointCount() const { return mesh.points.size(); }
|
||||
size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; }
|
||||
|
||||
// Return position pos in local grid index space for polygon n and vertex v
|
||||
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
|
||||
};
|
||||
|
||||
void TriangleMeshDataAdapter::getIndexSpacePoint(size_t n,
|
||||
size_t v,
|
||||
openvdb::Vec3d &pos) const
|
||||
{
|
||||
auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v)));
|
||||
Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>();
|
||||
pos = {p.x(), p.y(), p.z()};
|
||||
}
|
||||
|
||||
void Contour3DDataAdapter::getIndexSpacePoint(size_t n,
|
||||
size_t v,
|
||||
openvdb::Vec3d &pos) const
|
||||
{
|
||||
size_t vidx = 0;
|
||||
if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v)));
|
||||
else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v)));
|
||||
|
||||
Slic3r::Vec3d p = mesh.points[vidx];
|
||||
pos = {p.x(), p.y(), p.z()};
|
||||
}
|
||||
|
||||
|
||||
// TODO: Do I need to call initialize? Seems to work without it as well but the
|
||||
// docs say it should be called ones. It does a mutex lock-unlock sequence all
|
||||
// even if was called previously.
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh,
|
||||
const openvdb::math::Transform &tr,
|
||||
float exteriorBandWidth,
|
||||
float interiorBandWidth,
|
||||
int flags)
|
||||
float voxel_scale,
|
||||
float exteriorBandWidth,
|
||||
float interiorBandWidth,
|
||||
int flags)
|
||||
{
|
||||
openvdb::initialize();
|
||||
|
||||
TriangleMeshPtrs meshparts = mesh.split();
|
||||
TriangleMeshPtrs meshparts_raw = mesh.split();
|
||||
auto meshparts = reserve_vector<std::unique_ptr<TriangleMesh>>(meshparts_raw.size());
|
||||
for (auto *p : meshparts_raw)
|
||||
meshparts.emplace_back(p);
|
||||
|
||||
auto it = std::remove_if(meshparts.begin(), meshparts.end(),
|
||||
[](TriangleMesh *m){
|
||||
m->require_shared_vertices();
|
||||
return !m->is_manifold() || m->volume() < EPSILON;
|
||||
});
|
||||
auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) {
|
||||
m->require_shared_vertices();
|
||||
return m->volume() < EPSILON;
|
||||
});
|
||||
|
||||
meshparts.erase(it, meshparts.end());
|
||||
|
||||
openvdb::FloatGrid::Ptr grid;
|
||||
for (TriangleMesh *m : meshparts) {
|
||||
for (auto &m : meshparts) {
|
||||
auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||
TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth,
|
||||
TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth,
|
||||
interiorBandWidth, flags);
|
||||
|
||||
if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid);
|
||||
|
@ -106,19 +86,9 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
|
|||
interiorBandWidth, flags);
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale));
|
||||
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh,
|
||||
const openvdb::math::Transform &tr,
|
||||
float exteriorBandWidth,
|
||||
float interiorBandWidth,
|
||||
int flags)
|
||||
{
|
||||
openvdb::initialize();
|
||||
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||
Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth,
|
||||
flags);
|
||||
return grid;
|
||||
}
|
||||
|
||||
template<class Grid>
|
||||
|
@ -128,20 +98,25 @@ sla::Contour3D _volumeToMesh(const Grid &grid,
|
|||
bool relaxDisorientedTriangles)
|
||||
{
|
||||
openvdb::initialize();
|
||||
|
||||
|
||||
std::vector<openvdb::Vec3s> points;
|
||||
std::vector<openvdb::Vec3I> triangles;
|
||||
std::vector<openvdb::Vec4I> quads;
|
||||
|
||||
|
||||
openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue,
|
||||
adaptivity, relaxDisorientedTriangles);
|
||||
|
||||
|
||||
float scale = 1.;
|
||||
try {
|
||||
scale = grid.template metaValue<float>("voxel_scale");
|
||||
} catch (...) { }
|
||||
|
||||
sla::Contour3D ret;
|
||||
ret.points.reserve(points.size());
|
||||
ret.faces3.reserve(triangles.size());
|
||||
ret.faces4.reserve(quads.size());
|
||||
|
||||
for (auto &v : points) ret.points.emplace_back(to_vec3d(v));
|
||||
for (auto &v : points) ret.points.emplace_back(to_vec3d(v) / scale);
|
||||
for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v));
|
||||
for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v));
|
||||
|
||||
|
@ -166,9 +141,18 @@ sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
|
|||
relaxDisorientedTriangles);
|
||||
}
|
||||
|
||||
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir)
|
||||
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
|
||||
double iso,
|
||||
double er,
|
||||
double ir)
|
||||
{
|
||||
return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir));
|
||||
auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso),
|
||||
float(er), float(ir));
|
||||
|
||||
// Copies voxel_scale metadata, if it exists.
|
||||
new_grid->insertMeta(*grid.deepCopyMeta());
|
||||
|
||||
return new_grid;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -21,14 +21,16 @@ inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast<double>
|
|||
inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; }
|
||||
inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; }
|
||||
|
||||
// Here voxel_scale defines the scaling of voxels which affects the voxel count.
|
||||
// 1.0 value means a voxel for every unit cube. 2 means the model is scaled to
|
||||
// be 2x larger and the voxel count is increased by the increment in the scaled
|
||||
// volume, thus 4 times. This kind a sampling accuracy selection is not
|
||||
// achievable through the Transform parameter. (TODO: or is it?)
|
||||
// The resulting grid will contain the voxel_scale in its metadata under the
|
||||
// "voxel_scale" key to be used in grid_to_mesh function.
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh,
|
||||
const openvdb::math::Transform &tr = {},
|
||||
float exteriorBandWidth = 3.0f,
|
||||
float interiorBandWidth = 3.0f,
|
||||
int flags = 0);
|
||||
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D & mesh,
|
||||
const openvdb::math::Transform &tr = {},
|
||||
float voxel_scale = 1.f,
|
||||
float exteriorBandWidth = 3.0f,
|
||||
float interiorBandWidth = 3.0f,
|
||||
int flags = 0);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <tbb/mutex.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_reduce.h>
|
||||
#include <tbb/task_arena.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
@ -76,13 +77,18 @@ template<> struct _ccr<true>
|
|||
from, to, init, std::forward<MergeFn>(mergefn),
|
||||
[](typename I::value_type &i) { return i; }, granularity);
|
||||
}
|
||||
|
||||
static size_t max_concurreny()
|
||||
{
|
||||
return tbb::this_task_arena::max_concurrency();
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct _ccr<false>
|
||||
{
|
||||
private:
|
||||
struct _Mtx { inline void lock() {} inline void unlock() {} };
|
||||
|
||||
|
||||
public:
|
||||
using SpinningMutex = _Mtx;
|
||||
using BlockingMutex = _Mtx;
|
||||
|
@ -133,6 +139,8 @@ public:
|
|||
return reduce(from, to, init, std::forward<MergeFn>(mergefn),
|
||||
[](typename I::value_type &i) { return i; });
|
||||
}
|
||||
|
||||
static size_t max_concurreny() { return 1; }
|
||||
};
|
||||
|
||||
using ccr = _ccr<USE_FULL_CONCURRENCY>;
|
||||
|
|
|
@ -26,64 +26,99 @@ inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); }
|
|||
template<class S, class = FloatingOnly<S>>
|
||||
inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; }
|
||||
|
||||
static TriangleMesh _generate_interior(const TriangleMesh &mesh,
|
||||
const JobController &ctl,
|
||||
double min_thickness,
|
||||
double voxel_scale,
|
||||
double closing_dist)
|
||||
struct Interior {
|
||||
TriangleMesh mesh;
|
||||
openvdb::FloatGrid::Ptr gridptr;
|
||||
mutable std::optional<openvdb::FloatGrid::ConstAccessor> accessor;
|
||||
|
||||
double closing_distance = 0.;
|
||||
double thickness = 0.;
|
||||
double voxel_scale = 1.;
|
||||
double nb_in = 3.; // narrow band width inwards
|
||||
double nb_out = 3.; // narrow band width outwards
|
||||
// Full narrow band is the sum of the two above values.
|
||||
|
||||
void reset_accessor() const // This resets the accessor and its cache
|
||||
// Not a thread safe call!
|
||||
{
|
||||
if (gridptr)
|
||||
accessor = gridptr->getConstAccessor();
|
||||
}
|
||||
};
|
||||
|
||||
void InteriorDeleter::operator()(Interior *p)
|
||||
{
|
||||
delete p;
|
||||
}
|
||||
|
||||
TriangleMesh &get_mesh(Interior &interior)
|
||||
{
|
||||
return interior.mesh;
|
||||
}
|
||||
|
||||
const TriangleMesh &get_mesh(const Interior &interior)
|
||||
{
|
||||
return interior.mesh;
|
||||
}
|
||||
|
||||
static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh,
|
||||
const JobController &ctl,
|
||||
double min_thickness,
|
||||
double voxel_scale,
|
||||
double closing_dist)
|
||||
{
|
||||
TriangleMesh imesh{mesh};
|
||||
|
||||
_scale(voxel_scale, imesh);
|
||||
|
||||
double offset = voxel_scale * min_thickness;
|
||||
double D = voxel_scale * closing_dist;
|
||||
float out_range = 0.1f * float(offset);
|
||||
float in_range = 1.1f * float(offset + D);
|
||||
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(0, L("Hollowing"));
|
||||
|
||||
auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range);
|
||||
|
||||
|
||||
auto gridptr = mesh_to_grid(mesh, {}, voxel_scale, out_range, in_range);
|
||||
|
||||
assert(gridptr);
|
||||
|
||||
|
||||
if (!gridptr) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL";
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(30, L("Hollowing"));
|
||||
|
||||
if (closing_dist > .0) {
|
||||
gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range));
|
||||
} else {
|
||||
D = -offset;
|
||||
}
|
||||
|
||||
|
||||
double iso_surface = D;
|
||||
auto narrowb = double(in_range);
|
||||
gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb);
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(70, L("Hollowing"));
|
||||
|
||||
double iso_surface = D;
|
||||
|
||||
double adaptivity = 0.;
|
||||
auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
|
||||
|
||||
_scale(1. / voxel_scale, omesh);
|
||||
|
||||
InteriorPtr interior = InteriorPtr{new Interior{}};
|
||||
|
||||
interior->mesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
|
||||
interior->gridptr = gridptr;
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(100, L("Hollowing"));
|
||||
|
||||
return omesh;
|
||||
|
||||
interior->closing_distance = D;
|
||||
interior->thickness = offset;
|
||||
interior->voxel_scale = voxel_scale;
|
||||
interior->nb_in = narrowb;
|
||||
interior->nb_out = narrowb;
|
||||
|
||||
return interior;
|
||||
}
|
||||
|
||||
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
|
||||
const HollowingConfig &hc,
|
||||
const JobController & ctl)
|
||||
InteriorPtr generate_interior(const TriangleMesh & mesh,
|
||||
const HollowingConfig &hc,
|
||||
const JobController & ctl)
|
||||
{
|
||||
static const double MIN_OVERSAMPL = 3.;
|
||||
static const double MAX_OVERSAMPL = 8.;
|
||||
|
||||
|
||||
// I can't figure out how to increase the grid resolution through openvdb
|
||||
// API so the model will be scaled up before conversion and the result
|
||||
// scaled down. Voxels have a unit size. If I set voxelSize smaller, it
|
||||
|
@ -92,26 +127,29 @@ std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
|
|||
//
|
||||
// max 8x upscale, min is native voxel size
|
||||
auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality;
|
||||
auto meshptr = std::make_unique<TriangleMesh>(
|
||||
_generate_interior(mesh, ctl, hc.min_thickness, voxel_scale,
|
||||
hc.closing_distance));
|
||||
|
||||
if (meshptr && !meshptr->empty()) {
|
||||
|
||||
|
||||
InteriorPtr interior =
|
||||
generate_interior_verbose(mesh, ctl, hc.min_thickness, voxel_scale,
|
||||
hc.closing_distance);
|
||||
|
||||
if (interior && !interior->mesh.empty()) {
|
||||
|
||||
// This flips the normals to be outward facing...
|
||||
meshptr->require_shared_vertices();
|
||||
indexed_triangle_set its = std::move(meshptr->its);
|
||||
|
||||
interior->mesh.require_shared_vertices();
|
||||
indexed_triangle_set its = std::move(interior->mesh.its);
|
||||
|
||||
Slic3r::simplify_mesh(its);
|
||||
|
||||
|
||||
// flip normals back...
|
||||
for (stl_triangle_vertex_indices &ind : its.indices)
|
||||
std::swap(ind(0), ind(2));
|
||||
|
||||
*meshptr = Slic3r::TriangleMesh{its};
|
||||
|
||||
interior->mesh = Slic3r::TriangleMesh{its};
|
||||
interior->mesh.repaired = true;
|
||||
interior->mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
return meshptr;
|
||||
|
||||
return interior;
|
||||
}
|
||||
|
||||
Contour3D DrainHole::to_mesh() const
|
||||
|
@ -273,12 +311,264 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
|||
obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]);
|
||||
}
|
||||
|
||||
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg)
|
||||
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags)
|
||||
{
|
||||
std::unique_ptr<Slic3r::TriangleMesh> inter_ptr =
|
||||
Slic3r::sla::generate_interior(mesh);
|
||||
InteriorPtr interior = generate_interior(mesh, cfg, JobController{});
|
||||
if (!interior) return;
|
||||
|
||||
if (inter_ptr) mesh.merge(*inter_ptr);
|
||||
hollow_mesh(mesh, *interior, flags);
|
||||
}
|
||||
|
||||
void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags)
|
||||
{
|
||||
if (mesh.empty() || interior.mesh.empty()) return;
|
||||
|
||||
if (flags & hfRemoveInsideTriangles && interior.gridptr)
|
||||
remove_inside_triangles(mesh, interior);
|
||||
|
||||
mesh.merge(interior.mesh);
|
||||
mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
// Get the distance of p to the interior's zero iso_surface. Interior should
|
||||
// have its zero isosurface positioned at offset + closing_distance inwards form
|
||||
// the model surface.
|
||||
static double get_distance_raw(const Vec3f &p, const Interior &interior)
|
||||
{
|
||||
assert(interior.gridptr);
|
||||
|
||||
if (!interior.accessor) interior.reset_accessor();
|
||||
|
||||
auto v = (p * interior.voxel_scale).cast<double>();
|
||||
auto grididx = interior.gridptr->transform().worldToIndexCellCentered(
|
||||
{v.x(), v.y(), v.z()});
|
||||
|
||||
return interior.accessor->getValue(grididx) ;
|
||||
}
|
||||
|
||||
struct TriangleBubble { Vec3f center; double R; };
|
||||
|
||||
// Return the distance of bubble center to the interior boundary or NaN if the
|
||||
// triangle is too big to be measured.
|
||||
static double get_distance(const TriangleBubble &b, const Interior &interior)
|
||||
{
|
||||
double R = b.R * interior.voxel_scale;
|
||||
double D = get_distance_raw(b.center, interior);
|
||||
|
||||
return (D > 0. && R >= interior.nb_out) ||
|
||||
(D < 0. && R >= interior.nb_in) ||
|
||||
((D - R) < 0. && 2 * R > interior.thickness) ?
|
||||
std::nan("") :
|
||||
// FIXME: Adding interior.voxel_scale is a compromise supposed
|
||||
// to prevent the deletion of the triangles forming the interior
|
||||
// itself. This has a side effect that a small portion of the
|
||||
// bad triangles will still be visible.
|
||||
D - interior.closing_distance /*+ 2 * interior.voxel_scale*/;
|
||||
}
|
||||
|
||||
double get_distance(const Vec3f &p, const Interior &interior)
|
||||
{
|
||||
double d = get_distance_raw(p, interior) - interior.closing_distance;
|
||||
return d / interior.voxel_scale;
|
||||
}
|
||||
|
||||
// A face that can be divided. Stores the indices into the original mesh if its
|
||||
// part of that mesh and the vertices it consists of.
|
||||
enum { NEW_FACE = -1};
|
||||
struct DivFace {
|
||||
Vec3i indx;
|
||||
std::array<Vec3f, 3> verts;
|
||||
long faceid = NEW_FACE;
|
||||
long parent = NEW_FACE;
|
||||
};
|
||||
|
||||
// Divide a face recursively and call visitor on all the sub-faces.
|
||||
template<class Fn>
|
||||
void divide_triangle(const DivFace &face, Fn &&visitor)
|
||||
{
|
||||
std::array<Vec3f, 3> edges = {(face.verts[0] - face.verts[1]),
|
||||
(face.verts[1] - face.verts[2]),
|
||||
(face.verts[2] - face.verts[0])};
|
||||
|
||||
std::array<size_t, 3> edgeidx = {0, 1, 2};
|
||||
|
||||
std::sort(edgeidx.begin(), edgeidx.end(), [&edges](size_t e1, size_t e2) {
|
||||
return edges[e1].squaredNorm() > edges[e2].squaredNorm();
|
||||
});
|
||||
|
||||
DivFace child1, child2;
|
||||
|
||||
child1.parent = face.faceid == NEW_FACE ? face.parent : face.faceid;
|
||||
child1.indx(0) = -1;
|
||||
child1.indx(1) = face.indx(edgeidx[1]);
|
||||
child1.indx(2) = face.indx((edgeidx[1] + 1) % 3);
|
||||
child1.verts[0] = (face.verts[edgeidx[0]] + face.verts[(edgeidx[0] + 1) % 3]) / 2.;
|
||||
child1.verts[1] = face.verts[edgeidx[1]];
|
||||
child1.verts[2] = face.verts[(edgeidx[1] + 1) % 3];
|
||||
|
||||
if (visitor(child1))
|
||||
divide_triangle(child1, std::forward<Fn>(visitor));
|
||||
|
||||
child2.parent = face.faceid == NEW_FACE ? face.parent : face.faceid;
|
||||
child2.indx(0) = -1;
|
||||
child2.indx(1) = face.indx(edgeidx[2]);
|
||||
child2.indx(2) = face.indx((edgeidx[2] + 1) % 3);
|
||||
child2.verts[0] = child1.verts[0];
|
||||
child2.verts[1] = face.verts[edgeidx[2]];
|
||||
child2.verts[2] = face.verts[(edgeidx[2] + 1) % 3];
|
||||
|
||||
if (visitor(child2))
|
||||
divide_triangle(child2, std::forward<Fn>(visitor));
|
||||
}
|
||||
|
||||
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
|
||||
const std::vector<bool> &exclude_mask)
|
||||
{
|
||||
enum TrPos { posInside, posTouch, posOutside };
|
||||
|
||||
auto &faces = mesh.its.indices;
|
||||
auto &vertices = mesh.its.vertices;
|
||||
auto bb = mesh.bounding_box();
|
||||
|
||||
bool use_exclude_mask = faces.size() == exclude_mask.size();
|
||||
auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) {
|
||||
return use_exclude_mask && exclude_mask[face_id];
|
||||
};
|
||||
|
||||
// TODO: Parallel mode not working yet
|
||||
using exec_policy = ccr_seq;
|
||||
|
||||
// Info about the needed modifications on the input mesh.
|
||||
struct MeshMods {
|
||||
|
||||
// Just a thread safe wrapper for a vector of triangles.
|
||||
struct {
|
||||
std::vector<std::array<Vec3f, 3>> data;
|
||||
exec_policy::SpinningMutex mutex;
|
||||
|
||||
void emplace_back(const std::array<Vec3f, 3> &pts)
|
||||
{
|
||||
std::lock_guard lk{mutex};
|
||||
data.emplace_back(pts);
|
||||
}
|
||||
|
||||
size_t size() const { return data.size(); }
|
||||
const std::array<Vec3f, 3>& operator[](size_t idx) const
|
||||
{
|
||||
return data[idx];
|
||||
}
|
||||
|
||||
} new_triangles;
|
||||
|
||||
// A vector of bool for all faces signaling if it needs to be removed
|
||||
// or not.
|
||||
std::vector<bool> to_remove;
|
||||
|
||||
MeshMods(const TriangleMesh &mesh):
|
||||
to_remove(mesh.its.indices.size(), false) {}
|
||||
|
||||
// Number of triangles that need to be removed.
|
||||
size_t to_remove_cnt() const
|
||||
{
|
||||
return std::accumulate(to_remove.begin(), to_remove.end(), size_t(0));
|
||||
}
|
||||
|
||||
} mesh_mods{mesh};
|
||||
|
||||
// Must return true if further division of the face is needed.
|
||||
auto divfn = [&interior, bb, &mesh_mods](const DivFace &f) {
|
||||
BoundingBoxf3 facebb { f.verts.begin(), f.verts.end() };
|
||||
|
||||
// Face is certainly outside the cavity
|
||||
if (! facebb.intersects(bb) && f.faceid != NEW_FACE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TriangleBubble bubble{facebb.center().cast<float>(), facebb.radius()};
|
||||
|
||||
double D = get_distance(bubble, interior);
|
||||
double R = bubble.R * interior.voxel_scale;
|
||||
|
||||
if (std::isnan(D)) // The distance cannot be measured, triangle too big
|
||||
return true;
|
||||
|
||||
// Distance of the bubble wall to the interior wall. Negative if the
|
||||
// bubble is overlapping with the interior
|
||||
double bubble_distance = D - R;
|
||||
|
||||
// The face is crossing the interior or inside, it must be removed and
|
||||
// parts of it re-added, that are outside the interior
|
||||
if (bubble_distance < 0.) {
|
||||
if (f.faceid != NEW_FACE)
|
||||
mesh_mods.to_remove[f.faceid] = true;
|
||||
|
||||
if (f.parent != NEW_FACE) // Top parent needs to be removed as well
|
||||
mesh_mods.to_remove[f.parent] = true;
|
||||
|
||||
// If the outside part is between the interior end the exterior
|
||||
// (inside the wall being invisible), no further division is needed.
|
||||
if ((R + D) < interior.thickness)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
} else if (f.faceid == NEW_FACE) {
|
||||
// New face completely outside needs to be re-added.
|
||||
mesh_mods.new_triangles.emplace_back(f.verts);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
interior.reset_accessor();
|
||||
|
||||
exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) {
|
||||
const Vec3i &face = faces[face_idx];
|
||||
|
||||
// If the triangle is excluded, we need to keep it.
|
||||
if (is_excluded(face_idx))
|
||||
return;
|
||||
|
||||
std::array<Vec3f, 3> pts =
|
||||
{ vertices[face(0)], vertices[face(1)], vertices[face(2)] };
|
||||
|
||||
BoundingBoxf3 facebb { pts.begin(), pts.end() };
|
||||
|
||||
// Face is certainly outside the cavity
|
||||
if (! facebb.intersects(bb)) return;
|
||||
|
||||
DivFace df{face, pts, long(face_idx)};
|
||||
|
||||
if (divfn(df))
|
||||
divide_triangle(df, divfn);
|
||||
|
||||
}, exec_policy::max_concurreny());
|
||||
|
||||
auto new_faces = reserve_vector<Vec3i>(faces.size() +
|
||||
mesh_mods.new_triangles.size());
|
||||
|
||||
for (size_t face_idx = 0; face_idx < faces.size(); ++face_idx) {
|
||||
if (!mesh_mods.to_remove[face_idx])
|
||||
new_faces.emplace_back(faces[face_idx]);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < mesh_mods.new_triangles.size(); ++i) {
|
||||
size_t o = vertices.size();
|
||||
vertices.emplace_back(mesh_mods.new_triangles[i][0]);
|
||||
vertices.emplace_back(mesh_mods.new_triangles[i][1]);
|
||||
vertices.emplace_back(mesh_mods.new_triangles[i][2]);
|
||||
new_faces.emplace_back(int(o), int(o + 1), int(o + 2));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info)
|
||||
<< "Trimming: " << mesh_mods.to_remove_cnt() << " triangles removed";
|
||||
BOOST_LOG_TRIVIAL(info)
|
||||
<< "Trimming: " << mesh_mods.new_triangles.size() << " triangles added";
|
||||
|
||||
faces.swap(new_faces);
|
||||
new_faces = {};
|
||||
|
||||
mesh = TriangleMesh{mesh.its};
|
||||
mesh.repaired = true;
|
||||
mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,17 @@ struct HollowingConfig
|
|||
bool enabled = true;
|
||||
};
|
||||
|
||||
enum HollowingFlags { hfRemoveInsideTriangles = 0x1 };
|
||||
|
||||
// All data related to a generated mesh interior. Includes the 3D grid and mesh
|
||||
// and various metadata. No need to manipulate from outside.
|
||||
struct Interior;
|
||||
struct InteriorDeleter { void operator()(Interior *p); };
|
||||
using InteriorPtr = std::unique_ptr<Interior, InteriorDeleter>;
|
||||
|
||||
TriangleMesh & get_mesh(Interior &interior);
|
||||
const TriangleMesh &get_mesh(const Interior &interior);
|
||||
|
||||
struct DrainHole
|
||||
{
|
||||
Vec3f pos;
|
||||
|
@ -60,11 +71,26 @@ using DrainHoles = std::vector<DrainHole>;
|
|||
|
||||
constexpr float HoleStickOutLength = 1.f;
|
||||
|
||||
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh,
|
||||
const HollowingConfig & = {},
|
||||
const JobController &ctl = {});
|
||||
InteriorPtr generate_interior(const TriangleMesh &mesh,
|
||||
const HollowingConfig & = {},
|
||||
const JobController &ctl = {});
|
||||
|
||||
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg);
|
||||
// Will do the hollowing
|
||||
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0);
|
||||
|
||||
// Hollowing prepared in "interior", merge with original mesh
|
||||
void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0);
|
||||
|
||||
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
|
||||
const std::vector<bool> &exclude_mask = {});
|
||||
|
||||
double get_distance(const Vec3f &p, const Interior &interior);
|
||||
|
||||
template<class T>
|
||||
FloatingOnly<T> get_distance(const Vec<3, T> &p, const Interior &interior)
|
||||
{
|
||||
return get_distance(Vec3f(p.template cast<float>()), interior);
|
||||
}
|
||||
|
||||
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
||||
const std::vector<float> &slicegrid,
|
||||
|
|
|
@ -1120,7 +1120,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
|
|||
return this->pad_mesh();
|
||||
case slaposDrillHoles:
|
||||
if (m_hollowing_data)
|
||||
return m_hollowing_data->hollow_mesh_with_holes;
|
||||
return get_mesh_to_print();
|
||||
[[fallthrough]];
|
||||
default:
|
||||
return TriangleMesh();
|
||||
|
@ -1149,8 +1149,9 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const
|
|||
|
||||
const TriangleMesh &SLAPrintObject::hollowed_interior_mesh() const
|
||||
{
|
||||
if (m_hollowing_data && m_config.hollowing_enable.getBool())
|
||||
return m_hollowing_data->interior;
|
||||
if (m_hollowing_data && m_hollowing_data->interior &&
|
||||
m_config.hollowing_enable.getBool())
|
||||
return sla::get_mesh(*m_hollowing_data->interior);
|
||||
|
||||
return EMPTY_MESH;
|
||||
}
|
||||
|
|
|
@ -85,6 +85,10 @@ public:
|
|||
// Get the mesh that is going to be printed with all the modifications
|
||||
// like hollowing and drilled holes.
|
||||
const TriangleMesh & get_mesh_to_print() const {
|
||||
return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh();
|
||||
}
|
||||
|
||||
const TriangleMesh & get_mesh_to_slice() const {
|
||||
return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh();
|
||||
}
|
||||
|
||||
|
@ -327,9 +331,10 @@ private:
|
|||
class HollowingData
|
||||
{
|
||||
public:
|
||||
|
||||
TriangleMesh interior;
|
||||
|
||||
sla::InteriorPtr interior;
|
||||
mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh
|
||||
mutable TriangleMesh hollow_mesh_with_holes_trimmed;
|
||||
};
|
||||
|
||||
std::unique_ptr<HollowingData> m_hollowing_data;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include <unordered_set>
|
||||
|
||||
#include <libslic3r/Exception.hpp>
|
||||
#include <libslic3r/SLAPrintSteps.hpp>
|
||||
#include <libslic3r/MeshBoolean.hpp>
|
||||
|
@ -84,17 +86,17 @@ SLAPrint::Steps::Steps(SLAPrint *print)
|
|||
void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin o)
|
||||
{
|
||||
if (o == soSupport && !po.m_supportdata) return;
|
||||
|
||||
|
||||
auto faded_lyrs = size_t(po.m_config.faded_layers.getInt());
|
||||
double min_w = m_print->m_printer_config.elefant_foot_min_width.getFloat() / 2.;
|
||||
double start_efc = m_print->m_printer_config.elefant_foot_compensation.getFloat();
|
||||
|
||||
|
||||
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
|
||||
coord_t clpr_offs = scaled(doffs);
|
||||
|
||||
|
||||
faded_lyrs = std::min(po.m_slice_index.size(), faded_lyrs);
|
||||
size_t faded_lyrs_efc = std::max(size_t(1), faded_lyrs - 1);
|
||||
|
||||
|
||||
auto efc = [start_efc, faded_lyrs_efc](size_t pos) {
|
||||
return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc;
|
||||
};
|
||||
|
@ -102,13 +104,13 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin
|
|||
std::vector<ExPolygons> &slices = o == soModel ?
|
||||
po.m_model_slices :
|
||||
po.m_supportdata->support_slices;
|
||||
|
||||
|
||||
if (clpr_offs != 0) for (size_t i = 0; i < po.m_slice_index.size(); ++i) {
|
||||
size_t idx = po.m_slice_index[i].get_slice_idx(o);
|
||||
if (idx < slices.size())
|
||||
slices[idx] = offset_ex(slices[idx], float(clpr_offs));
|
||||
}
|
||||
|
||||
|
||||
if (start_efc > 0.) for (size_t i = 0; i < faded_lyrs; ++i) {
|
||||
size_t idx = po.m_slice_index[i].get_slice_idx(o);
|
||||
if (idx < slices.size())
|
||||
|
@ -124,28 +126,157 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
|
|||
BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
|
||||
|
||||
double thickness = po.m_config.hollowing_min_thickness.getFloat();
|
||||
double quality = po.m_config.hollowing_quality.getFloat();
|
||||
double closing_d = po.m_config.hollowing_closing_distance.getFloat();
|
||||
sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
|
||||
auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg);
|
||||
|
||||
if (meshptr->empty())
|
||||
sla::InteriorPtr interior = generate_interior(po.transformed_mesh(), hlwcfg);
|
||||
|
||||
if (!interior || sla::get_mesh(*interior).empty())
|
||||
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
|
||||
else {
|
||||
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
|
||||
po.m_hollowing_data->interior = *meshptr;
|
||||
po.m_hollowing_data->interior = std::move(interior);
|
||||
}
|
||||
}
|
||||
|
||||
struct FaceHash {
|
||||
|
||||
// A hash is created for each triangle to be identifiable. The hash uses
|
||||
// only the triangle's geometric traits, not the index in a particular mesh.
|
||||
std::unordered_set<std::string> facehash;
|
||||
|
||||
static std::string facekey(const Vec3i &face,
|
||||
const std::vector<Vec3f> &vertices)
|
||||
{
|
||||
// Scale to integer to avoid floating points
|
||||
std::array<Vec<3, int64_t>, 3> pts = {
|
||||
scaled<int64_t>(vertices[face(0)]),
|
||||
scaled<int64_t>(vertices[face(1)]),
|
||||
scaled<int64_t>(vertices[face(2)])
|
||||
};
|
||||
|
||||
// Get the first two sides of the triangle, do a cross product and move
|
||||
// that vector to the center of the triangle. This encodes all
|
||||
// information to identify an identical triangle at the same position.
|
||||
Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2];
|
||||
Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3;
|
||||
|
||||
// Return a concatenated string representation of the coordinates
|
||||
return std::to_string(c(0)) + std::to_string(c(1)) + std::to_string(c(2));
|
||||
};
|
||||
|
||||
FaceHash(const indexed_triangle_set &its)
|
||||
{
|
||||
for (const Vec3i &face : its.indices) {
|
||||
std::string keystr = facekey(face, its.vertices);
|
||||
facehash.insert(keystr);
|
||||
}
|
||||
}
|
||||
|
||||
bool find(const std::string &key)
|
||||
{
|
||||
auto it = facehash.find(key);
|
||||
return it != facehash.end();
|
||||
}
|
||||
};
|
||||
|
||||
// Create exclude mask for triangle removal inside hollowed interiors.
|
||||
// This is necessary when the interior is already part of the mesh which was
|
||||
// drilled using CGAL mesh boolean operation. Excluded will be the triangles
|
||||
// originally part of the interior mesh and triangles that make up the drilled
|
||||
// hole walls.
|
||||
static std::vector<bool> create_exclude_mask(
|
||||
const indexed_triangle_set &its,
|
||||
const sla::Interior &interior,
|
||||
const std::vector<sla::DrainHole> &holes)
|
||||
{
|
||||
FaceHash interior_hash{sla::get_mesh(interior).its};
|
||||
|
||||
std::vector<bool> exclude_mask(its.indices.size(), false);
|
||||
|
||||
std::vector< std::vector<size_t> > neighbor_index =
|
||||
create_neighbor_index(its);
|
||||
|
||||
auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face)
|
||||
{
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const std::vector<size_t> &neighbors = neighbor_index[face(i)];
|
||||
for (size_t fi_n : neighbors) exclude_mask[fi_n] = true;
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t fi = 0; fi < its.indices.size(); ++fi) {
|
||||
auto &face = its.indices[fi];
|
||||
|
||||
if (interior_hash.find(FaceHash::facekey(face, its.vertices))) {
|
||||
exclude_mask[fi] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (exclude_mask[fi]) {
|
||||
exclude_neighbors(face);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Lets deal with the holes. All the triangles of a hole and all the
|
||||
// neighbors of these triangles need to be kept. The neigbors were
|
||||
// created by CGAL mesh boolean operation that modified the original
|
||||
// interior inside the input mesh to contain the holes.
|
||||
Vec3d tr_center = (
|
||||
its.vertices[face(0)] +
|
||||
its.vertices[face(1)] +
|
||||
its.vertices[face(2)]
|
||||
).cast<double>() / 3.;
|
||||
|
||||
// If the center is more than half a mm inside the interior,
|
||||
// it cannot possibly be part of a hole wall.
|
||||
if (sla::get_distance(tr_center, interior) < -0.5)
|
||||
continue;
|
||||
|
||||
Vec3f U = its.vertices[face(1)] - its.vertices[face(0)];
|
||||
Vec3f V = its.vertices[face(2)] - its.vertices[face(0)];
|
||||
Vec3f C = U.cross(V);
|
||||
Vec3f face_normal = C.normalized();
|
||||
|
||||
for (const sla::DrainHole &dh : holes) {
|
||||
Vec3d dhpos = dh.pos.cast<double>();
|
||||
Vec3d dhend = dhpos + dh.normal.cast<double>() * dh.height;
|
||||
|
||||
Linef3 holeaxis{dhpos, dhend};
|
||||
|
||||
double D_hole_center = line_alg::distance_to(holeaxis, tr_center);
|
||||
double D_hole = std::abs(D_hole_center - dh.radius);
|
||||
float dot = dh.normal.dot(face_normal);
|
||||
|
||||
// Empiric tolerances for center distance and normals angle.
|
||||
// For triangles that are part of a hole wall the angle of
|
||||
// triangle normal and the hole axis is around 90 degrees,
|
||||
// so the dot product is around zero.
|
||||
double D_tol = dh.radius / sla::DrainHole::steps;
|
||||
float normal_angle_tol = 1.f / sla::DrainHole::steps;
|
||||
|
||||
if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) {
|
||||
exclude_mask[fi] = true;
|
||||
exclude_neighbors(face);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return exclude_mask;
|
||||
}
|
||||
|
||||
// Drill holes into the hollowed/original mesh.
|
||||
void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
||||
{
|
||||
bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty();
|
||||
bool is_hollowed = (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty());
|
||||
bool is_hollowed =
|
||||
(po.m_hollowing_data && po.m_hollowing_data->interior &&
|
||||
!sla::get_mesh(*po.m_hollowing_data->interior).empty());
|
||||
|
||||
if (! is_hollowed && ! needs_drilling) {
|
||||
// In this case we can dump any data that might have been
|
||||
|
@ -163,19 +294,25 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
|||
// holes that are no longer on the frontend.
|
||||
TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
|
||||
hollowed_mesh = po.transformed_mesh();
|
||||
if (! po.m_hollowing_data->interior.empty()) {
|
||||
hollowed_mesh.merge(po.m_hollowing_data->interior);
|
||||
hollowed_mesh.require_shared_vertices();
|
||||
}
|
||||
if (is_hollowed)
|
||||
sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior);
|
||||
|
||||
TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed;
|
||||
|
||||
if (! needs_drilling) {
|
||||
mesh_view = po.transformed_mesh();
|
||||
|
||||
if (is_hollowed)
|
||||
sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior,
|
||||
sla::hfRemoveInsideTriangles);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes).";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes.";
|
||||
sla::DrainHoles drainholes = po.transformed_drainhole_points();
|
||||
|
||||
|
||||
std::uniform_real_distribution<float> dist(0., float(EPSILON));
|
||||
auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({});
|
||||
for (sla::DrainHole holept : drainholes) {
|
||||
|
@ -187,15 +324,25 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
|||
auto cgal_m = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
|
||||
MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_m);
|
||||
}
|
||||
|
||||
|
||||
if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal))
|
||||
throw Slic3r::SlicingError(L("Too many overlapping holes."));
|
||||
|
||||
|
||||
auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh);
|
||||
|
||||
|
||||
try {
|
||||
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
|
||||
hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal);
|
||||
mesh_view = hollowed_mesh;
|
||||
|
||||
if (is_hollowed) {
|
||||
auto &interior = *po.m_hollowing_data->interior;
|
||||
std::vector<bool> exclude_mask =
|
||||
create_exclude_mask(mesh_view.its, interior, drainholes);
|
||||
|
||||
sla::remove_inside_triangles(mesh_view, interior, exclude_mask);
|
||||
}
|
||||
|
||||
} catch (const std::runtime_error &) {
|
||||
throw Slic3r::SlicingError(L(
|
||||
"Drilling holes into the mesh failed. "
|
||||
|
@ -212,11 +359,11 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
|||
// of it. In any case, the model and the supports have to be sliced in the
|
||||
// same imaginary grid (the height vector argument to TriangleMeshSlicer).
|
||||
void SLAPrint::Steps::slice_model(SLAPrintObject &po)
|
||||
{
|
||||
const TriangleMesh &mesh = po.get_mesh_to_print();
|
||||
{
|
||||
const TriangleMesh &mesh = po.get_mesh_to_slice();
|
||||
|
||||
// We need to prepare the slice index...
|
||||
|
||||
|
||||
double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat();
|
||||
float lh = float(lhd);
|
||||
coord_t lhs = scaled(lhd);
|
||||
|
@ -226,43 +373,49 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
|
|||
auto minZf = float(minZ);
|
||||
coord_t minZs = scaled(minZ);
|
||||
coord_t maxZs = scaled(maxZ);
|
||||
|
||||
|
||||
po.m_slice_index.clear();
|
||||
|
||||
|
||||
size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs);
|
||||
po.m_slice_index.reserve(cap);
|
||||
|
||||
|
||||
po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh);
|
||||
|
||||
|
||||
for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs)
|
||||
po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh);
|
||||
|
||||
|
||||
// Just get the first record that is from the model:
|
||||
auto slindex_it =
|
||||
po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z)));
|
||||
|
||||
|
||||
if(slindex_it == po.m_slice_index.end())
|
||||
//TRN To be shown at the status bar on SLA slicing error.
|
||||
throw Slic3r::RuntimeError(
|
||||
L("Slicing had to be stopped due to an internal error: "
|
||||
"Inconsistent slice index."));
|
||||
|
||||
|
||||
po.m_model_height_levels.clear();
|
||||
po.m_model_height_levels.reserve(po.m_slice_index.size());
|
||||
for(auto it = slindex_it; it != po.m_slice_index.end(); ++it)
|
||||
po.m_model_height_levels.emplace_back(it->slice_level());
|
||||
|
||||
|
||||
TriangleMeshSlicer slicer(&mesh);
|
||||
|
||||
|
||||
po.m_model_slices.clear();
|
||||
float closing_r = float(po.config().slice_closing_radius.value);
|
||||
auto thr = [this]() { m_print->throw_if_canceled(); };
|
||||
auto &slice_grid = po.m_model_height_levels;
|
||||
slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr);
|
||||
|
||||
if (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()) {
|
||||
po.m_hollowing_data->interior.repair(true);
|
||||
TriangleMeshSlicer interior_slicer(&po.m_hollowing_data->interior);
|
||||
|
||||
sla::Interior *interior = po.m_hollowing_data ?
|
||||
po.m_hollowing_data->interior.get() :
|
||||
nullptr;
|
||||
|
||||
if (interior && ! sla::get_mesh(*interior).empty()) {
|
||||
TriangleMesh interiormesh = sla::get_mesh(*interior);
|
||||
interiormesh.repaired = false;
|
||||
interiormesh.repair(true);
|
||||
TriangleMeshSlicer interior_slicer(&interiormesh);
|
||||
std::vector<ExPolygons> interior_slices;
|
||||
interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr);
|
||||
|
||||
|
@ -273,17 +426,17 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
|
|||
diff_ex(po.m_model_slices[i], slice);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
auto mit = slindex_it;
|
||||
for (size_t id = 0;
|
||||
id < po.m_model_slices.size() && mit != po.m_slice_index.end();
|
||||
id++) {
|
||||
mit->set_model_slice_idx(po, id); ++mit;
|
||||
}
|
||||
|
||||
|
||||
// We apply the printer correction offset here.
|
||||
apply_printer_corrections(po, soModel);
|
||||
|
||||
|
||||
if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool())
|
||||
{
|
||||
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
|
||||
|
@ -296,22 +449,22 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
|
|||
{
|
||||
// If supports are disabled, we can skip the model scan.
|
||||
if(!po.m_config.supports_enable.getBool()) return;
|
||||
|
||||
const TriangleMesh &mesh = po.get_mesh_to_print();
|
||||
|
||||
|
||||
const TriangleMesh &mesh = po.get_mesh_to_slice();
|
||||
|
||||
if (!po.m_supportdata)
|
||||
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
|
||||
|
||||
|
||||
const ModelObject& mo = *po.m_model_object;
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Support point count "
|
||||
<< mo.sla_support_points.size();
|
||||
|
||||
|
||||
// Unless the user modified the points or we already did the calculation,
|
||||
// we will do the autoplacement. Otherwise we will just blindly copy the
|
||||
// frontend data into the backend cache.
|
||||
if (mo.sla_points_status != sla::PointsStatus::UserModified) {
|
||||
|
||||
|
||||
// calculate heights of slices (slices are calculated already)
|
||||
const std::vector<float>& heights = po.m_model_height_levels;
|
||||
|
||||
|
@ -319,27 +472,27 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
|
|||
// calculated on slices, the algorithm then raycasts the points
|
||||
// so they actually lie on the mesh.
|
||||
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
|
||||
|
||||
|
||||
throw_if_canceled();
|
||||
sla::SupportPointGenerator::Config config;
|
||||
const SLAPrintObjectConfig& cfg = po.config();
|
||||
|
||||
|
||||
// the density config value is in percents:
|
||||
config.density_relative = float(cfg.support_points_density_relative / 100.f);
|
||||
config.minimal_distance = float(cfg.support_points_minimal_distance);
|
||||
config.head_diameter = float(cfg.support_head_front_diameter);
|
||||
|
||||
|
||||
// scaling for the sub operations
|
||||
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
|
||||
double init = current_status();
|
||||
|
||||
|
||||
auto statuscb = [this, d, init](unsigned st)
|
||||
{
|
||||
double current = init + st * d;
|
||||
if(std::round(current_status()) < std::round(current))
|
||||
report_status(current, OBJ_STEP_LABELS(slaposSupportPoints));
|
||||
};
|
||||
|
||||
|
||||
// Construction of this object does the calculation.
|
||||
throw_if_canceled();
|
||||
sla::SupportPointGenerator auto_supports(
|
||||
|
@ -350,10 +503,10 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
|
|||
const std::vector<sla::SupportPoint>& points = auto_supports.output();
|
||||
throw_if_canceled();
|
||||
po.m_supportdata->pts = points;
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
|
||||
<< po.m_supportdata->pts.size();
|
||||
|
||||
|
||||
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
|
||||
// the update status to GLGizmoSlaSupports
|
||||
report_status(-1, L("Generating support points"),
|
||||
|
@ -368,9 +521,9 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
|
|||
void SLAPrint::Steps::support_tree(SLAPrintObject &po)
|
||||
{
|
||||
if(!po.m_supportdata) return;
|
||||
|
||||
|
||||
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
|
||||
|
||||
|
||||
if (pcfg.embed_object)
|
||||
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
|
||||
|
||||
|
@ -380,15 +533,15 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
|
|||
remove_bottom_points(po.m_supportdata->pts,
|
||||
float(po.m_supportdata->emesh.ground_level() + EPSILON));
|
||||
}
|
||||
|
||||
|
||||
po.m_supportdata->cfg = make_support_cfg(po.m_config);
|
||||
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
|
||||
|
||||
|
||||
// scaling for the sub operations
|
||||
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
|
||||
double init = current_status();
|
||||
sla::JobController ctl;
|
||||
|
||||
|
||||
ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) {
|
||||
double current = init + st * d;
|
||||
if (std::round(current_status()) < std::round(current))
|
||||
|
@ -397,26 +550,26 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
|
|||
};
|
||||
ctl.stopcondition = [this]() { return canceled(); };
|
||||
ctl.cancelfn = [this]() { throw_if_canceled(); };
|
||||
|
||||
|
||||
po.m_supportdata->create_support_tree(ctl);
|
||||
|
||||
|
||||
if (!po.m_config.supports_enable.getBool()) return;
|
||||
|
||||
|
||||
throw_if_canceled();
|
||||
|
||||
|
||||
// Create the unified mesh
|
||||
auto rc = SlicingStatus::RELOAD_SCENE;
|
||||
|
||||
|
||||
// This is to prevent "Done." being displayed during merged_mesh()
|
||||
report_status(-1, L("Visualizing supports"));
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
|
||||
<< po.m_supportdata->pts.size();
|
||||
|
||||
|
||||
// Check the mesh for later troubleshooting.
|
||||
if(po.support_mesh().empty())
|
||||
BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty";
|
||||
|
||||
|
||||
report_status(-1, L("Visualizing supports"), rc);
|
||||
}
|
||||
|
||||
|
@ -424,15 +577,15 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
|
|||
// this step can only go after the support tree has been created
|
||||
// and before the supports had been sliced. (or the slicing has to be
|
||||
// repeated)
|
||||
|
||||
|
||||
if(po.m_config.pad_enable.getBool()) {
|
||||
// Get the distilled pad configuration from the config
|
||||
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
|
||||
|
||||
|
||||
ExPolygons bp; // This will store the base plate of the pad.
|
||||
double pad_h = pcfg.full_height();
|
||||
const TriangleMesh &trmesh = po.transformed_mesh();
|
||||
|
||||
|
||||
if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) {
|
||||
// No support (thus no elevation) or zero elevation mode
|
||||
// we sometimes call it "builtin pad" is enabled so we will
|
||||
|
@ -442,19 +595,19 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
|
|||
float(po.m_config.layer_height.getFloat()),
|
||||
[this](){ throw_if_canceled(); });
|
||||
}
|
||||
|
||||
|
||||
po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
|
||||
auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad);
|
||||
|
||||
|
||||
if (!validate_pad(pad_mesh, pcfg))
|
||||
throw Slic3r::SlicingError(
|
||||
L("No pad can be generated for this model with the "
|
||||
"current configuration"));
|
||||
|
||||
|
||||
} else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) {
|
||||
po.m_supportdata->support_tree_ptr->remove_pad();
|
||||
}
|
||||
|
||||
|
||||
throw_if_canceled();
|
||||
report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE);
|
||||
}
|
||||
|
@ -464,25 +617,25 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
|
|||
// be part of the slices)
|
||||
void SLAPrint::Steps::slice_supports(SLAPrintObject &po) {
|
||||
auto& sd = po.m_supportdata;
|
||||
|
||||
|
||||
if(sd) sd->support_slices.clear();
|
||||
|
||||
|
||||
// Don't bother if no supports and no pad is present.
|
||||
if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool())
|
||||
return;
|
||||
|
||||
|
||||
if(sd && sd->support_tree_ptr) {
|
||||
auto heights = reserve_vector<float>(po.m_slice_index.size());
|
||||
|
||||
|
||||
for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level());
|
||||
|
||||
sd->support_slices = sd->support_tree_ptr->slice(
|
||||
heights, float(po.config().slice_closing_radius.value));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i)
|
||||
|
||||
for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i)
|
||||
po.m_slice_index[i].set_support_slice_idx(po, i);
|
||||
|
||||
|
||||
apply_printer_corrections(po, soSupport);
|
||||
|
||||
// Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
|
||||
|
@ -497,37 +650,37 @@ using ClipperPolygons = std::vector<ClipperPolygon>;
|
|||
static ClipperPolygons polyunion(const ClipperPolygons &subjects)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
|
||||
bool closed = true;
|
||||
|
||||
|
||||
for(auto& path : subjects) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
|
||||
|
||||
auto mode = ClipperLib::pftPositive;
|
||||
|
||||
|
||||
return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode);
|
||||
}
|
||||
|
||||
static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
|
||||
bool closed = true;
|
||||
|
||||
|
||||
for(auto& path : subjects) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
|
||||
|
||||
for(auto& path : clips) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptClip, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed);
|
||||
}
|
||||
|
||||
|
||||
auto mode = ClipperLib::pftPositive;
|
||||
|
||||
|
||||
return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode);
|
||||
}
|
||||
|
||||
|
@ -535,28 +688,28 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo
|
|||
static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o)
|
||||
{
|
||||
namespace sl = libnest2d::sl;
|
||||
|
||||
|
||||
if (!record.print_obj()) return {};
|
||||
|
||||
|
||||
ClipperPolygons polygons;
|
||||
auto &input_polygons = record.get_slice(o);
|
||||
auto &instances = record.print_obj()->instances();
|
||||
bool is_lefthanded = record.print_obj()->is_left_handed();
|
||||
polygons.reserve(input_polygons.size() * instances.size());
|
||||
|
||||
|
||||
for (const ExPolygon& polygon : input_polygons) {
|
||||
if(polygon.contour.empty()) continue;
|
||||
|
||||
|
||||
for (size_t i = 0; i < instances.size(); ++i)
|
||||
{
|
||||
ClipperPolygon poly;
|
||||
|
||||
|
||||
// We need to reverse if is_lefthanded is true but
|
||||
bool needreverse = is_lefthanded;
|
||||
|
||||
|
||||
// should be a move
|
||||
poly.Contour.reserve(polygon.contour.size() + 1);
|
||||
|
||||
|
||||
auto& cntr = polygon.contour.points;
|
||||
if(needreverse)
|
||||
for(auto it = cntr.rbegin(); it != cntr.rend(); ++it)
|
||||
|
@ -564,12 +717,12 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o
|
|||
else
|
||||
for(auto& p : cntr)
|
||||
poly.Contour.emplace_back(p.x(), p.y());
|
||||
|
||||
|
||||
for(auto& h : polygon.holes) {
|
||||
poly.Holes.emplace_back();
|
||||
auto& hole = poly.Holes.back();
|
||||
hole.reserve(h.points.size() + 1);
|
||||
|
||||
|
||||
if(needreverse)
|
||||
for(auto it = h.points.rbegin(); it != h.points.rend(); ++it)
|
||||
hole.emplace_back(it->x(), it->y());
|
||||
|
@ -577,42 +730,42 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o
|
|||
for(auto& p : h.points)
|
||||
hole.emplace_back(p.x(), p.y());
|
||||
}
|
||||
|
||||
|
||||
if(is_lefthanded) {
|
||||
for(auto& p : poly.Contour) p.X = -p.X;
|
||||
for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X;
|
||||
}
|
||||
|
||||
|
||||
sl::rotate(poly, double(instances[i].rotation));
|
||||
sl::translate(poly, ClipperPoint{instances[i].shift.x(),
|
||||
instances[i].shift.y()});
|
||||
|
||||
|
||||
polygons.emplace_back(std::move(poly));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return polygons;
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::initialize_printer_input()
|
||||
{
|
||||
auto &printer_input = m_print->m_printer_input;
|
||||
|
||||
|
||||
// clear the rasterizer input
|
||||
printer_input.clear();
|
||||
|
||||
|
||||
size_t mx = 0;
|
||||
for(SLAPrintObject * o : m_print->m_objects) {
|
||||
if(auto m = o->get_slice_index().size() > mx) mx = m;
|
||||
}
|
||||
|
||||
|
||||
printer_input.reserve(mx);
|
||||
|
||||
|
||||
auto eps = coord_t(SCALED_EPSILON);
|
||||
|
||||
|
||||
for(SLAPrintObject * o : m_print->m_objects) {
|
||||
coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
|
||||
|
||||
|
||||
for(const SliceRecord& slicerecord : o->get_slice_index()) {
|
||||
if (!slicerecord.is_valid())
|
||||
throw Slic3r::SlicingError(
|
||||
|
@ -621,7 +774,7 @@ void SLAPrint::Steps::initialize_printer_input()
|
|||
"objects printable."));
|
||||
|
||||
coord_t lvlid = slicerecord.print_level() - gndlvl;
|
||||
|
||||
|
||||
// Neat trick to round the layer levels to the grid.
|
||||
lvlid = eps * (lvlid / eps);
|
||||
|
||||
|
@ -631,8 +784,8 @@ void SLAPrint::Steps::initialize_printer_input()
|
|||
|
||||
if(it == printer_input.end() || it->level() != lvlid)
|
||||
it = printer_input.insert(it, PrintLayer(lvlid));
|
||||
|
||||
|
||||
|
||||
|
||||
it->add(slicerecord);
|
||||
}
|
||||
}
|
||||
|
@ -641,53 +794,53 @@ void SLAPrint::Steps::initialize_printer_input()
|
|||
// Merging the slices from all the print objects into one slice grid and
|
||||
// calculating print statistics from the merge result.
|
||||
void SLAPrint::Steps::merge_slices_and_eval_stats() {
|
||||
|
||||
|
||||
initialize_printer_input();
|
||||
|
||||
|
||||
auto &print_statistics = m_print->m_print_statistics;
|
||||
auto &printer_config = m_print->m_printer_config;
|
||||
auto &material_config = m_print->m_material_config;
|
||||
auto &printer_input = m_print->m_printer_input;
|
||||
|
||||
|
||||
print_statistics.clear();
|
||||
|
||||
|
||||
// libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
|
||||
auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); };
|
||||
|
||||
|
||||
const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
|
||||
const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0;
|
||||
const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0;
|
||||
|
||||
|
||||
const double init_exp_time = material_config.initial_exposure_time.getFloat();
|
||||
const double exp_time = material_config.exposure_time.getFloat();
|
||||
|
||||
|
||||
const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20]
|
||||
|
||||
|
||||
const auto width = scaled<double>(printer_config.display_width.getFloat());
|
||||
const auto height = scaled<double>(printer_config.display_height.getFloat());
|
||||
const double display_area = width*height;
|
||||
|
||||
|
||||
double supports_volume(0.0);
|
||||
double models_volume(0.0);
|
||||
|
||||
|
||||
double estim_time(0.0);
|
||||
std::vector<double> layers_times;
|
||||
layers_times.reserve(printer_input.size());
|
||||
|
||||
|
||||
size_t slow_layers = 0;
|
||||
size_t fast_layers = 0;
|
||||
|
||||
|
||||
const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
|
||||
double fade_layer_time = init_exp_time;
|
||||
|
||||
|
||||
sla::ccr::SpinningMutex mutex;
|
||||
using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
|
||||
|
||||
|
||||
// Going to parallel:
|
||||
auto printlayerfn = [this,
|
||||
// functions and read only vars
|
||||
areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time,
|
||||
|
||||
|
||||
// write vars
|
||||
&mutex, &models_volume, &supports_volume, &estim_time, &slow_layers,
|
||||
&fast_layers, &fade_layer_time, &layers_times](size_t sliced_layer_cnt)
|
||||
|
@ -696,87 +849,87 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
|
|||
|
||||
// vector of slice record references
|
||||
auto& slicerecord_references = layer.slices();
|
||||
|
||||
|
||||
if(slicerecord_references.empty()) return;
|
||||
|
||||
|
||||
// Layer height should match for all object slices for a given level.
|
||||
const auto l_height = double(slicerecord_references.front().get().layer_height());
|
||||
|
||||
|
||||
// Calculation of the consumed material
|
||||
|
||||
|
||||
ClipperPolygons model_polygons;
|
||||
ClipperPolygons supports_polygons;
|
||||
|
||||
|
||||
size_t c = std::accumulate(layer.slices().begin(),
|
||||
layer.slices().end(),
|
||||
size_t(0),
|
||||
[](size_t a, const SliceRecord &sr) {
|
||||
return a + sr.get_slice(soModel).size();
|
||||
});
|
||||
|
||||
|
||||
model_polygons.reserve(c);
|
||||
|
||||
|
||||
c = std::accumulate(layer.slices().begin(),
|
||||
layer.slices().end(),
|
||||
size_t(0),
|
||||
[](size_t a, const SliceRecord &sr) {
|
||||
return a + sr.get_slice(soModel).size();
|
||||
});
|
||||
|
||||
|
||||
supports_polygons.reserve(c);
|
||||
|
||||
|
||||
for(const SliceRecord& record : layer.slices()) {
|
||||
|
||||
|
||||
ClipperPolygons modelslices = get_all_polygons(record, soModel);
|
||||
for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp));
|
||||
|
||||
|
||||
ClipperPolygons supportslices = get_all_polygons(record, soSupport);
|
||||
for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
model_polygons = polyunion(model_polygons);
|
||||
double layer_model_area = 0;
|
||||
for (const ClipperPolygon& polygon : model_polygons)
|
||||
layer_model_area += areafn(polygon);
|
||||
|
||||
|
||||
if (layer_model_area < 0 || layer_model_area > 0) {
|
||||
Lock lck(mutex); models_volume += layer_model_area * l_height;
|
||||
}
|
||||
|
||||
|
||||
if(!supports_polygons.empty()) {
|
||||
if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons);
|
||||
else supports_polygons = polydiff(supports_polygons, model_polygons);
|
||||
// allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
|
||||
}
|
||||
|
||||
|
||||
double layer_support_area = 0;
|
||||
for (const ClipperPolygon& polygon : supports_polygons)
|
||||
layer_support_area += areafn(polygon);
|
||||
|
||||
|
||||
if (layer_support_area < 0 || layer_support_area > 0) {
|
||||
Lock lck(mutex); supports_volume += layer_support_area * l_height;
|
||||
}
|
||||
|
||||
|
||||
// Here we can save the expensively calculated polygons for printing
|
||||
ClipperPolygons trslices;
|
||||
trslices.reserve(model_polygons.size() + supports_polygons.size());
|
||||
for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly));
|
||||
for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly));
|
||||
|
||||
|
||||
layer.transformed_slices(polyunion(trslices));
|
||||
|
||||
|
||||
// Calculation of the slow and fast layers to the future controlling those values on FW
|
||||
|
||||
|
||||
const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
|
||||
const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
|
||||
|
||||
|
||||
{ Lock lck(mutex);
|
||||
if (is_fast_layer)
|
||||
fast_layers++;
|
||||
else
|
||||
slow_layers++;
|
||||
|
||||
|
||||
// Calculation of the printing time
|
||||
|
||||
double layer_times = 0.0;
|
||||
|
@ -794,15 +947,15 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
|
|||
estim_time += layer_times;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// sequential version for debugging:
|
||||
// for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
|
||||
sla::ccr::for_each(size_t(0), printer_input.size(), printlayerfn);
|
||||
|
||||
|
||||
auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR;
|
||||
print_statistics.support_used_material = supports_volume * SCALING2;
|
||||
print_statistics.objects_used_material = models_volume * SCALING2;
|
||||
|
||||
|
||||
// Estimated printing time
|
||||
// A layers count o the highest object
|
||||
if (printer_input.size() == 0)
|
||||
|
@ -811,10 +964,10 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
|
|||
print_statistics.estimated_print_time = estim_time;
|
||||
print_statistics.layers_times = layers_times;
|
||||
}
|
||||
|
||||
|
||||
print_statistics.fast_layers_count = fast_layers;
|
||||
print_statistics.slow_layers_count = slow_layers;
|
||||
|
||||
|
||||
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
}
|
||||
|
||||
|
@ -822,23 +975,23 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
|
|||
void SLAPrint::Steps::rasterize()
|
||||
{
|
||||
if(canceled() || !m_print->m_printer) return;
|
||||
|
||||
|
||||
// coefficient to map the rasterization state (0-99) to the allocated
|
||||
// portion (slot) of the process state
|
||||
double sd = (100 - max_objstatus) / 100.0;
|
||||
|
||||
|
||||
// slot is the portion of 100% that is realted to rasterization
|
||||
unsigned slot = PRINT_STEP_LEVELS[slapsRasterize];
|
||||
|
||||
|
||||
// pst: previous state
|
||||
double pst = current_status();
|
||||
|
||||
|
||||
double increment = (slot * sd) / m_print->m_printer_input.size();
|
||||
double dstatus = current_status();
|
||||
|
||||
|
||||
sla::ccr::SpinningMutex slck;
|
||||
using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
|
||||
|
||||
|
||||
// procedure to process one height level. This will run in parallel
|
||||
auto lvlfn =
|
||||
[this, &slck, increment, &dstatus, &pst]
|
||||
|
@ -846,10 +999,10 @@ void SLAPrint::Steps::rasterize()
|
|||
{
|
||||
PrintLayer& printlayer = m_print->m_printer_input[idx];
|
||||
if(canceled()) return;
|
||||
|
||||
|
||||
for (const ClipperLib::Polygon& poly : printlayer.transformed_slices())
|
||||
raster.draw(poly);
|
||||
|
||||
|
||||
// Status indication guarded with the spinlock
|
||||
{
|
||||
Lock lck(slck);
|
||||
|
@ -861,10 +1014,10 @@ void SLAPrint::Steps::rasterize()
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// last minute escape
|
||||
if(canceled()) return;
|
||||
|
||||
|
||||
// Print all the layers in parallel
|
||||
m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn);
|
||||
}
|
||||
|
|
|
@ -2063,4 +2063,22 @@ TriangleMesh make_sphere(double radius, double fa)
|
|||
return mesh;
|
||||
}
|
||||
|
||||
std::vector<std::vector<size_t> > create_neighbor_index(const indexed_triangle_set &its)
|
||||
{
|
||||
if (its.vertices.empty()) return {};
|
||||
|
||||
size_t res = its.indices.size() / its.vertices.size();
|
||||
std::vector< std::vector<size_t> > index(its.vertices.size(),
|
||||
reserve_vector<size_t>(res));
|
||||
|
||||
for (size_t fi = 0; fi < its.indices.size(); ++fi) {
|
||||
auto &face = its.indices[fi];
|
||||
index[face(0)].emplace_back(fi);
|
||||
index[face(1)].emplace_back(fi);
|
||||
index[face(2)].emplace_back(fi);
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -89,6 +89,12 @@ private:
|
|||
std::deque<uint32_t> find_unvisited_neighbors(std::vector<unsigned char> &facet_visited) const;
|
||||
};
|
||||
|
||||
// Create an index of faces belonging to each vertex. The returned vector can
|
||||
// be indexed with vertex indices and contains a list of face indices for each
|
||||
// vertex.
|
||||
std::vector< std::vector<size_t> >
|
||||
create_neighbor_index(const indexed_triangle_set &its);
|
||||
|
||||
enum FacetEdgeType {
|
||||
// A general case, the cutting plane intersect a face at two different edges.
|
||||
feGeneral,
|
||||
|
|
|
@ -200,12 +200,20 @@ void HollowedMesh::on_update()
|
|||
if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) {
|
||||
size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp;
|
||||
if (timestamp > m_old_hollowing_timestamp) {
|
||||
const TriangleMesh& backend_mesh = print_object->get_mesh_to_print();
|
||||
const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice();
|
||||
if (! backend_mesh.empty()) {
|
||||
m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh));
|
||||
Transform3d trafo_inv = canvas->sla_print()->sla_trafo(*mo).inverse();
|
||||
m_hollowed_mesh_transformed->transform(trafo_inv);
|
||||
m_old_hollowing_timestamp = timestamp;
|
||||
|
||||
const TriangleMesh &interior = print_object->hollowed_interior_mesh();
|
||||
if (!interior.empty()) {
|
||||
m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(interior);
|
||||
m_hollowed_interior_transformed->repaired = false;
|
||||
m_hollowed_interior_transformed->repair(true);
|
||||
m_hollowed_interior_transformed->transform(trafo_inv);
|
||||
}
|
||||
}
|
||||
else
|
||||
m_hollowed_mesh_transformed.reset(nullptr);
|
||||
|
@ -230,6 +238,10 @@ const TriangleMesh* HollowedMesh::get_hollowed_mesh() const
|
|||
return m_hollowed_mesh_transformed.get();
|
||||
}
|
||||
|
||||
const TriangleMesh* HollowedMesh::get_hollowed_interior() const
|
||||
{
|
||||
return m_hollowed_interior_transformed.get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -306,6 +318,10 @@ void ObjectClipper::on_update()
|
|||
m_clippers.back()->set_mesh(*mesh);
|
||||
}
|
||||
m_old_meshes = meshes;
|
||||
|
||||
if (has_hollowed)
|
||||
m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior());
|
||||
|
||||
m_active_inst_bb_radius =
|
||||
mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius();
|
||||
//if (has_hollowed && m_clp_ratio != 0.)
|
||||
|
|
|
@ -199,6 +199,7 @@ public:
|
|||
#endif // NDEBUG
|
||||
|
||||
const TriangleMesh* get_hollowed_mesh() const;
|
||||
const TriangleMesh* get_hollowed_interior() const;
|
||||
|
||||
protected:
|
||||
void on_update() override;
|
||||
|
@ -206,6 +207,7 @@ protected:
|
|||
|
||||
private:
|
||||
std::unique_ptr<TriangleMesh> m_hollowed_mesh_transformed;
|
||||
std::unique_ptr<TriangleMesh> m_hollowed_interior_transformed;
|
||||
size_t m_old_hollowing_timestamp = 0;
|
||||
int m_print_object_idx = -1;
|
||||
int m_print_objects_count = 0;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "libslic3r/Tesselate.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
|
||||
|
@ -31,6 +32,15 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh)
|
|||
}
|
||||
}
|
||||
|
||||
void MeshClipper::set_negative_mesh(const TriangleMesh& mesh)
|
||||
{
|
||||
if (m_negative_mesh != &mesh) {
|
||||
m_negative_mesh = &mesh;
|
||||
m_triangles_valid = false;
|
||||
m_triangles2d.resize(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
|
||||
|
@ -74,6 +84,15 @@ void MeshClipper::recalculate_triangles()
|
|||
std::vector<ExPolygons> list_of_expolys;
|
||||
m_tms->set_up_direction(up.cast<float>());
|
||||
m_tms->slice(std::vector<float>{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){});
|
||||
|
||||
if (m_negative_mesh && !m_negative_mesh->empty()) {
|
||||
TriangleMeshSlicer negative_tms{m_negative_mesh};
|
||||
negative_tms.set_up_direction(up.cast<float>());
|
||||
|
||||
std::vector<ExPolygons> neg_polys;
|
||||
negative_tms.slice(std::vector<float>{height_mesh}, SlicingMode::Regular, 0.f, &neg_polys, [](){});
|
||||
list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front());
|
||||
}
|
||||
m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.);
|
||||
|
||||
// Rotate the cut into world coords:
|
||||
|
|
|
@ -78,6 +78,8 @@ public:
|
|||
// must make sure that it stays valid.
|
||||
void set_mesh(const TriangleMesh& mesh);
|
||||
|
||||
void set_negative_mesh(const TriangleMesh &mesh);
|
||||
|
||||
// Inform the MeshClipper about the transformation that transforms the mesh
|
||||
// into world coordinates.
|
||||
void set_transformation(const Geometry::Transformation& trafo);
|
||||
|
@ -91,6 +93,7 @@ private:
|
|||
|
||||
Geometry::Transformation m_trafo;
|
||||
const TriangleMesh* m_mesh = nullptr;
|
||||
const TriangleMesh* m_negative_mesh = nullptr;
|
||||
ClippingPlane m_plane;
|
||||
std::vector<Vec2f> m_triangles2d;
|
||||
GLIndexedVertexArray m_vertex_array;
|
||||
|
|
|
@ -5363,7 +5363,7 @@ void Plater::export_stl(bool extended, bool selection_only)
|
|||
inst_mesh.merge(inst_supports_mesh);
|
||||
}
|
||||
|
||||
TriangleMesh inst_object_mesh = object->get_mesh_to_print();
|
||||
TriangleMesh inst_object_mesh = object->get_mesh_to_slice();
|
||||
inst_object_mesh.transform(mesh_trafo_inv);
|
||||
inst_object_mesh.transform(inst_transform, is_left_handed);
|
||||
|
||||
|
|
|
@ -2,45 +2,21 @@
|
|||
#include <fstream>
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
#include <openvdb/tools/Filter.h>
|
||||
#include "libslic3r/Format/OBJ.hpp"
|
||||
|
||||
#include <libnest2d/tools/benchmark.h>
|
||||
TEST_CASE("Hollow two overlapping spheres") {
|
||||
using namespace Slic3r;
|
||||
|
||||
#include <libslic3r/SimplifyMesh.hpp>
|
||||
TriangleMesh sphere1 = make_sphere(10., 2 * PI / 20.), sphere2 = sphere1;
|
||||
|
||||
#if defined(WIN32) || defined(_WIN32)
|
||||
#define PATH_SEPARATOR R"(\)"
|
||||
#else
|
||||
#define PATH_SEPARATOR R"(/)"
|
||||
#endif
|
||||
sphere1.translate(-5.f, 0.f, 0.f);
|
||||
sphere2.translate( 5.f, 0.f, 0.f);
|
||||
|
||||
static Slic3r::TriangleMesh load_model(const std::string &obj_filename)
|
||||
{
|
||||
Slic3r::TriangleMesh mesh;
|
||||
auto fpath = TEST_DATA_DIR PATH_SEPARATOR + obj_filename;
|
||||
Slic3r::load_obj(fpath.c_str(), &mesh);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("Negative 3D offset should produce smaller object.", "[Hollowing]")
|
||||
{
|
||||
Slic3r::TriangleMesh in_mesh = load_model("20mm_cube.obj");
|
||||
Benchmark bench;
|
||||
bench.start();
|
||||
|
||||
std::unique_ptr<Slic3r::TriangleMesh> out_mesh_ptr =
|
||||
Slic3r::sla::generate_interior(in_mesh);
|
||||
|
||||
bench.stop();
|
||||
|
||||
std::cout << "Elapsed processing time: " << bench.getElapsedSec() << std::endl;
|
||||
|
||||
if (out_mesh_ptr) in_mesh.merge(*out_mesh_ptr);
|
||||
in_mesh.require_shared_vertices();
|
||||
in_mesh.WriteOBJFile("merged_out.obj");
|
||||
sphere1.merge(sphere2);
|
||||
sphere1.require_shared_vertices();
|
||||
|
||||
sla::hollow_mesh(sphere1, sla::HollowingConfig{}, sla::HollowingFlags::hfRemoveInsideTriangles);
|
||||
|
||||
sphere1.WriteOBJFile("twospheres.obj");
|
||||
}
|
||||
|
||||
|
|
|
@ -88,9 +88,9 @@ void test_supports(const std::string &obj_filename,
|
|||
REQUIRE_FALSE(mesh.empty());
|
||||
|
||||
if (hollowingcfg.enabled) {
|
||||
auto inside = sla::generate_interior(mesh, hollowingcfg);
|
||||
REQUIRE(inside);
|
||||
mesh.merge(*inside);
|
||||
sla::InteriorPtr interior = sla::generate_interior(mesh, hollowingcfg);
|
||||
REQUIRE(interior);
|
||||
mesh.merge(sla::get_mesh(*interior));
|
||||
mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue