Merge branch 'fs_QuadricEdgeCollapse'
This commit is contained in:
commit
0ccc791750
@ -178,6 +178,8 @@ add_library(libslic3r STATIC
|
||||
PrintRegion.cpp
|
||||
PNGReadWrite.hpp
|
||||
PNGReadWrite.cpp
|
||||
QuadricEdgeCollapse.cpp
|
||||
QuadricEdgeCollapse.hpp
|
||||
Semver.cpp
|
||||
ShortestPath.cpp
|
||||
ShortestPath.hpp
|
||||
|
@ -354,7 +354,7 @@ inline void MutableSkipHeapPriorityQueue<T, LessPredicate, IndexSetter, blocking
|
||||
#endif /* NDEBUG */
|
||||
{
|
||||
// Mark as removed from the queue.
|
||||
m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max());
|
||||
m_index_setter(m_heap[1], std::numeric_limits<size_t>::max());
|
||||
}
|
||||
// Zero'th element is padding, thus non-empty queue must have at least two elements.
|
||||
if (m_heap.size() > 2) {
|
||||
|
654
src/libslic3r/QuadricEdgeCollapse.cpp
Normal file
654
src/libslic3r/QuadricEdgeCollapse.cpp
Normal file
@ -0,0 +1,654 @@
|
||||
#include "QuadricEdgeCollapse.hpp"
|
||||
#include <tuple>
|
||||
#include "MutablePriorityQueue.hpp"
|
||||
#include "SimplifyMeshImpl.hpp"
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
// only private namespace not neccessary be in hpp
|
||||
namespace QuadricEdgeCollapse {
|
||||
using Vertices = std::vector<stl_vertex>;
|
||||
using Triangle = stl_triangle_vertex_indices;
|
||||
using Indices = std::vector<stl_triangle_vertex_indices>;
|
||||
using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>;
|
||||
|
||||
// smallest error caused by edges, identify smallest edge in triangle
|
||||
struct Error
|
||||
{
|
||||
float value = -1.; // identifying of smallest edge is stored inside of TriangleInfo
|
||||
uint32_t triangle_index = 0;
|
||||
Error(float value, uint32_t triangle_index)
|
||||
: value(value)
|
||||
, triangle_index(triangle_index)
|
||||
{}
|
||||
Error() = default;
|
||||
};
|
||||
using Errors = std::vector<Error>;
|
||||
|
||||
// merge information together - faster access during processing
|
||||
struct TriangleInfo {
|
||||
Vec3f n; // normalized normal - used for check when fliped
|
||||
|
||||
// range(0 .. 2),
|
||||
unsigned char min_index = 0; // identify edge for minimal Error -> lightweight Error structure
|
||||
|
||||
TriangleInfo() = default;
|
||||
bool is_deleted() const { return n.x() > 2.f; }
|
||||
void set_deleted() { n.x() = 3.f; }
|
||||
};
|
||||
using TriangleInfos = std::vector<TriangleInfo>;
|
||||
struct VertexInfo {
|
||||
SymMat q; // sum quadric of surround triangles
|
||||
uint32_t start = 0, count = 0; // vertex neighbor triangles
|
||||
VertexInfo() = default;
|
||||
bool is_deleted() const { return count == 0; }
|
||||
};
|
||||
using VertexInfos = std::vector<VertexInfo>;
|
||||
struct EdgeInfo {
|
||||
uint32_t t_index=0; // triangle index
|
||||
unsigned char edge = 0; // 0 or 1 or 2
|
||||
EdgeInfo() = default;
|
||||
};
|
||||
using EdgeInfos = std::vector<EdgeInfo>;
|
||||
|
||||
// DTO for change neighbors
|
||||
struct CopyEdgeInfo {
|
||||
uint32_t start;
|
||||
uint32_t count;
|
||||
uint32_t move;
|
||||
CopyEdgeInfo(uint32_t start, uint32_t count, uint32_t move)
|
||||
: start(start), count(count), move(move)
|
||||
{}
|
||||
};
|
||||
using CopyEdgeInfos = std::vector<CopyEdgeInfo>;
|
||||
|
||||
Vec3d create_normal(const Triangle &triangle, const Vertices &vertices);
|
||||
std::array<Vec3d,3> create_vertices(uint32_t id_v1, uint32_t id_v2, const Vertices &vertices);
|
||||
std::array<double, 3> vertices_error(const SymMat &q, const std::array<Vec3d, 3> &vertices);
|
||||
double calculate_determinant(const SymMat &q);
|
||||
double calculate_error(uint32_t id_v1, uint32_t id_v2, const SymMat & q, const Vertices &vertices);
|
||||
Vec3f calculate_vertex(uint32_t id_v1, uint32_t id_v2, const SymMat & q, const Vertices &vertices);
|
||||
Vec3d calculate_vertex(double det, const SymMat &q);
|
||||
// calculate error for vertex and quadrics, triangle quadrics and triangle vertex give zero, only pozitive number
|
||||
double vertex_error(const SymMat &q, const Vec3d &vertex);
|
||||
SymMat create_quadric(const Triangle &t, const Vec3d& n, const Vertices &vertices);
|
||||
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors> init(const indexed_triangle_set &its);
|
||||
std::optional<uint32_t> find_triangle_index1(uint32_t vi, const VertexInfo& v_info,
|
||||
uint32_t ti, const EdgeInfos& e_infos, const Indices& indices);
|
||||
bool is_flipped(const Vec3f &new_vertex, uint32_t ti0, uint32_t ti1, const VertexInfo& v_info,
|
||||
const TriangleInfos &t_infos, const EdgeInfos &e_infos, const indexed_triangle_set &its);
|
||||
|
||||
// find edge with smallest error in triangle
|
||||
Vec3d calculate_3errors(const Triangle &t, const Vertices &vertices, const VertexInfos &v_infos);
|
||||
Error calculate_error(uint32_t ti, const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos, unsigned char& min_index);
|
||||
void remove_triangle(EdgeInfos &e_infos, VertexInfo &v_info, uint32_t ti);
|
||||
void change_neighbors(EdgeInfos &e_infos, VertexInfos &v_infos, uint32_t ti0, uint32_t ti1,
|
||||
uint32_t vi0, uint32_t vi1, uint32_t vi_top0,
|
||||
const Triangle &t1, CopyEdgeInfos& infos, EdgeInfos &e_infos1);
|
||||
void compact(const VertexInfos &v_infos, const TriangleInfos &t_infos, const EdgeInfos &e_infos, indexed_triangle_set &its);
|
||||
} // namespace QuadricEdgeCollapse
|
||||
|
||||
using namespace QuadricEdgeCollapse;
|
||||
|
||||
void Slic3r::its_quadric_edge_collapse(
|
||||
indexed_triangle_set & its,
|
||||
uint32_t triangle_count,
|
||||
float * max_error,
|
||||
std::function<void(void)> throw_on_cancel,
|
||||
std::function<void(int)> statusfn)
|
||||
{
|
||||
// constants --> may be move to config
|
||||
const int status_init_size = 10; // in percents
|
||||
const int check_cancel_period = 16; // how many edge to reduce before call throw_on_cancel
|
||||
|
||||
// check input
|
||||
if (triangle_count >= its.indices.size()) return;
|
||||
float maximal_error = (max_error == nullptr)? std::numeric_limits<float>::max() : *max_error;
|
||||
if (maximal_error <= 0.f) return;
|
||||
if (throw_on_cancel == nullptr) throw_on_cancel = []() {};
|
||||
if (statusfn == nullptr) statusfn = [](int) {};
|
||||
|
||||
TriangleInfos t_infos; // only normals with information about deleted triangle
|
||||
VertexInfos v_infos;
|
||||
EdgeInfos e_infos;
|
||||
Errors errors;
|
||||
std::tie(t_infos, v_infos, e_infos, errors) = init(its);
|
||||
throw_on_cancel();
|
||||
statusfn(status_init_size);
|
||||
|
||||
// convert from triangle index to mutable priority queue index
|
||||
std::vector<size_t> ti_2_mpqi(its.indices.size(), {0});
|
||||
auto setter = [&ti_2_mpqi](const Error &e, size_t index) { ti_2_mpqi[e.triangle_index] = index; };
|
||||
auto less = [](const Error &e1, const Error &e2) -> bool { return e1.value < e2.value; };
|
||||
auto mpq = make_miniheap_mutable_priority_queue<Error, 32, false>(std::move(setter), std::move(less));
|
||||
//MutablePriorityQueue<Error, decltype(setter), decltype(less)> mpq(std::move(setter), std::move(less));
|
||||
mpq.reserve(its.indices.size());
|
||||
for (Error &error :errors) mpq.push(error);
|
||||
|
||||
const size_t max_triangle_count_for_one_vertex = 50;
|
||||
CopyEdgeInfos ceis;
|
||||
ceis.reserve(max_triangle_count_for_one_vertex);
|
||||
EdgeInfos e_infos_swap;
|
||||
e_infos_swap.reserve(max_triangle_count_for_one_vertex);
|
||||
std::vector<uint32_t> changed_triangle_indices;
|
||||
changed_triangle_indices.reserve(2 * max_triangle_count_for_one_vertex);
|
||||
|
||||
uint32_t actual_triangle_count = its.indices.size();
|
||||
uint32_t count_triangle_to_reduce = actual_triangle_count - triangle_count;
|
||||
auto increase_status = [&]() {
|
||||
double reduced = (actual_triangle_count - triangle_count) /
|
||||
(double) count_triangle_to_reduce;
|
||||
double status = status_init_size + (100 - status_init_size) *
|
||||
(1. - reduced);
|
||||
statusfn(static_cast<int>(std::round(status)));
|
||||
};
|
||||
// modulo for update status
|
||||
uint32_t status_mod = std::max(uint32_t(16), count_triangle_to_reduce / 100);
|
||||
|
||||
uint32_t iteration_number = 0;
|
||||
float last_collapsed_error = 0.f;
|
||||
while (actual_triangle_count > triangle_count && !mpq.empty()) {
|
||||
++iteration_number;
|
||||
if (iteration_number % status_mod == 0) increase_status();
|
||||
if (iteration_number % check_cancel_period == 0) throw_on_cancel();
|
||||
|
||||
// triangle index 0
|
||||
Error e = mpq.top(); // copy
|
||||
if (e.value >= maximal_error) break; // Too big error
|
||||
mpq.pop();
|
||||
uint32_t ti0 = e.triangle_index;
|
||||
TriangleInfo &t_info0 = t_infos[ti0];
|
||||
if (t_info0.is_deleted()) continue;
|
||||
assert(t_info0.min_index < 3);
|
||||
|
||||
const Triangle &t0 = its.indices[ti0];
|
||||
uint32_t vi0 = t0[t_info0.min_index];
|
||||
uint32_t vi1 = t0[(t_info0.min_index+1) %3];
|
||||
// Need by move of neighbor edge infos in function: change_neighbors
|
||||
if (vi0 > vi1) std::swap(vi0, vi1);
|
||||
VertexInfo &v_info0 = v_infos[vi0];
|
||||
VertexInfo &v_info1 = v_infos[vi1];
|
||||
assert(!v_info0.is_deleted() && !v_info1.is_deleted());
|
||||
|
||||
// new vertex position
|
||||
SymMat q(v_info0.q);
|
||||
q += v_info1.q;
|
||||
Vec3f new_vertex0 = calculate_vertex(vi0, vi1, q, its.vertices);
|
||||
// set of triangle indices that change quadric
|
||||
auto ti1_opt = (v_info0.count < v_info1.count)?
|
||||
find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) :
|
||||
find_triangle_index1(vi0, v_info1, ti0, e_infos, its.indices) ;
|
||||
if (!ti1_opt.has_value() || // edge has only one triangle
|
||||
is_flipped(new_vertex0, ti0, *ti1_opt, v_info0, t_infos, e_infos, its) ||
|
||||
is_flipped(new_vertex0, ti0, *ti1_opt, v_info1, t_infos, e_infos, its)) {
|
||||
// try other triangle's edge
|
||||
Vec3d errors = calculate_3errors(t0, its.vertices, v_infos);
|
||||
Vec3i ord = (errors[0] < errors[1]) ?
|
||||
((errors[0] < errors[2])?
|
||||
((errors[1] < errors[2]) ? Vec3i(0, 1, 2) : Vec3i(0, 2, 1)) :
|
||||
Vec3i(2, 0, 1)):
|
||||
((errors[1] < errors[2])?
|
||||
((errors[0] < errors[2]) ? Vec3i(1, 0, 2) : Vec3i(1, 2, 0)) :
|
||||
Vec3i(2, 1, 0));
|
||||
if (t_info0.min_index == ord[0]) {
|
||||
t_info0.min_index = ord[1];
|
||||
e.value = errors[t_info0.min_index];
|
||||
} else if (t_info0.min_index == ord[1]) {
|
||||
t_info0.min_index = ord[2];
|
||||
e.value = errors[t_info0.min_index];
|
||||
} else {
|
||||
// error is changed when surround edge is reduced
|
||||
t_info0.min_index = 3; // bad index -> invalidate
|
||||
e.value = maximal_error;
|
||||
}
|
||||
// IMPROVE: check mpq top if it is ti1 with same edge
|
||||
mpq.push(e);
|
||||
continue;
|
||||
}
|
||||
uint32_t ti1 = *ti1_opt;
|
||||
last_collapsed_error = e.value;
|
||||
changed_triangle_indices.clear();
|
||||
changed_triangle_indices.reserve(v_info0.count + v_info1.count - 4);
|
||||
|
||||
// for each vertex0 triangles
|
||||
uint32_t v_info0_end = v_info0.start + v_info0.count;
|
||||
for (uint32_t di = v_info0.start; di < v_info0_end; ++di) {
|
||||
assert(di < e_infos.size());
|
||||
uint32_t ti = e_infos[di].t_index;
|
||||
if (ti == ti0) continue; // ti0 will be deleted
|
||||
if (ti == ti1) continue; // ti1 will be deleted
|
||||
changed_triangle_indices.emplace_back(ti);
|
||||
}
|
||||
|
||||
// for each vertex1 triangles
|
||||
uint32_t v_info1_end = v_info1.start + v_info1.count;
|
||||
for (uint32_t di = v_info1.start; di < v_info1_end; ++di) {
|
||||
assert(di < e_infos.size());
|
||||
EdgeInfo &e_info = e_infos[di];
|
||||
uint32_t ti = e_info.t_index;
|
||||
if (ti == ti0) continue; // ti0 will be deleted
|
||||
if (ti == ti1) continue; // ti1 will be deleted
|
||||
Triangle &t = its.indices[ti];
|
||||
t[e_info.edge] = vi0; // change index
|
||||
changed_triangle_indices.emplace_back(ti);
|
||||
}
|
||||
v_info0.q = q;
|
||||
|
||||
// fix neighbors
|
||||
|
||||
// vertex index of triangle 0 which is not vi0 nor vi1
|
||||
uint32_t vi_top0 = t0[(t_info0.min_index + 2) % 3];
|
||||
const Triangle &t1 = its.indices[ti1];
|
||||
change_neighbors(e_infos, v_infos, ti0, ti1, vi0, vi1,
|
||||
vi_top0, t1, ceis, e_infos_swap);
|
||||
|
||||
// Change vertex
|
||||
its.vertices[vi0] = new_vertex0;
|
||||
|
||||
// fix errors - must be after set neighbors - v_infos
|
||||
mpq.remove(ti_2_mpqi[ti1]);
|
||||
for (uint32_t ti : changed_triangle_indices) {
|
||||
size_t priority_queue_index = ti_2_mpqi[ti];
|
||||
TriangleInfo& t_info = t_infos[ti];
|
||||
t_info.n = create_normal(its.indices[ti], its.vertices).cast<float>(); // recalc normals
|
||||
mpq[priority_queue_index] = calculate_error(ti, its.indices[ti], its.vertices, v_infos, t_info.min_index);
|
||||
mpq.update(priority_queue_index);
|
||||
}
|
||||
|
||||
// set triangle(0 + 1) indices as deleted
|
||||
TriangleInfo &t_info1 = t_infos[ti1];
|
||||
t_info0.set_deleted();
|
||||
t_info1.set_deleted();
|
||||
// triangle counter decrementation
|
||||
actual_triangle_count-=2;
|
||||
}
|
||||
|
||||
// compact triangle
|
||||
compact(v_infos, t_infos, e_infos, its);
|
||||
if (max_error != nullptr) *max_error = last_collapsed_error;
|
||||
}
|
||||
|
||||
Vec3d QuadricEdgeCollapse::create_normal(const Triangle &triangle,
|
||||
const Vertices &vertices)
|
||||
{
|
||||
Vec3d v0 = vertices[triangle[0]].cast<double>();
|
||||
Vec3d v1 = vertices[triangle[1]].cast<double>();
|
||||
Vec3d v2 = vertices[triangle[2]].cast<double>();
|
||||
// n = triangle normal
|
||||
Vec3d n = (v1 - v0).cross(v2 - v0);
|
||||
n.normalize();
|
||||
return n;
|
||||
}
|
||||
|
||||
double QuadricEdgeCollapse::calculate_determinant(const SymMat &q)
|
||||
{
|
||||
return q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
|
||||
}
|
||||
|
||||
Vec3d QuadricEdgeCollapse::calculate_vertex(double det, const SymMat &q) {
|
||||
double det_1 = -1 / det;
|
||||
double det_x = q.det(1, 2, 3, 4, 5, 6, 5, 7, 8); // vx = A41/det(q_delta)
|
||||
double det_y = q.det(0, 2, 3, 1, 5, 6, 2, 7, 8); // vy = A42/det(q_delta)
|
||||
double det_z = q.det(0, 1, 3, 1, 4, 6, 2, 5, 8); // vz = A43/det(q_delta)
|
||||
return Vec3d(det_1 * det_x, -det_1 * det_y, det_1 * det_z);
|
||||
}
|
||||
|
||||
std::array<Vec3d,3> QuadricEdgeCollapse::create_vertices(uint32_t id_v1, uint32_t id_v2, const Vertices &vertices)
|
||||
{
|
||||
Vec3d v0 = vertices[id_v1].cast<double>();
|
||||
Vec3d v1 = vertices[id_v2].cast<double>();
|
||||
Vec3d vm = (v0 + v1) / 2.;
|
||||
return {v0, v1, vm};
|
||||
}
|
||||
|
||||
std::array<double, 3> QuadricEdgeCollapse::vertices_error(
|
||||
const SymMat &q, const std::array<Vec3d, 3> &vertices)
|
||||
{
|
||||
return {
|
||||
vertex_error(q, vertices[0]),
|
||||
vertex_error(q, vertices[1]),
|
||||
vertex_error(q, vertices[2])};
|
||||
}
|
||||
|
||||
double QuadricEdgeCollapse::calculate_error(uint32_t id_v1,
|
||||
uint32_t id_v2,
|
||||
const SymMat & q,
|
||||
const Vertices &vertices)
|
||||
{
|
||||
double det = calculate_determinant(q);
|
||||
if (std::abs(det) < std::numeric_limits<double>::epsilon()) {
|
||||
// can't divide by zero
|
||||
auto verts = create_vertices(id_v1, id_v2, vertices);
|
||||
auto errors = vertices_error(q, verts);
|
||||
return *std::min_element(std::begin(errors), std::end(errors));
|
||||
}
|
||||
Vec3d vertex = calculate_vertex(det, q);
|
||||
return vertex_error(q, vertex);
|
||||
}
|
||||
|
||||
// similar as calculate error but focus on new vertex without calculation of error
|
||||
Vec3f QuadricEdgeCollapse::calculate_vertex(uint32_t id_v1,
|
||||
uint32_t id_v2,
|
||||
const SymMat & q,
|
||||
const Vertices &vertices)
|
||||
{
|
||||
double det = calculate_determinant(q);
|
||||
if (std::abs(det) < std::numeric_limits<double>::epsilon()) {
|
||||
// can't divide by zero
|
||||
auto verts = create_vertices(id_v1, id_v2, vertices);
|
||||
auto errors = vertices_error(q, verts);
|
||||
auto mit = std::min_element(std::begin(errors), std::end(errors));
|
||||
return verts[mit - std::begin(errors)].cast<float>();
|
||||
}
|
||||
return calculate_vertex(det, q).cast<float>();
|
||||
}
|
||||
|
||||
double QuadricEdgeCollapse::vertex_error(const SymMat &q, const Vec3d &vertex)
|
||||
{
|
||||
const double &x = vertex.x(), &y = vertex.y(), &z = vertex.z();
|
||||
return q[0] * x * x + 2 * q[1] * x * y + 2 * q[2] * x * z +
|
||||
2 * q[3] * x + q[4] * y * y + 2 * q[5] * y * z +
|
||||
2 * q[6] * y + q[7] * z * z + 2 * q[8] * z + q[9];
|
||||
}
|
||||
|
||||
SymMat QuadricEdgeCollapse::create_quadric(const Triangle &t,
|
||||
const Vec3d & n,
|
||||
const Vertices &vertices)
|
||||
{
|
||||
Vec3d v0 = vertices[t[0]].cast<double>();
|
||||
return SymMat(n.x(), n.y(), n.z(), -n.dot(v0));
|
||||
}
|
||||
|
||||
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors>
|
||||
QuadricEdgeCollapse::init(const indexed_triangle_set &its)
|
||||
{
|
||||
TriangleInfos t_infos(its.indices.size());
|
||||
VertexInfos v_infos(its.vertices.size());
|
||||
{
|
||||
std::vector<SymMat> triangle_quadrics(its.indices.size());
|
||||
// calculate normals
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
|
||||
[&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
const Triangle &t = its.indices[i];
|
||||
TriangleInfo & t_info = t_infos[i];
|
||||
Vec3d normal = create_normal(t, its.vertices);
|
||||
t_info.n = normal.cast<float>();
|
||||
triangle_quadrics[i] = create_quadric(t, normal, its.vertices);
|
||||
}
|
||||
}); // END parallel for
|
||||
|
||||
// sum quadrics
|
||||
for (size_t i = 0; i < its.indices.size(); i++) {
|
||||
const Triangle &t = its.indices[i];
|
||||
const SymMat & q = triangle_quadrics[i];
|
||||
for (size_t e = 0; e < 3; e++) {
|
||||
VertexInfo &v_info = v_infos[t[e]];
|
||||
v_info.q += q;
|
||||
++v_info.count; // triangle count
|
||||
}
|
||||
}
|
||||
} // remove triangle quadrics
|
||||
|
||||
// set offseted starts
|
||||
uint32_t triangle_start = 0;
|
||||
for (VertexInfo &v_info : v_infos) {
|
||||
v_info.start = triangle_start;
|
||||
triangle_start += v_info.count;
|
||||
// set filled vertex to zero
|
||||
v_info.count = 0;
|
||||
}
|
||||
assert(its.indices.size() * 3 == triangle_start);
|
||||
|
||||
// calc error
|
||||
Errors errors(its.indices.size());
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
|
||||
[&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||
const Triangle &t = its.indices[i];
|
||||
TriangleInfo & t_info = t_infos[i];
|
||||
errors[i] = calculate_error(i, t, its.vertices, v_infos, t_info.min_index);
|
||||
}
|
||||
}); // END parallel for
|
||||
|
||||
// create reference
|
||||
EdgeInfos e_infos(its.indices.size() * 3);
|
||||
for (size_t i = 0; i < its.indices.size(); i++) {
|
||||
const Triangle &t = its.indices[i];
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
VertexInfo &v_info = v_infos[t[j]];
|
||||
size_t ei = v_info.start + v_info.count;
|
||||
assert(ei < e_infos.size());
|
||||
EdgeInfo &e_info = e_infos[ei];
|
||||
e_info.t_index = i;
|
||||
e_info.edge = j;
|
||||
++v_info.count;
|
||||
}
|
||||
}
|
||||
return {t_infos, v_infos, e_infos, errors};
|
||||
}
|
||||
|
||||
std::optional<uint32_t> QuadricEdgeCollapse::find_triangle_index1(uint32_t vi,
|
||||
const VertexInfo &v_info,
|
||||
uint32_t ti0,
|
||||
const EdgeInfos & e_infos,
|
||||
const Indices & indices)
|
||||
{
|
||||
coord_t vi_coord = static_cast<coord_t>(vi);
|
||||
uint32_t end = v_info.start + v_info.count;
|
||||
for (uint32_t ei = v_info.start; ei < end; ++ei) {
|
||||
const EdgeInfo &e_info = e_infos[ei];
|
||||
if (e_info.t_index == ti0) continue;
|
||||
const Triangle& t = indices[e_info.t_index];
|
||||
if (t[(e_info.edge + 1) % 3] == vi_coord ||
|
||||
t[(e_info.edge + 2) % 3] == vi_coord)
|
||||
return e_info.t_index;
|
||||
}
|
||||
// triangle0 is on border and do NOT have twin edge
|
||||
return {};
|
||||
}
|
||||
|
||||
bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex,
|
||||
uint32_t ti0,
|
||||
uint32_t ti1,
|
||||
const VertexInfo & v_info,
|
||||
const TriangleInfos & t_infos,
|
||||
const EdgeInfos & e_infos,
|
||||
const indexed_triangle_set &its)
|
||||
{
|
||||
static const float thr_pos = 1.0f - std::numeric_limits<float>::epsilon();
|
||||
static const float thr_neg = -thr_pos;
|
||||
static const float dot_thr = 0.2f; // Value from simplify mesh cca 80 DEG
|
||||
|
||||
// for each vertex triangles
|
||||
size_t v_info_end = v_info.start + v_info.count;
|
||||
for (size_t ei = v_info.start; ei < v_info_end; ++ei) {
|
||||
assert(ei < e_infos.size());
|
||||
const EdgeInfo &e_info = e_infos[ei];
|
||||
if (e_info.t_index == ti0) continue; // ti0 will be deleted
|
||||
if (e_info.t_index == ti1) continue; // ti1 will be deleted
|
||||
const Triangle &t = its.indices[e_info.t_index];
|
||||
const Vec3f &normal = t_infos[e_info.t_index].n;
|
||||
const Vec3f &vf = its.vertices[t[(e_info.edge + 1) % 3]];
|
||||
const Vec3f &vs = its.vertices[t[(e_info.edge + 2) % 3]];
|
||||
|
||||
Vec3f d1 = vf - new_vertex;
|
||||
d1.normalize();
|
||||
Vec3f d2 = vs - new_vertex;
|
||||
d2.normalize();
|
||||
|
||||
float dot = d1.dot(d2);
|
||||
if (dot > thr_pos || dot < thr_neg) return true;
|
||||
// IMPROVE: propagate new normal
|
||||
Vec3f n = d1.cross(d2);
|
||||
n.normalize();
|
||||
if(n.dot(normal) < dot_thr) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Vec3d QuadricEdgeCollapse::calculate_3errors(const Triangle & t,
|
||||
const Vertices & vertices,
|
||||
const VertexInfos &v_infos)
|
||||
{
|
||||
Vec3d error;
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
size_t j2 = (j == 2) ? 0 : (j + 1);
|
||||
uint32_t vi0 = t[j];
|
||||
uint32_t vi1 = t[j2];
|
||||
SymMat q(v_infos[vi0].q); // copy
|
||||
q += v_infos[vi1].q;
|
||||
error[j] = calculate_error(vi0, vi1, q, vertices);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
Error QuadricEdgeCollapse::calculate_error(uint32_t ti,
|
||||
const Triangle & t,
|
||||
const Vertices & vertices,
|
||||
const VertexInfos &v_infos,
|
||||
unsigned char & min_index)
|
||||
{
|
||||
Vec3d error = calculate_3errors(t, vertices, v_infos);
|
||||
// select min error
|
||||
min_index = (error[0] < error[1]) ? ((error[0] < error[2]) ? 0 : 2) :
|
||||
((error[1] < error[2]) ? 1 : 2);
|
||||
return Error(static_cast<float>(error[min_index]), ti);
|
||||
}
|
||||
|
||||
void QuadricEdgeCollapse::remove_triangle(EdgeInfos & e_infos,
|
||||
VertexInfo &v_info,
|
||||
uint32_t ti)
|
||||
{
|
||||
auto e_info = e_infos.begin() + v_info.start;
|
||||
auto e_info_end = e_info + v_info.count - 1;
|
||||
for (; e_info != e_info_end; ++e_info) {
|
||||
if (e_info->t_index == ti) {
|
||||
*e_info = *e_info_end;
|
||||
--v_info.count;
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert(e_info_end->t_index == ti);
|
||||
// last triangle is ti
|
||||
--v_info.count;
|
||||
}
|
||||
|
||||
void QuadricEdgeCollapse::change_neighbors(EdgeInfos & e_infos,
|
||||
VertexInfos & v_infos,
|
||||
uint32_t ti0,
|
||||
uint32_t ti1,
|
||||
uint32_t vi0,
|
||||
uint32_t vi1,
|
||||
uint32_t vi_top0,
|
||||
const Triangle &t1,
|
||||
CopyEdgeInfos& infos,
|
||||
EdgeInfos & e_infos1)
|
||||
{
|
||||
// have to copy Edge info from higher vertex index into smaller
|
||||
assert(vi0 < vi1);
|
||||
|
||||
// vertex index of triangle 1 which is not vi0 nor vi1
|
||||
uint32_t vi_top1 = t1[0];
|
||||
if (vi_top1 == vi0 || vi_top1 == vi1) {
|
||||
vi_top1 = t1[1];
|
||||
if (vi_top1 == vi0 || vi_top1 == vi1) vi_top1 = t1[2];
|
||||
}
|
||||
|
||||
remove_triangle(e_infos, v_infos[vi_top0], ti0);
|
||||
remove_triangle(e_infos, v_infos[vi_top1], ti1);
|
||||
|
||||
VertexInfo &v_info0 = v_infos[vi0];
|
||||
VertexInfo &v_info1 = v_infos[vi1];
|
||||
|
||||
uint32_t new_triangle_count = v_info0.count + v_info1.count - 4;
|
||||
remove_triangle(e_infos, v_info0, ti0);
|
||||
remove_triangle(e_infos, v_info0, ti1);
|
||||
|
||||
// copy second's edge infos out of e_infos, to free size
|
||||
e_infos1.clear();
|
||||
e_infos1.reserve(v_info1.count - 2);
|
||||
uint32_t v_info_s_end = v_info1.start + v_info1.count;
|
||||
for (uint32_t ei = v_info1.start; ei < v_info_s_end; ++ei) {
|
||||
const EdgeInfo &e_info = e_infos[ei];
|
||||
if (e_info.t_index == ti0) continue;
|
||||
if (e_info.t_index == ti1) continue;
|
||||
e_infos1.emplace_back(e_info);
|
||||
}
|
||||
v_info1.count = 0;
|
||||
|
||||
uint32_t need = (new_triangle_count < v_info0.count)? 0:
|
||||
(new_triangle_count - v_info0.count);
|
||||
|
||||
uint32_t act_vi = vi0 + 1;
|
||||
VertexInfo *act_v_info = &v_infos[act_vi];
|
||||
uint32_t act_start = act_v_info->start;
|
||||
uint32_t last_end = v_info0.start + v_info0.count;
|
||||
|
||||
infos.clear();
|
||||
infos.reserve(need);
|
||||
|
||||
while (true) {
|
||||
uint32_t save = act_start - last_end;
|
||||
if (save > 0) {
|
||||
if (save >= need) break;
|
||||
need -= save;
|
||||
infos.emplace_back(act_v_info->start, act_v_info->count, need);
|
||||
} else {
|
||||
infos.back().count += act_v_info->count;
|
||||
}
|
||||
last_end = act_v_info->start + act_v_info->count;
|
||||
act_v_info->start += need;
|
||||
++act_vi;
|
||||
if (act_vi < v_infos.size()) {
|
||||
act_v_info = &v_infos[act_vi];
|
||||
act_start = act_v_info->start;
|
||||
} else
|
||||
act_start = e_infos.size(); // fix for edge between last two triangles
|
||||
}
|
||||
|
||||
// copy by c_infos
|
||||
for (uint32_t i = infos.size(); i > 0; --i) {
|
||||
const CopyEdgeInfo &c_info = infos[i - 1];
|
||||
for (uint32_t ei = c_info.start + c_info.count - 1; ei >= c_info.start; --ei)
|
||||
e_infos[ei + c_info.move] = e_infos[ei]; // copy
|
||||
}
|
||||
|
||||
// copy triangle from first info into second
|
||||
for (uint32_t ei_s = 0; ei_s < e_infos1.size(); ++ei_s) {
|
||||
uint32_t ei_f = v_info0.start + v_info0.count;
|
||||
e_infos[ei_f] = e_infos1[ei_s]; // copy
|
||||
++v_info0.count;
|
||||
}
|
||||
}
|
||||
|
||||
void QuadricEdgeCollapse::compact(const VertexInfos & v_infos,
|
||||
const TriangleInfos & t_infos,
|
||||
const EdgeInfos & e_infos,
|
||||
indexed_triangle_set &its)
|
||||
{
|
||||
uint32_t vi_new = 0;
|
||||
for (uint32_t vi = 0; vi < v_infos.size(); ++vi) {
|
||||
const VertexInfo &v_info = v_infos[vi];
|
||||
if (v_info.is_deleted()) continue; // deleted
|
||||
uint32_t e_info_end = v_info.start + v_info.count;
|
||||
for (uint32_t ei = v_info.start; ei < e_info_end; ++ei) {
|
||||
const EdgeInfo &e_info = e_infos[ei];
|
||||
// change vertex index
|
||||
its.indices[e_info.t_index][e_info.edge] = vi_new;
|
||||
}
|
||||
// compact vertices
|
||||
its.vertices[vi_new++] = its.vertices[vi];
|
||||
}
|
||||
// remove vertices tail
|
||||
its.vertices.erase(its.vertices.begin() + vi_new, its.vertices.end());
|
||||
|
||||
uint32_t ti_new = 0;
|
||||
for (uint32_t ti = 0; ti < t_infos.size(); ti++) {
|
||||
const TriangleInfo &t_info = t_infos[ti];
|
||||
if (t_info.is_deleted()) continue;
|
||||
its.indices[ti_new++] = its.indices[ti];
|
||||
}
|
||||
its.indices.erase(its.indices.begin() + ti_new, its.indices.end());
|
||||
}
|
28
src/libslic3r/QuadricEdgeCollapse.hpp
Normal file
28
src/libslic3r/QuadricEdgeCollapse.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
// paper: https://people.eecs.berkeley.edu/~jrs/meshpapers/GarlandHeckbert2.pdf
|
||||
// sum up: https://users.csc.calpoly.edu/~zwood/teaching/csc570/final06/jseeba/
|
||||
// inspiration: https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include "TriangleMesh.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/// <summary>
|
||||
/// Simplify mesh by Quadric metric
|
||||
/// </summary>
|
||||
/// <param name="its">IN/OUT triangle mesh to be simplified.</param>
|
||||
/// <param name="triangle_count">Wanted triangle count.</param>
|
||||
/// <param name="max_error">Maximal Quadric for reduce.
|
||||
/// When nullptr then max float is used
|
||||
/// Output: Last used ErrorValue to collapse edge</param>
|
||||
/// <param name="throw_on_cancel">Could stop process of calculation.</param>
|
||||
/// <param name="statusfn">Give a feed back to user about progress. Values 1 - 100</param>
|
||||
void its_quadric_edge_collapse(
|
||||
indexed_triangle_set & its,
|
||||
uint32_t triangle_count = 0,
|
||||
float * max_error = nullptr,
|
||||
std::function<void(void)> throw_on_cancel = nullptr,
|
||||
std::function<void(int)> statusfn = nullptr);
|
||||
|
||||
} // namespace Slic3r
|
@ -107,7 +107,7 @@ public:
|
||||
// Determinant
|
||||
T det(int a11, int a12, int a13,
|
||||
int a21, int a22, int a23,
|
||||
int a31, int a32, int a33)
|
||||
int a31, int a32, int a33) const
|
||||
{
|
||||
T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] +
|
||||
m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] -
|
||||
@ -121,7 +121,7 @@ public:
|
||||
for (size_t i = 0; i < N; ++i) m[i] += n[i];
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
SymetricMatrix operator+(const SymetricMatrix& n)
|
||||
{
|
||||
SymetricMatrix self = *this;
|
||||
|
@ -57,6 +57,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Gizmos/GLGizmoPainterBase.hpp
|
||||
GUI/Gizmos/GLGizmoSeam.cpp
|
||||
GUI/Gizmos/GLGizmoSeam.hpp
|
||||
GUI/Gizmos/GLGizmoSimplify.cpp
|
||||
GUI/Gizmos/GLGizmoSimplify.hpp
|
||||
GUI/Gizmos/GLGizmoMmuSegmentation.cpp
|
||||
GUI/Gizmos/GLGizmoMmuSegmentation.hpp
|
||||
GUI/GLSelectionRectangle.cpp
|
||||
|
@ -657,6 +657,15 @@ wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu)
|
||||
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix through the Netfabb"), "",
|
||||
[](wxCommandEvent&) { obj_list()->fix_through_netfabb(); }, "", menu,
|
||||
[]() {return plater()->can_fix_through_netfabb(); }, plater());
|
||||
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
wxMenuItem* MenuFactory::append_menu_item_simplify(wxMenu* menu)
|
||||
{
|
||||
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Simplify model"), "",
|
||||
[](wxCommandEvent&) { obj_list()->simplify(); }, "", menu,
|
||||
[]() {return plater()->can_simplify(); }, plater());
|
||||
menu->AppendSeparator();
|
||||
|
||||
return menu_item;
|
||||
@ -874,6 +883,7 @@ void MenuFactory::create_common_object_menu(wxMenu* menu)
|
||||
append_menu_item_scale_selection_to_fit_print_volume(menu);
|
||||
|
||||
append_menu_item_fix_through_netfabb(menu);
|
||||
append_menu_item_simplify(menu);
|
||||
append_menu_items_mirror(menu);
|
||||
}
|
||||
|
||||
@ -923,6 +933,7 @@ void MenuFactory::create_part_menu()
|
||||
append_menu_item_replace_with_stl(menu);
|
||||
append_menu_item_export_stl(menu);
|
||||
append_menu_item_fix_through_netfabb(menu);
|
||||
append_menu_item_simplify(menu);
|
||||
append_menu_items_mirror(menu);
|
||||
|
||||
append_menu_item(menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual parts"),
|
||||
|
@ -92,6 +92,7 @@ private:
|
||||
wxMenuItem* append_menu_item_printable(wxMenu* menu);
|
||||
void append_menu_items_osx(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_simplify(wxMenu* menu);
|
||||
void append_menu_item_export_stl(wxMenu* menu);
|
||||
void append_menu_item_reload_from_disk(wxMenu* menu);
|
||||
void append_menu_item_replace_with_stl(wxMenu* menu);
|
||||
|
@ -3955,6 +3955,12 @@ void ObjectList::fix_through_netfabb()
|
||||
update_item_error_icon(obj_idx, vol_idx);
|
||||
}
|
||||
|
||||
void ObjectList::simplify()
|
||||
{
|
||||
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager();
|
||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
||||
}
|
||||
|
||||
void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const
|
||||
{
|
||||
const wxDataViewItem item = vol_idx <0 ? m_objects_model->GetItemById(obj_idx) :
|
||||
@ -3972,6 +3978,8 @@ void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) co
|
||||
// unmark fixed item only
|
||||
m_objects_model->DeleteWarningIcon(item);
|
||||
}
|
||||
else
|
||||
m_objects_model->AddWarningIcon(item);
|
||||
}
|
||||
|
||||
void ObjectList::msw_rescale()
|
||||
|
@ -357,6 +357,7 @@ public:
|
||||
void split_instances();
|
||||
void rename_item();
|
||||
void fix_through_netfabb();
|
||||
void simplify();
|
||||
void update_item_error_icon(const int obj_idx, int vol_idx) const ;
|
||||
|
||||
void copy_layers_to_clipboard();
|
||||
|
@ -51,6 +51,11 @@ bool GLGizmoMmuSegmentation::on_is_selectable() const
|
||||
&& wxGetApp().get_mode() != comSimple && wxGetApp().extruders_edited_cnt() > 1);
|
||||
}
|
||||
|
||||
bool GLGizmoMmuSegmentation::on_is_activable() const
|
||||
{
|
||||
return GLGizmoPainterBase::on_is_activable() && wxGetApp().extruders_edited_cnt() > 1;
|
||||
}
|
||||
|
||||
static std::vector<std::array<float, 4>> get_extruders_colors()
|
||||
{
|
||||
unsigned char rgb_color[3] = {};
|
||||
|
@ -109,6 +109,7 @@ protected:
|
||||
std::string on_get_name() const override;
|
||||
|
||||
bool on_is_selectable() const override;
|
||||
bool on_is_activable() const override;
|
||||
|
||||
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
||||
|
||||
|
@ -520,7 +520,7 @@ bool GLGizmoPainterBase::on_is_activable() const
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF
|
||||
|| !selection.is_single_full_instance())
|
||||
|| !selection.is_single_full_instance() || wxGetApp().get_mode() == comSimple)
|
||||
return false;
|
||||
|
||||
// Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
|
||||
|
322
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
Normal file
322
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
Normal file
@ -0,0 +1,322 @@
|
||||
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
|
||||
#include "GLGizmoSimplify.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/QuadricEdgeCollapse.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
|
||||
const std::string &icon_filename,
|
||||
unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, -1)
|
||||
, state(State::settings)
|
||||
, is_valid_result(false)
|
||||
, progress(0)
|
||||
, volume(nullptr)
|
||||
, obj_index(0)
|
||||
, need_reload(false)
|
||||
{}
|
||||
|
||||
GLGizmoSimplify::~GLGizmoSimplify() {
|
||||
state = State::canceling;
|
||||
if (worker.joinable()) worker.join();
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::on_init()
|
||||
{
|
||||
//m_grabbers.emplace_back();
|
||||
//m_shortcut_key = WXK_CONTROL_C;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
std::string GLGizmoSimplify::on_get_name() const
|
||||
{
|
||||
return (_L("Simplify")).ToUTF8().data();
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::on_render() {}
|
||||
void GLGizmoSimplify::on_render_for_picking() {}
|
||||
|
||||
void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
const int min_triangle_count = 4; // tetrahedron
|
||||
const int max_char_in_name = 25;
|
||||
create_gui_cfg();
|
||||
|
||||
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoCollapse;
|
||||
m_imgui->begin(on_get_name(), flag);
|
||||
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
int object_idx = selection.get_object_idx();
|
||||
ModelObject *obj = wxGetApp().plater()->model().objects[object_idx];
|
||||
ModelVolume *act_volume = obj->volumes.front();
|
||||
|
||||
// Check selection of new volume
|
||||
// Do not reselect object when processing
|
||||
if (act_volume != volume && state == State::settings) {
|
||||
obj_index = object_idx; // to remember correct object
|
||||
volume = act_volume;
|
||||
original_its = {};
|
||||
const TriangleMesh &tm = volume->mesh();
|
||||
c.wanted_percent = 50.; // default value
|
||||
c.update_percent(tm.its.indices.size());
|
||||
is_valid_result = false;
|
||||
// set window position
|
||||
ImVec2 pos = ImGui::GetMousePos();
|
||||
pos.x -= gui_cfg->window_offset;
|
||||
pos.y -= gui_cfg->window_offset;
|
||||
ImGui::SetWindowPos(pos, ImGuiCond_Always);
|
||||
}
|
||||
|
||||
size_t triangle_count = volume->mesh().its.indices.size();
|
||||
// already reduced mesh
|
||||
if (original_its.has_value())
|
||||
triangle_count = original_its->indices.size();
|
||||
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mesh name") + ":");
|
||||
ImGui::SameLine(gui_cfg->top_left_width);
|
||||
std::string name = volume->name;
|
||||
if (name.length() > max_char_in_name)
|
||||
name = name.substr(0, max_char_in_name-3) + "...";
|
||||
m_imgui->text(name);
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Triangles") + ":");
|
||||
ImGui::SameLine(gui_cfg->top_left_width);
|
||||
m_imgui->text(std::to_string(triangle_count));
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Text(_L("Limit by triangles").c_str());
|
||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
||||
// First initialization + fix triangle count
|
||||
if (m_imgui->checkbox("##UseCount", c.use_count)) {
|
||||
if (!c.use_count) c.use_error = true;
|
||||
is_valid_result = false;
|
||||
}
|
||||
|
||||
m_imgui->disabled_begin(!c.use_count);
|
||||
ImGui::Text(_L("Triangle count").c_str());
|
||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
||||
int wanted_count = c.wanted_count;
|
||||
ImGui::SetNextItemWidth(gui_cfg->input_width);
|
||||
if (ImGui::SliderInt("##triangle_count", &wanted_count, min_triangle_count, triangle_count, "%d")) {
|
||||
c.wanted_count = static_cast<uint32_t>(wanted_count);
|
||||
if (c.wanted_count < min_triangle_count)
|
||||
c.wanted_count = min_triangle_count;
|
||||
if (c.wanted_count > triangle_count)
|
||||
c.wanted_count = triangle_count;
|
||||
c.update_count(triangle_count);
|
||||
is_valid_result = false;
|
||||
}
|
||||
ImGui::Text(_L("Ratio").c_str());
|
||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
||||
ImGui::SetNextItemWidth(gui_cfg->input_small_width);
|
||||
const char * precision = (c.wanted_percent > 10)? "%.0f": ((c.wanted_percent > 1)? "%.1f":"%.2f");
|
||||
float step = (c.wanted_percent > 10)? 1.f: ((c.wanted_percent > 1)? 0.1f : 0.01f);
|
||||
if (ImGui::InputFloat("%", &c.wanted_percent, step, 10*step, precision)) {
|
||||
if (c.wanted_percent > 100.f) c.wanted_percent = 100.f;
|
||||
c.update_percent(triangle_count);
|
||||
if (c.wanted_count < min_triangle_count) {
|
||||
c.wanted_count = min_triangle_count;
|
||||
c.update_count(triangle_count);
|
||||
}
|
||||
is_valid_result = false;
|
||||
}
|
||||
m_imgui->disabled_end(); // use_count
|
||||
|
||||
ImGui::NewLine();
|
||||
ImGui::Text(_L("Limit by error").c_str());
|
||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
||||
if (m_imgui->checkbox("##UseError", c.use_error)) {
|
||||
if (!c.use_error) c.use_count = true;
|
||||
is_valid_result = false;
|
||||
}
|
||||
|
||||
m_imgui->disabled_begin(!c.use_error);
|
||||
ImGui::Text(_L("Max. error").c_str());
|
||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
||||
ImGui::SetNextItemWidth(gui_cfg->input_small_width);
|
||||
if (ImGui::InputFloat("##maxError", &c.max_error, 0.01f, .1f, "%.2f")) {
|
||||
if (c.max_error < 0.f) c.max_error = 0.f;
|
||||
is_valid_result = false;
|
||||
}
|
||||
m_imgui->disabled_end(); // use_error
|
||||
|
||||
if (state == State::settings) {
|
||||
if (m_imgui->button(_L("Cancel"))) {
|
||||
if (original_its.has_value()) {
|
||||
set_its(*original_its);
|
||||
state = State::close_on_end;
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
||||
if (m_imgui->button(_L("Preview"))) {
|
||||
state = State::simplifying;
|
||||
// simplify but not aply on mesh
|
||||
process();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (m_imgui->button(_L("Apply"))) {
|
||||
if (!is_valid_result) {
|
||||
state = State::close_on_end;
|
||||
process();
|
||||
} else {
|
||||
// use preview and close
|
||||
close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_imgui->disabled_begin(state == State::canceling);
|
||||
if (m_imgui->button(_L("Cancel"))) state = State::canceling;
|
||||
m_imgui->disabled_end();
|
||||
|
||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
||||
// draw progress bar
|
||||
char buf[32];
|
||||
sprintf(buf, L("Process %d / 100"), progress);
|
||||
ImGui::ProgressBar(progress / 100., ImVec2(gui_cfg->input_width, 0.f), buf);
|
||||
}
|
||||
m_imgui->end();
|
||||
|
||||
if (need_reload) {
|
||||
need_reload = false;
|
||||
|
||||
// Reload visualization of mesh - change VBO, FBO on GPU
|
||||
m_parent.reload_scene(true); // deactivate gizmo??
|
||||
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
|
||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
||||
|
||||
if (state == State::close_on_end) {
|
||||
// fix hollowing, sla support points, modifiers, ...
|
||||
auto plater = wxGetApp().plater();
|
||||
plater->changed_mesh(obj_index); // deactivate gizmo??
|
||||
// changed_mesh cause close();
|
||||
//close();
|
||||
}
|
||||
|
||||
// change from simplifying | aply
|
||||
state = State::settings;
|
||||
|
||||
// Fix warning icon in object list
|
||||
wxGetApp().obj_list()->update_item_error_icon(obj_index, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::close() {
|
||||
volume = nullptr;
|
||||
|
||||
// close gizmo == open it again
|
||||
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
|
||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
||||
}
|
||||
|
||||
|
||||
void GLGizmoSimplify::process()
|
||||
{
|
||||
class SimplifyCanceledException : public std::exception {
|
||||
public:
|
||||
const char* what() const throw() { return L("Model simplification has been canceled"); }
|
||||
};
|
||||
|
||||
if (!original_its.has_value())
|
||||
original_its = volume->mesh().its; // copy
|
||||
|
||||
auto plater = wxGetApp().plater();
|
||||
plater->take_snapshot(_L("Simplify ") + volume->name);
|
||||
plater->clear_before_change_mesh(obj_index);
|
||||
progress = 0;
|
||||
if (worker.joinable()) worker.join();
|
||||
worker = std::thread([&]() {
|
||||
// store original triangles
|
||||
uint32_t triangle_count = (c.use_count) ? c.wanted_count : 0;
|
||||
float max_error = (c.use_error) ? c.max_error : std::numeric_limits<float>::max();
|
||||
|
||||
std::function<void(void)> throw_on_cancel = [&]() {
|
||||
if (state == State::canceling) {
|
||||
throw SimplifyCanceledException();
|
||||
}
|
||||
};
|
||||
std::function<void(int)> statusfn = [&](int percent) {
|
||||
progress = percent;
|
||||
m_parent.schedule_extra_frame(0);
|
||||
};
|
||||
|
||||
indexed_triangle_set collapsed;
|
||||
if (last_error.has_value()) {
|
||||
// is chance to continue with last reduction
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
uint32_t last_triangle_count = static_cast<uint32_t>(its.indices.size());
|
||||
if ((!c.use_count || triangle_count <= last_triangle_count) &&
|
||||
(!c.use_error || c.max_error <= *last_error)) {
|
||||
collapsed = its; // small copy
|
||||
} else {
|
||||
collapsed = *original_its; // copy
|
||||
}
|
||||
} else {
|
||||
collapsed = *original_its; // copy
|
||||
}
|
||||
|
||||
try {
|
||||
its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn);
|
||||
set_its(collapsed);
|
||||
is_valid_result = true;
|
||||
last_error = max_error;
|
||||
} catch (SimplifyCanceledException &) {
|
||||
state = State::settings;
|
||||
}
|
||||
// need to render last status fn
|
||||
// without sleep it freezes until mouse move
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
m_parent.schedule_extra_frame(0);
|
||||
});
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::set_its(indexed_triangle_set &its) {
|
||||
auto tm = std::make_unique<TriangleMesh>(its);
|
||||
tm->repair();
|
||||
volume->set_mesh(std::move(tm));
|
||||
volume->set_new_unique_id();
|
||||
volume->get_object()->invalidate_bounding_box();
|
||||
need_reload = true;
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::on_is_activable() const
|
||||
{
|
||||
return !m_parent.get_selection().is_empty();
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::create_gui_cfg() {
|
||||
if (gui_cfg.has_value()) return;
|
||||
|
||||
int space_size = m_imgui->calc_text_size(":MM").x;
|
||||
GuiCfg cfg;
|
||||
cfg.top_left_width = std::max(m_imgui->calc_text_size(_L("Mesh name")).x,
|
||||
m_imgui->calc_text_size(_L("Triangles")).x)
|
||||
+ space_size;
|
||||
|
||||
cfg.bottom_left_width =
|
||||
std::max(
|
||||
std::max(m_imgui->calc_text_size(_L("Limit by triangles")).x,
|
||||
std::max(m_imgui->calc_text_size(_L("Triangle count")).x,
|
||||
m_imgui->calc_text_size(_L("Ratio")).x)),
|
||||
std::max(m_imgui->calc_text_size(_L("Limit by error")).x,
|
||||
m_imgui->calc_text_size(_L("Max. error")).x)) + space_size;
|
||||
cfg.input_width = cfg.bottom_left_width;
|
||||
cfg.input_small_width = cfg.input_width * 0.8;
|
||||
cfg.window_offset = cfg.input_width;
|
||||
gui_cfg = cfg;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
89
src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp
Normal file
89
src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp
Normal file
@ -0,0 +1,89 @@
|
||||
#ifndef slic3r_GLGizmoSimplify_hpp_
|
||||
#define slic3r_GLGizmoSimplify_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include <thread>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class GLGizmoSimplify : public GLGizmoBase
|
||||
{
|
||||
enum class State {
|
||||
settings,
|
||||
simplifying, // start processing
|
||||
canceling, // canceled
|
||||
successfull, // successful simplified
|
||||
close_on_end
|
||||
} state;
|
||||
|
||||
bool is_valid_result; // differ what to do in apply
|
||||
int progress;
|
||||
|
||||
ModelVolume *volume;
|
||||
size_t obj_index;
|
||||
std::optional<indexed_triangle_set> original_its;
|
||||
|
||||
std::optional<float> last_error; // for use previous reduction
|
||||
|
||||
bool need_reload; // after simplify, glReload must be on main thread
|
||||
std::thread worker;
|
||||
public:
|
||||
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
virtual ~GLGizmoSimplify();
|
||||
protected:
|
||||
virtual bool on_init() override;
|
||||
virtual std::string on_get_name() const override;
|
||||
virtual void on_render() override;
|
||||
virtual void on_render_for_picking() override;
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
virtual bool on_is_activable() const override;
|
||||
virtual bool on_is_selectable() const override { return false; }
|
||||
|
||||
private:
|
||||
void close();
|
||||
void process();
|
||||
void set_its(indexed_triangle_set &its);
|
||||
struct Configuration
|
||||
{
|
||||
bool use_count = true;
|
||||
// minimal triangle count
|
||||
float wanted_percent = 50.f;
|
||||
uint32_t wanted_count = 0; // initialize by percents
|
||||
|
||||
bool use_error = false;
|
||||
// maximal quadric error
|
||||
float max_error = 1.;
|
||||
|
||||
void update_count(size_t triangle_count)
|
||||
{
|
||||
wanted_percent = (float) wanted_count / triangle_count * 100.f;
|
||||
}
|
||||
void update_percent(size_t triangle_count)
|
||||
{
|
||||
wanted_count = static_cast<uint32_t>(
|
||||
std::round(triangle_count * wanted_percent / 100.f));
|
||||
}
|
||||
} c;
|
||||
|
||||
// This configs holds GUI layout size given by translated texts.
|
||||
// etc. When language changes, GUI is recreated and this class constructed again,
|
||||
// so the change takes effect. (info by GLGizmoFdmSupports.hpp)
|
||||
struct GuiCfg
|
||||
{
|
||||
int top_left_width = 100;
|
||||
int bottom_left_width = 100;
|
||||
int input_width = 100;
|
||||
int input_small_width = 80;
|
||||
int window_offset = 100;
|
||||
};
|
||||
std::optional<GuiCfg> gui_cfg;
|
||||
void create_gui_cfg();
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoSimplify_hpp_
|
@ -20,6 +20,7 @@
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
@ -50,14 +51,7 @@ std::vector<size_t> GLGizmosManager::get_selectable_idxs() const
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<size_t> GLGizmosManager::get_activable_idxs() const
|
||||
{
|
||||
std::vector<size_t> out;
|
||||
for (size_t i=0; i<m_gizmos.size(); ++i)
|
||||
if (m_gizmos[i]->is_activable())
|
||||
out.push_back(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const
|
||||
{
|
||||
@ -110,6 +104,7 @@ bool GLGizmosManager::init()
|
||||
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7));
|
||||
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8));
|
||||
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "fdm_supports.svg", 9));
|
||||
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "cut.svg", 10));
|
||||
|
||||
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
|
||||
|
||||
@ -169,7 +164,7 @@ void GLGizmosManager::refresh_on_off_state()
|
||||
return;
|
||||
|
||||
if (m_current != Undefined
|
||||
&& (! m_gizmos[m_current]->is_activable() || ! m_gizmos[m_current]->is_selectable())) {
|
||||
&& ! m_gizmos[m_current]->is_activable()) {
|
||||
activate_gizmo(Undefined);
|
||||
update_data();
|
||||
}
|
||||
@ -187,7 +182,7 @@ void GLGizmosManager::reset_all_states()
|
||||
bool GLGizmosManager::open_gizmo(EType type)
|
||||
{
|
||||
int idx = int(type);
|
||||
if (m_gizmos[idx]->is_selectable() && m_gizmos[idx]->is_activable()) {
|
||||
if (m_gizmos[idx]->is_activable()) {
|
||||
activate_gizmo(m_current == idx ? Undefined : (EType)idx);
|
||||
update_data();
|
||||
return true;
|
||||
@ -304,7 +299,7 @@ bool GLGizmosManager::handle_shortcut(int key)
|
||||
auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(),
|
||||
[key](const std::unique_ptr<GLGizmoBase>& gizmo) {
|
||||
int gizmo_key = gizmo->get_shortcut_key();
|
||||
return gizmo->is_selectable()
|
||||
return gizmo->is_activable()
|
||||
&& ((gizmo_key == key - 64) || (gizmo_key == key - 96));
|
||||
});
|
||||
|
||||
@ -1077,6 +1072,7 @@ void GLGizmosManager::do_render_overlay() const
|
||||
float u_offset = 1.0f / (float)tex_width;
|
||||
float v_offset = 1.0f / (float)tex_height;
|
||||
|
||||
float current_y = FLT_MAX;
|
||||
for (size_t idx : selectable_idxs)
|
||||
{
|
||||
GLGizmoBase* gizmo = m_gizmos[idx].get();
|
||||
@ -1090,12 +1086,18 @@ void GLGizmosManager::do_render_overlay() const
|
||||
float u_right = u_left + du - u_offset;
|
||||
|
||||
GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } });
|
||||
if (idx == m_current) {
|
||||
float toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height();
|
||||
gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top);
|
||||
if (idx == m_current || current_y == FLT_MAX) {
|
||||
// The FLT_MAX trick is here so that even non-selectable but activable
|
||||
// gizmos are passed some meaningful value.
|
||||
current_y = 0.5f * cnv_h - zoomed_top_y * zoom;
|
||||
}
|
||||
zoomed_top_y -= zoomed_stride_y;
|
||||
}
|
||||
|
||||
if (m_current != Undefined) {
|
||||
float toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height();
|
||||
m_gizmos[m_current]->render_input_window(width, current_y, toolbar_top);
|
||||
}
|
||||
}
|
||||
|
||||
float GLGizmosManager::get_scaled_total_height() const
|
||||
|
@ -69,6 +69,7 @@ public:
|
||||
FdmSupports,
|
||||
Seam,
|
||||
MmuSegmentation,
|
||||
Simplify,
|
||||
Undefined
|
||||
};
|
||||
|
||||
@ -101,7 +102,6 @@ private:
|
||||
std::pair<EType, bool> m_highlight; // bool true = higlightedShown, false = highlightedHidden
|
||||
|
||||
std::vector<size_t> get_selectable_idxs() const;
|
||||
std::vector<size_t> get_activable_idxs() const;
|
||||
size_t get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const;
|
||||
|
||||
void activate_gizmo(EType type);
|
||||
|
@ -1697,6 +1697,24 @@ wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_ty
|
||||
return *bmp;
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item)
|
||||
{
|
||||
if (!item.IsOk())
|
||||
return;
|
||||
ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID());
|
||||
|
||||
if (node->GetType() & itObject) {
|
||||
node->SetBitmap(m_warning_bmp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->GetType() & itVolume) {
|
||||
node->SetBitmap(GetVolumeIcon(node->GetVolumeType(), true));
|
||||
node->GetParent()->SetBitmap(m_warning_bmp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object/* = false*/)
|
||||
{
|
||||
if (!item.IsOk())
|
||||
|
@ -376,6 +376,7 @@ public:
|
||||
|
||||
wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type,
|
||||
const bool is_marked = false);
|
||||
void AddWarningIcon(const wxDataViewItem& item);
|
||||
void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
|
||||
t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const;
|
||||
|
||||
|
@ -1771,6 +1771,7 @@ struct Plater::priv
|
||||
bool can_arrange() const;
|
||||
bool can_layers_editing() const;
|
||||
bool can_fix_through_netfabb() const;
|
||||
bool can_simplify() const;
|
||||
bool can_set_instance_to_object() const;
|
||||
bool can_mirror() const;
|
||||
bool can_reload_from_disk() const;
|
||||
@ -3495,44 +3496,10 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
|
||||
// size_t snapshot_time = undo_redo_stack().active_snapshot_time();
|
||||
Plater::TakeSnapshot snapshot(q, _L("Fix through NetFabb"));
|
||||
|
||||
q->clear_before_change_mesh(obj_idx);
|
||||
ModelObject* mo = model.objects[obj_idx];
|
||||
|
||||
// If there are custom supports/seams/mmu segmentation, remove them. Fixed mesh
|
||||
// may be different and they would make no sense.
|
||||
bool paint_removed = false;
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty();
|
||||
mv->supported_facets.clear();
|
||||
mv->seam_facets.clear();
|
||||
mv->mmu_segmentation_facets.clear();
|
||||
}
|
||||
if (paint_removed) {
|
||||
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.
|
||||
notification_manager->push_notification(
|
||||
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
NotificationManager::NotificationLevel::RegularNotification,
|
||||
_u8L("Custom supports and seams were removed after repairing the mesh."));
|
||||
// _u8L("Undo the repair"),
|
||||
// [this, snapshot_time](wxEvtHandler*){
|
||||
// // Make sure the snapshot is still available and that
|
||||
// // we are in the main stack and not in a gizmo-stack.
|
||||
// if (undo_redo_stack().has_undo_snapshot(snapshot_time)
|
||||
// && q->canvas3D()->get_gizmos_manager().get_current() == nullptr)
|
||||
// undo_redo_to(snapshot_time);
|
||||
// else
|
||||
// notification_manager->push_notification(
|
||||
// NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
// NotificationManager::NotificationLevel::RegularNotification,
|
||||
// _u8L("Cannot undo to before the mesh repair!"));
|
||||
// return true;
|
||||
// });
|
||||
}
|
||||
|
||||
fix_model_by_win10_sdk_gui(*mo, vol_idx);
|
||||
sla::reproject_points_and_holes(mo);
|
||||
this->update();
|
||||
this->object_list_changed();
|
||||
this->schedule_background_process();
|
||||
q->changed_mesh(obj_idx);
|
||||
}
|
||||
|
||||
void Plater::priv::set_current_panel(wxPanel* panel)
|
||||
@ -4304,6 +4271,12 @@ bool Plater::priv::can_fix_through_netfabb() const
|
||||
return model.objects[obj_idx]->get_mesh_errors_count() > 0;
|
||||
}
|
||||
|
||||
|
||||
bool Plater::priv::can_simplify() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Plater::priv::can_increase_instances() const
|
||||
{
|
||||
if (m_ui_jobs.is_any_running()
|
||||
@ -6135,6 +6108,51 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Plater::clear_before_change_mesh(int obj_idx)
|
||||
{
|
||||
ModelObject* mo = model().objects[obj_idx];
|
||||
|
||||
// If there are custom supports/seams/mmu segmentation, remove them. Fixed mesh
|
||||
// may be different and they would make no sense.
|
||||
bool paint_removed = false;
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty();
|
||||
mv->supported_facets.clear();
|
||||
mv->seam_facets.clear();
|
||||
mv->mmu_segmentation_facets.clear();
|
||||
}
|
||||
if (paint_removed) {
|
||||
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.
|
||||
get_notification_manager()->push_notification(
|
||||
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
NotificationManager::NotificationLevel::RegularNotification,
|
||||
_u8L("Custom supports and seams were removed after repairing the mesh."));
|
||||
// _u8L("Undo the repair"),
|
||||
// [this, snapshot_time](wxEvtHandler*){
|
||||
// // Make sure the snapshot is still available and that
|
||||
// // we are in the main stack and not in a gizmo-stack.
|
||||
// if (undo_redo_stack().has_undo_snapshot(snapshot_time)
|
||||
// && q->canvas3D()->get_gizmos_manager().get_current() == nullptr)
|
||||
// undo_redo_to(snapshot_time);
|
||||
// else
|
||||
// notification_manager->push_notification(
|
||||
// NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
// NotificationManager::NotificationLevel::RegularNotification,
|
||||
// _u8L("Cannot undo to before the mesh repair!"));
|
||||
// return true;
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::changed_mesh(int obj_idx)
|
||||
{
|
||||
ModelObject* mo = model().objects[obj_idx];
|
||||
sla::reproject_points_and_holes(mo);
|
||||
update();
|
||||
p->object_list_changed();
|
||||
p->schedule_background_process();
|
||||
}
|
||||
|
||||
void Plater::changed_object(int obj_idx)
|
||||
{
|
||||
if (obj_idx < 0)
|
||||
@ -6402,6 +6420,7 @@ bool Plater::can_increase_instances() const { return p->can_increase_instances()
|
||||
bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); }
|
||||
bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); }
|
||||
bool Plater::can_fix_through_netfabb() const { return p->can_fix_through_netfabb(); }
|
||||
bool Plater::can_simplify() const { return p->can_simplify(); }
|
||||
bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); }
|
||||
bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); }
|
||||
bool Plater::can_arrange() const { return p->can_arrange(); }
|
||||
|
@ -226,6 +226,10 @@ public:
|
||||
void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false);
|
||||
void reslice_SLA_hollowing(const ModelObject &object, bool postpone_error_messages = false);
|
||||
void reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &object, bool postpone_error_messages = false);
|
||||
|
||||
void clear_before_change_mesh(int obj_idx);
|
||||
void changed_mesh(int obj_idx);
|
||||
|
||||
void changed_object(int obj_idx);
|
||||
void changed_objects(const std::vector<size_t>& object_idxs);
|
||||
void schedule_background_process(bool schedule = true);
|
||||
@ -306,6 +310,7 @@ public:
|
||||
bool can_decrease_instances() const;
|
||||
bool can_set_instance_to_object() const;
|
||||
bool can_fix_through_netfabb() const;
|
||||
bool can_simplify() const;
|
||||
bool can_split_to_objects() const;
|
||||
bool can_split_to_volumes() const;
|
||||
bool can_arrange() const;
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("Split empty mesh", "[its_split][its]") {
|
||||
using namespace Slic3r;
|
||||
|
||||
@ -100,3 +102,148 @@ TEST_CASE("Split two watertight meshes", "[its_split][its]") {
|
||||
debug_write_obj(res, "parts_watertight");
|
||||
}
|
||||
|
||||
#include <libslic3r/QuadricEdgeCollapse.hpp>
|
||||
static float triangle_area(const Vec3f &v0, const Vec3f &v1, const Vec3f &v2)
|
||||
{
|
||||
Vec3f ab = v1 - v0;
|
||||
Vec3f ac = v2 - v0;
|
||||
return ab.cross(ac).norm() / 2.f;
|
||||
}
|
||||
|
||||
static float triangle_area(const Vec3crd &triangle_inices, const std::vector<Vec3f> &vertices)
|
||||
{
|
||||
return triangle_area(vertices[triangle_inices[0]],
|
||||
vertices[triangle_inices[1]],
|
||||
vertices[triangle_inices[2]]);
|
||||
}
|
||||
|
||||
static std::mt19937 create_random_generator() {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
return gen;
|
||||
}
|
||||
|
||||
std::vector<Vec3f> its_sample_surface(const indexed_triangle_set &its,
|
||||
double sample_per_mm2,
|
||||
std::mt19937 random_generator = create_random_generator())
|
||||
{
|
||||
std::vector<Vec3f> samples;
|
||||
std::uniform_real_distribution<float> rand01(0.f, 1.f);
|
||||
for (const auto &triangle_indices : its.indices) {
|
||||
float area = triangle_area(triangle_indices, its.vertices);
|
||||
float countf;
|
||||
float fractional = std::modf(area * sample_per_mm2, &countf);
|
||||
int count = static_cast<int>(countf);
|
||||
|
||||
float generate = rand01(random_generator);
|
||||
if (generate < fractional) ++count;
|
||||
if (count == 0) continue;
|
||||
|
||||
const Vec3f &v0 = its.vertices[triangle_indices[0]];
|
||||
const Vec3f &v1 = its.vertices[triangle_indices[1]];
|
||||
const Vec3f &v2 = its.vertices[triangle_indices[2]];
|
||||
for (int c = 0; c < count; c++) {
|
||||
// barycentric coordinate
|
||||
Vec3f b;
|
||||
b[0] = rand01(random_generator);
|
||||
b[1] = rand01(random_generator);
|
||||
if ((b[0] + b[1]) > 1.f) {
|
||||
b[0] = 1.f - b[0];
|
||||
b[1] = 1.f - b[1];
|
||||
}
|
||||
b[2] = 1.f - b[0] - b[1];
|
||||
Vec3f pos;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
pos[i] = b[0] * v0[i] + b[1] * v1[i] + b[2] * v2[i];
|
||||
}
|
||||
samples.push_back(pos);
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||
|
||||
// return Average abs distance to original
|
||||
float compare(const indexed_triangle_set &original,
|
||||
const indexed_triangle_set &simplified,
|
||||
double sample_per_mm2)
|
||||
{
|
||||
// create ABBTree
|
||||
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
original.vertices, original.indices);
|
||||
|
||||
unsigned int init = 0;
|
||||
std::mt19937 rnd(init);
|
||||
auto samples = its_sample_surface(simplified, sample_per_mm2, rnd);
|
||||
|
||||
float sumDistance = 0;
|
||||
for (const Vec3f &sample : samples) {
|
||||
size_t hit_idx;
|
||||
Vec3f hit_point;
|
||||
float distance2 = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
original.vertices, original.indices, tree, sample, hit_idx,
|
||||
hit_point);
|
||||
sumDistance += sqrt(distance2);
|
||||
}
|
||||
return sumDistance / samples.size();
|
||||
}
|
||||
|
||||
TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]")
|
||||
{
|
||||
indexed_triangle_set its;
|
||||
its.vertices = {Vec3f(-1.f, 0.f, 0.f), Vec3f(0.f, 1.f, 0.f),
|
||||
Vec3f(1.f, 0.f, 0.f), Vec3f(0.f, 0.f, 1.f),
|
||||
// vertex to be removed
|
||||
Vec3f(0.9f, .1f, -.1f)};
|
||||
its.indices = {Vec3i(1, 0, 3), Vec3i(2, 1, 3), Vec3i(0, 2, 3),
|
||||
Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(2, 0, 4)};
|
||||
// edge to remove is between vertices 2 and 4 on trinagles 4 and 5
|
||||
|
||||
indexed_triangle_set its_ = its; // copy
|
||||
// its_write_obj(its, "tetrhedron_in.obj");
|
||||
uint32_t wanted_count = its.indices.size() - 1;
|
||||
its_quadric_edge_collapse(its, wanted_count);
|
||||
// its_write_obj(its, "tetrhedron_out.obj");
|
||||
CHECK(its.indices.size() == 4);
|
||||
CHECK(its.vertices.size() == 4);
|
||||
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
CHECK(its.indices[i] == its_.indices[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
if (i == 2) continue;
|
||||
CHECK(its.vertices[i] == its_.vertices[i]);
|
||||
}
|
||||
|
||||
const Vec3f &v = its.vertices[2]; // new vertex
|
||||
const Vec3f &v2 = its_.vertices[2]; // moved vertex
|
||||
const Vec3f &v4 = its_.vertices[4]; // removed vertex
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
bool is_between = (v[i] < v4[i] && v[i] > v2[i]) ||
|
||||
(v[i] > v4[i] && v[i] < v2[i]);
|
||||
CHECK(is_between);
|
||||
}
|
||||
float avg_distance = compare(its_, its, 10);
|
||||
CHECK(avg_distance < 8e-3f);
|
||||
}
|
||||
|
||||
#include "test_utils.hpp"
|
||||
TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]")
|
||||
{
|
||||
TriangleMesh mesh = load_model("frog_legs.obj");
|
||||
double original_volume = its_volume(mesh.its);
|
||||
uint32_t wanted_count = mesh.its.indices.size() * 0.05;
|
||||
REQUIRE_FALSE(mesh.empty());
|
||||
indexed_triangle_set its = mesh.its; // copy
|
||||
float max_error = std::numeric_limits<float>::max();
|
||||
its_quadric_edge_collapse(its, wanted_count, &max_error);
|
||||
//its_write_obj(its, "frog_legs_qec.obj");
|
||||
CHECK(its.indices.size() <= wanted_count);
|
||||
double volume = its_volume(its);
|
||||
CHECK(fabs(original_volume - volume) < 33.);
|
||||
float avg_distance = compare(mesh.its, its, 10);
|
||||
CHECK(avg_distance < 0.022f); // 0.02022 | 0.0199614074
|
||||
}
|
@ -339,3 +339,104 @@ TEST_CASE("Mutable priority queue - reshedule first", "[MutableSkipHeapPriorityQ
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]")
|
||||
{
|
||||
struct MyValue{
|
||||
int id;
|
||||
float val;
|
||||
};
|
||||
size_t count = 50000;
|
||||
std::vector<size_t> idxs(count, {0});
|
||||
std::vector<bool> dels(count, false);
|
||||
auto q = make_miniheap_mutable_priority_queue<MyValue, 16, true>(
|
||||
[&](MyValue &v, size_t idx) {
|
||||
idxs[v.id] = idx;
|
||||
},
|
||||
[](MyValue &l, MyValue &r) { return l.val < r.val; });
|
||||
q.reserve(count);
|
||||
for (size_t id = 0; id < count; id++) {
|
||||
MyValue mv;
|
||||
mv.id = id;
|
||||
mv.val = rand();
|
||||
q.push(mv);
|
||||
}
|
||||
MyValue it = q.top(); // copy
|
||||
q.pop();
|
||||
bool valid = (it.id != 0) && (idxs[0] < 3 * count);
|
||||
CHECK(valid);
|
||||
}
|
||||
|
||||
TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]")
|
||||
{
|
||||
struct MyValue {
|
||||
size_t id;
|
||||
float val;
|
||||
};
|
||||
size_t count = 5000;
|
||||
std::vector<size_t> idxs(count, {0});
|
||||
std::vector<bool> dels(count, false);
|
||||
auto q = make_miniheap_mutable_priority_queue<MyValue, 16, true>(
|
||||
[&](MyValue &v, size_t idx) { idxs[v.id] = idx; },
|
||||
[](MyValue &l, MyValue &r) { return l.val < r.val; });
|
||||
q.reserve(count);
|
||||
|
||||
auto rand_val = [&]()->float { return (rand() % 53) / 10.f; };
|
||||
size_t ord = 0;
|
||||
for (size_t id = 0; id < count; id++) {
|
||||
MyValue mv;
|
||||
mv.id = ord++;
|
||||
mv.val = rand_val();
|
||||
q.push(mv);
|
||||
}
|
||||
auto check = [&]()->bool{
|
||||
for (size_t i = 0; i < idxs.size(); ++i) {
|
||||
if (dels[i]) continue;
|
||||
size_t qid = idxs[i];
|
||||
if (qid > 3*count) {
|
||||
return false;
|
||||
}
|
||||
MyValue &mv = q[qid];
|
||||
if (mv.id != i) {
|
||||
return false; // ERROR
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
CHECK(check()); // initial check
|
||||
|
||||
auto get_valid_id = [&]()->int {
|
||||
int id = 0;
|
||||
do {
|
||||
id = rand() % count;
|
||||
} while (dels[id]);
|
||||
return id;
|
||||
};
|
||||
for (size_t i = 0; i < 100; i++) {
|
||||
MyValue it = q.top(); // copy
|
||||
q.pop();
|
||||
dels[it.id] = true;
|
||||
CHECK(check());
|
||||
if (i % 20 == 0) {
|
||||
it.val = rand_val();
|
||||
q.push(it);
|
||||
dels[it.id] = false;
|
||||
CHECK(check());
|
||||
continue;
|
||||
}
|
||||
|
||||
int id = get_valid_id();
|
||||
q.remove(idxs[id]);
|
||||
dels[id] = true;
|
||||
CHECK(check());
|
||||
for (size_t j = 0; j < 5; j++) {
|
||||
int id = get_valid_id();
|
||||
size_t qid = idxs[id];
|
||||
MyValue &mv = q[qid];
|
||||
mv.val = rand_val();
|
||||
q.update(qid);
|
||||
CHECK(check());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user