diff --git a/.clang-format b/.clang-format index 6b68254ec..440c89ec5 100644 --- a/.clang-format +++ b/.clang-format @@ -77,7 +77,7 @@ IndentWidth: 4 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true -KeepLineBreaksForNonEmptyLines: false +#KeepLineBreaksForNonEmptyLines: false KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' diff --git a/.gitignore b/.gitignore index efeaa91b5..63004bab2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ MANIFEST.bak xs/MANIFEST.bak xs/assertlib* .init_bundle.ini +.vs/* local-lib /src/TAGS /.vscode/ +build-linux/* +deps/build-linux/* diff --git a/resources/icons/toolbar_arrow.png b/resources/icons/toolbar_arrow.png deleted file mode 100644 index a370442ff..000000000 Binary files a/resources/icons/toolbar_arrow.png and /dev/null differ diff --git a/resources/icons/toolbar_arrow.svg b/resources/icons/toolbar_arrow.svg index 998646a24..b49645190 100644 --- a/resources/icons/toolbar_arrow.svg +++ b/resources/icons/toolbar_arrow.svg @@ -1,76 +1,22 @@ - -image/svg+xml - \ No newline at end of file diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 3dd5da307..a59165946 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -118,6 +118,11 @@ void triangle_mesh_to_cgal(const std::vector & V, { if (F.empty()) return; + size_t vertices_count = V.size(); + size_t edges_count = (F.size()* 3) / 2; + size_t faces_count = F.size(); + out.reserve(vertices_count, edges_count, faces_count); + for (auto &v : V) out.add_vertex(typename _Mesh::Point{v.x(), v.y(), v.z()}); diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index 95c7b40d8..6efc5f4a9 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -7,13 +7,14 @@ using namespace Slic3r; -// only private namespace not neccessary be in hpp +// 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; - + using ThrowOnCancel = std::function; + using StatusFn = std::function; // smallest error caused by edges, identify smallest edge in triangle struct Error { @@ -74,12 +75,14 @@ namespace QuadricEdgeCollapse { // calculate error for vertex and quadrics, triangle quadrics and triangle vertex give zero, only pozitive number double vertex_error(const SymMat &q, const Vec3d &vertex); SymMat create_quadric(const Triangle &t, const Vec3d& n, const Vertices &vertices); - std::tuple init(const indexed_triangle_set &its); + std::tuple + init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn); 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); - + bool degenerate(uint32_t vi, uint32_t ti0, uint32_t ti1, const VertexInfo &v_info, + const EdgeInfos &e_infos, const Indices &indices); // 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); @@ -88,6 +91,14 @@ namespace QuadricEdgeCollapse { 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); + +#ifndef NDEBUG + void store_surround(const char *obj_filename, size_t triangle_index, int depth, const indexed_triangle_set &its, + const VertexInfos &v_infos, const EdgeInfos &e_infos); + bool check_neighbors(const indexed_triangle_set &its, const TriangleInfos &t_infos, + const VertexInfos &v_infos, const EdgeInfos &e_infos); +#endif /* NDEBUG */ + } // namespace QuadricEdgeCollapse using namespace QuadricEdgeCollapse; @@ -97,7 +108,7 @@ void Slic3r::its_quadric_edge_collapse( uint32_t triangle_count, float * max_error, std::function throw_on_cancel, - std::function statusfn) + std::function status_fn) { // constants --> may be move to config const int status_init_size = 10; // in percents @@ -108,15 +119,22 @@ void Slic3r::its_quadric_edge_collapse( 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) {}; + if (status_fn == nullptr) status_fn = [](int) {}; + + StatusFn init_status_fn = [&](int percent) { + status_fn(std::round((percent * status_init_size) / 100.)); + }; 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); + std::tie(t_infos, v_infos, e_infos, errors) = init(its, throw_on_cancel, init_status_fn); throw_on_cancel(); - statusfn(status_init_size); + status_fn(status_init_size); + + //its_store_triangle(its, "triangle.obj", 1182); + //store_surround("triangle_surround1.obj", 1182, 1, its, v_infos, e_infos); // convert from triangle index to mutable priority queue index std::vector ti_2_mpqi(its.indices.size(), {0}); @@ -142,7 +160,7 @@ void Slic3r::its_quadric_edge_collapse( (double) count_triangle_to_reduce; double status = status_init_size + (100 - status_init_size) * (1. - reduced); - statusfn(static_cast(std::round(status))); + status_fn(static_cast(std::round(status))); }; // modulo for update status uint32_t status_mod = std::max(uint32_t(16), count_triangle_to_reduce / 100); @@ -181,6 +199,8 @@ void Slic3r::its_quadric_edge_collapse( find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) : find_triangle_index1(vi0, v_info1, ti0, e_infos, its.indices) ; if (!ti1_opt.has_value() || // edge has only one triangle + degenerate(vi0, ti0, *ti1_opt, v_info1, e_infos, its.indices) || + degenerate(vi1, ti0, *ti1_opt, v_info0, e_infos, its.indices) || 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 @@ -236,8 +256,7 @@ void Slic3r::its_quadric_edge_collapse( } v_info0.q = q; - // fix neighbors - + // fix neighbors // vertex index of triangle 0 which is not vi0 nor vi1 uint32_t vi_top0 = t0[(t_info0.min_index + 2) % 3]; const Triangle &t1 = its.indices[ti1]; @@ -263,6 +282,7 @@ void Slic3r::its_quadric_edge_collapse( t_info1.set_deleted(); // triangle counter decrementation actual_triangle_count-=2; + assert(check_neighbors(its, t_infos, v_infos, e_infos)); } // compact triangle @@ -362,8 +382,16 @@ SymMat QuadricEdgeCollapse::create_quadric(const Triangle &t, } std::tuple -QuadricEdgeCollapse::init(const indexed_triangle_set &its) +QuadricEdgeCollapse::init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn) { + // change speed of progress bargraph + const int status_normal_size = 25; + const int status_sum_quadric = 25; + const int status_set_offsets = 10; + const int status_calc_errors = 30; + const int status_create_refs = 10; + + int status_offset = 0; TriangleInfos t_infos(its.indices.size()); VertexInfos v_infos(its.vertices.size()); { @@ -377,8 +405,13 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its) Vec3d normal = create_normal(t, its.vertices); t_info.n = normal.cast(); triangle_quadrics[i] = create_quadric(t, normal, its.vertices); + if (i % 1000000 == 0) { + throw_on_cancel(); + status_fn(status_offset + (i * status_normal_size) / its.indices.size()); + } } }); // END parallel for + status_offset += status_normal_size; // sum quadrics for (size_t i = 0; i < its.indices.size(); i++) { @@ -389,7 +422,12 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its) v_info.q += q; ++v_info.count; // triangle count } + if (i % 1000000 == 0) { + throw_on_cancel(); + status_fn(status_offset + (i * status_sum_quadric) / its.indices.size()); + } } + status_offset += status_sum_quadric; } // remove triangle quadrics // set offseted starts @@ -402,6 +440,10 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its) } assert(its.indices.size() * 3 == triangle_start); + status_offset += status_set_offsets; + throw_on_cancel(); + status_fn(status_offset); + // calc error Errors errors(its.indices.size()); tbb::parallel_for(tbb::blocked_range(0, its.indices.size()), @@ -410,8 +452,15 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its) const Triangle &t = its.indices[i]; TriangleInfo & t_info = t_infos[i]; errors[i] = calculate_error(i, t, its.vertices, v_infos, t_info.min_index); + if (i % 1000000 == 0) { + throw_on_cancel(); + status_fn(status_offset + (i * status_calc_errors) / its.indices.size()); + } + if (i % 1000000 == 0) throw_on_cancel(); } }); // END parallel for + + status_offset += status_calc_errors; // create reference EdgeInfos e_infos(its.indices.size() * 3); @@ -426,7 +475,14 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its) e_info.edge = j; ++v_info.count; } + if (i % 1000000 == 0) { + throw_on_cancel(); + status_fn(status_offset + (i * status_create_refs) / its.indices.size()); + } } + + throw_on_cancel(); + status_fn(100); return {t_infos, v_infos, e_infos, errors}; } @@ -489,6 +545,28 @@ bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex, return false; } +bool QuadricEdgeCollapse::degenerate(uint32_t vi, + uint32_t ti0, + uint32_t ti1, + const VertexInfo &v_info, + const EdgeInfos & e_infos, + const Indices & indices) +{ + // check surround triangle do not contain vertex index + // protect from creation of triangle with two same vertices inside + 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 = indices[e_info.t_index]; + for (size_t i = 0; i < 3; ++i) + if (static_cast(t[i]) == vi) return true; + } + return false; +} + Vec3d QuadricEdgeCollapse::calculate_3errors(const Triangle & t, const Vertices & vertices, const VertexInfos &v_infos) @@ -653,3 +731,115 @@ void QuadricEdgeCollapse::compact(const VertexInfos & v_infos, } its.indices.erase(its.indices.begin() + ti_new, its.indices.end()); } + +#ifndef NDEBUG +// store triangle surrounding to file +void QuadricEdgeCollapse::store_surround(const char *obj_filename, + size_t triangle_index, + int depth, + const indexed_triangle_set &its, + const VertexInfos & v_infos, + const EdgeInfos & e_infos) +{ + std::set triangles; + // triangle index, depth + using Item = std::pair; + std::queue process; + process.push({triangle_index, depth}); + + while (!process.empty()) { + Item item = process.front(); + process.pop(); + size_t ti = item.first; + auto it = triangles.find(ti); + if (it != triangles.end()) continue; + triangles.insert(ti); + if (item.second == 0) continue; + + const Vec3i &t = its.indices[ti]; + for (size_t i = 0; i < 3; ++i) { + const auto &v_info = v_infos[t[i]]; + for (size_t d = 0; d < v_info.count; ++d) { + size_t ei = v_info.start + d; + const auto &e_info = e_infos[ei]; + auto it = triangles.find(e_info.t_index); + if (it != triangles.end()) continue; + process.push({e_info.t_index, item.second - 1}); + } + } + } + + std::vector trs; + trs.reserve(triangles.size()); + for (size_t ti : triangles) trs.push_back(ti); + its_store_triangles(its, obj_filename, trs); + // its_write_obj(its,"original.obj"); +} + +bool QuadricEdgeCollapse::check_neighbors(const indexed_triangle_set &its, + const TriangleInfos & t_infos, + const VertexInfos & v_infos, + const EdgeInfos & e_infos) +{ + VertexInfos v_infos2(v_infos.size()); + size_t count_indices = 0; + + for (size_t ti = 0; ti < its.indices.size(); ti++) { + if (t_infos[ti].is_deleted()) continue; + ++count_indices; + const Triangle &t = its.indices[ti]; + for (size_t e = 0; e < 3; e++) { + VertexInfo &v_info = v_infos2[t[e]]; + ++v_info.count; // triangle count + } + } + + uint32_t triangle_start = 0; + for (VertexInfo &v_info : v_infos2) { + v_info.start = triangle_start; + triangle_start += v_info.count; + // set filled vertex to zero + v_info.count = 0; + } + + // create reference + EdgeInfos e_infos2(count_indices * 3); + for (size_t ti = 0; ti < its.indices.size(); ti++) { + if (t_infos[ti].is_deleted()) continue; + const Triangle &t = its.indices[ti]; + for (size_t j = 0; j < 3; ++j) { + VertexInfo &v_info = v_infos2[t[j]]; + size_t ei = v_info.start + v_info.count; + assert(ei < e_infos2.size()); + EdgeInfo &e_info = e_infos2[ei]; + e_info.t_index = ti; + e_info.edge = j; + ++v_info.count; + } + } + + for (size_t vi = 0; vi < its.vertices.size(); vi++) { + const VertexInfo &v_info = v_infos[vi]; + if (v_info.is_deleted()) continue; + const VertexInfo &v_info2 = v_infos2[vi]; + if (v_info.count != v_info2.count) { return false; } + EdgeInfos eis; + eis.reserve(v_info.count); + std::copy(e_infos.begin() + v_info.start, + e_infos.begin() + v_info.start + v_info.count, + std::back_inserter(eis)); + auto compare = [](const EdgeInfo &ei1, const EdgeInfo &ei2) { + return ei1.t_index < ei2.t_index; + }; + std::sort(eis.begin(), eis.end(), compare); + std::sort(e_infos2.begin() + v_info2.start, + e_infos2.begin() + v_info2.start + v_info2.count, compare); + for (size_t ei = 0; ei < v_info.count; ++ei) { + if (eis[ei].t_index != e_infos2[ei + v_info2.start].t_index) { + return false; + } + } + } + return true; +} +#endif /* NDEBUG */ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 360a8b14e..d4baabc97 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -957,6 +957,48 @@ int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit) return removed; } +bool its_store_triangle(const indexed_triangle_set &its, + const char * obj_filename, + size_t triangle_index) +{ + if (its.indices.size() <= triangle_index) return false; + Vec3i t = its.indices[triangle_index]; + indexed_triangle_set its2; + its2.indices = {{0, 1, 2}}; + its2.vertices = {its.vertices[t[0]], its.vertices[t[1]], + its.vertices[t[2]]}; + return its_write_obj(its2, obj_filename); +} + +bool its_store_triangles(const indexed_triangle_set &its, + const char * obj_filename, + const std::vector & triangles) +{ + indexed_triangle_set its2; + its2.vertices.reserve(triangles.size() * 3); + its2.indices.reserve(triangles.size()); + std::map vertex_map; + for (auto ti : triangles) { + if (its.indices.size() <= ti) return false; + Vec3i t = its.indices[ti]; + Vec3i new_t; + for (size_t i = 0; i < 3; ++i) { + size_t vi = t[i]; + auto it = vertex_map.find(vi); + if (it != vertex_map.end()) { + new_t[i] = it->second; + continue; + } + size_t new_vi = its2.vertices.size(); + its2.vertices.push_back(its.vertices[vi]); + vertex_map[vi] = new_vi; + new_t[i] = new_vi; + } + its2.indices.push_back(new_t); + } + return its_write_obj(its2, obj_filename); +} + void its_shrink_to_fit(indexed_triangle_set &its) { its.indices.shrink_to_fit(); diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index c8c7e0dd7..b7a1bebb1 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -140,6 +140,10 @@ int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit = // Remove vertices, which none of the faces references. Return number of freed vertices. int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit = true); +// store part of index triangle set +bool its_store_triangle(const indexed_triangle_set &its, const char *obj_filename, size_t triangle_index); +bool its_store_triangles(const indexed_triangle_set &its, const char *obj_filename, const std::vector& triangles); + std::vector its_split(const indexed_triangle_set &its); bool its_is_splittable(const indexed_triangle_set &its); diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index fdbc1a6d3..72f1421f6 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -195,7 +195,7 @@ void CopyrightsDialog::on_dpi_changed(const wxRect &suggested_rect) void CopyrightsDialog::onLinkClicked(wxHtmlLinkEvent &event) { - wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref()); + wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref()); event.Skip(false); } @@ -344,7 +344,7 @@ void AboutDialog::on_dpi_changed(const wxRect &suggested_rect) void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event) { - wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref()); + wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref()); event.Skip(false); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 227e5ae4e..ee1d3b14a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2239,6 +2239,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); m_dirty |= mouse3d_controller_applied; m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this); + auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current(); + if (gizmo != nullptr) m_dirty |= gizmo->update_items_state(); if (!m_dirty) return; @@ -2780,11 +2782,10 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt) void GLCanvas3D::on_render_timer(wxTimerEvent& evt) { - // no need to do anything here - // right after this event is recieved, idle event is fired - - //m_dirty = true; - //wxWakeUpIdle(); + // no need to wake up idle + // right after this event, idle event is fired + // m_dirty = true; + // wxWakeUpIdle(); } @@ -2802,21 +2803,15 @@ void GLCanvas3D::schedule_extra_frame(int miliseconds) return; } } - // Start timer - int64_t now = timestamp_now(); + int remaining_time = m_render_timer.GetInterval(); // Timer is not running - if (! m_render_timer.IsRunning()) { - m_extra_frame_requested_delayed = miliseconds; + if (!m_render_timer.IsRunning()) { m_render_timer.StartOnce(miliseconds); - m_render_timer_start = now; // Timer is running - restart only if new period is shorter than remaning period } else { - const int64_t remaining_time = (m_render_timer_start + m_extra_frame_requested_delayed) - now; if (miliseconds + 20 < remaining_time) { m_render_timer.Stop(); - m_extra_frame_requested_delayed = miliseconds; m_render_timer.StartOnce(miliseconds); - m_render_timer_start = now; } } } @@ -4438,13 +4433,7 @@ bool GLCanvas3D::_init_main_toolbar() } // init arrow BackgroundTexture::Metadata arrow_data; - arrow_data.filename = "toolbar_arrow.png"; -// arrow_data.filename = "toolbar_arrow.svg"; - //arrow_data.left = 16; - //arrow_data.top = 16; - //arrow_data.right = 16; - //arrow_data.bottom = 16; - + arrow_data.filename = "toolbar_arrow.svg"; arrow_data.left = 0; arrow_data.top = 0; arrow_data.right = 0; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index d9cd55e35..ba8430c07 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -463,15 +463,13 @@ private: std::string m_sidebar_field; // when true renders an extra frame by not resetting m_dirty to false // see request_extra_frame() - bool m_extra_frame_requested; - int m_extra_frame_requested_delayed { std::numeric_limits::max() }; + bool m_extra_frame_requested; bool m_event_handlers_bound{ false }; GLVolumeCollection m_volumes; GCodeViewer m_gcode_viewer; RenderTimer m_render_timer; - int64_t m_render_timer_start; Selection m_selection; const DynamicPrintConfig* m_config; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 243031847..515b6ed3a 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -193,10 +193,9 @@ bool GLToolbar::init_arrow(const BackgroundTexture::Metadata& arrow_texture) std::string path = resources_dir() + "/icons/"; bool res = false; - if (!arrow_texture.filename.empty()) - res = m_arrow_texture.texture.load_from_file(path + arrow_texture.filename, false, GLTexture::SingleThreaded, false); -// res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, true, false, 100); - + if (!arrow_texture.filename.empty()) { + res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, false, false, 1000); + } if (res) m_arrow_texture.metadata = arrow_texture; @@ -1176,19 +1175,22 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte float right = left + scaled_icons_size; unsigned int tex_id = m_arrow_texture.texture.get_id(); + // width and height of icon arrow is pointing to float tex_width = (float)m_icons_texture.get_width(); float tex_height = (float)m_icons_texture.get_height(); + // arrow width and height + float arr_tex_width = (float)m_arrow_texture.texture.get_width(); + float arr_tex_height = (float)m_arrow_texture.texture.get_height(); + if ((tex_id != 0) && (arr_tex_width > 0) && (arr_tex_height > 0)) { + float inv_tex_width = (arr_tex_width != 0.0f) ? 1.0f / arr_tex_width : 0.0f; + float inv_tex_height = (arr_tex_height != 0.0f) ? 1.0f / arr_tex_height : 0.0f; - if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) { - float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; - float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; - - float internal_left = left + border - scaled_icons_size / 2; // add half scaled_icons_size for huge arrow - float internal_right = right - border + scaled_icons_size / 2; + float internal_left = left + border - scaled_icons_size * 1.5f; // add scaled_icons_size for huge arrow + float internal_right = right - border + scaled_icons_size * 1.5f; float internal_top = top - border; // bottom is not moving and should be calculated from arrow texture sides ratio float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width(); - float internal_bottom = internal_top - (internal_right - internal_left) * arrow_sides_ratio; + float internal_bottom = internal_top - (internal_right - internal_left) * arrow_sides_ratio ; float internal_left_uv = (float)m_arrow_texture.metadata.left * inv_tex_width; float internal_right_uv = 1.0f - (float)m_arrow_texture.metadata.right * inv_tex_width; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 58ce12ae4..102026a0c 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2327,7 +2327,7 @@ wxString GUI_App::current_language_code_safe() const void GUI_App::open_web_page_localized(const std::string &http_address) { - wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe()); + open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe()); } bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) @@ -2525,6 +2525,23 @@ void GUI_App::check_updates(const bool verbose) } } +bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/* = 0*/) +{ + bool launch = true; + + if (get_app_config()->get("suppress_hyperlinks").empty()) { + wxRichMessageDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + dialog.ShowCheckBox(_L("Remember my choice")); + int answer = dialog.ShowModal(); + launch = answer == wxID_YES; + get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : ""); + } + if (launch) + launch = get_app_config()->get("suppress_hyperlinks") != "1"; + + return launch && wxLaunchDefaultBrowser(url, flags); +} + // static method accepting a wxWindow object as first parameter // void warning_catcher{ // my($self, $message_dialog) = @_; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index a140247e0..1d8c2fcf7 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -261,7 +261,8 @@ public: void open_preferences(size_t open_on_tab = 0); virtual bool OnExceptionInMainLoop() override; - + // Calls wxLaunchDefaultBrowser if user confirms in dialog. + bool open_browser_with_warning_dialog(const wxString& url, int flags = 0); #ifdef __APPLE__ void OSXStoreOpenFiles(const wxArrayString &files) override; // wxWidgets override to get an event on open files. diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 39676e2d7..d4030d41f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4023,7 +4023,23 @@ void ObjectList::fix_through_netfabb() void ObjectList::simplify() { - GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager(); + auto plater = wxGetApp().plater(); + GLGizmosManager& gizmos_mgr = plater->canvas3D()->get_gizmos_manager(); + + // Do not simplify when a gizmo is open. There might be issues with updates + // and what is worse, the snapshot time would refer to the internal stack. + auto current_type = gizmos_mgr.get_current_type(); + if (current_type == GLGizmosManager::Simplify) { + // close first + gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); + }else if (current_type != GLGizmosManager::Undefined) { + plater->get_notification_manager()->push_notification( + NotificationType::CustomSupportsAndSeamRemovedAfterRepair, + NotificationManager::NotificationLevel::RegularNotification, + _u8L("ERROR: Please close all manipulators available from " + "the left toolbar before start simplify the mesh.")); + return; + } gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 92c03cb75..9cda6d75b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -85,6 +85,7 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u , m_dragging(false) , m_imgui(wxGetApp().imgui()) , m_first_input_window_render(true) + , m_dirty(false) { m_base_color = DEFAULT_BASE_COLOR; m_drag_color = DEFAULT_DRAG_COLOR; @@ -154,6 +155,13 @@ void GLGizmoBase::update(const UpdateData& data) on_update(data); } +bool GLGizmoBase::update_items_state() +{ + bool res = m_dirty; + m_dirty = false; + return res; +}; + std::array GLGizmoBase::picking_color_component(unsigned int id) const { static const float INV_255 = 1.0f / 255.0f; @@ -209,6 +217,10 @@ std::string GLGizmoBase::format(float value, unsigned int decimals) const return Slic3r::string_printf("%.*f", decimals, value); } +void GLGizmoBase::set_dirty() { + m_dirty = true; +} + void GLGizmoBase::render_input_window(float x, float y, float bottom_limit) { on_render_input_window(x, y, bottom_limit); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 2fb9c296a..8b033ce73 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -154,6 +154,9 @@ public: void update(const UpdateData& data); + // returns True when Gizmo changed its state + bool update_items_state(); + void render() { m_tooltip.clear(); on_render(); } void render_for_picking() { on_render_for_picking(); } void render_input_window(float x, float y, float bottom_limit); @@ -187,6 +190,13 @@ protected: void render_grabbers_for_picking(const BoundingBoxf3& box) const; std::string format(float value, unsigned int decimals) const; + + // Mark gizmo as dirty to Re-Render when idle() + void set_dirty(); +private: + // Flag for dirty visible state of Gizmo + // When True then need new rendering + bool m_dirty; }; // Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 694eeadd4..5ce0064db 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -1,34 +1,38 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoSimplify.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/NotificationManager.hpp" +#include "slic3r/GUI/Plater.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/QuadricEdgeCollapse.hpp" -#include -#include - 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) + , m_state(State::settings) + , m_is_valid_result(false) + , m_exist_preview(false) + , m_progress(0) + , m_volume(nullptr) + , m_obj_index(0) + , m_need_reload(false) + + , tr_mesh_name(_u8L("Mesh name")) + , tr_triangles(_u8L("Triangles")) + , tr_preview(_u8L("Preview")) + , tr_detail_level(_u8L("Detail level")) + , tr_decimate_ratio(_u8L("Decimate ratio")) {} GLGizmoSimplify::~GLGizmoSimplify() { - state = State::canceling; - if (worker.joinable()) worker.join(); + m_state = State::canceling; + if (m_worker.joinable()) m_worker.join(); } bool GLGizmoSimplify::on_init() @@ -38,7 +42,6 @@ bool GLGizmoSimplify::on_init() return true; } - std::string GLGizmoSimplify::on_get_name() const { return (_L("Simplify")).ToUTF8().data(); @@ -49,14 +52,7 @@ void GLGizmoSimplify::on_render_for_picking() {} void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { - const int min_triangle_count = 4; // tetrahedron - const int max_char_in_name = 25; create_gui_cfg(); - - int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse; - m_imgui->begin(on_get_name(), flag); - const Selection &selection = m_parent.get_selection(); int object_idx = selection.get_object_idx(); ModelObject *obj = wxGetApp().plater()->model().objects[object_idx]; @@ -64,159 +60,196 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi // Check selection of new volume // Do not reselect object when processing - if (act_volume != volume && state == State::settings) { - obj_index = object_idx; // to remember correct object - volume = act_volume; - original_its = {}; - const TriangleMesh &tm = volume->mesh(); - c.wanted_percent = 50.; // default value - c.update_percent(tm.its.indices.size()); - is_valid_result = false; - // set window position - ImVec2 pos = ImGui::GetMousePos(); - pos.x -= gui_cfg->window_offset; - pos.y -= gui_cfg->window_offset; - ImGui::SetWindowPos(pos, ImGuiCond_Always); + if (act_volume != m_volume && m_state == State::settings) { + bool change_window_position = (m_volume == nullptr); + // select different model + if (m_volume != nullptr && m_original_its.has_value()) { + set_its(*m_original_its); + } + + m_obj_index = object_idx; // to remember correct object + m_volume = act_volume; + m_original_its = {}; + m_configuration.decimate_ratio = 50.; // default value + m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size()); + m_is_valid_result = false; + m_exist_preview = false; + + if (change_window_position) { + ImVec2 pos = ImGui::GetMousePos(); + pos.x -= m_gui_cfg->window_offset_x; + pos.y -= m_gui_cfg->window_offset_y; + // minimal top left value + ImVec2 tl(m_gui_cfg->window_padding, m_gui_cfg->window_padding); + if (pos.x < tl.x) pos.x = tl.x; + if (pos.y < tl.y) pos.y = tl.y; + // maximal bottom right value + auto parent_size = m_parent.get_canvas_size(); + ImVec2 br( + parent_size.get_width() - (2 * m_gui_cfg->window_offset_x + m_gui_cfg->window_padding), + parent_size.get_height() - (2 * m_gui_cfg->window_offset_y + m_gui_cfg->window_padding)); + if (pos.x > br.x) pos.x = br.x; + if (pos.y > br.y) pos.y = br.y; + ImGui::SetNextWindowPos(pos, ImGuiCond_Always); + } } - size_t triangle_count = volume->mesh().its.indices.size(); - // already reduced mesh - if (original_its.has_value()) - triangle_count = original_its->indices.size(); + int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoCollapse; + m_imgui->begin(on_get_name(), flag); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mesh name") + ":"); - ImGui::SameLine(gui_cfg->top_left_width); - std::string name = volume->name; - if (name.length() > max_char_in_name) - name = name.substr(0, max_char_in_name-3) + "..."; + size_t triangle_count = m_volume->mesh().its.indices.size(); + // already reduced mesh + if (m_original_its.has_value()) + triangle_count = m_original_its->indices.size(); + + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_mesh_name + ":"); + ImGui::SameLine(m_gui_cfg->top_left_width); + std::string name = m_volume->name; + if (name.length() > m_gui_cfg->max_char_in_name) + name = name.substr(0, m_gui_cfg->max_char_in_name - 3) + "..."; m_imgui->text(name); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Triangles") + ":"); - ImGui::SameLine(gui_cfg->top_left_width); + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":"); + ImGui::SameLine(m_gui_cfg->top_left_width); m_imgui->text(std::to_string(triangle_count)); + /* + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_preview + ":"); + ImGui::SameLine(m_gui_cfg->top_left_width); + if (m_exist_preview) { + m_imgui->text(std::to_string(m_volume->mesh().its.indices.size())); + } else { + m_imgui->text("---"); + }*/ ImGui::Separator(); - ImGui::Text(_L("Limit by triangles").c_str()); - ImGui::SameLine(gui_cfg->bottom_left_width); - // First initialization + fix triangle count - if (m_imgui->checkbox("##UseCount", c.use_count)) { - if (!c.use_count) c.use_error = true; - is_valid_result = false; + if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) { + m_is_valid_result = false; + m_configuration.use_count = !m_configuration.use_count; + } + ImGui::SameLine(); + m_imgui->disabled_begin(m_configuration.use_count); + ImGui::Text("%s", tr_detail_level.c_str()); + std::vector reduce_captions = { + static_cast(_u8L("Extra high")), + static_cast(_u8L("High")), + static_cast(_u8L("Medium")), + static_cast(_u8L("Low")), + static_cast(_u8L("Extra low")) + }; + ImGui::SameLine(m_gui_cfg->bottom_left_width); + ImGui::SetNextItemWidth(m_gui_cfg->input_width); + static int reduction = 2; + if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) { + m_is_valid_result = false; + if (reduction < 0) reduction = 0; + if (reduction > 4) reduction = 4; + switch (reduction) { + case 0: m_configuration.max_error = 1e-3f; break; + case 1: m_configuration.max_error = 1e-2f; break; + case 2: m_configuration.max_error = 0.1f; break; + case 3: m_configuration.max_error = 0.5f; break; + case 4: m_configuration.max_error = 1.f; break; + } + } + m_imgui->disabled_end(); // !use_count + + if (ImGui::RadioButton("##use_count", m_configuration.use_count)) { + m_is_valid_result = false; + m_configuration.use_count = !m_configuration.use_count; + } + ImGui::SameLine(); + + // show preview result triangle count (percent) + if (m_need_reload && !m_configuration.use_count) { + m_configuration.wanted_count = static_cast(m_volume->mesh().its.indices.size()); + m_configuration.decimate_ratio = + (1.0f - (m_configuration.wanted_count / (float) triangle_count)) * 100.f; } - m_imgui->disabled_begin(!c.use_count); - ImGui::Text(_L("Triangle count").c_str()); - ImGui::SameLine(gui_cfg->bottom_left_width); - int wanted_count = c.wanted_count; - ImGui::SetNextItemWidth(gui_cfg->input_width); - if (ImGui::SliderInt("##triangle_count", &wanted_count, min_triangle_count, triangle_count, "%d")) { - c.wanted_count = static_cast(wanted_count); - if (c.wanted_count < min_triangle_count) - c.wanted_count = min_triangle_count; - if (c.wanted_count > triangle_count) - c.wanted_count = triangle_count; - c.update_count(triangle_count); - is_valid_result = false; + m_imgui->disabled_begin(!m_configuration.use_count); + ImGui::Text("%s", tr_decimate_ratio.c_str()); + ImGui::SameLine(m_gui_cfg->bottom_left_width); + ImGui::SetNextItemWidth(m_gui_cfg->input_width); + const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%": + ((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%"); + if (ImGui::SliderFloat("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) { + m_is_valid_result = false; + if (m_configuration.decimate_ratio < 0.f) + m_configuration.decimate_ratio = 0.01f; + if (m_configuration.decimate_ratio > 100.f) + m_configuration.decimate_ratio = 100.f; + m_configuration.fix_count_by_ratio(triangle_count); } - ImGui::Text(_L("Ratio").c_str()); - ImGui::SameLine(gui_cfg->bottom_left_width); - ImGui::SetNextItemWidth(gui_cfg->input_small_width); - const char * precision = (c.wanted_percent > 10)? "%.0f": ((c.wanted_percent > 1)? "%.1f":"%.2f"); - float step = (c.wanted_percent > 10)? 1.f: ((c.wanted_percent > 1)? 0.1f : 0.01f); - if (ImGui::InputFloat("%", &c.wanted_percent, step, 10*step, precision)) { - if (c.wanted_percent > 100.f) c.wanted_percent = 100.f; - c.update_percent(triangle_count); - if (c.wanted_count < min_triangle_count) { - c.wanted_count = min_triangle_count; - c.update_count(triangle_count); - } - is_valid_result = false; - } - m_imgui->disabled_end(); // use_count ImGui::NewLine(); - ImGui::Text(_L("Limit by error").c_str()); - ImGui::SameLine(gui_cfg->bottom_left_width); - if (m_imgui->checkbox("##UseError", c.use_error)) { - if (!c.use_error) c.use_count = true; - is_valid_result = false; - } + ImGui::SameLine(m_gui_cfg->bottom_left_width); + ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count); + m_imgui->disabled_end(); // use_count - m_imgui->disabled_begin(!c.use_error); - ImGui::Text(_L("Max. error").c_str()); - ImGui::SameLine(gui_cfg->bottom_left_width); - ImGui::SetNextItemWidth(gui_cfg->input_small_width); - if (ImGui::InputFloat("##maxError", &c.max_error, 0.01f, .1f, "%.2f")) { - if (c.max_error < 0.f) c.max_error = 0.f; - is_valid_result = false; - } - m_imgui->disabled_end(); // use_error - - if (state == State::settings) { + if (m_state == State::settings) { if (m_imgui->button(_L("Cancel"))) { - if (original_its.has_value()) { - set_its(*original_its); - state = State::close_on_end; + if (m_original_its.has_value()) { + set_its(*m_original_its); + m_state = State::close_on_end; } else { close(); } } - ImGui::SameLine(gui_cfg->bottom_left_width); + ImGui::SameLine(m_gui_cfg->bottom_left_width); if (m_imgui->button(_L("Preview"))) { - state = State::simplifying; + m_state = State::preview; // simplify but not aply on mesh process(); } ImGui::SameLine(); if (m_imgui->button(_L("Apply"))) { - if (!is_valid_result) { - state = State::close_on_end; + if (!m_is_valid_result) { + m_state = State::close_on_end; process(); } else { // use preview and close + if (m_exist_preview) { + // fix hollowing, sla support points, modifiers, ... + auto plater = wxGetApp().plater(); + plater->changed_mesh(m_obj_index); + } close(); } } } else { - m_imgui->disabled_begin(state == State::canceling); - if (m_imgui->button(_L("Cancel"))) state = State::canceling; + m_imgui->disabled_begin(m_state == State::canceling); + if (m_imgui->button(_L("Cancel"))) m_state = State::canceling; m_imgui->disabled_end(); - ImGui::SameLine(gui_cfg->bottom_left_width); + ImGui::SameLine(m_gui_cfg->bottom_left_width); // draw progress bar char buf[32]; - sprintf(buf, L("Process %d / 100"), progress); - ImGui::ProgressBar(progress / 100., ImVec2(gui_cfg->input_width, 0.f), buf); + sprintf(buf, L("Process %d / 100"), m_progress); + ImGui::ProgressBar(m_progress / 100., ImVec2(m_gui_cfg->input_width, 0.f), buf); } m_imgui->end(); - if (need_reload) { - need_reload = false; - + if (m_need_reload) { + m_need_reload = false; + bool close_on_end = (m_state == State::close_on_end); // 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) { + m_parent.reload_scene(true); + // set m_state must be before close() !!! + m_state = State::settings; + if (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(); + plater->changed_mesh(m_obj_index); + close(); } - - // change from simplifying | aply - state = State::settings; // Fix warning icon in object list - wxGetApp().obj_list()->update_item_error_icon(obj_index, -1); + wxGetApp().obj_list()->update_item_error_icon(m_obj_index, -1); } } void GLGizmoSimplify::close() { - volume = nullptr; - // close gizmo == open it again GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager(); gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); @@ -230,66 +263,60 @@ void GLGizmoSimplify::process() const char* what() const throw() { return L("Model simplification has been canceled"); } }; - if (!original_its.has_value()) - original_its = volume->mesh().its; // copy + if (!m_original_its.has_value()) + m_original_its = m_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([&]() { + plater->take_snapshot(_L("Simplify ") + m_volume->name); + plater->clear_before_change_mesh(m_obj_index); + m_progress = 0; + if (m_worker.joinable()) m_worker.join(); + m_worker = std::thread([this]() { // store original triangles - uint32_t triangle_count = (c.use_count) ? c.wanted_count : 0; - float max_error = (c.use_error) ? c.max_error : std::numeric_limits::max(); + uint32_t triangle_count = (m_configuration.use_count) ? m_configuration.wanted_count : 0; + float max_error = (!m_configuration.use_count) ? m_configuration.max_error : std::numeric_limits::max(); std::function throw_on_cancel = [&]() { - if (state == State::canceling) { + if (m_state == State::canceling) { throw SimplifyCanceledException(); } - }; - std::function statusfn = [&](int percent) { - progress = percent; - m_parent.schedule_extra_frame(0); }; - indexed_triangle_set collapsed; - if (last_error.has_value()) { - // is chance to continue with last reduction - const indexed_triangle_set &its = volume->mesh().its; - uint32_t last_triangle_count = static_cast(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 - } + std::function statusfn = [this](int percent) { + m_progress = percent; + + // check max 4fps + static int64_t last = 0; + int64_t now = m_parent.timestamp_now(); + if ((now - last) < 250) return; + last = now; + + request_rerender(); + }; + + indexed_triangle_set collapsed = *m_original_its; // copy try { its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn); set_its(collapsed); - is_valid_result = true; - last_error = max_error; + m_is_valid_result = true; + m_exist_preview = true; } catch (SimplifyCanceledException &) { - state = State::settings; + // set state out of main thread + m_state = State::settings; } - // need to render last status fn - // without sleep it freezes until mouse move - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - m_parent.schedule_extra_frame(0); + // need to render last status fn to change bar graph to buttons + request_rerender(); }); } 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; + m_volume->set_mesh(std::move(tm)); + m_volume->set_new_unique_id(); + m_volume->get_object()->invalidate_bounding_box(); + m_need_reload = true; } bool GLGizmoSimplify::on_is_activable() const @@ -297,26 +324,62 @@ bool GLGizmoSimplify::on_is_activable() const return !m_parent.get_selection().is_empty(); } -void GLGizmoSimplify::create_gui_cfg() { - if (gui_cfg.has_value()) return; +void GLGizmoSimplify::on_set_state() +{ + // Closing gizmo. e.g. selecting another one + if (GLGizmoBase::m_state == GLGizmoBase::Off) { + // refuse outgoing during simlification + if (m_state != State::settings) { + GLGizmoBase::m_state = GLGizmoBase::On; + auto notification_manager = wxGetApp().plater()->get_notification_manager(); + notification_manager->push_notification( + NotificationType::CustomNotification, + NotificationManager::NotificationLevel::RegularNotification, + _u8L("ERROR: Wait until Simplification ends or Cancel process.")); + return; + } + + // revert preview + if (m_exist_preview) { + set_its(*m_original_its); + m_parent.reload_scene(true); + m_need_reload = false; + } + + // invalidate selected model + m_volume = nullptr; + } else if (GLGizmoBase::m_state == GLGizmoBase::On) { + // when open by hyperlink it needs to show up + request_rerender(); + } +} + +void GLGizmoSimplify::create_gui_cfg() { + if (m_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) + cfg.top_left_width = std::max(m_imgui->calc_text_size(tr_mesh_name).x, + m_imgui->calc_text_size(tr_triangles).x) + space_size; + const float radio_size = ImGui::GetFrameHeight(); 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; + std::max(m_imgui->calc_text_size(tr_detail_level).x, + m_imgui->calc_text_size(tr_decimate_ratio).x) + + space_size + radio_size; + + cfg.input_width = cfg.bottom_left_width * 1.5; + cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2; + cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5; + m_gui_cfg = cfg; +} + +void GLGizmoSimplify::request_rerender() { + wxGetApp().plater()->CallAfter([this]() { + set_dirty(); + m_parent.schedule_extra_frame(0); + }); } } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index bd3637360..04d4b99ab 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -1,35 +1,22 @@ #ifndef slic3r_GLGizmoSimplify_hpp_ #define slic3r_GLGizmoSimplify_hpp_ +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, +// which overrides our localization "L" macro. #include "GLGizmoBase.hpp" -#include "libslic3r/Model.hpp" +#include "admesh/stl.h" // indexed_triangle_set #include #include namespace Slic3r { + +class ModelVolume; + 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; - - 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(); @@ -41,32 +28,51 @@ protected: 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 void on_set_state() override; private: void close(); void process(); void set_its(indexed_triangle_set &its); + void create_gui_cfg(); + void request_rerender(); + + std::atomic_bool m_is_valid_result; // differ what to do in apply + std::atomic_bool m_exist_preview; // set when process end + + volatile int m_progress; // percent of done work + ModelVolume *m_volume; // + size_t m_obj_index; + + std::optional m_original_its; + + volatile bool m_need_reload; // after simplify, glReload must be on main thread + std::thread m_worker; + + enum class State { + settings, + preview, // simplify to show preview + close_on_end, // simplify with close on end + canceling // after button click, before canceled + }; + volatile State m_state; + struct Configuration { - bool use_count = true; + bool use_count = false; // minimal triangle count - float wanted_percent = 50.f; + float decimate_ratio = 50.f; // in percent 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) + void fix_count_by_ratio(size_t triangle_count) { wanted_count = static_cast( - std::round(triangle_count * wanted_percent / 100.f)); + std::round(triangle_count * (100.f-decimate_ratio) / 100.f)); } - } c; + } m_configuration; // This configs holds GUI layout size given by translated texts. // etc. When language changes, GUI is recreated and this class constructed again, @@ -76,11 +82,21 @@ private: int top_left_width = 100; int bottom_left_width = 100; int input_width = 100; - int input_small_width = 80; - int window_offset = 100; + int window_offset_x = 100; + int window_offset_y = 100; + int window_padding = 0; + + // trunc model name when longer + size_t max_char_in_name = 30; }; - std::optional gui_cfg; - void create_gui_cfg(); + std::optional m_gui_cfg; + + // translations used for calc window size + const std::string tr_mesh_name; + const std::string tr_triangles; + const std::string tr_preview; + const std::string tr_detail_level; + const std::string tr_decimate_ratio; }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 5617b5ed8..6d9a03977 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -132,8 +132,7 @@ bool GLGizmosManager::init_arrow(const BackgroundTexture::Metadata& arrow_textur bool res = false; if (!arrow_texture.filename.empty()) - res = m_arrow_texture.texture.load_from_file(path + arrow_texture.filename, false, GLTexture::SingleThreaded, false); -// res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, true, false, 100); + res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, false, false, 1000); if (res) m_arrow_texture.metadata = arrow_texture; @@ -1019,7 +1018,7 @@ void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_t float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width(); - GLTexture::render_sub_texture(tex_id, zoomed_top_x + zoomed_icons_size * 1.2f, zoomed_top_x + zoomed_icons_size * 1.2f + zoomed_icons_size * arrow_sides_ratio, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { internal_left_uv, internal_bottom_uv }, { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, internal_bottom_uv } }); + GLTexture::render_sub_texture(tex_id, zoomed_top_x + zoomed_icons_size * 1.2f, zoomed_top_x + zoomed_icons_size * 1.2f + zoomed_icons_size * 2.2f * arrow_sides_ratio, zoomed_top_y - zoomed_icons_size * 1.6f , zoomed_top_y + zoomed_icons_size * 0.6f, { { internal_left_uv, internal_bottom_uv }, { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, internal_bottom_uv } }); break; } zoomed_top_y -= zoomed_stride_y; diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index 1d5931c48..ead7a8f29 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -236,8 +236,7 @@ bool tags_check(const std::string& disabled_tags, const std::string& enabled_tag } void launch_browser_if_allowed(const std::string& url) { - if (wxGetApp().app_config->get("suppress_hyperlinks") != "1") - wxLaunchDefaultBrowser(url); + wxGetApp().open_browser_with_warning_dialog(url); } } //namespace HintDatabase::~HintDatabase() diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 498b02605..329a9a62a 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1057,7 +1057,7 @@ static wxMenu* generate_help_menu() append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"), [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); }); append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"), - [](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); }); + [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); }); //# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ //# wxTheApp->check_version(1); //# }); @@ -1067,14 +1067,14 @@ static wxMenu* generate_help_menu() [](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); }); // append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("%s &Manual"), SLIC3R_APP_NAME), // wxString::Format(_L("Open the %s manual in your browser"), SLIC3R_APP_NAME), -// [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://manual.slic3r.org/"); }); +// [this](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("http://manual.slic3r.org/"); }); helpMenu->AppendSeparator(); append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"), [](wxCommandEvent&) { wxGetApp().system_info(); }); append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"), [](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME), - [](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); }); + [](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/slic3r/issues/new"); }); if (wxGetApp().is_editor()) append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"), [](wxCommandEvent&) { Slic3r::GUI::about(); }); diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 4c1c404da..3278a5a6e 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -42,7 +42,7 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat } }, {NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { - wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, + wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, {NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10, _u8L("You have just added a G-code for color change, but its value is empty.\n" "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 2be8ba26b..038290f82 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -96,7 +96,9 @@ enum class NotificationType DidYouKnowHint, // Shows when ObjectList::update_info_items finds information that should be stressed to the user // Might contain logo taken from gizmos - UpdatedItemsInfo + UpdatedItemsInfo, + // Give user advice to simplify object with big amount of triangles + SimplifySuggestion }; class NotificationManager @@ -538,7 +540,13 @@ private: // Timestamp of last rendering int64_t m_last_render { 0LL }; // Notification types that can be shown multiple types at once (compared by text) - const std::vector m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload }; + const std::vector m_multiple_types = { + NotificationType::CustomNotification, + NotificationType::PlaterWarning, + NotificationType::ProgressBar, + NotificationType::PrintHostUpload, + NotificationType::SimplifySuggestion + }; //prepared (basic) notifications static const NotificationData basic_notifications[]; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 72b2c5909..1841220d5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1721,7 +1721,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 create_simplify_notification(const std::vector& obj_ids); void set_current_panel(wxPanel* panel); void on_select_preset(wxCommandEvent&); @@ -2478,6 +2478,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ view3D->get_canvas3d()->update_gizmos_on_off_state(); } + create_simplify_notification(obj_idxs); + return obj_idxs; } @@ -3540,6 +3542,53 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = q->SetFocus(); } +void Plater::priv::create_simplify_notification(const std::vector& obj_ids) { + const uint32_t triangles_to_suggest_simplify = 1000000; + + std::vector big_ids; + big_ids.reserve(obj_ids.size()); + std::copy_if(obj_ids.begin(), obj_ids.end(), std::back_inserter(big_ids), + [this, triangles_to_suggest_simplify](size_t object_id) { + if (object_id >= model.objects.size()) return false; // out of object index + ModelVolumePtrs& volumes = model.objects[object_id]->volumes; + if (volumes.size() != 1) return false; // not only one volume + size_t triangle_count = volumes.front()->mesh().its.indices.size(); + if (triangle_count < triangles_to_suggest_simplify) return false; // small volume + return true; + }); + + if (big_ids.empty()) return; + + for (size_t object_id : big_ids) { + std::string t = _u8L( + "Processing model '@object_name' with more than 1M triangles " + "could be slow. It is highly recommend to reduce " + "amount of triangles."); + t.replace(t.find("@object_name"), sizeof("@object_name") - 1, + model.objects[object_id]->name); + std::stringstream text; + text << _u8L("WARNING:") << "\n" << t << "\n"; + std::string hypertext = _u8L("Simplify model"); + + std::function open_simplify = [object_id](wxEvtHandler *) { + auto plater = wxGetApp().plater(); + if (object_id >= plater->model().objects.size()) return true; + + Selection &selection = plater->canvas3D()->get_selection(); + selection.clear(); + selection.add_object((unsigned int) object_id); + + auto &manager = plater->canvas3D()->get_gizmos_manager(); + manager.open_gizmo(GLGizmosManager::EType::Simplify); + return true; + }; + notification_manager->push_notification( + NotificationType::SimplifySuggestion, + NotificationManager::NotificationLevel::WarningNotification, + text.str(), hypertext, open_simplify); + } +} + void Plater::priv::set_current_panel(wxPanel* panel) { if (std::find(panels.begin(), panels.end(), panel) == panels.end()) @@ -4316,6 +4365,12 @@ bool Plater::priv::can_fix_through_netfabb() const bool Plater::priv::can_simplify() const { + // is object for simplification selected + if (get_selected_object_idx() < 0) return false; + // is already opened? + if (q->canvas3D()->get_gizmos_manager().get_current_type() == + GLGizmosManager::EType::Simplify) + return false; return true; } diff --git a/tests/data/simplification.obj b/tests/data/simplification.obj new file mode 100644 index 000000000..f509e695c --- /dev/null +++ b/tests/data/simplification.obj @@ -0,0 +1,46 @@ +v 39.349007 -54.069000 -199.819000 +v 39.489006 -54.029007 -199.815002 +v 39.419006 -53.993011 -199.769012 +v 39.629005 -53.975006 -199.815002 +v 39.639008 -53.947006 -199.805023 +v 39.651001 -53.919006 -199.795013 +v 39.807007 -53.863007 -199.796997 +v 39.729004 -53.891006 -199.796997 +v 39.727005 -53.935013 -199.813019 +v 39.767006 -53.899002 -199.805023 +v 39.871002 -53.835007 -199.801025 +v 39.443001 -53.829010 -199.878998 +v 39.523003 -53.965012 -199.827026 +v 39.807007 -53.863007 -199.796997 +v 39.833008 -53.723007 -199.723022 +v 39.759003 -53.822998 -199.822998 +v 39.867004 -53.845001 -199.805023 +v 39.937004 -53.805008 -199.805023 +f 1 2 3 +f 4 5 2 +f 2 6 3 +f 7 8 4 +f 9 10 4 +f 10 9 11 +f 12 2 1 +f 13 6 4 +f 13 4 2 +f 8 7 9 +f 6 9 4 +f 6 14 15 +f 16 14 6 +f 17 18 9 +f 3 6 15 +f 12 16 6 +f 12 6 13 +f 12 13 2 +f 5 4 8 +f 6 8 9 +f 5 6 2 +f 6 5 8 +f 17 9 7 +f 7 11 17 +f 18 11 9 +f 11 18 17 +f 10 7 4 +f 7 10 11 diff --git a/tests/libslic3r/test_indexed_triangle_set.cpp b/tests/libslic3r/test_indexed_triangle_set.cpp index b640d8410..a3996e651 100644 --- a/tests/libslic3r/test_indexed_triangle_set.cpp +++ b/tests/libslic3r/test_indexed_triangle_set.cpp @@ -165,29 +165,51 @@ std::vector its_sample_surface(const indexed_triangle_set &its, #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) +struct CompareConfig +{ + float max_distance = 3.f; + float max_average_distance = 2.f; +}; + +bool is_similar(const indexed_triangle_set &from, + const indexed_triangle_set &to, + const CompareConfig &cfg) { // create ABBTree auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( - original.vertices, original.indices); + from.vertices, from.indices); + float sum_distance = 0.f; + float max_distance = 0.f; - 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) { + auto collect_distances = [&](const Vec3f &surface_point) { 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); + float distance2 = + AABBTreeIndirect::squared_distance_to_indexed_triangle_set( + from.vertices, from.indices, tree, surface_point, hit_idx, hit_point); + float distance = sqrt(distance2); + if (max_distance < distance) max_distance = distance; + sum_distance += distance; + }; + + for (const Vec3f &vertex : to.vertices) { + collect_distances(vertex); } - return sumDistance / samples.size(); + + for (const Vec3i &t : to.indices) { + Vec3f center(0,0,0); + for (size_t i = 0; i < 3; ++i) { + center += to.vertices[t[i]] / 3; + } + collect_distances(center); + } + + size_t count = to.vertices.size() + to.indices.size(); + float avg_distance = sum_distance / count; + if (avg_distance > cfg.max_average_distance || + max_distance > cfg.max_distance) + return false; + return true; } TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]") @@ -226,8 +248,12 @@ 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); + CompareConfig cfg; + cfg.max_average_distance = 0.014f; + cfg.max_distance = 0.75f; + + CHECK(is_similar(its, its_, cfg)); + CHECK(is_similar(its_, its, cfg)); } #include "test_utils.hpp" @@ -244,6 +270,21 @@ TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]") CHECK(its.indices.size() <= wanted_count); double volume = its_volume(its); CHECK(fabs(original_volume - volume) < 33.); - float avg_distance = compare(mesh.its, its, 10); - CHECK(avg_distance < 0.022f); // 0.02022 | 0.0199614074 -} \ No newline at end of file + + CompareConfig cfg; + cfg.max_average_distance = 0.043f; + cfg.max_distance = 0.32f; + + CHECK(is_similar(mesh.its, its, cfg)); + CHECK(is_similar(its, mesh.its, cfg)); +} + +TEST_CASE("Simplify trouble case", "[its]") +{ + TriangleMesh tm = load_model("simplification.obj"); + REQUIRE_FALSE(tm.empty()); + float max_error = std::numeric_limits::max(); + uint32_t wanted_count = 8; + its_quadric_edge_collapse(tm.its, wanted_count, &max_error); + CHECK(tm.its.indices.size() <= 8); +}