Merge branch 'tm_remove_inside_triangles'

This commit is contained in:
tamasmeszaros 2021-03-08 17:48:15 +01:00
commit 6d11c50ba5
18 changed files with 881 additions and 358 deletions

View file

@ -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;
};

View file

@ -22,74 +22,54 @@ namespace Slic3r {
class TriangleMeshDataAdapter {
public:
const TriangleMesh &mesh;
float voxel_scale;
size_t polygonCount() const { return mesh.its.indices.size(); }
size_t pointCount() const { return mesh.its.vertices.size(); }
size_t vertexCount(size_t) const { return 3; }
// Return position pos in local grid index space for polygon n and vertex v
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
};
class Contour3DDataAdapter {
public:
const sla::Contour3D &mesh;
size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); }
size_t pointCount() const { return mesh.points.size(); }
size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; }
// Return position pos in local grid index space for polygon n and vertex v
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
};
void TriangleMeshDataAdapter::getIndexSpacePoint(size_t n,
size_t v,
openvdb::Vec3d &pos) const
// The actual mesh will appear to openvdb as scaled uniformly by voxel_size
// And the voxel count per unit volume can be affected this way.
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const
{
auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v)));
Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>();
pos = {p.x(), p.y(), p.z()};
}
void Contour3DDataAdapter::getIndexSpacePoint(size_t n,
size_t v,
openvdb::Vec3d &pos) const
{
size_t vidx = 0;
if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v)));
else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v)));
Slic3r::Vec3d p = mesh.points[vidx];
Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>() * voxel_scale;
pos = {p.x(), p.y(), p.z()};
}
TriangleMeshDataAdapter(const TriangleMesh &m, float voxel_sc = 1.f)
: mesh{m}, voxel_scale{voxel_sc} {};
};
// TODO: Do I need to call initialize? Seems to work without it as well but the
// docs say it should be called ones. It does a mutex lock-unlock sequence all
// even if was called previously.
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh,
const openvdb::math::Transform &tr,
float voxel_scale,
float exteriorBandWidth,
float interiorBandWidth,
int flags)
{
openvdb::initialize();
TriangleMeshPtrs meshparts = mesh.split();
TriangleMeshPtrs meshparts_raw = mesh.split();
auto meshparts = reserve_vector<std::unique_ptr<TriangleMesh>>(meshparts_raw.size());
for (auto *p : meshparts_raw)
meshparts.emplace_back(p);
auto it = std::remove_if(meshparts.begin(), meshparts.end(),
[](TriangleMesh *m){
auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) {
m->require_shared_vertices();
return !m->is_manifold() || m->volume() < EPSILON;
return m->volume() < EPSILON;
});
meshparts.erase(it, meshparts.end());
openvdb::FloatGrid::Ptr grid;
for (TriangleMesh *m : meshparts) {
for (auto &m : meshparts) {
auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth,
TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth,
interiorBandWidth, flags);
if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid);
@ -106,19 +86,9 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
interiorBandWidth, flags);
}
return grid;
}
grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale));
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh,
const openvdb::math::Transform &tr,
float exteriorBandWidth,
float interiorBandWidth,
int flags)
{
openvdb::initialize();
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth,
flags);
return grid;
}
template<class Grid>
@ -136,12 +106,17 @@ sla::Contour3D _volumeToMesh(const Grid &grid,
openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue,
adaptivity, relaxDisorientedTriangles);
float scale = 1.;
try {
scale = grid.template metaValue<float>("voxel_scale");
} catch (...) { }
sla::Contour3D ret;
ret.points.reserve(points.size());
ret.faces3.reserve(triangles.size());
ret.faces4.reserve(quads.size());
for (auto &v : points) ret.points.emplace_back(to_vec3d(v));
for (auto &v : points) ret.points.emplace_back(to_vec3d(v) / scale);
for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v));
for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v));
@ -166,9 +141,18 @@ sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
relaxDisorientedTriangles);
}
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir)
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
double iso,
double er,
double ir)
{
return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir));
auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso),
float(er), float(ir));
// Copies voxel_scale metadata, if it exists.
new_grid->insertMeta(*grid.deepCopyMeta());
return new_grid;
}
} // namespace Slic3r

View file

@ -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);

View file

@ -5,6 +5,7 @@
#include <tbb/mutex.h>
#include <tbb/parallel_for.h>
#include <tbb/parallel_reduce.h>
#include <tbb/task_arena.h>
#include <algorithm>
#include <numeric>
@ -76,6 +77,11 @@ template<> struct _ccr<true>
from, to, init, std::forward<MergeFn>(mergefn),
[](typename I::value_type &i) { return i; }, granularity);
}
static size_t max_concurreny()
{
return tbb::this_task_arena::max_concurrency();
}
};
template<> struct _ccr<false>
@ -133,6 +139,8 @@ public:
return reduce(from, to, init, std::forward<MergeFn>(mergefn),
[](typename I::value_type &i) { return i; });
}
static size_t max_concurreny() { return 1; }
};
using ccr = _ccr<USE_FULL_CONCURRENCY>;

View file

@ -26,16 +26,47 @@ inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); }
template<class S, class = FloatingOnly<S>>
inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; }
static TriangleMesh _generate_interior(const TriangleMesh &mesh,
struct Interior {
TriangleMesh mesh;
openvdb::FloatGrid::Ptr gridptr;
mutable std::optional<openvdb::FloatGrid::ConstAccessor> accessor;
double closing_distance = 0.;
double thickness = 0.;
double voxel_scale = 1.;
double nb_in = 3.; // narrow band width inwards
double nb_out = 3.; // narrow band width outwards
// Full narrow band is the sum of the two above values.
void reset_accessor() const // This resets the accessor and its cache
// Not a thread safe call!
{
if (gridptr)
accessor = gridptr->getConstAccessor();
}
};
void InteriorDeleter::operator()(Interior *p)
{
delete p;
}
TriangleMesh &get_mesh(Interior &interior)
{
return interior.mesh;
}
const TriangleMesh &get_mesh(const Interior &interior)
{
return interior.mesh;
}
static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh,
const JobController &ctl,
double min_thickness,
double voxel_scale,
double closing_dist)
{
TriangleMesh imesh{mesh};
_scale(voxel_scale, imesh);
double offset = voxel_scale * min_thickness;
double D = voxel_scale * closing_dist;
float out_range = 0.1f * float(offset);
@ -44,7 +75,7 @@ static TriangleMesh _generate_interior(const TriangleMesh &mesh,
if (ctl.stopcondition()) return {};
else ctl.statuscb(0, L("Hollowing"));
auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range);
auto gridptr = mesh_to_grid(mesh, {}, voxel_scale, out_range, in_range);
assert(gridptr);
@ -56,28 +87,32 @@ static TriangleMesh _generate_interior(const TriangleMesh &mesh,
if (ctl.stopcondition()) return {};
else ctl.statuscb(30, L("Hollowing"));
if (closing_dist > .0) {
gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range));
} else {
D = -offset;
}
double iso_surface = D;
auto narrowb = double(in_range);
gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb);
if (ctl.stopcondition()) return {};
else ctl.statuscb(70, L("Hollowing"));
double iso_surface = D;
double adaptivity = 0.;
auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
InteriorPtr interior = InteriorPtr{new Interior{}};
_scale(1. / voxel_scale, omesh);
interior->mesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
interior->gridptr = gridptr;
if (ctl.stopcondition()) return {};
else ctl.statuscb(100, L("Hollowing"));
return omesh;
interior->closing_distance = D;
interior->thickness = offset;
interior->voxel_scale = voxel_scale;
interior->nb_in = narrowb;
interior->nb_out = narrowb;
return interior;
}
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
InteriorPtr generate_interior(const TriangleMesh & mesh,
const HollowingConfig &hc,
const JobController & ctl)
{
@ -92,15 +127,16 @@ std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
//
// max 8x upscale, min is native voxel size
auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality;
auto meshptr = std::make_unique<TriangleMesh>(
_generate_interior(mesh, ctl, hc.min_thickness, voxel_scale,
hc.closing_distance));
if (meshptr && !meshptr->empty()) {
InteriorPtr interior =
generate_interior_verbose(mesh, ctl, hc.min_thickness, voxel_scale,
hc.closing_distance);
if (interior && !interior->mesh.empty()) {
// This flips the normals to be outward facing...
meshptr->require_shared_vertices();
indexed_triangle_set its = std::move(meshptr->its);
interior->mesh.require_shared_vertices();
indexed_triangle_set its = std::move(interior->mesh.its);
Slic3r::simplify_mesh(its);
@ -108,10 +144,12 @@ std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
for (stl_triangle_vertex_indices &ind : its.indices)
std::swap(ind(0), ind(2));
*meshptr = Slic3r::TriangleMesh{its};
interior->mesh = Slic3r::TriangleMesh{its};
interior->mesh.repaired = true;
interior->mesh.require_shared_vertices();
}
return meshptr;
return interior;
}
Contour3D DrainHole::to_mesh() const
@ -273,12 +311,264 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices,
obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]);
}
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg)
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags)
{
std::unique_ptr<Slic3r::TriangleMesh> inter_ptr =
Slic3r::sla::generate_interior(mesh);
InteriorPtr interior = generate_interior(mesh, cfg, JobController{});
if (!interior) return;
if (inter_ptr) mesh.merge(*inter_ptr);
hollow_mesh(mesh, *interior, flags);
}
void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags)
{
if (mesh.empty() || interior.mesh.empty()) return;
if (flags & hfRemoveInsideTriangles && interior.gridptr)
remove_inside_triangles(mesh, interior);
mesh.merge(interior.mesh);
mesh.require_shared_vertices();
}
// Get the distance of p to the interior's zero iso_surface. Interior should
// have its zero isosurface positioned at offset + closing_distance inwards form
// the model surface.
static double get_distance_raw(const Vec3f &p, const Interior &interior)
{
assert(interior.gridptr);
if (!interior.accessor) interior.reset_accessor();
auto v = (p * interior.voxel_scale).cast<double>();
auto grididx = interior.gridptr->transform().worldToIndexCellCentered(
{v.x(), v.y(), v.z()});
return interior.accessor->getValue(grididx) ;
}
struct TriangleBubble { Vec3f center; double R; };
// Return the distance of bubble center to the interior boundary or NaN if the
// triangle is too big to be measured.
static double get_distance(const TriangleBubble &b, const Interior &interior)
{
double R = b.R * interior.voxel_scale;
double D = get_distance_raw(b.center, interior);
return (D > 0. && R >= interior.nb_out) ||
(D < 0. && R >= interior.nb_in) ||
((D - R) < 0. && 2 * R > interior.thickness) ?
std::nan("") :
// FIXME: Adding interior.voxel_scale is a compromise supposed
// to prevent the deletion of the triangles forming the interior
// itself. This has a side effect that a small portion of the
// bad triangles will still be visible.
D - interior.closing_distance /*+ 2 * interior.voxel_scale*/;
}
double get_distance(const Vec3f &p, const Interior &interior)
{
double d = get_distance_raw(p, interior) - interior.closing_distance;
return d / interior.voxel_scale;
}
// A face that can be divided. Stores the indices into the original mesh if its
// part of that mesh and the vertices it consists of.
enum { NEW_FACE = -1};
struct DivFace {
Vec3i indx;
std::array<Vec3f, 3> verts;
long faceid = NEW_FACE;
long parent = NEW_FACE;
};
// Divide a face recursively and call visitor on all the sub-faces.
template<class Fn>
void divide_triangle(const DivFace &face, Fn &&visitor)
{
std::array<Vec3f, 3> edges = {(face.verts[0] - face.verts[1]),
(face.verts[1] - face.verts[2]),
(face.verts[2] - face.verts[0])};
std::array<size_t, 3> edgeidx = {0, 1, 2};
std::sort(edgeidx.begin(), edgeidx.end(), [&edges](size_t e1, size_t e2) {
return edges[e1].squaredNorm() > edges[e2].squaredNorm();
});
DivFace child1, child2;
child1.parent = face.faceid == NEW_FACE ? face.parent : face.faceid;
child1.indx(0) = -1;
child1.indx(1) = face.indx(edgeidx[1]);
child1.indx(2) = face.indx((edgeidx[1] + 1) % 3);
child1.verts[0] = (face.verts[edgeidx[0]] + face.verts[(edgeidx[0] + 1) % 3]) / 2.;
child1.verts[1] = face.verts[edgeidx[1]];
child1.verts[2] = face.verts[(edgeidx[1] + 1) % 3];
if (visitor(child1))
divide_triangle(child1, std::forward<Fn>(visitor));
child2.parent = face.faceid == NEW_FACE ? face.parent : face.faceid;
child2.indx(0) = -1;
child2.indx(1) = face.indx(edgeidx[2]);
child2.indx(2) = face.indx((edgeidx[2] + 1) % 3);
child2.verts[0] = child1.verts[0];
child2.verts[1] = face.verts[edgeidx[2]];
child2.verts[2] = face.verts[(edgeidx[2] + 1) % 3];
if (visitor(child2))
divide_triangle(child2, std::forward<Fn>(visitor));
}
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask)
{
enum TrPos { posInside, posTouch, posOutside };
auto &faces = mesh.its.indices;
auto &vertices = mesh.its.vertices;
auto bb = mesh.bounding_box();
bool use_exclude_mask = faces.size() == exclude_mask.size();
auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) {
return use_exclude_mask && exclude_mask[face_id];
};
// TODO: Parallel mode not working yet
using exec_policy = ccr_seq;
// Info about the needed modifications on the input mesh.
struct MeshMods {
// Just a thread safe wrapper for a vector of triangles.
struct {
std::vector<std::array<Vec3f, 3>> data;
exec_policy::SpinningMutex mutex;
void emplace_back(const std::array<Vec3f, 3> &pts)
{
std::lock_guard lk{mutex};
data.emplace_back(pts);
}
size_t size() const { return data.size(); }
const std::array<Vec3f, 3>& operator[](size_t idx) const
{
return data[idx];
}
} new_triangles;
// A vector of bool for all faces signaling if it needs to be removed
// or not.
std::vector<bool> to_remove;
MeshMods(const TriangleMesh &mesh):
to_remove(mesh.its.indices.size(), false) {}
// Number of triangles that need to be removed.
size_t to_remove_cnt() const
{
return std::accumulate(to_remove.begin(), to_remove.end(), size_t(0));
}
} mesh_mods{mesh};
// Must return true if further division of the face is needed.
auto divfn = [&interior, bb, &mesh_mods](const DivFace &f) {
BoundingBoxf3 facebb { f.verts.begin(), f.verts.end() };
// Face is certainly outside the cavity
if (! facebb.intersects(bb) && f.faceid != NEW_FACE) {
return false;
}
TriangleBubble bubble{facebb.center().cast<float>(), facebb.radius()};
double D = get_distance(bubble, interior);
double R = bubble.R * interior.voxel_scale;
if (std::isnan(D)) // The distance cannot be measured, triangle too big
return true;
// Distance of the bubble wall to the interior wall. Negative if the
// bubble is overlapping with the interior
double bubble_distance = D - R;
// The face is crossing the interior or inside, it must be removed and
// parts of it re-added, that are outside the interior
if (bubble_distance < 0.) {
if (f.faceid != NEW_FACE)
mesh_mods.to_remove[f.faceid] = true;
if (f.parent != NEW_FACE) // Top parent needs to be removed as well
mesh_mods.to_remove[f.parent] = true;
// If the outside part is between the interior end the exterior
// (inside the wall being invisible), no further division is needed.
if ((R + D) < interior.thickness)
return false;
return true;
} else if (f.faceid == NEW_FACE) {
// New face completely outside needs to be re-added.
mesh_mods.new_triangles.emplace_back(f.verts);
}
return false;
};
interior.reset_accessor();
exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) {
const Vec3i &face = faces[face_idx];
// If the triangle is excluded, we need to keep it.
if (is_excluded(face_idx))
return;
std::array<Vec3f, 3> pts =
{ vertices[face(0)], vertices[face(1)], vertices[face(2)] };
BoundingBoxf3 facebb { pts.begin(), pts.end() };
// Face is certainly outside the cavity
if (! facebb.intersects(bb)) return;
DivFace df{face, pts, long(face_idx)};
if (divfn(df))
divide_triangle(df, divfn);
}, exec_policy::max_concurreny());
auto new_faces = reserve_vector<Vec3i>(faces.size() +
mesh_mods.new_triangles.size());
for (size_t face_idx = 0; face_idx < faces.size(); ++face_idx) {
if (!mesh_mods.to_remove[face_idx])
new_faces.emplace_back(faces[face_idx]);
}
for(size_t i = 0; i < mesh_mods.new_triangles.size(); ++i) {
size_t o = vertices.size();
vertices.emplace_back(mesh_mods.new_triangles[i][0]);
vertices.emplace_back(mesh_mods.new_triangles[i][1]);
vertices.emplace_back(mesh_mods.new_triangles[i][2]);
new_faces.emplace_back(int(o), int(o + 1), int(o + 2));
}
BOOST_LOG_TRIVIAL(info)
<< "Trimming: " << mesh_mods.to_remove_cnt() << " triangles removed";
BOOST_LOG_TRIVIAL(info)
<< "Trimming: " << mesh_mods.new_triangles.size() << " triangles added";
faces.swap(new_faces);
new_faces = {};
mesh = TriangleMesh{mesh.its};
mesh.repaired = true;
mesh.require_shared_vertices();
}

View file

@ -19,6 +19,17 @@ struct HollowingConfig
bool enabled = true;
};
enum HollowingFlags { hfRemoveInsideTriangles = 0x1 };
// All data related to a generated mesh interior. Includes the 3D grid and mesh
// and various metadata. No need to manipulate from outside.
struct Interior;
struct InteriorDeleter { void operator()(Interior *p); };
using InteriorPtr = std::unique_ptr<Interior, InteriorDeleter>;
TriangleMesh & get_mesh(Interior &interior);
const TriangleMesh &get_mesh(const Interior &interior);
struct DrainHole
{
Vec3f pos;
@ -60,11 +71,26 @@ using DrainHoles = std::vector<DrainHole>;
constexpr float HoleStickOutLength = 1.f;
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh,
InteriorPtr generate_interior(const TriangleMesh &mesh,
const HollowingConfig & = {},
const JobController &ctl = {});
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg);
// Will do the hollowing
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0);
// Hollowing prepared in "interior", merge with original mesh
void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0);
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask = {});
double get_distance(const Vec3f &p, const Interior &interior);
template<class T>
FloatingOnly<T> get_distance(const Vec<3, T> &p, const Interior &interior)
{
return get_distance(Vec3f(p.template cast<float>()), interior);
}
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
const std::vector<float> &slicegrid,

View file

@ -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;
}

View file

@ -85,6 +85,10 @@ public:
// Get the mesh that is going to be printed with all the modifications
// like hollowing and drilled holes.
const TriangleMesh & get_mesh_to_print() const {
return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh();
}
const TriangleMesh & get_mesh_to_slice() const {
return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh();
}
@ -328,8 +332,9 @@ private:
{
public:
TriangleMesh interior;
sla::InteriorPtr interior;
mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh
mutable TriangleMesh hollow_mesh_with_holes_trimmed;
};
std::unique_ptr<HollowingData> m_hollowing_data;

View file

@ -1,3 +1,5 @@
#include <unordered_set>
#include <libslic3r/Exception.hpp>
#include <libslic3r/SLAPrintSteps.hpp>
#include <libslic3r/MeshBoolean.hpp>
@ -131,21 +133,150 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
double quality = po.m_config.hollowing_quality.getFloat();
double closing_d = po.m_config.hollowing_closing_distance.getFloat();
sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg);
if (meshptr->empty())
sla::InteriorPtr interior = generate_interior(po.transformed_mesh(), hlwcfg);
if (!interior || sla::get_mesh(*interior).empty())
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
else {
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
po.m_hollowing_data->interior = *meshptr;
po.m_hollowing_data->interior = std::move(interior);
}
}
struct FaceHash {
// A hash is created for each triangle to be identifiable. The hash uses
// only the triangle's geometric traits, not the index in a particular mesh.
std::unordered_set<std::string> facehash;
static std::string facekey(const Vec3i &face,
const std::vector<Vec3f> &vertices)
{
// Scale to integer to avoid floating points
std::array<Vec<3, int64_t>, 3> pts = {
scaled<int64_t>(vertices[face(0)]),
scaled<int64_t>(vertices[face(1)]),
scaled<int64_t>(vertices[face(2)])
};
// Get the first two sides of the triangle, do a cross product and move
// that vector to the center of the triangle. This encodes all
// information to identify an identical triangle at the same position.
Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2];
Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3;
// Return a concatenated string representation of the coordinates
return std::to_string(c(0)) + std::to_string(c(1)) + std::to_string(c(2));
};
FaceHash(const indexed_triangle_set &its)
{
for (const Vec3i &face : its.indices) {
std::string keystr = facekey(face, its.vertices);
facehash.insert(keystr);
}
}
bool find(const std::string &key)
{
auto it = facehash.find(key);
return it != facehash.end();
}
};
// Create exclude mask for triangle removal inside hollowed interiors.
// This is necessary when the interior is already part of the mesh which was
// drilled using CGAL mesh boolean operation. Excluded will be the triangles
// originally part of the interior mesh and triangles that make up the drilled
// hole walls.
static std::vector<bool> create_exclude_mask(
const indexed_triangle_set &its,
const sla::Interior &interior,
const std::vector<sla::DrainHole> &holes)
{
FaceHash interior_hash{sla::get_mesh(interior).its};
std::vector<bool> exclude_mask(its.indices.size(), false);
std::vector< std::vector<size_t> > neighbor_index =
create_neighbor_index(its);
auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face)
{
for (int i = 0; i < 3; ++i) {
const std::vector<size_t> &neighbors = neighbor_index[face(i)];
for (size_t fi_n : neighbors) exclude_mask[fi_n] = true;
}
};
for (size_t fi = 0; fi < its.indices.size(); ++fi) {
auto &face = its.indices[fi];
if (interior_hash.find(FaceHash::facekey(face, its.vertices))) {
exclude_mask[fi] = true;
continue;
}
if (exclude_mask[fi]) {
exclude_neighbors(face);
continue;
}
// Lets deal with the holes. All the triangles of a hole and all the
// neighbors of these triangles need to be kept. The neigbors were
// created by CGAL mesh boolean operation that modified the original
// interior inside the input mesh to contain the holes.
Vec3d tr_center = (
its.vertices[face(0)] +
its.vertices[face(1)] +
its.vertices[face(2)]
).cast<double>() / 3.;
// If the center is more than half a mm inside the interior,
// it cannot possibly be part of a hole wall.
if (sla::get_distance(tr_center, interior) < -0.5)
continue;
Vec3f U = its.vertices[face(1)] - its.vertices[face(0)];
Vec3f V = its.vertices[face(2)] - its.vertices[face(0)];
Vec3f C = U.cross(V);
Vec3f face_normal = C.normalized();
for (const sla::DrainHole &dh : holes) {
Vec3d dhpos = dh.pos.cast<double>();
Vec3d dhend = dhpos + dh.normal.cast<double>() * dh.height;
Linef3 holeaxis{dhpos, dhend};
double D_hole_center = line_alg::distance_to(holeaxis, tr_center);
double D_hole = std::abs(D_hole_center - dh.radius);
float dot = dh.normal.dot(face_normal);
// Empiric tolerances for center distance and normals angle.
// For triangles that are part of a hole wall the angle of
// triangle normal and the hole axis is around 90 degrees,
// so the dot product is around zero.
double D_tol = dh.radius / sla::DrainHole::steps;
float normal_angle_tol = 1.f / sla::DrainHole::steps;
if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) {
exclude_mask[fi] = true;
exclude_neighbors(face);
}
}
}
return exclude_mask;
}
// Drill holes into the hollowed/original mesh.
void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
{
bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty();
bool is_hollowed = (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty());
bool is_hollowed =
(po.m_hollowing_data && po.m_hollowing_data->interior &&
!sla::get_mesh(*po.m_hollowing_data->interior).empty());
if (! is_hollowed && ! needs_drilling) {
// In this case we can dump any data that might have been
@ -163,12 +294,18 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
// holes that are no longer on the frontend.
TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
hollowed_mesh = po.transformed_mesh();
if (! po.m_hollowing_data->interior.empty()) {
hollowed_mesh.merge(po.m_hollowing_data->interior);
hollowed_mesh.require_shared_vertices();
}
if (is_hollowed)
sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior);
TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed;
if (! needs_drilling) {
mesh_view = po.transformed_mesh();
if (is_hollowed)
sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior,
sla::hfRemoveInsideTriangles);
BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes).";
return;
}
@ -196,6 +333,16 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
try {
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal);
mesh_view = hollowed_mesh;
if (is_hollowed) {
auto &interior = *po.m_hollowing_data->interior;
std::vector<bool> exclude_mask =
create_exclude_mask(mesh_view.its, interior, drainholes);
sla::remove_inside_triangles(mesh_view, interior, exclude_mask);
}
} catch (const std::runtime_error &) {
throw Slic3r::SlicingError(L(
"Drilling holes into the mesh failed. "
@ -213,7 +360,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
// same imaginary grid (the height vector argument to TriangleMeshSlicer).
void SLAPrint::Steps::slice_model(SLAPrintObject &po)
{
const TriangleMesh &mesh = po.get_mesh_to_print();
const TriangleMesh &mesh = po.get_mesh_to_slice();
// We need to prepare the slice index...
@ -260,9 +407,15 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
auto &slice_grid = po.m_model_height_levels;
slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr);
if (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()) {
po.m_hollowing_data->interior.repair(true);
TriangleMeshSlicer interior_slicer(&po.m_hollowing_data->interior);
sla::Interior *interior = po.m_hollowing_data ?
po.m_hollowing_data->interior.get() :
nullptr;
if (interior && ! sla::get_mesh(*interior).empty()) {
TriangleMesh interiormesh = sla::get_mesh(*interior);
interiormesh.repaired = false;
interiormesh.repair(true);
TriangleMeshSlicer interior_slicer(&interiormesh);
std::vector<ExPolygons> interior_slices;
interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr);
@ -297,7 +450,7 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
// If supports are disabled, we can skip the model scan.
if(!po.m_config.supports_enable.getBool()) return;
const TriangleMesh &mesh = po.get_mesh_to_print();
const TriangleMesh &mesh = po.get_mesh_to_slice();
if (!po.m_supportdata)
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));

View file

@ -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;
}
}

View file

@ -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,

View file

@ -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.)

View file

@ -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;

View file

@ -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:

View file

@ -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;

View file

@ -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);

View file

@ -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");
}

View file

@ -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();
}