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 @@
-
-
+
+
\ 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);
+}