Refactored hollowing backend to use indexed_triangle_mesh
This commit is contained in:
parent
f12187b53d
commit
e6f97358bc
@ -54,14 +54,16 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
|
||||
{
|
||||
openvdb::initialize();
|
||||
|
||||
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);
|
||||
std::vector<indexed_triangle_set> meshparts;
|
||||
its_split(mesh, std::back_inserter(meshparts));
|
||||
|
||||
// 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) {
|
||||
m->require_shared_vertices();
|
||||
return m->volume() < EPSILON;
|
||||
return its_volume(m) < EPSILON;
|
||||
});
|
||||
|
||||
meshparts.erase(it, meshparts.end());
|
||||
@ -69,7 +71,7 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
|
||||
openvdb::FloatGrid::Ptr grid;
|
||||
for (auto &m : meshparts) {
|
||||
auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||
TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth,
|
||||
TriangleMeshDataAdapter{m, voxel_scale}, tr, exteriorBandWidth,
|
||||
interiorBandWidth, flags);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
template<class Grid>
|
||||
sla::Contour3D _volumeToMesh(const Grid &grid,
|
||||
indexed_triangle_set grid_to_mesh(const openvdb::FloatGrid &grid,
|
||||
double isovalue,
|
||||
double adaptivity,
|
||||
bool relaxDisorientedTriangles)
|
||||
@ -111,36 +112,20 @@ sla::Contour3D _volumeToMesh(const Grid &grid,
|
||||
scale = grid.template metaValue<float>("voxel_scale");
|
||||
} catch (...) { }
|
||||
|
||||
sla::Contour3D ret;
|
||||
ret.points.reserve(points.size());
|
||||
ret.faces3.reserve(triangles.size());
|
||||
ret.faces4.reserve(quads.size());
|
||||
indexed_triangle_set ret;
|
||||
ret.vertices.reserve(points.size());
|
||||
ret.indices.reserve(triangles.size() + quads.size() * 2);
|
||||
|
||||
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));
|
||||
for (auto &v : points) ret.vertices.emplace_back(to_vec3f(v) / scale);
|
||||
for (auto &v : triangles) ret.indices.emplace_back(to_vec3i(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;
|
||||
}
|
||||
|
||||
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,
|
||||
double iso,
|
||||
double er,
|
||||
|
@ -35,11 +35,6 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const indexed_triangle_set & mesh,
|
||||
float interiorBandWidth = 3.0f,
|
||||
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,
|
||||
double isovalue = 0.0,
|
||||
double adaptivity = 0.0,
|
||||
|
@ -77,7 +77,7 @@ static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh,
|
||||
if (ctl.stopcondition()) return {};
|
||||
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);
|
||||
|
||||
@ -136,19 +136,15 @@ InteriorPtr generate_interior(const TriangleMesh & mesh,
|
||||
|
||||
if (interior && !interior->mesh.empty()) {
|
||||
|
||||
// This flips the normals to be outward facing...
|
||||
interior->mesh.require_shared_vertices();
|
||||
indexed_triangle_set its = std::move(interior->mesh.its);
|
||||
// flip normals back...
|
||||
swap_normals(interior->mesh);
|
||||
Slic3r::simplify_mesh(interior->mesh);
|
||||
|
||||
Slic3r::simplify_mesh(its);
|
||||
its_compactify_vertices(interior->mesh);
|
||||
its_merge_vertices(interior->mesh);
|
||||
|
||||
// flip normals back...
|
||||
for (stl_triangle_vertex_indices &ind : its.indices)
|
||||
std::swap(ind(0), ind(2));
|
||||
|
||||
interior->mesh = Slic3r::TriangleMesh{its};
|
||||
interior->mesh.repaired = true;
|
||||
interior->mesh.require_shared_vertices();
|
||||
swap_normals(interior->mesh);
|
||||
}
|
||||
|
||||
return interior;
|
||||
@ -325,7 +321,7 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags)
|
||||
if (flags & hfRemoveInsideTriangles && interior.gridptr)
|
||||
remove_inside_triangles(mesh, interior);
|
||||
|
||||
mesh.merge(interior.mesh);
|
||||
mesh.merge(TriangleMesh{interior.mesh});
|
||||
mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
|
@ -99,6 +99,12 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
||||
const sla::DrainHoles & holes,
|
||||
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 Slic3r
|
||||
|
||||
|
@ -480,7 +480,6 @@ void pad_blueprint(const indexed_triangle_set &mesh,
|
||||
{
|
||||
if (mesh.empty()) return;
|
||||
|
||||
assert(mesh.has_shared_vertices());
|
||||
std::vector<ExPolygons> out = slice_mesh_ex(mesh, heights, thrfn);
|
||||
|
||||
size_t count = 0;
|
||||
|
@ -45,7 +45,6 @@ std::vector<ExPolygons> SupportTree::slice(const std::vector<float> &grid,
|
||||
|
||||
if (!sup_mesh.empty()) {
|
||||
slices.emplace_back();
|
||||
assert(sup_mesh.has_shared_vertices());
|
||||
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));
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1067,6 +1067,7 @@ Vec3d SLAPrint::relative_correction() const
|
||||
namespace { // dummy empty static containers for return values in some methods
|
||||
const std::vector<ExPolygons> EMPTY_SLICES;
|
||||
const TriangleMesh EMPTY_MESH;
|
||||
const indexed_triangle_set EMPTY_TRIANGLE_SET;
|
||||
const ExPolygons EMPTY_SLICE;
|
||||
const std::vector<sla::SupportPoint> EMPTY_SUPPORT_POINTS;
|
||||
}
|
||||
@ -1143,13 +1144,13 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const
|
||||
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 &&
|
||||
m_config.hollowing_enable.getBool())
|
||||
return sla::get_mesh(*m_hollowing_data->interior);
|
||||
|
||||
return EMPTY_MESH;
|
||||
return EMPTY_TRIANGLE_SET;
|
||||
}
|
||||
|
||||
const TriangleMesh &SLAPrintObject::transformed_mesh() const {
|
||||
|
@ -79,7 +79,7 @@ public:
|
||||
const TriangleMesh& pad_mesh() const;
|
||||
|
||||
// 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
|
||||
// like hollowing and drilled holes.
|
||||
|
@ -194,7 +194,7 @@ static std::vector<bool> create_exclude_mask(
|
||||
const sla::Interior &interior,
|
||||
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);
|
||||
|
||||
@ -489,11 +489,11 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
|
||||
nullptr;
|
||||
|
||||
if (interior && ! sla::get_mesh(*interior).empty()) {
|
||||
TriangleMesh interiormesh = sla::get_mesh(*interior);
|
||||
interiormesh.repaired = false;
|
||||
interiormesh.repair(true);
|
||||
indexed_triangle_set interiormesh = sla::get_mesh(*interior);
|
||||
sla::swap_normals(interiormesh);
|
||||
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(),
|
||||
[&po, &interior_slices] (size_t i) {
|
||||
|
@ -1161,4 +1161,67 @@ void its_merge(indexed_triangle_set &A, const Pointf3s &triangles)
|
||||
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
|
||||
|
@ -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.
|
||||
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.
|
||||
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]);
|
||||
}
|
||||
|
||||
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 std::vector<Vec3f> &triangles);
|
||||
void its_merge(indexed_triangle_set &A, const Pointf3s &triangles);
|
||||
|
@ -208,7 +208,7 @@ void HollowedMesh::on_update()
|
||||
m_drainholes = print_object->model_object()->sla_drain_holes;
|
||||
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()) {
|
||||
m_hollowed_interior_transformed = std::make_unique<TriangleMesh>(interior);
|
||||
m_hollowed_interior_transformed->repaired = false;
|
||||
|
@ -20,3 +20,23 @@ TEST_CASE("Hollow two overlapping spheres") {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,7 @@ void test_supports(const std::string &obj_filename,
|
||||
if (hollowingcfg.enabled) {
|
||||
sla::InteriorPtr interior = sla::generate_interior(mesh, hollowingcfg);
|
||||
REQUIRE(interior);
|
||||
mesh.merge(sla::get_mesh(*interior));
|
||||
mesh.merge(TriangleMesh{sla::get_mesh(*interior)});
|
||||
mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user