Resurrect the old hollowing and hole drilling functions.

Apply them if generic cgal fails and there are no negative volumes.
This commit is contained in:
tamasmeszaros 2023-01-10 14:39:03 +01:00
parent 7c834de6ab
commit b2ef76f4d0
4 changed files with 270 additions and 16 deletions

View File

@ -2,11 +2,13 @@
#include <optional>
#include <numeric>
#include <unordered_set>
#include <random>
#include <libslic3r/OpenVDBUtils.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/TriangleMeshSlicer.hpp>
#include <libslic3r/SLA/Hollowing.hpp>
#include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/AABBMesh.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/QuadricEdgeCollapse.hpp>
@ -14,6 +16,8 @@
#include <libslic3r/Execution/ExecutionSeq.hpp>
#include <libslic3r/Model.hpp>
#include <libslic3r/MeshBoolean.hpp>
#include <boost/log/trivial.hpp>
#include <libslic3r/MTUtils.hpp>
@ -291,6 +295,19 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags)
mesh.merge(inter);
}
void hollow_mesh(indexed_triangle_set &mesh, const Interior &interior, int flags)
{
if (mesh.empty() || interior.mesh.empty()) return;
if (flags & hfRemoveInsideTriangles && interior.gridptr)
remove_inside_triangles(mesh, interior);
indexed_triangle_set interi = interior.mesh;
sla::swap_normals(interi);
its_merge(mesh, interi);
}
// 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.
@ -378,14 +395,14 @@ void divide_triangle(const DivFace &face, Fn &&visitor)
divide_triangle(child2, std::forward<Fn>(visitor));
}
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
void remove_inside_triangles(indexed_triangle_set &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();
auto &faces = mesh.indices;
auto &vertices = mesh.vertices;
auto bb = bounding_box(mesh); //mesh.bounding_box();
bool use_exclude_mask = faces.size() == exclude_mask.size();
auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) {
@ -421,8 +438,8 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
// or not.
std::vector<bool> to_remove;
MeshMods(const TriangleMesh &mesh):
to_remove(mesh.its.indices.size(), false) {}
MeshMods(const indexed_triangle_set &mesh):
to_remove(mesh.indices.size(), false) {}
// Number of triangles that need to be removed.
size_t to_remove_cnt() const
@ -484,7 +501,8 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
const Vec3i &face = faces[face_idx];
// If the triangle is excluded, we need to keep it.
if (is_excluded(face_idx)) return;
if (is_excluded(face_idx))
return;
std::array<Vec3f, 3> pts = {vertices[face(0)], vertices[face(1)],
vertices[face(2)]};
@ -492,7 +510,8 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
BoundingBoxf3 facebb{pts.begin(), pts.end()};
// Face is certainly outside the cavity
if (!facebb.intersects(bb)) return;
if (!facebb.intersects(bb))
return;
DivFace df{face, pts, long(face_idx)};
@ -525,10 +544,16 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
faces.swap(new_faces);
new_faces = {};
mesh = TriangleMesh{mesh.its};
// mesh = TriangleMesh{mesh.its};
//FIXME do we want to repair the mesh? Are there duplicate vertices or flipped triangles?
}
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask)
{
remove_inside_triangles(mesh.its, interior, exclude_mask);
}
struct FaceHash {
// A 64 bit number's max hex digits
@ -590,8 +615,10 @@ struct FaceHash {
FaceHash (const indexed_triangle_set &its): facehash(its.indices.size())
{
for (const Vec3i &face : its.indices)
for (Vec3i face : its.indices) {
std::swap(face(0), face(2));
facehash.insert(facekey(face, its.vertices));
}
}
bool find(const std::string &key)
@ -747,4 +774,126 @@ double get_voxel_scale(double mesh_volume, const HollowingConfig &hc)
return voxel_scale;
}
// The same as its_compactify_vertices, but returns a new mesh, doesn't touch
// the original
static indexed_triangle_set
remove_unconnected_vertices(const indexed_triangle_set &its)
{
if (its.indices.empty()) {};
indexed_triangle_set M;
std::vector<int> vtransl(its.vertices.size(), -1);
int vcnt = 0;
for (auto &f : its.indices) {
for (int i = 0; i < 3; ++i)
if (vtransl[size_t(f(i))] < 0) {
M.vertices.emplace_back(its.vertices[size_t(f(i))]);
vtransl[size_t(f(i))] = vcnt++;
}
std::array<int, 3> new_f = {
vtransl[size_t(f(0))],
vtransl[size_t(f(1))],
vtransl[size_t(f(2))]
};
M.indices.emplace_back(new_f[0], new_f[1], new_f[2]);
}
return M;
}
int hollow_mesh_and_drill(indexed_triangle_set &hollowed_mesh,
const Interior &interior,
const DrainHoles &drainholes,
std::function<void(size_t)> on_hole_fail)
{
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
hollowed_mesh.vertices,
hollowed_mesh.indices
);
std::uniform_real_distribution<float> dist(0., float(EPSILON));
auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}, {});
indexed_triangle_set part_to_drill = hollowed_mesh;
std::mt19937 m_rng{std::random_device{}()};
for (size_t i = 0; i < drainholes.size(); ++i) {
sla::DrainHole holept = drainholes[i];
holept.normal += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
holept.normal.normalize();
holept.pos += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
indexed_triangle_set m = holept.to_mesh();
part_to_drill.indices.clear();
auto bb = bounding_box(m);
Eigen::AlignedBox<float, 3> ebb{bb.min.cast<float>(),
bb.max.cast<float>()};
AABBTreeIndirect::traverse(
tree,
AABBTreeIndirect::intersecting(ebb),
[&part_to_drill, &hollowed_mesh](const auto& node)
{
part_to_drill.indices.emplace_back(hollowed_mesh.indices[node.idx]);
// continue traversal
return true;
});
auto cgal_meshpart = MeshBoolean::cgal::triangle_mesh_to_cgal(
remove_unconnected_vertices(part_to_drill));
if (MeshBoolean::cgal::does_self_intersect(*cgal_meshpart)) {
on_hole_fail(i);
continue;
}
auto cgal_hole = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_hole);
}
auto ret = static_cast<int>(HollowMeshResult::Ok);
if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) {
ret |= static_cast<int>(HollowMeshResult::DrillingFailed);
}
auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh);
if (!MeshBoolean::cgal::does_bound_a_volume(*hollowed_mesh_cgal)) {
ret |= static_cast<int>(HollowMeshResult::FaultyMesh);
}
if (!MeshBoolean::cgal::empty(*holes_mesh_cgal)
&& !MeshBoolean::cgal::does_bound_a_volume(*holes_mesh_cgal)) {
ret |= static_cast<int>(HollowMeshResult::FaultyHoles);
}
// Don't even bother
if (ret & static_cast<int>(HollowMeshResult::DrillingFailed))
return ret;
try {
if (!MeshBoolean::cgal::empty(*holes_mesh_cgal))
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
hollowed_mesh =
MeshBoolean::cgal::cgal_to_indexed_triangle_set(*hollowed_mesh_cgal);
std::vector<bool> exclude_mask =
create_exclude_mask(hollowed_mesh, interior, drainholes);
sla::remove_inside_triangles(hollowed_mesh, interior, exclude_mask);
} catch (const Slic3r::RuntimeError &) {
ret |= static_cast<int>(HollowMeshResult::DrillingFailed);
}
return ret;
}
}} // namespace Slic3r::sla

View File

@ -159,9 +159,32 @@ 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);
// Will do the hollowing
void hollow_mesh(indexed_triangle_set &mesh, const HollowingConfig &cfg, int flags = 0);
// Hollowing prepared in "interior", merge with original mesh
void hollow_mesh(indexed_triangle_set &mesh, const Interior &interior, int flags = 0);
enum class HollowMeshResult {
Ok = 0,
FaultyMesh = 1,
FaultyHoles = 2,
DrillingFailed = 4
};
// Return HollowMeshResult codes OR-ed.
int hollow_mesh_and_drill(
indexed_triangle_set &mesh,
const Interior& interior,
const DrainHoles &holes,
std::function<void(size_t)> on_hole_fail = [](size_t){});
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask = {});
void remove_inside_triangles(indexed_triangle_set &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask = {});
sla::DrainHoles transformed_drainhole_points(const ModelObject &mo,
const Transform3d &trafo);

View File

@ -87,7 +87,6 @@ std::string PRINT_STEP_LABELS(size_t idx)
SLAPrint::Steps::Steps(SLAPrint *print)
: m_print{print}
, m_rng{std::random_device{}()}
, objcount{m_print->m_objects.size()}
, ilhd{m_print->m_material_config.initial_layer_height.getFloat()}
, ilh{float(ilhd)}
@ -187,6 +186,12 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb(
return m;
}
inline auto parts_to_slice(const std::multiset<CSGPartForStep> &parts,
SLAPrintObjectStep step)
{
auto r = parts.equal_range(step);
return Range{r.first, r.second};
}
void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step)
{
@ -196,13 +201,91 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st
auto r = range(po.m_mesh_to_slice);
auto m = indexed_triangle_set{};
bool handled = false;
if (is_all_positive(r)) {
m = csgmesh_merge_positive_parts(r);
handled = true;
} else if (csg::check_csgmesh_booleans(r) == r.end()) {
auto cgalmeshptr = csg::perform_csgmesh_booleans(r);
if (cgalmeshptr)
if (cgalmeshptr) {
m = MeshBoolean::cgal::cgal_to_indexed_triangle_set(*cgalmeshptr);
handled = true;
}
} else {
// Normal cgal processing failed. If there are no negative volumes,
// the hollowing can be tried with the old algorithm which didn't handled volumes.
// If that fails for any of the drillholes, the voxelization fallback is
// used.
bool is_pure_model = is_all_positive(parts_to_slice(po.m_mesh_to_slice, slaposAssembly));
bool can_hollow = po.m_hollowing_data && po.m_hollowing_data->interior &&
!sla::get_mesh(*po.m_hollowing_data->interior).empty();
bool hole_fail = false;
if (step == slaposHollowing && is_pure_model) {
if (can_hollow) {
m = csgmesh_merge_positive_parts(r);
sla::hollow_mesh(m, *po.m_hollowing_data->interior,
sla::hfRemoveInsideTriangles);
}
handled = true;
} else if (step == slaposDrillHoles && is_pure_model) {
if (po.m_model_object->sla_drain_holes.empty()) {
m = po.m_preview_meshes[slaposHollowing].its;
handled = true;
} else if (can_hollow) {
m = csgmesh_merge_positive_parts(r);
sla::hollow_mesh(m, *po.m_hollowing_data->interior);
sla::DrainHoles drainholes = po.transformed_drainhole_points();
auto ret = sla::hollow_mesh_and_drill(
m, *po.m_hollowing_data->interior, drainholes,
[/*&po, &drainholes, */&hole_fail](size_t i)
{
hole_fail = /*drainholes[i].failed =
po.model_object()->sla_drain_holes[i].failed =*/ true;
});
if (ret & static_cast<int>(sla::HollowMeshResult::FaultyMesh)) {
po.active_step_add_warning(
PrintStateBase::WarningLevel::NON_CRITICAL,
L("Mesh to be hollowed is not suitable for hollowing (does not "
"bound a volume)."));
}
if (ret & static_cast<int>(sla::HollowMeshResult::FaultyHoles)) {
po.active_step_add_warning(
PrintStateBase::WarningLevel::NON_CRITICAL,
L("Unable to drill the current configuration of holes into the "
"model."));
}
handled = true;
if (ret & static_cast<int>(sla::HollowMeshResult::DrillingFailed)) {
po.active_step_add_warning(
PrintStateBase::WarningLevel::NON_CRITICAL, L(
"Drilling holes into the mesh failed. "
"This is usually caused by broken model. Try to fix it first."));
handled = false;
}
if (hole_fail) {
po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
L("Failed to drill some holes into the model"));
handled = false;
}
}
}
}
if (!handled) { // Last resort to voxelization.
po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
L("Can't perform full mesh booleans! "
"Some parts of the print will be previewed with approximated meshes. "
@ -290,9 +373,6 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
indexed_triangle_set m = sla::get_mesh(*po.m_hollowing_data->interior);
// Release the data, won't be needed anymore, takes huge amount of ram
po.m_hollowing_data->interior.reset();
if (!m.empty()) {
// simplify mesh lossless
float loss_less_max_error = 2*std::numeric_limits<float>::epsilon();
@ -327,6 +407,9 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
generate_preview(po, slaposDrillHoles);
else
po.m_preview_meshes[slaposDrillHoles] = po.get_mesh_to_print();
// Release the data, won't be needed anymore, takes huge amount of ram
po.m_hollowing_data->interior.reset();
}
template<class Pred>

View File

@ -14,7 +14,6 @@ class SLAPrint::Steps
{
private:
SLAPrint *m_print = nullptr;
std::mt19937 m_rng;
public:
// where the per object operations start and end