Refactored hollowing backend to use indexed_triangle_mesh

This commit is contained in:
tamasmeszaros 2021-05-26 16:41:34 +02:00
parent f12187b53d
commit e6f97358bc
14 changed files with 204 additions and 68 deletions

View File

@ -54,14 +54,16 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
{ {
openvdb::initialize(); openvdb::initialize();
TriangleMeshPtrs meshparts_raw = mesh.split(); std::vector<indexed_triangle_set> meshparts;
auto meshparts = reserve_vector<std::unique_ptr<TriangleMesh>>(meshparts_raw.size()); its_split(mesh, std::back_inserter(meshparts));
for (auto *p : meshparts_raw)
meshparts.emplace_back(p); // TriangleMeshPtrs meshparts_raw = mesh.split();
// auto meshparts = reserve_vector<std::unique_ptr<TriangleMesh>>(meshparts_raw.size());
// for (auto *p : meshparts_raw)
// meshparts.emplace_back(p);
auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) { auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) {
m->require_shared_vertices(); return its_volume(m) < EPSILON;
return m->volume() < EPSILON;
}); });
meshparts.erase(it, meshparts.end()); meshparts.erase(it, meshparts.end());
@ -69,7 +71,7 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
openvdb::FloatGrid::Ptr grid; openvdb::FloatGrid::Ptr grid;
for (auto &m : meshparts) { for (auto &m : meshparts) {
auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>( auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth, TriangleMeshDataAdapter{m, voxel_scale}, tr, exteriorBandWidth,
interiorBandWidth, flags); interiorBandWidth, flags);
if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid);
@ -91,8 +93,7 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
return grid; return grid;
} }
template<class Grid> indexed_triangle_set grid_to_mesh(const openvdb::FloatGrid &grid,
sla::Contour3D _volumeToMesh(const Grid &grid,
double isovalue, double isovalue,
double adaptivity, double adaptivity,
bool relaxDisorientedTriangles) bool relaxDisorientedTriangles)
@ -111,36 +112,20 @@ sla::Contour3D _volumeToMesh(const Grid &grid,
scale = grid.template metaValue<float>("voxel_scale"); scale = grid.template metaValue<float>("voxel_scale");
} catch (...) { } } catch (...) { }
sla::Contour3D ret; indexed_triangle_set ret;
ret.points.reserve(points.size()); ret.vertices.reserve(points.size());
ret.faces3.reserve(triangles.size()); ret.indices.reserve(triangles.size() + quads.size() * 2);
ret.faces4.reserve(quads.size());
for (auto &v : points) ret.points.emplace_back(to_vec3d(v) / scale); for (auto &v : points) ret.vertices.emplace_back(to_vec3f(v) / scale);
for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v)); for (auto &v : triangles) ret.indices.emplace_back(to_vec3i(v));
for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v)); for (auto &quad : quads) {
ret.indices.emplace_back(quad(0), quad(1), quad(2));
ret.indices.emplace_back(quad(2), quad(3), quad(0));
}
return ret; return ret;
} }
TriangleMesh grid_to_mesh(const openvdb::FloatGrid &grid,
double isovalue,
double adaptivity,
bool relaxDisorientedTriangles)
{
return to_triangle_mesh(
_volumeToMesh(grid, isovalue, adaptivity, relaxDisorientedTriangles));
}
sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
double isovalue,
double adaptivity,
bool relaxDisorientedTriangles)
{
return _volumeToMesh(grid, isovalue, adaptivity,
relaxDisorientedTriangles);
}
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
double iso, double iso,
double er, double er,

View File

@ -35,11 +35,6 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
float interiorBandWidth = 3.0f, float interiorBandWidth = 3.0f,
int flags = 0); int flags = 0);
sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
double isovalue,
double adaptivity,
bool relaxDisorientedTriangles = true);
indexed_triangle_set grid_to_mesh(const openvdb::FloatGrid &grid, indexed_triangle_set grid_to_mesh(const openvdb::FloatGrid &grid,
double isovalue = 0.0, double isovalue = 0.0,
double adaptivity = 0.0, double adaptivity = 0.0,

View File

@ -77,7 +77,7 @@ static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh,
if (ctl.stopcondition()) return {}; if (ctl.stopcondition()) return {};
else ctl.statuscb(0, L("Hollowing")); else ctl.statuscb(0, L("Hollowing"));
auto gridptr = mesh_to_grid(mesh, {}, voxel_scale, out_range, in_range); auto gridptr = mesh_to_grid(mesh.its, {}, voxel_scale, out_range, in_range);
assert(gridptr); assert(gridptr);
@ -136,19 +136,15 @@ InteriorPtr generate_interior(const TriangleMesh & mesh,
if (interior && !interior->mesh.empty()) { if (interior && !interior->mesh.empty()) {
// This flips the normals to be outward facing... // flip normals back...
interior->mesh.require_shared_vertices(); swap_normals(interior->mesh);
indexed_triangle_set its = std::move(interior->mesh.its); Slic3r::simplify_mesh(interior->mesh);
Slic3r::simplify_mesh(its); its_compactify_vertices(interior->mesh);
its_merge_vertices(interior->mesh);
// flip normals back... // flip normals back...
for (stl_triangle_vertex_indices &ind : its.indices) swap_normals(interior->mesh);
std::swap(ind(0), ind(2));
interior->mesh = Slic3r::TriangleMesh{its};
interior->mesh.repaired = true;
interior->mesh.require_shared_vertices();
} }
return interior; return interior;
@ -325,7 +321,7 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags)
if (flags & hfRemoveInsideTriangles && interior.gridptr) if (flags & hfRemoveInsideTriangles && interior.gridptr)
remove_inside_triangles(mesh, interior); remove_inside_triangles(mesh, interior);
mesh.merge(interior.mesh); mesh.merge(TriangleMesh{interior.mesh});
mesh.require_shared_vertices(); mesh.require_shared_vertices();
} }

View File

@ -99,6 +99,12 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices,
const sla::DrainHoles & holes, const sla::DrainHoles & holes,
std::function<void(void)> thr); std::function<void(void)> thr);
inline void swap_normals(indexed_triangle_set &its)
{
for (auto &face : its.indices)
std::swap(face(0), face(2));
}
} // namespace sla } // namespace sla
} // namespace Slic3r } // namespace Slic3r

View File

@ -480,7 +480,6 @@ void pad_blueprint(const indexed_triangle_set &mesh,
{ {
if (mesh.empty()) return; if (mesh.empty()) return;
assert(mesh.has_shared_vertices());
std::vector<ExPolygons> out = slice_mesh_ex(mesh, heights, thrfn); std::vector<ExPolygons> out = slice_mesh_ex(mesh, heights, thrfn);
size_t count = 0; size_t count = 0;

View File

@ -45,7 +45,6 @@ std::vector<ExPolygons> SupportTree::slice(const std::vector<float> &grid,
if (!sup_mesh.empty()) { if (!sup_mesh.empty()) {
slices.emplace_back(); slices.emplace_back();
assert(sup_mesh.has_shared_vertices());
slices.back() = slice_mesh_ex(sup_mesh, grid, cr, ctl().cancelfn); slices.back() = slice_mesh_ex(sup_mesh, grid, cr, ctl().cancelfn);
} }
@ -59,7 +58,6 @@ std::vector<ExPolygons> SupportTree::slice(const std::vector<float> &grid,
auto padgrid = reserve_vector<float>(size_t(cap > 0 ? cap : 0)); auto padgrid = reserve_vector<float>(size_t(cap > 0 ? cap : 0));
std::copy(grid.begin(), maxzit, std::back_inserter(padgrid)); std::copy(grid.begin(), maxzit, std::back_inserter(padgrid));
assert(pad_mesh.has_shared_vertices());
slices.back() = slice_mesh_ex(pad_mesh, padgrid, cr, ctl().cancelfn); slices.back() = slice_mesh_ex(pad_mesh, padgrid, cr, ctl().cancelfn);
} }

View File

@ -1067,6 +1067,7 @@ Vec3d SLAPrint::relative_correction() const
namespace { // dummy empty static containers for return values in some methods namespace { // dummy empty static containers for return values in some methods
const std::vector<ExPolygons> EMPTY_SLICES; const std::vector<ExPolygons> EMPTY_SLICES;
const TriangleMesh EMPTY_MESH; const TriangleMesh EMPTY_MESH;
const indexed_triangle_set EMPTY_TRIANGLE_SET;
const ExPolygons EMPTY_SLICE; const ExPolygons EMPTY_SLICE;
const std::vector<sla::SupportPoint> EMPTY_SUPPORT_POINTS; const std::vector<sla::SupportPoint> EMPTY_SUPPORT_POINTS;
} }
@ -1143,13 +1144,13 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const
return EMPTY_MESH; return EMPTY_MESH;
} }
const TriangleMesh &SLAPrintObject::hollowed_interior_mesh() const const indexed_triangle_set &SLAPrintObject::hollowed_interior_mesh() const
{ {
if (m_hollowing_data && m_hollowing_data->interior && if (m_hollowing_data && m_hollowing_data->interior &&
m_config.hollowing_enable.getBool()) m_config.hollowing_enable.getBool())
return sla::get_mesh(*m_hollowing_data->interior); return sla::get_mesh(*m_hollowing_data->interior);
return EMPTY_MESH; return EMPTY_TRIANGLE_SET;
} }
const TriangleMesh &SLAPrintObject::transformed_mesh() const { const TriangleMesh &SLAPrintObject::transformed_mesh() const {

View File

@ -79,7 +79,7 @@ public:
const TriangleMesh& pad_mesh() const; const TriangleMesh& pad_mesh() const;
// Ready after this->is_step_done(slaposDrillHoles) is true // Ready after this->is_step_done(slaposDrillHoles) is true
const TriangleMesh& hollowed_interior_mesh() const; const indexed_triangle_set &hollowed_interior_mesh() const;
// Get the mesh that is going to be printed with all the modifications // Get the mesh that is going to be printed with all the modifications
// like hollowing and drilled holes. // like hollowing and drilled holes.

View File

@ -194,7 +194,7 @@ static std::vector<bool> create_exclude_mask(
const sla::Interior &interior, const sla::Interior &interior,
const std::vector<sla::DrainHole> &holes) const std::vector<sla::DrainHole> &holes)
{ {
FaceHash interior_hash{sla::get_mesh(interior).its}; FaceHash interior_hash{sla::get_mesh(interior)};
std::vector<bool> exclude_mask(its.indices.size(), false); std::vector<bool> exclude_mask(its.indices.size(), false);
@ -489,11 +489,11 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
nullptr; nullptr;
if (interior && ! sla::get_mesh(*interior).empty()) { if (interior && ! sla::get_mesh(*interior).empty()) {
TriangleMesh interiormesh = sla::get_mesh(*interior); indexed_triangle_set interiormesh = sla::get_mesh(*interior);
interiormesh.repaired = false; sla::swap_normals(interiormesh);
interiormesh.repair(true);
params.mode = MeshSlicingParams::SlicingMode::Regular; params.mode = MeshSlicingParams::SlicingMode::Regular;
std::vector<ExPolygons> interior_slices = slice_mesh_ex(interiormesh.its, slice_grid, params, thr);
std::vector<ExPolygons> interior_slices = slice_mesh_ex(interiormesh, slice_grid, closing_r, thr);
sla::ccr::for_each(size_t(0), interior_slices.size(), sla::ccr::for_each(size_t(0), interior_slices.size(),
[&po, &interior_slices] (size_t i) { [&po, &interior_slices] (size_t i) {

View File

@ -1161,4 +1161,67 @@ void its_merge(indexed_triangle_set &A, const Pointf3s &triangles)
its_merge(A, trianglesf); its_merge(A, trianglesf);
} }
float its_volume(const indexed_triangle_set &its)
{
if (its.empty()) return 0.;
// Choose a point, any point as the reference.
auto p0 = its.vertices.front();
float volume = 0.f;
for (size_t i = 0; i < its.indices.size(); ++ i) {
// Do dot product to get distance from point to plane.
its_triangle triangle = its_triangle_vertices(its, i);
Vec3f U = triangle[1] - triangle[0];
Vec3f V = triangle[2] - triangle[0];
Vec3f C = U.cross(V);
Vec3f normal = C.normalized();
float area = 0.5 * C.norm();
float height = normal.dot(triangle[0] - p0);
volume += (area * height) / 3.0f;
}
return volume;
} }
PartMap::PartMap(const indexed_triangle_set & its,
const std::vector<std::vector<size_t>> &vfidx)
: count(0), face_part_indices(its.indices.size(), UNVISITED)
{
auto next_face_idx = [this](size_t start) {
size_t i = start;
while (face_part_indices[i++] >= 0);
return i;
};
size_t face_idx = 0;
size_t part_idx = 0;
do {
face_idx = next_face_idx(face_idx);
} while(split_recurse(its, vfidx, face_idx, part_idx++));
count = size_t(part_idx - 1);
}
bool PartMap::split_recurse(const indexed_triangle_set & its,
const std::vector<std::vector<size_t>> &vfidx,
size_t fi,
size_t part_idx)
{
if (face_part_indices[fi] >= 0)
return false;
face_part_indices[fi] = part_idx;
const auto &face = its.indices[fi];
for (size_t v = 0; v < 3; ++v) {
auto vi = face(v);
const std::vector<size_t> neigh_faces = vfidx[vi];
for (size_t neigh_face : neigh_faces)
split_recurse(its, vfidx, neigh_face, part_idx);
}
return true;
}
} // namespace Slic3r

View File

@ -136,6 +136,77 @@ int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit =
// Remove vertices, which none of the faces references. Return number of freed vertices. // Remove vertices, which none of the faces references. Return number of freed vertices.
int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit = true); int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit = true);
// Used by its_split to map each face of a mesh to a part index. Can be used
// to query the number of parts in a mesh.
struct PartMap
{
static constexpr int UNVISITED = -1;
size_t count;
std::vector<int> face_part_indices;
PartMap(const indexed_triangle_set & its,
const std::vector<std::vector<size_t>> &vfidx);
explicit PartMap(const indexed_triangle_set &its)
: PartMap(its, create_vertex_faces_index(its))
{}
private:
bool split_recurse(const indexed_triangle_set & its,
const std::vector<std::vector<size_t>> &vfidx,
size_t fi,
size_t part_idx);
};
template<class OutputIt>
void its_split(const indexed_triangle_set &its,
const PartMap & partmap,
OutputIt out_it)
{
std::vector<indexed_triangle_set> meshes(partmap.count);
std::vector<int> vidx_conv(its.vertices.size() * meshes.size(),
PartMap::UNVISITED);
auto &parts = partmap.face_part_indices;
for (size_t fi = 0; fi < parts.size(); ++fi) {
int pi = parts[fi];
if (pi < 0) continue;
indexed_triangle_set &part_its = meshes[size_t(pi)];
const auto & face = its.indices[fi];
size_t conv_begin = (pi * its.vertices.size());
Vec3i new_face;
for (size_t v = 0; v < 3; ++v) {
auto vi = face(v);
size_t conv_idx = conv_begin + vi;
if (vidx_conv[conv_idx] == PartMap::UNVISITED) {
vidx_conv[conv_idx] = part_its.vertices.size();
part_its.vertices.emplace_back(its.vertices[size_t(vi)]);
}
new_face(v) = vidx_conv[conv_idx];
}
part_its.indices.emplace_back(new_face);
}
for (indexed_triangle_set &part_its : meshes)
out_it = std::move(part_its);
}
template<class OutputIt>
void its_split(const indexed_triangle_set & its,
OutputIt out_it)
{
its_split(its, PartMap{its}, out_it);
}
// Shrink the vectors of its.vertices and its.faces to a minimum size by reallocating the two vectors. // Shrink the vectors of its.vertices and its.faces to a minimum size by reallocating the two vectors.
void its_shrink_to_fit(indexed_triangle_set &its); void its_shrink_to_fit(indexed_triangle_set &its);
@ -164,6 +235,8 @@ inline stl_normal its_unnormalized_normal(const indexed_triangle_set &its,
return (tri[1] - tri[0]).cross(tri[2] - tri[0]); return (tri[1] - tri[0]).cross(tri[2] - tri[0]);
} }
float its_volume(const indexed_triangle_set &its);
void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B); void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B);
void its_merge(indexed_triangle_set &A, const std::vector<Vec3f> &triangles); void its_merge(indexed_triangle_set &A, const std::vector<Vec3f> &triangles);
void its_merge(indexed_triangle_set &A, const Pointf3s &triangles); void its_merge(indexed_triangle_set &A, const Pointf3s &triangles);

View File

@ -208,7 +208,7 @@ void HollowedMesh::on_update()
m_drainholes = print_object->model_object()->sla_drain_holes; m_drainholes = print_object->model_object()->sla_drain_holes;
m_old_hollowing_timestamp = timestamp; m_old_hollowing_timestamp = timestamp;
const TriangleMesh &interior = print_object->hollowed_interior_mesh(); const indexed_triangle_set &interior = print_object->hollowed_interior_mesh();
if (!interior.empty()) { if (!interior.empty()) {
m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(interior); m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(interior);
m_hollowed_interior_transformed->repaired = false; m_hollowed_interior_transformed->repaired = false;

View File

@ -20,3 +20,23 @@ TEST_CASE("Hollow two overlapping spheres") {
sphere1.WriteOBJFile("twospheres.obj"); sphere1.WriteOBJFile("twospheres.obj");
} }
TEST_CASE("Split its") {
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();
std::vector<indexed_triangle_set> parts;
its_split(sphere1.its, std::back_inserter(parts));
size_t part_idx = 0;
for (auto &part_its : parts) {
its_write_obj(part_its, (std::string("part_its") + std::to_string(part_idx++) + ".obj").c_str());
}
}

View File

@ -94,7 +94,7 @@ void test_supports(const std::string &obj_filename,
if (hollowingcfg.enabled) { if (hollowingcfg.enabled) {
sla::InteriorPtr interior = sla::generate_interior(mesh, hollowingcfg); sla::InteriorPtr interior = sla::generate_interior(mesh, hollowingcfg);
REQUIRE(interior); REQUIRE(interior);
mesh.merge(sla::get_mesh(*interior)); mesh.merge(TriangleMesh{sla::get_mesh(*interior)});
mesh.require_shared_vertices(); mesh.require_shared_vertices();
} }