diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 2d3606c47..31289e7ba 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -2,11 +2,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -14,6 +16,8 @@ #include #include +#include + #include #include @@ -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(visitor)); } -void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, +void remove_inside_triangles(indexed_triangle_set &mesh, const Interior &interior, const std::vector &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 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 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 &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 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 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 on_hole_fail) +{ + auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + hollowed_mesh.vertices, + hollowed_mesh.indices + ); + + std::uniform_real_distribution 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 ebb{bb.min.cast(), + bb.max.cast()}; + + 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(HollowMeshResult::Ok); + + if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) { + ret |= static_cast(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(HollowMeshResult::FaultyMesh); + } + + if (!MeshBoolean::cgal::empty(*holes_mesh_cgal) + && !MeshBoolean::cgal::does_bound_a_volume(*holes_mesh_cgal)) { + ret |= static_cast(HollowMeshResult::FaultyHoles); + } + + // Don't even bother + if (ret & static_cast(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 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(HollowMeshResult::DrillingFailed); + } + + return ret; +} + }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index aa43b021b..c21bdff4e 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -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 on_hole_fail = [](size_t){}); + void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, const std::vector &exclude_mask = {}); +void remove_inside_triangles(indexed_triangle_set &mesh, const Interior &interior, + const std::vector &exclude_mask = {}); + sla::DrainHoles transformed_drainhole_points(const ModelObject &mo, const Transform3d &trafo); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 1c56b8946..c7567528b 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -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 &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(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(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(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::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 diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index a1b3ef42d..32d10a424 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -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