Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_world_coordinates

This commit is contained in:
enricoturri1966 2021-11-15 09:36:09 +01:00
commit 0a238adbab
55 changed files with 3287 additions and 3833 deletions

View File

@ -72,12 +72,12 @@ hypertext_settings_category = Layers and perimeters
disabled_tags = SLA
[hint:Shapes gallery]
text = Shapes gallery\nDid you know that PrusaSlicer has a Shapes Gallery? You can use the included models as modifiers, negative volumes or as printable objects. Right-click the platter and select<a>Add Shape - Gallery.</a>
text = Shapes gallery\nDid you know that PrusaSlicer has a Shapes Gallery? You can use the included models as modifiers, negative volumes or as printable objects. Right-click the platter and select<a>Add Shape - Gallery</a>.
hypertext_type = gallery
disable_modes = simple
[hint:Auto-arrange settings]
text = Auto-arrange settings\nDid you know that you can right-click the<a>auto-arrange icon</a>to adjust the size of the gap between objects and to allow automatic rotations?
[hint:Arrange settings]
text = Arrange settings\nDid you know that you can right-click the<a>Arrange icon</a>to adjust the size of the gap between objects and to allow automatic rotations?
hypertext_type = plater
hypertext_plater_item = arrange
@ -95,7 +95,7 @@ text = Reload from disk\nDid you know that if you created a newer version of you
documentation_link = https://help.prusa3d.com/en/article/reload-from-disk_120427
[hint:Hiding sidebar]
text = Hiding sidebar\nDid you know that you can hide the right sidebar using the shortcut <b>Shift+Tab</b>? You can also enable the icon for this from the<a>Preferences.</a>
text = Hiding sidebar\nDid you know that you can hide the right sidebar using the shortcut <b>Shift+Tab</b>? You can also enable the icon for this from the<a>Preferences</a>.
hypertext_type = preferences
hypertext_preferences_page = 2
hypertext_preferences_item = show_collapse_button
@ -115,7 +115,7 @@ hypertext_gizmo_item = place
text = Set number of instances\nDid you know that you can right-click a model and set an exact number of instances instead of copy-pasting it several times?
[hint:Combine infill]
text = Combine infill\nDid you know that you can print the infill with a higher layer height compared to perimeters to save print time using the setting<a>Combine infill every.</a>
text = Combine infill\nDid you know that you can print the infill with a higher layer height compared to perimeters to save print time using the setting<a>Combine infill every</a>.
hypertext_type = settings
hypertext_settings_opt = infill_every_layers
hypertext_settings_type = 1
@ -139,7 +139,7 @@ documentation_link= https://help.prusa3d.com/en/article/per-model-settings_1674
disabled_tags = SLA
[hint:Solid infill threshold area]
text = Solid infill threshold area\nDid you know that you can make parts of your model with a small cross-section be filled with solid infill automatically? Set the<a>Solid infill threshold area.</a>(Expert mode only.)
text = Solid infill threshold area\nDid you know that you can make parts of your model with a small cross-section be filled with solid infill automatically? Set the<a>Solid infill threshold area</a>.(Expert mode only.)
hypertext_type = settings
hypertext_settings_opt = solid_infill_below_area
hypertext_settings_type = 1
@ -167,7 +167,7 @@ text = Mirror\nDid you know that you can mirror the selected model to create a r
text = PageUp / PageDown quick rotation by 45 degrees\nDid you know that you can quickly rotate selected models by 45 degrees around the Z-axis clockwise or counter-clockwise by pressing <b>Page Up</b> or <b>Page Down</b> respectively?
[hint:Load config from G-code]
text = Load config from G-code\nDid you know that you can use File-Import Config to load print, filament and printer profiles from an existing G-code file? Similarly, you can use File-Import SL1 archive, which also lets you reconstruct 3D models from the voxel data.
text = Load config from G-code\nDid you know that you can use File-Import-Import Config to load print, filament and printer profiles from an existing G-code file? Similarly, you can use File-Import-Import SL1 / SL1S archive, which also lets you reconstruct 3D models from the voxel data.
[hint:Ironing]
text = Ironing\nDid you know that you can smooth top surfaces of prints using Ironing? The nozzle will run a special second infill phase at the same layer to fill in holes and flatten any lifted plastic. Read more in the documentation. (Requires Advanced or Expert mode.)

View File

@ -4,7 +4,7 @@
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="hex_x5F_X">
<g>
<polygon fill="#808080" points="8,1 2,5 2,7 2,11 8,15 14,11 14,7 14,5 "/>
<polygon fill="#818181" points="8,1 2,5 2,7 2,11 8,15 14,11 14,7 14,5 "/>
</g>
<g id="plus_2_">

Before

Width:  |  Height:  |  Size: 782 B

After

Width:  |  Height:  |  Size: 782 B

View File

@ -4,7 +4,7 @@
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="hex_x5F_X">
<g>
<polygon fill="#808080" points="8,1 2,5 2,7 2,11 8,15 14,11 14,7 14,5 "/>
<polygon fill="#818181" points="8,1 2,5 2,7 2,11 8,15 14,11 14,7 14,5 "/>
</g>
<g id="plus_2_">

Before

Width:  |  Height:  |  Size: 786 B

After

Width:  |  Height:  |  Size: 786 B

File diff suppressed because it is too large Load Diff

View File

@ -232,9 +232,6 @@ add_library(libslic3r STATIC
MinAreaBoundingBox.cpp
miniz_extension.hpp
miniz_extension.cpp
SimplifyMesh.hpp
SimplifyMeshImpl.hpp
SimplifyMesh.cpp
MarchingSquares.hpp
Execution/Execution.hpp
Execution/ExecutionSeq.hpp

View File

@ -490,6 +490,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
gap_over_supports += support_layer_height_min;
}
std::vector<std::pair<double, double>> warning_ranges;
// Pair the object layers with the support layers by z.
size_t idx_object_layer = 0;
size_t idx_support_layer = 0;
@ -535,15 +537,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
// Negative support_contact_z is not taken into account, it can result in false positives in cases
// where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752)
if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) {
const_cast<Print*>(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
Slic3r::format(_(L("Empty layer detected between heights %1% and %2%. Make sure the object is printable.")),
(last_extrusion_layer ? last_extrusion_layer->print_z() : 0.),
layers_to_print.back().print_z())
+ "\n" + Slic3r::format(_(L("Object name: %1%")), object.model_object()->name) + "\n\n"
+ _(L("This is usually caused by negligibly small extrusions or by a faulty model. "
"Try to repair the model or change its orientation on the bed.")));
}
if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON)
warning_ranges.emplace_back(std::make_pair((last_extrusion_layer ? last_extrusion_layer->print_z() : 0.), layers_to_print.back().print_z()));
// Remember last layer with extrusions.
if (has_extrusions)
@ -551,6 +546,23 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
}
}
if (! warning_ranges.empty()) {
std::string warning;
size_t i = 0;
for (i = 0; i < std::min(warning_ranges.size(), size_t(3)); ++i)
warning += Slic3r::format(_(L("Empty layer between %1% and %2%.")),
warning_ranges[i].first, warning_ranges[i].second) + "\n";
if (i < warning_ranges.size())
warning += _(L("(Some lines not shown)")) + "\n";
warning += "\n";
warning += Slic3r::format(_(L("Object name: %1%")), object.model_object()->name) + "\n\n"
+ _(L("Make sure the object is printable. This is usually caused by negligibly small extrusions or by a faulty model. "
"Try to repair the model or change its orientation on the bed."));
const_cast<Print*>(object.print())->active_step_add_warning(
PrintStateBase::WarningLevel::CRITICAL, warning);
}
return layers_to_print;
}
@ -732,7 +744,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re
reports += source + ": \"" + keyword + "\"\n";
}
print->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
_(L("Found reserved keyword(s) into custom g-code:")) + "\n" +
_(L("In the custom G-code were found reserved keywords:")) + "\n" +
reports +
_(L("This may cause problems in g-code visualization and printing time estimation.")));
}

View File

@ -1379,6 +1379,14 @@ const Preset* PrinterPresetCollection::find_system_preset_by_model_and_variant(c
return it != cend() ? &*it : nullptr;
}
bool PrinterPresetCollection::only_default_printers() const
{
for (const auto& printer : get_presets()) {
if (!boost::starts_with(printer.name,"- default"))
return false;
}
return true;
}
// -------------------------
// *** PhysicalPrinter ***
// -------------------------

View File

@ -597,6 +597,7 @@ public:
const Preset* find_system_preset_by_model_and_variant(const std::string &model_id, const std::string &variant) const;
bool only_default_printers() const;
private:
PrinterPresetCollection() = default;
PrinterPresetCollection(const PrinterPresetCollection &other) = default;

View File

@ -481,7 +481,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("brim_width", coFloat);
def->label = L("Brim width");
def->category = L("Skirt and brim");
def->tooltip = L("Horizontal width of the brim that will be printed around each object on the first layer."
def->tooltip = L("The horizontal width of the brim that will be printed around each object on the first layer. "
"When raft is used, no brim is generated (use raft_first_layer_expansion).");
def->sidetext = L("mm");
def->min = 0;
@ -1252,7 +1252,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("fuzzy_skin_thickness", coFloat);
def->label = L("Fuzzy skin thickness");
def->category = L("Fuzzy Skin");
def->tooltip = "";
def->tooltip = L("The maximum distance that each skin point can be offset (both ways), "
"measured perpendicular to the perimeter wall.");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
@ -1261,7 +1262,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("fuzzy_skin_point_dist", coFloat);
def->label = L("Fuzzy skin point distance");
def->category = L("Fuzzy Skin");
def->tooltip = "";
def->tooltip = L("Perimeters will be split into multiple segments by inserting Fuzzy skin points. "
"Lowering the Fuzzy skin point distance will increase the number of randomly offset points on the perimeter wall.");
def->sidetext = L("mm");
def->min = 0;
def->mode = comAdvanced;
@ -2439,13 +2441,13 @@ void PrintConfigDef::init_fff_params()
def = this->add("slicing_mode", coEnum);
def->label = L("Slicing Mode");
def->category = L("Advanced");
def->tooltip = L("Use \"Even / Odd\" for 3DLabPrint airplane models. Use \"Close holes\" to close all holes in the model.");
def->tooltip = L("Use \"Even-odd\" for 3DLabPrint airplane models. Use \"Close holes\" to close all holes in the model.");
def->enum_keys_map = &ConfigOptionEnum<SlicingMode>::get_enum_values();
def->enum_values.push_back("regular");
def->enum_values.push_back("even_odd");
def->enum_values.push_back("close_holes");
def->enum_labels.push_back(L("Regular"));
def->enum_labels.push_back(L("Even / Odd"));
def->enum_labels.push_back(L("Even-odd"));
def->enum_labels.push_back(L("Close holes"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<SlicingMode>(SlicingMode::Regular));
@ -2745,7 +2747,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("thick_bridges", coBool);
def->label = L("Thick bridges");
def->category = L("Layers and Perimeters");
def->tooltip = L("Print bridges with round extrusions.");
def->tooltip = L("If enabled, bridges are more reliable, can bridge longer distances, but may look worse. "
"If disabled, bridges look better but are reliable just for shorter bridged distances.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
@ -4268,7 +4271,7 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def = this->add("config_compatibility", coEnum);
def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF).");
def->tooltip = L("This version of PrusaSlicer may not understand configurations produced by newest PrusaSlicer versions. "
def->tooltip = L("This version of PrusaSlicer may not understand configurations produced by the newest PrusaSlicer versions. "
"For example, newer PrusaSlicer may extend the list of supported firmware flavors. One may decide to "
"bail out or to substitute an unknown value with a default silently or verbosely.");
def->enum_keys_map = &ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values();

View File

@ -2,7 +2,6 @@
#include <tuple>
#include <optional>
#include "MutablePriorityQueue.hpp"
#include "SimplifyMeshImpl.hpp"
#include <tbb/parallel_for.h>
using namespace Slic3r;
@ -13,10 +12,47 @@ using namespace Slic3r;
// only private namespace not neccessary be in .hpp
namespace QuadricEdgeCollapse {
// SymetricMatrix
class SymMat {
using T = double;
static const constexpr size_t N = 10;
T m[N];
public:
explicit SymMat(ArithmeticOnly<T> c = T()) { std::fill(m, m + N, c); }
// Make plane
SymMat(T a, T b, T c, T d)
{
m[0] = a * a; m[1] = a * b; m[2] = a * c; m[3] = a * d;
m[4] = b * b; m[5] = b * c; m[6] = b * d;
m[7] = c * c; m[8] = c * d;
m[9] = d * d;
}
T operator[](int c) const { return m[c]; }
// Determinant
T det(int a11, int a12, int a13,
int a21, int a22, int a23,
int a31, int a32, int a33) const
{
T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] +
m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] -
m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33];
return det;
}
const SymMat &operator+=(const SymMat &n)
{
for (size_t i = 0; i < N; ++i) m[i] += n[i];
return *this;
}
};
using Vertices = std::vector<stl_vertex>;
using Triangle = stl_triangle_vertex_indices;
using Indices = std::vector<stl_triangle_vertex_indices>;
using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>;
using ThrowOnCancel = std::function<void(void)>;
using StatusFn = std::function<void(int)>;
// smallest error caused by edges, identify smallest edge in triangle

View File

@ -7,7 +7,7 @@
#include <libslic3r/SLA/Hollowing.hpp>
#include <libslic3r/SLA/IndexedMesh.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/SimplifyMesh.hpp>
#include <libslic3r/QuadricEdgeCollapse.hpp>
#include <libslic3r/SLA/SupportTreeMesher.hpp>
#include <boost/log/trivial.hpp>
@ -132,7 +132,10 @@ InteriorPtr generate_interior(const TriangleMesh & mesh,
// flip normals back...
swap_normals(interior->mesh);
Slic3r::simplify_mesh(interior->mesh);
// simplify mesh lossless
float loss_less_max_error = 2*std::numeric_limits<float>::epsilon();
its_quadric_edge_collapse(interior->mesh, 0U, &loss_less_max_error);
its_compactify_vertices(interior->mesh);
its_merge_vertices(interior->mesh);

View File

@ -1,66 +0,0 @@
#include "SimplifyMesh.hpp"
#include "SimplifyMeshImpl.hpp"
namespace SimplifyMesh {
template<> struct vertex_traits<stl_vertex> {
using coord_type = float;
using compute_type = double;
static inline float x(const stl_vertex &v) { return v.x(); }
static inline float& x(stl_vertex &v) { return v.x(); }
static inline float y(const stl_vertex &v) { return v.y(); }
static inline float& y(stl_vertex &v) { return v.y(); }
static inline float z(const stl_vertex &v) { return v.z(); }
static inline float& z(stl_vertex &v) { return v.z(); }
};
template<> struct mesh_traits<indexed_triangle_set> {
using vertex_t = stl_vertex;
static size_t face_count(const indexed_triangle_set &m)
{
return m.indices.size();
}
static size_t vertex_count(const indexed_triangle_set &m)
{
return m.vertices.size();
}
static vertex_t vertex(const indexed_triangle_set &m, size_t idx)
{
return m.vertices[idx];
}
static void vertex(indexed_triangle_set &m, size_t idx, const vertex_t &v)
{
m.vertices[idx] = v;
}
static Index3 triangle(const indexed_triangle_set &m, size_t idx)
{
std::array<size_t, 3> t;
for (size_t i = 0; i < 3; ++i) t[i] = size_t(m.indices[idx](int(i)));
return t;
}
static void triangle(indexed_triangle_set &m, size_t fidx, const Index3 &t)
{
auto &face = m.indices[fidx];
face(0) = int(t[0]); face(1) = int(t[1]); face(2) = int(t[2]);
}
static void update(indexed_triangle_set &m, size_t vc, size_t fc)
{
m.vertices.resize(vc);
m.indices.resize(fc);
}
};
} // namespace SimplifyMesh
namespace Slic3r {
void simplify_mesh(indexed_triangle_set &m)
{
SimplifyMesh::implementation::SimplifiableMesh sm{&m};
sm.simplify_mesh_lossless();
}
}

View File

@ -1,23 +0,0 @@
#ifndef MESHSIMPLIFY_HPP
#define MESHSIMPLIFY_HPP
#include <vector>
#include <libslic3r/TriangleMesh.hpp>
namespace Slic3r {
void simplify_mesh(indexed_triangle_set &);
// TODO: (but this can be done with IGL as well)
// void simplify_mesh(indexed_triangle_set &, int face_count, float agressiveness = 0.5f);
template<class...Args> void simplify_mesh(TriangleMesh &m, Args &&...a)
{
simplify_mesh(m.its, std::forward<Args>(a)...);
m = TriangleMesh{ std::move(m.its) };
}
} // namespace Slic3r
#endif // MESHSIMPLIFY_H

View File

@ -1,670 +0,0 @@
// ///////////////////////////////////////////
//
// Mesh Simplification Tutorial
//
// (C) by Sven Forstmann in 2014
//
// License : MIT
// http://opensource.org/licenses/MIT
//
// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
//
// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile
// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification/
//
// libslic3r refactor by tamasmeszaros
#ifndef SIMPLIFYMESHIMPL_HPP
#define SIMPLIFYMESHIMPL_HPP
#include <vector>
#include <array>
#include <type_traits>
#include <algorithm>
#include <cmath>
#ifndef NDEBUG
#include <ostream>
#include <iostream>
#endif
namespace SimplifyMesh {
using Bary = std::array<double, 3>;
using Index3 = std::array<size_t, 3>;
template<class Vertex> struct vertex_traits {
using coord_type = typename Vertex::coord_type;
using compute_type = coord_type;
static coord_type x(const Vertex &v);
static coord_type& x(Vertex &v);
static coord_type y(const Vertex &v);
static coord_type& y(Vertex &v);
static coord_type z(const Vertex &v);
static coord_type& z(Vertex &v);
};
template<class Mesh> struct mesh_traits {
using vertex_t = typename Mesh::vertex_t;
static size_t face_count(const Mesh &m);
static size_t vertex_count(const Mesh &m);
static vertex_t vertex(const Mesh &m, size_t vertex_idx);
static void vertex(Mesh &m, size_t vertex_idx, const vertex_t &v);
static Index3 triangle(const Mesh &m, size_t face_idx);
static void triangle(Mesh &m, size_t face_idx, const Index3 &t);
static void update(Mesh &m, size_t vertex_count, size_t face_count);
};
namespace implementation {
// A shorter C++14 style form of the enable_if metafunction
template<bool B, class T>
using enable_if_t = typename std::enable_if<B, T>::type;
// Meta predicates for floating, integer and generic arithmetic types
template<class T, class O = T>
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
template<class T, class O = T>
using IntegerOnly = enable_if_t<std::is_integral<T>::value, O>;
template<class T, class O = T>
using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, O>;
template< class T >
struct remove_cvref {
using type = typename std::remove_cv<
typename std::remove_reference<T>::type>::type;
};
template< class T >
using remove_cvref_t = typename remove_cvref<T>::type;
template<class T> FloatingOnly<T, bool> is_approx(T val, T ref) { return std::abs(val - ref) < 1e-8; }
template<class T> IntegerOnly <T, bool> is_approx(T val, T ref) { val == ref; }
template<class T> class SymetricMatrix {
static const constexpr size_t N = 10;
public:
explicit SymetricMatrix(ArithmeticOnly<T> c = T()) { std::fill(m, m + N, c); }
// Make plane
SymetricMatrix(T a, T b, T c, T d)
{
m[0] = a * a; m[1] = a * b; m[2] = a * c; m[3] = a * d;
m[4] = b * b; m[5] = b * c; m[6] = b * d;
m[7] = c * c; m[8] = c * d;
m[9] = d * d;
}
T operator[](int c) const { return m[c]; }
// Determinant
T det(int a11, int a12, int a13,
int a21, int a22, int a23,
int a31, int a32, int a33) const
{
T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] +
m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] -
m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33];
return det;
}
const SymetricMatrix& operator+=(const SymetricMatrix& n)
{
for (size_t i = 0; i < N; ++i) m[i] += n[i];
return *this;
}
SymetricMatrix operator+(const SymetricMatrix& n)
{
SymetricMatrix self = *this;
return self += n;
}
T m[N];
};
template<class V> using TCoord = typename vertex_traits<remove_cvref_t<V>>::coord_type;
template<class V> using TCompute = typename vertex_traits<remove_cvref_t<V>>::compute_type;
template<class V> inline TCoord<V> x(const V &v) { return vertex_traits<remove_cvref_t<V>>::x(v); }
template<class V> inline TCoord<V> y(const V &v) { return vertex_traits<remove_cvref_t<V>>::y(v); }
template<class V> inline TCoord<V> z(const V &v) { return vertex_traits<remove_cvref_t<V>>::z(v); }
template<class V> inline TCoord<V>& x(V &v) { return vertex_traits<remove_cvref_t<V>>::x(v); }
template<class V> inline TCoord<V>& y(V &v) { return vertex_traits<remove_cvref_t<V>>::y(v); }
template<class V> inline TCoord<V>& z(V &v) { return vertex_traits<remove_cvref_t<V>>::z(v); }
template<class M> using TVertex = typename mesh_traits<remove_cvref_t<M>>::vertex_t;
template<class Mesh> using TMeshCoord = TCoord<TVertex<Mesh>>;
template<class Vertex> TCompute<Vertex> dot(const Vertex &v1, const Vertex &v2)
{
return TCompute<Vertex>(x(v1)) * x(v2) +
TCompute<Vertex>(y(v1)) * y(v2) +
TCompute<Vertex>(z(v1)) * z(v2);
}
template<class Vertex> Vertex cross(const Vertex &a, const Vertex &b)
{
return Vertex{y(a) * z(b) - z(a) * y(b),
z(a) * x(b) - x(a) * z(b),
x(a) * y(b) - y(a) * x(b)};
}
template<class Vertex> TCompute<Vertex> lengthsq(const Vertex &v)
{
return TCompute<Vertex>(x(v)) * x(v) + TCompute<Vertex>(y(v)) * y(v) +
TCompute<Vertex>(z(v)) * z(v);
}
template<class Vertex> void normalize(Vertex &v)
{
double square = std::sqrt(lengthsq(v));
x(v) /= square; y(v) /= square; z(v) /= square;
}
using Bary = std::array<double, 3>;
template<class Vertex>
Bary barycentric(const Vertex &p, const Vertex &a, const Vertex &b, const Vertex &c)
{
Vertex v0 = (b - a);
Vertex v1 = (c - a);
Vertex v2 = (p - a);
double d00 = dot(v0, v0);
double d01 = dot(v0, v1);
double d11 = dot(v1, v1);
double d20 = dot(v2, v0);
double d21 = dot(v2, v1);
double denom = d00 * d11 - d01 * d01;
double v = (d11 * d20 - d01 * d21) / denom;
double w = (d00 * d21 - d01 * d20) / denom;
double u = 1.0 - v - w;
return {u, v, w};
}
template<class Mesh> class SimplifiableMesh {
Mesh *m_mesh;
using Vertex = TVertex<Mesh>;
using Coord = TMeshCoord<Mesh>;
using HiPrecison = TCompute<TVertex<Mesh>>;
using SymMat = SymetricMatrix<HiPrecison>;
struct FaceInfo {
size_t idx;
double err[4] = {0.};
bool deleted = false, dirty = false;
Vertex n;
explicit FaceInfo(size_t id): idx(id) {}
};
struct VertexInfo {
size_t idx;
size_t tstart = 0, tcount = 0;
bool border = false;
SymMat q;
explicit VertexInfo(size_t id): idx(id) {}
};
struct Ref { size_t face; size_t vertex; };
std::vector<Ref> m_refs;
std::vector<FaceInfo> m_faceinfo;
std::vector<VertexInfo> m_vertexinfo;
void compact_faces();
void compact();
size_t mesh_vcount() const { return mesh_traits<Mesh>::vertex_count(*m_mesh); }
size_t mesh_facecount() const { return mesh_traits<Mesh>::face_count(*m_mesh); }
size_t vcount() const { return m_vertexinfo.size(); }
inline Vertex read_vertex(size_t vi) const
{
return mesh_traits<Mesh>::vertex(*m_mesh, vi);
}
inline Vertex read_vertex(const VertexInfo &vinf) const
{
return read_vertex(vinf.idx);
}
inline void write_vertex(size_t idx, const Vertex &v) const
{
mesh_traits<Mesh>::vertex(*m_mesh, idx, v);
}
inline void write_vertex(const VertexInfo &vinf, const Vertex &v) const
{
write_vertex(vinf.idx, v);
}
inline Index3 read_triangle(size_t fi) const
{
return mesh_traits<Mesh>::triangle(*m_mesh, fi);
}
inline Index3 read_triangle(const FaceInfo &finf) const
{
return read_triangle(finf.idx);
}
inline void write_triangle(size_t idx, const Index3 &t)
{
return mesh_traits<Mesh>::triangle(*m_mesh, idx, t);
}
inline void write_triangle(const FaceInfo &finf, const Index3 &t)
{
return write_triangle(finf.idx, t);
}
inline std::array<Vertex, 3> triangle_vertices(const Index3 &f) const
{
std::array<Vertex, 3> p;
for (size_t i = 0; i < 3; ++i) p[i] = read_vertex(f[i]);
return p;
}
// Error between vertex and Quadric
static double vertex_error(const SymMat &q, const Vertex &v)
{
Coord _x = x(v) , _y = y(v), _z = z(v);
return q[0] * _x * _x + 2 * q[1] * _x * _y + 2 * q[2] * _x * _z +
2 * q[3] * _x + q[4] * _y * _y + 2 * q[5] * _y * _z +
2 * q[6] * _y + q[7] * _z * _z + 2 * q[8] * _z + q[9];
}
// Error for one edge
double calculate_error(size_t id_v1, size_t id_v2, Vertex &p_result);
void calculate_error(FaceInfo &fi)
{
Vertex p;
Index3 t = read_triangle(fi);
for (size_t j = 0; j < 3; ++j)
fi.err[j] = calculate_error(t[j], t[(j + 1) % 3], p);
fi.err[3] = std::min(fi.err[0], std::min(fi.err[1], fi.err[2]));
}
void update_mesh(int iteration);
// Update triangle connections and edge error after a edge is collapsed
void update_triangles(size_t i, VertexInfo &vi, std::vector<bool> &deleted, int &deleted_triangles);
// Check if a triangle flips when this edge is removed
bool flipped(const Vertex &p, size_t i0, size_t i1, VertexInfo &v0, VertexInfo &v1, std::vector<bool> &deleted);
public:
explicit SimplifiableMesh(Mesh *m) : m_mesh{m}
{
static_assert(
std::is_arithmetic<Coord>::value,
"Coordinate type of mesh has to be an arithmetic type!");
m_faceinfo.reserve(mesh_traits<Mesh>::face_count(*m));
m_vertexinfo.reserve(mesh_traits<Mesh>::vertex_count(*m));
for (size_t i = 0; i < mesh_facecount(); ++i) m_faceinfo.emplace_back(i);
for (size_t i = 0; i < mesh_vcount(); ++i) m_vertexinfo.emplace_back(i);
}
template<class ProgressFn> void simplify_mesh_lossless(ProgressFn &&fn);
void simplify_mesh_lossless() { simplify_mesh_lossless([](int){}); }
};
template<class Mesh> void SimplifiableMesh<Mesh>::compact_faces()
{
auto it = std::remove_if(m_faceinfo.begin(), m_faceinfo.end(),
[](const FaceInfo &inf) { return inf.deleted; });
m_faceinfo.erase(it, m_faceinfo.end());
}
template<class M> void SimplifiableMesh<M>::compact()
{
for (auto &vi : m_vertexinfo) vi.tcount = 0;
compact_faces();
for (FaceInfo &fi : m_faceinfo)
for (size_t vidx : read_triangle(fi)) m_vertexinfo[vidx].tcount = 1;
size_t dst = 0;
for (VertexInfo &vi : m_vertexinfo) {
if (vi.tcount) {
vi.tstart = dst;
write_vertex(dst++, read_vertex(vi));
}
}
size_t vertex_count = dst;
dst = 0;
for (const FaceInfo &fi : m_faceinfo) {
Index3 t = read_triangle(fi);
for (size_t &idx : t) idx = m_vertexinfo[idx].tstart;
write_triangle(dst++, t);
}
mesh_traits<M>::update(*m_mesh, vertex_count, m_faceinfo.size());
}
template<class Mesh>
double SimplifiableMesh<Mesh>::calculate_error(size_t id_v1, size_t id_v2, Vertex &p_result)
{
// compute interpolated vertex
SymMat q = m_vertexinfo[id_v1].q + m_vertexinfo[id_v2].q;
bool border = m_vertexinfo[id_v1].border & m_vertexinfo[id_v2].border;
double error = 0;
HiPrecison det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
if (!is_approx(det, HiPrecison(0)) && !border)
{
// q_delta is invertible
x(p_result) = Coord(-1) / det * q.det(1, 2, 3, 4, 5, 6, 5, 7, 8); // vx = A41/det(q_delta)
y(p_result) = Coord( 1) / det * q.det(0, 2, 3, 1, 5, 6, 2, 7, 8); // vy = A42/det(q_delta)
z(p_result) = Coord(-1) / det * q.det(0, 1, 3, 1, 4, 6, 2, 5, 8); // vz = A43/det(q_delta)
error = vertex_error(q, p_result);
} else {
// det = 0 -> try to find best result
Vertex p1 = read_vertex(id_v1);
Vertex p2 = read_vertex(id_v2);
Vertex p3 = (p1 + p2) / 2;
double error1 = vertex_error(q, p1);
double error2 = vertex_error(q, p2);
double error3 = vertex_error(q, p3);
error = std::min(error1, std::min(error2, error3));
if (is_approx(error1, error)) p_result = p1;
if (is_approx(error2, error)) p_result = p2;
if (is_approx(error3, error)) p_result = p3;
}
return error;
}
template<class Mesh> void SimplifiableMesh<Mesh>::update_mesh(int iteration)
{
if (iteration > 0) compact_faces();
assert(mesh_vcount() == m_vertexinfo.size());
//
// Init Quadrics by Plane & Edge Errors
//
// required at the beginning ( iteration == 0 )
// recomputing during the simplification is not required,
// but mostly improves the result for closed meshes
//
if (iteration == 0) {
for (VertexInfo &vinf : m_vertexinfo) vinf.q = SymMat{};
for (FaceInfo &finf : m_faceinfo) {
Index3 t = read_triangle(finf);
std::array<Vertex, 3> p = triangle_vertices(t);
Vertex n = cross(Vertex(p[1] - p[0]), Vertex(p[2] - p[0]));
normalize(n);
finf.n = n;
for (size_t fi : t)
m_vertexinfo[fi].q += SymMat(x(n), y(n), z(n), -dot(n, p[0]));
calculate_error(finf);
}
}
// Init Reference ID list
for (VertexInfo &vi : m_vertexinfo) { vi.tstart = 0; vi.tcount = 0; }
for (FaceInfo &fi : m_faceinfo)
for (size_t vidx : read_triangle(fi))
m_vertexinfo[vidx].tcount++;
size_t tstart = 0;
for (VertexInfo &vi : m_vertexinfo) {
vi.tstart = tstart;
tstart += vi.tcount;
vi.tcount = 0;
}
// Write References
m_refs.resize(m_faceinfo.size() * 3);
for (size_t i = 0; i < m_faceinfo.size(); ++i) {
const FaceInfo &fi = m_faceinfo[i];
Index3 t = read_triangle(fi);
for (size_t j = 0; j < 3; ++j) {
VertexInfo &vi = m_vertexinfo[t[j]];
assert(vi.tstart + vi.tcount < m_refs.size());
Ref &ref = m_refs[vi.tstart + vi.tcount];
ref.face = i;
ref.vertex = j;
vi.tcount++;
}
}
// Identify boundary : vertices[].border=0,1
if (iteration == 0) {
for (VertexInfo &vi: m_vertexinfo) vi.border = false;
std::vector<size_t> vcount, vids;
for (VertexInfo &vi: m_vertexinfo) {
vcount.clear();
vids.clear();
for(size_t j = 0; j < vi.tcount; ++j) {
assert(vi.tstart + j < m_refs.size());
FaceInfo &fi = m_faceinfo[m_refs[vi.tstart + j].face];
Index3 t = read_triangle(fi);
for (size_t fid : t) {
size_t ofs=0;
while (ofs < vcount.size())
{
if (vids[ofs] == fid) break;
ofs++;
}
if (ofs == vcount.size())
{
vcount.emplace_back(1);
vids.emplace_back(fid);
}
else
vcount[ofs]++;
}
}
for (size_t j = 0; j < vcount.size(); ++j)
if(vcount[j] == 1) m_vertexinfo[vids[j]].border = true;
}
}
}
template<class Mesh>
void SimplifiableMesh<Mesh>::update_triangles(size_t i0,
VertexInfo & vi,
std::vector<bool> &deleted,
int &deleted_triangles)
{
Vertex p;
for (size_t k = 0; k < vi.tcount; ++k) {
assert(vi.tstart + k < m_refs.size());
Ref &r = m_refs[vi.tstart + k];
FaceInfo &fi = m_faceinfo[r.face];
if (fi.deleted) continue;
if (deleted[k]) {
fi.deleted = true;
deleted_triangles++;
continue;
}
Index3 t = read_triangle(fi);
t[r.vertex] = i0;
write_triangle(fi, t);
fi.dirty = true;
fi.err[0] = calculate_error(t[0], t[1], p);
fi.err[1] = calculate_error(t[1], t[2], p);
fi.err[2] = calculate_error(t[2], t[0], p);
fi.err[3] = std::min(fi.err[0], std::min(fi.err[1], fi.err[2]));
m_refs.emplace_back(r);
}
}
template<class Mesh>
bool SimplifiableMesh<Mesh>::flipped(const Vertex & p,
size_t /*i0*/,
size_t i1,
VertexInfo & v0,
VertexInfo & /*v1*/,
std::vector<bool> &deleted)
{
for (size_t k = 0; k < v0.tcount; ++k) {
size_t ridx = v0.tstart + k;
assert(ridx < m_refs.size());
FaceInfo &fi = m_faceinfo[m_refs[ridx].face];
if (fi.deleted) continue;
Index3 t = read_triangle(fi);
int s = m_refs[ridx].vertex;
size_t id1 = t[(s+1) % 3];
size_t id2 = t[(s+2) % 3];
if(id1 == i1 || id2 == i1) // delete ?
{
deleted[k] = true;
continue;
}
Vertex d1 = read_vertex(id1) - p;
normalize(d1);
Vertex d2 = read_vertex(id2) - p;
normalize(d2);
if (std::abs(dot(d1, d2)) > 0.999) return true;
Vertex n = cross(d1, d2);
normalize(n);
deleted[k] = false;
if (dot(n, fi.n) < 0.2) return true;
}
return false;
}
template<class Mesh>
template<class Fn> void SimplifiableMesh<Mesh>::simplify_mesh_lossless(Fn &&fn)
{
// init
for (FaceInfo &fi : m_faceinfo) fi.deleted = false;
// main iteration loop
int deleted_triangles=0;
std::vector<bool> deleted0, deleted1;
for (int iteration = 0; iteration < 9999; iteration ++) {
// update mesh constantly
update_mesh(iteration);
// clear dirty flag
for (FaceInfo &fi : m_faceinfo) fi.dirty = false;
//
// All triangles with edges below the threshold will be removed
//
// The following numbers works well for most models.
// If it does not, try to adjust the 3 parameters
//
double threshold = std::numeric_limits<double>::epsilon(); //1.0E-3 EPS; // Really? (tm)
fn(iteration);
for (FaceInfo &fi : m_faceinfo) {
if (fi.err[3] > threshold || fi.deleted || fi.dirty) continue;
for (size_t j = 0; j < 3; ++j) {
if (fi.err[j] > threshold) continue;
Index3 t = read_triangle(fi);
size_t i0 = t[j];
VertexInfo &v0 = m_vertexinfo[i0];
size_t i1 = t[(j + 1) % 3];
VertexInfo &v1 = m_vertexinfo[i1];
// Border check
if(v0.border != v1.border) continue;
// Compute vertex to collapse to
Vertex p;
calculate_error(i0, i1, p);
deleted0.resize(v0.tcount); // normals temporarily
deleted1.resize(v1.tcount); // normals temporarily
// don't remove if flipped
if (flipped(p, i0, i1, v0, v1, deleted0)) continue;
if (flipped(p, i1, i0, v1, v0, deleted1)) continue;
// not flipped, so remove edge
write_vertex(v0, p);
v0.q = v1.q + v0.q;
size_t tstart = m_refs.size();
update_triangles(i0, v0, deleted0, deleted_triangles);
update_triangles(i0, v1, deleted1, deleted_triangles);
assert(m_refs.size() >= tstart);
size_t tcount = m_refs.size() - tstart;
if(tcount <= v0.tcount)
{
// save ram
if (tcount) {
auto from = m_refs.begin() + tstart, to = from + tcount;
std::copy(from, to, m_refs.begin() + v0.tstart);
}
}
else
// append
v0.tstart = tstart;
v0.tcount = tcount;
break;
}
}
if (deleted_triangles <= 0) break;
deleted_triangles = 0;
}
compact();
}
} // namespace implementation
} // namespace SimplifyMesh
#endif // SIMPLIFYMESHIMPL_HPP

View File

@ -13,7 +13,10 @@
#include <functional>
#include <optional>
#ifndef NDEBUG
#define HAS_GLSAFE
#endif // NDEBUG
#ifdef HAS_GLSAFE
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }

View File

@ -559,7 +559,9 @@ PagePrinters::PagePrinters(ConfigWizard *parent,
append(picker);
printer_pickers.push_back(picker);
has_printers = true;
}
}
void PagePrinters::select_all(bool select, bool alternates)
@ -599,7 +601,7 @@ std::set<std::string> PagePrinters::get_selected_models()
void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
{
if (technology == T_FFF
if (is_primary_printer_page
&& (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY)
&& printer_pickers.size() > 0
&& printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) {
@ -770,7 +772,11 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
, second_line
);
} else {
wxString second_line = printer_names.empty() ? "" : format_wxstr(_L("Only the following installed printers are compatible with the selected %1%:"), materials->technology == T_FFF ? _L("filament") : _L("SLA material"));
wxString second_line;
if (!printer_names.empty())
second_line = (materials->technology == T_FFF ?
_L("Only the following installed printers are compatible with the selected filaments") :
_L("Only the following installed printers are compatible with the selected SLA materials")) + ":";
text = wxString::Format(
"<html>"
"<style>"
@ -1904,25 +1910,28 @@ void ConfigWizard::priv::load_pages()
index->add_page(page_welcome);
// Printers
index->add_page(page_fff);
if (!only_sla_mode)
index->add_page(page_fff);
index->add_page(page_msla);
index->add_page(page_vendors);
for (const auto &pages : pages_3rdparty) {
for ( PagePrinters* page : { pages.second.first, pages.second.second })
if (page && page->install)
index->add_page(page);
}
index->add_page(page_custom);
if (page_custom->custom_wanted()) {
index->add_page(page_firmware);
index->add_page(page_bed);
index->add_page(page_diams);
index->add_page(page_temps);
}
if (!only_sla_mode) {
index->add_page(page_vendors);
for (const auto &pages : pages_3rdparty) {
for ( PagePrinters* page : { pages.second.first, pages.second.second })
if (page && page->install)
index->add_page(page);
}
index->add_page(page_custom);
if (page_custom->custom_wanted()) {
index->add_page(page_firmware);
index->add_page(page_bed);
index->add_page(page_diams);
index->add_page(page_temps);
}
// Filaments & Materials
if (any_fff_selected) { index->add_page(page_filaments); }
if (any_fff_selected) { index->add_page(page_filaments); }
}
if (any_sla_selected) { index->add_page(page_sla_materials); }
// there should to be selected at least one printer
@ -1956,7 +1965,7 @@ void ConfigWizard::priv::init_dialog_size()
9*disp_rect.width / 10,
9*disp_rect.height / 10);
const int width_hint = index->GetSize().GetWidth() + page_fff->get_width() + 30 * em(); // XXX: magic constant, I found no better solution
const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em()); // XXX: magic constant, I found no better solution
if (width_hint < window_rect.width) {
window_rect.x += (window_rect.width - width_hint) / 2;
window_rect.width = width_hint;
@ -2483,7 +2492,7 @@ static std::string get_first_added_preset(const std::map<std::string, std::strin
bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes)
{
wxString header, caption = _L("Configuration is editing from ConfigWizard");
wxString header, caption = _L("Configuration is edited in ConfigWizard");
const auto enabled_vendors = appconfig_new.vendors();
bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model());
@ -2556,7 +2565,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
}
if (!check_unsaved_preset_changes)
if ((check_unsaved_preset_changes = install_bundles.size() > 0))
header = _L_PLURAL("New vendor was installed and one of its printer will be activated", "New vendors were installed and one of theirs printer will be activated", install_bundles.size());
header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size());
#ifdef __linux__
// Desktop integration on Linux
@ -2585,7 +2594,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
break;
}
if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?")))
if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?")))
return false;
if (check_unsaved_preset_changes &&
@ -2725,7 +2734,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem,
{preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
if (page_custom->custom_wanted()) {
if (!only_sla_mode && page_custom->custom_wanted()) {
// if unsaved changes was not cheched till this moment
if (!check_unsaved_preset_changes &&
!wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes))
@ -2838,25 +2847,38 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->add_page(p->page_welcome = new PageWelcome(this));
p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF);
p->add_page(p->page_fff);
p->only_sla_mode = !p->page_fff->has_printers;
if (!p->only_sla_mode) {
p->add_page(p->page_fff);
p->page_fff->is_primary_printer_page = true;
}
p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA);
p->add_page(p->page_msla);
if (p->only_sla_mode) {
p->page_msla->is_primary_printer_page = true;
}
// Pages for 3rd party vendors
p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors
p->add_page(p->page_vendors = new PageVendors(this));
p->add_page(p->page_custom = new PageCustom(this));
p->custom_printer_selected = p->page_custom->custom_wanted();
if (!p->only_sla_mode) {
// Pages for 3rd party vendors
p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors
p->add_page(p->page_vendors = new PageVendors(this));
p->add_page(p->page_custom = new PageCustom(this));
p->custom_printer_selected = p->page_custom->custom_wanted();
}
p->any_sla_selected = p->check_sla_selected();
p->any_fff_selected = p->check_fff_selected();
if (p->only_sla_mode)
p->any_fff_selected = p->check_fff_selected();
p->update_materials(T_ANY);
if (!p->only_sla_mode)
p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
_L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") ));
p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
_L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") ));
p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
_L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") ));

View File

@ -257,6 +257,9 @@ struct PagePrinters: ConfigWizardPage
std::string get_vendor_id() const { return printer_pickers.empty() ? "" : printer_pickers[0]->vendor_id; }
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
bool has_printers { false };
bool is_primary_printer_page { false };
};
// Here we extend wxListBox and wxCheckListBox
@ -548,7 +551,9 @@ struct ConfigWizard::priv
std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition
bool any_fff_selected; // Used to decide whether to display Filaments page
bool any_sla_selected; // Used to decide whether to display SLA Materials page
bool custom_printer_selected;
bool custom_printer_selected { false };
// Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers)
bool only_sla_mode { false };
wxScrolledWindow *hscroll = nullptr;
wxBoxSizer *hscroll_sizer = nullptr;

View File

@ -366,7 +366,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
}
} else {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not find applications directory";
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found.";
return;
}
}
@ -374,8 +374,8 @@ void DesktopIntegrationDialog::perform_desktop_integration()
}
if(target_dir_desktop.empty()) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not find applications directory";
show_error(nullptr, _L("Performing desktop integration failed - could not find applications directory."));
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found.";
show_error(nullptr, _L("Performing desktop integration failed because the application directory was not found."));
return;
}
// save path to desktop file

View File

@ -24,6 +24,7 @@
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
@ -1143,7 +1144,7 @@ void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObje
}
}
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx)
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv)
{
for (GLVolume* vol : m_volumes.volumes) {
if (vol->composite_id.object_id == 1000) { // wipe tower
@ -1151,7 +1152,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
}
else {
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)) {
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
&& (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) {
vol->is_active = visible;
if (instance_idx == -1) {
@ -5238,11 +5240,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
// visible when inside modifier meshes etc.
{
const GLGizmosManager& gm = get_gizmos_manager();
GLGizmosManager::EType type = gm.get_current_type();
if (type == GLGizmosManager::FdmSupports
|| type == GLGizmosManager::Seam
|| type == GLGizmosManager::MmuSegmentation
|| type == GLGizmosManager::Simplify ) {
// GLGizmosManager::EType type = gm.get_current_type();
if (dynamic_cast<GLGizmoPainterBase*>(gm.get_current())) {
shader->stop_using();
gm.render_painter_gizmo();
shader->start_using();

View File

@ -635,7 +635,7 @@ public:
void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); }
void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1, const ModelVolume* mv = nullptr);
void update_instance_printable_state_for_object(size_t obj_idx);
void update_instance_printable_state_for_objects(const std::vector<size_t>& object_idxs);

View File

@ -204,7 +204,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
}
catch (const std::exception &e)
{
wxLogError(format_wxstr(_L("Internal error when changing value for %1%: %2%"), opt_key, e.what()));
wxLogError(format_wxstr("Internal error when changing value for %1%: %2%", opt_key, e.what()));
}
}

View File

@ -812,13 +812,11 @@ static boost::optional<Semver> parse_semver_from_ini(std::string path)
std::stringstream buffer;
buffer << stream.rdbuf();
std::string body = buffer.str();
size_t end_line = body.find_first_of("\n\r");
body.resize(end_line);
size_t start = body.find("PrusaSlicer ");
if (start == std::string::npos)
return boost::none;
body = body.substr(start + 12);
size_t end = body.find_first_of(" \n\r");
size_t end = body.find_first_of(" \n");
if (end < body.size())
body.resize(end);
return Semver::parse(body);
@ -2497,7 +2495,7 @@ bool GUI_App::can_load_project()
int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified."));
if (saved_project == wxID_CANCEL ||
(plater()->is_project_dirty() && saved_project == wxID_NO &&
!check_and_save_current_preset_changes(_L("Project is loading"), _L("Loading a new project while some presets are modified."))))
!check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved."))))
return false;
return true;
}
@ -2879,7 +2877,7 @@ void GUI_App::window_pos_sanitize(wxTopLevelWindow* window)
bool GUI_App::config_wizard_startup()
{
if (!m_app_conf_exists || preset_bundle->printers.size() <= 1) {
if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) {
run_wizard(ConfigWizard::RR_DATA_EMPTY);
return true;
} else if (get_app_config()->legacy_datadir()) {
@ -2921,7 +2919,7 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/*
bool launch = true;
if (get_app_config()->get("suppress_hyperlinks").empty()) {
RichMessageDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
RichMessageDialog dialog(nullptr, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
dialog.ShowCheckBox(_L("Remember my choice"));
int answer = dialog.ShowModal();
launch = answer == wxID_YES;

View File

@ -419,10 +419,10 @@ MeshErrorsInfo ObjectList::get_mesh_errors_info(const int obj_idx, const int vol
if (repaired.facets_reversed > 0)
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet reversed", "%1$d facets reversed", repaired.facets_reversed), repaired.facets_reversed) + "\n";
if (repaired.backwards_edges > 0)
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d backwards edge", "%1$d backwards edges", repaired.backwards_edges), repaired.backwards_edges) + "\n";
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d backward edge", "%1$d backward edges", repaired.backwards_edges), repaired.backwards_edges) + "\n";
}
if (!stats.manifold()) {
remaining_info = format_wxstr(_L_PLURAL("Remaining %1$d open edge", "Remaining %1$d open edges", stats.open_edges), stats.open_edges);
remaining_info = format_wxstr(_L_PLURAL("%1$d open edge", "%1$d open edges", stats.open_edges), stats.open_edges);
tooltip += _L("Remaning errors") + ":\n";
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d open edge", "%1$d open edges", stats.open_edges), stats.open_edges) + "\n";
@ -4165,7 +4165,7 @@ void ObjectList::fix_through_netfabb()
wxString msg;
wxString bullet_suf = "\n - ";
if (!succes_models.empty()) {
msg = _L_PLURAL("Folowing model is repaired successfully", "Folowing models are repaired successfully", succes_models.size()) + ":";
msg = _L_PLURAL("The following model was repaired successfully", "The following models were repaired successfully", succes_models.size()) + ":";
for (auto& model : succes_models)
msg += bullet_suf + from_u8(model);
msg += "\n\n";

View File

@ -727,7 +727,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
NotificationType::SignDetected, NotificationManager::NotificationLevel::PrintInfoNotificationLevel,
_u8L("NOTE:") + "\n" +
format(_u8L("Sliced object \"%1%\" looks like a logo or a sign"), object->model_object()->name) + "\n",
_u8L("Apply automatic color change"),
_u8L("Apply color change automatically"),
[this](wxEvtHandler*) {
m_layers_slider->auto_color_change();
return true;

View File

@ -286,7 +286,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width);
m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width);
m_imgui->disabled_end();
} else {

View File

@ -118,7 +118,7 @@ bool GLGizmoMmuSegmentation::on_init()
m_desc["second_color"] = _L("Second color");
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Remove painted color");
m_desc["remove_all"] = _L("Remove all painted areas");
m_desc["remove_all"] = _L("Clear all");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
m_desc["pointer"] = _L("Triangles");

View File

@ -97,6 +97,8 @@ public:
// will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
static const constexpr size_t EXTRUDERS_LIMIT = 16;
virtual const float get_cursor_radius_min() const { return CursorRadiusMin; }
protected:
std::array<float, 4> get_cursor_sphere_left_button_color() const override;
std::array<float, 4> get_cursor_sphere_right_button_color() const override;
@ -120,6 +122,8 @@ protected:
std::vector<std::array<float, 4>> m_modified_extruders_colors;
std::vector<int> m_original_volumes_extruder_idxs;
static const constexpr float CursorRadiusMin = 0.1f; // cannot be zero
private:
bool on_init() override;

View File

@ -254,8 +254,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
}
else if (alt_down) {
if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) {
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - this->get_cursor_radius_step(), this->get_cursor_radius_min())
: std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max());
m_parent.set_as_dirty();
return true;
} else if (m_tool_type == ToolType::SMART_FILL) {

View File

@ -99,20 +99,11 @@ protected:
GLPaintContour m_paint_contour;
};
class GLGizmoTransparentRender
{
public:
// Following function renders the triangles and cursor. Having this separated
// from usual on_render method allows to render them before transparent
// objects, so they can be seen inside them. The usual on_render is called
// after all volumes (including transparent ones) are rendered.
virtual void render_painter_gizmo() const = 0;
};
// Following class is a base class for a gizmo with ability to paint on mesh
// using circular blush (such as FDM supports gizmo and seam painting gizmo).
// The purpose is not to duplicate code related to mesh painting.
class GLGizmoPainterBase : public GLGizmoTransparentRender, public GLGizmoBase
class GLGizmoPainterBase : public GLGizmoBase
{
private:
ObjectID m_old_mo_id;
@ -125,6 +116,16 @@ public:
virtual void set_painter_gizmo_data(const Selection& selection);
virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
// Following function renders the triangles and cursor. Having this separated
// from usual on_render method allows to render them before transparent
// objects, so they can be seen inside them. The usual on_render is called
// after all volumes (including transparent ones) are rendered.
virtual void render_painter_gizmo() const = 0;
virtual const float get_cursor_radius_min() const { return CursorRadiusMin; }
virtual const float get_cursor_radius_max() const { return CursorRadiusMax; }
virtual const float get_cursor_radius_step() const { return CursorRadiusStep; }
protected:
virtual void render_triangles(const Selection& selection) const;
void render_cursor() const;

View File

@ -1,60 +1,94 @@
#include "GLGizmoSimplify.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/format.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/QuadricEdgeCollapse.hpp"
#include <GL/glew.h>
#include <thread>
namespace Slic3r::GUI {
// Extend call after only when Simplify gizmo is still alive
static void call_after_if_active(std::function<void()> fn, GUI_App* app = &wxGetApp())
{
// check application GUI
if (app == nullptr) return;
app->CallAfter([fn, app]() {
// app must exist because it call this
// if (app == nullptr) return;
const Plater *plater = app->plater();
if (plater == nullptr) return;
const GLCanvas3D *canvas = plater->canvas3D();
if (canvas == nullptr) return;
const GLGizmosManager &mng = canvas->get_gizmos_manager();
// check if simplify is still activ gizmo
if (mng.get_current_type() != GLGizmosManager::Simplify) return;
fn();
});
}
static ModelVolume* get_model_volume(const Selection& selection, Model& model)
{
const Selection::IndicesList& idxs = selection.get_volume_idxs();
// only one selected volume
if (idxs.size() != 1)
return nullptr;
const GLVolume* selected_volume = selection.get_volume(*idxs.begin());
if (selected_volume == nullptr)
return nullptr;
const GLVolume::CompositeID& cid = selected_volume->composite_id;
const ModelObjectPtrs& objs = model.objects;
if (cid.object_id < 0 || objs.size() <= static_cast<size_t>(cid.object_id))
return nullptr;
const ModelObject* obj = objs[cid.object_id];
if (cid.volume_id < 0 || obj->volumes.size() <= static_cast<size_t>(cid.volume_id))
return nullptr;
return obj->volumes[cid.volume_id];
}
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
const std::string &icon_filename,
unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, -1)
, m_state(State::settings)
, m_is_valid_result(false)
, m_exist_preview(false)
, m_progress(0)
, m_volume(nullptr)
, m_obj_index(0)
, m_need_reload(false)
, m_show_wireframe(false)
, m_move_to_center(false)
// translation for GUI size
, 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"))
// for wireframe
, m_wireframe_VBO_id(0)
, m_wireframe_IBO_id(0)
, m_wireframe_IBO_size(0)
{}
GLGizmoSimplify::~GLGizmoSimplify() {
m_state = State::canceling;
if (m_worker.joinable()) m_worker.join();
free_gpu();
GLGizmoSimplify::~GLGizmoSimplify()
{
stop_worker_thread_request();
if (m_worker.joinable())
m_worker.join();
m_glmodel.reset();
}
bool GLGizmoSimplify::on_esc_key_down() {
if (m_state == State::settings || m_state == State::canceling)
return false;
/*if (!m_is_worker_running)
return false;
m_state = State::canceling;
return true;
stop_worker_thread_request();
return true;*/
}
// while opening needs GLGizmoSimplify to set window position
void GLGizmoSimplify::add_simplify_suggestion_notification(
const std::vector<size_t> &object_ids,
const ModelObjectPtrs & objects,
const std::vector<ModelObject*>& objects,
NotificationManager & manager)
{
std::vector<size_t> big_ids;
@ -74,14 +108,10 @@ void GLGizmoSimplify::add_simplify_suggestion_notification(
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 "
std::string t = GUI::format(_u8L(
"Processing model '%1%' 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,
objects[object_id]->name);
// std::stringstream text;
// text << t << "\n";
"amount of triangles."), objects[object_id]->name);
std::string hypertext = _u8L("Simplify model");
std::function<bool(wxEvtHandler *)> open_simplify =
@ -116,39 +146,47 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
{
create_gui_cfg();
const Selection &selection = m_parent.get_selection();
int obj_index = selection.get_object_idx();
ModelVolume *act_volume = get_volume(selection, wxGetApp().plater()->model());
const ModelVolume *act_volume = get_model_volume(selection, wxGetApp().plater()->model());
if (act_volume == nullptr) {
switch (m_state) {
case State::settings: close(); break;
case State::canceling: break;
default: m_state = State::canceling;
}
stop_worker_thread_request();
close();
return;
}
bool is_cancelling = false;
bool is_worker_running = false;
bool is_result_ready = false;
int progress = 0;
{
std::lock_guard lk(m_state_mutex);
is_cancelling = m_state.status == State::cancelling;
is_worker_running = m_state.status == State::running;
is_result_ready = bool(m_state.result);
progress = m_state.progress;
}
// Whether to trigger calculation after rendering is done.
bool start_process = false;
// Check selection of new volume
// Do not reselect object when processing
if (act_volume != m_volume && m_state == State::settings) {
if (act_volume != m_volume) {
bool change_window_position = (m_volume == nullptr);
// select different model
if (m_volume != nullptr && m_original_its.has_value()) {
set_its(*m_original_its);
}
// close suggestion notification
auto notification_manager = wxGetApp().plater()->get_notification_manager();
notification_manager->remove_simplify_suggestion_with_id(act_volume->get_object()->id());
m_obj_index = obj_index; // to remember correct object
m_volume = act_volume;
m_original_its = {};
m_configuration.decimate_ratio = 50.; // default value
m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size());
m_is_valid_result = false;
m_exist_preview = false;
init_wireframe();
live_preview();
init_model(m_volume->mesh().its);
// Start processing. If we switched from another object, process will
// stop the background thread and it will restart itself later.
start_process = true;
// set window position
if (m_move_to_center && change_window_position) {
@ -179,12 +217,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse;
m_imgui->begin(on_get_name(), flag);
size_t triangle_count = m_volume->mesh().its.indices.size();
// already reduced mesh
if (m_original_its.has_value())
triangle_count = m_original_its->indices.size();
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_mesh_name + ":");
ImGui::SameLine(m_gui_cfg->top_left_width);
std::string name = m_volume->name;
@ -193,21 +225,16 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
m_imgui->text(name);
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":");
ImGui::SameLine(m_gui_cfg->top_left_width);
m_imgui->text(std::to_string(triangle_count));
/*
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_preview + ":");
ImGui::SameLine(m_gui_cfg->top_left_width);
if (m_exist_preview) {
m_imgui->text(std::to_string(m_volume->mesh().its.indices.size()));
} else {
m_imgui->text("---");
}*/
size_t orig_triangle_count = m_volume->mesh().its.indices.size();
m_imgui->text(std::to_string(orig_triangle_count));
ImGui::Separator();
if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) {
m_configuration.use_count = !m_configuration.use_count;
live_preview();
start_process = true;
}
ImGui::SameLine();
m_imgui->disabled_begin(m_configuration.use_count);
@ -232,21 +259,21 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
case 3: m_configuration.max_error = 0.5f; break;
case 4: m_configuration.max_error = 1.f; break;
}
live_preview();
start_process = true;
}
m_imgui->disabled_end(); // !use_count
if (ImGui::RadioButton("##use_count", m_configuration.use_count)) {
m_configuration.use_count = !m_configuration.use_count;
live_preview();
start_process = true;
}
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());
if (!m_configuration.use_count) {
m_configuration.wanted_count = static_cast<uint32_t>(m_triangle_count);
m_configuration.decimate_ratio =
(1.0f - (m_configuration.wanted_count / (float) triangle_count)) * 100.f;
(1.0f - (m_configuration.wanted_count / (float) orig_triangle_count)) * 100.f;
}
m_imgui->disabled_begin(!m_configuration.use_count);
@ -261,8 +288,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
m_configuration.decimate_ratio = 0.01f;
if (m_configuration.decimate_ratio > 100.f)
m_configuration.decimate_ratio = 100.f;
m_configuration.fix_count_by_ratio(triangle_count);
live_preview();
m_configuration.fix_count_by_ratio(orig_triangle_count);
start_process = true;
}
ImGui::NewLine();
@ -270,79 +297,37 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
ImGui::Text(_u8L("%d triangles").c_str(), m_configuration.wanted_count);
m_imgui->disabled_end(); // use_count
if (ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe)) {
if (m_show_wireframe) init_wireframe();
else free_gpu();
}
ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe);
bool is_canceling = m_state == State::canceling;
m_imgui->disabled_begin(is_canceling);
if (m_imgui->button(_u8L("Cancel"))) {
if (m_state == State::settings) {
if (m_original_its.has_value()) {
set_its(*m_original_its);
m_state = State::close_on_end;
} else {
close();
}
} else {
m_state = State::canceling;
}
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_canceling)
ImGui::SetTooltip("%s", _u8L("Operation already canceling. Please wait few seconds.").c_str());
m_imgui->disabled_end(); // state canceling
m_imgui->disabled_begin(is_cancelling);
if (m_imgui->button(_L("Close"))) {
close();
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_cancelling)
ImGui::SetTooltip("%s", _u8L("Operation already cancelling. Please wait few seconds.").c_str());
m_imgui->disabled_end(); // state cancelling
ImGui::SameLine();
bool is_processing = m_state != State::settings;
m_imgui->disabled_begin(is_processing);
if (m_imgui->button(_u8L("Apply"))) {
if (!m_is_valid_result) {
m_state = State::close_on_end;
process();
} else if (m_exist_preview) {
// use preview and close
after_apply();
} else { // no changes made
close();
}
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_processing)
m_imgui->disabled_begin(is_worker_running || ! is_result_ready);
if (m_imgui->button(_L("Apply"))) {
apply_simplify();
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_worker_running)
ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str());
m_imgui->disabled_end(); // state !settings
// draw progress bar
if (is_processing) { // apply or preview
if (is_worker_running) { // apply or preview
ImGui::SameLine(m_gui_cfg->bottom_left_width);
// draw progress bar
char buf[32];
sprintf(buf, L("Process %d / 100"), m_progress);
ImGui::ProgressBar(m_progress / 100., ImVec2(m_gui_cfg->input_width, 0.f), buf);
std::string progress_text = GUI::format(_L("Process %1% / 100"), std::to_string(progress));
ImVec2 progress_size(m_gui_cfg->input_width, 0.f);
ImGui::ProgressBar(progress / 100., progress_size, progress_text.c_str());
}
m_imgui->end();
// refresh view when needed
if (m_need_reload) {
m_need_reload = false;
bool close_on_end = (m_state == State::close_on_end);
// Reload visualization of mesh - change VBO, FBO on GPU
m_parent.reload_scene(true);
// set m_state must be before close() !!!
m_state = State::settings;
if (close_on_end) after_apply();
else init_wireframe();
// Fix warning icon in object list
wxGetApp().obj_list()->update_item_error_icon(m_obj_index, -1);
}
if (start_process)
process();
}
void GLGizmoSimplify::after_apply() {
// set flag to NOT revert changes when switch GLGizmoBase::m_state
m_exist_preview = false;
// fix hollowing, sla support points, modifiers, ...
auto plater = wxGetApp().plater();
plater->changed_mesh(m_obj_index);
close();
}
void GLGizmoSimplify::close() {
// close gizmo == open it again
@ -350,117 +335,158 @@ void GLGizmoSimplify::close() {
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
}
void GLGizmoSimplify::live_preview() {
m_is_valid_result = false;
if (m_state != State::settings) {
// already canceling process
if (m_state == State::canceling) return;
void GLGizmoSimplify::stop_worker_thread_request()
{
std::lock_guard lk(m_state_mutex);
if (m_state.status == State::running)
m_state.status = State::Status::cancelling;
}
// wait until cancel
if (m_worker.joinable()) {
m_state = State::canceling;
m_dealy_process_cv.notify_one();
m_worker.join();
// Following is called from a UI thread when the worker terminates
// worker calls it through a CallAfter.
void GLGizmoSimplify::worker_finished()
{
{
std::lock_guard lk(m_state_mutex);
if (m_state.status == State::running) {
// Someone started the worker again, before this callback
// was called. Do nothing.
return;
}
}
m_state = State::preview;
process();
if (m_worker.joinable())
m_worker.join();
if (GLGizmoBase::m_state == Off)
return;
if (m_state.result)
init_model(*m_state.result);
if (m_state.config != m_configuration || m_state.mv != m_volume) {
// Settings were changed, restart the worker immediately.
process();
}
request_rerender(true);
}
void GLGizmoSimplify::process()
{
if (m_volume == nullptr) return;
if (m_volume->mesh().its.indices.empty()) return;
size_t count_triangles = m_volume->mesh().its.indices.size();
// Is neccessary simplification
if ((m_configuration.use_count && m_configuration.wanted_count >= count_triangles) ||
(!m_configuration.use_count && m_configuration.max_error <= 0.f)) {
// Exist different original volume?
if (m_original_its.has_value() &&
m_original_its->indices.size() != count_triangles) {
indexed_triangle_set its = *m_original_its; // copy
set_its(its);
}
m_is_valid_result = true;
if (m_volume == nullptr || m_volume->mesh().its.indices.empty())
return;
// re-render bargraph
set_dirty();
m_parent.schedule_extra_frame(0);
bool configs_match = false;
bool result_valid = false;
bool is_worker_running = false;
{
std::lock_guard lk(m_state_mutex);
configs_match = (m_volume == m_state.mv && m_state.config == m_configuration);
result_valid = bool(m_state.result);
is_worker_running = m_state.status == State::running;
}
if ((result_valid || is_worker_running) && configs_match) {
// Either finished or waiting for result already. Nothing to do.
return;
}
// when not store original volume store it for cancelation
if (!m_original_its.has_value()) {
m_original_its = m_volume->mesh().its; // copy
// store previous state
auto plater = wxGetApp().plater();
plater->take_snapshot(_u8L("Simplify ") + m_volume->name);
plater->clear_before_change_mesh(m_obj_index);
if (is_worker_running && ! configs_match) {
// Worker is running with outdated config. Stop it. It will
// restart itself when cancellation is done.
stop_worker_thread_request();
return;
}
m_progress = 0;
if (m_worker.joinable()) m_worker.join();
m_worker = std::thread([this]() {
{// delay before process
std::unique_lock<std::mutex> lk(m_state_mutex);
auto is_modify = [this]() { return m_state == State::canceling; };
if (m_dealy_process_cv.wait_for(lk, m_gui_cfg->prcess_delay, is_modify)) {
// exist modification
m_state = State::settings;
request_rerender();
return;
}
}
if (m_worker.joinable()) {
// This can happen when process() is called after previous worker terminated,
// but before the worker_finished callback was called. In this case, just join the thread,
// the callback will check this and do nothing.
m_worker.join();
}
// store original triangles
uint32_t triangle_count = (m_configuration.use_count) ? m_configuration.wanted_count : 0;
float max_error = (!m_configuration.use_count) ? m_configuration.max_error : std::numeric_limits<float>::max();
// Copy configuration that will be used.
m_state.config = m_configuration;
m_state.mv = m_volume;
m_state.status = State::running;
std::function<void(void)> throw_on_cancel = [&]() {
if (m_state == State::canceling) {
// Create a copy of current mesh to pass to the worker thread.
// Using unique_ptr instead of pass-by-value to avoid an extra
// copy (which would happen when passing to std::thread).
auto its = std::make_unique<indexed_triangle_set>(m_volume->mesh().its);
m_worker = std::thread([this](std::unique_ptr<indexed_triangle_set> its) {
// Checks that the UI thread did not request cancellation, throws if so.
std::function<void(void)> throw_on_cancel = [this]() {
std::lock_guard lk(m_state_mutex);
if (m_state.status == State::cancelling)
throw SimplifyCanceledException();
}
};
int64_t last = 0;
std::function<void(int)> statusfn = [this, &last](int percent) {
m_progress = percent;
// check max 4fps
int64_t now = m_parent.timestamp_now();
if ((now - last) < 250) return;
last = now;
request_rerender();
// Called by worker thread, updates progress bar.
// Using CallAfter so the rerequest function is run in UI thread.
std::function<void(int)> statusfn = [this](int percent) {
std::lock_guard lk(m_state_mutex);
m_state.progress = percent;
call_after_if_active([this]() { request_rerender(); });
};
indexed_triangle_set collapsed = *m_original_its; // copy
try {
its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn);
set_its(collapsed);
m_is_valid_result = true;
m_exist_preview = true;
} catch (SimplifyCanceledException &) {
// set state out of main thread
m_state = State::settings;
// Initialize.
uint32_t triangle_count = 0;
float max_error = std::numeric_limits<float>::max();
{
std::lock_guard lk(m_state_mutex);
if (m_state.config.use_count)
triangle_count = m_state.config.wanted_count;
if (! m_state.config.use_count)
max_error = m_state.config.max_error;
m_state.progress = 0;
m_state.result.reset();
m_state.status = State::Status::running;
}
// need to render last status fn to change bar graph to buttons
request_rerender();
});
// Start the actual calculation.
try {
its_quadric_edge_collapse(*its, triangle_count, &max_error, throw_on_cancel, statusfn);
} catch (SimplifyCanceledException &) {
std::lock_guard lk(m_state_mutex);
m_state.status = State::idle;
}
std::lock_guard lk(m_state_mutex);
if (m_state.status == State::Status::running) {
// We were not cancelled, the result is valid.
m_state.status = State::Status::idle;
m_state.result = std::move(its);
}
// Update UI. Use CallAfter so the function is run on UI thread.
call_after_if_active([this]() { worker_finished(); });
}, std::move(its));
}
void GLGizmoSimplify::set_its(indexed_triangle_set &its) {
if (m_volume == nullptr) return; // could appear after process
m_volume->set_mesh(its);
m_volume->calculate_convex_hull();
m_volume->set_new_unique_id();
m_volume->get_object()->invalidate_bounding_box();
m_need_reload = true;
void GLGizmoSimplify::apply_simplify() {
const Selection& selection = m_parent.get_selection();
int object_idx = selection.get_object_idx();
auto plater = wxGetApp().plater();
plater->take_snapshot(_u8L("Simplify ") + m_volume->name);
plater->clear_before_change_mesh(object_idx);
ModelVolume* mv = get_model_volume(selection, wxGetApp().model());
assert(mv == m_volume);
mv->set_mesh(std::move(*m_state.result));
m_state.result.reset();
mv->calculate_convex_hull();
mv->set_new_unique_id();
mv->get_object()->invalidate_bounding_box();
mv->get_object()->ensure_on_bed(true); // allow negative z
// fix hollowing, sla support points, modifiers, ...
plater->changed_mesh(object_idx);
// Fix warning icon in object list
wxGetApp().obj_list()->update_item_error_icon(object_idx, -1);
close();
}
bool GLGizmoSimplify::on_is_activable() const
@ -472,39 +498,11 @@ void GLGizmoSimplify::on_set_state()
{
// Closing gizmo. e.g. selecting another one
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
// can appear when delete objects
bool empty_selection = m_parent.get_selection().is_empty();
m_parent.toggle_model_objects_visibility(true);
// cancel processing
if (empty_selection &&
m_state != State::settings &&
m_state != State::canceling)
m_state = State::canceling;
// refuse outgoing during simlification
// object is not selected when it is deleted(cancel and close gizmo)
if (m_state != State::settings && !empty_selection) {
GLGizmoBase::m_state = GLGizmoBase::On;
auto notification_manager = wxGetApp().plater()->get_notification_manager();
notification_manager->push_notification(
NotificationType::CustomNotification,
NotificationManager::NotificationLevel::PrintInfoNotificationLevel,
_u8L("ERROR: Wait until Simplification ends or Cancel process."));
return;
}
// revert preview
if (m_exist_preview) {
m_exist_preview = false;
if (exist_volume(m_volume)) {
set_its(*m_original_its);
m_parent.reload_scene(false);
m_need_reload = false;
}
}
// invalidate selected model
m_volume = nullptr;
stop_worker_thread_request();
m_volume = nullptr; // invalidate selected model
m_glmodel.reset();
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
// when open by hyperlink it needs to show up
request_rerender();
@ -532,142 +530,95 @@ void GLGizmoSimplify::create_gui_cfg() {
m_gui_cfg = cfg;
}
void GLGizmoSimplify::request_rerender() {
wxGetApp().plater()->CallAfter([this]() {
void GLGizmoSimplify::request_rerender(bool force) {
int64_t now = m_parent.timestamp_now();
if (force || now > m_last_rerender_timestamp + 250) { // 250 ms
set_dirty();
m_parent.schedule_extra_frame(0);
});
m_last_rerender_timestamp = now;
}
}
void GLGizmoSimplify::set_center_position() {
m_move_to_center = true;
}
bool GLGizmoSimplify::exist_volume(ModelVolume *volume) {
auto objs = wxGetApp().plater()->model().objects;
for (const auto &obj : objs) {
const auto &vlms = obj->volumes;
auto item = std::find(vlms.begin(), vlms.end(), volume);
if (item != vlms.end()) return true;
}
return false;
}
ModelVolume * GLGizmoSimplify::get_volume(const Selection &selection, Model &model)
void GLGizmoSimplify::init_model(const indexed_triangle_set& its)
{
const Selection::IndicesList& idxs = selection.get_volume_idxs();
if (idxs.empty()) return nullptr;
// only one selected volume
if (idxs.size() != 1) return nullptr;
const GLVolume *selected_volume = selection.get_volume(*idxs.begin());
if (selected_volume == nullptr) return nullptr;
if (its.indices.empty())
return;
const GLVolume::CompositeID &cid = selected_volume->composite_id;
const ModelObjectPtrs& objs = model.objects;
if (cid.object_id < 0 || objs.size() <= static_cast<size_t>(cid.object_id))
return nullptr;
const ModelObject* obj = objs[cid.object_id];
if (cid.volume_id < 0 || obj->volumes.size() <= static_cast<size_t>(cid.volume_id))
return nullptr;
return obj->volumes[cid.volume_id];
}
const ModelVolume *GLGizmoSimplify::get_volume(const GLVolume::CompositeID &cid, const Model &model)
{
const ModelObjectPtrs &objs = model.objects;
if (cid.object_id < 0 || objs.size() <= static_cast<size_t>(cid.object_id))
return nullptr;
const ModelObject *obj = objs[cid.object_id];
if (cid.volume_id < 0 || obj->volumes.size() <= static_cast<size_t>(cid.volume_id))
return nullptr;
return obj->volumes[cid.volume_id];
}
void GLGizmoSimplify::init_wireframe()
{
if (!m_show_wireframe) return;
const indexed_triangle_set &its = m_volume->mesh().its;
free_gpu();
if (its.indices.empty()) return;
// vertices
glsafe(::glGenBuffers(1, &m_wireframe_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_VBO_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER,
its.vertices.size() * 3 * sizeof(float),
its.vertices.data(), GL_STATIC_DRAW));
m_glmodel.reset();
m_glmodel.init_from(its);
m_parent.toggle_model_objects_visibility(true); // selected volume may have changed
m_parent.toggle_model_objects_visibility(false, m_c->selection_info()->model_object(),
m_c->selection_info()->get_active_instance(), m_volume);
// indices
std::vector<Vec2i> contour_indices;
contour_indices.reserve((its.indices.size() * 3) / 2);
for (const auto &triangle : its.indices) {
for (size_t ti1 = 0; ti1 < 3; ++ti1) {
size_t ti2 = (ti1 == 2) ? 0 : (ti1 + 1);
if (triangle[ti1] > triangle[ti2]) continue;
contour_indices.emplace_back(triangle[ti1], triangle[ti2]);
}
}
glsafe(::glGenBuffers(1, &m_wireframe_IBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_IBO_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER,
2*contour_indices.size() * sizeof(coord_t),
contour_indices.data(), GL_STATIC_DRAW));
m_wireframe_IBO_size = contour_indices.size() * 2;
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
if (const Selection&sel = m_parent.get_selection(); sel.get_volume_idxs().size() == 1)
m_glmodel.set_color(-1, sel.get_volume(*sel.get_volume_idxs().begin())->color);
m_triangle_count = its.indices.size();
}
void GLGizmoSimplify::render_wireframe() const
void GLGizmoSimplify::on_render()
{
// is initialized?
if (m_wireframe_VBO_id == 0 || m_wireframe_IBO_id == 0) return;
if (!m_show_wireframe) return;
if (! m_glmodel.is_initialized())
return;
const auto& selection = m_parent.get_selection();
const auto& volume_idxs = selection.get_volume_idxs();
if (volume_idxs.empty() || volume_idxs.size() != 1) return;
const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin());
// check that selected model is wireframe initialized
if (m_volume != get_volume(selected_volume->composite_id, *m_parent.get_model()))
// Check that the GLVolume still belongs to the ModelObject we work on.
if (m_volume != get_model_volume(selection, wxGetApp().model()))
return;
const Transform3d trafo_matrix = selected_volume->world_matrix();
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
auto *contour_shader = wxGetApp().get_shader("mm_contour");
contour_shader->start_using();
glsafe(::glDepthFunc(GL_LEQUAL));
glsafe(::glLineWidth(1.0f));
auto *gouraud_shader = wxGetApp().get_shader("gouraud_light");
glsafe(::glPushAttrib(GL_DEPTH_TEST));
glsafe(::glEnable(GL_DEPTH_TEST));
gouraud_shader->start_using();
m_glmodel.render();
gouraud_shader->stop_using();
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_VBO_id));
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
if (m_show_wireframe) {
auto* contour_shader = wxGetApp().get_shader("mm_contour");
contour_shader->start_using();
glsafe(::glLineWidth(1.0f));
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE));
//ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); });
//glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
//glsafe(::glPolygonOffset(5.0, 5.0));
m_glmodel.render();
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL));
contour_shader->stop_using();
}
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_wireframe_IBO_id));
glsafe(::glDrawElements(GL_LINES, m_wireframe_IBO_size, GL_UNSIGNED_INT, nullptr));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
glsafe(::glDepthFunc(GL_LESS));
glsafe(::glPopMatrix()); // pop trafo
contour_shader->stop_using();
glsafe(::glPopAttrib());
glsafe(::glPopMatrix());
}
void GLGizmoSimplify::free_gpu()
{
if (m_wireframe_VBO_id != 0) {
glsafe(::glDeleteBuffers(1, &m_wireframe_VBO_id));
m_wireframe_VBO_id = 0;
}
if (m_wireframe_IBO_id != 0) {
glsafe(::glDeleteBuffers(1, &m_wireframe_IBO_id));
m_wireframe_IBO_id = 0;
}
CommonGizmosDataID GLGizmoSimplify::on_get_requirements() const
{
return CommonGizmosDataID(
int(CommonGizmosDataID::SelectionInfo));
}
void GLGizmoSimplify::Configuration::fix_count_by_ratio(size_t triangle_count)
{
if (decimate_ratio <= 0.f)
wanted_count = static_cast<uint32_t>(triangle_count);
else if (decimate_ratio >= 100.f)
wanted_count = 0;
else
wanted_count = static_cast<uint32_t>(std::round(
triangle_count * (100.f - decimate_ratio) / 100.f));
}
} // namespace Slic3r::GUI

View File

@ -4,27 +4,19 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
// which overrides our localization "L" macro.
#include "GLGizmoBase.hpp"
#include "GLGizmoPainterBase.hpp" // for render wireframe
#include "slic3r/GUI/3DScene.hpp"
#include "admesh/stl.h" // indexed_triangle_set
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <optional>
#include <atomic>
#include <GL/glew.h> // GLUint
// for simplify suggestion
class ModelObjectPtrs; // std::vector<ModelObject*>
#include <thread>
namespace Slic3r {
class ModelVolume;
class Model;
namespace GUI {
class NotificationManager; // for simplify suggestion
class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GLGizmoBase
class GLGizmoSimplify: public GLGizmoBase
{
public:
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
@ -32,8 +24,9 @@ public:
bool on_esc_key_down();
static void add_simplify_suggestion_notification(
const std::vector<size_t> &object_ids,
const ModelObjectPtrs & objects,
const std::vector<ModelObject*> & objects,
NotificationManager & manager);
protected:
virtual std::string on_get_name() const override;
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
@ -43,76 +36,75 @@ protected:
// must implement
virtual bool on_init() override { return true;};
virtual void on_render() override{};
virtual void on_render() override;
virtual void on_render_for_picking() override{};
// GLGizmoPainterBase
virtual void render_painter_gizmo() const override{ render_wireframe(); }
virtual CommonGizmosDataID on_get_requirements() const;
private:
void after_apply();
void apply_simplify();
void close();
void live_preview();
void process();
void set_its(indexed_triangle_set &its);
void stop_worker_thread_request();
void worker_finished();
void create_gui_cfg();
void request_rerender();
void request_rerender(bool force = false);
void init_model(const indexed_triangle_set& its);
void set_center_position();
// move to global functions
static ModelVolume *get_volume(const Selection &selection, Model &model);
static const ModelVolume *get_volume(const GLVolume::CompositeID &cid, const Model &model);
// return false when volume was deleted
static bool exist_volume(ModelVolume *volume);
std::atomic_bool m_is_valid_result; // differ what to do in apply
std::atomic_bool m_exist_preview; // set when process end
bool m_move_to_center; // opening gizmo
volatile int m_progress; // percent of done work
ModelVolume *m_volume; // keep pointer to actual working volume
size_t m_obj_index;
std::optional<indexed_triangle_set> m_original_its;
bool m_show_wireframe;
volatile bool m_need_reload; // after simplify, glReload must be on main thread
std::thread m_worker;
// wait before process
std::mutex m_state_mutex;
std::condition_variable m_dealy_process_cv;
enum class State {
settings,
preview, // simplify to show preview
close_on_end, // simplify with close on end
canceling // after button click, before canceled
};
volatile State m_state;
struct Configuration
{
bool use_count = false;
// minimal triangle count
float decimate_ratio = 50.f; // in percent
uint32_t wanted_count = 0; // initialize by percents
uint32_t wanted_count = 0; // initialize by percents
float max_error = 1.; // maximal quadric error
// maximal quadric error
float max_error = 1.;
void fix_count_by_ratio(size_t triangle_count)
{
if (decimate_ratio <= 0.f)
wanted_count = static_cast<uint32_t>(triangle_count);
else if (decimate_ratio >= 100.f)
wanted_count = 0;
else
wanted_count = static_cast<uint32_t>(std::round(
triangle_count * (100.f - decimate_ratio) / 100.f));
void fix_count_by_ratio(size_t triangle_count);
bool operator==(const Configuration& rhs) {
return (use_count == rhs.use_count && decimate_ratio == rhs.decimate_ratio
&& wanted_count == rhs.wanted_count && max_error == rhs.max_error);
}
} m_configuration;
bool operator!=(const Configuration& rhs) {
return ! (*this == rhs);
}
};
Configuration m_configuration;
bool m_move_to_center; // opening gizmo
const ModelVolume *m_volume; // keep pointer to actual working volume
bool m_show_wireframe;
GLModel m_glmodel;
size_t m_triangle_count; // triangle count of the model currently shown
// Timestamp of the last rerender request. Only accessed from UI thread.
int64_t m_last_rerender_timestamp = std::numeric_limits<int64_t>::min();
// Following struct is accessed by both UI and worker thread.
// Accesses protected by a mutex.
struct State {
enum Status {
idle,
running,
cancelling
};
Status status = idle;
int progress = 0; // percent of done work
Configuration config; // Configuration we started with.
const ModelVolume* mv = nullptr;
std::unique_ptr<indexed_triangle_set> result;
};
std::thread m_worker;
std::mutex m_state_mutex; // guards m_state
State m_state; // accessed by both threads
// This configs holds GUI layout size given by translated texts.
// etc. When language changes, GUI is recreated and this class constructed again,
@ -138,17 +130,9 @@ private:
// 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;
// rendering wireframe
void render_wireframe() const;
void init_wireframe();
void free_gpu();
GLuint m_wireframe_VBO_id, m_wireframe_IBO_id;
size_t m_wireframe_IBO_size;
// cancel exception
class SimplifyCanceledException: public std::exception
{

View File

@ -486,7 +486,7 @@ void GLGizmosManager::render_painter_gizmo() const
if (!m_enabled || m_current == Undefined)
return;
auto *gizmo = dynamic_cast<GLGizmoTransparentRender*>(get_current());
auto *gizmo = dynamic_cast<GLGizmoPainterBase*>(get_current());
assert(gizmo); // check the precondition
gizmo->render_painter_gizmo();
}

View File

@ -9,6 +9,7 @@
#include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp>
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
#include <boost/algorithm/string/predicate.hpp>
#include <boost/nowide/convert.hpp>
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
@ -486,7 +487,16 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float
{
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power);
std::string str_label;
if (boost::algorithm::istarts_with(label, "##"))
str_label = std::string(label);
else {
str_label = std::string("##") + std::string(label);
this->text(label);
ImGui::SameLine();
}
bool ret = ImGui::SliderFloat(str_label.c_str(), v, v_min, v_max, format, power);
if (!tooltip.empty() && ImGui::IsItemHovered())
this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width);
@ -497,10 +507,8 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float
const ImGuiStyle& style = ImGui::GetStyle();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y });
ImGui::SameLine();
std::wstring btn_name;
btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(std::string(label));
ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 1.0f });
std::wstring btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(str_label);
ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f });
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.5f, 0.5f, 0.5f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.5f, 0.5f, 0.5f, 1.0f });
if (ImGui::Button(into_u8(btn_name).c_str())) {

View File

@ -48,6 +48,9 @@ GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
if (evt.GetInt() == status_range() || m_worker_error) {
// set back the original range and cancel callback
m_progress->set_range(m_range);
// Make sure progress indicators get the last value of their range
// to make sure they close, fade out, whathever
m_progress->set_progress(m_range);
m_progress->set_cancel_callback();
wxEndBusyCursor();

View File

@ -1194,9 +1194,9 @@ void MainFrame::init_menubar_as_editor()
[this](wxCommandEvent&) { save_project(); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this);
#ifdef __APPLE__
append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"),
append_menu_item(fileMenu, wxID_ANY, _L("Save project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"),
#else
append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"),
append_menu_item(fileMenu, wxID_ANY, _L("Save project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"),
#endif // __APPLE__
[this](wxCommandEvent&) { save_project_as(); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save_as(); }, this);

View File

@ -63,21 +63,26 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
SetSizerAndFit(main_sizer);
}
void MsgDialog::add_btn(wxWindowID btn_id, bool set_focus /*= false*/, const wxString& label/* = wxString()*/)
wxButton* MsgDialog::add_button(wxWindowID btn_id, bool set_focus /*= false*/, const wxString& label/* = wxString()*/)
{
wxButton* btn = new wxButton(this, btn_id, label);
if (set_focus)
btn->SetFocus();
btn_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, HORIZ_SPACING);
btn->Bind(wxEVT_BUTTON, [this, btn_id](wxCommandEvent&) { this->EndModal(btn_id); });
return btn;
};
wxButton* MsgDialog::get_button(wxWindowID btn_id){
return static_cast<wxButton*>(FindWindowById(btn_id, this));
}
void MsgDialog::apply_style(long style)
{
if (style & wxOK) add_btn(wxID_OK, true);
if (style & wxYES) add_btn(wxID_YES, true);
if (style & wxNO) add_btn(wxID_NO);
if (style & wxCANCEL) add_btn(wxID_CANCEL);
if (style & wxOK) add_button(wxID_OK, true);
if (style & wxYES) add_button(wxID_YES, true);
if (style & wxNO) add_button(wxID_NO);
if (style & wxCANCEL) add_button(wxID_CANCEL);
logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" :
style & wxICON_INFORMATION ? "info" :

View File

@ -42,8 +42,10 @@ protected:
};
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, long style = wxOK, wxBitmap bitmap = wxNullBitmap);
void add_btn(wxWindowID btn_id, bool set_focus = false, const wxString& label = wxString());
// returns pointer to created button
wxButton* add_button(wxWindowID btn_id, bool set_focus = false, const wxString& label = wxString());
// returns pointer to found button or NULL
wxButton* get_button(wxWindowID btn_id);
void apply_style(long style);
void finalize();

View File

@ -9,6 +9,7 @@
#include "libslic3r/Config.hpp"
#include "../Utils/PrintHost.hpp"
#include "libslic3r/Config.hpp"
#include "format.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
@ -1068,14 +1069,12 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
for (it = m_types_and_counts.begin(); it != m_types_and_counts.end(); ++it) {
if ((*it).second == 0)
continue;
text += std::to_string((*it).second);
text += _L_PLURAL(" Object was loaded with "," Objects were loaded with ", (*it).second).ToUTF8().data();
switch ((*it).first) {
case InfoItemType::CustomSupports: text += _utf8("custom supports.\n"); break;
case InfoItemType::CustomSeam: text += _utf8("custom seam.\n"); break;
case InfoItemType::MmuSegmentation: text += _utf8("multimaterial painting.\n"); break;
case InfoItemType::VariableLayerHeight: text += _utf8("variable layer height.\n"); break;
case InfoItemType::Sinking: text += _utf8("Partial sinking.\n"); break;
case InfoItemType::CustomSupports: text += format(_L_PLURAL("%1$d Object was loaded with custom supports.", "%1$d Objects were loaded with custom supports.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::CustomSeam: text += format(_L_PLURAL("%1$d Object was loaded with custom seam.", "%1$d Objects were loaded with custom seam.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d Object was loaded with multimaterial painting.", "%1$d Objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break;
case InfoItemType::VariableLayerHeight: text += format(_L_PLURAL("%1$d Object was loaded with variable layer height.", "%1$d Objects were loaded with variable layer height.", (*it).second), (*it).second) + "\n"; break;
case InfoItemType::Sinking: text += format(_L_PLURAL("%1$d Object was loaded with partial sinking.", "%1$d Objects were loaded with partial sinking.", (*it).second), (*it).second) + "\n"; break;
default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break;
}
}

View File

@ -772,65 +772,30 @@ bool OG_CustomCtrl::CtrlLine::launch_browser() const
bool launch = true;
if (get_app_config()->get("suppress_hyperlinks").empty()) {
RememberChoiceDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"));
RichMessageDialog dialog(nullptr, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxYES_NO);
dialog.ShowCheckBox(_L("Remember my choice"));
int answer = dialog.ShowModal();
launch = answer == wxID_YES;
get_app_config()->set("suppress_hyperlinks", dialog.remember_choice() ? (answer == wxID_NO ? "1" : "0") : "");
}
if (launch)
launch = get_app_config()->get("suppress_hyperlinks") != "1";
return launch && wxLaunchDefaultBrowser(get_url(og_line.label_path));
}
RememberChoiceDialog::RememberChoiceDialog(wxWindow* parent, const wxString& msg_text, const wxString& caption)
: wxDialog(parent, wxID_ANY, caption, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxICON_INFORMATION)
{
this->SetEscapeId(wxID_CLOSE);
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
m_remember_choice = new wxCheckBox(this, wxID_ANY, _L("Remember my choice"));
m_remember_choice->SetValue(false);
m_remember_choice->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& evt)
{
if (!evt.IsChecked())
return;
if (dialog.IsCheckBoxChecked()) {
wxString preferences_item = _L("Suppress to open hyperlink in browser");
wxString msg =
_L("PrusaSlicer will remember your choice.") + "\n\n" +
_L("You will not be asked about it again on label hovering.") + "\n\n" +
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
//wxMessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
MessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
if (dialog.ShowModal() == wxID_CANCEL)
m_remember_choice->SetValue(false);
});
MessageDialog msg_dlg(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
if (msg_dlg.ShowModal() == wxID_CANCEL)
return false;
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
}
// Add dialog's buttons
wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxYES | wxNO);
wxButton* btnYES = static_cast<wxButton*>(this->FindWindowById(wxID_YES, this));
wxButton* btnNO = static_cast<wxButton*>(this->FindWindowById(wxID_NO, this));
btnYES->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { this->EndModal(wxID_YES); });
btnNO->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { this->EndModal(wxID_NO); });
launch = answer == wxID_YES;
}
if (launch)
launch = get_app_config()->get("suppress_hyperlinks") != "1";
topSizer->Add(new wxStaticText(this, wxID_ANY, msg_text), 0, wxEXPAND | wxALL, 10);
topSizer->Add(m_remember_choice, 0, wxEXPAND | wxALL, 10);
topSizer->Add(btns, 0, wxEXPAND | wxALL, 10);
#ifdef _WIN32
wxGetApp().UpdateDlgDarkUI(this);
#else
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif
this->SetSizer(topSizer);
topSizer->SetSizeHints(this);
this->CenterOnScreen();
return launch && wxLaunchDefaultBrowser(get_url(og_line.label_path));
}
} // GUI

View File

@ -106,20 +106,6 @@ public:
};
//-----------------------------------------------
// RememberChoiceDialog
//-----------------------------------------------
class RememberChoiceDialog : public wxDialog
{
wxCheckBox* m_remember_choice;
public:
RememberChoiceDialog(wxWindow* parent, const wxString& msg_text, const wxString& caption);
~RememberChoiceDialog() {}
bool remember_choice() const { return m_remember_choice->GetValue(); }
};
}}
#endif /* slic3r_OG_CustomCtrl_hpp_ */

View File

@ -460,6 +460,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
std::vector<float> extruders = dlg.get_extruders();
(project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values = std::vector<double>(matrix.begin(), matrix.end());
(project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(extruders.begin(), extruders.end());
wxGetApp().plater()->update_project_dirty_from_presets();
wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent));
}
}));
@ -2424,8 +2425,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
// show notification about temporarily installed presets
if (!names.empty()) {
std::string notif_text = into_u8(_L_PLURAL("The preset below was temporarily installed on active instance of PrusaSlicer",
"The presets below were temporarily installed on active instance of PrusaSlicer", names.size())) + ":";
std::string notif_text = into_u8(_L_PLURAL("The preset below was temporarily installed on the active instance of PrusaSlicer",
"The presets below were temporarily installed on the active instance of PrusaSlicer", names.size())) + ":";
for (std::string& name : names)
notif_text += "\n - " + name;
notification_manager->push_notification(NotificationType::CustomNotification,
@ -2476,9 +2477,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
MessageDialog(q, format_wxstr(_L_PLURAL(
"Object size from file %s appears to be zero.\n"
"This object has been removed from the model",
"Objects size from file %s appear to be zero.\n"
"Objects size from file %s appears to be zero.\n"
"These objects have been removed from the model", deleted_objects), from_path(filename)) + "\n",
_L("Object size is zero"), wxICON_INFORMATION | wxOK).ShowModal();
_L("The size of the object is zero"), wxICON_INFORMATION | wxOK).ShowModal();
}
if (imperial_units)
// Convert even if the object is big.
@ -2492,9 +2493,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
if (answer_convert_from_meters == wxOK_DEFAULT) {
RichMessageDialog dlg(q, format_wxstr(_L_PLURAL(
"The dimensions of the object from file %s seem to be defined in meters.\n"
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
"The internal unit of PrusaSlicer is a millimeter. Do you want to recalculate the dimensions of the object?",
"The dimensions of some objects from file %s seem to be defined in meters.\n"
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
"The internal unit of PrusaSlicer is a millimeter. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
_L("The object is too small"), wxICON_QUESTION | wxYES_NO);
dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded."));
int answer = dlg.ShowModal();
@ -2514,9 +2515,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
if (answer_convert_from_imperial_units == wxOK_DEFAULT) {
RichMessageDialog dlg(q, format_wxstr(_L_PLURAL(
"The dimensions of the object from file %s seem to be defined in inches.\n"
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
"The internal unit of PrusaSlicer is a millimeter. Do you want to recalculate the dimensions of the object?",
"The dimensions of some objects from file %s seem to be defined in inches.\n"
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
"The internal unit of PrusaSlicer is a millimeter. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
_L("The object is too small"), wxICON_QUESTION | wxYES_NO);
dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded."));
int answer = dlg.ShowModal();
@ -2533,7 +2534,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
MessageDialog msg_dlg(q, _L(
"This file contains several objects positioned at multiple heights.\n"
"Instead of considering them as multiple objects, should \n"
"should the file be loaded as a single object having multiple parts?") + "\n",
"the file be loaded as a single object having multiple parts?") + "\n",
_L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES) {
model.convert_multipart_object(nozzle_dmrs->values.size());
@ -3438,7 +3439,7 @@ void Plater::priv::replace_with_stl()
if (!volume->source.input_file.empty() && fs::exists(volume->source.input_file))
input_path = volume->source.input_file;
wxString title = _L("Please select the file to replace");
wxString title = _L("Select the new file");
title += ":";
wxFileDialog dialog(q, title, "", from_u8(input_path.filename().string()), file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
@ -5062,7 +5063,7 @@ void Plater::new_project()
int act_buttons = ab::KEEP;
if (saved_project == wxID_NO)
act_buttons |= ab::SAVE;
if (!wxGetApp().check_and_keep_current_preset_changes(_L("New Project is creating"), header, act_buttons))
if (!wxGetApp().check_and_keep_current_preset_changes(_L("Creating a new project"), header, act_buttons))
return;
}
@ -5889,7 +5890,7 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path)
{
#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
if (p->model.objects.empty()) {
MessageDialog dialog(nullptr, _L("The plater is empty.\nConfirm you want to save the project ?"), _L("Save project"), wxYES_NO);
MessageDialog dialog(nullptr, _L("The plater is empty.\nDo you want to save the project?"), _L("Save project"), wxYES_NO);
if (dialog.ShowModal() != wxID_YES)
return false;
}

View File

@ -218,7 +218,7 @@ void PreferencesDialog::build(size_t selected_tab)
m_optgroup_general->append_separator();
def.label = L("Ask for unsaved changes when closing application or loading new project");
def.label = L("Ask to save unsaved changes when closing the application or when loading a new project");
def.type = coBool;
def.tooltip = L("Always ask for unsaved changes, when: \n"
"- Closing PrusaSlicer while some presets are modified,\n"
@ -468,7 +468,7 @@ void PreferencesDialog::build(size_t selected_tab)
// Add "Dark Mode" tab
if (is_editor) {
// Add "Dark Mode" tab
m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs);
m_optgroup_dark_mode = create_options_tab(_L("Dark mode IU (experimental)"), tabs);
m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
};
@ -542,8 +542,8 @@ void PreferencesDialog::accept(wxEvent&)
wxString title = wxGetApp().is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME);
title += " - " + _L("Changes for the critical options");
MessageDialog dialog(nullptr,
_L("Changing fo some options will trigger application restart.\n"
"You will lose content of the plater.") + "\n\n" +
_L("Changing some options will trigger application restart.\n"
"You will lose the content of the plater.") + "\n\n" +
_L("Do you want to proceed?"),
title,
wxICON_QUESTION | wxYES | wxNO);
@ -762,7 +762,7 @@ void PreferencesDialog::create_settings_text_color_widget()
{
wxWindow* parent = m_optgroup_gui->parent();
wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _L("Text color Settings"));
wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _L("Text colors"));
wxGetApp().UpdateDarkUI(stb);
if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT);

View File

@ -573,31 +573,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
}
// Swallow the mouse click and open the color picker.
// get current color
DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config();
auto colors = static_cast<ConfigOptionStrings*>(cfg->option("extruder_colour")->clone());
wxColour clr(colors->values[m_extruder_idx]);
if (!clr.IsOk())
clr = wxColour(0,0,0); // Don't set alfa to transparence
auto data = new wxColourData();
data->SetChooseFull(1);
data->SetColour(clr);
wxColourDialog dialog(this, data);
dialog.CenterOnParent();
if (dialog.ShowModal() == wxID_OK)
{
colors->values[m_extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
DynamicPrintConfig cfg_new = *cfg;
cfg_new.set_key_value("extruder_colour", colors);
wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new);
this->update();
wxGetApp().plater()->on_config_change(cfg_new);
}
change_extruder_color();
});
}
@ -607,28 +583,15 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent)
{
// In a case of a physical printer, for its editing open PhysicalPrinterDialog
if (m_type == Preset::TYPE_PRINTER/* && this->is_selected_physical_printer()*/) {
this->show_edit_menu();
return;
}
if (!switch_to_tab())
return;
/* In a case of a multi-material printing, for editing another Filament Preset
* it's needed to select this preset for the "Filament settings" Tab
*/
if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
{
const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data();
// Call select_preset() only if there is new preset and not just modified
if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) )
{
const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
wxGetApp().get_tab(m_type)->select_preset(preset_name);
}
}
if (m_type == Preset::TYPE_PRINTER
#ifdef __linux__
// To edit extruder color from the sidebar
|| m_type == Preset::TYPE_FILAMENT
#endif //__linux__
)
show_edit_menu();
else
switch_to_tab();
});
}
@ -672,20 +635,59 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt)
evt.Skip();
}
bool PlaterPresetComboBox::switch_to_tab()
void PlaterPresetComboBox::switch_to_tab()
{
Tab* tab = wxGetApp().get_tab(m_type);
if (!tab)
return false;
return;
int page_id = wxGetApp().tab_panel()->FindPage(tab);
if (page_id == wxNOT_FOUND)
return false;
if (int page_id = wxGetApp().tab_panel()->FindPage(tab); page_id != wxNOT_FOUND)
{
wxGetApp().tab_panel()->SetSelection(page_id);
// Switch to Settings NotePad
wxGetApp().mainframe->select_tab();
wxGetApp().tab_panel()->SetSelection(page_id);
// Switch to Settings NotePad
wxGetApp().mainframe->select_tab();
return true;
//In a case of a multi-material printing, for editing another Filament Preset
//it's needed to select this preset for the "Filament settings" Tab
if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
{
const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data();
// Call select_preset() only if there is new preset and not just modified
if (!boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()))
{
const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
wxGetApp().get_tab(m_type)->select_preset(preset_name);
}
}
}
}
void PlaterPresetComboBox::change_extruder_color()
{
// get current color
DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config();
auto colors = static_cast<ConfigOptionStrings*>(cfg->option("extruder_colour")->clone());
wxColour clr(colors->values[m_extruder_idx]);
if (!clr.IsOk())
clr = wxColour(0, 0, 0); // Don't set alfa to transparence
auto data = new wxColourData();
data->SetChooseFull(1);
data->SetColour(clr);
wxColourDialog dialog(this, data);
dialog.CenterOnParent();
if (dialog.ShowModal() == wxID_OK)
{
colors->values[m_extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
DynamicPrintConfig cfg_new = *cfg;
cfg_new.set_key_value("extruder_colour", colors);
wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new);
this->update();
wxGetApp().plater()->on_config_change(cfg_new);
}
}
void PlaterPresetComboBox::show_add_menu()
@ -714,6 +716,16 @@ void PlaterPresetComboBox::show_edit_menu()
append_menu_item(menu, wxID_ANY, _L("Edit preset"), "",
[this](wxCommandEvent&) { this->switch_to_tab(); }, "cog", menu, []() { return true; }, wxGetApp().plater());
#ifdef __linux__
// To edit extruder color from the sidebar
if (m_type == Preset::TYPE_FILAMENT) {
append_menu_item(menu, wxID_ANY, _L("Change extruder color"), "",
[this](wxCommandEvent&) { this->change_extruder_color(); }, "funnel", menu, []() { return true; }, wxGetApp().plater());
wxGetApp().plater()->PopupMenu(menu);
return;
}
#endif //__linux__
if (this->is_selected_physical_printer()) {
append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "",
[this](wxCommandEvent&) { this->edit_physical_printer(); }, "cog", menu, []() { return true; }, wxGetApp().plater());

View File

@ -147,7 +147,8 @@ public:
void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; }
int get_extruder_idx() const { return m_extruder_idx; }
bool switch_to_tab();
void switch_to_tab();
void change_extruder_color();
void show_add_menu();
void show_edit_menu();

View File

@ -39,9 +39,8 @@ static const char *CONFIG_KEY_PRINT = "printhost_print";
static const char *CONFIG_KEY_GROUP = "printhost_group";
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print, const wxArrayString &groups)
: MsgDialog(static_cast<wxWindow*>(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), wxOK | wxCANCEL)
: MsgDialog(static_cast<wxWindow*>(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"))
, txt_filename(new wxTextCtrl(this, wxID_ANY))
, box_print(can_start_print ? new wxCheckBox(this, wxID_ANY, _L("Start printing after upload")) : nullptr)
, combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr)
{
#ifdef __APPLE__
@ -55,10 +54,6 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
content_sizer->Add(txt_filename, 0, wxEXPAND);
content_sizer->Add(label_dir_hint);
content_sizer->AddSpacer(VERT_SPACING);
if (box_print != nullptr) {
content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
box_print->SetValue(app_config->get("recent", CONFIG_KEY_PRINT) == "1");
}
if (combo_groups != nullptr) {
// Repetier specific: Show a selection of file groups.
@ -84,18 +79,37 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
wxString suffix = recent_path.substr(recent_path.find_last_of('.'));
static_cast<wxButton*>(FindWindowById(wxID_OK, this))->Bind(wxEVT_BUTTON, [this, suffix](wxCommandEvent&) {
wxString path = txt_filename->GetValue();
// .gcode suffix control
if (!path.Lower().EndsWith(suffix.Lower()))
{
MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO);
if (msg_wingow.ShowModal() == wxID_NO)
return;
}
EndDialog(wxID_OK);
});
if (can_start_print) {
auto* btn_print = add_button(wxID_YES, false, _L("Upload and Print"));
btn_print->Bind(wxEVT_BUTTON, [this, suffix](wxCommandEvent&) {
wxString path = txt_filename->GetValue();
// .gcode suffix control
if (!path.Lower().EndsWith(suffix.Lower()))
{
MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO);
if (msg_wingow.ShowModal() == wxID_NO)
return;
}
start_print_selected = true;
EndDialog(wxID_OK);
});
}
add_button(wxID_CANCEL);
if (auto* btn_ok = get_button(wxID_NO); btn_ok != NULL) {
btn_ok->SetLabel(_L("Upload"));
btn_ok->Bind(wxEVT_BUTTON, [this, suffix](wxCommandEvent&) {
wxString path = txt_filename->GetValue();
// .gcode suffix control
if (!path.Lower().EndsWith(suffix.Lower()))
{
MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO);
if (msg_wingow.ShowModal() == wxID_NO)
return;
}
EndDialog(wxID_OK);
});
}
finalize();
#ifdef __linux__
@ -125,7 +139,7 @@ fs::path PrintHostSendDialog::filename() const
bool PrintHostSendDialog::start_print() const
{
return box_print != nullptr ? box_print->GetValue() : false;
return start_print_selected;
}
std::string PrintHostSendDialog::group() const

View File

@ -36,8 +36,8 @@ public:
virtual void EndModal(int ret) override;
private:
wxTextCtrl *txt_filename;
wxCheckBox *box_print;
wxComboBox *combo_groups;
bool start_print_selected { false };
};

View File

@ -26,12 +26,14 @@ void ProjectDirtyStateManager::update_from_presets()
{
m_presets_dirty = false;
// check switching of the presets only for exist/loaded project, but not for new
if (!wxGetApp().plater()->get_project_filename().IsEmpty()) {
for (const auto& [type, name] : wxGetApp().get_selected_presets())
GUI_App &app = wxGetApp();
if (!app.plater()->get_project_filename().IsEmpty()) {
for (const auto& [type, name] : app.get_selected_presets())
m_presets_dirty |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
}
m_presets_dirty |= wxGetApp().has_unsaved_preset_changes();
wxGetApp().mainframe->update_title();
m_presets_dirty |= app.has_unsaved_preset_changes();
m_project_config_dirty = m_initial_project_config != app.preset_bundle->project_config;
app.mainframe->update_title();
}
void ProjectDirtyStateManager::reset_after_save()
@ -39,14 +41,17 @@ void ProjectDirtyStateManager::reset_after_save()
this->reset_initial_presets();
m_plater_dirty = false;
m_presets_dirty = false;
m_project_config_dirty = false;
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::reset_initial_presets()
{
m_initial_presets.fill(std::string{});
for (const auto& [type, name] : wxGetApp().get_selected_presets())
GUI_App &app = wxGetApp();
for (const auto& [type, name] : app.get_selected_presets())
m_initial_presets[type] = name;
m_initial_project_config = app.preset_bundle->project_config;
}
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW

View File

@ -14,7 +14,7 @@ public:
void reset_after_save();
void reset_initial_presets();
bool is_dirty() const { return m_plater_dirty || m_presets_dirty; }
bool is_dirty() const { return m_plater_dirty || m_project_config_dirty || m_presets_dirty; }
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void render_debug_window() const;
@ -25,8 +25,11 @@ private:
bool m_plater_dirty { false };
// Do the presets indicate the project is dirty?
bool m_presets_dirty { false };
// Is the project config dirty?
bool m_project_config_dirty { false };
// Keeps track of preset names selected at the time of last project save.
std::array<std::string, Preset::TYPE_COUNT> m_initial_presets;
DynamicPrintConfig m_initial_project_config;
};
} // namespace GUI

View File

@ -1478,14 +1478,13 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Advanced"));
optgroup->append_single_option_line("seam_position", category_path + "seam-position");
optgroup->append_single_option_line("external_perimeters_first", category_path + "external-perimeters-first");
optgroup->append_single_option_line("gap_fill_enabled");
optgroup->append_single_option_line("gap_fill_enabled", category_path + "fill-gaps");
optgroup = page->new_optgroup(L("Fuzzy skin (experimental)"));
Option option = optgroup->get_option("fuzzy_skin");
// option.opt.width = 30;
optgroup->append_single_option_line(option);
optgroup->append_single_option_line(optgroup->get_option("fuzzy_skin_thickness"));
optgroup->append_single_option_line(optgroup->get_option("fuzzy_skin_point_dist"));
category_path = "fuzzy-skin_246186/#";
optgroup->append_single_option_line("fuzzy_skin", category_path + "fuzzy-skin-type");
optgroup->append_single_option_line("fuzzy_skin_thickness", category_path + "fuzzy-skin-thickness");
optgroup->append_single_option_line("fuzzy_skin_point_dist", category_path + "fuzzy-skin-point-distance");
page = add_options_page(L("Infill"), "infill");
category_path = "infill_42#";
@ -1498,12 +1497,14 @@ void TabPrint::build()
optgroup->append_single_option_line("bottom_fill_pattern", category_path + "bottom-fill-pattern");
optgroup = page->new_optgroup(L("Ironing"));
optgroup->append_single_option_line("ironing");
optgroup->append_single_option_line("ironing_type");
optgroup->append_single_option_line("ironing_flowrate");
optgroup->append_single_option_line("ironing_spacing");
category_path = "ironing_177488#";
optgroup->append_single_option_line("ironing", category_path);
optgroup->append_single_option_line("ironing_type", category_path + "ironing-type");
optgroup->append_single_option_line("ironing_flowrate", category_path + "flow-rate");
optgroup->append_single_option_line("ironing_spacing", category_path + "spacing-between-ironing-passes");
optgroup = page->new_optgroup(L("Reducing printing time"));
category_path = "infill_42#";
optgroup->append_single_option_line("infill_every_layers", category_path + "combine-infill-every-x-layers");
optgroup->append_single_option_line("infill_only_where_needed", category_path + "only-infill-where-needed");
@ -1541,7 +1542,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Raft"));
optgroup->append_single_option_line("raft_layers", category_path + "raft-layers");
optgroup->append_single_option_line("raft_contact_distance");
optgroup->append_single_option_line("raft_contact_distance", category_path + "raft-layers");
optgroup->append_single_option_line("raft_expansion");
optgroup = page->new_optgroup(L("Options for support material and raft"));
@ -1666,7 +1667,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Output file"));
optgroup->append_single_option_line("gcode_comments");
optgroup->append_single_option_line("gcode_label_objects");
option = optgroup->get_option("output_filename_format");
Option option = optgroup->get_option("output_filename_format");
option.opt.full_width = true;
optgroup->append_single_option_line(option);
@ -3248,7 +3249,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset);
PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology();
PrinterTechnology new_printer_technology = new_printer_preset.printer_technology();
if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset is selecting")))
if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset selected")))
canceled = true;
else {
struct PresetUpdate {
@ -3651,8 +3652,8 @@ void Tab::delete_preset()
for (const std::string& printer : ph_printers)
msg += "\n \"" + from_u8(printer) + "\",";
msg.RemoveLast();
msg += "\n" + _L_PLURAL("Note, that selected preset will be deleted from this printer too.",
"Note, that selected preset will be deleted from these printers too.", ph_printers.size()) + "\n\n";
msg += "\n" + _L_PLURAL("Note, that the selected preset will be deleted from this printer too.",
"Note, that the selected preset will be deleted from these printers too.", ph_printers.size()) + "\n\n";
}
if (!ph_printers_only.empty()) {
@ -3661,8 +3662,8 @@ void Tab::delete_preset()
for (const std::string& printer : ph_printers_only)
msg += "\n \"" + from_u8(printer) + "\",";
msg.RemoveLast();
msg += "\n" + _L_PLURAL("Note, that this printer will be deleted after deleting of the selected preset.",
"Note, that these printers will be deleted after deleting of the selected preset.", ph_printers_only.size()) + "\n\n";
msg += "\n" + _L_PLURAL("Note, that this printer will be deleted after deleting the selected preset.",
"Note, that these printers will be deleted after deleting the selected preset.", ph_printers_only.size()) + "\n\n";
}
}

View File

@ -899,7 +899,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_
_L("You will not be asked about the unsaved changes the next time you: \n"
"- close the application,\n"
"- load project,\n"
"- process Undo / Redo with change of print technologie,\n"
"- process Undo / Redo with a change of print technology,\n"
"- take/load snapshot,\n"
"- load config file/bundle,\n"
"- export config_bundle") ;
@ -1628,7 +1628,7 @@ void DiffPresetDialog::update_tree()
const DynamicPrintConfig& right_congig = right_preset->config;
if (left_pt != right_preset->printer_technology()) {
bottom_info = _L("Comparable printer presets has different printer technology");
bottom_info = _L("Compared presets has different printer technology");
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "question"));
preset_combos.equal_bmp->SetToolTip(bottom_info);
continue;
@ -1651,7 +1651,7 @@ void DiffPresetDialog::update_tree()
show_tree = true;
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "not_equal"));
preset_combos.equal_bmp->SetToolTip(_L("Presets are different.\n"
"Click this button to select the same as left preset for the right preset."));
"Click this button to select the same preset for the right and left preset."));
m_tree->model->AddPreset(type, "\"" + from_u8(left_preset->name) + "\" vs \"" + from_u8(right_preset->name) + "\"", left_pt);

View File

@ -72,7 +72,7 @@ MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_on
content_sizer->Add(cbox);
content_sizer->AddSpacer(VERT_SPACING);
Fit();
finalize();
}
MsgUpdateSlic3r::~MsgUpdateSlic3r() {}
@ -133,12 +133,12 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
add_btn(wxID_OK, true, force_before_wizard ? _L("Install") : "OK");
add_button(wxID_OK, true, force_before_wizard ? _L("Install") : "OK");
if (force_before_wizard) {
add_btn(wxID_CLOSE, false, _L("Don't install"));
static_cast<wxButton*>(FindWindowById(wxID_CLOSE, this))->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) { this->EndModal(wxID_CLOSE); });
auto* btn = add_button(wxID_CLOSE, false, _L("Don't install"));
btn->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) { this->EndModal(wxID_CLOSE); });
}
add_btn(wxID_CANCEL);
add_button(wxID_CANCEL);
finalize();
}
@ -190,9 +190,9 @@ MsgUpdateForced::MsgUpdateForced(const std::vector<Update>& updates) :
content_sizer->Add(versions);
content_sizer->AddSpacer(2 * VERT_SPACING);
add_btn(wxID_EXIT, false, wxString::Format(_L("Exit %s"), SLIC3R_APP_NAME));
add_button(wxID_EXIT, false, wxString::Format(_L("Exit %s"), SLIC3R_APP_NAME));
for (auto ID : { wxID_EXIT, wxID_OK })
static_cast<wxButton*>(FindWindowById(ID, this))->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); });
get_button(ID)->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); });
finalize();
}
@ -236,11 +236,11 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, w
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
add_btn(wxID_REPLACE, true, _L("Re-configure"));
add_btn(wxID_EXIT, false, wxString::Format(_L("Exit %s"), SLIC3R_APP_NAME));
add_button(wxID_REPLACE, true, _L("Re-configure"));
add_button(wxID_EXIT, false, wxString::Format(_L("Exit %s"), SLIC3R_APP_NAME));
for (auto ID : {wxID_EXIT, wxID_REPLACE})
static_cast<wxButton*>(FindWindowById(ID, this))->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); });
get_button(ID)->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); });
finalize();
}

View File

@ -776,7 +776,7 @@ static bool reload_configs_update_gui()
{
wxString header = _L("Configuration Updates causes a lost of preset modification.\n"
"So, check unsaved changes and save them if necessary.");
if (!GUI::wxGetApp().check_and_save_current_preset_changes(_L("Updater is processing"), header, false ))
if (!GUI::wxGetApp().check_and_save_current_preset_changes(_L("Updating"), header, false ))
return false;
// Reload global configuration

View File

@ -14,7 +14,6 @@ add_executable(${_TEST_NAME}_tests
test_mutable_polygon.cpp
test_mutable_priority_queue.cpp
test_stl.cpp
test_meshsimplify.cpp
test_meshboolean.cpp
test_marchingsquares.cpp
test_timeutils.cpp

View File

@ -3,7 +3,6 @@
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/MeshBoolean.hpp>
#include <libslic3r/SimplifyMesh.hpp>
using namespace Slic3r;

View File

@ -1,11 +0,0 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
//#include <libslic3r/MeshSimplify.hpp>
//TEST_CASE("Mesh simplification", "[mesh_simplify]") {
// Simplify::load_obj(TEST_DATA_DIR PATH_SEPARATOR "zaba.obj");
// Simplify::simplify_mesh_lossless();
// Simplify::write_obj("zaba_simplified.obj");
//}