From e57eca0289e2f5e3e6bb2f6d6fa53e0fc48987c3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Dec 2020 13:38:09 +0100 Subject: [PATCH 01/16] Add voxel scale to openvdb metadata. To be able to retrieve that information from a generated grid alone. To avoid the copying of input mesh (for scaling) when doing the hollowing Also remove some unused stuff from OpenVDBUtils --- src/libslic3r/OpenVDBUtils.cpp | 110 +++++++++++++------------------- src/libslic3r/OpenVDBUtils.hpp | 14 ++-- src/libslic3r/SLA/Hollowing.cpp | 36 +++++------ 3 files changed, 70 insertions(+), 90 deletions(-) diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 0f5bfa157..259cd1cbd 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -22,74 +22,52 @@ 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() * 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(); - 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(); auto it = std::remove_if(meshparts.begin(), meshparts.end(), - [](TriangleMesh *m){ - m->require_shared_vertices(); - return !m->is_manifold() || m->volume() < EPSILON; - }); + [](TriangleMesh *m){ + m->require_shared_vertices(); + return !m->is_manifold() || m->volume() < EPSILON; + }); meshparts.erase(it, meshparts.end()); openvdb::FloatGrid::Ptr grid; for (TriangleMesh *m : meshparts) { auto subgrid = openvdb::tools::meshToVolume( - TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth, + TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth, interiorBandWidth, flags); if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); @@ -106,19 +84,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( - Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth, - flags); + return grid; } template @@ -128,20 +96,25 @@ sla::Contour3D _volumeToMesh(const Grid &grid, bool relaxDisorientedTriangles) { openvdb::initialize(); - + std::vector points; std::vector triangles; std::vector quads; - + openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue, adaptivity, relaxDisorientedTriangles); - + + float scale = 1.; + try { + scale = grid.template metaValue("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 +139,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 diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp index aa4b5154a..5df816abb 100644 --- a/src/libslic3r/OpenVDBUtils.hpp +++ b/src/libslic3r/OpenVDBUtils.hpp @@ -21,14 +21,16 @@ inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast 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); diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 6df752fd3..61e1e122b 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -32,48 +32,44 @@ static TriangleMesh _generate_interior(const TriangleMesh &mesh, 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")); - + + double iso_surface = D; + auto narrowb = double(in_range); if (closing_dist > .0) { - gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range)); + gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); } else { - D = -offset; + iso_surface = -offset; } - + 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); - + if (ctl.stopcondition()) return {}; else ctl.statuscb(100, L("Hollowing")); - + return omesh; } From 82954ba7152dc05f76445614e17ed97edbe0091e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 17 Dec 2020 16:38:04 +0100 Subject: [PATCH 02/16] Group hollowing result (including grid) into one struct --- src/libslic3r/SLA/Hollowing.cpp | 104 +++++++++++++++++++++-------- src/libslic3r/SLA/Hollowing.hpp | 23 +++++-- src/libslic3r/SLAPrint.cpp | 5 +- src/libslic3r/SLAPrint.hpp | 4 +- src/libslic3r/SLAPrintSteps.cpp | 28 +++++--- tests/libslic3r/test_hollowing.cpp | 30 ++++----- tests/sla_print/sla_test_utils.cpp | 6 +- 7 files changed, 133 insertions(+), 67 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 61e1e122b..44358cebe 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -26,11 +26,36 @@ inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); } template> 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; + double closing_distance = 0.; + double thickness = 0.; + double voxel_scale = 1.; + double nb_in = 3.; + double nb_out = 3.; +}; + +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) { double offset = voxel_scale * min_thickness; double D = voxel_scale * closing_dist; @@ -64,22 +89,30 @@ static TriangleMesh _generate_interior(const TriangleMesh &mesh, else ctl.statuscb(70, L("Hollowing")); double adaptivity = 0.; + InteriorPtr interior = InteriorPtr{new Interior{}}; - auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity); + 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 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 @@ -88,26 +121,29 @@ std::unique_ptr 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( - _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 @@ -269,12 +305,22 @@ void cut_drainholes(std::vector & 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 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) +// erase_inside_triangles_2(mesh, interior); + + mesh.merge(interior.mesh); mesh.require_shared_vertices(); } diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index 949cc2393..356907846 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -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; + +TriangleMesh & get_mesh(Interior &interior); +const TriangleMesh &get_mesh(const Interior &interior); + struct DrainHole { Vec3f pos; @@ -60,11 +71,15 @@ using DrainHoles = std::vector; constexpr float HoleStickOutLength = 1.f; -std::unique_ptr 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 cut_drainholes(std::vector & obj_slices, const std::vector &slicegrid, diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 16b068cb9..07042692a 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -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; } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index bed66ab4f..87ab3db8a 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -327,8 +327,8 @@ private: class HollowingData { public: - - TriangleMesh interior; + + sla::InteriorPtr interior; mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh }; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index d8bea62ae..976e6d3d1 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -131,13 +131,14 @@ 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); } } @@ -145,7 +146,9 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) 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,10 +166,7 @@ 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(); - } + sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior/*, sla::hfRemoveInsideTriangles*/); if (! needs_drilling) { BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; @@ -260,9 +260,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 interior_slices; interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); diff --git a/tests/libslic3r/test_hollowing.cpp b/tests/libslic3r/test_hollowing.cpp index 65b87c2a2..2218a27b7 100644 --- a/tests/libslic3r/test_hollowing.cpp +++ b/tests/libslic3r/test_hollowing.cpp @@ -26,21 +26,19 @@ static Slic3r::TriangleMesh load_model(const std::string &obj_filename) } -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 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"); +TEST_CASE("Hollow two overlapping spheres") { + using namespace Slic3r; + + TriangleMesh sphere1 = make_sphere(10., 2 * PI / 20.), sphere2 = sphere1; + + sphere1.translate(-5.f, 0.f, 0.f); + sphere2.translate( 5.f, 0.f, 0.f); + + sphere1.merge(sphere2); + sphere1.require_shared_vertices(); + + sla::hollow_mesh(sphere1, sla::HollowingConfig{}, sla::HollowingFlags::hfRemoveInsideTriangles); + + sphere1.WriteOBJFile("twospheres.obj"); } diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 653221cd3..1ec890beb 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -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(); } From 527e6752941bf8336fba91e63de65cf8fb4da544 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 4 Jan 2021 14:12:40 +0100 Subject: [PATCH 03/16] Use triangle removal only for visualized mesh --- src/libslic3r/SLA/Hollowing.cpp | 5 +++++ src/libslic3r/SLA/Hollowing.hpp | 2 ++ src/libslic3r/SLAPrint.hpp | 5 +++++ src/libslic3r/SLAPrintSteps.cpp | 9 ++++++--- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 44358cebe..a350e6faa 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -324,4 +324,9 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) mesh.require_shared_vertices(); } +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) +{ + +} + }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index 356907846..caa7d7b6b 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -81,6 +81,8 @@ 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); + void cut_drainholes(std::vector & obj_slices, const std::vector &slicegrid, float closing_radius, diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 87ab3db8a..74c71dc1e 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -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(); } @@ -330,6 +334,7 @@ private: sla::InteriorPtr interior; mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh + mutable TriangleMesh hollow_mesh_with_holes_trimmed; }; std::unique_ptr m_hollowing_data; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 976e6d3d1..59ad20c72 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -166,7 +166,10 @@ 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(); - sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior/*, sla::hfRemoveInsideTriangles*/); + sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); + + TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; + sla::remove_inside_triangles(mesh_view, *po.m_hollowing_data->interior); if (! needs_drilling) { BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; @@ -213,7 +216,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... @@ -303,7 +306,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)); From d48ca7fd030119a926518c1c5705854471d4a5ed Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 17:03:33 +0100 Subject: [PATCH 04/16] Fix incorrect mesh shown on plater after hollowing --- src/libslic3r/SLAPrint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 07042692a..42ed8b80f 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -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(); From dd202af8cd3a96977acfefc26c898989129f6485 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 26 Feb 2021 15:37:26 +0100 Subject: [PATCH 05/16] Fix stl export with hollowed mesh --- src/slic3r/GUI/Plater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7a38dd3a1..3d4d381fb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -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); From 06bf02df695ee70f266a5c33c55f73f5f7714d1d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 26 Feb 2021 15:37:48 +0100 Subject: [PATCH 06/16] Fix Gizmo preview with hollowed mesh --- src/libslic3r/SLAPrintSteps.cpp | 305 ++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 18 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 2 + src/slic3r/GUI/MeshUtils.cpp | 19 ++ src/slic3r/GUI/MeshUtils.hpp | 3 + 5 files changed, 194 insertions(+), 153 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 59ad20c72..4637aa761 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -84,17 +84,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 +102,13 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin std::vector &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,7 +124,7 @@ 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(); @@ -169,16 +169,17 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; - sla::remove_inside_triangles(mesh_view, *po.m_hollowing_data->interior); + mesh_view = po.transformed_mesh(); + sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, sla::hfRemoveInsideTriangles); if (! needs_drilling) { 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 dist(0., float(EPSILON)); auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({}); for (sla::DrainHole holept : drainholes) { @@ -190,12 +191,12 @@ 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); @@ -215,11 +216,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_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); @@ -229,40 +230,40 @@ 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(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); - + sla::Interior *interior = po.m_hollowing_data ? po.m_hollowing_data->interior.get() : nullptr; @@ -282,17 +283,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)); @@ -305,22 +306,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_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& heights = po.m_model_height_levels; @@ -328,27 +329,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( @@ -359,10 +360,10 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) const std::vector& 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"), @@ -377,9 +378,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); @@ -389,15 +390,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)) @@ -406,26 +407,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); } @@ -433,15 +434,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 @@ -451,19 +452,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); } @@ -473,25 +474,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(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 @@ -506,37 +507,37 @@ using ClipperPolygons = std::vector; 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); } @@ -544,28 +545,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) @@ -573,12 +574,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()); @@ -586,42 +587,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( @@ -630,7 +631,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); @@ -640,8 +641,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); } } @@ -650,53 +651,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(printer_config.display_width.getFloat()); const auto height = scaled(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 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; - + // 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) @@ -705,87 +706,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; @@ -803,15 +804,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) @@ -820,10 +821,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); } @@ -831,23 +832,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; - + // procedure to process one height level. This will run in parallel auto lvlfn = [this, &slck, increment, &dstatus, &pst] @@ -855,10 +856,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); @@ -870,10 +871,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); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index a34c7562e..7f6b10670 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -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(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.) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index 61c273297..ace256748 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -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 m_hollowed_mesh_transformed; + std::unique_ptr m_hollowed_interior_transformed; size_t m_old_hollowing_timestamp = 0; int m_print_object_idx = -1; int m_print_objects_count = 0; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index ee0abe76f..f9ccfd0d6 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -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 list_of_expolys; m_tms->set_up_direction(up.cast()); m_tms->slice(std::vector{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()); + + std::vector neg_polys; + negative_tms.slice(std::vector{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: diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 60dcb30c8..09caf199b 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -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 m_triangles2d; GLIndexedVertexArray m_vertex_array; From 195b39bb5bc47b07828ca2c1d0de8dd2107c1c4b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 24 Feb 2021 18:56:01 +0100 Subject: [PATCH 07/16] Eliminate memory leaks from hollowing code --- src/libslic3r/OpenVDBUtils.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 259cd1cbd..e09fceed7 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -54,18 +54,20 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh, { openvdb::initialize(); - TriangleMeshPtrs meshparts = mesh.split(); + TriangleMeshPtrs meshparts_raw = mesh.split(); + auto meshparts = reserve_vector>(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( TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth, interiorBandWidth, flags); From 7830c8f8aa2d8a447b0848771728575ba42c8618 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 4 Jan 2021 15:41:58 +0100 Subject: [PATCH 08/16] Add BoundingBox constructor with point set iterators --- src/libslic3r/BoundingBox.hpp | 52 ++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 8de28af5c..37483fc3e 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -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& points) : min(PointClass::Zero()), max(PointClass::Zero()) + + template > + 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::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(); + this->max = this->min; + for (++ it; it != to; ++ it) { + auto vec = it->template cast(); + 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 &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 &points); @@ -74,19 +82,27 @@ public: { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; } BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : BoundingBoxBase(p1, p1) { merge(p2); merge(p3); } - BoundingBox3Base(const std::vector& points) + + template > BoundingBox3Base(It from, It to) { - if (points.empty()) + if (from == to) throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor"); - typename std::vector::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(); + this->max = this->min; + for (++ it; it != to; ++ it) { + auto vec = it->template cast(); + 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 &points) + : BoundingBox3Base(points.begin(), points.end()) + {} + void merge(const PointClass &point); void merge(const std::vector &points); void merge(const BoundingBox3Base &bb); @@ -188,9 +204,7 @@ public: class BoundingBoxf3 : public BoundingBox3Base { public: - BoundingBoxf3() : BoundingBox3Base() {} - BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base(pmin, pmax) {} - BoundingBoxf3(const std::vector &points) : BoundingBox3Base(points) {} + using BoundingBox3Base::BoundingBox3Base; BoundingBoxf3 transformed(const Transform3d& matrix) const; }; From e3c2e513fa599a31480979b535e4cfe9346d5806 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 25 Feb 2021 15:49:50 +0100 Subject: [PATCH 09/16] Do grid redistance even with zero closing distance This prevents having a leftover grid with zero at the exterior boundary. Trimming expects zero at (offset + closing distance) inwards --- src/libslic3r/SLA/Hollowing.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index a350e6faa..ccc0caf98 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -79,11 +79,7 @@ static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh, double iso_surface = D; auto narrowb = double(in_range); - if (closing_dist > .0) { - gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); - } else { - iso_surface = -offset; - } + gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb); if (ctl.stopcondition()) return {}; else ctl.statuscb(70, L("Hollowing")); From b8c1c136667c8d4ab679a571478fa653220a0b1f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 18:20:11 +0100 Subject: [PATCH 10/16] Add max_concurrency method for various execution policies --- src/libslic3r/SLA/Concurrency.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/Concurrency.hpp b/src/libslic3r/SLA/Concurrency.hpp index b692914ac..8ff0ff809 100644 --- a/src/libslic3r/SLA/Concurrency.hpp +++ b/src/libslic3r/SLA/Concurrency.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -76,13 +77,18 @@ template<> struct _ccr from, to, init, std::forward(mergefn), [](typename I::value_type &i) { return i; }, granularity); } + + static size_t max_concurreny() + { + return tbb::this_task_arena::max_concurrency(); + } }; template<> struct _ccr { 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), [](typename I::value_type &i) { return i; }); } + + static size_t max_concurreny() { return 1; } }; using ccr = _ccr; From 1ec154012ec8e1017a71f883356314894acd0b66 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 18:24:57 +0100 Subject: [PATCH 11/16] Add working version of triangle trimming for hollowed meshes --- src/libslic3r/SLA/Hollowing.cpp | 245 +++++++++++++++++++++++++++++++- 1 file changed, 241 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index ccc0caf98..632917266 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -29,11 +29,21 @@ inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; } struct Interior { TriangleMesh mesh; openvdb::FloatGrid::Ptr gridptr; + mutable std::optional accessor; + double closing_distance = 0.; double thickness = 0.; double voxel_scale = 1.; - double nb_in = 3.; - double nb_out = 3.; + 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) @@ -313,16 +323,243 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) { if (mesh.empty() || interior.mesh.empty()) return; -// if (flags & hfRemoveInsideTriangles && interior.gridptr) -// erase_inside_triangles_2(mesh, interior); + 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(); + 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 verts; + long faceid = NEW_FACE; + long parent = NEW_FACE; +}; + +// Divide a face recursively and call visitor on all the sub-faces. +template +void divide_triangle(const DivFace &face, Fn &&visitor) +{ + std::array edges = {(face.verts[0] - face.verts[1]), + (face.verts[1] - face.verts[2]), + (face.verts[2] - face.verts[0])}; + + std::array 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(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(visitor)); +} + void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) { + enum TrPos { posInside, posTouch, posOutside }; + auto &faces = mesh.its.indices; + auto &vertices = mesh.its.vertices; + auto bb = mesh.bounding_box(); + + // 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> data; + exec_policy::SpinningMutex mutex; + + void emplace_back(const std::array &pts) + { + std::lock_guard lk{mutex}; + data.emplace_back(pts); + } + + size_t size() const { return data.size(); } + const std::array& 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 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(), 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]; + + std::array 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(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(); } }} // namespace Slic3r::sla From 4374716bfb45fc1dfa0af37bbc5836dce4da012a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 18:25:24 +0100 Subject: [PATCH 12/16] Triangle trimming should handle drilled meshes separately --- src/libslic3r/SLAPrintSteps.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 4637aa761..036fed171 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -169,10 +169,10 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed; - mesh_view = po.transformed_mesh(); - sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, sla::hfRemoveInsideTriangles); if (! needs_drilling) { + mesh_view = po.transformed_mesh(); + sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, sla::hfRemoveInsideTriangles); BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; return; } @@ -200,6 +200,9 @@ 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; + sla::remove_inside_triangles(mesh_view, *po.m_hollowing_data->interior); } catch (const std::runtime_error &) { throw Slic3r::SlicingError(L( "Drilling holes into the mesh failed. " From fbc758642bbfc8f5affc0dff24ce10cc6b383b2d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 18:48:24 +0100 Subject: [PATCH 13/16] Fix crash when the interior is corrupted --- src/libslic3r/SLAPrintSteps.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 036fed171..fe5d7505f 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -166,13 +166,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(); - sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior); + 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(); - sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, sla::hfRemoveInsideTriangles); + + if (is_hollowed) + sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior, + sla::hfRemoveInsideTriangles); + BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes)."; return; } @@ -200,9 +205,13 @@ 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; - sla::remove_inside_triangles(mesh_view, *po.m_hollowing_data->interior); + + if (is_hollowed) + sla::remove_inside_triangles(mesh_view, + *po.m_hollowing_data->interior, + drainholes); + } catch (const std::runtime_error &) { throw Slic3r::SlicingError(L( "Drilling holes into the mesh failed. " From a62262666a2d3ad304bcbe88a3971542e282ff1d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 3 Mar 2021 09:29:13 +0100 Subject: [PATCH 14/16] Exclude triangles of original interior mesh and drillholes from trimming --- src/libslic3r/SLA/Hollowing.cpp | 12 ++- src/libslic3r/SLA/Hollowing.hpp | 11 ++- src/libslic3r/SLAPrintSteps.cpp | 143 +++++++++++++++++++++++++++++++- src/libslic3r/TriangleMesh.cpp | 18 ++++ src/libslic3r/TriangleMesh.hpp | 6 ++ 5 files changed, 184 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 632917266..b38784521 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -421,7 +421,8 @@ 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(TriangleMesh &mesh, const Interior &interior, + const std::vector &exclude_mask) { enum TrPos { posInside, posTouch, posOutside }; @@ -429,6 +430,11 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) 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; @@ -518,6 +524,10 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior) 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 pts = { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index caa7d7b6b..5d2181e7a 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -81,7 +81,16 @@ 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); +void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, + const std::vector &exclude_mask = {}); + +double get_distance(const Vec3f &p, const Interior &interior); + +template +FloatingOnly get_distance(const Vec<3, T> &p, const Interior &interior) +{ + return get_distance(Vec3f(p.template cast()), interior); +} void cut_drainholes(std::vector & obj_slices, const std::vector &slicegrid, diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index fe5d7505f..455141051 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -142,6 +144,136 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) } } + +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 facehash; + + static std::string facekey(const Vec3i &face, + const std::vector &vertices) + { + // Scale to integer to avoid floating points + std::array, 3> pts = { + scaled(vertices[face(0)]), + scaled(vertices[face(1)]), + scaled(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 create_exclude_mask( + const indexed_triangle_set &its, + const sla::Interior &interior, + const std::vector &holes) +{ + FaceHash interior_hash{sla::get_mesh(interior).its}; + + std::vector exclude_mask(its.indices.size(), false); + + std::vector< std::vector > 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 &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]; + + std::string key = + FaceHash::facekey(face, its.vertices); + + if (interior_hash.find(key)) { + 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() / 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(); + Vec3d dhend = dhpos + dh.normal.cast() * 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) { @@ -207,10 +339,13 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); mesh_view = hollowed_mesh; - if (is_hollowed) - sla::remove_inside_triangles(mesh_view, - *po.m_hollowing_data->interior, - drainholes); + if (is_hollowed) { + auto &interior = *po.m_hollowing_data->interior; + std::vector 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( diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index adb9be64d..d5a349087 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -2063,4 +2063,22 @@ TriangleMesh make_sphere(double radius, double fa) return mesh; } +std::vector > 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 > index(its.vertices.size(), + reserve_vector(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; +} + } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 9625298f4..e6f6dc84b 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -89,6 +89,12 @@ private: std::deque find_unvisited_neighbors(std::vector &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 > +create_neighbor_index(const indexed_triangle_set &its); + enum FacetEdgeType { // A general case, the cutting plane intersect a face at two different edges. feGeneral, From 33d6655f26d70f23007afb26345dc302da894f23 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Mar 2021 19:35:46 +0100 Subject: [PATCH 15/16] Clean up hollowing test Needs rethinking anyway --- tests/libslic3r/test_hollowing.cpp | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/tests/libslic3r/test_hollowing.cpp b/tests/libslic3r/test_hollowing.cpp index 2218a27b7..1f5ca3845 100644 --- a/tests/libslic3r/test_hollowing.cpp +++ b/tests/libslic3r/test_hollowing.cpp @@ -2,29 +2,7 @@ #include #include -#include #include "libslic3r/SLA/Hollowing.hpp" -#include -#include "libslic3r/Format/OBJ.hpp" - -#include - -#include - -#if defined(WIN32) || defined(_WIN32) -#define PATH_SEPARATOR R"(\)" -#else -#define PATH_SEPARATOR R"(/)" -#endif - -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("Hollow two overlapping spheres") { using namespace Slic3r; From 3c2d0b7c6e6668a148e309b2b39bd9d41acdd5d5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 3 Mar 2021 10:04:26 +0100 Subject: [PATCH 16/16] Tiny cosmetics --- src/libslic3r/SLAPrintSteps.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 455141051..22fee6976 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -144,7 +144,6 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po) } } - struct FaceHash { // A hash is created for each triangle to be identifiable. The hash uses @@ -214,10 +213,7 @@ static std::vector create_exclude_mask( for (size_t fi = 0; fi < its.indices.size(); ++fi) { auto &face = its.indices[fi]; - std::string key = - FaceHash::facekey(face, its.vertices); - - if (interior_hash.find(key)) { + if (interior_hash.find(FaceHash::facekey(face, its.vertices))) { exclude_mask[fi] = true; continue; }