From e3cdeda67324a4f2ff4edc8d8d14ecc2e8642749 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Tue, 22 Jun 2021 09:21:16 +0200 Subject: [PATCH 01/29] Add quadric edge collapse --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/QuadricEdgeCollapse.cpp | 620 ++++++++++++++++++ src/libslic3r/QuadricEdgeCollapse.hpp | 17 + src/libslic3r/SimplifyMeshImpl.hpp | 10 +- tests/libslic3r/test_indexed_triangle_set.cpp | 40 ++ 5 files changed, 687 insertions(+), 2 deletions(-) create mode 100644 src/libslic3r/QuadricEdgeCollapse.cpp create mode 100644 src/libslic3r/QuadricEdgeCollapse.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index fe09e6557a..d04c082727 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -177,6 +177,8 @@ add_library(libslic3r STATIC PrintRegion.cpp PNGReadWrite.hpp PNGReadWrite.cpp + QuadricEdgeCollapse.cpp + QuadricEdgeCollapse.hpp Semver.cpp ShortestPath.cpp ShortestPath.hpp diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp new file mode 100644 index 0000000000..1974f51013 --- /dev/null +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -0,0 +1,620 @@ +#include "QuadricEdgeCollapse.hpp" +#include +#include "MutablePriorityQueue.hpp" +#include "SimplifyMeshImpl.hpp" + +using namespace Slic3r; + +// only private namespace not neccessary be in hpp +namespace QuadricEdgeCollapse { + using Vertices = std::vector; + using Triangle = stl_triangle_vertex_indices; + using Indices = std::vector; + using SymMat = SimplifyMesh::implementation::SymetricMatrix; + + struct Error + { + float value; + // range(0 .. 2), + unsigned char min_index; + Error(float value, unsigned char min_index): value(value), min_index(min_index) { + assert(min_index < 3); + } + Error() = default; + }; + using Errors = std::vector; + // merge information together - faster access during processing + struct TriangleInfo + { + Vec3f n; // normalized normal - speed up calcualtion of q and check flip + Error e; // smallest error caused by edges, identify smallest edge in triangle + TriangleInfo() = default; + bool is_deleted() const { return e.min_index > 2; } + void set_deleted() { e.min_index = 3; } + }; + using TriangleInfos = std::vector; + struct VertexInfo + { + SymMat q; // sum quadric of surround triangles + size_t start = 0, count = 0; // vertex neighbor triangles + VertexInfo() = default; + bool is_deleted() const { return count == 0; } + }; + using VertexInfos = std::vector; + struct EdgeInfo + { + size_t t_index=0; // triangle index + unsigned char edge = 0; // 0 or 1 or 2 + EdgeInfo() = default; + }; + using EdgeInfos = std::vector; + + Vec3f create_normal(const Triangle &triangle, const Vertices &vertices); + double calculate_error(size_t id_v1, size_t id_v2, SymMat & q, const Vertices &vertices); + Vec3f calculate_vertex(size_t id_v1, size_t id_v2, SymMat & q, const Vertices &vertices); + // 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 TriangleInfo &t_info, const Vertices &vertices); + std::tuple init(const indexed_triangle_set &its); + size_t find_triangle_index1(size_t vi, const VertexInfo& v_info, size_t ti, const EdgeInfos& e_infos, const Indices& indices); + bool is_flipped(const Vec3f &vn, const Vec3f &v1, const Vec3f &v2, const Vec3f &normal); + bool is_flipped(Vec3f &new_vertex, size_t ti0, size_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 + Error calculate_error(const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos); + // subtract quadric of one triangle from triangle vertex + void sub_quadric(const Triangle &t, const TriangleInfo &t_info, VertexInfos &v_infos, const Vertices &vertices); + + void remove_triangle(EdgeInfos &e_infos, VertexInfo &v_info, size_t ti); + void change_neighbors(EdgeInfos &e_infos, VertexInfos &v_infos, size_t ti0, size_t ti1, + size_t vi0, size_t vi1, size_t vi_top0, const Triangle &t1); + + void compact(const VertexInfos &v_infos, const TriangleInfos &t_infos, const EdgeInfos &e_infos, indexed_triangle_set &its); + } + +using namespace QuadricEdgeCollapse; + +bool check_neighbors(TriangleInfos &t_infos, + Indices& indices, + VertexInfos & v_infos) +{ + std::vector t_counts(v_infos.size(), 0); + for (size_t i = 0; i < indices.size(); i++) { + TriangleInfo &t_info = t_infos[i]; + if (t_info.is_deleted()) continue; + Triangle &t = indices[i]; + for (size_t vidx : t) ++t_counts[vidx]; + } + + size_t prev_end = 0; + for (size_t i = 0; i < v_infos.size(); i++) { + VertexInfo &v_info = v_infos[i]; + if (v_info.is_deleted()) continue; + if (v_info.count != t_counts[i]) { + // no correct count + return false; + } + if (prev_end > v_info.start) { + // overlap of start + return false; + } + prev_end = v_info.start + v_info.count; + } + return true; +} + +bool check_new_vertex(const Vec3f& nv, const Vec3f& v0, const Vec3f& v1) { + float epsilon = 1.f; + for (size_t i = 0; i < 3; i++) { + if (nv[i] > (v0[i] + epsilon) && nv[i] > (v1[i] + epsilon) || + nv[i] < (v0[i] - epsilon) && nv[i] < (v1[i] - epsilon)) { + return false; + } + } + return true; +} + +bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, + size_t triangle_count) +{ + TriangleInfos t_infos; + VertexInfos v_infos; + EdgeInfos e_infos; + std::tie(t_infos, v_infos, e_infos) = init(its); + + static constexpr double max_error = std::numeric_limits::max(); + + auto cmp = [&t_infos](size_t vi0, size_t vi1) -> bool { + const Error &e0 = t_infos[vi0].e; + const Error &e1 = t_infos[vi1].e; + return e0.value < e1.value; + }; + // convert triangle index to priority queue index + std::vector i_convert(its.indices.size(), {0}); + auto setter = [&i_convert](size_t it, size_t index) { i_convert[it] = index; }; + MutablePriorityQueue mpq(std::move(setter), std::move(cmp)); + mpq.reserve(its.indices.size()); + for (size_t i = 0; i < its.indices.size(); i++) mpq.push(i); + + size_t actual_triangle_count = its.indices.size(); + while (actual_triangle_count > triangle_count && !mpq.empty()) { + // triangle index 0 + size_t ti0 = mpq.top(); + mpq.pop(); + TriangleInfo &t_info0 = t_infos[ti0]; + if (t_info0.is_deleted()) continue; + + Error &e = t_info0.e; + + const Triangle &t0 = its.indices[ti0]; + size_t vi0 = t0[e.min_index]; + size_t vi1 = t0[(e.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); + assert(check_new_vertex(new_vertex0, its.vertices[vi0], its.vertices[vi1])); + // set of triangle indices that change quadric + size_t ti1 = (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 (is_flipped(new_vertex0, ti0, ti1, v_info0, t_infos, e_infos, its) || + is_flipped(new_vertex0, ti0, ti1, v_info1, t_infos, e_infos, its)) { + // IMPROVE1: what about other edges in triangle? + // IMPROVE2: check mpq top if it is ti1 with same edge + e.value = max_error; + mpq.push(ti0); + continue; + } + + std::vector changed_triangle_indices; + changed_triangle_indices.reserve(v_info0.count + v_info1.count - 4); + + sub_quadric(t0, t_info0, v_infos, its.vertices); + TriangleInfo &t_info1 = t_infos[ti1]; + const Triangle &t1 = its.indices[ti1]; + sub_quadric(t1, t_info1, v_infos, its.vertices); + // for each vertex0 triangles + size_t v_info0_end = v_info0.start + v_info0.count; + for (size_t di = v_info0.start; di < v_info0_end; ++di) { + assert(di < e_infos.size()); + EdgeInfo &e_info = e_infos[di]; + size_t ti = e_info.t_index; + if (ti == ti0) continue; // ti0 will be deleted + if (ti == ti1) continue; // ti1 will be deleted + sub_quadric(its.indices[ti], t_infos[ti], v_infos, its.vertices); + changed_triangle_indices.emplace_back(ti); + } + + // for each vertex1 triangles + size_t v_info1_end = v_info1.start + v_info1.count; + for (size_t di = v_info1.start; di < v_info1_end; ++di) { + assert(di < e_infos.size()); + EdgeInfo &e_info = e_infos[di]; + size_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]; + sub_quadric(t, t_infos[ti], v_infos, its.vertices); + t[e_info.edge] = vi0; // change index + changed_triangle_indices.emplace_back(ti); + } + + // fix neighbors + + // vertex index of triangle 0 which is not vi0 nor vi1 + size_t vi_top0 = t0[(e.min_index + 2) % 3]; + change_neighbors(e_infos, v_infos, ti0, ti1, vi0, vi1, vi_top0, t1); + + // Change vertex + // Has to be set after subtract quadric + its.vertices[vi0] = new_vertex0; + + // add new quadrics + v_info0.q = SymMat(); // zero value + for (size_t ti : changed_triangle_indices) { + const Triangle& t = its.indices[ti]; + TriangleInfo &t_info = t_infos[ti]; + t_info.n = create_normal(t, its.vertices); // new normal + SymMat q = create_quadric(t, t_info, its.vertices); + for (const size_t vi: t) v_infos[vi].q += q; + } + + // fix errors - must be after calculate all quadric + t_info1.e.value = max_error; // not neccessary when check deleted triangles at begining + //mpq.remove(i_convert[ti1]); + for (size_t ti : changed_triangle_indices) { + const Triangle &t = its.indices[ti]; + t_infos[ti].e = calculate_error(t, its.vertices, v_infos); + mpq.update(i_convert[ti]); + } + + // set triangle(0 + 1) indices as deleted + t_info0.set_deleted(); + t_info1.set_deleted(); + // triangle counter decrementation + actual_triangle_count-=2; + + //assert(check_neighbors(t_infos, its.indices, v_infos)); + } + + // compact triangle + compact(v_infos, t_infos, e_infos, its); + return true; +} + +Vec3f QuadricEdgeCollapse::create_normal(const Triangle & triangle, + const Vertices &vertices) +{ + const Vec3f &v0 = vertices[triangle[0]]; + const Vec3f &v1 = vertices[triangle[1]]; + const Vec3f &v2 = vertices[triangle[2]]; + // n = triangle normal + Vec3f n = (v1 - v0).cross(v2 - v0); + n.normalize(); + return n; +} + +double QuadricEdgeCollapse::calculate_error(size_t id_v1, + size_t id_v2, + SymMat & q, + const Vertices &vertices) +{ + double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7); + if (abs(det) < std::numeric_limits::epsilon()) { + // can't divide by zero + const Vec3f &v0 = vertices[id_v1]; + const Vec3f &v1 = vertices[id_v2]; + Vec3d verts[3] = {v0.cast(), v1.cast(), Vec3d()}; + verts[2] = (verts[0] + verts[1]) / 2; + double errors[] = {vertex_error(q, verts[0]), + vertex_error(q, verts[1]), + vertex_error(q, verts[2])}; + return *std::min_element(std::begin(errors), std::end(errors)); + } + + 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) + Vec3d vertex(det_1 * det_x, -det_1 * det_y, det_1 * det_z); + return vertex_error(q, vertex); +} + +// similar as calculate error but focus on new vertex without calculation of error +Vec3f QuadricEdgeCollapse::calculate_vertex(size_t id_v1, + size_t id_v2, + SymMat & q, + const Vertices &vertices) +{ + double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7); + if (abs(det) < std::numeric_limits::epsilon()) { + // can't divide by zero + const Vec3f &v0 = vertices[id_v1]; + const Vec3f &v1 = vertices[id_v2]; + Vec3d verts[3] = {v0.cast(), v1.cast(), Vec3d()}; + verts[2] = (verts[0] + verts[1]) / 2; + double errors[] = {vertex_error(q, verts[0]), + vertex_error(q, verts[1]), + vertex_error(q, verts[2])}; + auto mit = std::min_element(std::begin(errors), std::end(errors)); + return verts[mit - std::begin(errors)].cast(); + } + + 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 Vec3f(det_1 * det_x, -det_1 * det_y, det_1 * det_z); +} + +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 TriangleInfo &t_info, + const Vertices & vertices) +{ + Vec3d n = t_info.n.cast(); + Vec3d v0 = vertices[t[0]].cast(); + return SymMat(n.x(), n.y(), n.z(), -n.dot(v0)); +} + +std::tuple QuadricEdgeCollapse::init( + const indexed_triangle_set &its) +{ + TriangleInfos t_infos(its.indices.size()); + VertexInfos v_infos(its.vertices.size()); + EdgeInfos e_infos(its.indices.size() * 3); + + for (size_t i = 0; i < its.indices.size(); i++) { + const Triangle &t = its.indices[i]; + TriangleInfo &t_info = t_infos[i]; + t_info.n = create_normal(t, its.vertices); + SymMat q = create_quadric(t, t_info, its.vertices); + for (size_t e = 0; e < 3; e++) { + VertexInfo &v_info = v_infos[t[e]]; + v_info.q += q; + ++v_info.count; // triangle count + } + } + + // set offseted starts + size_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 + create reference + for (size_t i = 0; i < its.indices.size(); i++) { + const Triangle &t = its.indices[i]; + TriangleInfo &t_info = t_infos[i]; + t_info.e = calculate_error(t, its.vertices, v_infos); + 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}; +} + +size_t QuadricEdgeCollapse::find_triangle_index1(size_t vi, + const VertexInfo &v_info, + size_t ti0, + const EdgeInfos & e_infos, + const Indices &indices) +{ + size_t end = v_info.start + v_info.count; + for (size_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 || + t[(e_info.edge + 2) % 3] == vi) + return e_info.t_index; + } + // triangle0 is on border and do NOT have twin edge + assert(false); + return -1; +} + + +bool QuadricEdgeCollapse::is_flipped(const Vec3f &vn, + const Vec3f &v1, + const Vec3f &v2, + const Vec3f &normal) +{ + static const float thr_pos = 1.0f - std::numeric_limits::epsilon(); + static const float thr_neg = -thr_pos; + static const float dot_thr = 0.2f; // Value from simplify mesh + + Vec3f d1 = v1 - vn; + d1.normalize(); + Vec3f d2 = v2 - vn; + 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(); + return n.dot(normal) < dot_thr; +} + + +bool QuadricEdgeCollapse::is_flipped(Vec3f & new_vertex, + size_t ti0, + size_t ti1, + const VertexInfo & v_info, + const TriangleInfos &t_infos, + const EdgeInfos & e_infos, + const indexed_triangle_set &its) +{ + // 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]]; + if (is_flipped(new_vertex, vf, vs, normal)) + return true; + } + return false; +} + +Error QuadricEdgeCollapse::calculate_error(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); + size_t vi0 = t[j]; + size_t vi1 = t[j2]; + SymMat q(v_infos[vi0].q); // copy + q += v_infos[vi1].q; + error[j] = calculate_error(vi0, vi1, q, vertices); + } + unsigned char min_index = (error[0] < error[1]) ? + ((error[0] < error[2]) ? 0 : 2) : + ((error[1] < error[2]) ? 1 : 2); + return Error(static_cast(error[min_index]), min_index); +} + +void QuadricEdgeCollapse::sub_quadric(const Triangle &t, + const TriangleInfo & t_info, + VertexInfos &v_infos, + const Vertices &vertices) +{ + SymMat quadric = create_quadric(t, t_info, vertices); + for (size_t vi: t) v_infos[vi].q -= quadric; +} + +void QuadricEdgeCollapse::remove_triangle(EdgeInfos & e_infos, + VertexInfo &v_info, + size_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, + size_t ti0, + size_t ti1, + size_t vi0, + size_t vi1, + size_t vi_top0, + const Triangle &t1) +{ + // 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 + size_t vi_top1 = t1[0]; + if (vi_top1 == vi0 || vi_top1 == vi1) { + vi_top1 = (t1[1] == vi0 || t1[1] == vi1)? t1[2] : t1[1]; + } + + 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]; + + size_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 + EdgeInfos e_infos1; + e_infos1.reserve(v_info1.count - 2); + size_t v_info_s_end = v_info1.start + v_info1.count; + for (size_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; + + size_t need = (new_triangle_count < v_info0.count)? 0: + (new_triangle_count - v_info0.count); + + size_t act_vi = vi0 + 1; + VertexInfo *act_v_info = &v_infos[act_vi]; + size_t act_start = act_v_info->start; + size_t last_end = v_info0.start + v_info0.count; + + struct CopyEdgeInfo + { + size_t start; + size_t count; + unsigned char move; + CopyEdgeInfo(size_t start, size_t count, unsigned char move) + : start(start), count(count), move(move) + {} + }; + std::vector c_infos; + c_infos.reserve(need); + while (true) { + size_t save = act_start - last_end; + if (save > 0) { + if (save >= need) break; + need -= save; + c_infos.emplace_back(act_v_info->start, act_v_info->count, need); + } else { + c_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 (size_t i = c_infos.size(); i > 0; --i) { + const CopyEdgeInfo &c_info = c_infos[i - 1]; + for (size_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 (size_t ei_s = 0; ei_s < e_infos1.size(); ++ei_s) { + size_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) +{ + size_t vi_new = 0; + for (size_t vi = 0; vi < v_infos.size(); vi++) { + const VertexInfo &v_info = v_infos[vi]; + if (v_info.is_deleted()) continue; // deleted + size_t e_info_end = v_info.start + v_info.count; + for (size_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()); + + size_t ti_new = 0; + for (size_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()); +} \ No newline at end of file diff --git a/src/libslic3r/QuadricEdgeCollapse.hpp b/src/libslic3r/QuadricEdgeCollapse.hpp new file mode 100644 index 0000000000..d435656b20 --- /dev/null +++ b/src/libslic3r/QuadricEdgeCollapse.hpp @@ -0,0 +1,17 @@ +// 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 "TriangleMesh.hpp" + +namespace Slic3r { + +/// +/// Simplify mesh by Quadric metric +/// +/// IN/OUT triangle mesh to be simplified. +/// wanted triangle count. +/// TRUE on success otherwise FALSE +bool its_quadric_edge_collapse(indexed_triangle_set &its, size_t triangle_count); + +} // namespace Slic3r diff --git a/src/libslic3r/SimplifyMeshImpl.hpp b/src/libslic3r/SimplifyMeshImpl.hpp index 4b6b0f5cbc..0b3b9a2f64 100644 --- a/src/libslic3r/SimplifyMeshImpl.hpp +++ b/src/libslic3r/SimplifyMeshImpl.hpp @@ -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,13 @@ public: for (size_t i = 0; i < N; ++i) m[i] += n[i]; return *this; } - + + const SymetricMatrix &operator-=(const SymetricMatrix &n) + { + for (size_t i = 0; i < N; ++i) m[i] -= n[i]; + return *this; + } + SymetricMatrix operator+(const SymetricMatrix& n) { SymetricMatrix self = *this; diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index 4c11283451..0ec7e3bd05 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -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,41 @@ TEST_CASE("Split two watertight meshes", "[its_split][its]") { debug_write_obj(res, "parts_watertight"); } +#include +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"); + size_t wanted_count = its.indices.size() - 1; + CHECK(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); + } +} \ No newline at end of file From 7c13cfa1d9f540c0505a9d1d835f21736f0e48be Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Tue, 22 Jun 2021 09:41:38 +0200 Subject: [PATCH 02/29] Add test checking diference in volume before/after simplification FIX warnings ..\src\libslic3r\QuadricEdgeCollapse.cpp(173): warning C4056: overflow in floating-point constant arithmetic ..\src\libslic3r\QuadricEdgeCollapse.cpp(232): warning C4056: overflow in floating-point constant arithmetic --- src/libslic3r/QuadricEdgeCollapse.cpp | 2 +- tests/libslic3r/test_indexed_triangle_set.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index 1974f51013..13313da1f6 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -123,7 +123,7 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, EdgeInfos e_infos; std::tie(t_infos, v_infos, e_infos) = init(its); - static constexpr double max_error = std::numeric_limits::max(); + static constexpr float max_error = std::numeric_limits::max(); auto cmp = [&t_infos](size_t vi0, size_t vi1) -> bool { const Error &e0 = t_infos[vi0].e; diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index 0ec7e3bd05..ac676267ac 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -139,4 +139,19 @@ TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]") (v[i] > v4[i] && v[i] < v2[i]); CHECK(is_between); } +} + +#include "test_utils.hpp" +TEST_CASE("Symplify mesh by Quadric edge collapse to 5%", "[its]") +{ + TriangleMesh mesh = load_model("frog_legs.obj"); + double original_volume = its_volume(mesh.its); + size_t wanted_count = mesh.its.indices.size() * 0.05; + REQUIRE_FALSE(mesh.empty()); + indexed_triangle_set its = mesh.its; // copy + its_quadric_edge_collapse(its, wanted_count); + // its_write_obj(its, "frog_legs_qec.obj"); + CHECK(its.indices.size() <= wanted_count); + double volume = its_volume(its); + CHECK(fabs(original_volume - volume) < 30.); } \ No newline at end of file From 892c246700457160f1c67c9ef74aa83d2ea0ef29 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Tue, 22 Jun 2021 10:01:14 +0200 Subject: [PATCH 03/29] Fix Linux warnings ../src/libslic3r/QuadricEdgeCollapse.cpp:110:39: warning: suggest parentheses around '&&' within '||' [-Wparentheses] ../src/libslic3r/QuadricEdgeCollapse.cpp:394:38: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] ../src/libslic3r/QuadricEdgeCollapse.cpp:395:38: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] ../src/libslic3r/QuadricEdgeCollapse.cpp:514:26: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] ../src/libslic3r/QuadricEdgeCollapse.cpp:514:42: warning: comparison between signed and unsigned integer expressions [-Wsign-compare] --- src/libslic3r/QuadricEdgeCollapse.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index 13313da1f6..858a6fbbb8 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -107,8 +107,8 @@ bool check_neighbors(TriangleInfos &t_infos, bool check_new_vertex(const Vec3f& nv, const Vec3f& v0, const Vec3f& v1) { float epsilon = 1.f; for (size_t i = 0; i < 3; i++) { - if (nv[i] > (v0[i] + epsilon) && nv[i] > (v1[i] + epsilon) || - nv[i] < (v0[i] - epsilon) && nv[i] < (v1[i] - epsilon)) { + if ((nv[i] > (v0[i] + epsilon) && nv[i] > (v1[i] + epsilon)) || + (nv[i] < (v0[i] - epsilon) && nv[i] < (v1[i] - epsilon))) { return false; } } @@ -386,13 +386,14 @@ size_t QuadricEdgeCollapse::find_triangle_index1(size_t vi, const EdgeInfos & e_infos, const Indices &indices) { + coord_t vi_coord = static_cast(vi); size_t end = v_info.start + v_info.count; for (size_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 || - t[(e_info.edge + 2) % 3] == vi) + 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 @@ -508,10 +509,12 @@ void QuadricEdgeCollapse::change_neighbors(EdgeInfos & e_infos, // 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 size_t vi_top1 = t1[0]; if (vi_top1 == vi0 || vi_top1 == vi1) { - vi_top1 = (t1[1] == vi0 || t1[1] == vi1)? t1[2] : t1[1]; + vi_top1 = t1[1]; + if (vi_top1 == vi0 || vi_top1 == vi1) vi_top1 = t1[2]; } remove_triangle(e_infos, v_infos[vi_top0], ti0); From 1196fac55142c73ebd8f6464bc22e2b8aac66614 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Wed, 23 Jun 2021 09:50:27 +0200 Subject: [PATCH 04/29] Parallel QEC initialization --- src/libslic3r/QuadricEdgeCollapse.cpp | 102 ++++++++++++++------------ 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index 858a6fbbb8..7244b6c1aa 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -54,17 +54,16 @@ namespace QuadricEdgeCollapse { Vec3f calculate_vertex(size_t id_v1, size_t id_v2, SymMat & q, const Vertices &vertices); // 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 TriangleInfo &t_info, const Vertices &vertices); + SymMat create_quadric(const Triangle &t, const Vec3f& normal, const Vertices &vertices); std::tuple init(const indexed_triangle_set &its); size_t find_triangle_index1(size_t vi, const VertexInfo& v_info, size_t ti, const EdgeInfos& e_infos, const Indices& indices); - bool is_flipped(const Vec3f &vn, const Vec3f &v1, const Vec3f &v2, const Vec3f &normal); - bool is_flipped(Vec3f &new_vertex, size_t ti0, size_t ti1, const VertexInfo& v_info, + bool is_flipped(const Vec3f &new_vertex, size_t ti0, size_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 Error calculate_error(const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos); // subtract quadric of one triangle from triangle vertex - void sub_quadric(const Triangle &t, const TriangleInfo &t_info, VertexInfos &v_infos, const Vertices &vertices); + void sub_quadric(const Triangle &t, const Vec3f& normal, VertexInfos &v_infos, const Vertices &vertices); void remove_triangle(EdgeInfos &e_infos, VertexInfo &v_info, size_t ti); void change_neighbors(EdgeInfos &e_infos, VertexInfos &v_infos, size_t ti0, size_t ti1, @@ -178,10 +177,10 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, std::vector changed_triangle_indices; changed_triangle_indices.reserve(v_info0.count + v_info1.count - 4); - sub_quadric(t0, t_info0, v_infos, its.vertices); + sub_quadric(t0, t_info0.n, v_infos, its.vertices); TriangleInfo &t_info1 = t_infos[ti1]; const Triangle &t1 = its.indices[ti1]; - sub_quadric(t1, t_info1, v_infos, its.vertices); + sub_quadric(t1, t_info1.n, v_infos, its.vertices); // for each vertex0 triangles size_t v_info0_end = v_info0.start + v_info0.count; for (size_t di = v_info0.start; di < v_info0_end; ++di) { @@ -190,7 +189,7 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, size_t ti = e_info.t_index; if (ti == ti0) continue; // ti0 will be deleted if (ti == ti1) continue; // ti1 will be deleted - sub_quadric(its.indices[ti], t_infos[ti], v_infos, its.vertices); + sub_quadric(its.indices[ti], t_infos[ti].n, v_infos, its.vertices); changed_triangle_indices.emplace_back(ti); } @@ -203,7 +202,7 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, if (ti == ti0) continue; // ti0 will be deleted if (ti == ti1) continue; // ti1 will be deleted Triangle &t = its.indices[ti]; - sub_quadric(t, t_infos[ti], v_infos, its.vertices); + sub_quadric(t, t_infos[ti].n, v_infos, its.vertices); t[e_info.edge] = vi0; // change index changed_triangle_indices.emplace_back(ti); } @@ -224,7 +223,7 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, const Triangle& t = its.indices[ti]; TriangleInfo &t_info = t_infos[ti]; t_info.n = create_normal(t, its.vertices); // new normal - SymMat q = create_quadric(t, t_info, its.vertices); + SymMat q = create_quadric(t, t_info.n, its.vertices); for (const size_t vi: t) v_infos[vi].q += q; } @@ -325,10 +324,10 @@ double QuadricEdgeCollapse::vertex_error(const SymMat &q, const Vec3d &vertex) } SymMat QuadricEdgeCollapse::create_quadric(const Triangle & t, - const TriangleInfo &t_info, + const Vec3f &normal, const Vertices & vertices) { - Vec3d n = t_info.n.cast(); + Vec3d n = normal.cast(); Vec3d v0 = vertices[t[0]].cast(); return SymMat(n.x(), n.y(), n.z(), -n.dot(v0)); } @@ -339,12 +338,22 @@ std::tuple QuadricEdgeCollapse::init( TriangleInfos t_infos(its.indices.size()); VertexInfos v_infos(its.vertices.size()); EdgeInfos e_infos(its.indices.size() * 3); + + // calculate normals + tbb::parallel_for(tbb::blocked_range(0, its.indices.size()), + [&](const tbb::blocked_range &range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + const Triangle &t = its.indices[i]; + TriangleInfo & t_info = t_infos[i]; + t_info.n = create_normal(t, its.vertices); + } + }); // END parallel for + // sum quadrics for (size_t i = 0; i < its.indices.size(); i++) { const Triangle &t = its.indices[i]; TriangleInfo &t_info = t_infos[i]; - t_info.n = create_normal(t, its.vertices); - SymMat q = create_quadric(t, t_info, its.vertices); + SymMat q = create_quadric(t, t_info.n, its.vertices); for (size_t e = 0; e < 3; e++) { VertexInfo &v_info = v_infos[t[e]]; v_info.q += q; @@ -361,12 +370,20 @@ std::tuple QuadricEdgeCollapse::init( v_info.count = 0; } assert(its.indices.size() * 3 == triangle_start); + + // calc error + tbb::parallel_for(tbb::blocked_range(0, its.indices.size()), + [&](const tbb::blocked_range &range) { + for (size_t i = range.begin(); i < range.end(); ++i) { + const Triangle &t = its.indices[i]; + TriangleInfo & t_info = t_infos[i]; + t_info.e = calculate_error(t, its.vertices, v_infos); + } + }); // END parallel for - // calc error + create reference + // create reference for (size_t i = 0; i < its.indices.size(); i++) { - const Triangle &t = its.indices[i]; - TriangleInfo &t_info = t_infos[i]; - t_info.e = calculate_error(t, its.vertices, v_infos); + 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; @@ -401,32 +418,7 @@ size_t QuadricEdgeCollapse::find_triangle_index1(size_t vi, return -1; } - -bool QuadricEdgeCollapse::is_flipped(const Vec3f &vn, - const Vec3f &v1, - const Vec3f &v2, - const Vec3f &normal) -{ - static const float thr_pos = 1.0f - std::numeric_limits::epsilon(); - static const float thr_neg = -thr_pos; - static const float dot_thr = 0.2f; // Value from simplify mesh - - Vec3f d1 = v1 - vn; - d1.normalize(); - Vec3f d2 = v2 - vn; - 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(); - return n.dot(normal) < dot_thr; -} - - -bool QuadricEdgeCollapse::is_flipped(Vec3f & new_vertex, +bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex, size_t ti0, size_t ti1, const VertexInfo & v_info, @@ -434,6 +426,10 @@ bool QuadricEdgeCollapse::is_flipped(Vec3f & new_vertex, const EdgeInfos & e_infos, const indexed_triangle_set &its) { + static const float thr_pos = 1.0f - std::numeric_limits::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) { @@ -445,8 +441,18 @@ bool QuadricEdgeCollapse::is_flipped(Vec3f & new_vertex, 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]]; - if (is_flipped(new_vertex, vf, vs, normal)) - return true; + + 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; } @@ -471,12 +477,12 @@ Error QuadricEdgeCollapse::calculate_error(const Triangle & t, } void QuadricEdgeCollapse::sub_quadric(const Triangle &t, - const TriangleInfo & t_info, + const Vec3f& normal, VertexInfos &v_infos, const Vertices &vertices) { - SymMat quadric = create_quadric(t, t_info, vertices); - for (size_t vi: t) v_infos[vi].q -= quadric; + SymMat quadric = create_quadric(t, normal, vertices); + for (auto vi: t) v_infos[vi].q -= quadric; } void QuadricEdgeCollapse::remove_triangle(EdgeInfos & e_infos, From f8d759ad6432c76f21c4daff57415cf244e11b71 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Wed, 23 Jun 2021 11:42:24 +0200 Subject: [PATCH 05/29] Assert check of new vertex can't be used general on All models(depends on scale) Fix variable name in compare function Remove second triangle from MPQ --- src/libslic3r/QuadricEdgeCollapse.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index 7244b6c1aa..c59fa09b6f 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -124,9 +124,9 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, static constexpr float max_error = std::numeric_limits::max(); - auto cmp = [&t_infos](size_t vi0, size_t vi1) -> bool { - const Error &e0 = t_infos[vi0].e; - const Error &e1 = t_infos[vi1].e; + auto cmp = [&t_infos](size_t ti0, size_t ti1) -> bool { + const Error &e0 = t_infos[ti0].e; + const Error &e1 = t_infos[ti1].e; return e0.value < e1.value; }; // convert triangle index to priority queue index @@ -143,9 +143,9 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, mpq.pop(); TriangleInfo &t_info0 = t_infos[ti0]; if (t_info0.is_deleted()) continue; - Error &e = t_info0.e; - + if (e.value >= max_error) return false; // only flipped triangles + const Triangle &t0 = its.indices[ti0]; size_t vi0 = t0[e.min_index]; size_t vi1 = t0[(e.min_index+1) %3]; @@ -159,7 +159,7 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, SymMat q(v_info0.q); q += v_info1.q; Vec3f new_vertex0 = calculate_vertex(vi0, vi1, q, its.vertices); - assert(check_new_vertex(new_vertex0, its.vertices[vi0], its.vertices[vi1])); + //assert(check_new_vertex(new_vertex0, its.vertices[vi0], its.vertices[vi1])); // set of triangle indices that change quadric size_t ti1 = (v_info0.count < v_info1.count)? find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) : @@ -170,6 +170,7 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, // IMPROVE1: what about other edges in triangle? // IMPROVE2: check mpq top if it is ti1 with same edge e.value = max_error; + // error is changed when surround edge is reduced mpq.push(ti0); continue; } @@ -185,8 +186,7 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, size_t v_info0_end = v_info0.start + v_info0.count; for (size_t di = v_info0.start; di < v_info0_end; ++di) { assert(di < e_infos.size()); - EdgeInfo &e_info = e_infos[di]; - size_t ti = e_info.t_index; + size_t ti = e_infos[di].t_index; if (ti == ti0) continue; // ti0 will be deleted if (ti == ti1) continue; // ti1 will be deleted sub_quadric(its.indices[ti], t_infos[ti].n, v_infos, its.vertices); @@ -228,8 +228,7 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, } // fix errors - must be after calculate all quadric - t_info1.e.value = max_error; // not neccessary when check deleted triangles at begining - //mpq.remove(i_convert[ti1]); + mpq.remove(i_convert[ti1]); for (size_t ti : changed_triangle_indices) { const Triangle &t = its.indices[ti]; t_infos[ti].e = calculate_error(t, its.vertices, v_infos); From 35a906139ebf900ada4ea5aeb1e59d7c8a7082f6 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Thu, 8 Jul 2021 08:33:06 +0200 Subject: [PATCH 06/29] Extend test with checking simplified model distance to original model --- tests/libslic3r/test_indexed_triangle_set.cpp | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index ac676267ac..094c17fd6c 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -103,6 +103,93 @@ TEST_CASE("Split two watertight meshes", "[its_split][its]") { } #include +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 &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 its_sample_surface(const indexed_triangle_set &its, + double sample_per_mm2, + std::mt19937 &random_generator = create_random_generator()) +{ + std::vector samples; + std::uniform_real_distribution 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(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; @@ -139,6 +226,8 @@ TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]") (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" @@ -154,4 +243,7 @@ TEST_CASE("Symplify mesh by Quadric edge collapse to 5%", "[its]") CHECK(its.indices.size() <= wanted_count); double volume = its_volume(its); CHECK(fabs(original_volume - volume) < 30.); + + float avg_distance = compare(mesh.its, its, 10); + CHECK(avg_distance < 0.021f); // 0.02022 | 0.0199614074 } \ No newline at end of file From c00dca7810e7eec388af1c76525cea17d9a76d30 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Thu, 8 Jul 2021 16:17:36 +0200 Subject: [PATCH 07/29] Speed up QEC by move error directly into MutablePriorityQueue Memory optimization: Change size_t to 32bit int Lightweight Error structure for faster PriorityQueue --- src/libslic3r/QuadricEdgeCollapse.cpp | 343 +++++++++++++------------- src/libslic3r/QuadricEdgeCollapse.hpp | 12 +- 2 files changed, 179 insertions(+), 176 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index c59fa09b6f..da409ff58d 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -12,73 +12,83 @@ namespace QuadricEdgeCollapse { using Indices = std::vector; using SymMat = SimplifyMesh::implementation::SymetricMatrix; + // smallest error caused by edges, identify smallest edge in triangle struct Error { - float value; + float value = -1.; // range(0 .. 2), - unsigned char min_index; - Error(float value, unsigned char min_index): value(value), min_index(min_index) { + uint32_t triangle_index = 0; + Error(float value, uint32_t triangle_index) + : value(value) + , triangle_index(triangle_index) + { assert(min_index < 3); } Error() = default; }; using Errors = std::vector; + // merge information together - faster access during processing - struct TriangleInfo - { + struct TriangleInfo { Vec3f n; // normalized normal - speed up calcualtion of q and check flip - Error e; // smallest error caused by edges, identify smallest edge in triangle + unsigned char min_index = 0; // identify edge for minimal Error -> lightweight Error structure TriangleInfo() = default; - bool is_deleted() const { return e.min_index > 2; } - void set_deleted() { e.min_index = 3; } + bool is_deleted() const { return n.x() > 2.f; } + void set_deleted() { n.x() = 3.f; } }; using TriangleInfos = std::vector; - struct VertexInfo - { + struct VertexInfo { SymMat q; // sum quadric of surround triangles - size_t start = 0, count = 0; // vertex neighbor triangles + uint32_t start = 0, count = 0; // vertex neighbor triangles VertexInfo() = default; bool is_deleted() const { return count == 0; } }; using VertexInfos = std::vector; - struct EdgeInfo - { - size_t t_index=0; // triangle index + struct EdgeInfo { + uint32_t t_index=0; // triangle index unsigned char edge = 0; // 0 or 1 or 2 EdgeInfo() = default; }; using EdgeInfos = std::vector; + // 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; + Vec3f create_normal(const Triangle &triangle, const Vertices &vertices); - double calculate_error(size_t id_v1, size_t id_v2, SymMat & q, const Vertices &vertices); - Vec3f calculate_vertex(size_t id_v1, size_t id_v2, SymMat & q, const Vertices &vertices); + double calculate_error(uint32_t id_v1, uint32_t id_v2, SymMat & q, const Vertices &vertices); + Vec3f calculate_vertex(uint32_t id_v1, uint32_t id_v2, SymMat & q, const Vertices &vertices); // 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 Vec3f& normal, const Vertices &vertices); - std::tuple init(const indexed_triangle_set &its); - size_t find_triangle_index1(size_t vi, const VertexInfo& v_info, size_t ti, const EdgeInfos& e_infos, const Indices& indices); - bool is_flipped(const Vec3f &new_vertex, size_t ti0, size_t ti1, const VertexInfo& v_info, + std::tuple init(const indexed_triangle_set &its); + 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 - Error calculate_error(const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos); - // subtract quadric of one triangle from triangle vertex - void sub_quadric(const Triangle &t, const Vec3f& normal, VertexInfos &v_infos, const Vertices &vertices); - - void remove_triangle(EdgeInfos &e_infos, VertexInfo &v_info, size_t ti); - void change_neighbors(EdgeInfos &e_infos, VertexInfos &v_infos, size_t ti0, size_t ti1, - size_t vi0, size_t vi1, size_t vi_top0, const Triangle &t1); - + 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); } using namespace QuadricEdgeCollapse; +#ifdef NDEBUG bool check_neighbors(TriangleInfos &t_infos, Indices& indices, VertexInfos & v_infos) { - std::vector t_counts(v_infos.size(), 0); + std::vector t_counts(v_infos.size(), 0); for (size_t i = 0; i < indices.size(); i++) { TriangleInfo &t_info = t_infos[i]; if (t_info.is_deleted()) continue; @@ -114,41 +124,53 @@ bool check_new_vertex(const Vec3f& nv, const Vec3f& v0, const Vec3f& v1) { return true; } -bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, - size_t triangle_count) +#endif // NDEBUG + +void Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, + uint32_t triangle_count, + float * max_error) { - TriangleInfos t_infos; + TriangleInfos t_infos; // only normals with information about deleted triangle VertexInfos v_infos; EdgeInfos e_infos; - std::tie(t_infos, v_infos, e_infos) = init(its); + Errors errors; + std::tie(t_infos, v_infos, e_infos, errors) = init(its); - static constexpr float max_error = std::numeric_limits::max(); - - auto cmp = [&t_infos](size_t ti0, size_t ti1) -> bool { - const Error &e0 = t_infos[ti0].e; - const Error &e1 = t_infos[ti1].e; - return e0.value < e1.value; - }; - // convert triangle index to priority queue index - std::vector i_convert(its.indices.size(), {0}); - auto setter = [&i_convert](size_t it, size_t index) { i_convert[it] = index; }; - MutablePriorityQueue mpq(std::move(setter), std::move(cmp)); + float max_float = std::numeric_limits::max(); + float last_collapsed_error = 0.f; + if (max_error == nullptr) { + max_error = &max_float; + } + + // convert from triangle index to mutable priority queue index + std::vector 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; }; + MutablePriorityQueue mpq(std::move(setter), std::move(less)); mpq.reserve(its.indices.size()); - for (size_t i = 0; i < its.indices.size(); i++) mpq.push(i); + for (Error &error :errors) mpq.push(error); - size_t actual_triangle_count = its.indices.size(); + 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 changed_triangle_indices; + changed_triangle_indices.reserve(2 * max_triangle_count_for_one_vertex); + + uint32_t actual_triangle_count = its.indices.size(); while (actual_triangle_count > triangle_count && !mpq.empty()) { // triangle index 0 - size_t ti0 = mpq.top(); + Error e = mpq.top(); // copy + if (e.value >= *max_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; - Error &e = t_info0.e; - if (e.value >= max_error) return false; // only flipped triangles const Triangle &t0 = its.indices[ti0]; - size_t vi0 = t0[e.min_index]; - size_t vi1 = t0[(e.min_index+1) %3]; + 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]; @@ -161,7 +183,7 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, Vec3f new_vertex0 = calculate_vertex(vi0, vi1, q, its.vertices); //assert(check_new_vertex(new_vertex0, its.vertices[vi0], its.vertices[vi1])); // set of triangle indices that change quadric - size_t ti1 = (v_info0.count < v_info1.count)? + uint32_t ti1 = (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) ; @@ -169,73 +191,63 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, is_flipped(new_vertex0, ti0, ti1, v_info1, t_infos, e_infos, its)) { // IMPROVE1: what about other edges in triangle? // IMPROVE2: check mpq top if it is ti1 with same edge - e.value = max_error; + e.value = std::numeric_limits::max(); // error is changed when surround edge is reduced - mpq.push(ti0); + mpq.push(e); continue; } - - std::vector changed_triangle_indices; + last_collapsed_error = e.value; + changed_triangle_indices.clear(); changed_triangle_indices.reserve(v_info0.count + v_info1.count - 4); - - sub_quadric(t0, t_info0.n, v_infos, its.vertices); - TriangleInfo &t_info1 = t_infos[ti1]; - const Triangle &t1 = its.indices[ti1]; - sub_quadric(t1, t_info1.n, v_infos, its.vertices); + // for each vertex0 triangles - size_t v_info0_end = v_info0.start + v_info0.count; - for (size_t di = v_info0.start; di < v_info0_end; ++di) { + 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()); - size_t ti = e_infos[di].t_index; + uint32_t ti = e_infos[di].t_index; if (ti == ti0) continue; // ti0 will be deleted if (ti == ti1) continue; // ti1 will be deleted - sub_quadric(its.indices[ti], t_infos[ti].n, v_infos, its.vertices); changed_triangle_indices.emplace_back(ti); } // for each vertex1 triangles - size_t v_info1_end = v_info1.start + v_info1.count; - for (size_t di = v_info1.start; di < v_info1_end; ++di) { + 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]; - size_t ti = e_info.t_index; + 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]; - sub_quadric(t, t_infos[ti].n, v_infos, its.vertices); 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 - size_t vi_top0 = t0[(e.min_index + 2) % 3]; - change_neighbors(e_infos, v_infos, ti0, ti1, vi0, vi1, vi_top0, t1); + 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 // Has to be set after subtract quadric its.vertices[vi0] = new_vertex0; - // add new quadrics - v_info0.q = SymMat(); // zero value - for (size_t ti : changed_triangle_indices) { - const Triangle& t = its.indices[ti]; - TriangleInfo &t_info = t_infos[ti]; - t_info.n = create_normal(t, its.vertices); // new normal - SymMat q = create_quadric(t, t_info.n, its.vertices); - for (const size_t vi: t) v_infos[vi].q += q; - } - - // fix errors - must be after calculate all quadric - mpq.remove(i_convert[ti1]); - for (size_t ti : changed_triangle_indices) { - const Triangle &t = its.indices[ti]; - t_infos[ti].e = calculate_error(t, its.vertices, v_infos); - mpq.update(i_convert[ti]); + // 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]; + auto iterator = mpq.begin() + priority_queue_index; + TriangleInfo& t_info = t_infos[ti]; + *iterator = 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 @@ -246,10 +258,10 @@ bool Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, // compact triangle compact(v_infos, t_infos, e_infos, its); - return true; + *max_error = last_collapsed_error; } -Vec3f QuadricEdgeCollapse::create_normal(const Triangle & triangle, +Vec3f QuadricEdgeCollapse::create_normal(const Triangle &triangle, const Vertices &vertices) { const Vec3f &v0 = vertices[triangle[0]]; @@ -261,9 +273,9 @@ Vec3f QuadricEdgeCollapse::create_normal(const Triangle & triangle, return n; } -double QuadricEdgeCollapse::calculate_error(size_t id_v1, - size_t id_v2, - SymMat & q, +double QuadricEdgeCollapse::calculate_error(uint32_t id_v1, + uint32_t id_v2, + SymMat & q, const Vertices &vertices) { double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7); @@ -288,8 +300,8 @@ double QuadricEdgeCollapse::calculate_error(size_t id_v1, } // similar as calculate error but focus on new vertex without calculation of error -Vec3f QuadricEdgeCollapse::calculate_vertex(size_t id_v1, - size_t id_v2, +Vec3f QuadricEdgeCollapse::calculate_vertex(uint32_t id_v1, + uint32_t id_v2, SymMat & q, const Vertices &vertices) { @@ -331,13 +343,13 @@ SymMat QuadricEdgeCollapse::create_quadric(const Triangle & t, return SymMat(n.x(), n.y(), n.z(), -n.dot(v0)); } -std::tuple QuadricEdgeCollapse::init( - const indexed_triangle_set &its) +std::tuple +QuadricEdgeCollapse::init(const indexed_triangle_set &its) { TriangleInfos t_infos(its.indices.size()); VertexInfos v_infos(its.vertices.size()); EdgeInfos e_infos(its.indices.size() * 3); - + Errors errors(its.indices.size()); // calculate normals tbb::parallel_for(tbb::blocked_range(0, its.indices.size()), [&](const tbb::blocked_range &range) { @@ -361,7 +373,7 @@ std::tuple QuadricEdgeCollapse::init( } // set offseted starts - size_t triangle_start = 0; + uint32_t triangle_start = 0; for (VertexInfo &v_info : v_infos) { v_info.start = triangle_start; triangle_start += v_info.count; @@ -376,7 +388,7 @@ std::tuple QuadricEdgeCollapse::init( for (size_t i = range.begin(); i < range.end(); ++i) { const Triangle &t = its.indices[i]; TriangleInfo & t_info = t_infos[i]; - t_info.e = calculate_error(t, its.vertices, v_infos); + errors[i] = calculate_error(i, t, its.vertices, v_infos, t_info.min_index); } }); // END parallel for @@ -393,18 +405,18 @@ std::tuple QuadricEdgeCollapse::init( ++v_info.count; } } - return {t_infos, v_infos, e_infos}; + return {t_infos, v_infos, e_infos, errors}; } -size_t QuadricEdgeCollapse::find_triangle_index1(size_t vi, - const VertexInfo &v_info, - size_t ti0, - const EdgeInfos & e_infos, - const Indices &indices) +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(vi); - size_t end = v_info.start + v_info.count; - for (size_t ei = v_info.start; ei < end; ++ei) { + 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]; @@ -417,12 +429,12 @@ size_t QuadricEdgeCollapse::find_triangle_index1(size_t vi, return -1; } -bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex, - size_t ti0, - size_t ti1, - const VertexInfo & v_info, - const TriangleInfos &t_infos, - const EdgeInfos & e_infos, +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::epsilon(); @@ -456,37 +468,29 @@ bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex, return false; } -Error QuadricEdgeCollapse::calculate_error(const Triangle & t, +Error QuadricEdgeCollapse::calculate_error(uint32_t ti, + const Triangle & t, const Vertices & vertices, - const VertexInfos &v_infos) + const VertexInfos &v_infos, + unsigned char & min_index) { Vec3d error; for (size_t j = 0; j < 3; ++j) { - size_t j2 = (j == 2) ? 0 : (j + 1); - size_t vi0 = t[j]; - size_t vi1 = t[j2]; + 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); } - unsigned char min_index = (error[0] < error[1]) ? - ((error[0] < error[2]) ? 0 : 2) : - ((error[1] < error[2]) ? 1 : 2); - return Error(static_cast(error[min_index]), min_index); -} - -void QuadricEdgeCollapse::sub_quadric(const Triangle &t, - const Vec3f& normal, - VertexInfos &v_infos, - const Vertices &vertices) -{ - SymMat quadric = create_quadric(t, normal, vertices); - for (auto vi: t) v_infos[vi].q -= quadric; + min_index = (error[0] < error[1]) ? ((error[0] < error[2]) ? 0 : 2) : + ((error[1] < error[2]) ? 1 : 2); + return Error(static_cast(error[min_index]), ti); } void QuadricEdgeCollapse::remove_triangle(EdgeInfos & e_infos, VertexInfo &v_info, - size_t ti) + uint32_t ti) { auto e_info = e_infos.begin() + v_info.start; auto e_info_end = e_info + v_info.count - 1; @@ -504,19 +508,20 @@ void QuadricEdgeCollapse::remove_triangle(EdgeInfos & e_infos, void QuadricEdgeCollapse::change_neighbors(EdgeInfos & e_infos, VertexInfos & v_infos, - size_t ti0, - size_t ti1, - size_t vi0, - size_t vi1, - size_t vi_top0, - const Triangle &t1) + 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 - size_t vi_top1 = t1[0]; + 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]; @@ -528,15 +533,15 @@ void QuadricEdgeCollapse::change_neighbors(EdgeInfos & e_infos, VertexInfo &v_info0 = v_infos[vi0]; VertexInfo &v_info1 = v_infos[vi1]; - size_t new_triangle_count = v_info0.count + v_info1.count - 4; + 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 - EdgeInfos e_infos1; + e_infos1.clear(); e_infos1.reserve(v_info1.count - 2); - size_t v_info_s_end = v_info1.start + v_info1.count; - for (size_t ei = v_info1.start; ei < v_info_s_end; ++ei) { + 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; @@ -544,33 +549,25 @@ void QuadricEdgeCollapse::change_neighbors(EdgeInfos & e_infos, } v_info1.count = 0; - size_t need = (new_triangle_count < v_info0.count)? 0: + uint32_t need = (new_triangle_count < v_info0.count)? 0: (new_triangle_count - v_info0.count); - size_t act_vi = vi0 + 1; + uint32_t act_vi = vi0 + 1; VertexInfo *act_v_info = &v_infos[act_vi]; - size_t act_start = act_v_info->start; - size_t last_end = v_info0.start + v_info0.count; + uint32_t act_start = act_v_info->start; + uint32_t last_end = v_info0.start + v_info0.count; + + infos.clear(); + infos.reserve(need); - struct CopyEdgeInfo - { - size_t start; - size_t count; - unsigned char move; - CopyEdgeInfo(size_t start, size_t count, unsigned char move) - : start(start), count(count), move(move) - {} - }; - std::vector c_infos; - c_infos.reserve(need); while (true) { - size_t save = act_start - last_end; + uint32_t save = act_start - last_end; if (save > 0) { if (save >= need) break; need -= save; - c_infos.emplace_back(act_v_info->start, act_v_info->count, need); + infos.emplace_back(act_v_info->start, act_v_info->count, need); } else { - c_infos.back().count += act_v_info->count; + infos.back().count += act_v_info->count; } last_end = act_v_info->start + act_v_info->count; act_v_info->start += need; @@ -583,15 +580,15 @@ void QuadricEdgeCollapse::change_neighbors(EdgeInfos & e_infos, } // copy by c_infos - for (size_t i = c_infos.size(); i > 0; --i) { - const CopyEdgeInfo &c_info = c_infos[i - 1]; - for (size_t ei = c_info.start + c_info.count - 1; ei >= c_info.start; --ei) + 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 (size_t ei_s = 0; ei_s < e_infos1.size(); ++ei_s) { - size_t ei_f = v_info0.start + v_info0.count; + 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; } @@ -602,12 +599,12 @@ void QuadricEdgeCollapse::compact(const VertexInfos & v_infos, const EdgeInfos & e_infos, indexed_triangle_set &its) { - size_t vi_new = 0; - for (size_t vi = 0; vi < v_infos.size(); vi++) { + 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 - size_t e_info_end = v_info.start + v_info.count; - for (size_t ei = v_info.start; ei < e_info_end; ei++) { + 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; @@ -618,8 +615,8 @@ void QuadricEdgeCollapse::compact(const VertexInfos & v_infos, // remove vertices tail its.vertices.erase(its.vertices.begin() + vi_new, its.vertices.end()); - size_t ti_new = 0; - for (size_t ti = 0; ti < t_infos.size(); ti++) { + 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]; diff --git a/src/libslic3r/QuadricEdgeCollapse.hpp b/src/libslic3r/QuadricEdgeCollapse.hpp index d435656b20..422227d288 100644 --- a/src/libslic3r/QuadricEdgeCollapse.hpp +++ b/src/libslic3r/QuadricEdgeCollapse.hpp @@ -2,6 +2,7 @@ // sum up: https://users.csc.calpoly.edu/~zwood/teaching/csc570/final06/jseeba/ // inspiration: https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification +#include #include "TriangleMesh.hpp" namespace Slic3r { @@ -10,8 +11,13 @@ namespace Slic3r { /// Simplify mesh by Quadric metric /// /// IN/OUT triangle mesh to be simplified. -/// wanted triangle count. -/// TRUE on success otherwise FALSE -bool its_quadric_edge_collapse(indexed_triangle_set &its, size_t triangle_count); +/// Wanted triangle count. +/// Maximal Quadric for reduce. +/// When nullptr then max float is used +/// Output: Last used ErrorValue to collapse edge +/// +void its_quadric_edge_collapse(indexed_triangle_set &its, + uint32_t triangle_count = 0, + float * max_error = nullptr); } // namespace Slic3r From e26bffadd8fbbef29431ab7696dfed8f3dec82cc Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Fri, 9 Jul 2021 09:09:52 +0200 Subject: [PATCH 08/29] Add throw_on_cancel and statusfn into QEC --- src/libslic3r/QuadricEdgeCollapse.cpp | 46 ++++++++++++++----- src/libslic3r/QuadricEdgeCollapse.hpp | 15 ++++-- tests/libslic3r/test_indexed_triangle_set.cpp | 13 +++--- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index da409ff58d..e17fbe9c85 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -126,21 +126,29 @@ bool check_new_vertex(const Vec3f& nv, const Vec3f& v0, const Vec3f& v1) { #endif // NDEBUG -void Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, - uint32_t triangle_count, - float * max_error) +void Slic3r::its_quadric_edge_collapse( + indexed_triangle_set & its, + uint32_t triangle_count, + float * max_error, + std::function throw_on_cancel, + std::function 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::max() : *max_error; + if (maximal_error <= 0.f) return; + 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); - - float max_float = std::numeric_limits::max(); - float last_collapsed_error = 0.f; - if (max_error == nullptr) { - max_error = &max_float; - } + throw_on_cancel(); + statusfn(status_init_size); // convert from triangle index to mutable priority queue index std::vector ti_2_mpqi(its.indices.size(), {0}); @@ -159,10 +167,26 @@ void Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, 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 = (100 - status_init_size) * (1. - reduced); + statusfn(static_cast(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 >= *max_error) break; // Too big error + if (e.value >= maximal_error) break; // Too big error mpq.pop(); uint32_t ti0 = e.triangle_index; TriangleInfo &t_info0 = t_infos[ti0]; @@ -258,7 +282,7 @@ void Slic3r::its_quadric_edge_collapse(indexed_triangle_set &its, // compact triangle compact(v_infos, t_infos, e_infos, its); - *max_error = last_collapsed_error; + if (max_error != nullptr) *max_error = last_collapsed_error; } Vec3f QuadricEdgeCollapse::create_normal(const Triangle &triangle, diff --git a/src/libslic3r/QuadricEdgeCollapse.hpp b/src/libslic3r/QuadricEdgeCollapse.hpp index 422227d288..3fcf61a08c 100644 --- a/src/libslic3r/QuadricEdgeCollapse.hpp +++ b/src/libslic3r/QuadricEdgeCollapse.hpp @@ -3,6 +3,7 @@ // inspiration: https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification #include +#include #include "TriangleMesh.hpp" namespace Slic3r { @@ -14,10 +15,14 @@ namespace Slic3r { /// Wanted triangle count. /// Maximal Quadric for reduce. /// When nullptr then max float is used -/// Output: Last used ErrorValue to collapse edge -/// -void its_quadric_edge_collapse(indexed_triangle_set &its, - uint32_t triangle_count = 0, - float * max_error = nullptr); +/// Output: Last used ErrorValue to collapse edge +/// Could stop process of calculation. +/// Give a feed back to user about progress. +void its_quadric_edge_collapse( + indexed_triangle_set & its, + uint32_t triangle_count = 0, + float * max_error = nullptr, + std::function throw_on_cancel = nullptr, + std::function statusfn = nullptr); } // namespace Slic3r diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index 094c17fd6c..087636edf5 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -167,8 +167,8 @@ std::vector its_sample_surface(const indexed_triangle_set &its, // return Average abs distance to original float compare(const indexed_triangle_set &original, - const indexed_triangle_set &simplified, - double sample_per_mm2) + const indexed_triangle_set &simplified, + double sample_per_mm2) { // create ABBTree auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( @@ -203,8 +203,8 @@ TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]") indexed_triangle_set its_ = its; // copy // its_write_obj(its, "tetrhedron_in.obj"); - size_t wanted_count = its.indices.size() - 1; - CHECK(its_quadric_edge_collapse(its, wanted_count)); + 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); @@ -235,11 +235,12 @@ TEST_CASE("Symplify mesh by Quadric edge collapse to 5%", "[its]") { TriangleMesh mesh = load_model("frog_legs.obj"); double original_volume = its_volume(mesh.its); - size_t wanted_count = mesh.its.indices.size() * 0.05; + uint32_t wanted_count = mesh.its.indices.size() * 0.05; REQUIRE_FALSE(mesh.empty()); indexed_triangle_set its = mesh.its; // copy - its_quadric_edge_collapse(its, wanted_count); // its_write_obj(its, "frog_legs_qec.obj"); + float max_error = std::numeric_limits::max(); + its_quadric_edge_collapse(its, wanted_count, &max_error); CHECK(its.indices.size() <= wanted_count); double volume = its_volume(its); CHECK(fabs(original_volume - volume) < 30.); From 6f9f4f78b79f09bf2f3e4c2b9182b03392f07f00 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 9 Jul 2021 09:10:03 +0200 Subject: [PATCH 09/29] Added SimplificationDialog --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_Factories.cpp | 11 +++ src/slic3r/GUI/GUI_Factories.hpp | 1 + src/slic3r/GUI/GUI_ObjectList.cpp | 12 ++++ src/slic3r/GUI/GUI_ObjectList.hpp | 1 + src/slic3r/GUI/Plater.cpp | 36 ++++++++++ src/slic3r/GUI/Plater.hpp | 2 + src/slic3r/GUI/SimplificationDialog.cpp | 91 +++++++++++++++++++++++++ src/slic3r/GUI/SimplificationDialog.hpp | 25 +++++++ 9 files changed, 181 insertions(+) create mode 100644 src/slic3r/GUI/SimplificationDialog.cpp create mode 100644 src/slic3r/GUI/SimplificationDialog.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 76fd8d9891..08dd66d9d1 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -97,6 +97,8 @@ set(SLIC3R_GUI_SOURCES GUI/SavePresetDialog.cpp GUI/PhysicalPrinterDialog.hpp GUI/PhysicalPrinterDialog.cpp + GUI/SimplificationDialog.cpp + GUI/SimplificationDialog.hpp GUI/GUI_Factories.cpp GUI/GUI_Factories.hpp GUI/GUI_ObjectList.cpp diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index c4782615ce..d2d8602864 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -651,6 +651,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("Symplify model"), "", + [](wxCommandEvent&) { obj_list()->simplify(); }, "", menu, + []() {return plater()->can_simplify(); }, plater()); menu->AppendSeparator(); return menu_item; @@ -868,6 +877,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); } @@ -917,6 +927,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"), diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index e8928d3ffb..e0d091ed56 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -90,6 +90,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); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 443ef7815e..cea5dbcb92 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4,6 +4,7 @@ #include "GUI_Factories.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" +#include "SimplificationDialog.hpp" #include "GUI_App.hpp" #include "I18N.hpp" #include "Plater.hpp" @@ -3759,6 +3760,17 @@ void ObjectList::fix_through_netfabb() update_item_error_icon(obj_idx, vol_idx); } +void ObjectList::simplify() +{ + int obj_idx, vol_idx; + get_selected_item_indexes(obj_idx, vol_idx); + + SimplificationDialog dlg(this); + dlg.ShowModal(); + + wxGetApp().plater()->simplify(obj_idx, vol_idx); +} + 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) : diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 36b399e515..291518488a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -353,6 +353,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(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 49c0359bf7..756e74bae1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1730,6 +1730,7 @@ struct Plater::priv void replace_with_stl(); void reload_all_from_disk(); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); + void simplify(const int obj_idx, const int vol_idx = -1); void set_current_panel(wxPanel* panel); @@ -1782,6 +1783,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; @@ -3593,6 +3595,32 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = this->schedule_background_process(); } +void Plater::priv::simplify(const int obj_idx, const int vol_idx/* = -1*/) +{ + if (obj_idx < 0) + return; + + // Do not fix anything when a gizmo is open. There might be issues with updates + // and what is worse, the snapshot time would refer to the internal stack. + if (q->canvas3D()->get_gizmos_manager().get_current_type() != GLGizmosManager::Undefined) { + notification_manager->push_notification( + NotificationType::CustomSupportsAndSeamRemovedAfterRepair, + NotificationManager::NotificationLevel::RegularNotification, + _u8L("ERROR: Please close all manipulators available from " + "the left toolbar before fixing the mesh.")); + return; + } + + // size_t snapshot_time = undo_redo_stack().active_snapshot_time(); + Plater::TakeSnapshot snapshot(q, _L("Symplify model")); + + // ToDo + + this->update(); + this->object_list_changed(); + this->schedule_background_process(); +} + void Plater::priv::set_current_panel(wxPanel* panel) { if (std::find(panels.begin(), panels.end(), panel) == panels.end()) @@ -4375,6 +4403,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() @@ -6309,6 +6343,7 @@ void Plater::suppress_background_process(const bool stop_background_process) } void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); } +void Plater::simplify(const int obj_idx, const int vol_idx/* = -1*/) { p->simplify(obj_idx, vol_idx); } void Plater::mirror(Axis axis) { p->mirror(axis); } void Plater::split_object() { p->split_object(); } void Plater::split_volume() { p->split_volume(); } @@ -6509,6 +6544,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(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index fc4001ba59..d58e6b9b12 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -236,6 +236,7 @@ public: bool is_background_process_update_scheduled() const; void suppress_background_process(const bool stop_background_process) ; void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); + void simplify(const int obj_idx, const int vol_idx = -1); void send_gcode(); void eject_drive(); @@ -314,6 +315,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; diff --git a/src/slic3r/GUI/SimplificationDialog.cpp b/src/slic3r/GUI/SimplificationDialog.cpp new file mode 100644 index 0000000000..780fc24075 --- /dev/null +++ b/src/slic3r/GUI/SimplificationDialog.cpp @@ -0,0 +1,91 @@ +#include "SimplificationDialog.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "format.hpp" +#include "wxExtensions.hpp" +#include "I18N.hpp" +#include "libslic3r/Utils.hpp" + + +namespace Slic3r { +namespace GUI { + +#define BORDER_W 10 + +SimplificationDialog::SimplificationDialog(wxWindow* parent) : + DPIDialog(parent, wxID_ANY, _L("Name of Dialog"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/) +{ + SetFont(wxGetApp().normal_font()); + + wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Some text") + ":"); + + wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(3, 2, 1, 2); + grid_sizer->AddGrowableCol(1, 1); + grid_sizer->SetFlexibleDirection(wxBOTH); + + for (int i = 0; i < 3; i++) { + auto* text = new wxStaticText(this, wxID_ANY, _L("Text") + " " + std::to_string(i) + " :"); + +#ifdef _WIN32 + long style = wxBORDER_SIMPLE; +#else + long style = 0 +#endif + auto value = new wxTextCtrl(this, wxID_ANY, "Some Value", wxDefaultPosition, wxDefaultSize, style); + + grid_sizer->Add(text, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); + grid_sizer->Add(value, 1, wxEXPAND | wxBOTTOM, 1); + } + + wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); + this->Bind(wxEVT_BUTTON, &SimplificationDialog::OnOK, this, wxID_OK); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(grid_sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); + topSizer->Add(btns , 0, wxEXPAND | wxALL, BORDER_W); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); + + wxGetApp().UpdateDlgDarkUI(this); + + this->CenterOnScreen(); +} + +SimplificationDialog::~SimplificationDialog() +{ +} + +void SimplificationDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int& em = em_unit(); + msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); + + const wxSize& size = wxSize(45 * em, 35 * em); + SetMinSize(size); + + Fit(); + Refresh(); +} + +void SimplificationDialog::OnOK(wxEvent& event) +{ + event.Skip(); +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/SimplificationDialog.hpp b/src/slic3r/GUI/SimplificationDialog.hpp new file mode 100644 index 0000000000..4c0210ee86 --- /dev/null +++ b/src/slic3r/GUI/SimplificationDialog.hpp @@ -0,0 +1,25 @@ +#ifndef slic3r_SimplificationDialog_hpp_ +#define slic3r_SimplificationDialog_hpp_ + +#include "GUI_Utils.hpp" + +namespace Slic3r { +namespace GUI { + +class SimplificationDialog : public DPIDialog +{ + void OnOK(wxEvent& event); + +public: + SimplificationDialog(wxWindow* parent); + ~SimplificationDialog(); + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; +}; + + +} // namespace GUI +} // namespace Slic3r + +#endif //slic3r_SimplificationDialog_hpp_ From 2c0b89e4a7c6fb6bc89e0c9ee722b6c4778ae8e6 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Fri, 9 Jul 2021 10:31:05 +0200 Subject: [PATCH 10/29] FIX Simplify test --- src/libslic3r/QuadricEdgeCollapse.cpp | 10 +++++----- tests/libslic3r/test_indexed_triangle_set.cpp | 9 ++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index e17fbe9c85..94375ec9d0 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -15,15 +15,12 @@ namespace QuadricEdgeCollapse { // smallest error caused by edges, identify smallest edge in triangle struct Error { - float value = -1.; - // range(0 .. 2), + 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) - { - assert(min_index < 3); - } + {} Error() = default; }; using Errors = std::vector; @@ -31,6 +28,7 @@ namespace QuadricEdgeCollapse { // merge information together - faster access during processing struct TriangleInfo { Vec3f n; // normalized normal - speed up calcualtion of q and check flip + // 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; } @@ -141,6 +139,8 @@ void Slic3r::its_quadric_edge_collapse( if (triangle_count >= its.indices.size()) return; float maximal_error = (max_error == nullptr)? std::numeric_limits::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; diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index 087636edf5..e079cb3028 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -233,18 +233,17 @@ TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]") #include "test_utils.hpp" TEST_CASE("Symplify mesh by Quadric edge collapse to 5%", "[its]") { - TriangleMesh mesh = load_model("frog_legs.obj"); + 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 - // its_write_obj(its, "frog_legs_qec.obj"); float max_error = std::numeric_limits::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) < 30.); - + CHECK(fabs(original_volume - volume) < 33.); float avg_distance = compare(mesh.its, its, 10); - CHECK(avg_distance < 0.021f); // 0.02022 | 0.0199614074 + CHECK(avg_distance < 0.022f); // 0.02022 | 0.0199614074 } \ No newline at end of file From 756d2694eb2035ce4bbbb86aa81f8e277ef09692 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 12 Jul 2021 16:36:08 +0200 Subject: [PATCH 11/29] FIX: Mutable priority queue --- src/libslic3r/MutablePriorityQueue.hpp | 2 +- src/libslic3r/QuadricEdgeCollapse.cpp | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/MutablePriorityQueue.hpp b/src/libslic3r/MutablePriorityQueue.hpp index 68cd01859c..cc1cae68c9 100644 --- a/src/libslic3r/MutablePriorityQueue.hpp +++ b/src/libslic3r/MutablePriorityQueue.hpp @@ -354,7 +354,7 @@ inline void MutableSkipHeapPriorityQueue::max()); + m_index_setter(m_heap[1], std::numeric_limits::max()); } // Zero'th element is padding, thus non-empty queue must have at least two elements. if (m_heap.size() > 2) { diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index 94375ec9d0..c5de1b0a7f 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -151,10 +151,11 @@ void Slic3r::its_quadric_edge_collapse( statusfn(status_init_size); // convert from triangle index to mutable priority queue index - std::vector ti_2_mpqi(its.indices.size(), {0}); + std::vector 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; }; - MutablePriorityQueue mpq(std::move(setter), std::move(less)); + auto mpq = make_miniheap_mutable_priority_queue(std::move(setter), std::move(less)); + //MutablePriorityQueue mpq(std::move(setter), std::move(less)); mpq.reserve(its.indices.size()); for (Error &error :errors) mpq.push(error); @@ -257,16 +258,14 @@ void Slic3r::its_quadric_edge_collapse( vi_top0, t1, ceis, e_infos_swap); // Change vertex - // Has to be set after subtract quadric 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]; - auto iterator = mpq.begin() + priority_queue_index; TriangleInfo& t_info = t_infos[ti]; - *iterator = calculate_error(ti, its.indices[ti], its.vertices, v_infos, t_info.min_index); + mpq[priority_queue_index] = calculate_error(ti, its.indices[ti], its.vertices, v_infos, t_info.min_index); mpq.update(priority_queue_index); } From af526c54f4b421fb7dda636b86d2202b635ba80a Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 19 Jul 2021 09:17:50 +0200 Subject: [PATCH 12/29] Add simplification GUI --- src/slic3r/CMakeLists.txt | 4 +- src/slic3r/GUI/GUI_ObjectList.cpp | 10 +- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 231 ++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp | 76 ++++++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 13 +- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 1 + src/slic3r/GUI/Plater.cpp | 111 ++++----- src/slic3r/GUI/Plater.hpp | 5 +- src/slic3r/GUI/SimplificationDialog.cpp | 91 ------- src/slic3r/GUI/SimplificationDialog.hpp | 25 -- tests/libslic3r/test_indexed_triangle_set.cpp | 3 +- .../libslic3r/test_mutable_priority_queue.cpp | 101 ++++++++ 12 files changed, 476 insertions(+), 195 deletions(-) create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp delete mode 100644 src/slic3r/GUI/SimplificationDialog.cpp delete mode 100644 src/slic3r/GUI/SimplificationDialog.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 08dd66d9d1..d9e849deff 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -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 @@ -97,8 +99,6 @@ set(SLIC3R_GUI_SOURCES GUI/SavePresetDialog.cpp GUI/PhysicalPrinterDialog.hpp GUI/PhysicalPrinterDialog.cpp - GUI/SimplificationDialog.cpp - GUI/SimplificationDialog.hpp GUI/GUI_Factories.cpp GUI/GUI_Factories.hpp GUI/GUI_ObjectList.cpp diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index cea5dbcb92..8219550359 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4,7 +4,6 @@ #include "GUI_Factories.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" -#include "SimplificationDialog.hpp" #include "GUI_App.hpp" #include "I18N.hpp" #include "Plater.hpp" @@ -3762,13 +3761,8 @@ void ObjectList::fix_through_netfabb() void ObjectList::simplify() { - int obj_idx, vol_idx; - get_selected_item_indexes(obj_idx, vol_idx); - - SimplificationDialog dlg(this); - dlg.ShowModal(); - - wxGetApp().plater()->simplify(obj_idx, vol_idx); + 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 diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp new file mode 100644 index 0000000000..ce11813479 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -0,0 +1,231 @@ +// 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 "libslic3r/AppConfig.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/QuadricEdgeCollapse.hpp" + +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() const{} +void GLGizmoSimplify::on_render_for_picking() const{} + +void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) +{ + 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; + } + + size_t triangle_count = volume->mesh().its.indices.size(); + // already reduced mesh + if (original_its.has_value()) + triangle_count = original_its->indices.size(); + + int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse; + m_imgui->begin(_L("Simplify mesh ") + volume->name, flag); + std::string description = "Reduce amout of triangle in mesh( " + + volume->name + " has " + + std::to_string(triangle_count) + " triangles)"; + ImGui::Text(description.c_str()); + // First initialization + fix triangle count + if (c.wanted_count > triangle_count) c.update_percent(triangle_count); + if (m_imgui->checkbox(_L("Until triangle count is less than "), c.use_count)) { + if (!c.use_count) c.use_error = true; + is_valid_result = false; + } + + m_imgui->disabled_begin(!c.use_count); + ImGui::SameLine(); + int wanted_count = c.wanted_count; + if (ImGui::SliderInt("triangles", &wanted_count, 0, + triangle_count, "%d")) { + c.wanted_count = static_cast(wanted_count); + c.update_count(triangle_count); + is_valid_result = false; + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(80); + int precision = (c.wanted_percent > 10)? 0: ((c.wanted_percent > 1)? 1:2); + 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 < 0.f) c.wanted_percent = 0.f; + if (c.wanted_percent > 100.f) c.wanted_percent = 100.f; + c.update_percent(triangle_count); + is_valid_result = false; + } + m_imgui->disabled_end(); // use_count + + if (m_imgui->checkbox(_L("Until error"), c.use_error)) { + if (!c.use_error) c.use_count = true; + is_valid_result = false; + } + ImGui::SameLine(); + m_imgui->disabled_begin(!c.use_error); + if (ImGui::InputFloat("(maximal quadric error)", &c.max_error, 0.01f, .1f, 2)) { + 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(); + if (m_imgui->button(_L("Preview"))) { + state = State::simplifying; + // simplify but not aply on mesh + process(); + } + ImGui::SameLine(); + if (m_imgui->button(_L("Aply"))) { + 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(); + // draw progress bar + ImGui::Text("Processing %c \t %d / 100", + "|/-\\"[(int) (ImGui::GetTime() / 0.05f) & 3], progress); + } + 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; + + } +} + +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 : nullptr; + std::function throw_on_cancel = [&]() { if ( state == State::canceling) throw SimplifyCanceledException(); }; + std::function statusfn = [&](int percent) { progress = percent; }; + indexed_triangle_set collapsed = original_its.value(); // copy + try { + its_quadric_edge_collapse(collapsed, triangle_count, max_error, throw_on_cancel, statusfn); + set_its(collapsed); + is_valid_result = true; + } catch (SimplifyCanceledException &) { + is_valid_result = false; + } + }); +} + +void GLGizmoSimplify::set_its(indexed_triangle_set &its) { + auto tm = std::make_unique(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(); +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp new file mode 100644 index 0000000000..299d559c77 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -0,0 +1,76 @@ +#ifndef slic3r_GLGizmoSimplify_hpp_ +#define slic3r_GLGizmoSimplify_hpp_ + +#include "GLGizmoBase.hpp" +#include "libslic3r/Model.hpp" +#include +#include + +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 original_its; + + 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(); + void set_selectable(bool value); +protected: + virtual bool on_init() override; + virtual std::string on_get_name() const override; + virtual void on_render() const override; + virtual void on_render_for_picking() const 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( + std::round(triangle_count * wanted_percent / 100.f)); + } + }; + Configuration c; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoSimplify_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index ace6c07857..577720218e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -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" @@ -110,6 +111,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 +171,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_selectable() &&*/ m_gizmos[idx]->is_activable()) { activate_gizmo(m_current == idx ? Undefined : (EType)idx); update_data(); return true; @@ -1021,6 +1023,8 @@ void GLGizmosManager::do_render_overlay() const float u_offset = 1.0f / (float)tex_width; float v_offset = 1.0f / (float)tex_height; + float toolbar_top = 0.f; + float current_y = 0.f; for (size_t idx : selectable_idxs) { GLGizmoBase* gizmo = m_gizmos[idx].get(); @@ -1035,11 +1039,14 @@ void GLGizmosManager::do_render_overlay() const 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); + toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height(); + current_y = 0.5f * cnv_h - zoomed_top_y * zoom; } zoomed_top_y -= zoomed_stride_y; } + + if (m_current != Undefined) + m_gizmos[m_current]->render_input_window(width, current_y, toolbar_top); } float GLGizmosManager::get_scaled_total_height() const diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 01d7ea85c8..383d7099fa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -69,6 +69,7 @@ public: FdmSupports, Seam, MmuSegmentation, + Simplify, Undefined }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 756e74bae1..f2ef0b01b0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1730,7 +1730,6 @@ struct Plater::priv void replace_with_stl(); void reload_all_from_disk(); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); - void simplify(const int obj_idx, const int vol_idx = -1); void set_current_panel(wxPanel* panel); @@ -3555,70 +3554,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(); -} - -void Plater::priv::simplify(const int obj_idx, const int vol_idx/* = -1*/) -{ - if (obj_idx < 0) - return; - - // Do not fix anything when a gizmo is open. There might be issues with updates - // and what is worse, the snapshot time would refer to the internal stack. - if (q->canvas3D()->get_gizmos_manager().get_current_type() != GLGizmosManager::Undefined) { - notification_manager->push_notification( - NotificationType::CustomSupportsAndSeamRemovedAfterRepair, - NotificationManager::NotificationLevel::RegularNotification, - _u8L("ERROR: Please close all manipulators available from " - "the left toolbar before fixing the mesh.")); - return; - } - - // size_t snapshot_time = undo_redo_stack().active_snapshot_time(); - Plater::TakeSnapshot snapshot(q, _L("Symplify model")); - - // ToDo - - this->update(); - this->object_list_changed(); - this->schedule_background_process(); + q->changed_mesh(obj_idx); } void Plater::priv::set_current_panel(wxPanel* panel) @@ -6262,6 +6201,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) @@ -6343,7 +6327,6 @@ void Plater::suppress_background_process(const bool stop_background_process) } void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); } -void Plater::simplify(const int obj_idx, const int vol_idx/* = -1*/) { p->simplify(obj_idx, vol_idx); } void Plater::mirror(Axis axis) { p->mirror(axis); } void Plater::split_object() { p->split_object(); } void Plater::split_volume() { p->split_volume(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index d58e6b9b12..61a2da93dd 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -230,13 +230,16 @@ 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& object_idxs); void schedule_background_process(bool schedule = true); bool is_background_process_update_scheduled() const; void suppress_background_process(const bool stop_background_process) ; void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); - void simplify(const int obj_idx, const int vol_idx = -1); void send_gcode(); void eject_drive(); diff --git a/src/slic3r/GUI/SimplificationDialog.cpp b/src/slic3r/GUI/SimplificationDialog.cpp deleted file mode 100644 index 780fc24075..0000000000 --- a/src/slic3r/GUI/SimplificationDialog.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include "SimplificationDialog.hpp" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "GUI.hpp" -#include "GUI_App.hpp" -#include "format.hpp" -#include "wxExtensions.hpp" -#include "I18N.hpp" -#include "libslic3r/Utils.hpp" - - -namespace Slic3r { -namespace GUI { - -#define BORDER_W 10 - -SimplificationDialog::SimplificationDialog(wxWindow* parent) : - DPIDialog(parent, wxID_ANY, _L("Name of Dialog"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/) -{ - SetFont(wxGetApp().normal_font()); - - wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Some text") + ":"); - - wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(3, 2, 1, 2); - grid_sizer->AddGrowableCol(1, 1); - grid_sizer->SetFlexibleDirection(wxBOTH); - - for (int i = 0; i < 3; i++) { - auto* text = new wxStaticText(this, wxID_ANY, _L("Text") + " " + std::to_string(i) + " :"); - -#ifdef _WIN32 - long style = wxBORDER_SIMPLE; -#else - long style = 0 -#endif - auto value = new wxTextCtrl(this, wxID_ANY, "Some Value", wxDefaultPosition, wxDefaultSize, style); - - grid_sizer->Add(text, 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); - grid_sizer->Add(value, 1, wxEXPAND | wxBOTTOM, 1); - } - - wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); - this->Bind(wxEVT_BUTTON, &SimplificationDialog::OnOK, this, wxID_OK); - - wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); - - topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - topSizer->Add(grid_sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W); - topSizer->Add(btns , 0, wxEXPAND | wxALL, BORDER_W); - - SetSizer(topSizer); - topSizer->SetSizeHints(this); - - wxGetApp().UpdateDlgDarkUI(this); - - this->CenterOnScreen(); -} - -SimplificationDialog::~SimplificationDialog() -{ -} - -void SimplificationDialog::on_dpi_changed(const wxRect& suggested_rect) -{ - const int& em = em_unit(); - msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - - const wxSize& size = wxSize(45 * em, 35 * em); - SetMinSize(size); - - Fit(); - Refresh(); -} - -void SimplificationDialog::OnOK(wxEvent& event) -{ - event.Skip(); -} - -}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/SimplificationDialog.hpp b/src/slic3r/GUI/SimplificationDialog.hpp deleted file mode 100644 index 4c0210ee86..0000000000 --- a/src/slic3r/GUI/SimplificationDialog.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef slic3r_SimplificationDialog_hpp_ -#define slic3r_SimplificationDialog_hpp_ - -#include "GUI_Utils.hpp" - -namespace Slic3r { -namespace GUI { - -class SimplificationDialog : public DPIDialog -{ - void OnOK(wxEvent& event); - -public: - SimplificationDialog(wxWindow* parent); - ~SimplificationDialog(); - -protected: - void on_dpi_changed(const wxRect& suggested_rect) override; -}; - - -} // namespace GUI -} // namespace Slic3r - -#endif //slic3r_SimplificationDialog_hpp_ diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index e079cb3028..06ca3635e0 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -231,9 +231,10 @@ TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]") } #include "test_utils.hpp" -TEST_CASE("Symplify mesh by Quadric edge collapse to 5%", "[its]") +TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]") { TriangleMesh mesh = load_model("frog_legs.obj"); + //TriangleMesh mesh; load_obj("C:/Users/filip/Documents/models/scarecrow_torso.obj", &mesh); double original_volume = its_volume(mesh.its); uint32_t wanted_count = mesh.its.indices.size() * 0.05; REQUIRE_FALSE(mesh.empty()); diff --git a/tests/libslic3r/test_mutable_priority_queue.cpp b/tests/libslic3r/test_mutable_priority_queue.cpp index 37b2444321..ece655365b 100644 --- a/tests/libslic3r/test_mutable_priority_queue.cpp +++ b/tests/libslic3r/test_mutable_priority_queue.cpp @@ -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 idxs(count, {0}); + std::vector dels(count, false); + auto q = make_miniheap_mutable_priority_queue( + [&](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 { + int id; + float val; + }; + size_t count = 5000; + std::vector idxs(count, {0}); + std::vector dels(count, false); + auto q = make_miniheap_mutable_priority_queue( + [&](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; }; + int 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()); + } + } +} From 709ea1283e4e31a8e102b73cc0359455af44153a Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 19 Jul 2021 09:50:45 +0200 Subject: [PATCH 13/29] FIX: new imgui change input float --- src/slic3r/GUI/GUI_Factories.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index c745a57439..d494f84f0f 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -663,7 +663,7 @@ wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu) wxMenuItem* MenuFactory::append_menu_item_simplify(wxMenu* menu) { - wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Symplify model"), "", + wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Simplify model"), "", [](wxCommandEvent&) { obj_list()->simplify(); }, "", menu, []() {return plater()->can_simplify(); }, plater()); menu->AppendSeparator(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index ce11813479..fc96f5035a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -92,7 +92,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } ImGui::SameLine(); ImGui::SetNextItemWidth(80); - int precision = (c.wanted_percent > 10)? 0: ((c.wanted_percent > 1)? 1:2); + 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 < 0.f) c.wanted_percent = 0.f; @@ -108,7 +108,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } ImGui::SameLine(); m_imgui->disabled_begin(!c.use_error); - if (ImGui::InputFloat("(maximal quadric error)", &c.max_error, 0.01f, .1f, 2)) { + if (ImGui::InputFloat("(maximal quadric error)", &c.max_error, 0.01f, .1f, "%.2f")) { if (c.max_error < 0.f) c.max_error = 0.f; is_valid_result = false; } From c444ef81bdaf53bdadcc674edf47320f91d8b753 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 19 Jul 2021 10:12:19 +0200 Subject: [PATCH 14/29] Fix status function --- src/libslic3r/QuadricEdgeCollapse.cpp | 49 +---------------------- src/libslic3r/QuadricEdgeCollapse.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 1 - 3 files changed, 3 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index c5de1b0a7f..4976328c49 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -81,49 +81,6 @@ namespace QuadricEdgeCollapse { using namespace QuadricEdgeCollapse; -#ifdef NDEBUG -bool check_neighbors(TriangleInfos &t_infos, - Indices& indices, - VertexInfos & v_infos) -{ - std::vector t_counts(v_infos.size(), 0); - for (size_t i = 0; i < indices.size(); i++) { - TriangleInfo &t_info = t_infos[i]; - if (t_info.is_deleted()) continue; - Triangle &t = indices[i]; - for (size_t vidx : t) ++t_counts[vidx]; - } - - size_t prev_end = 0; - for (size_t i = 0; i < v_infos.size(); i++) { - VertexInfo &v_info = v_infos[i]; - if (v_info.is_deleted()) continue; - if (v_info.count != t_counts[i]) { - // no correct count - return false; - } - if (prev_end > v_info.start) { - // overlap of start - return false; - } - prev_end = v_info.start + v_info.count; - } - return true; -} - -bool check_new_vertex(const Vec3f& nv, const Vec3f& v0, const Vec3f& v1) { - float epsilon = 1.f; - for (size_t i = 0; i < 3; i++) { - if ((nv[i] > (v0[i] + epsilon) && nv[i] > (v1[i] + epsilon)) || - (nv[i] < (v0[i] - epsilon) && nv[i] < (v1[i] - epsilon))) { - return false; - } - } - return true; -} - -#endif // NDEBUG - void Slic3r::its_quadric_edge_collapse( indexed_triangle_set & its, uint32_t triangle_count, @@ -172,7 +129,8 @@ void Slic3r::its_quadric_edge_collapse( auto increase_status = [&]() { double reduced = (actual_triangle_count - triangle_count) / (double) count_triangle_to_reduce; - double status = (100 - status_init_size) * (1. - reduced); + double status = status_init_size + (100 - status_init_size) * + (1. - reduced); statusfn(static_cast(std::round(status))); }; // modulo for update status @@ -206,7 +164,6 @@ void Slic3r::its_quadric_edge_collapse( SymMat q(v_info0.q); q += v_info1.q; Vec3f new_vertex0 = calculate_vertex(vi0, vi1, q, its.vertices); - //assert(check_new_vertex(new_vertex0, its.vertices[vi0], its.vertices[vi1])); // set of triangle indices that change quadric uint32_t ti1 = (v_info0.count < v_info1.count)? find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) : @@ -275,8 +232,6 @@ void Slic3r::its_quadric_edge_collapse( t_info1.set_deleted(); // triangle counter decrementation actual_triangle_count-=2; - - //assert(check_neighbors(t_infos, its.indices, v_infos)); } // compact triangle diff --git a/src/libslic3r/QuadricEdgeCollapse.hpp b/src/libslic3r/QuadricEdgeCollapse.hpp index 3fcf61a08c..c7330bfc5f 100644 --- a/src/libslic3r/QuadricEdgeCollapse.hpp +++ b/src/libslic3r/QuadricEdgeCollapse.hpp @@ -17,7 +17,7 @@ namespace Slic3r { /// When nullptr then max float is used /// Output: Last used ErrorValue to collapse edge /// Could stop process of calculation. -/// Give a feed back to user about progress. +/// Give a feed back to user about progress. Values 1 - 100 void its_quadric_edge_collapse( indexed_triangle_set & its, uint32_t triangle_count = 0, diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index fc96f5035a..a681c09bec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -49,7 +49,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi 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) { From 25a48870754ff6fdd3b85956611974b9f05e2938 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 19 Jul 2021 15:46:41 +0200 Subject: [PATCH 15/29] QEC: When collapsing edge flip normal than check other edges in triangle Quadric is calculated with double precission of normal Fix calculation of normal for changed triangles --- src/libslic3r/QuadricEdgeCollapse.cpp | 217 +++++++++++------- src/libslic3r/SimplifyMeshImpl.hpp | 6 - src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 7 +- tests/libslic3r/test_indexed_triangle_set.cpp | 2 +- .../libslic3r/test_mutable_priority_queue.cpp | 4 +- 5 files changed, 139 insertions(+), 97 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index 4976328c49..0905253eb1 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -27,9 +27,11 @@ namespace QuadricEdgeCollapse { // merge information together - faster access during processing struct TriangleInfo { - Vec3f n; // normalized normal - speed up calcualtion of q and check flip + 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; } @@ -60,24 +62,30 @@ namespace QuadricEdgeCollapse { }; using CopyEdgeInfos = std::vector; - Vec3f create_normal(const Triangle &triangle, const Vertices &vertices); - double calculate_error(uint32_t id_v1, uint32_t id_v2, SymMat & q, const Vertices &vertices); - Vec3f calculate_vertex(uint32_t id_v1, uint32_t id_v2, SymMat & q, const Vertices &vertices); + Vec3d create_normal(const Triangle &triangle, const Vertices &vertices); + std::array create_vertices(uint32_t id_v1, uint32_t id_v2, const Vertices &vertices); + std::array vertices_error(const SymMat &q, const std::array &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 Vec3f& normal, const Vertices &vertices); + SymMat create_quadric(const Triangle &t, const Vec3d& n, const Vertices &vertices); std::tuple init(const indexed_triangle_set &its); 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; @@ -150,6 +158,7 @@ void Slic3r::its_quadric_edge_collapse( 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]; @@ -171,10 +180,29 @@ void Slic3r::its_quadric_edge_collapse( if (is_flipped(new_vertex0, ti0, ti1, v_info0, t_infos, e_infos, its) || is_flipped(new_vertex0, ti0, ti1, v_info1, t_infos, e_infos, its)) { - // IMPROVE1: what about other edges in triangle? - // IMPROVE2: check mpq top if it is ti1 with same edge - e.value = std::numeric_limits::max(); - // error is changed when surround edge is reduced + + // 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; } @@ -222,6 +250,7 @@ void Slic3r::its_quadric_edge_collapse( 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(); // 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); } @@ -239,69 +268,79 @@ void Slic3r::its_quadric_edge_collapse( if (max_error != nullptr) *max_error = last_collapsed_error; } -Vec3f QuadricEdgeCollapse::create_normal(const Triangle &triangle, +Vec3d QuadricEdgeCollapse::create_normal(const Triangle &triangle, const Vertices &vertices) { - const Vec3f &v0 = vertices[triangle[0]]; - const Vec3f &v1 = vertices[triangle[1]]; - const Vec3f &v2 = vertices[triangle[2]]; + Vec3d v0 = vertices[triangle[0]].cast(); + Vec3d v1 = vertices[triangle[1]].cast(); + Vec3d v2 = vertices[triangle[2]].cast(); // n = triangle normal - Vec3f n = (v1 - v0).cross(v2 - v0); + Vec3d n = (v1 - v0).cross(v2 - v0); n.normalize(); return n; } -double QuadricEdgeCollapse::calculate_error(uint32_t id_v1, - uint32_t id_v2, - SymMat & q, - const Vertices &vertices) +double QuadricEdgeCollapse::calculate_determinant(const SymMat &q) { - double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7); - if (abs(det) < std::numeric_limits::epsilon()) { - // can't divide by zero - const Vec3f &v0 = vertices[id_v1]; - const Vec3f &v1 = vertices[id_v2]; - Vec3d verts[3] = {v0.cast(), v1.cast(), Vec3d()}; - verts[2] = (verts[0] + verts[1]) / 2; - double errors[] = {vertex_error(q, verts[0]), - vertex_error(q, verts[1]), - vertex_error(q, verts[2])}; - return *std::min_element(std::begin(errors), std::end(errors)); - } + 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) - Vec3d vertex(det_1 * det_x, -det_1 * det_y, det_1 * det_z); + return Vec3d(det_1 * det_x, -det_1 * det_y, det_1 * det_z); +} + +std::array QuadricEdgeCollapse::create_vertices(uint32_t id_v1, uint32_t id_v2, const Vertices &vertices) +{ + Vec3d v0 = vertices[id_v1].cast(); + Vec3d v1 = vertices[id_v2].cast(); + Vec3d vm = (v0 + v1) / 2.; + return {v0, v1, vm}; +} + +std::array QuadricEdgeCollapse::vertices_error( + const SymMat &q, const std::array &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 (abs(det) < std::numeric_limits::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, - SymMat & q, + const SymMat & q, const Vertices &vertices) { - double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7); + double det = calculate_determinant(q); if (abs(det) < std::numeric_limits::epsilon()) { // can't divide by zero - const Vec3f &v0 = vertices[id_v1]; - const Vec3f &v1 = vertices[id_v2]; - Vec3d verts[3] = {v0.cast(), v1.cast(), Vec3d()}; - verts[2] = (verts[0] + verts[1]) / 2; - double errors[] = {vertex_error(q, verts[0]), - vertex_error(q, verts[1]), - vertex_error(q, verts[2])}; - auto mit = std::min_element(std::begin(errors), std::end(errors)); + 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(); } - - 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 Vec3f(det_1 * det_x, -det_1 * det_y, det_1 * det_z); + return calculate_vertex(det, q).cast(); } double QuadricEdgeCollapse::vertex_error(const SymMat &q, const Vec3d &vertex) @@ -312,11 +351,10 @@ double QuadricEdgeCollapse::vertex_error(const SymMat &q, const Vec3d &vertex) 2 * q[6] * y + q[7] * z * z + 2 * q[8] * z + q[9]; } -SymMat QuadricEdgeCollapse::create_quadric(const Triangle & t, - const Vec3f &normal, - const Vertices & vertices) +SymMat QuadricEdgeCollapse::create_quadric(const Triangle &t, + const Vec3d & n, + const Vertices &vertices) { - Vec3d n = normal.cast(); Vec3d v0 = vertices[t[0]].cast(); return SymMat(n.x(), n.y(), n.z(), -n.dot(v0)); } @@ -326,29 +364,31 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its) { TriangleInfos t_infos(its.indices.size()); VertexInfos v_infos(its.vertices.size()); - EdgeInfos e_infos(its.indices.size() * 3); - Errors errors(its.indices.size()); - // calculate normals - tbb::parallel_for(tbb::blocked_range(0, its.indices.size()), - [&](const tbb::blocked_range &range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - const Triangle &t = its.indices[i]; - TriangleInfo & t_info = t_infos[i]; - t_info.n = create_normal(t, its.vertices); - } - }); // END parallel for + { + std::vector triangle_quadrics(its.indices.size()); + // calculate normals + tbb::parallel_for(tbb::blocked_range(0, its.indices.size()), + [&](const tbb::blocked_range &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(); + 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]; - TriangleInfo &t_info = t_infos[i]; - SymMat q = create_quadric(t, t_info.n, its.vertices); - for (size_t e = 0; e < 3; e++) { - VertexInfo &v_info = v_infos[t[e]]; - v_info.q += q; - ++v_info.count; // triangle count - } - } + // 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; @@ -361,6 +401,7 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its) assert(its.indices.size() * 3 == triangle_start); // calc error + Errors errors(its.indices.size()); tbb::parallel_for(tbb::blocked_range(0, its.indices.size()), [&](const tbb::blocked_range &range) { for (size_t i = range.begin(); i < range.end(); ++i) { @@ -371,6 +412,7 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its) }); // 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) { @@ -446,21 +488,30 @@ bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex, 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; - 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); - } + 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(error[min_index]), ti); diff --git a/src/libslic3r/SimplifyMeshImpl.hpp b/src/libslic3r/SimplifyMeshImpl.hpp index 0b3b9a2f64..d143b6d876 100644 --- a/src/libslic3r/SimplifyMeshImpl.hpp +++ b/src/libslic3r/SimplifyMeshImpl.hpp @@ -122,12 +122,6 @@ public: return *this; } - const SymetricMatrix &operator-=(const SymetricMatrix &n) - { - for (size_t i = 0; i < N; ++i) m[i] -= n[i]; - return *this; - } - SymetricMatrix operator+(const SymetricMatrix& n) { SymetricMatrix self = *this; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index a681c09bec..8e756a6397 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -69,10 +69,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; m_imgui->begin(_L("Simplify mesh ") + volume->name, flag); - std::string description = "Reduce amout of triangle in mesh( " + - volume->name + " has " + - std::to_string(triangle_count) + " triangles)"; - ImGui::Text(description.c_str()); + ImGui::Text("Reduce amout of triangle in mesh(%s has %d triangles", volume->name, triangle_count); // First initialization + fix triangle count if (c.wanted_count > triangle_count) c.update_percent(triangle_count); if (m_imgui->checkbox(_L("Until triangle count is less than "), c.use_count)) { @@ -202,7 +199,7 @@ void GLGizmoSimplify::process() float* max_error = (c.use_error)? &c.max_error : nullptr; std::function throw_on_cancel = [&]() { if ( state == State::canceling) throw SimplifyCanceledException(); }; std::function statusfn = [&](int percent) { progress = percent; }; - indexed_triangle_set collapsed = original_its.value(); // copy + indexed_triangle_set collapsed = *original_its; // copy try { its_quadric_edge_collapse(collapsed, triangle_count, max_error, throw_on_cancel, statusfn); set_its(collapsed); diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index 06ca3635e0..195c32003a 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -125,7 +125,7 @@ static std::mt19937 create_random_generator() { std::vector its_sample_surface(const indexed_triangle_set &its, double sample_per_mm2, - std::mt19937 &random_generator = create_random_generator()) + std::mt19937 random_generator = create_random_generator()) { std::vector samples; std::uniform_real_distribution rand01(0.f, 1.f); diff --git a/tests/libslic3r/test_mutable_priority_queue.cpp b/tests/libslic3r/test_mutable_priority_queue.cpp index ece655365b..40ed5a1586 100644 --- a/tests/libslic3r/test_mutable_priority_queue.cpp +++ b/tests/libslic3r/test_mutable_priority_queue.cpp @@ -370,7 +370,7 @@ TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]" TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]") { struct MyValue { - int id; + size_t id; float val; }; size_t count = 5000; @@ -382,7 +382,7 @@ TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]") q.reserve(count); auto rand_val = [&]()->float { return (rand() % 53) / 10.f; }; - int ord = 0; + size_t ord = 0; for (size_t id = 0; id < count; id++) { MyValue mv; mv.id = ord++; From 2a2530924fc4d648f3c878feb0686335cd905516 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Tue, 20 Jul 2021 08:30:49 +0200 Subject: [PATCH 16/29] FIX for build Asan_OsX_Mojave_Universal_ARM64/590 ../src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp:72:81: error: cannot pass non-trivial object of type 'std::string' (aka 'basic_string, allocator >') to variadic function; expected type from format string was 'char *' [-Wnon-pod-varargs] ../src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp:72:87: warning: format specifies type 'int' but the argument has type 'size_t' (aka 'unsigned long') [-Wformat] --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 8e756a6397..b46c18a110 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -69,7 +69,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; m_imgui->begin(_L("Simplify mesh ") + volume->name, flag); - ImGui::Text("Reduce amout of triangle in mesh(%s has %d triangles", volume->name, triangle_count); + ImGui::Text(_L("Reduce amout of triangle in mesh(%s has %d triangles").c_str(), + volume->name.c_str(), static_cast(triangle_count)); // First initialization + fix triangle count if (c.wanted_count > triangle_count) c.update_percent(triangle_count); if (m_imgui->checkbox(_L("Until triangle count is less than "), c.use_count)) { From 6464a5b6985d3e40d86c1472923d9f34b6460574 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Tue, 20 Jul 2021 12:05:44 +0200 Subject: [PATCH 17/29] Fix Typo --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index b46c18a110..35b6e63cee 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -114,7 +114,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi if (state == State::settings) { if (m_imgui->button(_L("Cancel"))) { if (original_its.has_value()) { - set_its(*original_its); + set_its(*original_its); state = State::close_on_end; } else { close(); @@ -127,7 +127,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi process(); } ImGui::SameLine(); - if (m_imgui->button(_L("Aply"))) { + if (m_imgui->button(_L("Apply"))) { if (!is_valid_result) { state = State::close_on_end; process(); From b88629974d6b8cbe43d7242bfd8f173d423984ae Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Tue, 20 Jul 2021 15:07:48 +0200 Subject: [PATCH 18/29] Change UI Fix minimal triangle count Fix progress rendering --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 57 +++++++++++++++++------ 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 35b6e63cee..0edb1f5ce4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -68,27 +68,46 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; - m_imgui->begin(_L("Simplify mesh ") + volume->name, flag); - ImGui::Text(_L("Reduce amout of triangle in mesh(%s has %d triangles").c_str(), - volume->name.c_str(), static_cast(triangle_count)); + m_imgui->begin(on_get_name(), flag); + + int top_left_width = 100; + int bottom_left_width = 120; + int input_width = 80; + + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, + _L("Mesh name") + ":"); + ImGui::SameLine(top_left_width); + m_imgui->text(volume->name); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, + _L("Triangles") + ":"); + ImGui::SameLine(top_left_width); + m_imgui->text(std::to_string(volume->mesh().its.indices.size())); + + ImGui::Separator(); + + + ImGui::Text(_L("Limit by triangles").c_str()); + ImGui::SameLine(bottom_left_width); // First initialization + fix triangle count - if (c.wanted_count > triangle_count) c.update_percent(triangle_count); - if (m_imgui->checkbox(_L("Until triangle count is less than "), c.use_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::SameLine(); + ImGui::Text(_L("Triangle count").c_str()); + ImGui::SameLine(bottom_left_width); int wanted_count = c.wanted_count; - if (ImGui::SliderInt("triangles", &wanted_count, 0, - triangle_count, "%d")) { + if (ImGui::SliderInt("##triangle_count", &wanted_count, 0, triangle_count, "%d")) { c.wanted_count = static_cast(wanted_count); + if (c.wanted_count < 4) c.wanted_count = 4; + if (c.wanted_count > triangle_count) c.wanted_count = triangle_count; c.update_count(triangle_count); is_valid_result = false; } - ImGui::SameLine(); - ImGui::SetNextItemWidth(80); + ImGui::Text(_L("Ratio").c_str()); + ImGui::SameLine(bottom_left_width); + ImGui::SetNextItemWidth(input_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)) { @@ -99,13 +118,19 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } m_imgui->disabled_end(); // use_count - if (m_imgui->checkbox(_L("Until error"), c.use_error)) { + ImGui::NewLine(); + ImGui::Text(_L("Limit by error").c_str()); + ImGui::SameLine(bottom_left_width); + if (m_imgui->checkbox("##UseError", c.use_error)) { if (!c.use_error) c.use_count = true; is_valid_result = false; } - ImGui::SameLine(); + m_imgui->disabled_begin(!c.use_error); - if (ImGui::InputFloat("(maximal quadric error)", &c.max_error, 0.01f, .1f, "%.2f")) { + ImGui::Text(_L("Max. error").c_str()); + ImGui::SameLine(bottom_left_width); + ImGui::SetNextItemWidth(input_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; } @@ -199,7 +224,10 @@ void GLGizmoSimplify::process() uint32_t triangle_count = (c.use_count)? c.wanted_count : 0; float* max_error = (c.use_error)? &c.max_error : nullptr; std::function throw_on_cancel = [&]() { if ( state == State::canceling) throw SimplifyCanceledException(); }; - std::function statusfn = [&](int percent) { progress = percent; }; + std::function statusfn = [&](int percent) { + progress = percent; + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + }; indexed_triangle_set collapsed = *original_its; // copy try { its_quadric_edge_collapse(collapsed, triangle_count, max_error, throw_on_cancel, statusfn); @@ -208,6 +236,7 @@ void GLGizmoSimplify::process() } catch (SimplifyCanceledException &) { is_valid_result = false; } + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); }); } From ca3fc06e363447d46ba557ee1dc93dae56c85ca2 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Tue, 20 Jul 2021 18:39:09 +0200 Subject: [PATCH 19/29] GUI edits --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 102 ++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp | 4 +- 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 0edb1f5ce4..07a08de9ca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -44,6 +44,18 @@ void GLGizmoSimplify::on_render_for_picking() const{} void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { + const int min_triangle_count = 4; // tetrahedron + // GUI size constants + const int top_left_width = 100; + const int bottom_left_width = 100; + const int input_width = 100; + const int input_small_width = 80; + const int window_offset = 100; + + 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]; @@ -59,6 +71,11 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi 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 -= window_offset; + pos.y -= window_offset; + ImGui::SetWindowPos(pos, ImGuiCond_Always); } size_t triangle_count = volume->mesh().its.indices.size(); @@ -66,26 +83,15 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi if (original_its.has_value()) triangle_count = original_its->indices.size(); - int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse; - m_imgui->begin(on_get_name(), flag); - - int top_left_width = 100; - int bottom_left_width = 120; - int input_width = 80; - - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, - _L("Mesh name") + ":"); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mesh name") + ":"); ImGui::SameLine(top_left_width); m_imgui->text(volume->name); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, - _L("Triangles") + ":"); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Triangles") + ":"); ImGui::SameLine(top_left_width); - m_imgui->text(std::to_string(volume->mesh().its.indices.size())); + m_imgui->text(std::to_string(triangle_count)); ImGui::Separator(); - ImGui::Text(_L("Limit by triangles").c_str()); ImGui::SameLine(bottom_left_width); // First initialization + fix triangle count @@ -98,22 +104,28 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::Text(_L("Triangle count").c_str()); ImGui::SameLine(bottom_left_width); int wanted_count = c.wanted_count; - if (ImGui::SliderInt("##triangle_count", &wanted_count, 0, triangle_count, "%d")) { + ImGui::SetNextItemWidth(input_width); + if (ImGui::SliderInt("##triangle_count", &wanted_count, min_triangle_count, triangle_count, "%d")) { c.wanted_count = static_cast(wanted_count); - if (c.wanted_count < 4) c.wanted_count = 4; - if (c.wanted_count > triangle_count) c.wanted_count = triangle_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(bottom_left_width); - ImGui::SetNextItemWidth(input_width); + ImGui::SetNextItemWidth(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 < 0.f) c.wanted_percent = 0.f; 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 @@ -129,13 +141,14 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_imgui->disabled_begin(!c.use_error); ImGui::Text(_L("Max. error").c_str()); ImGui::SameLine(bottom_left_width); - ImGui::SetNextItemWidth(input_width); + ImGui::SetNextItemWidth(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()) { @@ -145,7 +158,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi close(); } } - ImGui::SameLine(); + ImGui::SameLine(bottom_left_width); if (m_imgui->button(_L("Preview"))) { state = State::simplifying; // simplify but not aply on mesh @@ -166,10 +179,11 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi if (m_imgui->button(_L("Cancel"))) state = State::canceling; m_imgui->disabled_end(); - ImGui::SameLine(); + ImGui::SameLine(bottom_left_width); // draw progress bar - ImGui::Text("Processing %c \t %d / 100", - "|/-\\"[(int) (ImGui::GetTime() / 0.05f) & 3], progress); + char buf[32]; + sprintf(buf, L("Process %d / 100"), progress); + ImGui::ProgressBar(progress / 100., ImVec2(input_width, 0.f), buf); } m_imgui->end(); @@ -190,8 +204,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } // change from simplifying | aply - state = State::settings; - + state = State::settings; } } @@ -211,9 +224,9 @@ void GLGizmoSimplify::process() const char* what() const throw() { return L("Model simplification has been canceled"); } }; - if (!original_its.has_value()) + 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); @@ -221,20 +234,41 @@ void GLGizmoSimplify::process() 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 : nullptr; - std::function throw_on_cancel = [&]() { if ( state == State::canceling) throw SimplifyCanceledException(); }; + uint32_t triangle_count = (c.use_count) ? c.wanted_count : 0; + float max_error = (c.use_error) ? c.max_error : std::numeric_limits::max(); + + std::function throw_on_cancel = [&]() { + if (state == State::canceling) { + throw SimplifyCanceledException(); + } + }; std::function statusfn = [&](int percent) { progress = percent; wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); }; - indexed_triangle_set collapsed = *original_its; // copy + + 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(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); + 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 &) { - is_valid_result = false; + state = State::settings; } wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); }); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 299d559c77..bc0bd7c5eb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -20,19 +20,19 @@ class GLGizmoSimplify : public GLGizmoBase } state; bool is_valid_result; // differ what to do in apply - int progress; ModelVolume *volume; size_t obj_index; std::optional original_its; + std::optional 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(); - void set_selectable(bool value); protected: virtual bool on_init() override; virtual std::string on_get_name() const override; From 6bcc576b5fbcc869f2db39af32f276366d71a0b4 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Wed, 21 Jul 2021 08:34:43 +0200 Subject: [PATCH 20/29] truncate model name --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 7 ++++++- tests/libslic3r/test_indexed_triangle_set.cpp | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 07a08de9ca..fc53490d7b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -46,11 +46,13 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi { const int min_triangle_count = 4; // tetrahedron // GUI size constants + // TODO: calculate from translation text sizes const int top_left_width = 100; const int bottom_left_width = 100; const int input_width = 100; const int input_small_width = 80; const int window_offset = 100; + const int max_char_in_name = 25; int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; @@ -85,7 +87,10 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mesh name") + ":"); ImGui::SameLine(top_left_width); - m_imgui->text(volume->name); + 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(top_left_width); m_imgui->text(std::to_string(triangle_count)); diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index 195c32003a..b640d8410c 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -233,8 +233,7 @@ TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]") #include "test_utils.hpp" TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]") { - TriangleMesh mesh = load_model("frog_legs.obj"); - //TriangleMesh mesh; load_obj("C:/Users/filip/Documents/models/scarecrow_torso.obj", &mesh); + 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()); From 0079091a8c74e1dd70a9be8945e24e76d1043567 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Wed, 21 Jul 2021 11:58:01 +0200 Subject: [PATCH 21/29] Fix gui size --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 61 +++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp | 15 +++++- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index fc53490d7b..1d98c9de33 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -34,6 +34,7 @@ bool GLGizmoSimplify::on_init() return true; } + std::string GLGizmoSimplify::on_get_name() const { return (_L("Simplify")).ToUTF8().data(); @@ -45,14 +46,8 @@ void GLGizmoSimplify::on_render_for_picking() const{} void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { const int min_triangle_count = 4; // tetrahedron - // GUI size constants - // TODO: calculate from translation text sizes - const int top_left_width = 100; - const int bottom_left_width = 100; - const int input_width = 100; - const int input_small_width = 80; - const int window_offset = 100; const int max_char_in_name = 25; + create_gui_cfg(); int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; @@ -75,8 +70,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi is_valid_result = false; // set window position ImVec2 pos = ImGui::GetMousePos(); - pos.x -= window_offset; - pos.y -= window_offset; + pos.x -= gui_cfg->window_offset; + pos.y -= gui_cfg->window_offset; ImGui::SetWindowPos(pos, ImGuiCond_Always); } @@ -86,19 +81,19 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi triangle_count = original_its->indices.size(); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mesh name") + ":"); - ImGui::SameLine(top_left_width); + 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(top_left_width); + 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(bottom_left_width); + 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; @@ -107,9 +102,9 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_imgui->disabled_begin(!c.use_count); ImGui::Text(_L("Triangle count").c_str()); - ImGui::SameLine(bottom_left_width); + ImGui::SameLine(gui_cfg->bottom_left_width); int wanted_count = c.wanted_count; - ImGui::SetNextItemWidth(input_width); + ImGui::SetNextItemWidth(gui_cfg->input_width); if (ImGui::SliderInt("##triangle_count", &wanted_count, min_triangle_count, triangle_count, "%d")) { c.wanted_count = static_cast(wanted_count); if (c.wanted_count < min_triangle_count) @@ -120,8 +115,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi is_valid_result = false; } ImGui::Text(_L("Ratio").c_str()); - ImGui::SameLine(bottom_left_width); - ImGui::SetNextItemWidth(input_small_width); + 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)) { @@ -137,7 +132,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::NewLine(); ImGui::Text(_L("Limit by error").c_str()); - ImGui::SameLine(bottom_left_width); + 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; @@ -145,8 +140,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_imgui->disabled_begin(!c.use_error); ImGui::Text(_L("Max. error").c_str()); - ImGui::SameLine(bottom_left_width); - ImGui::SetNextItemWidth(input_small_width); + 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; @@ -163,7 +158,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi close(); } } - ImGui::SameLine(bottom_left_width); + ImGui::SameLine(gui_cfg->bottom_left_width); if (m_imgui->button(_L("Preview"))) { state = State::simplifying; // simplify but not aply on mesh @@ -184,11 +179,11 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi if (m_imgui->button(_L("Cancel"))) state = State::canceling; m_imgui->disabled_end(); - ImGui::SameLine(bottom_left_width); + 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(input_width, 0.f), buf); + ImGui::ProgressBar(progress / 100., ImVec2(gui_cfg->input_width, 0.f), buf); } m_imgui->end(); @@ -293,4 +288,26 @@ 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 diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index bc0bd7c5eb..06461d222a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -66,8 +66,21 @@ private: wanted_count = static_cast( 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; }; - Configuration c; + std::optional gui_cfg; + void create_gui_cfg(); }; } // namespace GUI From b225fadfdee858c11a746ef56c6653e28573fd41 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Wed, 21 Jul 2021 15:23:48 +0200 Subject: [PATCH 22/29] Allow skip edge with only one triangle during simplification --- src/libslic3r/QuadricEdgeCollapse.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index 0905253eb1..e42ed5deb2 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -73,7 +73,8 @@ namespace QuadricEdgeCollapse { double vertex_error(const SymMat &q, const Vec3d &vertex); SymMat create_quadric(const Triangle &t, const Vec3d& n, const Vertices &vertices); std::tuple init(const indexed_triangle_set &its); - uint32_t find_triangle_index1(uint32_t vi, const VertexInfo& v_info, uint32_t ti, const EdgeInfos& e_infos, const Indices& indices); + std::optional 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); @@ -174,13 +175,12 @@ void Slic3r::its_quadric_edge_collapse( q += v_info1.q; Vec3f new_vertex0 = calculate_vertex(vi0, vi1, q, its.vertices); // set of triangle indices that change quadric - uint32_t ti1 = (v_info0.count < v_info1.count)? + 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 (is_flipped(new_vertex0, ti0, ti1, v_info0, t_infos, e_infos, its) || - is_flipped(new_vertex0, ti0, ti1, v_info1, t_infos, e_infos, its)) { - + 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]) ? @@ -190,7 +190,6 @@ void Slic3r::its_quadric_edge_collapse( ((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]; @@ -206,6 +205,7 @@ void Slic3r::its_quadric_edge_collapse( 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); @@ -428,7 +428,7 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its) return {t_infos, v_infos, e_infos, errors}; } -uint32_t QuadricEdgeCollapse::find_triangle_index1(uint32_t vi, +std::optional QuadricEdgeCollapse::find_triangle_index1(uint32_t vi, const VertexInfo &v_info, uint32_t ti0, const EdgeInfos & e_infos, @@ -445,8 +445,7 @@ uint32_t QuadricEdgeCollapse::find_triangle_index1(uint32_t vi, return e_info.t_index; } // triangle0 is on border and do NOT have twin edge - assert(false); - return -1; + return {}; } bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex, From 72d01d75e462ed8b4eceb669b15dde2e4080f33d Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Wed, 21 Jul 2021 15:48:10 +0200 Subject: [PATCH 23/29] FIX schedule_extra_frame on end; --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 1d98c9de33..ad08374ba0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -148,7 +148,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } m_imgui->disabled_end(); // use_error - if (state == State::settings) { if (m_imgui->button(_L("Cancel"))) { if (original_its.has_value()) { @@ -243,8 +242,8 @@ void GLGizmoSimplify::process() } }; std::function statusfn = [&](int percent) { - progress = percent; - wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + progress = percent; + m_parent.schedule_extra_frame(0); }; indexed_triangle_set collapsed; @@ -270,7 +269,10 @@ void GLGizmoSimplify::process() } catch (SimplifyCanceledException &) { state = State::settings; } - wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + // need to render last status fn + // without Sleep it freeze until mouse move + Sleep(50); + m_parent.schedule_extra_frame(0); }); } From c0b73a92a3e42881852b71836ac96f26645883a5 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Thu, 22 Jul 2021 15:17:23 +0200 Subject: [PATCH 24/29] Try fix warning icon --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index ad08374ba0..b8884d995a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -4,6 +4,7 @@ #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" @@ -203,7 +204,11 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi } // change from simplifying | aply - state = State::settings; + state = State::settings; + + // Fix warning icon in object list + wxGetApp().obj_list()->update_item_error_icon(obj_index, -1); + wxGetApp().obj_list()->part_selection_changed(); } } From 4a5496e3de97e8579d161917de74411aaf7bea8f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 23 Jul 2021 13:48:38 +0200 Subject: [PATCH 25/29] ObjectDataViewModel: Added function to add warning icon --- src/slic3r/GUI/GUI_ObjectList.cpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 1 - src/slic3r/GUI/ObjectDataViewModel.cpp | 18 ++++++++++++++++++ src/slic3r/GUI/ObjectDataViewModel.hpp | 1 + 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4b7cdf3c2b..45d7115e99 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3951,6 +3951,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() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index b8884d995a..22b3115270 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -208,7 +208,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi // Fix warning icon in object list wxGetApp().obj_list()->update_item_error_icon(obj_index, -1); - wxGetApp().obj_list()->part_selection_changed(); } } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 3eb0cd5c9b..ec09731c53 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -1696,6 +1696,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(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()) diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index da251ef849..7d3b132bac 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -375,6 +375,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; From fc4b18ebb1b27606114d5444764829c796aef666 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 2 Aug 2021 10:46:47 +0200 Subject: [PATCH 26/29] Merge master --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 22b3115270..92654c6064 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -41,8 +41,8 @@ std::string GLGizmoSimplify::on_get_name() const return (_L("Simplify")).ToUTF8().data(); } -void GLGizmoSimplify::on_render() const{} -void GLGizmoSimplify::on_render_for_picking() const{} +void GLGizmoSimplify::on_render() {} +void GLGizmoSimplify::on_render_for_picking() {} void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 06461d222a..49c3e8dbda 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -36,8 +36,8 @@ public: protected: virtual bool on_init() override; virtual std::string on_get_name() const override; - virtual void on_render() const override; - virtual void on_render_for_picking() 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; }; From 54897aeac0ca70afa9c0e002795d29eb9af8e9bd Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 5 Aug 2021 17:33:41 +0200 Subject: [PATCH 27/29] Fixed previous commit --- src/libslic3r/Model.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index c45acc0484..5383581778 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -476,7 +476,7 @@ bool Model::looks_like_imperial_units() const void Model::convert_from_imperial_units(bool only_small_volumes) { - static constexpr const in_to_mm = 25.4; + static constexpr const double in_to_mm = 25.4; for (ModelObject* obj : this->objects) if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_inches) { obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); From 01f32e18d6e5fc520a947d3b1b59cb41bf6d9c20 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 6 Aug 2021 13:03:30 +0200 Subject: [PATCH 28/29] Fixed build on Linux, abs->std::abs --- src/libslic3r/QuadricEdgeCollapse.cpp | 7 ++++--- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index e42ed5deb2..a66dbc192a 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -2,6 +2,7 @@ #include #include "MutablePriorityQueue.hpp" #include "SimplifyMeshImpl.hpp" +#include using namespace Slic3r; @@ -316,7 +317,7 @@ double QuadricEdgeCollapse::calculate_error(uint32_t id_v1, const Vertices &vertices) { double det = calculate_determinant(q); - if (abs(det) < std::numeric_limits::epsilon()) { + if (std::abs(det) < std::numeric_limits::epsilon()) { // can't divide by zero auto verts = create_vertices(id_v1, id_v2, vertices); auto errors = vertices_error(q, verts); @@ -333,7 +334,7 @@ Vec3f QuadricEdgeCollapse::calculate_vertex(uint32_t id_v1, const Vertices &vertices) { double det = calculate_determinant(q); - if (abs(det) < std::numeric_limits::epsilon()) { + if (std::abs(det) < std::numeric_limits::epsilon()) { // can't divide by zero auto verts = create_vertices(id_v1, id_v2, vertices); auto errors = vertices_error(q, verts); @@ -650,4 +651,4 @@ void QuadricEdgeCollapse::compact(const VertexInfos & v_infos, its.indices[ti_new++] = its.indices[ti]; } its.indices.erase(its.indices.begin() + ti_new, its.indices.end()); -} \ No newline at end of file +} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 92654c6064..694eeadd4e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -9,6 +9,9 @@ #include "libslic3r/Model.hpp" #include "libslic3r/QuadricEdgeCollapse.hpp" +#include +#include + namespace Slic3r::GUI { GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent, @@ -274,8 +277,8 @@ void GLGizmoSimplify::process() state = State::settings; } // need to render last status fn - // without Sleep it freeze until mouse move - Sleep(50); + // without sleep it freezes until mouse move + std::this_thread::sleep_for(std::chrono::milliseconds(50)); m_parent.schedule_extra_frame(0); }); } From 85c7dea1a98530a09c4485fbbf582dd6012f6cf3 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 6 Aug 2021 16:02:32 +0200 Subject: [PATCH 29/29] Finished concept of gizmos with no toolbar icon: on_is_selectable and on_is_activable functions are now completely independent, the former says if there shall be an icon in the left toolbar, the latter says if the gizmo can be activated (by a shortcut or GLGizmoManager::open_gizmo) --- .../GUI/Gizmos/GLGizmoMmuSegmentation.cpp | 5 ++++ .../GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 27 ++++++++----------- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 1 - 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index f6e7708fae..5bf2c1556a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -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> get_extruders_colors() { unsigned char rgb_color[3] = {}; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index e890ca0312..b1b19bfaca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -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; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 317d7ebcaa..507aeb021b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -515,7 +515,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. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 49c3e8dbda..bd36373607 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -40,7 +40,7 @@ protected: 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; }; + virtual bool on_is_selectable() const override { return false; } private: void close(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 8871fec23e..eda95a2a55 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -51,14 +51,7 @@ std::vector GLGizmosManager::get_selectable_idxs() const return out; } -std::vector GLGizmosManager::get_activable_idxs() const -{ - std::vector out; - for (size_t i=0; iis_activable()) - out.push_back(i); - return out; -} + size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const { @@ -171,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(); } @@ -189,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; @@ -306,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& gizmo) { int gizmo_key = gizmo->get_shortcut_key(); - return gizmo->is_selectable() + return gizmo->is_activable() && ((gizmo_key == key - 64) || (gizmo_key == key - 96)); }); @@ -1079,8 +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 toolbar_top = 0.f; - float current_y = 0.f; + float current_y = FLT_MAX; for (size_t idx : selectable_idxs) { GLGizmoBase* gizmo = m_gizmos[idx].get(); @@ -1094,15 +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) { - toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height(); + 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) + 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 diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index db628a0b76..cdfb3f6ff7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -102,7 +102,6 @@ private: std::pair m_highlight; // bool true = higlightedShown, false = highlightedHidden std::vector get_selectable_idxs() const; - std::vector get_activable_idxs() const; size_t get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const; void activate_gizmo(EType type);