mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-11 17:08:58 +08:00
Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_world_coordinates
This commit is contained in:
commit
0a238adbab
@ -72,12 +72,12 @@ hypertext_settings_category = Layers and perimeters
|
||||
disabled_tags = SLA
|
||||
|
||||
[hint:Shapes gallery]
|
||||
text = Shapes gallery\nDid you know that PrusaSlicer has a Shapes Gallery? You can use the included models as modifiers, negative volumes or as printable objects. Right-click the platter and select<a>Add Shape - Gallery.</a>
|
||||
text = Shapes gallery\nDid you know that PrusaSlicer has a Shapes Gallery? You can use the included models as modifiers, negative volumes or as printable objects. Right-click the platter and select<a>Add Shape - Gallery</a>.
|
||||
hypertext_type = gallery
|
||||
disable_modes = simple
|
||||
|
||||
[hint:Auto-arrange settings]
|
||||
text = Auto-arrange settings\nDid you know that you can right-click the<a>auto-arrange icon</a>to adjust the size of the gap between objects and to allow automatic rotations?
|
||||
[hint:Arrange settings]
|
||||
text = Arrange settings\nDid you know that you can right-click the<a>Arrange icon</a>to adjust the size of the gap between objects and to allow automatic rotations?
|
||||
hypertext_type = plater
|
||||
hypertext_plater_item = arrange
|
||||
|
||||
@ -95,7 +95,7 @@ text = Reload from disk\nDid you know that if you created a newer version of you
|
||||
documentation_link = https://help.prusa3d.com/en/article/reload-from-disk_120427
|
||||
|
||||
[hint:Hiding sidebar]
|
||||
text = Hiding sidebar\nDid you know that you can hide the right sidebar using the shortcut <b>Shift+Tab</b>? You can also enable the icon for this from the<a>Preferences.</a>
|
||||
text = Hiding sidebar\nDid you know that you can hide the right sidebar using the shortcut <b>Shift+Tab</b>? You can also enable the icon for this from the<a>Preferences</a>.
|
||||
hypertext_type = preferences
|
||||
hypertext_preferences_page = 2
|
||||
hypertext_preferences_item = show_collapse_button
|
||||
@ -115,7 +115,7 @@ hypertext_gizmo_item = place
|
||||
text = Set number of instances\nDid you know that you can right-click a model and set an exact number of instances instead of copy-pasting it several times?
|
||||
|
||||
[hint:Combine infill]
|
||||
text = Combine infill\nDid you know that you can print the infill with a higher layer height compared to perimeters to save print time using the setting<a>Combine infill every.</a>
|
||||
text = Combine infill\nDid you know that you can print the infill with a higher layer height compared to perimeters to save print time using the setting<a>Combine infill every</a>.
|
||||
hypertext_type = settings
|
||||
hypertext_settings_opt = infill_every_layers
|
||||
hypertext_settings_type = 1
|
||||
@ -139,7 +139,7 @@ documentation_link= https://help.prusa3d.com/en/article/per-model-settings_1674
|
||||
disabled_tags = SLA
|
||||
|
||||
[hint:Solid infill threshold area]
|
||||
text = Solid infill threshold area\nDid you know that you can make parts of your model with a small cross-section be filled with solid infill automatically? Set the<a>Solid infill threshold area.</a>(Expert mode only.)
|
||||
text = Solid infill threshold area\nDid you know that you can make parts of your model with a small cross-section be filled with solid infill automatically? Set the<a>Solid infill threshold area</a>.(Expert mode only.)
|
||||
hypertext_type = settings
|
||||
hypertext_settings_opt = solid_infill_below_area
|
||||
hypertext_settings_type = 1
|
||||
@ -167,7 +167,7 @@ text = Mirror\nDid you know that you can mirror the selected model to create a r
|
||||
text = PageUp / PageDown quick rotation by 45 degrees\nDid you know that you can quickly rotate selected models by 45 degrees around the Z-axis clockwise or counter-clockwise by pressing <b>Page Up</b> or <b>Page Down</b> respectively?
|
||||
|
||||
[hint:Load config from G-code]
|
||||
text = Load config from G-code\nDid you know that you can use File-Import Config to load print, filament and printer profiles from an existing G-code file? Similarly, you can use File-Import SL1 archive, which also lets you reconstruct 3D models from the voxel data.
|
||||
text = Load config from G-code\nDid you know that you can use File-Import-Import Config to load print, filament and printer profiles from an existing G-code file? Similarly, you can use File-Import-Import SL1 / SL1S archive, which also lets you reconstruct 3D models from the voxel data.
|
||||
|
||||
[hint:Ironing]
|
||||
text = Ironing\nDid you know that you can smooth top surfaces of prints using Ironing? The nozzle will run a special second infill phase at the same layer to fill in holes and flatten any lifted plastic. Read more in the documentation. (Requires Advanced or Expert mode.)
|
||||
|
@ -4,7 +4,7 @@
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="hex_x5F_X">
|
||||
<g>
|
||||
<polygon fill="#808080" points="8,1 2,5 2,7 2,11 8,15 14,11 14,7 14,5 "/>
|
||||
<polygon fill="#818181" points="8,1 2,5 2,7 2,11 8,15 14,11 14,7 14,5 "/>
|
||||
</g>
|
||||
<g id="plus_2_">
|
||||
|
||||
|
Before Width: | Height: | Size: 782 B After Width: | Height: | Size: 782 B |
@ -4,7 +4,7 @@
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="hex_x5F_X">
|
||||
<g>
|
||||
<polygon fill="#808080" points="8,1 2,5 2,7 2,11 8,15 14,11 14,7 14,5 "/>
|
||||
<polygon fill="#818181" points="8,1 2,5 2,7 2,11 8,15 14,11 14,7 14,5 "/>
|
||||
</g>
|
||||
<g id="plus_2_">
|
||||
|
||||
|
Before Width: | Height: | Size: 786 B After Width: | Height: | Size: 786 B |
File diff suppressed because it is too large
Load Diff
@ -232,9 +232,6 @@ add_library(libslic3r STATIC
|
||||
MinAreaBoundingBox.cpp
|
||||
miniz_extension.hpp
|
||||
miniz_extension.cpp
|
||||
SimplifyMesh.hpp
|
||||
SimplifyMeshImpl.hpp
|
||||
SimplifyMesh.cpp
|
||||
MarchingSquares.hpp
|
||||
Execution/Execution.hpp
|
||||
Execution/ExecutionSeq.hpp
|
||||
|
@ -490,6 +490,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
|
||||
gap_over_supports += support_layer_height_min;
|
||||
}
|
||||
|
||||
std::vector<std::pair<double, double>> warning_ranges;
|
||||
|
||||
// Pair the object layers with the support layers by z.
|
||||
size_t idx_object_layer = 0;
|
||||
size_t idx_support_layer = 0;
|
||||
@ -535,15 +537,8 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
|
||||
// Negative support_contact_z is not taken into account, it can result in false positives in cases
|
||||
// where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752)
|
||||
|
||||
if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) {
|
||||
const_cast<Print*>(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
|
||||
Slic3r::format(_(L("Empty layer detected between heights %1% and %2%. Make sure the object is printable.")),
|
||||
(last_extrusion_layer ? last_extrusion_layer->print_z() : 0.),
|
||||
layers_to_print.back().print_z())
|
||||
+ "\n" + Slic3r::format(_(L("Object name: %1%")), object.model_object()->name) + "\n\n"
|
||||
+ _(L("This is usually caused by negligibly small extrusions or by a faulty model. "
|
||||
"Try to repair the model or change its orientation on the bed.")));
|
||||
}
|
||||
if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON)
|
||||
warning_ranges.emplace_back(std::make_pair((last_extrusion_layer ? last_extrusion_layer->print_z() : 0.), layers_to_print.back().print_z()));
|
||||
|
||||
// Remember last layer with extrusions.
|
||||
if (has_extrusions)
|
||||
@ -551,6 +546,23 @@ std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObjec
|
||||
}
|
||||
}
|
||||
|
||||
if (! warning_ranges.empty()) {
|
||||
std::string warning;
|
||||
size_t i = 0;
|
||||
for (i = 0; i < std::min(warning_ranges.size(), size_t(3)); ++i)
|
||||
warning += Slic3r::format(_(L("Empty layer between %1% and %2%.")),
|
||||
warning_ranges[i].first, warning_ranges[i].second) + "\n";
|
||||
if (i < warning_ranges.size())
|
||||
warning += _(L("(Some lines not shown)")) + "\n";
|
||||
warning += "\n";
|
||||
warning += Slic3r::format(_(L("Object name: %1%")), object.model_object()->name) + "\n\n"
|
||||
+ _(L("Make sure the object is printable. This is usually caused by negligibly small extrusions or by a faulty model. "
|
||||
"Try to repair the model or change its orientation on the bed."));
|
||||
|
||||
const_cast<Print*>(object.print())->active_step_add_warning(
|
||||
PrintStateBase::WarningLevel::CRITICAL, warning);
|
||||
}
|
||||
|
||||
return layers_to_print;
|
||||
}
|
||||
|
||||
@ -732,7 +744,7 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re
|
||||
reports += source + ": \"" + keyword + "\"\n";
|
||||
}
|
||||
print->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
|
||||
_(L("Found reserved keyword(s) into custom g-code:")) + "\n" +
|
||||
_(L("In the custom G-code were found reserved keywords:")) + "\n" +
|
||||
reports +
|
||||
_(L("This may cause problems in g-code visualization and printing time estimation.")));
|
||||
}
|
||||
|
@ -1379,6 +1379,14 @@ const Preset* PrinterPresetCollection::find_system_preset_by_model_and_variant(c
|
||||
return it != cend() ? &*it : nullptr;
|
||||
}
|
||||
|
||||
bool PrinterPresetCollection::only_default_printers() const
|
||||
{
|
||||
for (const auto& printer : get_presets()) {
|
||||
if (!boost::starts_with(printer.name,"- default"))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// -------------------------
|
||||
// *** PhysicalPrinter ***
|
||||
// -------------------------
|
||||
|
@ -597,6 +597,7 @@ public:
|
||||
|
||||
const Preset* find_system_preset_by_model_and_variant(const std::string &model_id, const std::string &variant) const;
|
||||
|
||||
bool only_default_printers() const;
|
||||
private:
|
||||
PrinterPresetCollection() = default;
|
||||
PrinterPresetCollection(const PrinterPresetCollection &other) = default;
|
||||
|
@ -481,7 +481,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def = this->add("brim_width", coFloat);
|
||||
def->label = L("Brim width");
|
||||
def->category = L("Skirt and brim");
|
||||
def->tooltip = L("Horizontal width of the brim that will be printed around each object on the first layer."
|
||||
def->tooltip = L("The horizontal width of the brim that will be printed around each object on the first layer. "
|
||||
"When raft is used, no brim is generated (use raft_first_layer_expansion).");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
@ -1252,7 +1252,8 @@ void PrintConfigDef::init_fff_params()
|
||||
def = this->add("fuzzy_skin_thickness", coFloat);
|
||||
def->label = L("Fuzzy skin thickness");
|
||||
def->category = L("Fuzzy Skin");
|
||||
def->tooltip = "";
|
||||
def->tooltip = L("The maximum distance that each skin point can be offset (both ways), "
|
||||
"measured perpendicular to the perimeter wall.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
@ -1261,7 +1262,8 @@ void PrintConfigDef::init_fff_params()
|
||||
def = this->add("fuzzy_skin_point_dist", coFloat);
|
||||
def->label = L("Fuzzy skin point distance");
|
||||
def->category = L("Fuzzy Skin");
|
||||
def->tooltip = "";
|
||||
def->tooltip = L("Perimeters will be split into multiple segments by inserting Fuzzy skin points. "
|
||||
"Lowering the Fuzzy skin point distance will increase the number of randomly offset points on the perimeter wall.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
@ -2439,13 +2441,13 @@ void PrintConfigDef::init_fff_params()
|
||||
def = this->add("slicing_mode", coEnum);
|
||||
def->label = L("Slicing Mode");
|
||||
def->category = L("Advanced");
|
||||
def->tooltip = L("Use \"Even / Odd\" for 3DLabPrint airplane models. Use \"Close holes\" to close all holes in the model.");
|
||||
def->tooltip = L("Use \"Even-odd\" for 3DLabPrint airplane models. Use \"Close holes\" to close all holes in the model.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<SlicingMode>::get_enum_values();
|
||||
def->enum_values.push_back("regular");
|
||||
def->enum_values.push_back("even_odd");
|
||||
def->enum_values.push_back("close_holes");
|
||||
def->enum_labels.push_back(L("Regular"));
|
||||
def->enum_labels.push_back(L("Even / Odd"));
|
||||
def->enum_labels.push_back(L("Even-odd"));
|
||||
def->enum_labels.push_back(L("Close holes"));
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<SlicingMode>(SlicingMode::Regular));
|
||||
@ -2745,7 +2747,8 @@ void PrintConfigDef::init_fff_params()
|
||||
def = this->add("thick_bridges", coBool);
|
||||
def->label = L("Thick bridges");
|
||||
def->category = L("Layers and Perimeters");
|
||||
def->tooltip = L("Print bridges with round extrusions.");
|
||||
def->tooltip = L("If enabled, bridges are more reliable, can bridge longer distances, but may look worse. "
|
||||
"If disabled, bridges look better but are reliable just for shorter bridged distances.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
@ -4268,7 +4271,7 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
||||
|
||||
def = this->add("config_compatibility", coEnum);
|
||||
def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF).");
|
||||
def->tooltip = L("This version of PrusaSlicer may not understand configurations produced by newest PrusaSlicer versions. "
|
||||
def->tooltip = L("This version of PrusaSlicer may not understand configurations produced by the newest PrusaSlicer versions. "
|
||||
"For example, newer PrusaSlicer may extend the list of supported firmware flavors. One may decide to "
|
||||
"bail out or to substitute an unknown value with a default silently or verbosely.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>::get_enum_values();
|
||||
|
@ -2,7 +2,6 @@
|
||||
#include <tuple>
|
||||
#include <optional>
|
||||
#include "MutablePriorityQueue.hpp"
|
||||
#include "SimplifyMeshImpl.hpp"
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
using namespace Slic3r;
|
||||
@ -13,10 +12,47 @@ using namespace Slic3r;
|
||||
|
||||
// only private namespace not neccessary be in .hpp
|
||||
namespace QuadricEdgeCollapse {
|
||||
// SymetricMatrix
|
||||
class SymMat {
|
||||
using T = double;
|
||||
static const constexpr size_t N = 10;
|
||||
T m[N];
|
||||
public:
|
||||
explicit SymMat(ArithmeticOnly<T> c = T()) { std::fill(m, m + N, c); }
|
||||
|
||||
// Make plane
|
||||
SymMat(T a, T b, T c, T d)
|
||||
{
|
||||
m[0] = a * a; m[1] = a * b; m[2] = a * c; m[3] = a * d;
|
||||
m[4] = b * b; m[5] = b * c; m[6] = b * d;
|
||||
m[7] = c * c; m[8] = c * d;
|
||||
m[9] = d * d;
|
||||
}
|
||||
|
||||
T operator[](int c) const { return m[c]; }
|
||||
|
||||
// Determinant
|
||||
T det(int a11, int a12, int a13,
|
||||
int a21, int a22, int a23,
|
||||
int a31, int a32, int a33) const
|
||||
{
|
||||
T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] +
|
||||
m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] -
|
||||
m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33];
|
||||
|
||||
return det;
|
||||
}
|
||||
|
||||
const SymMat &operator+=(const SymMat &n)
|
||||
{
|
||||
for (size_t i = 0; i < N; ++i) m[i] += n[i];
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
using Vertices = std::vector<stl_vertex>;
|
||||
using Triangle = stl_triangle_vertex_indices;
|
||||
using Indices = std::vector<stl_triangle_vertex_indices>;
|
||||
using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>;
|
||||
using ThrowOnCancel = std::function<void(void)>;
|
||||
using StatusFn = std::function<void(int)>;
|
||||
// smallest error caused by edges, identify smallest edge in triangle
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <libslic3r/SLA/IndexedMesh.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/SimplifyMesh.hpp>
|
||||
#include <libslic3r/QuadricEdgeCollapse.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeMesher.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
@ -132,7 +132,10 @@ InteriorPtr generate_interior(const TriangleMesh & mesh,
|
||||
|
||||
// flip normals back...
|
||||
swap_normals(interior->mesh);
|
||||
Slic3r::simplify_mesh(interior->mesh);
|
||||
|
||||
// simplify mesh lossless
|
||||
float loss_less_max_error = 2*std::numeric_limits<float>::epsilon();
|
||||
its_quadric_edge_collapse(interior->mesh, 0U, &loss_less_max_error);
|
||||
|
||||
its_compactify_vertices(interior->mesh);
|
||||
its_merge_vertices(interior->mesh);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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
|
@ -13,7 +13,10 @@
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define HAS_GLSAFE
|
||||
#endif // NDEBUG
|
||||
|
||||
#ifdef HAS_GLSAFE
|
||||
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
|
||||
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
|
||||
|
@ -559,7 +559,9 @@ PagePrinters::PagePrinters(ConfigWizard *parent,
|
||||
|
||||
append(picker);
|
||||
printer_pickers.push_back(picker);
|
||||
has_printers = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PagePrinters::select_all(bool select, bool alternates)
|
||||
@ -599,7 +601,7 @@ std::set<std::string> PagePrinters::get_selected_models()
|
||||
|
||||
void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason)
|
||||
{
|
||||
if (technology == T_FFF
|
||||
if (is_primary_printer_page
|
||||
&& (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY)
|
||||
&& printer_pickers.size() > 0
|
||||
&& printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) {
|
||||
@ -770,7 +772,11 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
|
||||
, second_line
|
||||
);
|
||||
} else {
|
||||
wxString second_line = printer_names.empty() ? "" : format_wxstr(_L("Only the following installed printers are compatible with the selected %1%:"), materials->technology == T_FFF ? _L("filament") : _L("SLA material"));
|
||||
wxString second_line;
|
||||
if (!printer_names.empty())
|
||||
second_line = (materials->technology == T_FFF ?
|
||||
_L("Only the following installed printers are compatible with the selected filaments") :
|
||||
_L("Only the following installed printers are compatible with the selected SLA materials")) + ":";
|
||||
text = wxString::Format(
|
||||
"<html>"
|
||||
"<style>"
|
||||
@ -1904,25 +1910,28 @@ void ConfigWizard::priv::load_pages()
|
||||
index->add_page(page_welcome);
|
||||
|
||||
// Printers
|
||||
index->add_page(page_fff);
|
||||
if (!only_sla_mode)
|
||||
index->add_page(page_fff);
|
||||
index->add_page(page_msla);
|
||||
index->add_page(page_vendors);
|
||||
for (const auto &pages : pages_3rdparty) {
|
||||
for ( PagePrinters* page : { pages.second.first, pages.second.second })
|
||||
if (page && page->install)
|
||||
index->add_page(page);
|
||||
}
|
||||
if (!only_sla_mode) {
|
||||
index->add_page(page_vendors);
|
||||
for (const auto &pages : pages_3rdparty) {
|
||||
for ( PagePrinters* page : { pages.second.first, pages.second.second })
|
||||
if (page && page->install)
|
||||
index->add_page(page);
|
||||
}
|
||||
|
||||
index->add_page(page_custom);
|
||||
if (page_custom->custom_wanted()) {
|
||||
index->add_page(page_firmware);
|
||||
index->add_page(page_bed);
|
||||
index->add_page(page_diams);
|
||||
index->add_page(page_temps);
|
||||
}
|
||||
index->add_page(page_custom);
|
||||
if (page_custom->custom_wanted()) {
|
||||
index->add_page(page_firmware);
|
||||
index->add_page(page_bed);
|
||||
index->add_page(page_diams);
|
||||
index->add_page(page_temps);
|
||||
}
|
||||
|
||||
// Filaments & Materials
|
||||
if (any_fff_selected) { index->add_page(page_filaments); }
|
||||
if (any_fff_selected) { index->add_page(page_filaments); }
|
||||
}
|
||||
if (any_sla_selected) { index->add_page(page_sla_materials); }
|
||||
|
||||
// there should to be selected at least one printer
|
||||
@ -1956,7 +1965,7 @@ void ConfigWizard::priv::init_dialog_size()
|
||||
9*disp_rect.width / 10,
|
||||
9*disp_rect.height / 10);
|
||||
|
||||
const int width_hint = index->GetSize().GetWidth() + page_fff->get_width() + 30 * em(); // XXX: magic constant, I found no better solution
|
||||
const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em()); // XXX: magic constant, I found no better solution
|
||||
if (width_hint < window_rect.width) {
|
||||
window_rect.x += (window_rect.width - width_hint) / 2;
|
||||
window_rect.width = width_hint;
|
||||
@ -2483,7 +2492,7 @@ static std::string get_first_added_preset(const std::map<std::string, std::strin
|
||||
|
||||
bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes)
|
||||
{
|
||||
wxString header, caption = _L("Configuration is editing from ConfigWizard");
|
||||
wxString header, caption = _L("Configuration is edited in ConfigWizard");
|
||||
const auto enabled_vendors = appconfig_new.vendors();
|
||||
|
||||
bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model());
|
||||
@ -2556,7 +2565,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
}
|
||||
if (!check_unsaved_preset_changes)
|
||||
if ((check_unsaved_preset_changes = install_bundles.size() > 0))
|
||||
header = _L_PLURAL("New vendor was installed and one of its printer will be activated", "New vendors were installed and one of theirs printer will be activated", install_bundles.size());
|
||||
header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size());
|
||||
|
||||
#ifdef __linux__
|
||||
// Desktop integration on Linux
|
||||
@ -2585,7 +2594,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
break;
|
||||
}
|
||||
|
||||
if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?")))
|
||||
if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?")))
|
||||
return false;
|
||||
|
||||
if (check_unsaved_preset_changes &&
|
||||
@ -2725,7 +2734,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem,
|
||||
{preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
|
||||
|
||||
if (page_custom->custom_wanted()) {
|
||||
if (!only_sla_mode && page_custom->custom_wanted()) {
|
||||
// if unsaved changes was not cheched till this moment
|
||||
if (!check_unsaved_preset_changes &&
|
||||
!wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes))
|
||||
@ -2838,25 +2847,38 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
|
||||
|
||||
p->add_page(p->page_welcome = new PageWelcome(this));
|
||||
|
||||
|
||||
p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF);
|
||||
p->add_page(p->page_fff);
|
||||
p->only_sla_mode = !p->page_fff->has_printers;
|
||||
if (!p->only_sla_mode) {
|
||||
p->add_page(p->page_fff);
|
||||
p->page_fff->is_primary_printer_page = true;
|
||||
}
|
||||
|
||||
|
||||
p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA);
|
||||
p->add_page(p->page_msla);
|
||||
if (p->only_sla_mode) {
|
||||
p->page_msla->is_primary_printer_page = true;
|
||||
}
|
||||
|
||||
// Pages for 3rd party vendors
|
||||
p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors
|
||||
p->add_page(p->page_vendors = new PageVendors(this));
|
||||
p->add_page(p->page_custom = new PageCustom(this));
|
||||
p->custom_printer_selected = p->page_custom->custom_wanted();
|
||||
if (!p->only_sla_mode) {
|
||||
// Pages for 3rd party vendors
|
||||
p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors
|
||||
p->add_page(p->page_vendors = new PageVendors(this));
|
||||
p->add_page(p->page_custom = new PageCustom(this));
|
||||
p->custom_printer_selected = p->page_custom->custom_wanted();
|
||||
}
|
||||
|
||||
p->any_sla_selected = p->check_sla_selected();
|
||||
p->any_fff_selected = p->check_fff_selected();
|
||||
if (p->only_sla_mode)
|
||||
p->any_fff_selected = p->check_fff_selected();
|
||||
|
||||
p->update_materials(T_ANY);
|
||||
if (!p->only_sla_mode)
|
||||
p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
|
||||
_L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") ));
|
||||
|
||||
p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
|
||||
_L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") ));
|
||||
p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
|
||||
_L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") ));
|
||||
|
||||
|
@ -257,6 +257,9 @@ struct PagePrinters: ConfigWizardPage
|
||||
std::string get_vendor_id() const { return printer_pickers.empty() ? "" : printer_pickers[0]->vendor_id; }
|
||||
|
||||
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
|
||||
|
||||
bool has_printers { false };
|
||||
bool is_primary_printer_page { false };
|
||||
};
|
||||
|
||||
// Here we extend wxListBox and wxCheckListBox
|
||||
@ -548,7 +551,9 @@ struct ConfigWizard::priv
|
||||
std::unique_ptr<DynamicPrintConfig> custom_config; // Backing for custom printer definition
|
||||
bool any_fff_selected; // Used to decide whether to display Filaments page
|
||||
bool any_sla_selected; // Used to decide whether to display SLA Materials page
|
||||
bool custom_printer_selected;
|
||||
bool custom_printer_selected { false };
|
||||
// Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers)
|
||||
bool only_sla_mode { false };
|
||||
|
||||
wxScrolledWindow *hscroll = nullptr;
|
||||
wxBoxSizer *hscroll_sizer = nullptr;
|
||||
|
@ -366,7 +366,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
||||
}
|
||||
} else {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not find applications directory";
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -374,8 +374,8 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
||||
}
|
||||
if(target_dir_desktop.empty()) {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not find applications directory";
|
||||
show_error(nullptr, _L("Performing desktop integration failed - could not find applications directory."));
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found.";
|
||||
show_error(nullptr, _L("Performing desktop integration failed because the application directory was not found."));
|
||||
return;
|
||||
}
|
||||
// save path to desktop file
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
@ -1143,7 +1144,7 @@ void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObje
|
||||
}
|
||||
}
|
||||
|
||||
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx)
|
||||
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv)
|
||||
{
|
||||
for (GLVolume* vol : m_volumes.volumes) {
|
||||
if (vol->composite_id.object_id == 1000) { // wipe tower
|
||||
@ -1151,7 +1152,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
|
||||
}
|
||||
else {
|
||||
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
|
||||
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)) {
|
||||
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
|
||||
&& (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) {
|
||||
vol->is_active = visible;
|
||||
|
||||
if (instance_idx == -1) {
|
||||
@ -5238,11 +5240,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
|
||||
// visible when inside modifier meshes etc.
|
||||
{
|
||||
const GLGizmosManager& gm = get_gizmos_manager();
|
||||
GLGizmosManager::EType type = gm.get_current_type();
|
||||
if (type == GLGizmosManager::FdmSupports
|
||||
|| type == GLGizmosManager::Seam
|
||||
|| type == GLGizmosManager::MmuSegmentation
|
||||
|| type == GLGizmosManager::Simplify ) {
|
||||
// GLGizmosManager::EType type = gm.get_current_type();
|
||||
if (dynamic_cast<GLGizmoPainterBase*>(gm.get_current())) {
|
||||
shader->stop_using();
|
||||
gm.render_painter_gizmo();
|
||||
shader->start_using();
|
||||
|
@ -635,7 +635,7 @@ public:
|
||||
void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); }
|
||||
|
||||
void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
|
||||
void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
|
||||
void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1, const ModelVolume* mv = nullptr);
|
||||
void update_instance_printable_state_for_object(size_t obj_idx);
|
||||
void update_instance_printable_state_for_objects(const std::vector<size_t>& object_idxs);
|
||||
|
||||
|
@ -204,7 +204,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
wxLogError(format_wxstr(_L("Internal error when changing value for %1%: %2%"), opt_key, e.what()));
|
||||
wxLogError(format_wxstr("Internal error when changing value for %1%: %2%", opt_key, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -812,13 +812,11 @@ static boost::optional<Semver> parse_semver_from_ini(std::string path)
|
||||
std::stringstream buffer;
|
||||
buffer << stream.rdbuf();
|
||||
std::string body = buffer.str();
|
||||
size_t end_line = body.find_first_of("\n\r");
|
||||
body.resize(end_line);
|
||||
size_t start = body.find("PrusaSlicer ");
|
||||
if (start == std::string::npos)
|
||||
return boost::none;
|
||||
body = body.substr(start + 12);
|
||||
size_t end = body.find_first_of(" \n\r");
|
||||
size_t end = body.find_first_of(" \n");
|
||||
if (end < body.size())
|
||||
body.resize(end);
|
||||
return Semver::parse(body);
|
||||
@ -2497,7 +2495,7 @@ bool GUI_App::can_load_project()
|
||||
int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified."));
|
||||
if (saved_project == wxID_CANCEL ||
|
||||
(plater()->is_project_dirty() && saved_project == wxID_NO &&
|
||||
!check_and_save_current_preset_changes(_L("Project is loading"), _L("Loading a new project while some presets are modified."))))
|
||||
!check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved."))))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@ -2879,7 +2877,7 @@ void GUI_App::window_pos_sanitize(wxTopLevelWindow* window)
|
||||
|
||||
bool GUI_App::config_wizard_startup()
|
||||
{
|
||||
if (!m_app_conf_exists || preset_bundle->printers.size() <= 1) {
|
||||
if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) {
|
||||
run_wizard(ConfigWizard::RR_DATA_EMPTY);
|
||||
return true;
|
||||
} else if (get_app_config()->legacy_datadir()) {
|
||||
@ -2921,7 +2919,7 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/*
|
||||
bool launch = true;
|
||||
|
||||
if (get_app_config()->get("suppress_hyperlinks").empty()) {
|
||||
RichMessageDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
|
||||
RichMessageDialog dialog(nullptr, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
|
||||
dialog.ShowCheckBox(_L("Remember my choice"));
|
||||
int answer = dialog.ShowModal();
|
||||
launch = answer == wxID_YES;
|
||||
|
@ -419,10 +419,10 @@ MeshErrorsInfo ObjectList::get_mesh_errors_info(const int obj_idx, const int vol
|
||||
if (repaired.facets_reversed > 0)
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet reversed", "%1$d facets reversed", repaired.facets_reversed), repaired.facets_reversed) + "\n";
|
||||
if (repaired.backwards_edges > 0)
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d backwards edge", "%1$d backwards edges", repaired.backwards_edges), repaired.backwards_edges) + "\n";
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d backward edge", "%1$d backward edges", repaired.backwards_edges), repaired.backwards_edges) + "\n";
|
||||
}
|
||||
if (!stats.manifold()) {
|
||||
remaining_info = format_wxstr(_L_PLURAL("Remaining %1$d open edge", "Remaining %1$d open edges", stats.open_edges), stats.open_edges);
|
||||
remaining_info = format_wxstr(_L_PLURAL("%1$d open edge", "%1$d open edges", stats.open_edges), stats.open_edges);
|
||||
|
||||
tooltip += _L("Remaning errors") + ":\n";
|
||||
tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d open edge", "%1$d open edges", stats.open_edges), stats.open_edges) + "\n";
|
||||
@ -4165,7 +4165,7 @@ void ObjectList::fix_through_netfabb()
|
||||
wxString msg;
|
||||
wxString bullet_suf = "\n - ";
|
||||
if (!succes_models.empty()) {
|
||||
msg = _L_PLURAL("Folowing model is repaired successfully", "Folowing models are repaired successfully", succes_models.size()) + ":";
|
||||
msg = _L_PLURAL("The following model was repaired successfully", "The following models were repaired successfully", succes_models.size()) + ":";
|
||||
for (auto& model : succes_models)
|
||||
msg += bullet_suf + from_u8(model);
|
||||
msg += "\n\n";
|
||||
|
@ -727,7 +727,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
|
||||
NotificationType::SignDetected, NotificationManager::NotificationLevel::PrintInfoNotificationLevel,
|
||||
_u8L("NOTE:") + "\n" +
|
||||
format(_u8L("Sliced object \"%1%\" looks like a logo or a sign"), object->model_object()->name) + "\n",
|
||||
_u8L("Apply automatic color change"),
|
||||
_u8L("Apply color change automatically"),
|
||||
[this](wxEvtHandler*) {
|
||||
m_layers_slider->auto_color_change();
|
||||
return true;
|
||||
|
@ -286,7 +286,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
m_imgui->tooltip(_L("Split bigger facets into smaller ones while the object is painted."), max_tooltip_width);
|
||||
m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width);
|
||||
|
||||
m_imgui->disabled_end();
|
||||
} else {
|
||||
|
@ -118,7 +118,7 @@ bool GLGizmoMmuSegmentation::on_init()
|
||||
m_desc["second_color"] = _L("Second color");
|
||||
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
|
||||
m_desc["remove"] = _L("Remove painted color");
|
||||
m_desc["remove_all"] = _L("Remove all painted areas");
|
||||
m_desc["remove_all"] = _L("Clear all");
|
||||
m_desc["circle"] = _L("Circle");
|
||||
m_desc["sphere"] = _L("Sphere");
|
||||
m_desc["pointer"] = _L("Triangles");
|
||||
|
@ -97,6 +97,8 @@ public:
|
||||
// will be also extended to support additional states, requiring at least one state to remain free out of 19 states.
|
||||
static const constexpr size_t EXTRUDERS_LIMIT = 16;
|
||||
|
||||
virtual const float get_cursor_radius_min() const { return CursorRadiusMin; }
|
||||
|
||||
protected:
|
||||
std::array<float, 4> get_cursor_sphere_left_button_color() const override;
|
||||
std::array<float, 4> get_cursor_sphere_right_button_color() const override;
|
||||
@ -120,6 +122,8 @@ protected:
|
||||
std::vector<std::array<float, 4>> m_modified_extruders_colors;
|
||||
std::vector<int> m_original_volumes_extruder_idxs;
|
||||
|
||||
static const constexpr float CursorRadiusMin = 0.1f; // cannot be zero
|
||||
|
||||
private:
|
||||
bool on_init() override;
|
||||
|
||||
|
@ -254,8 +254,8 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
}
|
||||
else if (alt_down) {
|
||||
if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) {
|
||||
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
|
||||
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
|
||||
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - this->get_cursor_radius_step(), this->get_cursor_radius_min())
|
||||
: std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max());
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
} else if (m_tool_type == ToolType::SMART_FILL) {
|
||||
|
@ -99,20 +99,11 @@ protected:
|
||||
GLPaintContour m_paint_contour;
|
||||
};
|
||||
|
||||
class GLGizmoTransparentRender
|
||||
{
|
||||
public:
|
||||
// Following function renders the triangles and cursor. Having this separated
|
||||
// from usual on_render method allows to render them before transparent
|
||||
// objects, so they can be seen inside them. The usual on_render is called
|
||||
// after all volumes (including transparent ones) are rendered.
|
||||
virtual void render_painter_gizmo() const = 0;
|
||||
};
|
||||
|
||||
// Following class is a base class for a gizmo with ability to paint on mesh
|
||||
// using circular blush (such as FDM supports gizmo and seam painting gizmo).
|
||||
// The purpose is not to duplicate code related to mesh painting.
|
||||
class GLGizmoPainterBase : public GLGizmoTransparentRender, public GLGizmoBase
|
||||
class GLGizmoPainterBase : public GLGizmoBase
|
||||
{
|
||||
private:
|
||||
ObjectID m_old_mo_id;
|
||||
@ -125,6 +116,16 @@ public:
|
||||
virtual void set_painter_gizmo_data(const Selection& selection);
|
||||
virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
||||
// Following function renders the triangles and cursor. Having this separated
|
||||
// from usual on_render method allows to render them before transparent
|
||||
// objects, so they can be seen inside them. The usual on_render is called
|
||||
// after all volumes (including transparent ones) are rendered.
|
||||
virtual void render_painter_gizmo() const = 0;
|
||||
|
||||
virtual const float get_cursor_radius_min() const { return CursorRadiusMin; }
|
||||
virtual const float get_cursor_radius_max() const { return CursorRadiusMax; }
|
||||
virtual const float get_cursor_radius_step() const { return CursorRadiusStep; }
|
||||
|
||||
protected:
|
||||
virtual void render_triangles(const Selection& selection) const;
|
||||
void render_cursor() const;
|
||||
|
@ -1,60 +1,94 @@
|
||||
#include "GLGizmoSimplify.hpp"
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/QuadricEdgeCollapse.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
// Extend call after only when Simplify gizmo is still alive
|
||||
static void call_after_if_active(std::function<void()> fn, GUI_App* app = &wxGetApp())
|
||||
{
|
||||
// check application GUI
|
||||
if (app == nullptr) return;
|
||||
app->CallAfter([fn, app]() {
|
||||
// app must exist because it call this
|
||||
// if (app == nullptr) return;
|
||||
const Plater *plater = app->plater();
|
||||
if (plater == nullptr) return;
|
||||
const GLCanvas3D *canvas = plater->canvas3D();
|
||||
if (canvas == nullptr) return;
|
||||
const GLGizmosManager &mng = canvas->get_gizmos_manager();
|
||||
// check if simplify is still activ gizmo
|
||||
if (mng.get_current_type() != GLGizmosManager::Simplify) return;
|
||||
fn();
|
||||
});
|
||||
}
|
||||
|
||||
static ModelVolume* get_model_volume(const Selection& selection, Model& model)
|
||||
{
|
||||
const Selection::IndicesList& idxs = selection.get_volume_idxs();
|
||||
// only one selected volume
|
||||
if (idxs.size() != 1)
|
||||
return nullptr;
|
||||
const GLVolume* selected_volume = selection.get_volume(*idxs.begin());
|
||||
if (selected_volume == nullptr)
|
||||
return nullptr;
|
||||
|
||||
const GLVolume::CompositeID& cid = selected_volume->composite_id;
|
||||
const ModelObjectPtrs& objs = model.objects;
|
||||
if (cid.object_id < 0 || objs.size() <= static_cast<size_t>(cid.object_id))
|
||||
return nullptr;
|
||||
const ModelObject* obj = objs[cid.object_id];
|
||||
if (cid.volume_id < 0 || obj->volumes.size() <= static_cast<size_t>(cid.volume_id))
|
||||
return nullptr;
|
||||
return obj->volumes[cid.volume_id];
|
||||
}
|
||||
|
||||
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
|
||||
const std::string &icon_filename,
|
||||
unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, -1)
|
||||
, m_state(State::settings)
|
||||
, m_is_valid_result(false)
|
||||
, m_exist_preview(false)
|
||||
, m_progress(0)
|
||||
, m_volume(nullptr)
|
||||
, m_obj_index(0)
|
||||
, m_need_reload(false)
|
||||
, m_show_wireframe(false)
|
||||
, m_move_to_center(false)
|
||||
// translation for GUI size
|
||||
, tr_mesh_name(_u8L("Mesh name"))
|
||||
, tr_triangles(_u8L("Triangles"))
|
||||
, tr_preview(_u8L("Preview"))
|
||||
, tr_detail_level(_u8L("Detail level"))
|
||||
, tr_decimate_ratio(_u8L("Decimate ratio"))
|
||||
// for wireframe
|
||||
, m_wireframe_VBO_id(0)
|
||||
, m_wireframe_IBO_id(0)
|
||||
, m_wireframe_IBO_size(0)
|
||||
{}
|
||||
|
||||
GLGizmoSimplify::~GLGizmoSimplify() {
|
||||
m_state = State::canceling;
|
||||
if (m_worker.joinable()) m_worker.join();
|
||||
free_gpu();
|
||||
GLGizmoSimplify::~GLGizmoSimplify()
|
||||
{
|
||||
stop_worker_thread_request();
|
||||
if (m_worker.joinable())
|
||||
m_worker.join();
|
||||
m_glmodel.reset();
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::on_esc_key_down() {
|
||||
if (m_state == State::settings || m_state == State::canceling)
|
||||
return false;
|
||||
/*if (!m_is_worker_running)
|
||||
return false;
|
||||
|
||||
m_state = State::canceling;
|
||||
return true;
|
||||
stop_worker_thread_request();
|
||||
return true;*/
|
||||
}
|
||||
|
||||
// while opening needs GLGizmoSimplify to set window position
|
||||
void GLGizmoSimplify::add_simplify_suggestion_notification(
|
||||
const std::vector<size_t> &object_ids,
|
||||
const ModelObjectPtrs & objects,
|
||||
const std::vector<ModelObject*>& objects,
|
||||
NotificationManager & manager)
|
||||
{
|
||||
std::vector<size_t> big_ids;
|
||||
@ -74,14 +108,10 @@ void GLGizmoSimplify::add_simplify_suggestion_notification(
|
||||
if (big_ids.empty()) return;
|
||||
|
||||
for (size_t object_id : big_ids) {
|
||||
std::string t = _u8L(
|
||||
"Processing model '@object_name' with more than 1M triangles "
|
||||
std::string t = GUI::format(_u8L(
|
||||
"Processing model '%1%' with more than 1M triangles "
|
||||
"could be slow. It is highly recommend to reduce "
|
||||
"amount of triangles.");
|
||||
t.replace(t.find("@object_name"), sizeof("@object_name") - 1,
|
||||
objects[object_id]->name);
|
||||
// std::stringstream text;
|
||||
// text << t << "\n";
|
||||
"amount of triangles."), objects[object_id]->name);
|
||||
std::string hypertext = _u8L("Simplify model");
|
||||
|
||||
std::function<bool(wxEvtHandler *)> open_simplify =
|
||||
@ -116,39 +146,47 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
{
|
||||
create_gui_cfg();
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
int obj_index = selection.get_object_idx();
|
||||
ModelVolume *act_volume = get_volume(selection, wxGetApp().plater()->model());
|
||||
const ModelVolume *act_volume = get_model_volume(selection, wxGetApp().plater()->model());
|
||||
if (act_volume == nullptr) {
|
||||
switch (m_state) {
|
||||
case State::settings: close(); break;
|
||||
case State::canceling: break;
|
||||
default: m_state = State::canceling;
|
||||
}
|
||||
stop_worker_thread_request();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_cancelling = false;
|
||||
bool is_worker_running = false;
|
||||
bool is_result_ready = false;
|
||||
int progress = 0;
|
||||
{
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
is_cancelling = m_state.status == State::cancelling;
|
||||
is_worker_running = m_state.status == State::running;
|
||||
is_result_ready = bool(m_state.result);
|
||||
progress = m_state.progress;
|
||||
}
|
||||
|
||||
// Whether to trigger calculation after rendering is done.
|
||||
bool start_process = false;
|
||||
|
||||
|
||||
// Check selection of new volume
|
||||
// Do not reselect object when processing
|
||||
if (act_volume != m_volume && m_state == State::settings) {
|
||||
if (act_volume != m_volume) {
|
||||
bool change_window_position = (m_volume == nullptr);
|
||||
// select different model
|
||||
if (m_volume != nullptr && m_original_its.has_value()) {
|
||||
set_its(*m_original_its);
|
||||
}
|
||||
|
||||
// close suggestion notification
|
||||
auto notification_manager = wxGetApp().plater()->get_notification_manager();
|
||||
notification_manager->remove_simplify_suggestion_with_id(act_volume->get_object()->id());
|
||||
|
||||
m_obj_index = obj_index; // to remember correct object
|
||||
m_volume = act_volume;
|
||||
m_original_its = {};
|
||||
m_configuration.decimate_ratio = 50.; // default value
|
||||
m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size());
|
||||
m_is_valid_result = false;
|
||||
m_exist_preview = false;
|
||||
init_wireframe();
|
||||
live_preview();
|
||||
init_model(m_volume->mesh().its);
|
||||
|
||||
// Start processing. If we switched from another object, process will
|
||||
// stop the background thread and it will restart itself later.
|
||||
start_process = true;
|
||||
|
||||
// set window position
|
||||
if (m_move_to_center && change_window_position) {
|
||||
@ -179,12 +217,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoCollapse;
|
||||
m_imgui->begin(on_get_name(), flag);
|
||||
|
||||
size_t triangle_count = m_volume->mesh().its.indices.size();
|
||||
// already reduced mesh
|
||||
if (m_original_its.has_value())
|
||||
triangle_count = m_original_its->indices.size();
|
||||
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_mesh_name + ":");
|
||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
||||
std::string name = m_volume->name;
|
||||
@ -193,21 +225,16 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
m_imgui->text(name);
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":");
|
||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
||||
m_imgui->text(std::to_string(triangle_count));
|
||||
/*
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_preview + ":");
|
||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
||||
if (m_exist_preview) {
|
||||
m_imgui->text(std::to_string(m_volume->mesh().its.indices.size()));
|
||||
} else {
|
||||
m_imgui->text("---");
|
||||
}*/
|
||||
|
||||
size_t orig_triangle_count = m_volume->mesh().its.indices.size();
|
||||
m_imgui->text(std::to_string(orig_triangle_count));
|
||||
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) {
|
||||
m_configuration.use_count = !m_configuration.use_count;
|
||||
live_preview();
|
||||
start_process = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
m_imgui->disabled_begin(m_configuration.use_count);
|
||||
@ -232,21 +259,21 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
case 3: m_configuration.max_error = 0.5f; break;
|
||||
case 4: m_configuration.max_error = 1.f; break;
|
||||
}
|
||||
live_preview();
|
||||
start_process = true;
|
||||
}
|
||||
m_imgui->disabled_end(); // !use_count
|
||||
|
||||
if (ImGui::RadioButton("##use_count", m_configuration.use_count)) {
|
||||
m_configuration.use_count = !m_configuration.use_count;
|
||||
live_preview();
|
||||
start_process = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
// show preview result triangle count (percent)
|
||||
if (m_need_reload && !m_configuration.use_count) {
|
||||
m_configuration.wanted_count = static_cast<uint32_t>(m_volume->mesh().its.indices.size());
|
||||
if (!m_configuration.use_count) {
|
||||
m_configuration.wanted_count = static_cast<uint32_t>(m_triangle_count);
|
||||
m_configuration.decimate_ratio =
|
||||
(1.0f - (m_configuration.wanted_count / (float) triangle_count)) * 100.f;
|
||||
(1.0f - (m_configuration.wanted_count / (float) orig_triangle_count)) * 100.f;
|
||||
}
|
||||
|
||||
m_imgui->disabled_begin(!m_configuration.use_count);
|
||||
@ -261,8 +288,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
m_configuration.decimate_ratio = 0.01f;
|
||||
if (m_configuration.decimate_ratio > 100.f)
|
||||
m_configuration.decimate_ratio = 100.f;
|
||||
m_configuration.fix_count_by_ratio(triangle_count);
|
||||
live_preview();
|
||||
m_configuration.fix_count_by_ratio(orig_triangle_count);
|
||||
start_process = true;
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
@ -270,79 +297,37 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
||||
ImGui::Text(_u8L("%d triangles").c_str(), m_configuration.wanted_count);
|
||||
m_imgui->disabled_end(); // use_count
|
||||
|
||||
if (ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe)) {
|
||||
if (m_show_wireframe) init_wireframe();
|
||||
else free_gpu();
|
||||
}
|
||||
ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe);
|
||||
|
||||
bool is_canceling = m_state == State::canceling;
|
||||
m_imgui->disabled_begin(is_canceling);
|
||||
if (m_imgui->button(_u8L("Cancel"))) {
|
||||
if (m_state == State::settings) {
|
||||
if (m_original_its.has_value()) {
|
||||
set_its(*m_original_its);
|
||||
m_state = State::close_on_end;
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
} else {
|
||||
m_state = State::canceling;
|
||||
}
|
||||
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_canceling)
|
||||
ImGui::SetTooltip("%s", _u8L("Operation already canceling. Please wait few seconds.").c_str());
|
||||
m_imgui->disabled_end(); // state canceling
|
||||
m_imgui->disabled_begin(is_cancelling);
|
||||
if (m_imgui->button(_L("Close"))) {
|
||||
close();
|
||||
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_cancelling)
|
||||
ImGui::SetTooltip("%s", _u8L("Operation already cancelling. Please wait few seconds.").c_str());
|
||||
m_imgui->disabled_end(); // state cancelling
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
bool is_processing = m_state != State::settings;
|
||||
m_imgui->disabled_begin(is_processing);
|
||||
if (m_imgui->button(_u8L("Apply"))) {
|
||||
if (!m_is_valid_result) {
|
||||
m_state = State::close_on_end;
|
||||
process();
|
||||
} else if (m_exist_preview) {
|
||||
// use preview and close
|
||||
after_apply();
|
||||
} else { // no changes made
|
||||
close();
|
||||
}
|
||||
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_processing)
|
||||
m_imgui->disabled_begin(is_worker_running || ! is_result_ready);
|
||||
if (m_imgui->button(_L("Apply"))) {
|
||||
apply_simplify();
|
||||
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_worker_running)
|
||||
ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str());
|
||||
m_imgui->disabled_end(); // state !settings
|
||||
|
||||
// draw progress bar
|
||||
if (is_processing) { // apply or preview
|
||||
if (is_worker_running) { // apply or preview
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
// draw progress bar
|
||||
char buf[32];
|
||||
sprintf(buf, L("Process %d / 100"), m_progress);
|
||||
ImGui::ProgressBar(m_progress / 100., ImVec2(m_gui_cfg->input_width, 0.f), buf);
|
||||
std::string progress_text = GUI::format(_L("Process %1% / 100"), std::to_string(progress));
|
||||
ImVec2 progress_size(m_gui_cfg->input_width, 0.f);
|
||||
ImGui::ProgressBar(progress / 100., progress_size, progress_text.c_str());
|
||||
}
|
||||
m_imgui->end();
|
||||
|
||||
// refresh view when needed
|
||||
if (m_need_reload) {
|
||||
m_need_reload = false;
|
||||
bool close_on_end = (m_state == State::close_on_end);
|
||||
// Reload visualization of mesh - change VBO, FBO on GPU
|
||||
m_parent.reload_scene(true);
|
||||
// set m_state must be before close() !!!
|
||||
m_state = State::settings;
|
||||
if (close_on_end) after_apply();
|
||||
else init_wireframe();
|
||||
// Fix warning icon in object list
|
||||
wxGetApp().obj_list()->update_item_error_icon(m_obj_index, -1);
|
||||
}
|
||||
if (start_process)
|
||||
process();
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::after_apply() {
|
||||
// set flag to NOT revert changes when switch GLGizmoBase::m_state
|
||||
m_exist_preview = false;
|
||||
// fix hollowing, sla support points, modifiers, ...
|
||||
auto plater = wxGetApp().plater();
|
||||
plater->changed_mesh(m_obj_index);
|
||||
close();
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::close() {
|
||||
// close gizmo == open it again
|
||||
@ -350,117 +335,158 @@ void GLGizmoSimplify::close() {
|
||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::live_preview() {
|
||||
m_is_valid_result = false;
|
||||
if (m_state != State::settings) {
|
||||
// already canceling process
|
||||
if (m_state == State::canceling) return;
|
||||
void GLGizmoSimplify::stop_worker_thread_request()
|
||||
{
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
if (m_state.status == State::running)
|
||||
m_state.status = State::Status::cancelling;
|
||||
}
|
||||
|
||||
// wait until cancel
|
||||
if (m_worker.joinable()) {
|
||||
m_state = State::canceling;
|
||||
m_dealy_process_cv.notify_one();
|
||||
m_worker.join();
|
||||
|
||||
// Following is called from a UI thread when the worker terminates
|
||||
// worker calls it through a CallAfter.
|
||||
void GLGizmoSimplify::worker_finished()
|
||||
{
|
||||
{
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
if (m_state.status == State::running) {
|
||||
// Someone started the worker again, before this callback
|
||||
// was called. Do nothing.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_state = State::preview;
|
||||
process();
|
||||
if (m_worker.joinable())
|
||||
m_worker.join();
|
||||
if (GLGizmoBase::m_state == Off)
|
||||
return;
|
||||
if (m_state.result)
|
||||
init_model(*m_state.result);
|
||||
if (m_state.config != m_configuration || m_state.mv != m_volume) {
|
||||
// Settings were changed, restart the worker immediately.
|
||||
process();
|
||||
}
|
||||
request_rerender(true);
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::process()
|
||||
{
|
||||
if (m_volume == nullptr) return;
|
||||
if (m_volume->mesh().its.indices.empty()) return;
|
||||
size_t count_triangles = m_volume->mesh().its.indices.size();
|
||||
// Is neccessary simplification
|
||||
if ((m_configuration.use_count && m_configuration.wanted_count >= count_triangles) ||
|
||||
(!m_configuration.use_count && m_configuration.max_error <= 0.f)) {
|
||||
if (m_volume == nullptr || m_volume->mesh().its.indices.empty())
|
||||
return;
|
||||
|
||||
// Exist different original volume?
|
||||
if (m_original_its.has_value() &&
|
||||
m_original_its->indices.size() != count_triangles) {
|
||||
indexed_triangle_set its = *m_original_its; // copy
|
||||
set_its(its);
|
||||
}
|
||||
m_is_valid_result = true;
|
||||
bool configs_match = false;
|
||||
bool result_valid = false;
|
||||
bool is_worker_running = false;
|
||||
{
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
configs_match = (m_volume == m_state.mv && m_state.config == m_configuration);
|
||||
result_valid = bool(m_state.result);
|
||||
is_worker_running = m_state.status == State::running;
|
||||
}
|
||||
|
||||
// re-render bargraph
|
||||
set_dirty();
|
||||
m_parent.schedule_extra_frame(0);
|
||||
if ((result_valid || is_worker_running) && configs_match) {
|
||||
// Either finished or waiting for result already. Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// when not store original volume store it for cancelation
|
||||
if (!m_original_its.has_value()) {
|
||||
m_original_its = m_volume->mesh().its; // copy
|
||||
|
||||
// store previous state
|
||||
auto plater = wxGetApp().plater();
|
||||
plater->take_snapshot(_u8L("Simplify ") + m_volume->name);
|
||||
plater->clear_before_change_mesh(m_obj_index);
|
||||
if (is_worker_running && ! configs_match) {
|
||||
// Worker is running with outdated config. Stop it. It will
|
||||
// restart itself when cancellation is done.
|
||||
stop_worker_thread_request();
|
||||
return;
|
||||
}
|
||||
|
||||
m_progress = 0;
|
||||
if (m_worker.joinable()) m_worker.join();
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
// Copy configuration that will be used.
|
||||
m_state.config = m_configuration;
|
||||
m_state.mv = m_volume;
|
||||
m_state.status = State::running;
|
||||
|
||||
// store original triangles
|
||||
uint32_t triangle_count = (m_configuration.use_count) ? m_configuration.wanted_count : 0;
|
||||
float max_error = (!m_configuration.use_count) ? m_configuration.max_error : std::numeric_limits<float>::max();
|
||||
// Create a copy of current mesh to pass to the worker thread.
|
||||
// Using unique_ptr instead of pass-by-value to avoid an extra
|
||||
// copy (which would happen when passing to std::thread).
|
||||
auto its = std::make_unique<indexed_triangle_set>(m_volume->mesh().its);
|
||||
|
||||
std::function<void(void)> throw_on_cancel = [&]() {
|
||||
if (m_state == State::canceling) {
|
||||
m_worker = std::thread([this](std::unique_ptr<indexed_triangle_set> its) {
|
||||
|
||||
// Checks that the UI thread did not request cancellation, throws if so.
|
||||
std::function<void(void)> throw_on_cancel = [this]() {
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
if (m_state.status == State::cancelling)
|
||||
throw SimplifyCanceledException();
|
||||
}
|
||||
};
|
||||
|
||||
int64_t last = 0;
|
||||
std::function<void(int)> statusfn = [this, &last](int percent) {
|
||||
m_progress = percent;
|
||||
|
||||
// check max 4fps
|
||||
int64_t now = m_parent.timestamp_now();
|
||||
if ((now - last) < 250) return;
|
||||
last = now;
|
||||
|
||||
request_rerender();
|
||||
// Called by worker thread, updates progress bar.
|
||||
// Using CallAfter so the rerequest function is run in UI thread.
|
||||
std::function<void(int)> statusfn = [this](int percent) {
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
m_state.progress = percent;
|
||||
call_after_if_active([this]() { request_rerender(); });
|
||||
};
|
||||
|
||||
indexed_triangle_set collapsed = *m_original_its; // copy
|
||||
|
||||
try {
|
||||
its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn);
|
||||
set_its(collapsed);
|
||||
m_is_valid_result = true;
|
||||
m_exist_preview = true;
|
||||
} catch (SimplifyCanceledException &) {
|
||||
// set state out of main thread
|
||||
m_state = State::settings;
|
||||
// Initialize.
|
||||
uint32_t triangle_count = 0;
|
||||
float max_error = std::numeric_limits<float>::max();
|
||||
{
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
if (m_state.config.use_count)
|
||||
triangle_count = m_state.config.wanted_count;
|
||||
if (! m_state.config.use_count)
|
||||
max_error = m_state.config.max_error;
|
||||
m_state.progress = 0;
|
||||
m_state.result.reset();
|
||||
m_state.status = State::Status::running;
|
||||
}
|
||||
// need to render last status fn to change bar graph to buttons
|
||||
request_rerender();
|
||||
});
|
||||
|
||||
// Start the actual calculation.
|
||||
try {
|
||||
its_quadric_edge_collapse(*its, triangle_count, &max_error, throw_on_cancel, statusfn);
|
||||
} catch (SimplifyCanceledException &) {
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
m_state.status = State::idle;
|
||||
}
|
||||
|
||||
std::lock_guard lk(m_state_mutex);
|
||||
if (m_state.status == State::Status::running) {
|
||||
// We were not cancelled, the result is valid.
|
||||
m_state.status = State::Status::idle;
|
||||
m_state.result = std::move(its);
|
||||
}
|
||||
|
||||
// Update UI. Use CallAfter so the function is run on UI thread.
|
||||
call_after_if_active([this]() { worker_finished(); });
|
||||
}, std::move(its));
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::set_its(indexed_triangle_set &its) {
|
||||
if (m_volume == nullptr) return; // could appear after process
|
||||
m_volume->set_mesh(its);
|
||||
m_volume->calculate_convex_hull();
|
||||
m_volume->set_new_unique_id();
|
||||
m_volume->get_object()->invalidate_bounding_box();
|
||||
m_need_reload = true;
|
||||
void GLGizmoSimplify::apply_simplify() {
|
||||
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
int object_idx = selection.get_object_idx();
|
||||
|
||||
auto plater = wxGetApp().plater();
|
||||
plater->take_snapshot(_u8L("Simplify ") + m_volume->name);
|
||||
plater->clear_before_change_mesh(object_idx);
|
||||
|
||||
ModelVolume* mv = get_model_volume(selection, wxGetApp().model());
|
||||
assert(mv == m_volume);
|
||||
|
||||
mv->set_mesh(std::move(*m_state.result));
|
||||
m_state.result.reset();
|
||||
mv->calculate_convex_hull();
|
||||
mv->set_new_unique_id();
|
||||
mv->get_object()->invalidate_bounding_box();
|
||||
mv->get_object()->ensure_on_bed(true); // allow negative z
|
||||
|
||||
// fix hollowing, sla support points, modifiers, ...
|
||||
plater->changed_mesh(object_idx);
|
||||
// Fix warning icon in object list
|
||||
wxGetApp().obj_list()->update_item_error_icon(object_idx, -1);
|
||||
close();
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::on_is_activable() const
|
||||
@ -472,39 +498,11 @@ void GLGizmoSimplify::on_set_state()
|
||||
{
|
||||
// Closing gizmo. e.g. selecting another one
|
||||
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
|
||||
// can appear when delete objects
|
||||
bool empty_selection = m_parent.get_selection().is_empty();
|
||||
m_parent.toggle_model_objects_visibility(true);
|
||||
|
||||
// cancel processing
|
||||
if (empty_selection &&
|
||||
m_state != State::settings &&
|
||||
m_state != State::canceling)
|
||||
m_state = State::canceling;
|
||||
|
||||
// refuse outgoing during simlification
|
||||
// object is not selected when it is deleted(cancel and close gizmo)
|
||||
if (m_state != State::settings && !empty_selection) {
|
||||
GLGizmoBase::m_state = GLGizmoBase::On;
|
||||
auto notification_manager = wxGetApp().plater()->get_notification_manager();
|
||||
notification_manager->push_notification(
|
||||
NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::PrintInfoNotificationLevel,
|
||||
_u8L("ERROR: Wait until Simplification ends or Cancel process."));
|
||||
return;
|
||||
}
|
||||
|
||||
// revert preview
|
||||
if (m_exist_preview) {
|
||||
m_exist_preview = false;
|
||||
if (exist_volume(m_volume)) {
|
||||
set_its(*m_original_its);
|
||||
m_parent.reload_scene(false);
|
||||
m_need_reload = false;
|
||||
}
|
||||
}
|
||||
|
||||
// invalidate selected model
|
||||
m_volume = nullptr;
|
||||
stop_worker_thread_request();
|
||||
m_volume = nullptr; // invalidate selected model
|
||||
m_glmodel.reset();
|
||||
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
|
||||
// when open by hyperlink it needs to show up
|
||||
request_rerender();
|
||||
@ -532,142 +530,95 @@ void GLGizmoSimplify::create_gui_cfg() {
|
||||
m_gui_cfg = cfg;
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::request_rerender() {
|
||||
wxGetApp().plater()->CallAfter([this]() {
|
||||
void GLGizmoSimplify::request_rerender(bool force) {
|
||||
int64_t now = m_parent.timestamp_now();
|
||||
if (force || now > m_last_rerender_timestamp + 250) { // 250 ms
|
||||
set_dirty();
|
||||
m_parent.schedule_extra_frame(0);
|
||||
});
|
||||
m_last_rerender_timestamp = now;
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::set_center_position() {
|
||||
m_move_to_center = true;
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::exist_volume(ModelVolume *volume) {
|
||||
auto objs = wxGetApp().plater()->model().objects;
|
||||
for (const auto &obj : objs) {
|
||||
const auto &vlms = obj->volumes;
|
||||
auto item = std::find(vlms.begin(), vlms.end(), volume);
|
||||
if (item != vlms.end()) return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
void GLGizmoSimplify::init_model(const indexed_triangle_set& its)
|
||||
{
|
||||
if (its.indices.empty())
|
||||
return;
|
||||
|
||||
m_glmodel.reset();
|
||||
m_glmodel.init_from(its);
|
||||
m_parent.toggle_model_objects_visibility(true); // selected volume may have changed
|
||||
m_parent.toggle_model_objects_visibility(false, m_c->selection_info()->model_object(),
|
||||
m_c->selection_info()->get_active_instance(), m_volume);
|
||||
|
||||
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 (idxs.empty()) return nullptr;
|
||||
// only one selected volume
|
||||
if (idxs.size() != 1) return nullptr;
|
||||
const GLVolume *selected_volume = selection.get_volume(*idxs.begin());
|
||||
if (selected_volume == nullptr) return nullptr;
|
||||
|
||||
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;
|
||||
if (! m_glmodel.is_initialized())
|
||||
return;
|
||||
|
||||
const auto& selection = m_parent.get_selection();
|
||||
const auto& volume_idxs = selection.get_volume_idxs();
|
||||
if (volume_idxs.empty() || volume_idxs.size() != 1) return;
|
||||
const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin());
|
||||
|
||||
// check that selected model is wireframe initialized
|
||||
if (m_volume != get_volume(selected_volume->composite_id, *m_parent.get_model()))
|
||||
// Check that the GLVolume still belongs to the ModelObject we work on.
|
||||
if (m_volume != get_model_volume(selection, wxGetApp().model()))
|
||||
return;
|
||||
|
||||
const Transform3d trafo_matrix = selected_volume->world_matrix();
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glMultMatrixd(trafo_matrix.data()));
|
||||
|
||||
auto *contour_shader = wxGetApp().get_shader("mm_contour");
|
||||
contour_shader->start_using();
|
||||
glsafe(::glDepthFunc(GL_LEQUAL));
|
||||
glsafe(::glLineWidth(1.0f));
|
||||
auto *gouraud_shader = wxGetApp().get_shader("gouraud_light");
|
||||
glsafe(::glPushAttrib(GL_DEPTH_TEST));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
gouraud_shader->start_using();
|
||||
m_glmodel.render();
|
||||
gouraud_shader->stop_using();
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_VBO_id));
|
||||
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
if (m_show_wireframe) {
|
||||
auto* contour_shader = wxGetApp().get_shader("mm_contour");
|
||||
contour_shader->start_using();
|
||||
glsafe(::glLineWidth(1.0f));
|
||||
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE));
|
||||
//ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); });
|
||||
//glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
||||
//glsafe(::glPolygonOffset(5.0, 5.0));
|
||||
m_glmodel.render();
|
||||
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL));
|
||||
contour_shader->stop_using();
|
||||
}
|
||||
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_wireframe_IBO_id));
|
||||
glsafe(::glDrawElements(GL_LINES, m_wireframe_IBO_size, GL_UNSIGNED_INT, nullptr));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
|
||||
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
glsafe(::glDepthFunc(GL_LESS));
|
||||
|
||||
glsafe(::glPopMatrix()); // pop trafo
|
||||
contour_shader->stop_using();
|
||||
glsafe(::glPopAttrib());
|
||||
glsafe(::glPopMatrix());
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::free_gpu()
|
||||
{
|
||||
if (m_wireframe_VBO_id != 0) {
|
||||
glsafe(::glDeleteBuffers(1, &m_wireframe_VBO_id));
|
||||
m_wireframe_VBO_id = 0;
|
||||
}
|
||||
|
||||
if (m_wireframe_IBO_id != 0) {
|
||||
glsafe(::glDeleteBuffers(1, &m_wireframe_IBO_id));
|
||||
m_wireframe_IBO_id = 0;
|
||||
}
|
||||
CommonGizmosDataID GLGizmoSimplify::on_get_requirements() const
|
||||
{
|
||||
return CommonGizmosDataID(
|
||||
int(CommonGizmosDataID::SelectionInfo));
|
||||
}
|
||||
|
||||
|
||||
void GLGizmoSimplify::Configuration::fix_count_by_ratio(size_t triangle_count)
|
||||
{
|
||||
if (decimate_ratio <= 0.f)
|
||||
wanted_count = static_cast<uint32_t>(triangle_count);
|
||||
else if (decimate_ratio >= 100.f)
|
||||
wanted_count = 0;
|
||||
else
|
||||
wanted_count = static_cast<uint32_t>(std::round(
|
||||
triangle_count * (100.f - decimate_ratio) / 100.f));
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
@ -4,27 +4,19 @@
|
||||
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
|
||||
// which overrides our localization "L" macro.
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "GLGizmoPainterBase.hpp" // for render wireframe
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "admesh/stl.h" // indexed_triangle_set
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <atomic>
|
||||
|
||||
#include <GL/glew.h> // GLUint
|
||||
|
||||
// for simplify suggestion
|
||||
class ModelObjectPtrs; // std::vector<ModelObject*>
|
||||
#include <thread>
|
||||
|
||||
namespace Slic3r {
|
||||
class ModelVolume;
|
||||
class Model;
|
||||
|
||||
namespace GUI {
|
||||
class NotificationManager; // for simplify suggestion
|
||||
|
||||
class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GLGizmoBase
|
||||
class GLGizmoSimplify: public GLGizmoBase
|
||||
{
|
||||
public:
|
||||
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
@ -32,8 +24,9 @@ public:
|
||||
bool on_esc_key_down();
|
||||
static void add_simplify_suggestion_notification(
|
||||
const std::vector<size_t> &object_ids,
|
||||
const ModelObjectPtrs & objects,
|
||||
const std::vector<ModelObject*> & objects,
|
||||
NotificationManager & manager);
|
||||
|
||||
protected:
|
||||
virtual std::string on_get_name() const override;
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
@ -43,76 +36,75 @@ protected:
|
||||
|
||||
// must implement
|
||||
virtual bool on_init() override { return true;};
|
||||
virtual void on_render() override{};
|
||||
virtual void on_render() override;
|
||||
virtual void on_render_for_picking() override{};
|
||||
|
||||
// GLGizmoPainterBase
|
||||
virtual void render_painter_gizmo() const override{ render_wireframe(); }
|
||||
virtual CommonGizmosDataID on_get_requirements() const;
|
||||
|
||||
private:
|
||||
void after_apply();
|
||||
void apply_simplify();
|
||||
void close();
|
||||
void live_preview();
|
||||
|
||||
void process();
|
||||
void set_its(indexed_triangle_set &its);
|
||||
void stop_worker_thread_request();
|
||||
void worker_finished();
|
||||
|
||||
void create_gui_cfg();
|
||||
void request_rerender();
|
||||
void request_rerender(bool force = false);
|
||||
void init_model(const indexed_triangle_set& its);
|
||||
|
||||
void set_center_position();
|
||||
// move to global functions
|
||||
static ModelVolume *get_volume(const Selection &selection, Model &model);
|
||||
static const ModelVolume *get_volume(const GLVolume::CompositeID &cid, const Model &model);
|
||||
|
||||
// return false when volume was deleted
|
||||
static bool exist_volume(ModelVolume *volume);
|
||||
|
||||
std::atomic_bool m_is_valid_result; // differ what to do in apply
|
||||
std::atomic_bool m_exist_preview; // set when process end
|
||||
|
||||
bool m_move_to_center; // opening gizmo
|
||||
|
||||
volatile int m_progress; // percent of done work
|
||||
ModelVolume *m_volume; // keep pointer to actual working volume
|
||||
size_t m_obj_index;
|
||||
|
||||
std::optional<indexed_triangle_set> m_original_its;
|
||||
bool m_show_wireframe;
|
||||
|
||||
volatile bool m_need_reload; // after simplify, glReload must be on main thread
|
||||
|
||||
std::thread m_worker;
|
||||
// wait before process
|
||||
std::mutex m_state_mutex;
|
||||
std::condition_variable m_dealy_process_cv;
|
||||
|
||||
enum class State {
|
||||
settings,
|
||||
preview, // simplify to show preview
|
||||
close_on_end, // simplify with close on end
|
||||
canceling // after button click, before canceled
|
||||
};
|
||||
volatile State m_state;
|
||||
|
||||
struct Configuration
|
||||
{
|
||||
bool use_count = false;
|
||||
// minimal triangle count
|
||||
float decimate_ratio = 50.f; // in percent
|
||||
uint32_t wanted_count = 0; // initialize by percents
|
||||
uint32_t wanted_count = 0; // initialize by percents
|
||||
float max_error = 1.; // maximal quadric error
|
||||
|
||||
// maximal quadric error
|
||||
float max_error = 1.;
|
||||
|
||||
void fix_count_by_ratio(size_t triangle_count)
|
||||
{
|
||||
if (decimate_ratio <= 0.f)
|
||||
wanted_count = static_cast<uint32_t>(triangle_count);
|
||||
else if (decimate_ratio >= 100.f)
|
||||
wanted_count = 0;
|
||||
else
|
||||
wanted_count = static_cast<uint32_t>(std::round(
|
||||
triangle_count * (100.f - decimate_ratio) / 100.f));
|
||||
void fix_count_by_ratio(size_t triangle_count);
|
||||
bool operator==(const Configuration& rhs) {
|
||||
return (use_count == rhs.use_count && decimate_ratio == rhs.decimate_ratio
|
||||
&& wanted_count == rhs.wanted_count && max_error == rhs.max_error);
|
||||
}
|
||||
} m_configuration;
|
||||
bool operator!=(const Configuration& rhs) {
|
||||
return ! (*this == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
Configuration m_configuration;
|
||||
|
||||
bool m_move_to_center; // opening gizmo
|
||||
|
||||
const ModelVolume *m_volume; // keep pointer to actual working volume
|
||||
|
||||
bool m_show_wireframe;
|
||||
GLModel m_glmodel;
|
||||
size_t m_triangle_count; // triangle count of the model currently shown
|
||||
|
||||
// Timestamp of the last rerender request. Only accessed from UI thread.
|
||||
int64_t m_last_rerender_timestamp = std::numeric_limits<int64_t>::min();
|
||||
|
||||
// Following struct is accessed by both UI and worker thread.
|
||||
// Accesses protected by a mutex.
|
||||
struct State {
|
||||
enum Status {
|
||||
idle,
|
||||
running,
|
||||
cancelling
|
||||
};
|
||||
|
||||
Status status = idle;
|
||||
int progress = 0; // percent of done work
|
||||
Configuration config; // Configuration we started with.
|
||||
const ModelVolume* mv = nullptr;
|
||||
std::unique_ptr<indexed_triangle_set> result;
|
||||
};
|
||||
|
||||
std::thread m_worker;
|
||||
std::mutex m_state_mutex; // guards m_state
|
||||
State m_state; // accessed by both threads
|
||||
|
||||
|
||||
// This configs holds GUI layout size given by translated texts.
|
||||
// etc. When language changes, GUI is recreated and this class constructed again,
|
||||
@ -138,17 +130,9 @@ private:
|
||||
// translations used for calc window size
|
||||
const std::string tr_mesh_name;
|
||||
const std::string tr_triangles;
|
||||
const std::string tr_preview;
|
||||
const std::string tr_detail_level;
|
||||
const std::string tr_decimate_ratio;
|
||||
|
||||
// rendering wireframe
|
||||
void render_wireframe() const;
|
||||
void init_wireframe();
|
||||
void free_gpu();
|
||||
GLuint m_wireframe_VBO_id, m_wireframe_IBO_id;
|
||||
size_t m_wireframe_IBO_size;
|
||||
|
||||
// cancel exception
|
||||
class SimplifyCanceledException: public std::exception
|
||||
{
|
||||
|
@ -486,7 +486,7 @@ void GLGizmosManager::render_painter_gizmo() const
|
||||
if (!m_enabled || m_current == Undefined)
|
||||
return;
|
||||
|
||||
auto *gizmo = dynamic_cast<GLGizmoTransparentRender*>(get_current());
|
||||
auto *gizmo = dynamic_cast<GLGizmoPainterBase*>(get_current());
|
||||
assert(gizmo); // check the precondition
|
||||
gizmo->render_painter_gizmo();
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
||||
|
||||
@ -486,7 +487,16 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float
|
||||
{
|
||||
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
||||
|
||||
bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power);
|
||||
std::string str_label;
|
||||
if (boost::algorithm::istarts_with(label, "##"))
|
||||
str_label = std::string(label);
|
||||
else {
|
||||
str_label = std::string("##") + std::string(label);
|
||||
this->text(label);
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
bool ret = ImGui::SliderFloat(str_label.c_str(), v, v_min, v_max, format, power);
|
||||
if (!tooltip.empty() && ImGui::IsItemHovered())
|
||||
this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width);
|
||||
|
||||
@ -497,10 +507,8 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 1, style.ItemSpacing.y });
|
||||
ImGui::SameLine();
|
||||
|
||||
std::wstring btn_name;
|
||||
btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(std::string(label));
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 1.0f });
|
||||
std::wstring btn_name = ImGui::SliderFloatEditBtnIcon + boost::nowide::widen(str_label);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f });
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.5f, 0.5f, 0.5f, 1.0f });
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.5f, 0.5f, 0.5f, 1.0f });
|
||||
if (ImGui::Button(into_u8(btn_name).c_str())) {
|
||||
|
@ -48,6 +48,9 @@ GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
|
||||
if (evt.GetInt() == status_range() || m_worker_error) {
|
||||
// set back the original range and cancel callback
|
||||
m_progress->set_range(m_range);
|
||||
// Make sure progress indicators get the last value of their range
|
||||
// to make sure they close, fade out, whathever
|
||||
m_progress->set_progress(m_range);
|
||||
m_progress->set_cancel_callback();
|
||||
wxEndBusyCursor();
|
||||
|
||||
|
@ -1194,9 +1194,9 @@ void MainFrame::init_menubar_as_editor()
|
||||
[this](wxCommandEvent&) { save_project(); }, "save", nullptr,
|
||||
[this](){return m_plater != nullptr && can_save(); }, this);
|
||||
#ifdef __APPLE__
|
||||
append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"),
|
||||
append_menu_item(fileMenu, wxID_ANY, _L("Save project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"),
|
||||
#else
|
||||
append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"),
|
||||
append_menu_item(fileMenu, wxID_ANY, _L("Save project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"),
|
||||
#endif // __APPLE__
|
||||
[this](wxCommandEvent&) { save_project_as(); }, "save", nullptr,
|
||||
[this](){return m_plater != nullptr && can_save_as(); }, this);
|
||||
|
@ -63,21 +63,26 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
|
||||
SetSizerAndFit(main_sizer);
|
||||
}
|
||||
|
||||
void MsgDialog::add_btn(wxWindowID btn_id, bool set_focus /*= false*/, const wxString& label/* = wxString()*/)
|
||||
wxButton* MsgDialog::add_button(wxWindowID btn_id, bool set_focus /*= false*/, const wxString& label/* = wxString()*/)
|
||||
{
|
||||
wxButton* btn = new wxButton(this, btn_id, label);
|
||||
if (set_focus)
|
||||
btn->SetFocus();
|
||||
btn_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, HORIZ_SPACING);
|
||||
btn->Bind(wxEVT_BUTTON, [this, btn_id](wxCommandEvent&) { this->EndModal(btn_id); });
|
||||
return btn;
|
||||
};
|
||||
|
||||
wxButton* MsgDialog::get_button(wxWindowID btn_id){
|
||||
return static_cast<wxButton*>(FindWindowById(btn_id, this));
|
||||
}
|
||||
|
||||
void MsgDialog::apply_style(long style)
|
||||
{
|
||||
if (style & wxOK) add_btn(wxID_OK, true);
|
||||
if (style & wxYES) add_btn(wxID_YES, true);
|
||||
if (style & wxNO) add_btn(wxID_NO);
|
||||
if (style & wxCANCEL) add_btn(wxID_CANCEL);
|
||||
if (style & wxOK) add_button(wxID_OK, true);
|
||||
if (style & wxYES) add_button(wxID_YES, true);
|
||||
if (style & wxNO) add_button(wxID_NO);
|
||||
if (style & wxCANCEL) add_button(wxID_CANCEL);
|
||||
|
||||
logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" :
|
||||
style & wxICON_INFORMATION ? "info" :
|
||||
|
@ -42,8 +42,10 @@ protected:
|
||||
};
|
||||
|
||||
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, long style = wxOK, wxBitmap bitmap = wxNullBitmap);
|
||||
|
||||
void add_btn(wxWindowID btn_id, bool set_focus = false, const wxString& label = wxString());
|
||||
// returns pointer to created button
|
||||
wxButton* add_button(wxWindowID btn_id, bool set_focus = false, const wxString& label = wxString());
|
||||
// returns pointer to found button or NULL
|
||||
wxButton* get_button(wxWindowID btn_id);
|
||||
void apply_style(long style);
|
||||
void finalize();
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
@ -1068,14 +1069,12 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
|
||||
for (it = m_types_and_counts.begin(); it != m_types_and_counts.end(); ++it) {
|
||||
if ((*it).second == 0)
|
||||
continue;
|
||||
text += std::to_string((*it).second);
|
||||
text += _L_PLURAL(" Object was loaded with "," Objects were loaded with ", (*it).second).ToUTF8().data();
|
||||
switch ((*it).first) {
|
||||
case InfoItemType::CustomSupports: text += _utf8("custom supports.\n"); break;
|
||||
case InfoItemType::CustomSeam: text += _utf8("custom seam.\n"); break;
|
||||
case InfoItemType::MmuSegmentation: text += _utf8("multimaterial painting.\n"); break;
|
||||
case InfoItemType::VariableLayerHeight: text += _utf8("variable layer height.\n"); break;
|
||||
case InfoItemType::Sinking: text += _utf8("Partial sinking.\n"); break;
|
||||
case InfoItemType::CustomSupports: text += format(_L_PLURAL("%1$d Object was loaded with custom supports.", "%1$d Objects were loaded with custom supports.", (*it).second), (*it).second) + "\n"; break;
|
||||
case InfoItemType::CustomSeam: text += format(_L_PLURAL("%1$d Object was loaded with custom seam.", "%1$d Objects were loaded with custom seam.", (*it).second), (*it).second) + "\n"; break;
|
||||
case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d Object was loaded with multimaterial painting.", "%1$d Objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break;
|
||||
case InfoItemType::VariableLayerHeight: text += format(_L_PLURAL("%1$d Object was loaded with variable layer height.", "%1$d Objects were loaded with variable layer height.", (*it).second), (*it).second) + "\n"; break;
|
||||
case InfoItemType::Sinking: text += format(_L_PLURAL("%1$d Object was loaded with partial sinking.", "%1$d Objects were loaded with partial sinking.", (*it).second), (*it).second) + "\n"; break;
|
||||
default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break;
|
||||
}
|
||||
}
|
||||
|
@ -772,65 +772,30 @@ bool OG_CustomCtrl::CtrlLine::launch_browser() const
|
||||
bool launch = true;
|
||||
|
||||
if (get_app_config()->get("suppress_hyperlinks").empty()) {
|
||||
RememberChoiceDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"));
|
||||
RichMessageDialog dialog(nullptr, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxYES_NO);
|
||||
dialog.ShowCheckBox(_L("Remember my choice"));
|
||||
int answer = dialog.ShowModal();
|
||||
launch = answer == wxID_YES;
|
||||
|
||||
get_app_config()->set("suppress_hyperlinks", dialog.remember_choice() ? (answer == wxID_NO ? "1" : "0") : "");
|
||||
}
|
||||
if (launch)
|
||||
launch = get_app_config()->get("suppress_hyperlinks") != "1";
|
||||
|
||||
return launch && wxLaunchDefaultBrowser(get_url(og_line.label_path));
|
||||
}
|
||||
|
||||
|
||||
RememberChoiceDialog::RememberChoiceDialog(wxWindow* parent, const wxString& msg_text, const wxString& caption)
|
||||
: wxDialog(parent, wxID_ANY, caption, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxICON_INFORMATION)
|
||||
{
|
||||
this->SetEscapeId(wxID_CLOSE);
|
||||
|
||||
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_remember_choice = new wxCheckBox(this, wxID_ANY, _L("Remember my choice"));
|
||||
m_remember_choice->SetValue(false);
|
||||
m_remember_choice->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& evt)
|
||||
{
|
||||
if (!evt.IsChecked())
|
||||
return;
|
||||
if (dialog.IsCheckBoxChecked()) {
|
||||
wxString preferences_item = _L("Suppress to open hyperlink in browser");
|
||||
wxString msg =
|
||||
_L("PrusaSlicer will remember your choice.") + "\n\n" +
|
||||
_L("You will not be asked about it again on label hovering.") + "\n\n" +
|
||||
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
|
||||
|
||||
//wxMessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
|
||||
MessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
|
||||
if (dialog.ShowModal() == wxID_CANCEL)
|
||||
m_remember_choice->SetValue(false);
|
||||
});
|
||||
MessageDialog msg_dlg(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
|
||||
if (msg_dlg.ShowModal() == wxID_CANCEL)
|
||||
return false;
|
||||
|
||||
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
|
||||
}
|
||||
|
||||
// Add dialog's buttons
|
||||
wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxYES | wxNO);
|
||||
wxButton* btnYES = static_cast<wxButton*>(this->FindWindowById(wxID_YES, this));
|
||||
wxButton* btnNO = static_cast<wxButton*>(this->FindWindowById(wxID_NO, this));
|
||||
btnYES->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { this->EndModal(wxID_YES); });
|
||||
btnNO->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { this->EndModal(wxID_NO); });
|
||||
launch = answer == wxID_YES;
|
||||
}
|
||||
if (launch)
|
||||
launch = get_app_config()->get("suppress_hyperlinks") != "1";
|
||||
|
||||
topSizer->Add(new wxStaticText(this, wxID_ANY, msg_text), 0, wxEXPAND | wxALL, 10);
|
||||
topSizer->Add(m_remember_choice, 0, wxEXPAND | wxALL, 10);
|
||||
topSizer->Add(btns, 0, wxEXPAND | wxALL, 10);
|
||||
|
||||
#ifdef _WIN32
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
#else
|
||||
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif
|
||||
this->SetSizer(topSizer);
|
||||
topSizer->SetSizeHints(this);
|
||||
|
||||
this->CenterOnScreen();
|
||||
return launch && wxLaunchDefaultBrowser(get_url(og_line.label_path));
|
||||
}
|
||||
|
||||
} // GUI
|
||||
|
@ -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_ */
|
||||
|
@ -460,6 +460,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) :
|
||||
std::vector<float> extruders = dlg.get_extruders();
|
||||
(project_config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values = std::vector<double>(matrix.begin(), matrix.end());
|
||||
(project_config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(extruders.begin(), extruders.end());
|
||||
wxGetApp().plater()->update_project_dirty_from_presets();
|
||||
wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent));
|
||||
}
|
||||
}));
|
||||
@ -2424,8 +2425,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
|
||||
// show notification about temporarily installed presets
|
||||
if (!names.empty()) {
|
||||
std::string notif_text = into_u8(_L_PLURAL("The preset below was temporarily installed on active instance of PrusaSlicer",
|
||||
"The presets below were temporarily installed on active instance of PrusaSlicer", names.size())) + ":";
|
||||
std::string notif_text = into_u8(_L_PLURAL("The preset below was temporarily installed on the active instance of PrusaSlicer",
|
||||
"The presets below were temporarily installed on the active instance of PrusaSlicer", names.size())) + ":";
|
||||
for (std::string& name : names)
|
||||
notif_text += "\n - " + name;
|
||||
notification_manager->push_notification(NotificationType::CustomNotification,
|
||||
@ -2476,9 +2477,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
MessageDialog(q, format_wxstr(_L_PLURAL(
|
||||
"Object size from file %s appears to be zero.\n"
|
||||
"This object has been removed from the model",
|
||||
"Objects size from file %s appear to be zero.\n"
|
||||
"Objects size from file %s appears to be zero.\n"
|
||||
"These objects have been removed from the model", deleted_objects), from_path(filename)) + "\n",
|
||||
_L("Object size is zero"), wxICON_INFORMATION | wxOK).ShowModal();
|
||||
_L("The size of the object is zero"), wxICON_INFORMATION | wxOK).ShowModal();
|
||||
}
|
||||
if (imperial_units)
|
||||
// Convert even if the object is big.
|
||||
@ -2492,9 +2493,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
if (answer_convert_from_meters == wxOK_DEFAULT) {
|
||||
RichMessageDialog dlg(q, format_wxstr(_L_PLURAL(
|
||||
"The dimensions of the object from file %s seem to be defined in meters.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
|
||||
"The internal unit of PrusaSlicer is a millimeter. Do you want to recalculate the dimensions of the object?",
|
||||
"The dimensions of some objects from file %s seem to be defined in meters.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
|
||||
"The internal unit of PrusaSlicer is a millimeter. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
|
||||
_L("The object is too small"), wxICON_QUESTION | wxYES_NO);
|
||||
dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded."));
|
||||
int answer = dlg.ShowModal();
|
||||
@ -2514,9 +2515,9 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
if (answer_convert_from_imperial_units == wxOK_DEFAULT) {
|
||||
RichMessageDialog dlg(q, format_wxstr(_L_PLURAL(
|
||||
"The dimensions of the object from file %s seem to be defined in inches.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
|
||||
"The internal unit of PrusaSlicer is a millimeter. Do you want to recalculate the dimensions of the object?",
|
||||
"The dimensions of some objects from file %s seem to be defined in inches.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
|
||||
"The internal unit of PrusaSlicer is a millimeter. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
|
||||
_L("The object is too small"), wxICON_QUESTION | wxYES_NO);
|
||||
dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded."));
|
||||
int answer = dlg.ShowModal();
|
||||
@ -2533,7 +2534,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
MessageDialog msg_dlg(q, _L(
|
||||
"This file contains several objects positioned at multiple heights.\n"
|
||||
"Instead of considering them as multiple objects, should \n"
|
||||
"should the file be loaded as a single object having multiple parts?") + "\n",
|
||||
"the file be loaded as a single object having multiple parts?") + "\n",
|
||||
_L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES) {
|
||||
model.convert_multipart_object(nozzle_dmrs->values.size());
|
||||
@ -3438,7 +3439,7 @@ void Plater::priv::replace_with_stl()
|
||||
if (!volume->source.input_file.empty() && fs::exists(volume->source.input_file))
|
||||
input_path = volume->source.input_file;
|
||||
|
||||
wxString title = _L("Please select the file to replace");
|
||||
wxString title = _L("Select the new file");
|
||||
title += ":";
|
||||
wxFileDialog dialog(q, title, "", from_u8(input_path.filename().string()), file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog.ShowModal() != wxID_OK)
|
||||
@ -5062,7 +5063,7 @@ void Plater::new_project()
|
||||
int act_buttons = ab::KEEP;
|
||||
if (saved_project == wxID_NO)
|
||||
act_buttons |= ab::SAVE;
|
||||
if (!wxGetApp().check_and_keep_current_preset_changes(_L("New Project is creating"), header, act_buttons))
|
||||
if (!wxGetApp().check_and_keep_current_preset_changes(_L("Creating a new project"), header, act_buttons))
|
||||
return;
|
||||
}
|
||||
|
||||
@ -5889,7 +5890,7 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path)
|
||||
{
|
||||
#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
|
||||
if (p->model.objects.empty()) {
|
||||
MessageDialog dialog(nullptr, _L("The plater is empty.\nConfirm you want to save the project ?"), _L("Save project"), wxYES_NO);
|
||||
MessageDialog dialog(nullptr, _L("The plater is empty.\nDo you want to save the project?"), _L("Save project"), wxYES_NO);
|
||||
if (dialog.ShowModal() != wxID_YES)
|
||||
return false;
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ void PreferencesDialog::build(size_t selected_tab)
|
||||
|
||||
m_optgroup_general->append_separator();
|
||||
|
||||
def.label = L("Ask for unsaved changes when closing application or loading new project");
|
||||
def.label = L("Ask to save unsaved changes when closing the application or when loading a new project");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("Always ask for unsaved changes, when: \n"
|
||||
"- Closing PrusaSlicer while some presets are modified,\n"
|
||||
@ -468,7 +468,7 @@ void PreferencesDialog::build(size_t selected_tab)
|
||||
// Add "Dark Mode" tab
|
||||
if (is_editor) {
|
||||
// Add "Dark Mode" tab
|
||||
m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs);
|
||||
m_optgroup_dark_mode = create_options_tab(_L("Dark mode IU (experimental)"), tabs);
|
||||
m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
|
||||
};
|
||||
@ -542,8 +542,8 @@ void PreferencesDialog::accept(wxEvent&)
|
||||
wxString title = wxGetApp().is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME);
|
||||
title += " - " + _L("Changes for the critical options");
|
||||
MessageDialog dialog(nullptr,
|
||||
_L("Changing fo some options will trigger application restart.\n"
|
||||
"You will lose content of the plater.") + "\n\n" +
|
||||
_L("Changing some options will trigger application restart.\n"
|
||||
"You will lose the content of the plater.") + "\n\n" +
|
||||
_L("Do you want to proceed?"),
|
||||
title,
|
||||
wxICON_QUESTION | wxYES | wxNO);
|
||||
@ -762,7 +762,7 @@ void PreferencesDialog::create_settings_text_color_widget()
|
||||
{
|
||||
wxWindow* parent = m_optgroup_gui->parent();
|
||||
|
||||
wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _L("Text color Settings"));
|
||||
wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _L("Text colors"));
|
||||
wxGetApp().UpdateDarkUI(stb);
|
||||
if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
|
@ -573,31 +573,7 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
|
||||
}
|
||||
|
||||
// Swallow the mouse click and open the color picker.
|
||||
|
||||
// get current color
|
||||
DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config();
|
||||
auto colors = static_cast<ConfigOptionStrings*>(cfg->option("extruder_colour")->clone());
|
||||
wxColour clr(colors->values[m_extruder_idx]);
|
||||
if (!clr.IsOk())
|
||||
clr = wxColour(0,0,0); // Don't set alfa to transparence
|
||||
|
||||
auto data = new wxColourData();
|
||||
data->SetChooseFull(1);
|
||||
data->SetColour(clr);
|
||||
|
||||
wxColourDialog dialog(this, data);
|
||||
dialog.CenterOnParent();
|
||||
if (dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
colors->values[m_extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
|
||||
|
||||
DynamicPrintConfig cfg_new = *cfg;
|
||||
cfg_new.set_key_value("extruder_colour", colors);
|
||||
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new);
|
||||
this->update();
|
||||
wxGetApp().plater()->on_config_change(cfg_new);
|
||||
}
|
||||
change_extruder_color();
|
||||
});
|
||||
}
|
||||
|
||||
@ -607,28 +583,15 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
|
||||
edit_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent)
|
||||
{
|
||||
// In a case of a physical printer, for its editing open PhysicalPrinterDialog
|
||||
if (m_type == Preset::TYPE_PRINTER/* && this->is_selected_physical_printer()*/) {
|
||||
this->show_edit_menu();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!switch_to_tab())
|
||||
return;
|
||||
|
||||
/* In a case of a multi-material printing, for editing another Filament Preset
|
||||
* it's needed to select this preset for the "Filament settings" Tab
|
||||
*/
|
||||
if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
|
||||
{
|
||||
const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data();
|
||||
|
||||
// Call select_preset() only if there is new preset and not just modified
|
||||
if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) )
|
||||
{
|
||||
const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
|
||||
wxGetApp().get_tab(m_type)->select_preset(preset_name);
|
||||
}
|
||||
}
|
||||
if (m_type == Preset::TYPE_PRINTER
|
||||
#ifdef __linux__
|
||||
// To edit extruder color from the sidebar
|
||||
|| m_type == Preset::TYPE_FILAMENT
|
||||
#endif //__linux__
|
||||
)
|
||||
show_edit_menu();
|
||||
else
|
||||
switch_to_tab();
|
||||
});
|
||||
}
|
||||
|
||||
@ -672,20 +635,59 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt)
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
bool PlaterPresetComboBox::switch_to_tab()
|
||||
void PlaterPresetComboBox::switch_to_tab()
|
||||
{
|
||||
Tab* tab = wxGetApp().get_tab(m_type);
|
||||
if (!tab)
|
||||
return false;
|
||||
return;
|
||||
|
||||
int page_id = wxGetApp().tab_panel()->FindPage(tab);
|
||||
if (page_id == wxNOT_FOUND)
|
||||
return false;
|
||||
if (int page_id = wxGetApp().tab_panel()->FindPage(tab); page_id != wxNOT_FOUND)
|
||||
{
|
||||
wxGetApp().tab_panel()->SetSelection(page_id);
|
||||
// Switch to Settings NotePad
|
||||
wxGetApp().mainframe->select_tab();
|
||||
|
||||
wxGetApp().tab_panel()->SetSelection(page_id);
|
||||
// Switch to Settings NotePad
|
||||
wxGetApp().mainframe->select_tab();
|
||||
return true;
|
||||
//In a case of a multi-material printing, for editing another Filament Preset
|
||||
//it's needed to select this preset for the "Filament settings" Tab
|
||||
if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
|
||||
{
|
||||
const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data();
|
||||
// Call select_preset() only if there is new preset and not just modified
|
||||
if (!boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()))
|
||||
{
|
||||
const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
|
||||
wxGetApp().get_tab(m_type)->select_preset(preset_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlaterPresetComboBox::change_extruder_color()
|
||||
{
|
||||
// get current color
|
||||
DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config();
|
||||
auto colors = static_cast<ConfigOptionStrings*>(cfg->option("extruder_colour")->clone());
|
||||
wxColour clr(colors->values[m_extruder_idx]);
|
||||
if (!clr.IsOk())
|
||||
clr = wxColour(0, 0, 0); // Don't set alfa to transparence
|
||||
|
||||
auto data = new wxColourData();
|
||||
data->SetChooseFull(1);
|
||||
data->SetColour(clr);
|
||||
|
||||
wxColourDialog dialog(this, data);
|
||||
dialog.CenterOnParent();
|
||||
if (dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
colors->values[m_extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
|
||||
|
||||
DynamicPrintConfig cfg_new = *cfg;
|
||||
cfg_new.set_key_value("extruder_colour", colors);
|
||||
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new);
|
||||
this->update();
|
||||
wxGetApp().plater()->on_config_change(cfg_new);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaterPresetComboBox::show_add_menu()
|
||||
@ -714,6 +716,16 @@ void PlaterPresetComboBox::show_edit_menu()
|
||||
append_menu_item(menu, wxID_ANY, _L("Edit preset"), "",
|
||||
[this](wxCommandEvent&) { this->switch_to_tab(); }, "cog", menu, []() { return true; }, wxGetApp().plater());
|
||||
|
||||
#ifdef __linux__
|
||||
// To edit extruder color from the sidebar
|
||||
if (m_type == Preset::TYPE_FILAMENT) {
|
||||
append_menu_item(menu, wxID_ANY, _L("Change extruder color"), "",
|
||||
[this](wxCommandEvent&) { this->change_extruder_color(); }, "funnel", menu, []() { return true; }, wxGetApp().plater());
|
||||
wxGetApp().plater()->PopupMenu(menu);
|
||||
return;
|
||||
}
|
||||
#endif //__linux__
|
||||
|
||||
if (this->is_selected_physical_printer()) {
|
||||
append_menu_item(menu, wxID_ANY, _L("Edit physical printer"), "",
|
||||
[this](wxCommandEvent&) { this->edit_physical_printer(); }, "cog", menu, []() { return true; }, wxGetApp().plater());
|
||||
|
@ -147,7 +147,8 @@ public:
|
||||
void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; }
|
||||
int get_extruder_idx() const { return m_extruder_idx; }
|
||||
|
||||
bool switch_to_tab();
|
||||
void switch_to_tab();
|
||||
void change_extruder_color();
|
||||
void show_add_menu();
|
||||
void show_edit_menu();
|
||||
|
||||
|
@ -39,9 +39,8 @@ static const char *CONFIG_KEY_PRINT = "printhost_print";
|
||||
static const char *CONFIG_KEY_GROUP = "printhost_group";
|
||||
|
||||
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print, const wxArrayString &groups)
|
||||
: MsgDialog(static_cast<wxWindow*>(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), wxOK | wxCANCEL)
|
||||
: MsgDialog(static_cast<wxWindow*>(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"))
|
||||
, txt_filename(new wxTextCtrl(this, wxID_ANY))
|
||||
, box_print(can_start_print ? new wxCheckBox(this, wxID_ANY, _L("Start printing after upload")) : nullptr)
|
||||
, combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
@ -55,10 +54,6 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
|
||||
content_sizer->Add(txt_filename, 0, wxEXPAND);
|
||||
content_sizer->Add(label_dir_hint);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
if (box_print != nullptr) {
|
||||
content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING);
|
||||
box_print->SetValue(app_config->get("recent", CONFIG_KEY_PRINT) == "1");
|
||||
}
|
||||
|
||||
if (combo_groups != nullptr) {
|
||||
// Repetier specific: Show a selection of file groups.
|
||||
@ -84,18 +79,37 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_pr
|
||||
|
||||
wxString suffix = recent_path.substr(recent_path.find_last_of('.'));
|
||||
|
||||
static_cast<wxButton*>(FindWindowById(wxID_OK, this))->Bind(wxEVT_BUTTON, [this, suffix](wxCommandEvent&) {
|
||||
wxString path = txt_filename->GetValue();
|
||||
// .gcode suffix control
|
||||
if (!path.Lower().EndsWith(suffix.Lower()))
|
||||
{
|
||||
MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO);
|
||||
if (msg_wingow.ShowModal() == wxID_NO)
|
||||
return;
|
||||
}
|
||||
EndDialog(wxID_OK);
|
||||
});
|
||||
if (can_start_print) {
|
||||
auto* btn_print = add_button(wxID_YES, false, _L("Upload and Print"));
|
||||
btn_print->Bind(wxEVT_BUTTON, [this, suffix](wxCommandEvent&) {
|
||||
wxString path = txt_filename->GetValue();
|
||||
// .gcode suffix control
|
||||
if (!path.Lower().EndsWith(suffix.Lower()))
|
||||
{
|
||||
MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO);
|
||||
if (msg_wingow.ShowModal() == wxID_NO)
|
||||
return;
|
||||
}
|
||||
start_print_selected = true;
|
||||
EndDialog(wxID_OK);
|
||||
});
|
||||
}
|
||||
add_button(wxID_CANCEL);
|
||||
|
||||
if (auto* btn_ok = get_button(wxID_NO); btn_ok != NULL) {
|
||||
btn_ok->SetLabel(_L("Upload"));
|
||||
btn_ok->Bind(wxEVT_BUTTON, [this, suffix](wxCommandEvent&) {
|
||||
wxString path = txt_filename->GetValue();
|
||||
// .gcode suffix control
|
||||
if (!path.Lower().EndsWith(suffix.Lower()))
|
||||
{
|
||||
MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO);
|
||||
if (msg_wingow.ShowModal() == wxID_NO)
|
||||
return;
|
||||
}
|
||||
EndDialog(wxID_OK);
|
||||
});
|
||||
}
|
||||
finalize();
|
||||
|
||||
#ifdef __linux__
|
||||
@ -125,7 +139,7 @@ fs::path PrintHostSendDialog::filename() const
|
||||
|
||||
bool PrintHostSendDialog::start_print() const
|
||||
{
|
||||
return box_print != nullptr ? box_print->GetValue() : false;
|
||||
return start_print_selected;
|
||||
}
|
||||
|
||||
std::string PrintHostSendDialog::group() const
|
||||
|
@ -36,8 +36,8 @@ public:
|
||||
virtual void EndModal(int ret) override;
|
||||
private:
|
||||
wxTextCtrl *txt_filename;
|
||||
wxCheckBox *box_print;
|
||||
wxComboBox *combo_groups;
|
||||
bool start_print_selected { false };
|
||||
};
|
||||
|
||||
|
||||
|
@ -26,12 +26,14 @@ void ProjectDirtyStateManager::update_from_presets()
|
||||
{
|
||||
m_presets_dirty = false;
|
||||
// check switching of the presets only for exist/loaded project, but not for new
|
||||
if (!wxGetApp().plater()->get_project_filename().IsEmpty()) {
|
||||
for (const auto& [type, name] : wxGetApp().get_selected_presets())
|
||||
GUI_App &app = wxGetApp();
|
||||
if (!app.plater()->get_project_filename().IsEmpty()) {
|
||||
for (const auto& [type, name] : app.get_selected_presets())
|
||||
m_presets_dirty |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
|
||||
}
|
||||
m_presets_dirty |= wxGetApp().has_unsaved_preset_changes();
|
||||
wxGetApp().mainframe->update_title();
|
||||
m_presets_dirty |= app.has_unsaved_preset_changes();
|
||||
m_project_config_dirty = m_initial_project_config != app.preset_bundle->project_config;
|
||||
app.mainframe->update_title();
|
||||
}
|
||||
|
||||
void ProjectDirtyStateManager::reset_after_save()
|
||||
@ -39,14 +41,17 @@ void ProjectDirtyStateManager::reset_after_save()
|
||||
this->reset_initial_presets();
|
||||
m_plater_dirty = false;
|
||||
m_presets_dirty = false;
|
||||
m_project_config_dirty = false;
|
||||
wxGetApp().mainframe->update_title();
|
||||
}
|
||||
|
||||
void ProjectDirtyStateManager::reset_initial_presets()
|
||||
{
|
||||
m_initial_presets.fill(std::string{});
|
||||
for (const auto& [type, name] : wxGetApp().get_selected_presets())
|
||||
GUI_App &app = wxGetApp();
|
||||
for (const auto& [type, name] : app.get_selected_presets())
|
||||
m_initial_presets[type] = name;
|
||||
m_initial_project_config = app.preset_bundle->project_config;
|
||||
}
|
||||
|
||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
|
||||
|
@ -14,7 +14,7 @@ public:
|
||||
void reset_after_save();
|
||||
void reset_initial_presets();
|
||||
|
||||
bool is_dirty() const { return m_plater_dirty || m_presets_dirty; }
|
||||
bool is_dirty() const { return m_plater_dirty || m_project_config_dirty || m_presets_dirty; }
|
||||
|
||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
|
||||
void render_debug_window() const;
|
||||
@ -25,8 +25,11 @@ private:
|
||||
bool m_plater_dirty { false };
|
||||
// Do the presets indicate the project is dirty?
|
||||
bool m_presets_dirty { false };
|
||||
// Is the project config dirty?
|
||||
bool m_project_config_dirty { false };
|
||||
// Keeps track of preset names selected at the time of last project save.
|
||||
std::array<std::string, Preset::TYPE_COUNT> m_initial_presets;
|
||||
DynamicPrintConfig m_initial_project_config;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
@ -1478,14 +1478,13 @@ void TabPrint::build()
|
||||
optgroup = page->new_optgroup(L("Advanced"));
|
||||
optgroup->append_single_option_line("seam_position", category_path + "seam-position");
|
||||
optgroup->append_single_option_line("external_perimeters_first", category_path + "external-perimeters-first");
|
||||
optgroup->append_single_option_line("gap_fill_enabled");
|
||||
optgroup->append_single_option_line("gap_fill_enabled", category_path + "fill-gaps");
|
||||
|
||||
optgroup = page->new_optgroup(L("Fuzzy skin (experimental)"));
|
||||
Option option = optgroup->get_option("fuzzy_skin");
|
||||
// option.opt.width = 30;
|
||||
optgroup->append_single_option_line(option);
|
||||
optgroup->append_single_option_line(optgroup->get_option("fuzzy_skin_thickness"));
|
||||
optgroup->append_single_option_line(optgroup->get_option("fuzzy_skin_point_dist"));
|
||||
category_path = "fuzzy-skin_246186/#";
|
||||
optgroup->append_single_option_line("fuzzy_skin", category_path + "fuzzy-skin-type");
|
||||
optgroup->append_single_option_line("fuzzy_skin_thickness", category_path + "fuzzy-skin-thickness");
|
||||
optgroup->append_single_option_line("fuzzy_skin_point_dist", category_path + "fuzzy-skin-point-distance");
|
||||
|
||||
page = add_options_page(L("Infill"), "infill");
|
||||
category_path = "infill_42#";
|
||||
@ -1498,12 +1497,14 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("bottom_fill_pattern", category_path + "bottom-fill-pattern");
|
||||
|
||||
optgroup = page->new_optgroup(L("Ironing"));
|
||||
optgroup->append_single_option_line("ironing");
|
||||
optgroup->append_single_option_line("ironing_type");
|
||||
optgroup->append_single_option_line("ironing_flowrate");
|
||||
optgroup->append_single_option_line("ironing_spacing");
|
||||
category_path = "ironing_177488#";
|
||||
optgroup->append_single_option_line("ironing", category_path);
|
||||
optgroup->append_single_option_line("ironing_type", category_path + "ironing-type");
|
||||
optgroup->append_single_option_line("ironing_flowrate", category_path + "flow-rate");
|
||||
optgroup->append_single_option_line("ironing_spacing", category_path + "spacing-between-ironing-passes");
|
||||
|
||||
optgroup = page->new_optgroup(L("Reducing printing time"));
|
||||
category_path = "infill_42#";
|
||||
optgroup->append_single_option_line("infill_every_layers", category_path + "combine-infill-every-x-layers");
|
||||
optgroup->append_single_option_line("infill_only_where_needed", category_path + "only-infill-where-needed");
|
||||
|
||||
@ -1541,7 +1542,7 @@ void TabPrint::build()
|
||||
|
||||
optgroup = page->new_optgroup(L("Raft"));
|
||||
optgroup->append_single_option_line("raft_layers", category_path + "raft-layers");
|
||||
optgroup->append_single_option_line("raft_contact_distance");
|
||||
optgroup->append_single_option_line("raft_contact_distance", category_path + "raft-layers");
|
||||
optgroup->append_single_option_line("raft_expansion");
|
||||
|
||||
optgroup = page->new_optgroup(L("Options for support material and raft"));
|
||||
@ -1666,7 +1667,7 @@ void TabPrint::build()
|
||||
optgroup = page->new_optgroup(L("Output file"));
|
||||
optgroup->append_single_option_line("gcode_comments");
|
||||
optgroup->append_single_option_line("gcode_label_objects");
|
||||
option = optgroup->get_option("output_filename_format");
|
||||
Option option = optgroup->get_option("output_filename_format");
|
||||
option.opt.full_width = true;
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
@ -3248,7 +3249,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
|
||||
const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset);
|
||||
PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology();
|
||||
PrinterTechnology new_printer_technology = new_printer_preset.printer_technology();
|
||||
if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset is selecting")))
|
||||
if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset selected")))
|
||||
canceled = true;
|
||||
else {
|
||||
struct PresetUpdate {
|
||||
@ -3651,8 +3652,8 @@ void Tab::delete_preset()
|
||||
for (const std::string& printer : ph_printers)
|
||||
msg += "\n \"" + from_u8(printer) + "\",";
|
||||
msg.RemoveLast();
|
||||
msg += "\n" + _L_PLURAL("Note, that selected preset will be deleted from this printer too.",
|
||||
"Note, that selected preset will be deleted from these printers too.", ph_printers.size()) + "\n\n";
|
||||
msg += "\n" + _L_PLURAL("Note, that the selected preset will be deleted from this printer too.",
|
||||
"Note, that the selected preset will be deleted from these printers too.", ph_printers.size()) + "\n\n";
|
||||
}
|
||||
|
||||
if (!ph_printers_only.empty()) {
|
||||
@ -3661,8 +3662,8 @@ void Tab::delete_preset()
|
||||
for (const std::string& printer : ph_printers_only)
|
||||
msg += "\n \"" + from_u8(printer) + "\",";
|
||||
msg.RemoveLast();
|
||||
msg += "\n" + _L_PLURAL("Note, that this printer will be deleted after deleting of the selected preset.",
|
||||
"Note, that these printers will be deleted after deleting of the selected preset.", ph_printers_only.size()) + "\n\n";
|
||||
msg += "\n" + _L_PLURAL("Note, that this printer will be deleted after deleting the selected preset.",
|
||||
"Note, that these printers will be deleted after deleting the selected preset.", ph_printers_only.size()) + "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -899,7 +899,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_
|
||||
_L("You will not be asked about the unsaved changes the next time you: \n"
|
||||
"- close the application,\n"
|
||||
"- load project,\n"
|
||||
"- process Undo / Redo with change of print technologie,\n"
|
||||
"- process Undo / Redo with a change of print technology,\n"
|
||||
"- take/load snapshot,\n"
|
||||
"- load config file/bundle,\n"
|
||||
"- export config_bundle") ;
|
||||
@ -1628,7 +1628,7 @@ void DiffPresetDialog::update_tree()
|
||||
const DynamicPrintConfig& right_congig = right_preset->config;
|
||||
|
||||
if (left_pt != right_preset->printer_technology()) {
|
||||
bottom_info = _L("Comparable printer presets has different printer technology");
|
||||
bottom_info = _L("Compared presets has different printer technology");
|
||||
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "question"));
|
||||
preset_combos.equal_bmp->SetToolTip(bottom_info);
|
||||
continue;
|
||||
@ -1651,7 +1651,7 @@ void DiffPresetDialog::update_tree()
|
||||
show_tree = true;
|
||||
preset_combos.equal_bmp->SetBitmap_(ScalableBitmap(this, "not_equal"));
|
||||
preset_combos.equal_bmp->SetToolTip(_L("Presets are different.\n"
|
||||
"Click this button to select the same as left preset for the right preset."));
|
||||
"Click this button to select the same preset for the right and left preset."));
|
||||
|
||||
m_tree->model->AddPreset(type, "\"" + from_u8(left_preset->name) + "\" vs \"" + from_u8(right_preset->name) + "\"", left_pt);
|
||||
|
||||
|
@ -72,7 +72,7 @@ MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_on
|
||||
content_sizer->Add(cbox);
|
||||
content_sizer->AddSpacer(VERT_SPACING);
|
||||
|
||||
Fit();
|
||||
finalize();
|
||||
}
|
||||
|
||||
MsgUpdateSlic3r::~MsgUpdateSlic3r() {}
|
||||
@ -133,12 +133,12 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_
|
||||
content_sizer->Add(versions);
|
||||
content_sizer->AddSpacer(2*VERT_SPACING);
|
||||
|
||||
add_btn(wxID_OK, true, force_before_wizard ? _L("Install") : "OK");
|
||||
add_button(wxID_OK, true, force_before_wizard ? _L("Install") : "OK");
|
||||
if (force_before_wizard) {
|
||||
add_btn(wxID_CLOSE, false, _L("Don't install"));
|
||||
static_cast<wxButton*>(FindWindowById(wxID_CLOSE, this))->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) { this->EndModal(wxID_CLOSE); });
|
||||
auto* btn = add_button(wxID_CLOSE, false, _L("Don't install"));
|
||||
btn->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) { this->EndModal(wxID_CLOSE); });
|
||||
}
|
||||
add_btn(wxID_CANCEL);
|
||||
add_button(wxID_CANCEL);
|
||||
|
||||
finalize();
|
||||
}
|
||||
@ -190,9 +190,9 @@ MsgUpdateForced::MsgUpdateForced(const std::vector<Update>& updates) :
|
||||
content_sizer->Add(versions);
|
||||
content_sizer->AddSpacer(2 * VERT_SPACING);
|
||||
|
||||
add_btn(wxID_EXIT, false, wxString::Format(_L("Exit %s"), SLIC3R_APP_NAME));
|
||||
add_button(wxID_EXIT, false, wxString::Format(_L("Exit %s"), SLIC3R_APP_NAME));
|
||||
for (auto ID : { wxID_EXIT, wxID_OK })
|
||||
static_cast<wxButton*>(FindWindowById(ID, this))->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); });
|
||||
get_button(ID)->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); });
|
||||
|
||||
finalize();
|
||||
}
|
||||
@ -236,11 +236,11 @@ MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, w
|
||||
content_sizer->Add(versions);
|
||||
content_sizer->AddSpacer(2*VERT_SPACING);
|
||||
|
||||
add_btn(wxID_REPLACE, true, _L("Re-configure"));
|
||||
add_btn(wxID_EXIT, false, wxString::Format(_L("Exit %s"), SLIC3R_APP_NAME));
|
||||
add_button(wxID_REPLACE, true, _L("Re-configure"));
|
||||
add_button(wxID_EXIT, false, wxString::Format(_L("Exit %s"), SLIC3R_APP_NAME));
|
||||
|
||||
for (auto ID : {wxID_EXIT, wxID_REPLACE})
|
||||
static_cast<wxButton*>(FindWindowById(ID, this))->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); });
|
||||
get_button(ID)->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); });
|
||||
|
||||
finalize();
|
||||
}
|
||||
|
@ -776,7 +776,7 @@ static bool reload_configs_update_gui()
|
||||
{
|
||||
wxString header = _L("Configuration Updates causes a lost of preset modification.\n"
|
||||
"So, check unsaved changes and save them if necessary.");
|
||||
if (!GUI::wxGetApp().check_and_save_current_preset_changes(_L("Updater is processing"), header, false ))
|
||||
if (!GUI::wxGetApp().check_and_save_current_preset_changes(_L("Updating"), header, false ))
|
||||
return false;
|
||||
|
||||
// Reload global configuration
|
||||
|
@ -14,7 +14,6 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_mutable_polygon.cpp
|
||||
test_mutable_priority_queue.cpp
|
||||
test_stl.cpp
|
||||
test_meshsimplify.cpp
|
||||
test_meshboolean.cpp
|
||||
test_marchingsquares.cpp
|
||||
test_timeutils.cpp
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/MeshBoolean.hpp>
|
||||
#include <libslic3r/SimplifyMesh.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
|
@ -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");
|
||||
//}
|
||||
|
Loading…
x
Reference in New Issue
Block a user