Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer
This commit is contained in:
commit
f27448f322
@ -77,7 +77,7 @@ IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepLineBreaksForNonEmptyLines: false
|
||||
#KeepLineBreaksForNonEmptyLines: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -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/*
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.9 KiB |
@ -1,76 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="toolbar_arrow.svg"
|
||||
xml:space="preserve"
|
||||
style="enable-background:new 0 0 330 330;"
|
||||
viewBox="0 0 330 330"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"><metadata
|
||||
id="metadata21"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs19" /><sodipodi:namedview
|
||||
inkscape:current-layer="Layer_1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:cy="165"
|
||||
inkscape:cx="165"
|
||||
inkscape:zoom="3.0545455"
|
||||
showgrid="false"
|
||||
id="namedview17"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<style
|
||||
id="style2"
|
||||
type="text/css">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{display:none;}
|
||||
.st1{fill:none;stroke:#ED6B21;stroke-width:17.0079;stroke-linecap:round;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<path
|
||||
d="M180,315V51.2l49.4,49.4c5.9,5.9,15.4,5.9,21.2,0c5.9-5.9,5.9-15.4,0-21.2l-75-75 c-5.9-5.9-15.4-5.9-21.2,0l-75,75C76.5,82.3,75,86.2,75,90s1.5,7.7,4.4,10.6c5.9,5.9,15.4,5.9,21.2,0L150,51.2V315 c0,8.3,6.7,15,15,15S180,323.3,180,315z"
|
||||
class="st0"
|
||||
id="XMLID_28_" />
|
||||
<g
|
||||
id="XMLID_1_">
|
||||
<g
|
||||
id="g5">
|
||||
<path id="XMLID_28_" class="st0" d="M180,315V51.2l49.4,49.4c5.9,5.9,15.4,5.9,21.2,0c5.9-5.9,5.9-15.4,0-21.2l-75-75
|
||||
c-5.9-5.9-15.4-5.9-21.2,0l-75,75C76.5,82.3,75,86.2,75,90s1.5,7.7,4.4,10.6c5.9,5.9,15.4,5.9,21.2,0L150,51.2V315
|
||||
c0,8.3,6.7,15,15,15S180,323.3,180,315z"
|
||||
style="fill:#ed6b21;"/>
|
||||
<g id="XMLID_1_">
|
||||
<g>
|
||||
</g>
|
||||
<g
|
||||
id="g11">
|
||||
<polyline
|
||||
id="polyline7"
|
||||
points="113.6,84.5 164.3,18.3 164.3,18.3 "
|
||||
class="st1" />
|
||||
<polyline
|
||||
id="polyline9"
|
||||
points="216.4,84.5 164.3,18.3 164.3,18.3 "
|
||||
class="st1" />
|
||||
<g>
|
||||
<polyline class="st1" points="113.6,84.5 164.3,18.3 164.3,18.3 "/>
|
||||
<polyline class="st1" points="216.4,84.5 164.3,18.3 164.3,18.3 "/>
|
||||
</g>
|
||||
</g>
|
||||
<line
|
||||
id="line14"
|
||||
y2="18.3"
|
||||
x2="164.3"
|
||||
y1="263.3"
|
||||
x1="164.3"
|
||||
class="st1" />
|
||||
<line class="st1" x1="164.3" y1="263.3" x2="164.3" y2="18.3"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.1 KiB |
@ -118,6 +118,11 @@ void triangle_mesh_to_cgal(const std::vector<stl_vertex> & 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()});
|
||||
|
||||
|
@ -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<stl_vertex>;
|
||||
using Triangle = stl_triangle_vertex_indices;
|
||||
using Indices = std::vector<stl_triangle_vertex_indices>;
|
||||
using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>;
|
||||
|
||||
using ThrowOnCancel = std::function<void(void)>;
|
||||
using StatusFn = std::function<void(int)>;
|
||||
// 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<TriangleInfos, VertexInfos, EdgeInfos, Errors> init(const indexed_triangle_set &its);
|
||||
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors>
|
||||
init(const indexed_triangle_set &its, ThrowOnCancel& throw_on_cancel, StatusFn& status_fn);
|
||||
std::optional<uint32_t> find_triangle_index1(uint32_t vi, const VertexInfo& v_info,
|
||||
uint32_t ti, const EdgeInfos& e_infos, const Indices& indices);
|
||||
bool is_flipped(const Vec3f &new_vertex, uint32_t ti0, uint32_t ti1, const VertexInfo& v_info,
|
||||
const TriangleInfos &t_infos, const EdgeInfos &e_infos, const indexed_triangle_set &its);
|
||||
|
||||
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<void(void)> throw_on_cancel,
|
||||
std::function<void(int)> statusfn)
|
||||
std::function<void(int)> 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<float>::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<size_t> 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<int>(std::round(status)));
|
||||
status_fn(static_cast<int>(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
|
||||
@ -237,7 +257,6 @@ void Slic3r::its_quadric_edge_collapse(
|
||||
v_info0.q = q;
|
||||
|
||||
// fix neighbors
|
||||
|
||||
// vertex index of triangle 0 which is not vi0 nor vi1
|
||||
uint32_t vi_top0 = t0[(t_info0.min_index + 2) % 3];
|
||||
const Triangle &t1 = its.indices[ti1];
|
||||
@ -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<TriangleInfos, VertexInfos, EdgeInfos, Errors>
|
||||
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<float>();
|
||||
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<size_t>(0, its.indices.size()),
|
||||
@ -410,9 +452,16 @@ 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);
|
||||
for (size_t i = 0; i < its.indices.size(); i++) {
|
||||
@ -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<uint32_t>(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<size_t> triangles;
|
||||
// triangle index, depth
|
||||
using Item = std::pair<size_t, int>;
|
||||
std::queue<Item> 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<size_t> 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 */
|
||||
|
@ -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<size_t> & triangles)
|
||||
{
|
||||
indexed_triangle_set its2;
|
||||
its2.vertices.reserve(triangles.size() * 3);
|
||||
its2.indices.reserve(triangles.size());
|
||||
std::map<size_t, size_t> 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();
|
||||
|
@ -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<size_t>& triangles);
|
||||
|
||||
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its);
|
||||
|
||||
bool its_is_splittable(const indexed_triangle_set &its);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,9 +2782,8 @@ 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
|
||||
|
||||
// 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;
|
||||
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;
|
||||
|
@ -464,14 +464,12 @@ private:
|
||||
// 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<int>::max() };
|
||||
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;
|
||||
|
@ -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,15 +1175,18 @@ 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();
|
||||
|
@ -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) = @_;
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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<float, 4> 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);
|
||||
|
@ -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
|
||||
|
@ -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 <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
|
||||
const std::string &icon_filename,
|
||||
unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, -1)
|
||||
, state(State::settings)
|
||||
, is_valid_result(false)
|
||||
, progress(0)
|
||||
, volume(nullptr)
|
||||
, obj_index(0)
|
||||
, need_reload(false)
|
||||
, 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);
|
||||
}
|
||||
|
||||
size_t triangle_count = volume->mesh().its.indices.size();
|
||||
// already reduced mesh
|
||||
if (original_its.has_value())
|
||||
triangle_count = original_its->indices.size();
|
||||
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;
|
||||
|
||||
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) + "...";
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoCollapse;
|
||||
m_imgui->begin(on_get_name(), flag);
|
||||
|
||||
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<std::string> reduce_captions = {
|
||||
static_cast<std::string>(_u8L("Extra high")),
|
||||
static_cast<std::string>(_u8L("High")),
|
||||
static_cast<std::string>(_u8L("Medium")),
|
||||
static_cast<std::string>(_u8L("Low")),
|
||||
static_cast<std::string>(_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<uint32_t>(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<uint32_t>(wanted_count);
|
||||
if (c.wanted_count < min_triangle_count)
|
||||
c.wanted_count = min_triangle_count;
|
||||
if (c.wanted_count > triangle_count)
|
||||
c.wanted_count = triangle_count;
|
||||
c.update_count(triangle_count);
|
||||
is_valid_result = false;
|
||||
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<float>::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<float>::max();
|
||||
|
||||
std::function<void(void)> throw_on_cancel = [&]() {
|
||||
if (state == State::canceling) {
|
||||
if (m_state == State::canceling) {
|
||||
throw SimplifyCanceledException();
|
||||
}
|
||||
};
|
||||
std::function<void(int)> statusfn = [&](int percent) {
|
||||
progress = percent;
|
||||
m_parent.schedule_extra_frame(0);
|
||||
|
||||
std::function<void(int)> 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;
|
||||
if (last_error.has_value()) {
|
||||
// is chance to continue with last reduction
|
||||
const indexed_triangle_set &its = volume->mesh().its;
|
||||
uint32_t last_triangle_count = static_cast<uint32_t>(its.indices.size());
|
||||
if ((!c.use_count || triangle_count <= last_triangle_count) &&
|
||||
(!c.use_error || c.max_error <= *last_error)) {
|
||||
collapsed = its; // small copy
|
||||
} else {
|
||||
collapsed = *original_its; // copy
|
||||
}
|
||||
} else {
|
||||
collapsed = *original_its; // copy
|
||||
}
|
||||
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<TriangleMesh>(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
|
||||
|
@ -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 <thread>
|
||||
#include <optional>
|
||||
|
||||
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<indexed_triangle_set> original_its;
|
||||
|
||||
std::optional<float> last_error; // for use previous reduction
|
||||
|
||||
bool need_reload; // after simplify, glReload must be on main thread
|
||||
std::thread worker;
|
||||
public:
|
||||
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
virtual ~GLGizmoSimplify();
|
||||
@ -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<indexed_triangle_set> 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<uint32_t>(
|
||||
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<GuiCfg> gui_cfg;
|
||||
void create_gui_cfg();
|
||||
std::optional<GuiCfg> 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
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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(); });
|
||||
|
@ -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\"") },
|
||||
|
@ -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<NotificationType> m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload };
|
||||
const std::vector<NotificationType> m_multiple_types = {
|
||||
NotificationType::CustomNotification,
|
||||
NotificationType::PlaterWarning,
|
||||
NotificationType::ProgressBar,
|
||||
NotificationType::PrintHostUpload,
|
||||
NotificationType::SimplifySuggestion
|
||||
};
|
||||
//prepared (basic) notifications
|
||||
static const NotificationData basic_notifications[];
|
||||
};
|
||||
|
@ -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<size_t>& obj_ids);
|
||||
void set_current_panel(wxPanel* panel);
|
||||
|
||||
void on_select_preset(wxCommandEvent&);
|
||||
@ -2478,6 +2478,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& 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<size_t>& obj_ids) {
|
||||
const uint32_t triangles_to_suggest_simplify = 1000000;
|
||||
|
||||
std::vector<size_t> 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<bool(wxEvtHandler *)> 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;
|
||||
}
|
||||
|
||||
|
46
tests/data/simplification.obj
Normal file
46
tests/data/simplification.obj
Normal file
@ -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
|
@ -165,29 +165,51 @@ std::vector<Vec3f> 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
|
||||
|
||||
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<float>::max();
|
||||
uint32_t wanted_count = 8;
|
||||
its_quadric_edge_collapse(tm.its, wanted_count, &max_error);
|
||||
CHECK(tm.its.indices.size() <= 8);
|
||||
}
|
Loading…
Reference in New Issue
Block a user