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 disabled_tags = SLA
[hint:Shapes gallery] [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 hypertext_type = gallery
disable_modes = simple disable_modes = simple
[hint:Auto-arrange settings] [hint: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? 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_type = plater
hypertext_plater_item = arrange 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 documentation_link = https://help.prusa3d.com/en/article/reload-from-disk_120427
[hint:Hiding sidebar] [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_type = preferences
hypertext_preferences_page = 2 hypertext_preferences_page = 2
hypertext_preferences_item = show_collapse_button 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? 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] [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_type = settings
hypertext_settings_opt = infill_every_layers hypertext_settings_opt = infill_every_layers
hypertext_settings_type = 1 hypertext_settings_type = 1
@ -139,7 +139,7 @@ documentation_link= https://help.prusa3d.com/en/article/per-model-settings_1674
disabled_tags = SLA disabled_tags = SLA
[hint:Solid infill threshold area] [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_type = settings
hypertext_settings_opt = solid_infill_below_area hypertext_settings_opt = solid_infill_below_area
hypertext_settings_type = 1 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? 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] [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] [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.) 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"> viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="hex_x5F_X"> <g id="hex_x5F_X">
<g> <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>
<g id="plus_2_"> <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"> viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="hex_x5F_X"> <g id="hex_x5F_X">
<g> <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>
<g id="plus_2_"> <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 MinAreaBoundingBox.cpp
miniz_extension.hpp miniz_extension.hpp
miniz_extension.cpp miniz_extension.cpp
SimplifyMesh.hpp
SimplifyMeshImpl.hpp
SimplifyMesh.cpp
MarchingSquares.hpp MarchingSquares.hpp
Execution/Execution.hpp Execution/Execution.hpp
Execution/ExecutionSeq.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; 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. // Pair the object layers with the support layers by z.
size_t idx_object_layer = 0; size_t idx_object_layer = 0;
size_t idx_support_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 // 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) // 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) { 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, warning_ranges.emplace_back(std::make_pair((last_extrusion_layer ? last_extrusion_layer->print_z() : 0.), layers_to_print.back().print_z()));
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.")));
}
// Remember last layer with extrusions. // Remember last layer with extrusions.
if (has_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; return layers_to_print;
} }
@ -732,7 +744,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re
reports += source + ": \"" + keyword + "\"\n"; reports += source + ": \"" + keyword + "\"\n";
} }
print->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, 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 + reports +
_(L("This may cause problems in g-code visualization and printing time estimation."))); _(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; 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 *** // *** 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; 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: private:
PrinterPresetCollection() = default; PrinterPresetCollection() = default;
PrinterPresetCollection(const PrinterPresetCollection &other) = default; PrinterPresetCollection(const PrinterPresetCollection &other) = default;

View File

@ -481,7 +481,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("brim_width", coFloat); def = this->add("brim_width", coFloat);
def->label = L("Brim width"); def->label = L("Brim width");
def->category = L("Skirt and brim"); 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)."); "When raft is used, no brim is generated (use raft_first_layer_expansion).");
def->sidetext = L("mm"); def->sidetext = L("mm");
def->min = 0; def->min = 0;
@ -1252,7 +1252,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("fuzzy_skin_thickness", coFloat); def = this->add("fuzzy_skin_thickness", coFloat);
def->label = L("Fuzzy skin thickness"); def->label = L("Fuzzy skin thickness");
def->category = L("Fuzzy Skin"); 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->sidetext = L("mm");
def->min = 0; def->min = 0;
def->mode = comAdvanced; def->mode = comAdvanced;
@ -1261,7 +1262,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("fuzzy_skin_point_dist", coFloat); def = this->add("fuzzy_skin_point_dist", coFloat);
def->label = L("Fuzzy skin point distance"); def->label = L("Fuzzy skin point distance");
def->category = L("Fuzzy Skin"); 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->sidetext = L("mm");
def->min = 0; def->min = 0;
def->mode = comAdvanced; def->mode = comAdvanced;
@ -2439,13 +2441,13 @@ void PrintConfigDef::init_fff_params()
def = this->add("slicing_mode", coEnum); def = this->add("slicing_mode", coEnum);
def->label = L("Slicing Mode"); def->label = L("Slicing Mode");
def->category = L("Advanced"); 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_keys_map = &ConfigOptionEnum<SlicingMode>::get_enum_values();
def->enum_values.push_back("regular"); def->enum_values.push_back("regular");
def->enum_values.push_back("even_odd"); def->enum_values.push_back("even_odd");
def->enum_values.push_back("close_holes"); def->enum_values.push_back("close_holes");
def->enum_labels.push_back(L("Regular")); 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->enum_labels.push_back(L("Close holes"));
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<SlicingMode>(SlicingMode::Regular)); 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 = this->add("thick_bridges", coBool);
def->label = L("Thick bridges"); def->label = L("Thick bridges");
def->category = L("Layers and Perimeters"); 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->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true)); def->set_default_value(new ConfigOptionBool(true));
@ -4268,7 +4271,7 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def = this->add("config_compatibility", coEnum); def = this->add("config_compatibility", coEnum);
def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF)."); 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 " "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."); "bail out or to substitute an unknown value with a default silently or verbosely.");
def->enum_keys_map = &ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values(); def->enum_keys_map = &ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values();

View File

@ -2,7 +2,6 @@
#include <tuple> #include <tuple>
#include <optional> #include <optional>
#include "MutablePriorityQueue.hpp" #include "MutablePriorityQueue.hpp"
#include "SimplifyMeshImpl.hpp"
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
using namespace Slic3r; using namespace Slic3r;
@ -13,10 +12,47 @@ using namespace Slic3r;
// only private namespace not neccessary be in .hpp // only private namespace not neccessary be in .hpp
namespace QuadricEdgeCollapse { 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 Vertices = std::vector<stl_vertex>;
using Triangle = stl_triangle_vertex_indices; using Triangle = stl_triangle_vertex_indices;
using Indices = std::vector<stl_triangle_vertex_indices>; using Indices = std::vector<stl_triangle_vertex_indices>;
using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>;
using ThrowOnCancel = std::function<void(void)>; using ThrowOnCancel = std::function<void(void)>;
using StatusFn = std::function<void(int)>; using StatusFn = std::function<void(int)>;
// smallest error caused by edges, identify smallest edge in triangle // smallest error caused by edges, identify smallest edge in triangle

View File

@ -7,7 +7,7 @@
#include <libslic3r/SLA/Hollowing.hpp> #include <libslic3r/SLA/Hollowing.hpp>
#include <libslic3r/SLA/IndexedMesh.hpp> #include <libslic3r/SLA/IndexedMesh.hpp>
#include <libslic3r/ClipperUtils.hpp> #include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/SimplifyMesh.hpp> #include <libslic3r/QuadricEdgeCollapse.hpp>
#include <libslic3r/SLA/SupportTreeMesher.hpp> #include <libslic3r/SLA/SupportTreeMesher.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
@ -132,7 +132,10 @@ InteriorPtr generate_interior(const TriangleMesh & mesh,
// flip normals back... // flip normals back...
swap_normals(interior->mesh); 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_compactify_vertices(interior->mesh);
its_merge_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 <functional>
#include <optional> #include <optional>
#ifndef NDEBUG
#define HAS_GLSAFE #define HAS_GLSAFE
#endif // NDEBUG
#ifdef HAS_GLSAFE #ifdef HAS_GLSAFE
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }

View File

@ -559,7 +559,9 @@ PagePrinters::PagePrinters(ConfigWizard *parent,
append(picker); append(picker);
printer_pickers.push_back(picker); printer_pickers.push_back(picker);
has_printers = true;
} }
} }
void PagePrinters::select_all(bool select, bool alternates) 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) 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) && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY)
&& printer_pickers.size() > 0 && printer_pickers.size() > 0
&& printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) { && 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 , second_line
); );
} else { } 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( text = wxString::Format(
"<html>" "<html>"
"<style>" "<style>"
@ -1904,8 +1910,10 @@ void ConfigWizard::priv::load_pages()
index->add_page(page_welcome); index->add_page(page_welcome);
// Printers // Printers
if (!only_sla_mode)
index->add_page(page_fff); index->add_page(page_fff);
index->add_page(page_msla); index->add_page(page_msla);
if (!only_sla_mode) {
index->add_page(page_vendors); index->add_page(page_vendors);
for (const auto &pages : pages_3rdparty) { for (const auto &pages : pages_3rdparty) {
for ( PagePrinters* page : { pages.second.first, pages.second.second }) for ( PagePrinters* page : { pages.second.first, pages.second.second })
@ -1923,6 +1931,7 @@ void ConfigWizard::priv::load_pages()
// Filaments & Materials // 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); } if (any_sla_selected) { index->add_page(page_sla_materials); }
// there should to be selected at least one printer // 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.width / 10,
9*disp_rect.height / 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) { if (width_hint < window_rect.width) {
window_rect.x += (window_rect.width - width_hint) / 2; window_rect.x += (window_rect.width - width_hint) / 2;
window_rect.width = width_hint; 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) 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(); const auto enabled_vendors = appconfig_new.vendors();
bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model()); 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)
if ((check_unsaved_preset_changes = install_bundles.size() > 0)) 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__ #ifdef __linux__
// Desktop integration on Linux // Desktop integration on Linux
@ -2585,7 +2594,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
break; 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; return false;
if (check_unsaved_preset_changes && 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, preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem,
{preferred_model, preferred_variant, first_added_filament, first_added_sla_material}); {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 unsaved changes was not cheched till this moment
if (!check_unsaved_preset_changes && 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)) !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->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->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF);
p->only_sla_mode = !p->page_fff->has_printers;
if (!p->only_sla_mode) {
p->add_page(p->page_fff); 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->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA);
p->add_page(p->page_msla); p->add_page(p->page_msla);
if (p->only_sla_mode) {
p->page_msla->is_primary_printer_page = true;
}
if (!p->only_sla_mode) {
// Pages for 3rd party vendors // Pages for 3rd party vendors
p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors 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_vendors = new PageVendors(this));
p->add_page(p->page_custom = new PageCustom(this)); p->add_page(p->page_custom = new PageCustom(this));
p->custom_printer_selected = p->page_custom->custom_wanted(); p->custom_printer_selected = p->page_custom->custom_wanted();
}
p->any_sla_selected = p->check_sla_selected(); p->any_sla_selected = p->check_sla_selected();
if (p->only_sla_mode)
p->any_fff_selected = p->check_fff_selected(); p->any_fff_selected = p->check_fff_selected();
p->update_materials(T_ANY); p->update_materials(T_ANY);
if (!p->only_sla_mode)
p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments, p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
_L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") )); _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") ));
p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials, p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
_L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") )); _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; } 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; 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 // Here we extend wxListBox and wxCheckListBox
@ -548,7 +551,9 @@ struct ConfigWizard::priv
std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition 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_fff_selected; // Used to decide whether to display Filaments page
bool any_sla_selected; // Used to decide whether to display SLA Materials 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; wxScrolledWindow *hscroll = nullptr;
wxBoxSizer *hscroll_sizer = nullptr; wxBoxSizer *hscroll_sizer = nullptr;

View File

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

View File

@ -24,6 +24,7 @@
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/MainFrame.hpp" #include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/Utils/UndoRedo.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "GUI_ObjectList.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) { for (GLVolume* vol : m_volumes.volumes) {
if (vol->composite_id.object_id == 1000) { // wipe tower if (vol->composite_id.object_id == 1000) { // wipe tower
@ -1151,7 +1152,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
} }
else { else {
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) 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; vol->is_active = visible;
if (instance_idx == -1) { if (instance_idx == -1) {
@ -5238,11 +5240,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
// visible when inside modifier meshes etc. // visible when inside modifier meshes etc.
{ {
const GLGizmosManager& gm = get_gizmos_manager(); const GLGizmosManager& gm = get_gizmos_manager();
GLGizmosManager::EType type = gm.get_current_type(); // GLGizmosManager::EType type = gm.get_current_type();
if (type == GLGizmosManager::FdmSupports if (dynamic_cast<GLGizmoPainterBase*>(gm.get_current())) {
|| type == GLGizmosManager::Seam
|| type == GLGizmosManager::MmuSegmentation
|| type == GLGizmosManager::Simplify ) {
shader->stop_using(); shader->stop_using();
gm.render_painter_gizmo(); gm.render_painter_gizmo();
shader->start_using(); 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 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_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_object(size_t obj_idx);
void update_instance_printable_state_for_objects(const std::vector<size_t>& object_idxs); 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) 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; std::stringstream buffer;
buffer << stream.rdbuf(); buffer << stream.rdbuf();
std::string body = buffer.str(); 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 "); size_t start = body.find("PrusaSlicer ");
if (start == std::string::npos) if (start == std::string::npos)
return boost::none; return boost::none;
body = body.substr(start + 12); 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()) if (end < body.size())
body.resize(end); body.resize(end);
return Semver::parse(body); 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.")); int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified."));
if (saved_project == wxID_CANCEL || if (saved_project == wxID_CANCEL ||
(plater()->is_project_dirty() && saved_project == wxID_NO && (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 false;
return true; return true;
} }
@ -2879,7 +2877,7 @@ void GUI_App::window_pos_sanitize(wxTopLevelWindow* window)
bool GUI_App::config_wizard_startup() 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); run_wizard(ConfigWizard::RR_DATA_EMPTY);
return true; return true;
} else if (get_app_config()->legacy_datadir()) { } 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; bool launch = true;
if (get_app_config()->get("suppress_hyperlinks").empty()) { 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")); dialog.ShowCheckBox(_L("Remember my choice"));
int answer = dialog.ShowModal(); int answer = dialog.ShowModal();
launch = answer == wxID_YES; 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) 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"; 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) 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()) { 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 += _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"; 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 msg;
wxString bullet_suf = "\n - "; wxString bullet_suf = "\n - ";
if (!succes_models.empty()) { 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) for (auto& model : succes_models)
msg += bullet_suf + from_u8(model); msg += bullet_suf + from_u8(model);
msg += "\n\n"; 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, NotificationType::SignDetected, NotificationManager::NotificationLevel::PrintInfoNotificationLevel,
_u8L("NOTE:") + "\n" + _u8L("NOTE:") + "\n" +
format(_u8L("Sliced object \"%1%\" looks like a logo or a sign"), object->model_object()->name) + "\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*) { [this](wxEvtHandler*) {
m_layers_slider->auto_color_change(); m_layers_slider->auto_color_change();
return true; 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); m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
if (ImGui::IsItemHovered()) 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(); m_imgui->disabled_end();
} else { } else {

View File

@ -118,7 +118,7 @@ bool GLGizmoMmuSegmentation::on_init()
m_desc["second_color"] = _L("Second color"); m_desc["second_color"] = _L("Second color");
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Remove painted color"); 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["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere"); m_desc["sphere"] = _L("Sphere");
m_desc["pointer"] = _L("Triangles"); 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. // 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; static const constexpr size_t EXTRUDERS_LIMIT = 16;
virtual const float get_cursor_radius_min() const { return CursorRadiusMin; }
protected: protected:
std::array<float, 4> get_cursor_sphere_left_button_color() const override; std::array<float, 4> get_cursor_sphere_left_button_color() const override;
std::array<float, 4> get_cursor_sphere_right_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<std::array<float, 4>> m_modified_extruders_colors;
std::vector<int> m_original_volumes_extruder_idxs; std::vector<int> m_original_volumes_extruder_idxs;
static const constexpr float CursorRadiusMin = 0.1f; // cannot be zero
private: private:
bool on_init() override; bool on_init() override;

View File

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

View File

@ -99,20 +99,11 @@ protected:
GLPaintContour m_paint_contour; 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 // 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). // using circular blush (such as FDM supports gizmo and seam painting gizmo).
// The purpose is not to duplicate code related to mesh painting. // The purpose is not to duplicate code related to mesh painting.
class GLGizmoPainterBase : public GLGizmoTransparentRender, public GLGizmoBase class GLGizmoPainterBase : public GLGizmoBase
{ {
private: private:
ObjectID m_old_mo_id; ObjectID m_old_mo_id;
@ -125,6 +116,16 @@ public:
virtual void set_painter_gizmo_data(const Selection& selection); 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); 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: protected:
virtual void render_triangles(const Selection& selection) const; virtual void render_triangles(const Selection& selection) const;
void render_cursor() const; void render_cursor() const;

View File

@ -1,60 +1,94 @@
#include "GLGizmoSimplify.hpp" #include "GLGizmoSimplify.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/format.hpp"
#include "libslic3r/AppConfig.hpp" #include "libslic3r/AppConfig.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/QuadricEdgeCollapse.hpp" #include "libslic3r/QuadricEdgeCollapse.hpp"
#include <GL/glew.h>
#include <thread>
namespace Slic3r::GUI { 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, GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
const std::string &icon_filename, const std::string &icon_filename,
unsigned int sprite_id) unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, -1) : GLGizmoBase(parent, icon_filename, -1)
, m_state(State::settings)
, m_is_valid_result(false)
, m_exist_preview(false)
, m_progress(0)
, m_volume(nullptr) , m_volume(nullptr)
, m_obj_index(0)
, m_need_reload(false)
, m_show_wireframe(false) , m_show_wireframe(false)
, m_move_to_center(false) , m_move_to_center(false)
// translation for GUI size // translation for GUI size
, tr_mesh_name(_u8L("Mesh name")) , tr_mesh_name(_u8L("Mesh name"))
, tr_triangles(_u8L("Triangles")) , tr_triangles(_u8L("Triangles"))
, tr_preview(_u8L("Preview"))
, tr_detail_level(_u8L("Detail level")) , tr_detail_level(_u8L("Detail level"))
, tr_decimate_ratio(_u8L("Decimate ratio")) , 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() { GLGizmoSimplify::~GLGizmoSimplify()
m_state = State::canceling; {
if (m_worker.joinable()) m_worker.join(); stop_worker_thread_request();
free_gpu(); if (m_worker.joinable())
m_worker.join();
m_glmodel.reset();
} }
bool GLGizmoSimplify::on_esc_key_down() { bool GLGizmoSimplify::on_esc_key_down() {
if (m_state == State::settings || m_state == State::canceling)
return false; return false;
/*if (!m_is_worker_running)
m_state = State::canceling; return false;
return true; stop_worker_thread_request();
return true;*/
} }
// while opening needs GLGizmoSimplify to set window position // while opening needs GLGizmoSimplify to set window position
void GLGizmoSimplify::add_simplify_suggestion_notification( void GLGizmoSimplify::add_simplify_suggestion_notification(
const std::vector<size_t> &object_ids, const std::vector<size_t> &object_ids,
const ModelObjectPtrs & objects, const std::vector<ModelObject*>& objects,
NotificationManager & manager) NotificationManager & manager)
{ {
std::vector<size_t> big_ids; std::vector<size_t> big_ids;
@ -74,14 +108,10 @@ void GLGizmoSimplify::add_simplify_suggestion_notification(
if (big_ids.empty()) return; if (big_ids.empty()) return;
for (size_t object_id : big_ids) { for (size_t object_id : big_ids) {
std::string t = _u8L( std::string t = GUI::format(_u8L(
"Processing model '@object_name' with more than 1M triangles " "Processing model '%1%' with more than 1M triangles "
"could be slow. It is highly recommend to reduce " "could be slow. It is highly recommend to reduce "
"amount of triangles."); "amount of triangles."), objects[object_id]->name);
t.replace(t.find("@object_name"), sizeof("@object_name") - 1,
objects[object_id]->name);
// std::stringstream text;
// text << t << "\n";
std::string hypertext = _u8L("Simplify model"); std::string hypertext = _u8L("Simplify model");
std::function<bool(wxEvtHandler *)> open_simplify = 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(); create_gui_cfg();
const Selection &selection = m_parent.get_selection(); const Selection &selection = m_parent.get_selection();
int obj_index = selection.get_object_idx(); const ModelVolume *act_volume = get_model_volume(selection, wxGetApp().plater()->model());
ModelVolume *act_volume = get_volume(selection, wxGetApp().plater()->model());
if (act_volume == nullptr) { if (act_volume == nullptr) {
switch (m_state) { stop_worker_thread_request();
case State::settings: close(); break; close();
case State::canceling: break;
default: m_state = State::canceling;
}
return; 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 // Check selection of new volume
// Do not reselect object when processing // 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); bool change_window_position = (m_volume == nullptr);
// select different model // select different model
if (m_volume != nullptr && m_original_its.has_value()) {
set_its(*m_original_its);
}
// close suggestion notification // close suggestion notification
auto notification_manager = wxGetApp().plater()->get_notification_manager(); auto notification_manager = wxGetApp().plater()->get_notification_manager();
notification_manager->remove_simplify_suggestion_with_id(act_volume->get_object()->id()); 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_volume = act_volume;
m_original_its = {};
m_configuration.decimate_ratio = 50.; // default value m_configuration.decimate_ratio = 50.; // default value
m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size()); m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size());
m_is_valid_result = false; init_model(m_volume->mesh().its);
m_exist_preview = false;
init_wireframe(); // Start processing. If we switched from another object, process will
live_preview(); // stop the background thread and it will restart itself later.
start_process = true;
// set window position // set window position
if (m_move_to_center && change_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 | int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse; ImGuiWindowFlags_NoCollapse;
m_imgui->begin(on_get_name(), flag); 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 + ":"); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_mesh_name + ":");
ImGui::SameLine(m_gui_cfg->top_left_width); ImGui::SameLine(m_gui_cfg->top_left_width);
std::string name = m_volume->name; 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(name);
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":"); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":");
ImGui::SameLine(m_gui_cfg->top_left_width); ImGui::SameLine(m_gui_cfg->top_left_width);
m_imgui->text(std::to_string(triangle_count));
/* size_t orig_triangle_count = m_volume->mesh().its.indices.size();
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_preview + ":"); m_imgui->text(std::to_string(orig_triangle_count));
ImGui::SameLine(m_gui_cfg->top_left_width);
if (m_exist_preview) {
m_imgui->text(std::to_string(m_volume->mesh().its.indices.size()));
} else {
m_imgui->text("---");
}*/
ImGui::Separator(); ImGui::Separator();
if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) { if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) {
m_configuration.use_count = !m_configuration.use_count; m_configuration.use_count = !m_configuration.use_count;
live_preview(); start_process = true;
} }
ImGui::SameLine(); ImGui::SameLine();
m_imgui->disabled_begin(m_configuration.use_count); 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 3: m_configuration.max_error = 0.5f; break;
case 4: m_configuration.max_error = 1.f; break; case 4: m_configuration.max_error = 1.f; break;
} }
live_preview(); start_process = true;
} }
m_imgui->disabled_end(); // !use_count m_imgui->disabled_end(); // !use_count
if (ImGui::RadioButton("##use_count", m_configuration.use_count)) { if (ImGui::RadioButton("##use_count", m_configuration.use_count)) {
m_configuration.use_count = !m_configuration.use_count; m_configuration.use_count = !m_configuration.use_count;
live_preview(); start_process = true;
} }
ImGui::SameLine(); ImGui::SameLine();
// show preview result triangle count (percent) // show preview result triangle count (percent)
if (m_need_reload && !m_configuration.use_count) { if (!m_configuration.use_count) {
m_configuration.wanted_count = static_cast<uint32_t>(m_volume->mesh().its.indices.size()); m_configuration.wanted_count = static_cast<uint32_t>(m_triangle_count);
m_configuration.decimate_ratio = 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); 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; m_configuration.decimate_ratio = 0.01f;
if (m_configuration.decimate_ratio > 100.f) if (m_configuration.decimate_ratio > 100.f)
m_configuration.decimate_ratio = 100.f; m_configuration.decimate_ratio = 100.f;
m_configuration.fix_count_by_ratio(triangle_count); m_configuration.fix_count_by_ratio(orig_triangle_count);
live_preview(); start_process = true;
} }
ImGui::NewLine(); 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); ImGui::Text(_u8L("%d triangles").c_str(), m_configuration.wanted_count);
m_imgui->disabled_end(); // use_count m_imgui->disabled_end(); // use_count
if (ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe)) { ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe);
if (m_show_wireframe) init_wireframe();
else free_gpu();
}
bool is_canceling = m_state == State::canceling; m_imgui->disabled_begin(is_cancelling);
m_imgui->disabled_begin(is_canceling); if (m_imgui->button(_L("Close"))) {
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(); close();
} } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_cancelling)
} else { ImGui::SetTooltip("%s", _u8L("Operation already cancelling. Please wait few seconds.").c_str());
m_state = State::canceling; m_imgui->disabled_end(); // state cancelling
}
} 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
ImGui::SameLine(); ImGui::SameLine();
bool is_processing = m_state != State::settings; m_imgui->disabled_begin(is_worker_running || ! is_result_ready);
m_imgui->disabled_begin(is_processing); if (m_imgui->button(_L("Apply"))) {
if (m_imgui->button(_u8L("Apply"))) { apply_simplify();
if (!m_is_valid_result) { } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_worker_running)
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)
ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str()); ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str());
m_imgui->disabled_end(); // state !settings m_imgui->disabled_end(); // state !settings
// draw progress bar // draw progress bar
if (is_processing) { // apply or preview if (is_worker_running) { // apply or preview
ImGui::SameLine(m_gui_cfg->bottom_left_width); ImGui::SameLine(m_gui_cfg->bottom_left_width);
// draw progress bar // draw progress bar
char buf[32]; std::string progress_text = GUI::format(_L("Process %1% / 100"), std::to_string(progress));
sprintf(buf, L("Process %d / 100"), m_progress); ImVec2 progress_size(m_gui_cfg->input_width, 0.f);
ImGui::ProgressBar(m_progress / 100., ImVec2(m_gui_cfg->input_width, 0.f), buf); ImGui::ProgressBar(progress / 100., progress_size, progress_text.c_str());
} }
m_imgui->end(); m_imgui->end();
if (start_process)
// refresh view when needed process();
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);
}
} }
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() { void GLGizmoSimplify::close() {
// close gizmo == open it again // close gizmo == open it again
@ -350,117 +335,158 @@ void GLGizmoSimplify::close() {
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
} }
void GLGizmoSimplify::live_preview() { void GLGizmoSimplify::stop_worker_thread_request()
m_is_valid_result = false; {
if (m_state != State::settings) { std::lock_guard lk(m_state_mutex);
// already canceling process if (m_state.status == State::running)
if (m_state == State::canceling) return; m_state.status = State::Status::cancelling;
}
// wait until cancel
if (m_worker.joinable()) { // Following is called from a UI thread when the worker terminates
m_state = State::canceling; // worker calls it through a CallAfter.
m_dealy_process_cv.notify_one(); 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;
}
}
if (m_worker.joinable())
m_worker.join(); m_worker.join();
} if (GLGizmoBase::m_state == Off)
} return;
if (m_state.result)
m_state = State::preview; init_model(*m_state.result);
if (m_state.config != m_configuration || m_state.mv != m_volume) {
// Settings were changed, restart the worker immediately.
process(); process();
}
request_rerender(true);
} }
void GLGizmoSimplify::process() void GLGizmoSimplify::process()
{ {
if (m_volume == nullptr) return; if (m_volume == nullptr || m_volume->mesh().its.indices.empty())
if (m_volume->mesh().its.indices.empty()) return; 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? bool configs_match = false;
if (m_original_its.has_value() && bool result_valid = false;
m_original_its->indices.size() != count_triangles) { bool is_worker_running = false;
indexed_triangle_set its = *m_original_its; // copy {
set_its(its); 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;
} }
m_is_valid_result = true;
// re-render bargraph if ((result_valid || is_worker_running) && configs_match) {
set_dirty(); // Either finished or waiting for result already. Nothing to do.
m_parent.schedule_extra_frame(0);
return; return;
} }
// when not store original volume store it for cancelation if (is_worker_running && ! configs_match) {
if (!m_original_its.has_value()) { // Worker is running with outdated config. Stop it. It will
m_original_its = m_volume->mesh().its; // copy // restart itself when cancellation is done.
stop_worker_thread_request();
// store previous state
auto plater = wxGetApp().plater();
plater->take_snapshot(_u8L("Simplify ") + m_volume->name);
plater->clear_before_change_mesh(m_obj_index);
}
m_progress = 0;
if (m_worker.joinable()) m_worker.join();
m_worker = std::thread([this]() {
{// 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; 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 // Copy configuration that will be used.
uint32_t triangle_count = (m_configuration.use_count) ? m_configuration.wanted_count : 0; m_state.config = m_configuration;
float max_error = (!m_configuration.use_count) ? m_configuration.max_error : std::numeric_limits<float>::max(); m_state.mv = m_volume;
m_state.status = State::running;
std::function<void(void)> throw_on_cancel = [&]() { // Create a copy of current mesh to pass to the worker thread.
if (m_state == State::canceling) { // 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(); throw SimplifyCanceledException();
};
// 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(); });
};
// 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;
} }
};
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();
};
indexed_triangle_set collapsed = *m_original_its; // copy
// Start the actual calculation.
try { try {
its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn); its_quadric_edge_collapse(*its, triangle_count, &max_error, throw_on_cancel, statusfn);
set_its(collapsed);
m_is_valid_result = true;
m_exist_preview = true;
} catch (SimplifyCanceledException &) { } catch (SimplifyCanceledException &) {
// set state out of main thread std::lock_guard lk(m_state_mutex);
m_state = State::settings; m_state.status = State::idle;
} }
// need to render last status fn to change bar graph to buttons
request_rerender(); 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) { void GLGizmoSimplify::apply_simplify() {
if (m_volume == nullptr) return; // could appear after process
m_volume->set_mesh(its); const Selection& selection = m_parent.get_selection();
m_volume->calculate_convex_hull(); int object_idx = selection.get_object_idx();
m_volume->set_new_unique_id();
m_volume->get_object()->invalidate_bounding_box(); auto plater = wxGetApp().plater();
m_need_reload = true; 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 bool GLGizmoSimplify::on_is_activable() const
@ -472,39 +498,11 @@ void GLGizmoSimplify::on_set_state()
{ {
// Closing gizmo. e.g. selecting another one // Closing gizmo. e.g. selecting another one
if (GLGizmoBase::m_state == GLGizmoBase::Off) { if (GLGizmoBase::m_state == GLGizmoBase::Off) {
// can appear when delete objects m_parent.toggle_model_objects_visibility(true);
bool empty_selection = m_parent.get_selection().is_empty();
// cancel processing stop_worker_thread_request();
if (empty_selection && m_volume = nullptr; // invalidate selected model
m_state != State::settings && m_glmodel.reset();
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;
} else if (GLGizmoBase::m_state == GLGizmoBase::On) { } else if (GLGizmoBase::m_state == GLGizmoBase::On) {
// when open by hyperlink it needs to show up // when open by hyperlink it needs to show up
request_rerender(); request_rerender();
@ -532,142 +530,95 @@ void GLGizmoSimplify::create_gui_cfg() {
m_gui_cfg = cfg; m_gui_cfg = cfg;
} }
void GLGizmoSimplify::request_rerender() { void GLGizmoSimplify::request_rerender(bool force) {
wxGetApp().plater()->CallAfter([this]() { int64_t now = m_parent.timestamp_now();
if (force || now > m_last_rerender_timestamp + 250) { // 250 ms
set_dirty(); set_dirty();
m_parent.schedule_extra_frame(0); m_parent.schedule_extra_frame(0);
}); m_last_rerender_timestamp = now;
}
} }
void GLGizmoSimplify::set_center_position() { void GLGizmoSimplify::set_center_position() {
m_move_to_center = true; m_move_to_center = true;
} }
bool GLGizmoSimplify::exist_volume(ModelVolume *volume) {
auto objs = wxGetApp().plater()->model().objects; void GLGizmoSimplify::init_model(const indexed_triangle_set& its)
for (const auto &obj : objs) { {
const auto &vlms = obj->volumes; if (its.indices.empty())
auto item = std::find(vlms.begin(), vlms.end(), volume); return;
if (item != vlms.end()) return true;
} m_glmodel.reset();
return false; 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);
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();
} }
ModelVolume * GLGizmoSimplify::get_volume(const Selection &selection, Model &model) void GLGizmoSimplify::on_render()
{ {
const Selection::IndicesList& idxs = selection.get_volume_idxs(); if (! m_glmodel.is_initialized())
if (idxs.empty()) return nullptr; return;
// 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];
}
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));
// 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));
}
void GLGizmoSimplify::render_wireframe() const
{
// is initialized?
if (m_wireframe_VBO_id == 0 || m_wireframe_IBO_id == 0) return;
if (!m_show_wireframe) return;
const auto& selection = m_parent.get_selection(); const auto& selection = m_parent.get_selection();
const auto& volume_idxs = selection.get_volume_idxs(); const auto& volume_idxs = selection.get_volume_idxs();
if (volume_idxs.empty() || volume_idxs.size() != 1) return; if (volume_idxs.empty() || volume_idxs.size() != 1) return;
const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin()); const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin());
// check that selected model is wireframe initialized // Check that the GLVolume still belongs to the ModelObject we work on.
if (m_volume != get_volume(selected_volume->composite_id, *m_parent.get_model())) if (m_volume != get_model_volume(selection, wxGetApp().model()))
return; return;
const Transform3d trafo_matrix = selected_volume->world_matrix(); const Transform3d trafo_matrix = selected_volume->world_matrix();
glsafe(::glPushMatrix()); glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data())); glsafe(::glMultMatrixd(trafo_matrix.data()));
auto *contour_shader = wxGetApp().get_shader("mm_contour"); 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();
if (m_show_wireframe) {
auto* contour_shader = wxGetApp().get_shader("mm_contour");
contour_shader->start_using(); contour_shader->start_using();
glsafe(::glDepthFunc(GL_LEQUAL));
glsafe(::glLineWidth(1.0f)); glsafe(::glLineWidth(1.0f));
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_VBO_id)); //ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); });
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); //glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); //glsafe(::glPolygonOffset(5.0, 5.0));
m_glmodel.render();
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_wireframe_IBO_id)); glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL));
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(); 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) { CommonGizmosDataID GLGizmoSimplify::on_get_requirements() const
glsafe(::glDeleteBuffers(1, &m_wireframe_IBO_id)); {
m_wireframe_IBO_id = 0; 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 } // namespace Slic3r::GUI

View File

@ -4,27 +4,19 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
// which overrides our localization "L" macro. // which overrides our localization "L" macro.
#include "GLGizmoBase.hpp" #include "GLGizmoBase.hpp"
#include "GLGizmoPainterBase.hpp" // for render wireframe #include "slic3r/GUI/3DScene.hpp"
#include "admesh/stl.h" // indexed_triangle_set #include "admesh/stl.h" // indexed_triangle_set
#include <thread>
#include <mutex> #include <mutex>
#include <condition_variable> #include <thread>
#include <chrono>
#include <optional>
#include <atomic>
#include <GL/glew.h> // GLUint
// for simplify suggestion
class ModelObjectPtrs; // std::vector<ModelObject*>
namespace Slic3r { namespace Slic3r {
class ModelVolume; class ModelVolume;
class Model;
namespace GUI { namespace GUI {
class NotificationManager; // for simplify suggestion class NotificationManager; // for simplify suggestion
class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GLGizmoBase class GLGizmoSimplify: public GLGizmoBase
{ {
public: public:
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
@ -32,8 +24,9 @@ public:
bool on_esc_key_down(); bool on_esc_key_down();
static void add_simplify_suggestion_notification( static void add_simplify_suggestion_notification(
const std::vector<size_t> &object_ids, const std::vector<size_t> &object_ids,
const ModelObjectPtrs & objects, const std::vector<ModelObject*> & objects,
NotificationManager & manager); NotificationManager & manager);
protected: protected:
virtual std::string on_get_name() const override; virtual std::string on_get_name() const override;
virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual void on_render_input_window(float x, float y, float bottom_limit) override;
@ -43,76 +36,75 @@ protected:
// must implement // must implement
virtual bool on_init() override { return true;}; virtual bool on_init() override { return true;};
virtual void on_render() override{}; virtual void on_render() override;
virtual void on_render_for_picking() override{}; virtual void on_render_for_picking() override{};
// GLGizmoPainterBase virtual CommonGizmosDataID on_get_requirements() const;
virtual void render_painter_gizmo() const override{ render_wireframe(); }
private: private:
void after_apply(); void apply_simplify();
void close(); void close();
void live_preview();
void process(); void process();
void set_its(indexed_triangle_set &its); void stop_worker_thread_request();
void worker_finished();
void create_gui_cfg(); 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(); 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 struct Configuration
{ {
bool use_count = false; bool use_count = false;
// minimal triangle count
float decimate_ratio = 50.f; // in percent 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 void fix_count_by_ratio(size_t triangle_count);
float max_error = 1.; bool operator==(const Configuration& rhs) {
return (use_count == rhs.use_count && decimate_ratio == rhs.decimate_ratio
void fix_count_by_ratio(size_t triangle_count) && wanted_count == rhs.wanted_count && max_error == rhs.max_error);
{
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));
} }
} 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. // This configs holds GUI layout size given by translated texts.
// etc. When language changes, GUI is recreated and this class constructed again, // etc. When language changes, GUI is recreated and this class constructed again,
@ -138,17 +130,9 @@ private:
// translations used for calc window size // translations used for calc window size
const std::string tr_mesh_name; const std::string tr_mesh_name;
const std::string tr_triangles; const std::string tr_triangles;
const std::string tr_preview;
const std::string tr_detail_level; const std::string tr_detail_level;
const std::string tr_decimate_ratio; 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 // cancel exception
class SimplifyCanceledException: public std::exception class SimplifyCanceledException: public std::exception
{ {

View File

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

View File

@ -9,6 +9,7 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT #if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
#include <boost/algorithm/string/predicate.hpp>
#include <boost/nowide/convert.hpp> #include <boost/nowide/convert.hpp>
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT #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; 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()) if (!tooltip.empty() && ImGui::IsItemHovered())
this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width); 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(); const ImGuiStyle& style = ImGui::GetStyle();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y }); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y });
ImGui::SameLine(); ImGui::SameLine();
std::wstring btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(str_label);
std::wstring btn_name; ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f });
btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(std::string(label));
ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.5f, 0.5f, 0.5f, 1.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 }); ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.5f, 0.5f, 0.5f, 1.0f });
if (ImGui::Button(into_u8(btn_name).c_str())) { 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) { if (evt.GetInt() == status_range() || m_worker_error) {
// set back the original range and cancel callback // set back the original range and cancel callback
m_progress->set_range(m_range); 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(); m_progress->set_cancel_callback();
wxEndBusyCursor(); wxEndBusyCursor();

View File

@ -1194,9 +1194,9 @@ void MainFrame::init_menubar_as_editor()
[this](wxCommandEvent&) { save_project(); }, "save", nullptr, [this](wxCommandEvent&) { save_project(); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this); [this](){return m_plater != nullptr && can_save(); }, this);
#ifdef __APPLE__ #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 #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__ #endif // __APPLE__
[this](wxCommandEvent&) { save_project_as(); }, "save", nullptr, [this](wxCommandEvent&) { save_project_as(); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save_as(); }, this); [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); 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); wxButton* btn = new wxButton(this, btn_id, label);
if (set_focus) if (set_focus)
btn->SetFocus(); btn->SetFocus();
btn_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, HORIZ_SPACING); btn_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, HORIZ_SPACING);
btn->Bind(wxEVT_BUTTON, [this, btn_id](wxCommandEvent&) { this->EndModal(btn_id); }); 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) void MsgDialog::apply_style(long style)
{ {
if (style & wxOK) add_btn(wxID_OK, true); if (style & wxOK) add_button(wxID_OK, true);
if (style & wxYES) add_btn(wxID_YES, true); if (style & wxYES) add_button(wxID_YES, true);
if (style & wxNO) add_btn(wxID_NO); if (style & wxNO) add_button(wxID_NO);
if (style & wxCANCEL) add_btn(wxID_CANCEL); if (style & wxCANCEL) add_button(wxID_CANCEL);
logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" : logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" :
style & wxICON_INFORMATION ? "info" : 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); MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, long style = wxOK, wxBitmap bitmap = wxNullBitmap);
// returns pointer to created button
void add_btn(wxWindowID btn_id, bool set_focus = false, const wxString& label = wxString()); 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 apply_style(long style);
void finalize(); void finalize();

View File

@ -9,6 +9,7 @@
#include "libslic3r/Config.hpp" #include "libslic3r/Config.hpp"
#include "../Utils/PrintHost.hpp" #include "../Utils/PrintHost.hpp"
#include "libslic3r/Config.hpp" #include "libslic3r/Config.hpp"
#include "format.hpp"
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/log/trivial.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) { for (it = m_types_and_counts.begin(); it != m_types_and_counts.end(); ++it) {
if ((*it).second == 0) if ((*it).second == 0)
continue; 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) { switch ((*it).first) {
case InfoItemType::CustomSupports: text += _utf8("custom supports.\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 += _utf8("custom seam.\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 += _utf8("multimaterial painting.\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 += _utf8("variable layer height.\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 += _utf8("Partial sinking.\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; default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break;
} }
} }

View File

@ -772,11 +772,25 @@ bool OG_CustomCtrl::CtrlLine::launch_browser() const
bool launch = true; bool launch = true;
if (get_app_config()->get("suppress_hyperlinks").empty()) { 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(); int answer = dialog.ShowModal();
launch = answer == wxID_YES;
get_app_config()->set("suppress_hyperlinks", dialog.remember_choice() ? (answer == wxID_NO ? "1" : "0") : ""); 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);
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") : "");
}
launch = answer == wxID_YES;
} }
if (launch) if (launch)
launch = get_app_config()->get("suppress_hyperlinks") != "1"; launch = get_app_config()->get("suppress_hyperlinks") != "1";
@ -784,54 +798,5 @@ bool OG_CustomCtrl::CtrlLine::launch_browser() const
return launch && wxLaunchDefaultBrowser(get_url(og_line.label_path)); 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;
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);
});
// 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); });
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();
}
} // GUI } // GUI
} // Slic3r } // Slic3r

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_ */ #endif /* slic3r_OG_CustomCtrl_hpp_ */

View File

@ -460,6 +460,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
std::vector<float> extruders = dlg.get_extruders(); 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_matrix"))->values = std::vector<double>(matrix.begin(), matrix.end());
(project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(extruders.begin(), extruders.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)); 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 // show notification about temporarily installed presets
if (!names.empty()) { if (!names.empty()) {
std::string notif_text = into_u8(_L_PLURAL("The preset below was temporarily installed on active instance of PrusaSlicer", 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 active instance of PrusaSlicer", names.size())) + ":"; "The presets below were temporarily installed on the active instance of PrusaSlicer", names.size())) + ":";
for (std::string& name : names) for (std::string& name : names)
notif_text += "\n - " + name; notif_text += "\n - " + name;
notification_manager->push_notification(NotificationType::CustomNotification, 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( MessageDialog(q, format_wxstr(_L_PLURAL(
"Object size from file %s appears to be zero.\n" "Object size from file %s appears to be zero.\n"
"This object has been removed from the model", "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", "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) if (imperial_units)
// Convert even if the object is big. // 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) { if (answer_convert_from_meters == wxOK_DEFAULT) {
RichMessageDialog dlg(q, format_wxstr(_L_PLURAL( RichMessageDialog dlg(q, format_wxstr(_L_PLURAL(
"The dimensions of the object from file %s seem to be defined in meters.\n" "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 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); _L("The object is too small"), wxICON_QUESTION | wxYES_NO);
dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded.")); dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded."));
int answer = dlg.ShowModal(); 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) { if (answer_convert_from_imperial_units == wxOK_DEFAULT) {
RichMessageDialog dlg(q, format_wxstr(_L_PLURAL( RichMessageDialog dlg(q, format_wxstr(_L_PLURAL(
"The dimensions of the object from file %s seem to be defined in inches.\n" "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 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); _L("The object is too small"), wxICON_QUESTION | wxYES_NO);
dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded.")); dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded."));
int answer = dlg.ShowModal(); 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( MessageDialog msg_dlg(q, _L(
"This file contains several objects positioned at multiple heights.\n" "This file contains several objects positioned at multiple heights.\n"
"Instead of considering them as multiple objects, should \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); _L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
if (msg_dlg.ShowModal() == wxID_YES) { if (msg_dlg.ShowModal() == wxID_YES) {
model.convert_multipart_object(nozzle_dmrs->values.size()); 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)) if (!volume->source.input_file.empty() && fs::exists(volume->source.input_file))
input_path = 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 += ":"; title += ":";
wxFileDialog dialog(q, title, "", from_u8(input_path.filename().string()), file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); 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) if (dialog.ShowModal() != wxID_OK)
@ -5062,7 +5063,7 @@ void Plater::new_project()
int act_buttons = ab::KEEP; int act_buttons = ab::KEEP;
if (saved_project == wxID_NO) if (saved_project == wxID_NO)
act_buttons |= ab::SAVE; 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; return;
} }
@ -5889,7 +5890,7 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path)
{ {
#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED #if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
if (p->model.objects.empty()) { 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) if (dialog.ShowModal() != wxID_YES)
return false; return false;
} }

View File

@ -218,7 +218,7 @@ void PreferencesDialog::build(size_t selected_tab)
m_optgroup_general->append_separator(); 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.type = coBool;
def.tooltip = L("Always ask for unsaved changes, when: \n" def.tooltip = L("Always ask for unsaved changes, when: \n"
"- Closing PrusaSlicer while some presets are modified,\n" "- Closing PrusaSlicer while some presets are modified,\n"
@ -468,7 +468,7 @@ void PreferencesDialog::build(size_t selected_tab)
// Add "Dark Mode" tab // Add "Dark Mode" tab
if (is_editor) { if (is_editor) {
// Add "Dark Mode" tab // 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_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"; 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); wxString title = wxGetApp().is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME);
title += " - " + _L("Changes for the critical options"); title += " - " + _L("Changes for the critical options");
MessageDialog dialog(nullptr, MessageDialog dialog(nullptr,
_L("Changing fo some options will trigger application restart.\n" _L("Changing some options will trigger application restart.\n"
"You will lose content of the plater.") + "\n\n" + "You will lose the content of the plater.") + "\n\n" +
_L("Do you want to proceed?"), _L("Do you want to proceed?"),
title, title,
wxICON_QUESTION | wxYES | wxNO); wxICON_QUESTION | wxYES | wxNO);
@ -762,7 +762,7 @@ void PreferencesDialog::create_settings_text_color_widget()
{ {
wxWindow* parent = m_optgroup_gui->parent(); 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); wxGetApp().UpdateDarkUI(stb);
if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); 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. // Swallow the mouse click and open the color picker.
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);
}
}); });
} }
@ -607,28 +583,15 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent) edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent)
{ {
// In a case of a physical printer, for its editing open PhysicalPrinterDialog // In a case of a physical printer, for its editing open PhysicalPrinterDialog
if (m_type == Preset::TYPE_PRINTER/* && this->is_selected_physical_printer()*/) { if (m_type == Preset::TYPE_PRINTER
this->show_edit_menu(); #ifdef __linux__
return; // To edit extruder color from the sidebar
} || m_type == Preset::TYPE_FILAMENT
#endif //__linux__
if (!switch_to_tab()) )
return; show_edit_menu();
else
/* In a case of a multi-material printing, for editing another Filament Preset switch_to_tab();
* 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);
}
}
}); });
} }
@ -672,20 +635,59 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt)
evt.Skip(); evt.Skip();
} }
bool PlaterPresetComboBox::switch_to_tab() void PlaterPresetComboBox::switch_to_tab()
{ {
Tab* tab = wxGetApp().get_tab(m_type); Tab* tab = wxGetApp().get_tab(m_type);
if (!tab) 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); wxGetApp().tab_panel()->SetSelection(page_id);
// Switch to Settings NotePad // Switch to Settings NotePad
wxGetApp().mainframe->select_tab(); 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() void PlaterPresetComboBox::show_add_menu()
@ -714,6 +716,16 @@ void PlaterPresetComboBox::show_edit_menu()
append_menu_item(menu, wxID_ANY, _L("Edit preset"), "", append_menu_item(menu, wxID_ANY, _L("Edit preset"), "",
[this](wxCommandEvent&) { this->switch_to_tab(); }, "cog", menu, []() { return true; }, wxGetApp().plater()); [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()) { if (this->is_selected_physical_printer()) {
append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "", append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "",
[this](wxCommandEvent&) { this->edit_physical_printer(); }, "cog", menu, []() { return true; }, wxGetApp().plater()); [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; } void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; }
int get_extruder_idx() const { return m_extruder_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_add_menu();
void show_edit_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"; static const char *CONFIG_KEY_GROUP = "printhost_group";
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print, const wxArrayString &groups) 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)) , 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) , combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr)
{ {
#ifdef __APPLE__ #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(txt_filename, 0, wxEXPAND);
content_sizer->Add(label_dir_hint); content_sizer->Add(label_dir_hint);
content_sizer->AddSpacer(VERT_SPACING); 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) { if (combo_groups != nullptr) {
// Repetier specific: Show a selection of file groups. // Repetier specific: Show a selection of file groups.
@ -84,7 +79,26 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
wxString suffix = recent_path.substr(recent_path.find_last_of('.')); wxString suffix = recent_path.substr(recent_path.find_last_of('.'));
static_cast<wxButton*>(FindWindowById(wxID_OK, this))->Bind(wxEVT_BUTTON, [this, suffix](wxCommandEvent&) { 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(); wxString path = txt_filename->GetValue();
// .gcode suffix control // .gcode suffix control
if (!path.Lower().EndsWith(suffix.Lower())) if (!path.Lower().EndsWith(suffix.Lower()))
@ -95,7 +109,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
} }
EndDialog(wxID_OK); EndDialog(wxID_OK);
}); });
}
finalize(); finalize();
#ifdef __linux__ #ifdef __linux__
@ -125,7 +139,7 @@ fs::path PrintHostSendDialog::filename() const
bool PrintHostSendDialog::start_print() const bool PrintHostSendDialog::start_print() const
{ {
return box_print != nullptr ? box_print->GetValue() : false; return start_print_selected;
} }
std::string PrintHostSendDialog::group() const std::string PrintHostSendDialog::group() const

View File

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

View File

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

View File

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

View File

@ -1478,14 +1478,13 @@ void TabPrint::build()
optgroup = page->new_optgroup(L("Advanced")); optgroup = page->new_optgroup(L("Advanced"));
optgroup->append_single_option_line("seam_position", category_path + "seam-position"); 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("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)")); optgroup = page->new_optgroup(L("Fuzzy skin (experimental)"));
Option option = optgroup->get_option("fuzzy_skin"); category_path = "fuzzy-skin_246186/#";
// option.opt.width = 30; optgroup->append_single_option_line("fuzzy_skin", category_path + "fuzzy-skin-type");
optgroup->append_single_option_line(option); optgroup->append_single_option_line("fuzzy_skin_thickness", category_path + "fuzzy-skin-thickness");
optgroup->append_single_option_line(optgroup->get_option("fuzzy_skin_thickness")); optgroup->append_single_option_line("fuzzy_skin_point_dist", category_path + "fuzzy-skin-point-distance");
optgroup->append_single_option_line(optgroup->get_option("fuzzy_skin_point_dist"));
page = add_options_page(L("Infill"), "infill"); page = add_options_page(L("Infill"), "infill");
category_path = "infill_42#"; 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->append_single_option_line("bottom_fill_pattern", category_path + "bottom-fill-pattern");
optgroup = page->new_optgroup(L("Ironing")); optgroup = page->new_optgroup(L("Ironing"));
optgroup->append_single_option_line("ironing"); category_path = "ironing_177488#";
optgroup->append_single_option_line("ironing_type"); optgroup->append_single_option_line("ironing", category_path);
optgroup->append_single_option_line("ironing_flowrate"); optgroup->append_single_option_line("ironing_type", category_path + "ironing-type");
optgroup->append_single_option_line("ironing_spacing"); 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")); 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_every_layers", category_path + "combine-infill-every-x-layers");
optgroup->append_single_option_line("infill_only_where_needed", category_path + "only-infill-where-needed"); 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 = page->new_optgroup(L("Raft"));
optgroup->append_single_option_line("raft_layers", category_path + "raft-layers"); 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->append_single_option_line("raft_expansion");
optgroup = page->new_optgroup(L("Options for support material and raft")); 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 = page->new_optgroup(L("Output file"));
optgroup->append_single_option_line("gcode_comments"); optgroup->append_single_option_line("gcode_comments");
optgroup->append_single_option_line("gcode_label_objects"); 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; option.opt.full_width = true;
optgroup->append_single_option_line(option); 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); 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 old_printer_technology = m_presets->get_edited_preset().printer_technology();
PrinterTechnology new_printer_technology = new_printer_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; canceled = true;
else { else {
struct PresetUpdate { struct PresetUpdate {
@ -3651,8 +3652,8 @@ void Tab::delete_preset()
for (const std::string& printer : ph_printers) for (const std::string& printer : ph_printers)
msg += "\n \"" + from_u8(printer) + "\","; msg += "\n \"" + from_u8(printer) + "\",";
msg.RemoveLast(); msg.RemoveLast();
msg += "\n" + _L_PLURAL("Note, that selected preset will be deleted from this printer too.", msg += "\n" + _L_PLURAL("Note, that the 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"; "Note, that the selected preset will be deleted from these printers too.", ph_printers.size()) + "\n\n";
} }
if (!ph_printers_only.empty()) { if (!ph_printers_only.empty()) {
@ -3661,8 +3662,8 @@ void Tab::delete_preset()
for (const std::string& printer : ph_printers_only) for (const std::string& printer : ph_printers_only)
msg += "\n \"" + from_u8(printer) + "\","; msg += "\n \"" + from_u8(printer) + "\",";
msg.RemoveLast(); msg.RemoveLast();
msg += "\n" + _L_PLURAL("Note, that this printer will be deleted after deleting of the selected preset.", 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 of the selected preset.", ph_printers_only.size()) + "\n\n"; "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" _L("You will not be asked about the unsaved changes the next time you: \n"
"- close the application,\n" "- close the application,\n"
"- load project,\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" "- take/load snapshot,\n"
"- load config file/bundle,\n" "- load config file/bundle,\n"
"- export config_bundle") ; "- export config_bundle") ;
@ -1628,7 +1628,7 @@ void DiffPresetDialog::update_tree()
const DynamicPrintConfig& right_congig = right_preset->config; const DynamicPrintConfig& right_congig = right_preset->config;
if (left_pt != right_preset->printer_technology()) { 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->SetBitmap_(ScalableBitmap(this, "question"));
preset_combos.equal_bmp->SetToolTip(bottom_info); preset_combos.equal_bmp->SetToolTip(bottom_info);
continue; continue;
@ -1651,7 +1651,7 @@ void DiffPresetDialog::update_tree()
show_tree = true; show_tree = true;
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "not_equal")); preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "not_equal"));
preset_combos.equal_bmp->SetToolTip(_L("Presets are different.\n" 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); 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->Add(cbox);
content_sizer->AddSpacer(VERT_SPACING); content_sizer->AddSpacer(VERT_SPACING);
Fit(); finalize();
} }
MsgUpdateSlic3r::~MsgUpdateSlic3r() {} MsgUpdateSlic3r::~MsgUpdateSlic3r() {}
@ -133,12 +133,12 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_
content_sizer->Add(versions); content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING); 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) { if (force_before_wizard) {
add_btn(wxID_CLOSE, false, _L("Don't install")); auto* btn = add_button(wxID_CLOSE, false, _L("Don't install"));
static_cast<wxButton*>(FindWindowById(wxID_CLOSE, this))->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) { this->EndModal(wxID_CLOSE); }); btn->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) { this->EndModal(wxID_CLOSE); });
} }
add_btn(wxID_CANCEL); add_button(wxID_CANCEL);
finalize(); finalize();
} }
@ -190,9 +190,9 @@ MsgUpdateForced::MsgUpdateForced(const std::vector<Update>& updates) :
content_sizer->Add(versions); content_sizer->Add(versions);
content_sizer->AddSpacer(2 * VERT_SPACING); 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 }) 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(); finalize();
} }
@ -236,11 +236,11 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, w
content_sizer->Add(versions); content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING); content_sizer->AddSpacer(2*VERT_SPACING);
add_btn(wxID_REPLACE, true, _L("Re-configure")); add_button(wxID_REPLACE, true, _L("Re-configure"));
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_REPLACE}) 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(); 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" wxString header = _L("Configuration Updates causes a lost of preset modification.\n"
"So, check unsaved changes and save them if necessary."); "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; return false;
// Reload global configuration // Reload global configuration

View File

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

View File

@ -3,7 +3,6 @@
#include <libslic3r/TriangleMesh.hpp> #include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/MeshBoolean.hpp> #include <libslic3r/MeshBoolean.hpp>
#include <libslic3r/SimplifyMesh.hpp>
using namespace Slic3r; 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");
//}