Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer
This commit is contained in:
commit
f27448f322
@ -77,7 +77,7 @@ IndentWidth: 4
|
|||||||
IndentWrappedFunctionNames: false
|
IndentWrappedFunctionNames: false
|
||||||
JavaScriptQuotes: Leave
|
JavaScriptQuotes: Leave
|
||||||
JavaScriptWrapImports: true
|
JavaScriptWrapImports: true
|
||||||
KeepLineBreaksForNonEmptyLines: false
|
#KeepLineBreaksForNonEmptyLines: false
|
||||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
MacroBlockBegin: ''
|
MacroBlockBegin: ''
|
||||||
MacroBlockEnd: ''
|
MacroBlockEnd: ''
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -11,6 +11,9 @@ MANIFEST.bak
|
|||||||
xs/MANIFEST.bak
|
xs/MANIFEST.bak
|
||||||
xs/assertlib*
|
xs/assertlib*
|
||||||
.init_bundle.ini
|
.init_bundle.ini
|
||||||
|
.vs/*
|
||||||
local-lib
|
local-lib
|
||||||
/src/TAGS
|
/src/TAGS
|
||||||
/.vscode/
|
/.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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg
|
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
<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"
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
<style type="text/css">
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
.st0{display:none;}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
.st1{fill:none;stroke:#ED6B21;stroke-width:17.0079;stroke-linecap:round;stroke-miterlimit:10;}
|
||||||
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">
|
|
||||||
.st0{display:none;}
|
|
||||||
.st1{fill:none;stroke:#ED6B21;stroke-width:17.0079;stroke-linecap:round;stroke-miterlimit:10;}
|
|
||||||
</style>
|
</style>
|
||||||
<path
|
<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
|
||||||
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"
|
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
|
||||||
class="st0"
|
c0,8.3,6.7,15,15,15S180,323.3,180,315z"
|
||||||
id="XMLID_28_" />
|
style="fill:#ed6b21;"/>
|
||||||
<g
|
<g id="XMLID_1_">
|
||||||
id="XMLID_1_">
|
<g>
|
||||||
<g
|
</g>
|
||||||
id="g5">
|
<g>
|
||||||
</g>
|
<polyline class="st1" points="113.6,84.5 164.3,18.3 164.3,18.3 "/>
|
||||||
<g
|
<polyline class="st1" points="216.4,84.5 164.3,18.3 164.3,18.3 "/>
|
||||||
id="g11">
|
</g>
|
||||||
<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>
|
|
||||||
</g>
|
</g>
|
||||||
<line
|
<line class="st1" x1="164.3" y1="263.3" x2="164.3" y2="18.3"/>
|
||||||
id="line14"
|
</svg>
|
||||||
y2="18.3"
|
|
||||||
x2="164.3"
|
|
||||||
y1="263.3"
|
|
||||||
x1="164.3"
|
|
||||||
class="st1" />
|
|
||||||
</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;
|
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)
|
for (auto &v : V)
|
||||||
out.add_vertex(typename _Mesh::Point{v.x(), v.y(), v.z()});
|
out.add_vertex(typename _Mesh::Point{v.x(), v.y(), v.z()});
|
||||||
|
|
||||||
|
@ -7,13 +7,14 @@
|
|||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
// only private namespace not neccessary be in hpp
|
// only private namespace not neccessary be in .hpp
|
||||||
namespace QuadricEdgeCollapse {
|
namespace QuadricEdgeCollapse {
|
||||||
using Vertices = std::vector<stl_vertex>;
|
using Vertices = std::vector<stl_vertex>;
|
||||||
using Triangle = stl_triangle_vertex_indices;
|
using Triangle = stl_triangle_vertex_indices;
|
||||||
using Indices = std::vector<stl_triangle_vertex_indices>;
|
using Indices = std::vector<stl_triangle_vertex_indices>;
|
||||||
using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>;
|
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
|
// smallest error caused by edges, identify smallest edge in triangle
|
||||||
struct Error
|
struct Error
|
||||||
{
|
{
|
||||||
@ -74,12 +75,14 @@ namespace QuadricEdgeCollapse {
|
|||||||
// calculate error for vertex and quadrics, triangle quadrics and triangle vertex give zero, only pozitive number
|
// 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);
|
double vertex_error(const SymMat &q, const Vec3d &vertex);
|
||||||
SymMat create_quadric(const Triangle &t, const Vec3d& n, const Vertices &vertices);
|
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,
|
std::optional<uint32_t> find_triangle_index1(uint32_t vi, const VertexInfo& v_info,
|
||||||
uint32_t ti, const EdgeInfos& e_infos, const Indices& indices);
|
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,
|
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);
|
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
|
// find edge with smallest error in triangle
|
||||||
Vec3d calculate_3errors(const Triangle &t, const Vertices &vertices, const VertexInfos &v_infos);
|
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);
|
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,
|
uint32_t vi0, uint32_t vi1, uint32_t vi_top0,
|
||||||
const Triangle &t1, CopyEdgeInfos& infos, EdgeInfos &e_infos1);
|
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);
|
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
|
} // namespace QuadricEdgeCollapse
|
||||||
|
|
||||||
using namespace QuadricEdgeCollapse;
|
using namespace QuadricEdgeCollapse;
|
||||||
@ -97,7 +108,7 @@ void Slic3r::its_quadric_edge_collapse(
|
|||||||
uint32_t triangle_count,
|
uint32_t triangle_count,
|
||||||
float * max_error,
|
float * max_error,
|
||||||
std::function<void(void)> throw_on_cancel,
|
std::function<void(void)> throw_on_cancel,
|
||||||
std::function<void(int)> statusfn)
|
std::function<void(int)> status_fn)
|
||||||
{
|
{
|
||||||
// constants --> may be move to config
|
// constants --> may be move to config
|
||||||
const int status_init_size = 10; // in percents
|
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;
|
float maximal_error = (max_error == nullptr)? std::numeric_limits<float>::max() : *max_error;
|
||||||
if (maximal_error <= 0.f) return;
|
if (maximal_error <= 0.f) return;
|
||||||
if (throw_on_cancel == nullptr) throw_on_cancel = []() {};
|
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
|
TriangleInfos t_infos; // only normals with information about deleted triangle
|
||||||
VertexInfos v_infos;
|
VertexInfos v_infos;
|
||||||
EdgeInfos e_infos;
|
EdgeInfos e_infos;
|
||||||
Errors errors;
|
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();
|
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
|
// convert from triangle index to mutable priority queue index
|
||||||
std::vector<size_t> ti_2_mpqi(its.indices.size(), {0});
|
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) count_triangle_to_reduce;
|
||||||
double status = status_init_size + (100 - status_init_size) *
|
double status = status_init_size + (100 - status_init_size) *
|
||||||
(1. - reduced);
|
(1. - reduced);
|
||||||
statusfn(static_cast<int>(std::round(status)));
|
status_fn(static_cast<int>(std::round(status)));
|
||||||
};
|
};
|
||||||
// modulo for update status
|
// modulo for update status
|
||||||
uint32_t status_mod = std::max(uint32_t(16), count_triangle_to_reduce / 100);
|
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(vi1, v_info0, ti0, e_infos, its.indices) :
|
||||||
find_triangle_index1(vi0, v_info1, 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
|
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_info0, t_infos, e_infos, its) ||
|
||||||
is_flipped(new_vertex0, ti0, *ti1_opt, v_info1, t_infos, e_infos, its)) {
|
is_flipped(new_vertex0, ti0, *ti1_opt, v_info1, t_infos, e_infos, its)) {
|
||||||
// try other triangle's edge
|
// try other triangle's edge
|
||||||
@ -236,8 +256,7 @@ void Slic3r::its_quadric_edge_collapse(
|
|||||||
}
|
}
|
||||||
v_info0.q = q;
|
v_info0.q = q;
|
||||||
|
|
||||||
// fix neighbors
|
// fix neighbors
|
||||||
|
|
||||||
// vertex index of triangle 0 which is not vi0 nor vi1
|
// vertex index of triangle 0 which is not vi0 nor vi1
|
||||||
uint32_t vi_top0 = t0[(t_info0.min_index + 2) % 3];
|
uint32_t vi_top0 = t0[(t_info0.min_index + 2) % 3];
|
||||||
const Triangle &t1 = its.indices[ti1];
|
const Triangle &t1 = its.indices[ti1];
|
||||||
@ -263,6 +282,7 @@ void Slic3r::its_quadric_edge_collapse(
|
|||||||
t_info1.set_deleted();
|
t_info1.set_deleted();
|
||||||
// triangle counter decrementation
|
// triangle counter decrementation
|
||||||
actual_triangle_count-=2;
|
actual_triangle_count-=2;
|
||||||
|
assert(check_neighbors(its, t_infos, v_infos, e_infos));
|
||||||
}
|
}
|
||||||
|
|
||||||
// compact triangle
|
// compact triangle
|
||||||
@ -362,8 +382,16 @@ SymMat QuadricEdgeCollapse::create_quadric(const Triangle &t,
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors>
|
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());
|
TriangleInfos t_infos(its.indices.size());
|
||||||
VertexInfos v_infos(its.vertices.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);
|
Vec3d normal = create_normal(t, its.vertices);
|
||||||
t_info.n = normal.cast<float>();
|
t_info.n = normal.cast<float>();
|
||||||
triangle_quadrics[i] = create_quadric(t, normal, its.vertices);
|
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
|
}); // END parallel for
|
||||||
|
status_offset += status_normal_size;
|
||||||
|
|
||||||
// sum quadrics
|
// sum quadrics
|
||||||
for (size_t i = 0; i < its.indices.size(); i++) {
|
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.q += q;
|
||||||
++v_info.count; // triangle count
|
++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
|
} // remove triangle quadrics
|
||||||
|
|
||||||
// set offseted starts
|
// set offseted starts
|
||||||
@ -402,6 +440,10 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its)
|
|||||||
}
|
}
|
||||||
assert(its.indices.size() * 3 == triangle_start);
|
assert(its.indices.size() * 3 == triangle_start);
|
||||||
|
|
||||||
|
status_offset += status_set_offsets;
|
||||||
|
throw_on_cancel();
|
||||||
|
status_fn(status_offset);
|
||||||
|
|
||||||
// calc error
|
// calc error
|
||||||
Errors errors(its.indices.size());
|
Errors errors(its.indices.size());
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
|
||||||
@ -410,8 +452,15 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its)
|
|||||||
const Triangle &t = its.indices[i];
|
const Triangle &t = its.indices[i];
|
||||||
TriangleInfo & t_info = t_infos[i];
|
TriangleInfo & t_info = t_infos[i];
|
||||||
errors[i] = calculate_error(i, t, its.vertices, v_infos, t_info.min_index);
|
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
|
}); // END parallel for
|
||||||
|
|
||||||
|
status_offset += status_calc_errors;
|
||||||
|
|
||||||
// create reference
|
// create reference
|
||||||
EdgeInfos e_infos(its.indices.size() * 3);
|
EdgeInfos e_infos(its.indices.size() * 3);
|
||||||
@ -426,7 +475,14 @@ QuadricEdgeCollapse::init(const indexed_triangle_set &its)
|
|||||||
e_info.edge = j;
|
e_info.edge = j;
|
||||||
++v_info.count;
|
++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};
|
return {t_infos, v_infos, e_infos, errors};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,6 +545,28 @@ bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex,
|
|||||||
return false;
|
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,
|
Vec3d QuadricEdgeCollapse::calculate_3errors(const Triangle & t,
|
||||||
const Vertices & vertices,
|
const Vertices & vertices,
|
||||||
const VertexInfos &v_infos)
|
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());
|
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;
|
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)
|
void its_shrink_to_fit(indexed_triangle_set &its)
|
||||||
{
|
{
|
||||||
its.indices.shrink_to_fit();
|
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.
|
// 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);
|
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);
|
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its);
|
||||||
|
|
||||||
bool its_is_splittable(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)
|
void CopyrightsDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
||||||
{
|
{
|
||||||
wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
|
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
|
||||||
event.Skip(false);
|
event.Skip(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,7 +344,7 @@ void AboutDialog::on_dpi_changed(const wxRect &suggested_rect)
|
|||||||
|
|
||||||
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
||||||
{
|
{
|
||||||
wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
|
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
|
||||||
event.Skip(false);
|
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());
|
bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
|
||||||
m_dirty |= mouse3d_controller_applied;
|
m_dirty |= mouse3d_controller_applied;
|
||||||
m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
|
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)
|
if (!m_dirty)
|
||||||
return;
|
return;
|
||||||
@ -2780,11 +2782,10 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt)
|
|||||||
|
|
||||||
void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
|
void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
|
||||||
{
|
{
|
||||||
// no need to do anything here
|
// no need to wake up idle
|
||||||
// right after this event is recieved, idle event is fired
|
// right after this event, idle event is fired
|
||||||
|
// m_dirty = true;
|
||||||
//m_dirty = true;
|
// wxWakeUpIdle();
|
||||||
//wxWakeUpIdle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2802,21 +2803,15 @@ void GLCanvas3D::schedule_extra_frame(int miliseconds)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Start timer
|
int remaining_time = m_render_timer.GetInterval();
|
||||||
int64_t now = timestamp_now();
|
|
||||||
// Timer is not running
|
// Timer is not running
|
||||||
if (! m_render_timer.IsRunning()) {
|
if (!m_render_timer.IsRunning()) {
|
||||||
m_extra_frame_requested_delayed = miliseconds;
|
|
||||||
m_render_timer.StartOnce(miliseconds);
|
m_render_timer.StartOnce(miliseconds);
|
||||||
m_render_timer_start = now;
|
|
||||||
// Timer is running - restart only if new period is shorter than remaning period
|
// Timer is running - restart only if new period is shorter than remaning period
|
||||||
} else {
|
} else {
|
||||||
const int64_t remaining_time = (m_render_timer_start + m_extra_frame_requested_delayed) - now;
|
|
||||||
if (miliseconds + 20 < remaining_time) {
|
if (miliseconds + 20 < remaining_time) {
|
||||||
m_render_timer.Stop();
|
m_render_timer.Stop();
|
||||||
m_extra_frame_requested_delayed = miliseconds;
|
|
||||||
m_render_timer.StartOnce(miliseconds);
|
m_render_timer.StartOnce(miliseconds);
|
||||||
m_render_timer_start = now;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4438,13 +4433,7 @@ bool GLCanvas3D::_init_main_toolbar()
|
|||||||
}
|
}
|
||||||
// init arrow
|
// init arrow
|
||||||
BackgroundTexture::Metadata arrow_data;
|
BackgroundTexture::Metadata arrow_data;
|
||||||
arrow_data.filename = "toolbar_arrow.png";
|
arrow_data.filename = "toolbar_arrow.svg";
|
||||||
// arrow_data.filename = "toolbar_arrow.svg";
|
|
||||||
//arrow_data.left = 16;
|
|
||||||
//arrow_data.top = 16;
|
|
||||||
//arrow_data.right = 16;
|
|
||||||
//arrow_data.bottom = 16;
|
|
||||||
|
|
||||||
arrow_data.left = 0;
|
arrow_data.left = 0;
|
||||||
arrow_data.top = 0;
|
arrow_data.top = 0;
|
||||||
arrow_data.right = 0;
|
arrow_data.right = 0;
|
||||||
|
@ -463,15 +463,13 @@ private:
|
|||||||
std::string m_sidebar_field;
|
std::string m_sidebar_field;
|
||||||
// when true renders an extra frame by not resetting m_dirty to false
|
// when true renders an extra frame by not resetting m_dirty to false
|
||||||
// see request_extra_frame()
|
// see request_extra_frame()
|
||||||
bool m_extra_frame_requested;
|
bool m_extra_frame_requested;
|
||||||
int m_extra_frame_requested_delayed { std::numeric_limits<int>::max() };
|
|
||||||
bool m_event_handlers_bound{ false };
|
bool m_event_handlers_bound{ false };
|
||||||
|
|
||||||
GLVolumeCollection m_volumes;
|
GLVolumeCollection m_volumes;
|
||||||
GCodeViewer m_gcode_viewer;
|
GCodeViewer m_gcode_viewer;
|
||||||
|
|
||||||
RenderTimer m_render_timer;
|
RenderTimer m_render_timer;
|
||||||
int64_t m_render_timer_start;
|
|
||||||
|
|
||||||
Selection m_selection;
|
Selection m_selection;
|
||||||
const DynamicPrintConfig* m_config;
|
const DynamicPrintConfig* m_config;
|
||||||
|
@ -193,10 +193,9 @@ bool GLToolbar::init_arrow(const BackgroundTexture::Metadata& arrow_texture)
|
|||||||
std::string path = resources_dir() + "/icons/";
|
std::string path = resources_dir() + "/icons/";
|
||||||
bool res = false;
|
bool res = false;
|
||||||
|
|
||||||
if (!arrow_texture.filename.empty())
|
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, false, false, 1000);
|
||||||
// res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, true, false, 100);
|
}
|
||||||
|
|
||||||
if (res)
|
if (res)
|
||||||
m_arrow_texture.metadata = arrow_texture;
|
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;
|
float right = left + scaled_icons_size;
|
||||||
|
|
||||||
unsigned int tex_id = m_arrow_texture.texture.get_id();
|
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_width = (float)m_icons_texture.get_width();
|
||||||
float tex_height = (float)m_icons_texture.get_height();
|
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 internal_left = left + border - scaled_icons_size * 1.5f; // add scaled_icons_size for huge arrow
|
||||||
float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f;
|
float internal_right = right - border + scaled_icons_size * 1.5f;
|
||||||
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_top = top - border;
|
float internal_top = top - border;
|
||||||
// bottom is not moving and should be calculated from arrow texture sides ratio
|
// 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 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_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;
|
float internal_right_uv = 1.0f - (float)m_arrow_texture.metadata.right * inv_tex_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)
|
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)
|
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
|
// static method accepting a wxWindow object as first parameter
|
||||||
// void warning_catcher{
|
// void warning_catcher{
|
||||||
// my($self, $message_dialog) = @_;
|
// my($self, $message_dialog) = @_;
|
||||||
|
@ -261,7 +261,8 @@ public:
|
|||||||
void open_preferences(size_t open_on_tab = 0);
|
void open_preferences(size_t open_on_tab = 0);
|
||||||
|
|
||||||
virtual bool OnExceptionInMainLoop() override;
|
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__
|
#ifdef __APPLE__
|
||||||
void OSXStoreOpenFiles(const wxArrayString &files) override;
|
void OSXStoreOpenFiles(const wxArrayString &files) override;
|
||||||
// wxWidgets override to get an event on open files.
|
// wxWidgets override to get an event on open files.
|
||||||
|
@ -4023,7 +4023,23 @@ void ObjectList::fix_through_netfabb()
|
|||||||
|
|
||||||
void ObjectList::simplify()
|
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);
|
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_dragging(false)
|
||||||
, m_imgui(wxGetApp().imgui())
|
, m_imgui(wxGetApp().imgui())
|
||||||
, m_first_input_window_render(true)
|
, m_first_input_window_render(true)
|
||||||
|
, m_dirty(false)
|
||||||
{
|
{
|
||||||
m_base_color = DEFAULT_BASE_COLOR;
|
m_base_color = DEFAULT_BASE_COLOR;
|
||||||
m_drag_color = DEFAULT_DRAG_COLOR;
|
m_drag_color = DEFAULT_DRAG_COLOR;
|
||||||
@ -154,6 +155,13 @@ void GLGizmoBase::update(const UpdateData& data)
|
|||||||
on_update(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
|
std::array<float, 4> GLGizmoBase::picking_color_component(unsigned int id) const
|
||||||
{
|
{
|
||||||
static const float INV_255 = 1.0f / 255.0f;
|
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);
|
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)
|
void GLGizmoBase::render_input_window(float x, float y, float bottom_limit)
|
||||||
{
|
{
|
||||||
on_render_input_window(x, y, bottom_limit);
|
on_render_input_window(x, y, bottom_limit);
|
||||||
|
@ -154,6 +154,9 @@ public:
|
|||||||
|
|
||||||
void update(const UpdateData& data);
|
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() { m_tooltip.clear(); on_render(); }
|
||||||
void render_for_picking() { on_render_for_picking(); }
|
void render_for_picking() { on_render_for_picking(); }
|
||||||
void render_input_window(float x, float y, float bottom_limit);
|
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;
|
void render_grabbers_for_picking(const BoundingBoxf3& box) const;
|
||||||
|
|
||||||
std::string format(float value, unsigned int decimals) 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
|
// 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 "GLGizmoSimplify.hpp"
|
||||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
#include "slic3r/GUI/Plater.hpp"
|
|
||||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||||
|
#include "slic3r/GUI/NotificationManager.hpp"
|
||||||
|
#include "slic3r/GUI/Plater.hpp"
|
||||||
#include "libslic3r/AppConfig.hpp"
|
#include "libslic3r/AppConfig.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
#include "libslic3r/QuadricEdgeCollapse.hpp"
|
#include "libslic3r/QuadricEdgeCollapse.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI {
|
||||||
|
|
||||||
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
|
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
|
||||||
const std::string &icon_filename,
|
const std::string &icon_filename,
|
||||||
unsigned int sprite_id)
|
unsigned int sprite_id)
|
||||||
: GLGizmoBase(parent, icon_filename, -1)
|
: GLGizmoBase(parent, icon_filename, -1)
|
||||||
, state(State::settings)
|
, m_state(State::settings)
|
||||||
, is_valid_result(false)
|
, m_is_valid_result(false)
|
||||||
, progress(0)
|
, m_exist_preview(false)
|
||||||
, volume(nullptr)
|
, m_progress(0)
|
||||||
, obj_index(0)
|
, m_volume(nullptr)
|
||||||
, need_reload(false)
|
, 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() {
|
GLGizmoSimplify::~GLGizmoSimplify() {
|
||||||
state = State::canceling;
|
m_state = State::canceling;
|
||||||
if (worker.joinable()) worker.join();
|
if (m_worker.joinable()) m_worker.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GLGizmoSimplify::on_init()
|
bool GLGizmoSimplify::on_init()
|
||||||
@ -38,7 +42,6 @@ bool GLGizmoSimplify::on_init()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string GLGizmoSimplify::on_get_name() const
|
std::string GLGizmoSimplify::on_get_name() const
|
||||||
{
|
{
|
||||||
return (_L("Simplify")).ToUTF8().data();
|
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)
|
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();
|
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();
|
const Selection &selection = m_parent.get_selection();
|
||||||
int object_idx = selection.get_object_idx();
|
int object_idx = selection.get_object_idx();
|
||||||
ModelObject *obj = wxGetApp().plater()->model().objects[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
|
// Check selection of new volume
|
||||||
// Do not reselect object when processing
|
// Do not reselect object when processing
|
||||||
if (act_volume != volume && state == State::settings) {
|
if (act_volume != m_volume && m_state == State::settings) {
|
||||||
obj_index = object_idx; // to remember correct object
|
bool change_window_position = (m_volume == nullptr);
|
||||||
volume = act_volume;
|
// select different model
|
||||||
original_its = {};
|
if (m_volume != nullptr && m_original_its.has_value()) {
|
||||||
const TriangleMesh &tm = volume->mesh();
|
set_its(*m_original_its);
|
||||||
c.wanted_percent = 50.; // default value
|
}
|
||||||
c.update_percent(tm.its.indices.size());
|
|
||||||
is_valid_result = false;
|
m_obj_index = object_idx; // to remember correct object
|
||||||
// set window position
|
m_volume = act_volume;
|
||||||
ImVec2 pos = ImGui::GetMousePos();
|
m_original_its = {};
|
||||||
pos.x -= gui_cfg->window_offset;
|
m_configuration.decimate_ratio = 50.; // default value
|
||||||
pos.y -= gui_cfg->window_offset;
|
m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size());
|
||||||
ImGui::SetWindowPos(pos, ImGuiCond_Always);
|
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();
|
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
|
||||||
// already reduced mesh
|
ImGuiWindowFlags_NoCollapse;
|
||||||
if (original_its.has_value())
|
m_imgui->begin(on_get_name(), flag);
|
||||||
triangle_count = original_its->indices.size();
|
|
||||||
|
|
||||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mesh name") + ":");
|
size_t triangle_count = m_volume->mesh().its.indices.size();
|
||||||
ImGui::SameLine(gui_cfg->top_left_width);
|
// already reduced mesh
|
||||||
std::string name = volume->name;
|
if (m_original_its.has_value())
|
||||||
if (name.length() > max_char_in_name)
|
triangle_count = m_original_its->indices.size();
|
||||||
name = name.substr(0, max_char_in_name-3) + "...";
|
|
||||||
|
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(name);
|
||||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Triangles") + ":");
|
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":");
|
||||||
ImGui::SameLine(gui_cfg->top_left_width);
|
ImGui::SameLine(m_gui_cfg->top_left_width);
|
||||||
m_imgui->text(std::to_string(triangle_count));
|
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::Separator();
|
||||||
|
|
||||||
ImGui::Text(_L("Limit by triangles").c_str());
|
if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) {
|
||||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
m_is_valid_result = false;
|
||||||
// First initialization + fix triangle count
|
m_configuration.use_count = !m_configuration.use_count;
|
||||||
if (m_imgui->checkbox("##UseCount", c.use_count)) {
|
}
|
||||||
if (!c.use_count) c.use_error = true;
|
ImGui::SameLine();
|
||||||
is_valid_result = false;
|
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);
|
m_imgui->disabled_begin(!m_configuration.use_count);
|
||||||
ImGui::Text(_L("Triangle count").c_str());
|
ImGui::Text("%s", tr_decimate_ratio.c_str());
|
||||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||||
int wanted_count = c.wanted_count;
|
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
||||||
ImGui::SetNextItemWidth(gui_cfg->input_width);
|
const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%":
|
||||||
if (ImGui::SliderInt("##triangle_count", &wanted_count, min_triangle_count, triangle_count, "%d")) {
|
((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%");
|
||||||
c.wanted_count = static_cast<uint32_t>(wanted_count);
|
if (ImGui::SliderFloat("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) {
|
||||||
if (c.wanted_count < min_triangle_count)
|
m_is_valid_result = false;
|
||||||
c.wanted_count = min_triangle_count;
|
if (m_configuration.decimate_ratio < 0.f)
|
||||||
if (c.wanted_count > triangle_count)
|
m_configuration.decimate_ratio = 0.01f;
|
||||||
c.wanted_count = triangle_count;
|
if (m_configuration.decimate_ratio > 100.f)
|
||||||
c.update_count(triangle_count);
|
m_configuration.decimate_ratio = 100.f;
|
||||||
is_valid_result = false;
|
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::NewLine();
|
||||||
ImGui::Text(_L("Limit by error").c_str());
|
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count);
|
||||||
if (m_imgui->checkbox("##UseError", c.use_error)) {
|
m_imgui->disabled_end(); // use_count
|
||||||
if (!c.use_error) c.use_count = true;
|
|
||||||
is_valid_result = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_imgui->disabled_begin(!c.use_error);
|
if (m_state == State::settings) {
|
||||||
ImGui::Text(_L("Max. error").c_str());
|
|
||||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
|
||||||
ImGui::SetNextItemWidth(gui_cfg->input_small_width);
|
|
||||||
if (ImGui::InputFloat("##maxError", &c.max_error, 0.01f, .1f, "%.2f")) {
|
|
||||||
if (c.max_error < 0.f) c.max_error = 0.f;
|
|
||||||
is_valid_result = false;
|
|
||||||
}
|
|
||||||
m_imgui->disabled_end(); // use_error
|
|
||||||
|
|
||||||
if (state == State::settings) {
|
|
||||||
if (m_imgui->button(_L("Cancel"))) {
|
if (m_imgui->button(_L("Cancel"))) {
|
||||||
if (original_its.has_value()) {
|
if (m_original_its.has_value()) {
|
||||||
set_its(*original_its);
|
set_its(*m_original_its);
|
||||||
state = State::close_on_end;
|
m_state = State::close_on_end;
|
||||||
} else {
|
} else {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||||
if (m_imgui->button(_L("Preview"))) {
|
if (m_imgui->button(_L("Preview"))) {
|
||||||
state = State::simplifying;
|
m_state = State::preview;
|
||||||
// simplify but not aply on mesh
|
// simplify but not aply on mesh
|
||||||
process();
|
process();
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (m_imgui->button(_L("Apply"))) {
|
if (m_imgui->button(_L("Apply"))) {
|
||||||
if (!is_valid_result) {
|
if (!m_is_valid_result) {
|
||||||
state = State::close_on_end;
|
m_state = State::close_on_end;
|
||||||
process();
|
process();
|
||||||
} else {
|
} else {
|
||||||
// use preview and close
|
// 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();
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m_imgui->disabled_begin(state == State::canceling);
|
m_imgui->disabled_begin(m_state == State::canceling);
|
||||||
if (m_imgui->button(_L("Cancel"))) state = State::canceling;
|
if (m_imgui->button(_L("Cancel"))) m_state = State::canceling;
|
||||||
m_imgui->disabled_end();
|
m_imgui->disabled_end();
|
||||||
|
|
||||||
ImGui::SameLine(gui_cfg->bottom_left_width);
|
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||||
// draw progress bar
|
// draw progress bar
|
||||||
char buf[32];
|
char buf[32];
|
||||||
sprintf(buf, L("Process %d / 100"), progress);
|
sprintf(buf, L("Process %d / 100"), m_progress);
|
||||||
ImGui::ProgressBar(progress / 100., ImVec2(gui_cfg->input_width, 0.f), buf);
|
ImGui::ProgressBar(m_progress / 100., ImVec2(m_gui_cfg->input_width, 0.f), buf);
|
||||||
}
|
}
|
||||||
m_imgui->end();
|
m_imgui->end();
|
||||||
|
|
||||||
if (need_reload) {
|
if (m_need_reload) {
|
||||||
need_reload = false;
|
m_need_reload = false;
|
||||||
|
bool close_on_end = (m_state == State::close_on_end);
|
||||||
// Reload visualization of mesh - change VBO, FBO on GPU
|
// Reload visualization of mesh - change VBO, FBO on GPU
|
||||||
m_parent.reload_scene(true); // deactivate gizmo??
|
m_parent.reload_scene(true);
|
||||||
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
|
// set m_state must be before close() !!!
|
||||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
m_state = State::settings;
|
||||||
|
if (close_on_end) {
|
||||||
if (state == State::close_on_end) {
|
|
||||||
// fix hollowing, sla support points, modifiers, ...
|
// fix hollowing, sla support points, modifiers, ...
|
||||||
auto plater = wxGetApp().plater();
|
auto plater = wxGetApp().plater();
|
||||||
plater->changed_mesh(obj_index); // deactivate gizmo??
|
plater->changed_mesh(m_obj_index);
|
||||||
// changed_mesh cause close();
|
close();
|
||||||
//close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// change from simplifying | aply
|
|
||||||
state = State::settings;
|
|
||||||
|
|
||||||
// Fix warning icon in object list
|
// 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() {
|
void GLGizmoSimplify::close() {
|
||||||
volume = nullptr;
|
|
||||||
|
|
||||||
// close gizmo == open it again
|
// close gizmo == open it again
|
||||||
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
|
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
|
||||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
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"); }
|
const char* what() const throw() { return L("Model simplification has been canceled"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!original_its.has_value())
|
if (!m_original_its.has_value())
|
||||||
original_its = volume->mesh().its; // copy
|
m_original_its = m_volume->mesh().its; // copy
|
||||||
|
|
||||||
auto plater = wxGetApp().plater();
|
auto plater = wxGetApp().plater();
|
||||||
plater->take_snapshot(_L("Simplify ") + volume->name);
|
plater->take_snapshot(_L("Simplify ") + m_volume->name);
|
||||||
plater->clear_before_change_mesh(obj_index);
|
plater->clear_before_change_mesh(m_obj_index);
|
||||||
progress = 0;
|
m_progress = 0;
|
||||||
if (worker.joinable()) worker.join();
|
if (m_worker.joinable()) m_worker.join();
|
||||||
worker = std::thread([&]() {
|
m_worker = std::thread([this]() {
|
||||||
// store original triangles
|
// store original triangles
|
||||||
uint32_t triangle_count = (c.use_count) ? c.wanted_count : 0;
|
uint32_t triangle_count = (m_configuration.use_count) ? m_configuration.wanted_count : 0;
|
||||||
float max_error = (c.use_error) ? c.max_error : std::numeric_limits<float>::max();
|
float max_error = (!m_configuration.use_count) ? m_configuration.max_error : std::numeric_limits<float>::max();
|
||||||
|
|
||||||
std::function<void(void)> throw_on_cancel = [&]() {
|
std::function<void(void)> throw_on_cancel = [&]() {
|
||||||
if (state == State::canceling) {
|
if (m_state == State::canceling) {
|
||||||
throw SimplifyCanceledException();
|
throw SimplifyCanceledException();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
std::function<void(int)> statusfn = [&](int percent) {
|
|
||||||
progress = percent;
|
|
||||||
m_parent.schedule_extra_frame(0);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
indexed_triangle_set collapsed;
|
std::function<void(int)> statusfn = [this](int percent) {
|
||||||
if (last_error.has_value()) {
|
m_progress = percent;
|
||||||
// is chance to continue with last reduction
|
|
||||||
const indexed_triangle_set &its = volume->mesh().its;
|
// check max 4fps
|
||||||
uint32_t last_triangle_count = static_cast<uint32_t>(its.indices.size());
|
static int64_t last = 0;
|
||||||
if ((!c.use_count || triangle_count <= last_triangle_count) &&
|
int64_t now = m_parent.timestamp_now();
|
||||||
(!c.use_error || c.max_error <= *last_error)) {
|
if ((now - last) < 250) return;
|
||||||
collapsed = its; // small copy
|
last = now;
|
||||||
} else {
|
|
||||||
collapsed = *original_its; // copy
|
request_rerender();
|
||||||
}
|
};
|
||||||
} else {
|
|
||||||
collapsed = *original_its; // copy
|
indexed_triangle_set collapsed = *m_original_its; // copy
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn);
|
its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn);
|
||||||
set_its(collapsed);
|
set_its(collapsed);
|
||||||
is_valid_result = true;
|
m_is_valid_result = true;
|
||||||
last_error = max_error;
|
m_exist_preview = true;
|
||||||
} catch (SimplifyCanceledException &) {
|
} catch (SimplifyCanceledException &) {
|
||||||
state = State::settings;
|
// set state out of main thread
|
||||||
|
m_state = State::settings;
|
||||||
}
|
}
|
||||||
// need to render last status fn
|
// need to render last status fn to change bar graph to buttons
|
||||||
// without sleep it freezes until mouse move
|
request_rerender();
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
||||||
m_parent.schedule_extra_frame(0);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSimplify::set_its(indexed_triangle_set &its) {
|
void GLGizmoSimplify::set_its(indexed_triangle_set &its) {
|
||||||
auto tm = std::make_unique<TriangleMesh>(its);
|
auto tm = std::make_unique<TriangleMesh>(its);
|
||||||
tm->repair();
|
tm->repair();
|
||||||
volume->set_mesh(std::move(tm));
|
m_volume->set_mesh(std::move(tm));
|
||||||
volume->set_new_unique_id();
|
m_volume->set_new_unique_id();
|
||||||
volume->get_object()->invalidate_bounding_box();
|
m_volume->get_object()->invalidate_bounding_box();
|
||||||
need_reload = true;
|
m_need_reload = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GLGizmoSimplify::on_is_activable() const
|
bool GLGizmoSimplify::on_is_activable() const
|
||||||
@ -297,26 +324,62 @@ bool GLGizmoSimplify::on_is_activable() const
|
|||||||
return !m_parent.get_selection().is_empty();
|
return !m_parent.get_selection().is_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSimplify::create_gui_cfg() {
|
void GLGizmoSimplify::on_set_state()
|
||||||
if (gui_cfg.has_value()) return;
|
{
|
||||||
|
// 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;
|
int space_size = m_imgui->calc_text_size(":MM").x;
|
||||||
GuiCfg cfg;
|
GuiCfg cfg;
|
||||||
cfg.top_left_width = std::max(m_imgui->calc_text_size(_L("Mesh name")).x,
|
cfg.top_left_width = std::max(m_imgui->calc_text_size(tr_mesh_name).x,
|
||||||
m_imgui->calc_text_size(_L("Triangles")).x)
|
m_imgui->calc_text_size(tr_triangles).x)
|
||||||
+ space_size;
|
+ space_size;
|
||||||
|
|
||||||
|
const float radio_size = ImGui::GetFrameHeight();
|
||||||
cfg.bottom_left_width =
|
cfg.bottom_left_width =
|
||||||
std::max(
|
std::max(m_imgui->calc_text_size(tr_detail_level).x,
|
||||||
std::max(m_imgui->calc_text_size(_L("Limit by triangles")).x,
|
m_imgui->calc_text_size(tr_decimate_ratio).x) +
|
||||||
std::max(m_imgui->calc_text_size(_L("Triangle count")).x,
|
space_size + radio_size;
|
||||||
m_imgui->calc_text_size(_L("Ratio")).x)),
|
|
||||||
std::max(m_imgui->calc_text_size(_L("Limit by error")).x,
|
cfg.input_width = cfg.bottom_left_width * 1.5;
|
||||||
m_imgui->calc_text_size(_L("Max. error")).x)) + space_size;
|
cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2;
|
||||||
cfg.input_width = cfg.bottom_left_width;
|
cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5;
|
||||||
cfg.input_small_width = cfg.input_width * 0.8;
|
m_gui_cfg = cfg;
|
||||||
cfg.window_offset = cfg.input_width;
|
}
|
||||||
gui_cfg = cfg;
|
|
||||||
|
void GLGizmoSimplify::request_rerender() {
|
||||||
|
wxGetApp().plater()->CallAfter([this]() {
|
||||||
|
set_dirty();
|
||||||
|
m_parent.schedule_extra_frame(0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Slic3r::GUI
|
} // namespace Slic3r::GUI
|
||||||
|
@ -1,35 +1,22 @@
|
|||||||
#ifndef slic3r_GLGizmoSimplify_hpp_
|
#ifndef slic3r_GLGizmoSimplify_hpp_
|
||||||
#define 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 "GLGizmoBase.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "admesh/stl.h" // indexed_triangle_set
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class ModelVolume;
|
||||||
|
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
|
|
||||||
class GLGizmoSimplify : public GLGizmoBase
|
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:
|
public:
|
||||||
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||||
virtual ~GLGizmoSimplify();
|
virtual ~GLGizmoSimplify();
|
||||||
@ -41,32 +28,51 @@ protected:
|
|||||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||||
virtual bool on_is_activable() const override;
|
virtual bool on_is_activable() const override;
|
||||||
virtual bool on_is_selectable() const override { return false; }
|
virtual bool on_is_selectable() const override { return false; }
|
||||||
|
virtual void on_set_state() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void close();
|
void close();
|
||||||
void process();
|
void process();
|
||||||
void set_its(indexed_triangle_set &its);
|
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
|
struct Configuration
|
||||||
{
|
{
|
||||||
bool use_count = true;
|
bool use_count = false;
|
||||||
// minimal triangle count
|
// minimal triangle count
|
||||||
float wanted_percent = 50.f;
|
float decimate_ratio = 50.f; // in percent
|
||||||
uint32_t wanted_count = 0; // initialize by percents
|
uint32_t wanted_count = 0; // initialize by percents
|
||||||
|
|
||||||
bool use_error = false;
|
|
||||||
// maximal quadric error
|
// maximal quadric error
|
||||||
float max_error = 1.;
|
float max_error = 1.;
|
||||||
|
|
||||||
void update_count(size_t triangle_count)
|
void fix_count_by_ratio(size_t triangle_count)
|
||||||
{
|
|
||||||
wanted_percent = (float) wanted_count / triangle_count * 100.f;
|
|
||||||
}
|
|
||||||
void update_percent(size_t triangle_count)
|
|
||||||
{
|
{
|
||||||
wanted_count = static_cast<uint32_t>(
|
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.
|
// This configs holds GUI layout size given by translated texts.
|
||||||
// etc. When language changes, GUI is recreated and this class constructed again,
|
// etc. When language changes, GUI is recreated and this class constructed again,
|
||||||
@ -76,11 +82,21 @@ private:
|
|||||||
int top_left_width = 100;
|
int top_left_width = 100;
|
||||||
int bottom_left_width = 100;
|
int bottom_left_width = 100;
|
||||||
int input_width = 100;
|
int input_width = 100;
|
||||||
int input_small_width = 80;
|
int window_offset_x = 100;
|
||||||
int window_offset = 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;
|
std::optional<GuiCfg> m_gui_cfg;
|
||||||
void create_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
|
} // namespace GUI
|
||||||
|
@ -132,8 +132,7 @@ bool GLGizmosManager::init_arrow(const BackgroundTexture::Metadata& arrow_textur
|
|||||||
bool res = false;
|
bool res = false;
|
||||||
|
|
||||||
if (!arrow_texture.filename.empty())
|
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, false, false, 1000);
|
||||||
// res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, true, false, 100);
|
|
||||||
if (res)
|
if (res)
|
||||||
m_arrow_texture.metadata = arrow_texture;
|
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();
|
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;
|
break;
|
||||||
}
|
}
|
||||||
zoomed_top_y -= zoomed_stride_y;
|
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)
|
void launch_browser_if_allowed(const std::string& url)
|
||||||
{
|
{
|
||||||
if (wxGetApp().app_config->get("suppress_hyperlinks") != "1")
|
wxGetApp().open_browser_with_warning_dialog(url);
|
||||||
wxLaunchDefaultBrowser(url);
|
|
||||||
}
|
}
|
||||||
} //namespace
|
} //namespace
|
||||||
HintDatabase::~HintDatabase()
|
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"),
|
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"); });
|
[](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"),
|
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{
|
//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{
|
||||||
//# wxTheApp->check_version(1);
|
//# wxTheApp->check_version(1);
|
||||||
//# });
|
//# });
|
||||||
@ -1067,14 +1067,14 @@ static wxMenu* generate_help_menu()
|
|||||||
[](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); });
|
[](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),
|
// 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),
|
// 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();
|
helpMenu->AppendSeparator();
|
||||||
append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"),
|
append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"),
|
||||||
[](wxCommandEvent&) { wxGetApp().system_info(); });
|
[](wxCommandEvent&) { wxGetApp().system_info(); });
|
||||||
append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"),
|
append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"),
|
||||||
[](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
|
[](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),
|
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())
|
if (wxGetApp().is_editor())
|
||||||
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"),
|
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"),
|
||||||
[](wxCommandEvent&) { Slic3r::GUI::about(); });
|
[](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) {
|
{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,
|
{NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10,
|
||||||
_u8L("You have just added a G-code for color change, but its value is empty.\n"
|
_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\"") },
|
"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,
|
DidYouKnowHint,
|
||||||
// Shows when ObjectList::update_info_items finds information that should be stressed to the user
|
// Shows when ObjectList::update_info_items finds information that should be stressed to the user
|
||||||
// Might contain logo taken from gizmos
|
// Might contain logo taken from gizmos
|
||||||
UpdatedItemsInfo
|
UpdatedItemsInfo,
|
||||||
|
// Give user advice to simplify object with big amount of triangles
|
||||||
|
SimplifySuggestion
|
||||||
};
|
};
|
||||||
|
|
||||||
class NotificationManager
|
class NotificationManager
|
||||||
@ -538,7 +540,13 @@ private:
|
|||||||
// Timestamp of last rendering
|
// Timestamp of last rendering
|
||||||
int64_t m_last_render { 0LL };
|
int64_t m_last_render { 0LL };
|
||||||
// Notification types that can be shown multiple types at once (compared by text)
|
// 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
|
//prepared (basic) notifications
|
||||||
static const NotificationData basic_notifications[];
|
static const NotificationData basic_notifications[];
|
||||||
};
|
};
|
||||||
|
@ -1721,7 +1721,7 @@ struct Plater::priv
|
|||||||
void replace_with_stl();
|
void replace_with_stl();
|
||||||
void reload_all_from_disk();
|
void reload_all_from_disk();
|
||||||
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
|
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 set_current_panel(wxPanel* panel);
|
||||||
|
|
||||||
void on_select_preset(wxCommandEvent&);
|
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();
|
view3D->get_canvas3d()->update_gizmos_on_off_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create_simplify_notification(obj_idxs);
|
||||||
|
|
||||||
return 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();
|
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)
|
void Plater::priv::set_current_panel(wxPanel* panel)
|
||||||
{
|
{
|
||||||
if (std::find(panels.begin(), panels.end(), panel) == panels.end())
|
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
|
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;
|
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"
|
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||||
|
|
||||||
// return Average abs distance to original
|
struct CompareConfig
|
||||||
float compare(const indexed_triangle_set &original,
|
{
|
||||||
const indexed_triangle_set &simplified,
|
float max_distance = 3.f;
|
||||||
double sample_per_mm2)
|
float max_average_distance = 2.f;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool is_similar(const indexed_triangle_set &from,
|
||||||
|
const indexed_triangle_set &to,
|
||||||
|
const CompareConfig &cfg)
|
||||||
{
|
{
|
||||||
// create ABBTree
|
// create ABBTree
|
||||||
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
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;
|
auto collect_distances = [&](const Vec3f &surface_point) {
|
||||||
std::mt19937 rnd(init);
|
|
||||||
auto samples = its_sample_surface(simplified, sample_per_mm2, rnd);
|
|
||||||
|
|
||||||
float sumDistance = 0;
|
|
||||||
for (const Vec3f &sample : samples) {
|
|
||||||
size_t hit_idx;
|
size_t hit_idx;
|
||||||
Vec3f hit_point;
|
Vec3f hit_point;
|
||||||
float distance2 = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
float distance2 =
|
||||||
original.vertices, original.indices, tree, sample, hit_idx,
|
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||||
hit_point);
|
from.vertices, from.indices, tree, surface_point, hit_idx, hit_point);
|
||||||
sumDistance += sqrt(distance2);
|
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]")
|
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]);
|
(v[i] > v4[i] && v[i] < v2[i]);
|
||||||
CHECK(is_between);
|
CHECK(is_between);
|
||||||
}
|
}
|
||||||
float avg_distance = compare(its_, its, 10);
|
CompareConfig cfg;
|
||||||
CHECK(avg_distance < 8e-3f);
|
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"
|
#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);
|
CHECK(its.indices.size() <= wanted_count);
|
||||||
double volume = its_volume(its);
|
double volume = its_volume(its);
|
||||||
CHECK(fabs(original_volume - volume) < 33.);
|
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