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:
parent
7c834de6ab
commit
b2ef76f4d0
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user