From df5312d3b279cbcf64568b484706cfeabd10110d Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 28 Nov 2022 17:06:32 +0100 Subject: [PATCH 01/45] Fix to valid file name --- src/slic3r/GUI/Jobs/EmbossJob.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 56d8c7e454..a5fa476722 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -483,7 +483,7 @@ TriangleMesh priv::create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl& ctl) TriangleMesh priv::create_default_mesh() { // When cant load any font use default object loaded from file - std::string path = Slic3r::resources_dir() + "/data/embossed_text.stl"; + std::string path = Slic3r::resources_dir() + "/data/embossed_text.obj"; TriangleMesh triangle_mesh; if (!load_obj(path.c_str(), &triangle_mesh)) { // when can't load mesh use cube From 7bb80a84aba778c51578182f391b08a1cb25dc49 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 28 Nov 2022 17:16:25 +0100 Subject: [PATCH 02/45] Fix UTF8 coding for filename --- src/admesh/shared.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index 807d9ef4c4..8ead78d08e 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -210,8 +210,9 @@ bool its_write_obj(const indexed_triangle_set &its, const char *file) bool its_write_obj(const indexed_triangle_set& its, const std::vector &color, const char* file) { Slic3r::CNumericLocalesSetter locales_setter; - FILE* fp = fopen(file, "w"); + FILE* fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing"; return false; } From f022402963b52b79889c0e733e775fde17db899a Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 29 Nov 2022 08:42:29 +0100 Subject: [PATCH 03/45] Revert ModelVolumeType separation --- src/libslic3r/CMakeLists.txt | 1 - src/libslic3r/Model.hpp | 1 - src/libslic3r/ModelVolumeType.hpp | 16 ---------------- src/slic3r/GUI/Jobs/EmbossJob.hpp | 1 - 4 files changed, 19 deletions(-) delete mode 100644 src/libslic3r/ModelVolumeType.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index bee7ceb628..fd641b9164 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -183,7 +183,6 @@ set(SLIC3R_SOURCES Model.hpp ModelArrange.hpp ModelArrange.cpp - #ModelVolumeType.hpp MultiMaterialSegmentation.cpp MultiMaterialSegmentation.hpp MeshNormals.hpp diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index d514171f17..b280ebcee1 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -14,7 +14,6 @@ #include "Arrange.hpp" #include "CustomGCode.hpp" #include "enum_bitmask.hpp" -//#include "ModelVolumeType.hpp" #include "TextConfiguration.hpp" #include diff --git a/src/libslic3r/ModelVolumeType.hpp b/src/libslic3r/ModelVolumeType.hpp deleted file mode 100644 index 5ae1c6440a..0000000000 --- a/src/libslic3r/ModelVolumeType.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef slic3r_ModelVolumeType_hpp_ -#define slic3r_ModelVolumeType_hpp_ - -namespace Slic3r { - -enum class ModelVolumeType : int { - INVALID = -1, - MODEL_PART = 0, - NEGATIVE_VOLUME, - PARAMETER_MODIFIER, - SUPPORT_BLOCKER, - SUPPORT_ENFORCER, -}; - -} // namespace Slic3r -#endif /* slic3r_ModelVolumeType_hpp_ */ diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index c8ef7fee64..0081024a05 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -5,7 +5,6 @@ #include #include #include -//#include #include "slic3r/Utils/RaycastManager.hpp" #include "slic3r/GUI/Camera.hpp" #include "Job.hpp" From 676a77fbf93a5132e04523f0655f2a2b9a4e0ec1 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 29 Nov 2022 11:16:50 +0100 Subject: [PATCH 04/45] Remove ambigous function --- src/libslic3r/BoundingBox.hpp | 1 - src/libslic3r/IntersectionPoints.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 92c12d4e90..4818ff9eee 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -174,7 +174,6 @@ public: BoundingBox rotated(double angle, const Point ¢er) const; void rotate(double angle) { (*this) = this->rotated(angle); } void rotate(double angle, const Point ¢er) { (*this) = this->rotated(angle, center); } - bool intersects(const BoundingBox &other) const { return this->min(0) <= other.max(0) && this->max(0) >= other.min(0) && this->min(1) <= other.max(1) && this->max(1) >= other.min(1); } // Align the min corner to a grid of cell_size x cell_size cells, // to encompass the original bounding box. void align_to_grid(const coord_t cell_size); diff --git a/src/libslic3r/IntersectionPoints.cpp b/src/libslic3r/IntersectionPoints.cpp index f2c63a53bf..3537e74ab4 100644 --- a/src/libslic3r/IntersectionPoints.cpp +++ b/src/libslic3r/IntersectionPoints.cpp @@ -133,7 +133,7 @@ Slic3r::Pointfs compute_intersections(const Slic3r::Lines &lines) Point max_(std::max(a_.x(), b_.x()), std::max(a_.y(), b_.y())); BoundingBox bb_(min_, max_); // intersect of BB compare min max - if (bb.intersects(bb_) && + if (bb.overlap(bb_) && l.intersection(l_, &i)) pts.push_back(i.cast()); } From d330bbc54b73b2f98ddd4636b1914451e7ce75a7 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 29 Nov 2022 13:39:28 +0100 Subject: [PATCH 05/45] comment source of idea --- src/libslic3r/NSVGUtils.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index a8f23e2e98..2844fc55e6 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -3,6 +3,8 @@ using namespace Slic3r; +// inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez +// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335 void NSVGUtils::flatten_cubic_bez(Polygon &polygon, float tessTol, Vec2f p1, From b1f9d50aad249aaa5500c47caf79eae5b9a8c3d9 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 29 Nov 2022 13:52:47 +0100 Subject: [PATCH 06/45] Rename function: collect_duplications -> collect_duplicates more english correct name --- src/libslic3r/Emboss.cpp | 10 +++++----- src/libslic3r/Point.cpp | 2 +- src/libslic3r/Point.hpp | 2 +- src/libslic3r/Triangulation.cpp | 4 ++-- tests/libslic3r/test_emboss.cpp | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index d7a3ac1f63..440903757a 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -283,7 +283,7 @@ ExPolygons Emboss::heal_shape(const Polygons &shape) { // Do not remove all duplicits but do it better way // Overlap all duplicit points by rectangle 3x3 - Points duplicits = collect_duplications(to_points(polygons)); + Points duplicits = collect_duplicates(to_points(polygons)); if (!duplicits.empty()) { polygons.reserve(polygons.size() + duplicits.size()); for (const Point &p : duplicits) { @@ -310,7 +310,7 @@ bool Emboss::heal_shape(ExPolygons &shape, unsigned max_iteration) priv::remove_same_neighbor(shape); Pointfs intersections = intersection_points(shape); - Points duplicits = collect_duplications(to_points(shape)); + Points duplicits = collect_duplicates(to_points(shape)); //Points close = priv::collect_close_points(shape, 1.); if (intersections.empty() && duplicits.empty() /* && close.empty() */) break; @@ -353,7 +353,7 @@ bool Emboss::heal_shape(ExPolygons &shape, unsigned max_iteration) svg.draw(shape, "green"); svg.draw(duplicits, "lightgray", 13 / Emboss::SHAPE_SCALE); - Points duplicits3 = collect_duplications(to_points(shape)); + Points duplicits3 = collect_duplicates(to_points(shape)); svg.draw(duplicits3, "black", 7 / Emboss::SHAPE_SCALE); Pointfs pts2 = intersection_points(shape); @@ -387,7 +387,7 @@ bool Emboss::heal_shape(ExPolygons &shape, unsigned max_iteration) } assert(intersection_points(shape).empty()); - assert(collect_duplications(to_points(shape)).empty()); + assert(collect_duplicates(to_points(shape)).empty()); return true; } @@ -1186,7 +1186,7 @@ indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d, const IProjection &projection) { Points points = to_points(shape2d); - Points duplicits = collect_duplications(points); + Points duplicits = collect_duplicates(points); return (duplicits.empty()) ? priv::polygons2model_unique(shape2d, projection, points) : priv::polygons2model_duplicit(shape2d, projection, points, duplicits); diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 2e282b400a..ade8a5621b 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -158,7 +158,7 @@ bool has_duplicate_points(std::vector &&pts) return false; } -Points collect_duplications(Points pts /* Copy */) +Points collect_duplicates(Points pts /* Copy */) { std::stable_sort(pts.begin(), pts.end()); Points duplicits; diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index c4504c2115..ae1e882768 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -269,7 +269,7 @@ inline bool has_duplicate_successive_points_closed(const std::vector &pts } // Collect adjecent(duplicit points) -Points collect_duplications(Points pts /* Copy */); +Points collect_duplicates(Points pts /* Copy */); inline bool shorter_then(const Point& p0, const coord_t len) { diff --git a/src/libslic3r/Triangulation.cpp b/src/libslic3r/Triangulation.cpp index a355d725d2..f5978d3e95 100644 --- a/src/libslic3r/Triangulation.cpp +++ b/src/libslic3r/Triangulation.cpp @@ -241,7 +241,7 @@ Triangulation::Indices Triangulation::triangulate(const ExPolygon &expolygon){ Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons){ Points pts = to_points(expolygons); - Points d_pts = collect_duplications(pts); + Points d_pts = collect_duplicates(pts); if (d_pts.empty()) return triangulate(expolygons, pts); Changes changes = create_changes(pts, d_pts); @@ -262,7 +262,7 @@ Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons, { assert(count_points(expolygons) == points.size()); // when contain duplicit coordinate in points will not work properly - assert(collect_duplications(points).empty()); + assert(collect_duplicates(points).empty()); HalfEdges edges; edges.reserve(points.size()); diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index 6a523ed31a..ebb1a17920 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -201,7 +201,7 @@ ExPolygons heal_and_check(const Polygons &polygons) { Pointfs intersections_prev = intersection_points(polygons); Points polygons_points = to_points(polygons); - Points duplicits_prev = collect_duplications(polygons_points); + Points duplicits_prev = collect_duplicates(polygons_points); ExPolygons shape = Emboss::heal_shape(polygons); @@ -215,7 +215,7 @@ ExPolygons heal_and_check(const Polygons &polygons) Pointfs intersections = intersection_points(shape); Points shape_points = to_points(shape); - Points duplicits = collect_duplications(shape_points); + Points duplicits = collect_duplicates(shape_points); //{ // BoundingBox bb(polygons_points); // // bb.scale(svg_scale); From fe736291c795cea5b4e0ab623f51590e89c72c22 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 29 Nov 2022 13:54:38 +0100 Subject: [PATCH 07/45] rename function which change m_volume value to set_volume() --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 180 ++++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 8 +- 2 files changed, 96 insertions(+), 92 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 7a853e7187..766cb04ef1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -329,6 +329,15 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) namespace priv { +/// +/// Access to model from gl_volume +/// TODO: it is more general function --> move to utils +/// +/// Volume to model belongs to +/// Object containing gl_volume +/// Model for volume +static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObject *object); + /// /// Access to model from gl_volume /// TODO: it is more general function --> move to utils @@ -343,9 +352,8 @@ static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObjec /// TODO: it is more general function --> move to select utils /// /// Actual selection -/// All objects /// Model from selection -static ModelVolume *get_selected_volume(const Selection &selection, const ModelObjectPtrs &objects); +static ModelVolume *get_selected_volume(const Selection &selection); /// /// Calculate offset from mouse position to center of text @@ -671,7 +679,7 @@ static void draw_mouse_offset(const std::optional &offset) void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) { if (!m_gui_cfg.has_value()) initialize(); - check_selection(); + set_volume_by_selection(); // Do not render window for not selected text volume if (m_volume == nullptr || !m_volume->text_configuration.has_value()) { @@ -741,8 +749,8 @@ void GLGizmoEmboss::on_set_state() // to reload fonts from system, when install new one wxFontEnumerator::InvalidateCache(); - // Try(when exist) set text configuration by volume - load_configuration(get_selected_volume()); + // Try(when exist) set text configuration by volume + set_volume(priv::get_selected_volume(m_parent.get_selection())); // change position of just opened emboss window set_fine_position(); @@ -900,9 +908,9 @@ EmbossStyles GLGizmoEmboss::create_default_styles() void GLGizmoEmboss::set_default_text(){ m_text = _u8L("Embossed text"); } #include "imgui/imgui_internal.h" // to unfocus input --> ClearActiveID -void GLGizmoEmboss::check_selection() +void GLGizmoEmboss::set_volume_by_selection() { - ModelVolume *vol = get_selected_volume(); + ModelVolume *vol = priv::get_selected_volume(m_parent.get_selection()); // is same volume selected? if (vol != nullptr && m_volume == vol) return; @@ -913,27 +921,88 @@ void GLGizmoEmboss::check_selection() if (m_volume != nullptr) ImGui::ClearActiveID(); // is select embossed volume? - if (load_configuration(vol)) - // successfull load volume for editing - return; - - // behave like adding new text - m_volume = nullptr; - set_default_text(); + if (!set_volume(vol)) { + // Can't load so behave like adding new text + m_volume = nullptr; + set_default_text(); + } +} + +bool GLGizmoEmboss::set_volume(ModelVolume *volume) +{ + if (volume == nullptr) return false; + const std::optional tc_opt = volume->text_configuration; + if (!tc_opt.has_value()) return false; + const TextConfiguration &tc = *tc_opt; + const EmbossStyle &style = tc.style; + + auto has_same_name = [&style](const StyleManager::Item &style_item) -> bool { + const EmbossStyle &es = style_item.style; + return es.name == style.name; + }; + + wxFont wx_font; + bool is_path_changed = false; + if (style.type == WxFontUtils::get_actual_type()) + wx_font = WxFontUtils::load_wxFont(style.path); + if (!wx_font.IsOk()) { + create_notification_not_valid_font(tc); + // Try create similar wx font + wx_font = WxFontUtils::create_wxFont(style); + is_path_changed = wx_font.IsOk(); + } + + const auto& styles = m_style_manager.get_styles(); + auto it = std::find_if(styles.begin(), styles.end(), has_same_name); + if (it == styles.end()) { + // style was not found + if (wx_font.IsOk()) + m_style_manager.load_style(style, wx_font); + } else { + size_t style_index = it - styles.begin(); + if (!m_style_manager.load_style(style_index)) { + // can`t load stored style + m_style_manager.erase(style_index); + if (wx_font.IsOk()) + m_style_manager.load_style(style, wx_font); + + } else { + // stored style is loaded, now set modification of style + m_style_manager.get_style() = style; + m_style_manager.set_wx_font(wx_font); + } + } + + if (is_path_changed) { + std::string path = WxFontUtils::store_wxFont(wx_font); + m_style_manager.get_style().path = path; + } + + m_text = tc.text; + m_volume = volume; + + // store volume state before edit + m_unmodified_volume = {*volume->get_mesh_shared_ptr(), // copy + tc, volume->get_matrix(), volume->name}; + + return true; +} + +ModelVolume *priv::get_model_volume(const GLVolume *gl_volume, const ModelObject *object) +{ + int volume_id = gl_volume->volume_idx(); + if (volume_id < 0 || static_cast(volume_id) >= object->volumes.size()) return nullptr; + return object->volumes[volume_id]; } ModelVolume *priv::get_model_volume(const GLVolume *gl_volume, const ModelObjectPtrs &objects) { - const GLVolume::CompositeID &id = gl_volume->composite_id; - - if (id.object_id < 0 || static_cast(id.object_id) >= objects.size()) return nullptr; - ModelObject *object = objects[id.object_id]; - - if (id.volume_id < 0 || static_cast(id.volume_id) >= object->volumes.size()) return nullptr; - return object->volumes[id.volume_id]; + int object_id = gl_volume->object_idx(); + if (object_id < 0 || static_cast(object_id) >= objects.size()) return nullptr; + return get_model_volume(gl_volume, objects[object_id]); } -ModelVolume *priv::get_selected_volume(const Selection &selection, const ModelObjectPtrs &objects) +ModelVolume *priv::get_selected_volume(const Selection &selection) { int object_idx = selection.get_object_idx(); // is more object selected? @@ -944,15 +1013,10 @@ ModelVolume *priv::get_selected_volume(const Selection &selection, const ModelOb if (volume_idxs.size() != 1) return nullptr; unsigned int vol_id_gl = *volume_idxs.begin(); const GLVolume *vol_gl = selection.get_volume(vol_id_gl); + const ModelObjectPtrs &objects = selection.get_model()->objects; return get_model_volume(vol_gl, objects); } -ModelVolume *GLGizmoEmboss::get_selected_volume() -{ - return priv::get_selected_volume(m_parent.get_selection(), - wxGetApp().plater()->model().objects); -} - // Run Job on main thread (blocking) - ONLY DEBUG static inline void execute_job(std::shared_ptr j) { @@ -2985,66 +3049,6 @@ bool GLGizmoEmboss::choose_svg_file() //return add_volume(name, its); } -bool GLGizmoEmboss::load_configuration(ModelVolume *volume) -{ - if (volume == nullptr) return false; - const std::optional tc_opt = volume->text_configuration; - if (!tc_opt.has_value()) return false; - const TextConfiguration &tc = *tc_opt; - const EmbossStyle &style = tc.style; - - auto has_same_name = [&style](const StyleManager::Item &style_item) -> bool { - const EmbossStyle &es = style_item.style; - return es.name == style.name; - }; - - wxFont wx_font; - bool is_path_changed = false; - if (style.type == WxFontUtils::get_actual_type()) - wx_font = WxFontUtils::load_wxFont(style.path); - if (!wx_font.IsOk()) { - create_notification_not_valid_font(tc); - // Try create similar wx font - wx_font = WxFontUtils::create_wxFont(style); - is_path_changed = wx_font.IsOk(); - } - - const auto& styles = m_style_manager.get_styles(); - auto it = std::find_if(styles.begin(), styles.end(), has_same_name); - if (it == styles.end()) { - // style was not found - if (wx_font.IsOk()) - m_style_manager.load_style(style, wx_font); - } else { - size_t style_index = it - styles.begin(); - if (!m_style_manager.load_style(style_index)) { - // can`t load stored style - m_style_manager.erase(style_index); - if (wx_font.IsOk()) - m_style_manager.load_style(style, wx_font); - - } else { - // stored style is loaded, now set modification of style - m_style_manager.get_style() = style; - m_style_manager.set_wx_font(wx_font); - } - } - - if (is_path_changed) { - std::string path = WxFontUtils::store_wxFont(wx_font); - m_style_manager.get_style().path = path; - } - - m_text = tc.text; - m_volume = volume; - - // store volume state before edit - m_unmodified_volume = {*volume->get_mesh_shared_ptr(), // copy - tc, volume->get_matrix(), volume->name}; - - return true; -} - void GLGizmoEmboss::create_notification_not_valid_font( const TextConfiguration &tc) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 092041e96e..1df32150f4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -91,8 +91,10 @@ private: // localized default text void set_default_text(); - void check_selection(); - ModelVolume *get_selected_volume(); + void set_volume_by_selection(); + // load text configuration from volume into gizmo + bool set_volume(ModelVolume *volume); + // create volume from text - main functionality bool process(); void close(); @@ -158,8 +160,6 @@ private: bool choose_true_type_file(); bool choose_svg_file(); - bool load_configuration(ModelVolume *volume); - // When open text loaded from .3mf it could be written with unknown font bool m_is_unknown_font; void create_notification_not_valid_font(const TextConfiguration& tc); From 1fa532c624e26c22b114c3531425b7b2ba259b8d Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 29 Nov 2022 13:58:41 +0100 Subject: [PATCH 08/45] change stable_sort to sort(in this place it is better) --- src/libslic3r/Point.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index ade8a5621b..8c28616749 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -160,7 +160,7 @@ bool has_duplicate_points(std::vector &&pts) Points collect_duplicates(Points pts /* Copy */) { - std::stable_sort(pts.begin(), pts.end()); + std::sort(pts.begin(), pts.end()); Points duplicits; const Point *prev = &pts.front(); for (size_t i = 1; i < pts.size(); ++i) { From b71b42b9f32e2f61f4caf2ba8cee98f13b7e3922 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 29 Nov 2022 14:26:10 +0100 Subject: [PATCH 09/45] add Note why escape white character in XML --- src/libslic3r/utils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 2cca334d9d..2615937eb3 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -956,7 +956,8 @@ std::string xml_escape(std::string text, bool is_marked/* = false*/) } // Definition of escape symbols https://www.w3.org/TR/REC-xml/#AVNormalize - +// During the read of xml attribute normalization of white spaces is applied +// Soo for not lose white space character it is escaped before store std::string xml_escape_double_quotes_attribute_value(std::string text) { std::string::size_type pos = 0; From b8503ed9048978d3a12dd9c1f295ec51b6702300 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 29 Nov 2022 14:33:47 +0100 Subject: [PATCH 10/45] Remove comment iritating @vojta --- src/libslic3r/libslic3r.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 83608aa72d..0de638ac88 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -112,7 +112,6 @@ inline void append(std::vector& dest, const std::vector& src) dest = src; // copy else dest.insert(dest.end(), src.begin(), src.end()); - // NOTE: insert reserve space when needed } template From d9cc6ecad96d8e4e94d48912a4e6ed23b07c87b4 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 29 Nov 2022 18:42:11 +0100 Subject: [PATCH 11/45] Show imgui demo window --- src/slic3r/GUI/GLCanvas3D.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 11d1bb80c3..11d8f16688 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1697,6 +1697,8 @@ void GLCanvas3D::render() #endif // ENABLE_RAYCAST_PICKING_DEBUG } + ImGui::ShowDemoWindow(); + const bool is_looking_downward = camera.is_looking_downward(); // draw scene From 9a40e63a11575c16a66e9bc7e77b619f2cce9004 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 30 Nov 2022 10:49:53 +0100 Subject: [PATCH 12/45] Disable background fadeout animation for modal imgui windows --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 766cb04ef1..bacc1b1514 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1908,6 +1908,7 @@ void GLGizmoEmboss::draw_style_rename_button() else ImGui::SetTooltip("%s", _u8L("Can't rename temporary style.").c_str()); } if (ImGui::BeginPopupModal(popup_id, 0, ImGuiWindowFlags_AlwaysAutoResize)) { + m_imgui->disable_background_fadeout_animation(); draw_style_rename_popup(); ImGui::EndPopup(); } @@ -2002,6 +2003,7 @@ void GLGizmoEmboss::draw_style_add_button() } if (ImGui::BeginPopupModal(popup_id, 0, ImGuiWindowFlags_AlwaysAutoResize)) { + m_imgui->disable_background_fadeout_animation(); draw_style_save_as_popup(); ImGui::EndPopup(); } @@ -2050,6 +2052,7 @@ void GLGizmoEmboss::draw_delete_style_button() { } if (ImGui::BeginPopupModal(popup_id)) { + m_imgui->disable_background_fadeout_animation(); const std::string &style_name = m_style_manager.get_style().name; std::string text_in_popup = GUI::format(_L("Are you sure,\nthat you want permanently and unrecoverable \nremove style \"%1%\"?"), style_name); ImGui::Text("%s", text_in_popup.c_str()); From 1309dab7e708448189e821dcddd1c2b5ed1033d0 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 30 Nov 2022 11:35:48 +0100 Subject: [PATCH 13/45] Do not inform user about disable cut surface functionality --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index bacc1b1514..514c207e87 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -205,10 +205,6 @@ static void find_closest_volume(const Selection &selection, /// Screen coordinat, where to create new object laying on bed static void start_create_object_job(DataBase &emboss_data, const Vec2d &coor); -static void message_disable_cut_surface(){ - wxMessageBox(_L("Can NOT cut surface from nothing. Function 'use surface' was disabled for this text."), - _L("Disable 'use surface' from style"), wxOK | wxICON_WARNING);} - /// /// Create transformation for new created emboss object by mouse position /// @@ -1063,12 +1059,9 @@ bool GLGizmoEmboss::process() // check cutting from source mesh bool &use_surface = data.text_configuration.style.prop.use_surface; bool is_object = m_volume->get_object()->volumes.size() == 1; - if (use_surface && is_object) { - priv::message_disable_cut_surface(); + if (use_surface && is_object) use_surface = false; - } - - + if (use_surface) { // Model to cut surface from. SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume); @@ -3317,10 +3310,8 @@ void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor) if (prop.distance.has_value()) prop.distance.reset(); // can't create new object with using surface - if (prop.use_surface) { - priv::message_disable_cut_surface(); + if (prop.use_surface) prop.use_surface = false; - } // Transform3d volume_tr = priv::create_transformation_on_bed(mouse_pos, camera, bed_shape, prop.emboss / 2); DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape}; @@ -3340,7 +3331,6 @@ void priv::start_create_volume_job(const ModelObject *object, // Model to cut surface from. SurfaceVolumeData::ModelSources sources = create_sources(object->volumes); if (sources.empty()) { - priv::message_disable_cut_surface(); use_surface = false; } else { bool is_outside = volume_type == ModelVolumeType::MODEL_PART; From 8d54798231b3eb07e659ee18a6ba7a39d1ddcb11 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 30 Nov 2022 12:07:46 +0100 Subject: [PATCH 14/45] Extend Hot fix by description --- src/imgui/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/imgui/README.md b/src/imgui/README.md index 58008bded0..9413811ae2 100644 --- a/src/imgui/README.md +++ b/src/imgui/README.md @@ -17,6 +17,12 @@ imstb_truetype.h modification: Hot fix for open symbolic fonts on windows 62bdfe6f8d04b88e8bd511cd613be80c0baa7f55 +Add case STBTT_MS_EID_SYMBOL to swith in file imstb_truetype.h on line 1440. Hot fix for open curved fonts mainly on MAC 2148e49f75d82cb19dc6ec409fb7825296ed005c +viz. https://github.com/nothings/stb/issues/1296 +In file imstb_truetype.h line 1667 change malloc size from: +vertices = (stbtt_vertex *) STBTT_malloc((m + 1) * sizeof(vertices[0]), info->userdata); +to: +vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); \ No newline at end of file From 36f6c8bf655a6638d2babd8d60639deb9cb17284 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 30 Nov 2022 15:46:27 +0100 Subject: [PATCH 15/45] temporary add scope guard test for MacOs --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 514c207e87..09fba9212c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1138,6 +1138,23 @@ void GLGizmoEmboss::discard_and_close() { // * Volume containing 3mf fix transformation - needs work around } +void scopeguard_test() { + bool v = false; + { + ScopeGuard sg; + if (!v) { + v = true; + sg = ScopeGuard([&v]() { + if(!v) wxMessageBox("Guard is called twice."); + v = false; + }); + if (!v) wxMessageBox("v should be true in condition."); + } + if (!v) wxMessageBox("v should be true in scope."); + } + if (v) wxMessageBox("v should NOT be true."); +} + void GLGizmoEmboss::draw_window() { #ifdef ALLOW_DEBUG_MODE @@ -1155,6 +1172,8 @@ void GLGizmoEmboss::draw_window() m_imgui->disabled_end(); }); + scopeguard_test(); + draw_text_input(); draw_model_type(); draw_style_list(); From a4af4bb0b332ab0a6100e16bcb5cf910b70d5d8e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 1 Dec 2022 09:45:49 +0100 Subject: [PATCH 16/45] Sychronize imgui keys state into ImGuiWrapper::new_frame() to prevent misalignment when the key up event happens after the application loses focus --- src/slic3r/GUI/ImGuiWrapper.cpp | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 17709788d7..7b82276423 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -294,6 +294,42 @@ void ImGuiWrapper::new_frame() ImGui::NewFrame(); m_new_frame_open = true; + + // synchronize key states + // when the application loses the focus it may happen that the key up event is not processed + + // modifier keys + auto synchronize_mod_key = [](const std::pair& key) { + ImGuiIO& io = ImGui::GetIO(); + if ((io.KeyMods & key.first) != 0 && !wxGetKeyState(key.second)) + io.KeyMods &= ~key.first; + }; + + std::vector> imgui_mod_keys = { + { ImGuiKeyModFlags_Ctrl, WXK_CONTROL }, + { ImGuiKeyModFlags_Shift, WXK_SHIFT }, + { ImGuiKeyModFlags_Alt, WXK_ALT } + }; + + for (const std::pair& key : imgui_mod_keys) { + synchronize_mod_key(key); + } + + // regular keys + ImGuiIO& io = ImGui::GetIO(); + for (size_t i = 0; i < IM_ARRAYSIZE(io.KeysDown); ++i) { + wxKeyCode keycode = WXK_NONE; + if (33 <= i && i <= 126) + keycode = (wxKeyCode)i; + else { + auto it = std::find(std::begin(io.KeyMap), std::end(io.KeyMap), i); + if (it != std::end(io.KeyMap)) + keycode = (wxKeyCode)i; + } + + if (io.KeysDown[i] && keycode != WXK_NONE && !wxGetKeyState(keycode)) + io.KeysDown[i] = false; + } } void ImGuiWrapper::render() From 58a825dd91c2853aac12829a19f5ca77cfc299df Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 1 Dec 2022 17:20:10 +0100 Subject: [PATCH 17/45] Fix Release ghost pressing of key backspace, del, ... Add debug log for imgui update key data - aka key events --- src/slic3r/GUI/ImGuiWrapper.cpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 17709788d7..233e102a6c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -257,20 +257,37 @@ bool ImGuiWrapper::update_key_data(wxKeyEvent &evt) return false; } - ImGuiIO& io = ImGui::GetIO(); + auto to_string = [](wxEventType type) -> std::string { + if (type == wxEVT_CHAR) return "Char"; + if (type == wxEVT_KEY_DOWN) return "KeyDown"; + if (type == wxEVT_KEY_UP) return "KeyUp"; + return "Other"; + }; - if (evt.GetEventType() == wxEVT_CHAR) { + wxEventType type = evt.GetEventType(); + ImGuiIO& io = ImGui::GetIO(); + BOOST_LOG_TRIVIAL(debug) << "ImGui - key event(" << to_string(type) << "):" + //<< " Unicode(" << evt.GetUnicodeKey() << ")" + << " KeyCode(" << evt.GetKeyCode() << ")"; + + if (type == wxEVT_CHAR) { // Char event - const auto key = evt.GetUnicodeKey(); + const auto key = evt.GetUnicodeKey(); + unsigned int key_u = static_cast(key); + // Release BackSpace, Delete, ... when miss wxEVT_KEY_UP event + if (key_u >= 0 && key_u < IM_ARRAYSIZE(io.KeysDown) && io.KeysDown[key_u]) { + io.KeysDown[key_u] = false; + } + if (key != 0) { io.AddInputCharacter(key); } - } else { + } else if (type == wxEVT_KEY_DOWN || type == wxEVT_KEY_UP) { // Key up/down event int key = evt.GetKeyCode(); wxCHECK_MSG(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown), false, "Received invalid key code"); - io.KeysDown[key] = evt.GetEventType() == wxEVT_KEY_DOWN; + io.KeysDown[key] = (type == wxEVT_KEY_DOWN); io.KeyShift = evt.ShiftDown(); io.KeyCtrl = evt.ControlDown(); io.KeyAlt = evt.AltDown(); From 9f395a26a3925f615063d7424362ab194b915160 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 1 Dec 2022 17:20:23 +0100 Subject: [PATCH 18/45] Remove test --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 09fba9212c..514c207e87 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1138,23 +1138,6 @@ void GLGizmoEmboss::discard_and_close() { // * Volume containing 3mf fix transformation - needs work around } -void scopeguard_test() { - bool v = false; - { - ScopeGuard sg; - if (!v) { - v = true; - sg = ScopeGuard([&v]() { - if(!v) wxMessageBox("Guard is called twice."); - v = false; - }); - if (!v) wxMessageBox("v should be true in condition."); - } - if (!v) wxMessageBox("v should be true in scope."); - } - if (v) wxMessageBox("v should NOT be true."); -} - void GLGizmoEmboss::draw_window() { #ifdef ALLOW_DEBUG_MODE @@ -1172,8 +1155,6 @@ void GLGizmoEmboss::draw_window() m_imgui->disabled_end(); }); - scopeguard_test(); - draw_text_input(); draw_model_type(); draw_style_list(); From d6add813192ccb3e2653da8acdbed67038cc07b2 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 1 Dec 2022 17:35:17 +0100 Subject: [PATCH 19/45] Show imgui demo window after type "demo" --- src/slic3r/GUI/GLCanvas3D.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8493f38dc6..13ea764d0b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -94,6 +94,11 @@ static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE = 131072 * 2; // 1.05MB //static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4; // 128MB #endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#define SHOW_IMGUI_DEMO_WINDOW +#ifdef SHOW_IMGUI_DEMO_WINDOW +static bool show_imgui_demo_window = false; +#endif // SHOW_IMGUI_DEMO_WINDOW + namespace Slic3r { namespace GUI { @@ -1694,8 +1699,10 @@ void GLCanvas3D::render() } #endif // ENABLE_RAYCAST_PICKING_DEBUG } - - ImGui::ShowDemoWindow(); + +#ifdef SHOW_IMGUI_DEMO_WINDOW + if (show_imgui_demo_window) ImGui::ShowDemoWindow(); +#endif // SHOW_IMGUI_DEMO_WINDOW const bool is_looking_downward = camera.is_looking_downward(); @@ -2642,10 +2649,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) if (!m_initialized) return; - // see include/wx/defs.h enum wxKeyCode - int keyCode = evt.GetKeyCode(); - int ctrlMask = wxMOD_CONTROL; - int shiftMask = wxMOD_SHIFT; +#ifdef SHOW_IMGUI_DEMO_WINDOW + static int cur = 0; + if (wxString("demo")[cur] = evt.GetUnicodeKey()) ++cur; else cur = 0; + if (cur == 4) { show_imgui_demo_window = !show_imgui_demo_window; cur = 0;} +#endif // SHOW_IMGUI_DEMO_WINDOW auto imgui = wxGetApp().imgui(); if (imgui->update_key_data(evt)) { @@ -2653,6 +2661,10 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) return; } + // see include/wx/defs.h enum wxKeyCode + int keyCode = evt.GetKeyCode(); + int ctrlMask = wxMOD_CONTROL; + int shiftMask = wxMOD_SHIFT; if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu())) return; From 0e01e6af72764f844894bbe58f0982bd6f156bc0 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 1 Dec 2022 21:14:55 +0100 Subject: [PATCH 20/45] Fix crash when double click on wipe tower Add option to open embos gizmo by doubleclick when move scale rotate or emboss + fix demo opening --- src/slic3r/GUI/GLCanvas3D.cpp | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 13ea764d0b..13ed57c7bf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2651,7 +2651,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #ifdef SHOW_IMGUI_DEMO_WINDOW static int cur = 0; - if (wxString("demo")[cur] = evt.GetUnicodeKey()) ++cur; else cur = 0; + if (wxString("demo")[cur] == evt.GetUnicodeKey()) ++cur; else cur = 0; if (cur == 4) { show_imgui_demo_window = !show_imgui_demo_window; cur = 0;} #endif // SHOW_IMGUI_DEMO_WINDOW @@ -3740,20 +3740,27 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) evt.Skip(); // Detection of doubleclick on text to open emboss edit window - if (evt.LeftDClick() && m_gizmos.get_current() == nullptr && !m_hover_volume_idxs.empty()) { + auto type = m_gizmos.get_current_type(); + if (evt.LeftDClick() && !m_hover_volume_idxs.empty() && + (type == GLGizmosManager::EType::Undefined || + type == GLGizmosManager::EType::Move || + type == GLGizmosManager::EType::Rotate || + type == GLGizmosManager::EType::Scale || + type == GLGizmosManager::EType::Emboss) ) { for (int hover_volume_id : m_hover_volume_idxs) { const GLVolume &hover_gl_volume = *m_volumes.volumes[hover_volume_id]; - const ModelObject* hover_object = m_model->objects[hover_gl_volume.object_idx()]; + int object_idx = hover_gl_volume.object_idx(); + if (object_idx < 0 || object_idx >= m_model->objects.size()) continue; + const ModelObject* hover_object = m_model->objects[object_idx]; int hover_volume_idx = hover_gl_volume.volume_idx(); + if (hover_volume_idx < 0 || hover_volume_idx >= hover_object->volumes.size()) continue; const ModelVolume* hover_volume = hover_object->volumes[hover_volume_idx]; - if (hover_volume->text_configuration.has_value()) { - //m_selection.set_mode(Selection::EMode::Volume); - //m_selection.add(hover_volume_id); // add whole instance - m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id}); + if (!hover_volume->text_configuration.has_value()) continue; + m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id}); + if (type != GLGizmosManager::EType::Emboss) m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss); - wxGetApp().obj_list()->update_selections(); - return; - } + wxGetApp().obj_list()->update_selections(); + return; } } From cb7705ddbd67a750a7eb2b1291ff98d6a7f215f9 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 2 Dec 2022 17:57:34 +0100 Subject: [PATCH 21/45] optimize for each frame --- src/slic3r/GUI/ImGuiWrapper.cpp | 54 +++++++++++++++------------------ 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index e63dab1461..7d68565e97 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -274,10 +274,12 @@ bool ImGuiWrapper::update_key_data(wxKeyEvent &evt) // Char event const auto key = evt.GetUnicodeKey(); unsigned int key_u = static_cast(key); + // Release BackSpace, Delete, ... when miss wxEVT_KEY_UP event - if (key_u >= 0 && key_u < IM_ARRAYSIZE(io.KeysDown) && io.KeysDown[key_u]) { - io.KeysDown[key_u] = false; - } + // Already Fixed at begining of new frame + //if (key_u >= 0 && key_u < IM_ARRAYSIZE(io.KeysDown) && io.KeysDown[key_u]) { + // io.KeysDown[key_u] = false; + //} if (key != 0) { io.AddInputCharacter(key); @@ -299,6 +301,7 @@ bool ImGuiWrapper::update_key_data(wxKeyEvent &evt) return ret; } +#include void ImGuiWrapper::new_frame() { if (m_new_frame_open) { @@ -312,40 +315,33 @@ void ImGuiWrapper::new_frame() ImGui::NewFrame(); m_new_frame_open = true; + ImGuiIO& io = ImGui::GetIO(); // synchronize key states // when the application loses the focus it may happen that the key up event is not processed - // modifier keys - auto synchronize_mod_key = [](const std::pair& key) { - ImGuiIO& io = ImGui::GetIO(); + // synchronize modifier keys + constexpr std::array, 3> imgui_mod_keys{ + std::make_pair(ImGuiKeyModFlags_Ctrl, WXK_CONTROL), + std::make_pair(ImGuiKeyModFlags_Shift, WXK_SHIFT), + std::make_pair(ImGuiKeyModFlags_Alt, WXK_ALT)}; + for (const std::pair& key : imgui_mod_keys) { if ((io.KeyMods & key.first) != 0 && !wxGetKeyState(key.second)) io.KeyMods &= ~key.first; - }; - - std::vector> imgui_mod_keys = { - { ImGuiKeyModFlags_Ctrl, WXK_CONTROL }, - { ImGuiKeyModFlags_Shift, WXK_SHIFT }, - { ImGuiKeyModFlags_Alt, WXK_ALT } - }; - - for (const std::pair& key : imgui_mod_keys) { - synchronize_mod_key(key); } - // regular keys - ImGuiIO& io = ImGui::GetIO(); - for (size_t i = 0; i < IM_ARRAYSIZE(io.KeysDown); ++i) { - wxKeyCode keycode = WXK_NONE; - if (33 <= i && i <= 126) - keycode = (wxKeyCode)i; - else { - auto it = std::find(std::begin(io.KeyMap), std::end(io.KeyMap), i); - if (it != std::end(io.KeyMap)) - keycode = (wxKeyCode)i; - } - + // Not sure if it is neccessary + // values from 33 to 126 are reserved for the standard ASCII characters + for (size_t i = 33; i <= 126; ++i) { + wxKeyCode keycode = static_cast(i); if (io.KeysDown[i] && keycode != WXK_NONE && !wxGetKeyState(keycode)) - io.KeysDown[i] = false; + io.KeysDown[i] = false; + } + + // special keys: delete, backspace, ... + for (int key: io.KeyMap) { + wxKeyCode keycode = static_cast(key); + if (io.KeysDown[key] && keycode != WXK_NONE && !wxGetKeyState(keycode)) + io.KeysDown[key] = false; } } From 3c961f38168f8faab9d13ad4d96e202a3fa2df9f Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 2 Dec 2022 18:47:11 +0100 Subject: [PATCH 22/45] Move 3mf tag into Slic3r namespace - Break back compatibility with loading text volume Use surface retype as int --- src/libslic3r/Format/3mf.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index bc379814e9..8997df1920 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -148,7 +148,7 @@ static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed"; static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges"; // Store / load of TextConfiguration -static constexpr const char *TEXT_TAG = "emboss"; +static constexpr const char *TEXT_TAG = "slic3rpe:text"; static constexpr const char *TEXT_DATA_ATTR = "text"; // TextConfiguration::EmbossStyle static constexpr const char *STYLE_NAME_ATTR = "style_name"; @@ -3628,8 +3628,8 @@ std::optional TextConfigurationSerialization::read(const char float distance = get_attribute_value_float(attributes, num_attributes, DISTANCE_ATTR); if (std::fabs(distance) > std::numeric_limits::epsilon()) fp.distance = distance; - std::string use_surface = get_attribute_value_string(attributes, num_attributes, USE_SURFACE_ATTR); - if (!use_surface.empty()) fp.use_surface = true; + int use_surface = get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR); + if (use_surface == 1) fp.use_surface = true; float angle = get_attribute_value_float(attributes, num_attributes, ANGLE_ATTR); if (std::fabs(angle) > std::numeric_limits::epsilon()) fp.angle = angle; From 9d1204d6f5618e62bd7535b611c3dbea3985b9c0 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 2 Dec 2022 19:00:44 +0100 Subject: [PATCH 23/45] Fix typo 'activ_font' to 'active_font' --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 20 +++++++++---------- src/slic3r/Utils/EmbossStyleManager.cpp | 4 ++-- src/slic3r/Utils/EmbossStyleManager.hpp | 2 +- src/slic3r/Utils/EmbossStylesSerializable.cpp | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 514c207e87..d1a920b5bf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -315,8 +315,8 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) angle_opt.reset(); // set into activ style - assert(m_style_manager.is_activ_font()); - if (m_style_manager.is_activ_font()) + assert(m_style_manager.is_active_font()); + if (m_style_manager.is_active_font()) m_style_manager.get_font_prop().angle = angle_opt; } @@ -1042,7 +1042,7 @@ bool GLGizmoEmboss::process() if (m_text.empty()) return false; // exist loaded font file? - if (!m_style_manager.is_activ_font()) return false; + if (!m_style_manager.is_active_font()) return false; // Cancel previous Job, when it is in process // Can't use cancel, because I want cancel only previous EmbossUpdateJob no other jobs @@ -1145,8 +1145,8 @@ void GLGizmoEmboss::draw_window() if (ImGui::Button("add svg")) choose_svg_file(); #endif // ALLOW_DEBUG_MODE - bool is_activ_font = m_style_manager.is_activ_font(); - if (!is_activ_font) + bool is_active_font = m_style_manager.is_active_font(); + if (!is_active_font) m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Warning: No font is selected. Select correct one.")); // Disable all except selection of font, when open text from 3mf with unknown font @@ -1158,7 +1158,7 @@ void GLGizmoEmboss::draw_window() draw_text_input(); draw_model_type(); draw_style_list(); - m_imgui->disabled_begin(!is_activ_font); + m_imgui->disabled_begin(!is_active_font); ImGui::TreePush(); draw_style_edit(); ImGui::TreePop(); @@ -1176,7 +1176,7 @@ void GLGizmoEmboss::draw_window() ImGui::TreePop(); } else if (m_is_advanced_edit_style) set_minimal_window_size(false); - m_imgui->disabled_end(); // !is_activ_font + m_imgui->disabled_end(); // !is_active_font #ifdef SHOW_WX_FONT_DESCRIPTOR if (is_selected_style) @@ -1655,7 +1655,7 @@ void GLGizmoEmboss::draw_font_list() { // Set partial wxString actual_face_name; - if (m_style_manager.is_activ_font()) { + if (m_style_manager.is_active_font()) { const std::optional &wx_font_opt = m_style_manager.get_wx_font(); if (wx_font_opt.has_value()) actual_face_name = wx_font_opt->GetFaceName(); @@ -2088,7 +2088,7 @@ void GLGizmoEmboss::fix_transformation(const FontProp &from, } void GLGizmoEmboss::draw_style_list() { - if (!m_style_manager.is_activ_font()) return; + if (!m_style_manager.is_active_font()) return; const EmbossStyle *stored_style = nullptr; bool is_stored = m_style_manager.exist_stored_style(); @@ -3255,7 +3255,7 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager& st }; auto create_configuration = [&]() -> TextConfiguration { - if (!style_manager.is_activ_font()) { + if (!style_manager.is_active_font()) { std::string default_text_for_emboss = _u8L("Embossed text"); EmbossStyle es = style_manager.get_style(); TextConfiguration tc{es, default_text_for_emboss}; diff --git a/src/slic3r/Utils/EmbossStyleManager.cpp b/src/slic3r/Utils/EmbossStyleManager.cpp index 256b1160b0..77df3e2170 100644 --- a/src/slic3r/Utils/EmbossStyleManager.cpp +++ b/src/slic3r/Utils/EmbossStyleManager.cpp @@ -200,7 +200,7 @@ bool StyleManager::load_style(const EmbossStyle &style, const wxFont &font) return true; } -bool StyleManager::is_activ_font() { return m_style_cache.font_file.has_value(); } +bool StyleManager::is_active_font() { return m_style_cache.font_file.has_value(); } bool StyleManager::load_first_valid_font() { while (!m_style_items.empty()) { @@ -228,7 +228,7 @@ void StyleManager::clear_imgui_font() { m_style_cache.atlas.Clear(); } ImFont *StyleManager::get_imgui_font() { - if (!is_activ_font()) return nullptr; + if (!is_active_font()) return nullptr; ImVector &fonts = m_style_cache.atlas.Fonts; if (fonts.empty()) return nullptr; diff --git a/src/slic3r/Utils/EmbossStyleManager.hpp b/src/slic3r/Utils/EmbossStyleManager.hpp index 502ba84bd5..bc04ede32b 100644 --- a/src/slic3r/Utils/EmbossStyleManager.hpp +++ b/src/slic3r/Utils/EmbossStyleManager.hpp @@ -185,7 +185,7 @@ public: }; // check if exist selected font style in manager - bool is_activ_font(); + bool is_active_font(); // Limits for imgui loaded font size // Value out of limits is crop diff --git a/src/slic3r/Utils/EmbossStylesSerializable.cpp b/src/slic3r/Utils/EmbossStylesSerializable.cpp index 483f147b11..a1c3c599af 100644 --- a/src/slic3r/Utils/EmbossStylesSerializable.cpp +++ b/src/slic3r/Utils/EmbossStylesSerializable.cpp @@ -19,7 +19,7 @@ const std::string EmbossStylesSerializable::APP_CONFIG_FONT_COLLECTION = "colle const std::string EmbossStylesSerializable::APP_CONFIG_FONT_CHAR_GAP = "char_gap"; const std::string EmbossStylesSerializable::APP_CONFIG_FONT_LINE_GAP = "line_gap"; -const std::string EmbossStylesSerializable::APP_CONFIG_ACTIVE_FONT = "activ_font"; +const std::string EmbossStylesSerializable::APP_CONFIG_ACTIVE_FONT = "active_font"; std::string EmbossStylesSerializable::create_section_name(unsigned index) { @@ -150,8 +150,8 @@ void EmbossStylesSerializable::store_style_index(AppConfig &cfg, unsigned index) // store actual font index cfg.clear_section(AppConfig::SECTION_EMBOSS_STYLE); // activ font first index is +1 to correspond with section name - std::string activ_font = std::to_string(index); - cfg.set(AppConfig::SECTION_EMBOSS_STYLE, APP_CONFIG_ACTIVE_FONT, activ_font); + std::string active_font = std::to_string(index); + cfg.set(AppConfig::SECTION_EMBOSS_STYLE, APP_CONFIG_ACTIVE_FONT, active_font); } std::optional EmbossStylesSerializable::load_style_index(const AppConfig &cfg) From 5266c6be8e7ecbdf127e7d329900a1f9579b7753 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Fri, 2 Dec 2022 19:03:26 +0100 Subject: [PATCH 24/45] Fix typo 'activ_' to 'active_' --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 12 ++++++------ src/slic3r/Utils/EmbossStyleManager.cpp | 22 +++++++++++----------- src/slic3r/Utils/EmbossStyleManager.hpp | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index d1a920b5bf..8a9a51c983 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -2013,9 +2013,9 @@ void GLGizmoEmboss::draw_delete_style_button() { if (draw_button(IconType::erase, !can_delete)) { while (true) { // NOTE: can't use previous loaded activ index -> erase could change index - size_t activ_index = m_style_manager.get_style_index(); - next_style_index = (activ_index > 0) ? activ_index - 1 : - activ_index + 1; + size_t active_index = m_style_manager.get_style_index(); + next_style_index = (active_index > 0) ? active_index - 1 : + active_index + 1; if (next_style_index >= m_style_manager.get_styles().size()) { // can't remove last font style // TODO: inform user @@ -2029,7 +2029,7 @@ void GLGizmoEmboss::draw_delete_style_button() { } // load back - m_style_manager.load_style(activ_index); + m_style_manager.load_style(active_index); ImGui::OpenPopup(popup_id); break; } @@ -2050,9 +2050,9 @@ void GLGizmoEmboss::draw_delete_style_button() { std::string text_in_popup = GUI::format(_L("Are you sure,\nthat you want permanently and unrecoverable \nremove style \"%1%\"?"), style_name); ImGui::Text("%s", text_in_popup.c_str()); if (ImGui::Button(_u8L("Yes").c_str())) { - size_t activ_index = m_style_manager.get_style_index(); + size_t active_index = m_style_manager.get_style_index(); m_style_manager.load_style(next_style_index); - m_style_manager.erase(activ_index); + m_style_manager.erase(active_index); m_style_manager.store_styles_to_app_config(wxGetApp().app_config); ImGui::CloseCurrentPopup(); process(); diff --git a/src/slic3r/Utils/EmbossStyleManager.cpp b/src/slic3r/Utils/EmbossStyleManager.cpp index 77df3e2170..22ad0827ea 100644 --- a/src/slic3r/Utils/EmbossStyleManager.cpp +++ b/src/slic3r/Utils/EmbossStyleManager.cpp @@ -39,19 +39,19 @@ void StyleManager::init(AppConfig *app_config, const EmbossStyles &default_style m_style_items.push_back({style}); } - std::optional activ_index_opt = (app_config != nullptr) ? + std::optional active_index_opt = (app_config != nullptr) ? EmbossStylesSerializable::load_style_index(*app_config) : std::optional{}; - size_t activ_index = 0; - if (activ_index_opt.has_value()) activ_index = *activ_index_opt; - if (activ_index >= m_style_items.size()) activ_index = 0; + size_t active_index = 0; + if (active_index_opt.has_value()) active_index = *active_index_opt; + if (active_index >= m_style_items.size()) active_index = 0; // find valid font item - if (!load_style(activ_index)) { - m_style_items.erase(m_style_items.begin() + activ_index); - activ_index = 0; - while (m_style_items.empty() || !load_style(activ_index)) + if (!load_style(active_index)) { + m_style_items.erase(m_style_items.begin() + active_index); + active_index = 0; + while (m_style_items.empty() || !load_style(active_index)) m_style_items.erase(m_style_items.begin()); // no one style from config is loadable if (m_style_items.empty()) { @@ -61,14 +61,14 @@ void StyleManager::init(AppConfig *app_config, const EmbossStyles &default_style m_style_items.push_back({std::move(style)}); } // try to load first default font - [[maybe_unused]] bool loaded = load_style(activ_index); + [[maybe_unused]] bool loaded = load_style(active_index); assert(loaded); } } } bool StyleManager::store_styles_to_app_config(bool use_modification, - bool store_activ_index) + bool store_active_index) { assert(m_app_config != nullptr); if (m_app_config == nullptr) return false; @@ -87,7 +87,7 @@ bool StyleManager::store_styles_to_app_config(bool use_modification, m_style_cache.stored_wx_font = m_style_cache.wx_font; } - if (store_activ_index) + if (store_active_index) { size_t style_index = exist_stored_style() ? m_style_cache.style_index : diff --git a/src/slic3r/Utils/EmbossStyleManager.hpp b/src/slic3r/Utils/EmbossStyleManager.hpp index bc04ede32b..3851a10517 100644 --- a/src/slic3r/Utils/EmbossStyleManager.hpp +++ b/src/slic3r/Utils/EmbossStyleManager.hpp @@ -46,7 +46,7 @@ public: /// When true cache state will be used for store /// When true store activ index into configuration /// True on succes otherwise False. - bool store_styles_to_app_config(bool use_modification = true, bool store_activ_index = true); + bool store_styles_to_app_config(bool use_modification = true, bool store_active_index = true); /// /// Append actual style to style list From 22891acc07a45d558c9a9cb76965d96a8658af0c Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 5 Dec 2022 15:20:56 +0100 Subject: [PATCH 25/45] Lock emboss text window position --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 83 ++++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 11 ++-- 2 files changed, 51 insertions(+), 43 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 8a9a51c983..6d89132b08 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -54,7 +54,7 @@ #define SHOW_ICONS_TEXTURE #define SHOW_FINE_POSITION // draw convex hull around volume #define SHOW_WX_WEIGHT_INPUT -#define DRAW_PLACE_TO_ADD_TEXT +#define DRAW_PLACE_TO_ADD_TEXT // Interactive draw of window position #endif // ALLOW_DEBUG_MODE using namespace Slic3r; @@ -681,7 +681,7 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) if (m_volume == nullptr || !m_volume->text_configuration.has_value()) { close(); return; - } + } // TODO: fix width - showing scroll in first draw of advanced. const ImVec2 &min_window_size = get_minimal_window_size(); @@ -697,13 +697,19 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) draw_mouse_offset(m_dragging_mouse_offset); #endif // SHOW_OFFSET_DURING_DRAGGING - // check if is set window offset - if (m_set_window_offset.has_value()) { - ImGui::SetNextWindowPos(*m_set_window_offset, ImGuiCond_Always); - m_set_window_offset.reset(); + ImGuiWindowFlags flag = ImGuiWindowFlags_NoCollapse; + if (m_allow_float_window){ + // check if is set window offset + if (m_set_window_offset.has_value()) { + ImGui::SetNextWindowPos(*m_set_window_offset, ImGuiCond_Always); + m_set_window_offset.reset(); + } + } else { + flag |= ImGuiWindowFlags_NoMove; + y = std::min(y, bottom_limit - min_window_size.y); + ImGui::SetNextWindowPos(ImVec2(x, y), ImGuiCond_Always); } - ImGuiWindowFlags flag = ImGuiWindowFlags_NoCollapse; if (ImGui::Begin(on_get_name().c_str(), nullptr, flag)) { // Need to pop var before draw window ImGui::PopStyleVar(); // WindowMinSize @@ -714,6 +720,29 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) ImGui::End(); } +namespace priv { +/// +/// Move window for edit emboss text near to embossed object +/// NOTE: embossed object must be selected +/// +ImVec2 calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size) +{ + const Selection::IndicesList indices = selection.get_volume_idxs(); + // no selected volume + if (indices.empty()) return {}; + const GLVolume *volume = selection.get_volume(*indices.begin()); + // bad volume selected (e.g. deleted one) + if (volume == nullptr) return {}; + + const Camera &camera = wxGetApp().plater()->get_camera(); + Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *volume); + + ImVec2 c_size(canvas_size.get_width(), canvas_size.get_height()); + ImVec2 offset = ImGuiWrapper::suggest_location(windows_size, hull, c_size); + return offset; +} +} // namespace priv + void GLGizmoEmboss::on_set_state() { // enable / disable bed from picking @@ -749,7 +778,8 @@ void GLGizmoEmboss::on_set_state() set_volume(priv::get_selected_volume(m_parent.get_selection())); // change position of just opened emboss window - set_fine_position(); + if (m_allow_float_window) + m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); // when open by hyperlink it needs to show up // or after key 'T' windows doesn't appear @@ -1228,6 +1258,15 @@ void GLGizmoEmboss::draw_window() const auto &atlas = m_style_manager.get_atlas(); ImGui::Image(atlas.TexID, ImVec2(atlas.TexWidth, atlas.TexHeight)); #endif // SHOW_IMGUI_ATLAS + ImGui::SameLine(); + if (ImGui::Checkbox("##allow_float_window", &m_allow_float_window)) { + if (m_allow_float_window) + m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", ((m_allow_float_window) ? + _u8L("Fix settings possition"): + _u8L("Allow floating window near text")).c_str()); + } } void GLGizmoEmboss::draw_text_input() @@ -2648,34 +2687,6 @@ void GLGizmoEmboss::do_rotate(float relative_z_angle) m_parent.do_rotate(snapshot_name); } -void GLGizmoEmboss::set_fine_position() -{ - const Selection &selection = m_parent.get_selection(); - const Selection::IndicesList indices = selection.get_volume_idxs(); - // no selected volume - if (indices.empty()) return; - const GLVolume *volume = selection.get_volume(*indices.begin()); - // bad volume selected (e.g. deleted one) - if (volume == nullptr) return; - - const Camera &camera = wxGetApp().plater()->get_camera(); - Polygon hull = CameraUtils::create_hull2d(camera, *volume); - - const ImVec2 &windows_size = get_minimal_window_size(); - Size c_size = m_parent.get_canvas_size(); - ImVec2 canvas_size(c_size.get_width(), c_size.get_height()); - ImVec2 offset = ImGuiWrapper::suggest_location(windows_size, hull, canvas_size); - m_set_window_offset = offset; - return; - - Polygon rect({Point(offset.x, offset.y), - Point(offset.x + windows_size.x, offset.y), - Point(offset.x + windows_size.x, offset.y + windows_size.y), - Point(offset.x, offset.y + windows_size.y)}); - ImGuiWrapper::draw(hull); - ImGuiWrapper::draw(rect); -} - void GLGizmoEmboss::draw_advanced() { const auto &ff = m_style_manager.get_font_file_with_cache(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 1df32150f4..cf2ebfd449 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -125,12 +125,6 @@ private: void do_translate(const Vec3d& relative_move); void do_rotate(float relative_z_angle); - /// - /// Move window for edit emboss text near to embossed object - /// NOTE: embossed object must be selected - /// - void set_fine_position(); - /// /// Reversible input float with option to restor default value /// TODO: make more general, static and move to ImGuiWrapper @@ -220,9 +214,12 @@ private: GuiCfg() = default; }; std::optional m_gui_cfg; + bool m_is_advanced_edit_style = false; + + // when true window will appear near to text + bool m_allow_float_window = false; // setted only when wanted to use - not all the time std::optional m_set_window_offset; - bool m_is_advanced_edit_style = false; Emboss::StyleManager m_style_manager; From 2168d0935a5c9d36390eeac5e3508495a70dc0d5 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Mon, 5 Dec 2022 16:52:35 +0100 Subject: [PATCH 26/45] Allow letter "T" for open text gizmo --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 9 +++++++++ src/slic3r/GUI/KBShortcutsDialog.cpp | 1 + 2 files changed, 10 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 194c34ce28..ae5de7be00 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -526,6 +526,7 @@ bool GLGizmoEmboss::on_init() m_rotate_gizmo.init(); ColorRGBA gray_color(.6f, .6f, .6f, .3f); m_rotate_gizmo.set_highlight_color(gray_color); + m_shortcut_key = WXK_CONTROL_T; return true; } @@ -771,6 +772,14 @@ void GLGizmoEmboss::on_set_state() // Try(when exist) set text configuration by volume set_volume(priv::get_selected_volume(m_parent.get_selection())); + // when open window by "T" and no valid volume is selected, so Create new one + if (m_volume == nullptr) { + // reopen gizmo when new object is created + GLGizmoBase::m_state = GLGizmoBase::Off; + // start creating new object + create_volume(ModelVolumeType::MODEL_PART); + } + // change position of just opened emboss window if (m_allow_float_window) m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 04668b593a..cf23c85d37 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -154,6 +154,7 @@ void KBShortcutsDialog::fill_shortcuts() { "L", L("Gizmo FDM paint-on supports") }, { "P", L("Gizmo FDM paint-on seam") }, { "N", L("Gizmo Multi Material painting") }, + { "T", L("Gizmo Text emboss / engrave")}, { "Esc", L("Unselect gizmo or clear selection") }, { "K", L("Change camera type (perspective, orthographic)") }, { "B", L("Zoom to Bed") }, From 12e6948feff840c1d30a6af64c9b319f38e538b1 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 6 Dec 2022 14:33:34 +0100 Subject: [PATCH 27/45] Fix imgui want to have persistant io key during new window imgui assert - [link]{https://github.com/Prusa-Development/PrusaSlicerPrivate/blob/b1bfef44ba6bb72d925fc6c9f3133956be60e6ac/src/imgui/imgui.cpp#L7180} --- src/slic3r/GUI/ImGuiWrapper.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 7d68565e97..d7106173cb 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -312,9 +312,6 @@ void ImGuiWrapper::new_frame() init_font(true); } - ImGui::NewFrame(); - m_new_frame_open = true; - ImGuiIO& io = ImGui::GetIO(); // synchronize key states // when the application loses the focus it may happen that the key up event is not processed @@ -343,6 +340,9 @@ void ImGuiWrapper::new_frame() if (io.KeysDown[key] && keycode != WXK_NONE && !wxGetKeyState(keycode)) io.KeysDown[key] = false; } + + ImGui::NewFrame(); + m_new_frame_open = true; } void ImGuiWrapper::render() From 307d541eafa9119136bdcd7b033bb5cc241a52c1 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 6 Dec 2022 14:34:16 +0100 Subject: [PATCH 28/45] Add option to use camera orientation for text. --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 53 ++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index ae5de7be00..78547fee2d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1171,6 +1171,48 @@ void GLGizmoEmboss::discard_and_close() { // * Volume containing 3mf fix transformation - needs work around } +void use_camera_dir(const Camera &camera, GLCanvas3D &canvas) { + const Vec3d &cam_dir = camera.get_dir_forward(); + + Selection &sel = canvas.get_selection(); + if (sel.is_empty()) return; + assert(sel.get_volume_idxs().size() == 1); + GLVolume *vol = sel.get_volume(*sel.get_volume_idxs().begin()); + + // camera direction transformed into volume coordinate system + Vec3d cam_dir_tr = vol->world_matrix().inverse().linear() * cam_dir; + cam_dir_tr.normalize(); + + Vec3d emboss_dir(0., 0., -1.); + + // check wether cam_dir is already used + if (is_approx(cam_dir_tr, emboss_dir)) return; + + Transform3d vol_rot; + Transform3d vol_tr = vol->get_volume_transformation().get_matrix(); + // check whether cam_dir is opposit to emboss dir + if (is_approx(cam_dir_tr, -emboss_dir)) { + // rotate 180 DEG by y + vol_rot = Eigen::AngleAxis(M_PI_2, Vec3d(0., 1., 0.)); + } else { + // calc params for rotation + Vec3d axe = emboss_dir.cross(cam_dir_tr); + axe.normalize(); + double angle = std::acos(emboss_dir.dot(cam_dir_tr)); + vol_rot = Eigen::AngleAxis(angle, axe); + } + + Vec3d offset = vol_tr * Vec3d::Zero(); + Vec3d offset_inv = vol_rot.inverse() * offset; + Transform3d res = vol_tr * + Eigen::Translation(-offset) * + vol_rot * + Eigen::Translation(offset_inv); + //Transform3d res = vol_tr * vol_rot; + vol->set_volume_transformation(res); + priv::get_model_volume(vol, sel.get_model()->objects)->set_transformation(res); +} + void GLGizmoEmboss::draw_window() { #ifdef ALLOW_DEBUG_MODE @@ -1261,6 +1303,7 @@ void GLGizmoEmboss::draw_window() const auto &atlas = m_style_manager.get_atlas(); ImGui::Image(atlas.TexID, ImVec2(atlas.TexWidth, atlas.TexHeight)); #endif // SHOW_IMGUI_ATLAS + ImGui::SameLine(); if (ImGui::Checkbox("##allow_float_window", &m_allow_float_window)) { if (m_allow_float_window) @@ -1270,7 +1313,15 @@ void GLGizmoEmboss::draw_window() _u8L("Fix settings possition"): _u8L("Allow floating window near text")).c_str()); } -} + + ImGui::SameLine(); + if (ImGui::Button("use")) { + assert(priv::get_selected_volume(m_parent.get_selection()) == m_volume); + use_camera_dir(wxGetApp().plater()->get_camera(), m_parent); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", _u8L("Use camera direction for text orientation").c_str()); + } + } void GLGizmoEmboss::draw_text_input() { From c8503d583597afc8fd0bb76c101d0c4755079f78 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 6 Dec 2022 14:45:25 +0100 Subject: [PATCH 29/45] Add using camera direction together with use surface --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 78547fee2d..6481220825 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -1171,11 +1171,11 @@ void GLGizmoEmboss::discard_and_close() { // * Volume containing 3mf fix transformation - needs work around } -void use_camera_dir(const Camera &camera, GLCanvas3D &canvas) { +bool use_camera_dir(const Camera &camera, GLCanvas3D &canvas) { const Vec3d &cam_dir = camera.get_dir_forward(); Selection &sel = canvas.get_selection(); - if (sel.is_empty()) return; + if (sel.is_empty()) return false; assert(sel.get_volume_idxs().size() == 1); GLVolume *vol = sel.get_volume(*sel.get_volume_idxs().begin()); @@ -1186,7 +1186,7 @@ void use_camera_dir(const Camera &camera, GLCanvas3D &canvas) { Vec3d emboss_dir(0., 0., -1.); // check wether cam_dir is already used - if (is_approx(cam_dir_tr, emboss_dir)) return; + if (is_approx(cam_dir_tr, emboss_dir)) return false; Transform3d vol_rot; Transform3d vol_tr = vol->get_volume_transformation().get_matrix(); @@ -1211,6 +1211,7 @@ void use_camera_dir(const Camera &camera, GLCanvas3D &canvas) { //Transform3d res = vol_tr * vol_rot; vol->set_volume_transformation(res); priv::get_model_volume(vol, sel.get_model()->objects)->set_transformation(res); + return true; } void GLGizmoEmboss::draw_window() @@ -1317,7 +1318,10 @@ void GLGizmoEmboss::draw_window() ImGui::SameLine(); if (ImGui::Button("use")) { assert(priv::get_selected_volume(m_parent.get_selection()) == m_volume); - use_camera_dir(wxGetApp().plater()->get_camera(), m_parent); + const Camera& cam = wxGetApp().plater()->get_camera(); + bool use_surface = m_style_manager.get_style().prop.use_surface; + if (use_camera_dir(cam, m_parent) && use_surface) + process(); } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Use camera direction for text orientation").c_str()); } From d214159fc99f25588456245d932afe670cb8d842 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 7 Dec 2022 13:33:22 +0100 Subject: [PATCH 30/45] Bigger surface offset to be able see negative volume alligned with surface(constant used by @enrico ) --- src/slic3r/GUI/Jobs/EmbossJob.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index a5fa476722..0e7db94e87 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -679,7 +679,7 @@ OrthoProject3d priv::create_emboss_projection( bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut) { // Offset of clossed side to model - const float surface_offset = 1e-3f; // [in mm] + const float surface_offset = 0.015f; // [in mm] float front_move = (is_outside) ? emboss : surface_offset, back_move = -((is_outside) ? surface_offset : emboss); From c0a60d4637b8d2de96618735af6ba17306195dac Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Wed, 7 Dec 2022 13:34:36 +0100 Subject: [PATCH 31/45] scale for text height information showed to user --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 68 ++++++++++++++++++++----- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 6481220825..cbd9524a37 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -2563,23 +2563,60 @@ void GLGizmoEmboss::draw_style_edit() { bool use_inch = wxGetApp().app_config->get("use_inches") == "1"; const std::string revert_text_size = _u8L("Revert text size."); FontProp &font_prop = style.prop; - const float * def_size = exist_stored_style? - &m_style_manager.get_stored_style()->prop.size_in_mm : nullptr; - bool is_size_changed = false; + + const GLVolume* gl_vol = m_parent.get_selection().get_first_volume(); + Transform3d to_world = gl_vol->world_matrix(); + // Use fix of .3mf loaded tranformation when exist + if (m_volume->text_configuration->fix_3mf_tr.has_value()) + to_world = to_world * (*m_volume->text_configuration->fix_3mf_tr); + + Vec3d up_world = to_world.linear() * Vec3d(0., 1., 0.); + double norm_sq = up_world.squaredNorm(); + std::optional height_scale; + if (!is_approx(norm_sq, 1.)) + height_scale = sqrt(norm_sq); + + bool use_correction = use_inch || height_scale.has_value(); + const char *format = ((use_inch) ? "%.2f in" : "%.1f mm"); + float *size_ptr = nullptr; + float size_value; + const float * def_size_ptr = nullptr; + float def_value; if (use_inch) { - float size_in_inch = ObjectManipulation::mm_to_in * font_prop.size_in_mm; - float def_size_inch = exist_stored_style ? ObjectManipulation::mm_to_in * (*def_size) : 0.f; - if (def_size != nullptr) def_size = &def_size_inch; - if (rev_input(tr.size, size_in_inch, def_size, revert_text_size, 0.1f, 1.f, "%.2f in")) { - font_prop.size_in_mm = ObjectManipulation::in_to_mm * size_in_inch; - is_size_changed = true; + // calc value in inch + size_value = ObjectManipulation::mm_to_in * font_prop.size_in_mm; + if (exist_stored_style) { + def_value = ObjectManipulation::mm_to_in * (*def_size_ptr); + def_size_ptr = &def_value; + } + size_ptr = &size_value; + } + if (height_scale.has_value()) { + // use inch + if (size_ptr == nullptr) { + size_value = font_prop.size_in_mm; + size_ptr = &size_value; + } + size_value *= *height_scale; + if (def_size_ptr == nullptr) { + def_size_ptr = &m_style_manager.get_stored_style()->prop.size_in_mm; } - } else { - if (rev_input(tr.size, font_prop.size_in_mm, def_size, revert_text_size, 0.1f, 1.f, "%.1f mm")) - is_size_changed = true; } - if (is_size_changed) { + if (!use_correction){ + size_ptr = &font_prop.size_in_mm; + if (exist_stored_style) + def_size_ptr = &m_style_manager.get_stored_style()->prop.size_in_mm; + } + + assert(size_ptr != nullptr); + assert(exist_stored_style == (def_size_ptr != nullptr)); + if (rev_input(tr.size, *size_ptr, def_size_ptr, revert_text_size, 0.1f, 1.f, format)) { + if (use_correction) { + font_prop.size_in_mm = *size_ptr; + if (use_inch) font_prop.size_in_mm *= ObjectManipulation::in_to_mm; + if (height_scale.has_value()) font_prop.size_in_mm /= *height_scale; + } // size can't be zero or negative Limits::apply(font_prop.size_in_mm, limits.size_in_mm); @@ -2623,6 +2660,11 @@ void GLGizmoEmboss::draw_style_edit() { } #endif // SHOW_WX_WEIGHT_INPUT + Vec3d depth_world = to_world.linear() * Vec3d(0., 0., 1.); + double depth_sq = depth_world.squaredNorm(); + std::optional depth_scale; + if (!is_approx(depth_sq, 1.)) depth_scale = sqrt(depth_sq); + const std::string revert_emboss_depth = _u8L("Revert embossed depth."); const float *def_depth = exist_stored_style ? &m_style_manager.get_stored_style()->prop.emboss : nullptr; From a754535dfc1c155ebc74fd507c3235637de57b17 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 8 Dec 2022 08:54:17 +0100 Subject: [PATCH 32/45] Show correct depth and height for scaled object inside emboss gizmo --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 244 +++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 7 + 2 files changed, 137 insertions(+), 114 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 7a3b93d0ef..e9e0abe11b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -2449,6 +2449,43 @@ bool GLGizmoEmboss::rev_input(const std::string &name, return revertible(name, value, default_value, undo_tooltip, undo_offset, draw_offseted_input); } +bool GLGizmoEmboss::rev_input_mm(const std::string &name, + float &value, + const float *default_value_ptr, + const std::string &undo_tooltip, + float step, + float step_fast, + const char *format, + bool use_inch, + std::optional scale) +{ + // _variable which temporary keep value + float value_ = value; + float default_value_; + if (use_inch) { + // calc value in inch + value_ *= ObjectManipulation::mm_to_in; + if (default_value_ptr) { + default_value_ = ObjectManipulation::mm_to_in * (*default_value_ptr); + default_value_ptr = &default_value_; + } + } + if (scale.has_value()) + value_ *= *scale; + bool use_correction = use_inch || scale.has_value(); + if (rev_input(name, use_correction ? value_ : value, default_value_ptr, undo_tooltip, step, step_fast, format)) { + if (use_correction) { + value = value_; + if (use_inch) + value *= ObjectManipulation::in_to_mm; + if (scale.has_value()) + value /= *scale; + } + return true; + } + return false; +} + bool GLGizmoEmboss::rev_checkbox(const std::string &name, bool &value, const bool *default_value, @@ -2464,12 +2501,48 @@ bool GLGizmoEmboss::rev_checkbox(const std::string &name, undo_offset, draw_offseted_input); } +bool is_font_changed( + const wxFont &wx_font, const wxFont &wx_font_stored, + const FontProp &prop, const FontProp &prop_stored) +{ + // Exist change in face name? + if(wx_font_stored.GetFaceName() != wx_font.GetFaceName()) return true; + + const std::optional &skew = prop.skew; + bool is_italic = skew.has_value() || WxFontUtils::is_italic(wx_font); + const std::optional &skew_stored = prop_stored.skew; + bool is_stored_italic = skew_stored.has_value() || WxFontUtils::is_italic(wx_font_stored); + // is italic changed + if (is_italic != is_stored_italic) + return true; + + const std::optional &boldness = prop.boldness; + bool is_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font); + const std::optional &boldness_stored = prop_stored.boldness; + bool is_stored_bold = boldness_stored.has_value() || WxFontUtils::is_bold(wx_font_stored); + // is bold changed + return is_bold != is_stored_bold; +} + +bool is_font_changed(const StyleManager &mng) { + const std::optional &wx_font_opt = mng.get_wx_font(); + if (!wx_font_opt.has_value()) + return false; + if (!mng.exist_stored_style()) + return false; + const EmbossStyle *stored_style = mng.get_stored_style(); + if (stored_style == nullptr) + return false; + + const std::optional &wx_font_stored_opt = mng.get_stored_wx_font(); + if (!wx_font_stored_opt.has_value()) + return false; + + return is_font_changed(*wx_font_opt, *wx_font_stored_opt, mng.get_style().prop, stored_style->prop); +} + void GLGizmoEmboss::draw_style_edit() { - const GuiCfg::Translations &tr = m_gui_cfg->translations; - const std::optional &wx_font_opt = m_style_manager.get_wx_font(); - EmbossStyle &style = m_style_manager.get_style(); - assert(wx_font_opt.has_value()); if (!wx_font_opt.has_value()) { ImGui::TextColored(ImGuiWrapper::COL_ORANGE_DARK, "%s", _u8L("WxFont is not loaded properly.").c_str()); @@ -2477,33 +2550,9 @@ void GLGizmoEmboss::draw_style_edit() { } bool exist_stored_style = m_style_manager.exist_stored_style(); - bool is_font_changed = false; - if (exist_stored_style && wx_font_opt.has_value()) { - const wxFont &wx_font = *wx_font_opt; - const EmbossStyle *stored_style = m_style_manager.get_stored_style(); - assert(stored_style != nullptr); - const std::optional &stored_wx = m_style_manager.get_stored_wx_font(); - assert(stored_wx.has_value()); - bool is_font_face_changed = stored_wx->GetFaceName() != wx_font.GetFaceName(); - - const std::optional &skew = m_style_manager.get_font_prop().skew; - bool is_italic = skew.has_value() || WxFontUtils::is_italic(wx_font); - const std::optional &skew_stored = stored_style->prop.skew; - bool is_stored_italic = skew_stored.has_value() || WxFontUtils::is_italic(*stored_wx); - bool is_italic_changed = is_italic != is_stored_italic; - - const std::optional &boldness = m_style_manager.get_font_prop().boldness; - bool is_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font); - const std::optional &boldness_stored = stored_style->prop.boldness; - bool is_stored_bold = boldness_stored.has_value() || WxFontUtils::is_bold(*stored_wx); - bool is_bold_changed = is_bold != is_stored_bold; - - bool is_font_style_changed = is_italic_changed || is_bold_changed; - - is_font_changed = is_font_face_changed || is_font_style_changed; - } - - if (is_font_changed || !exist_stored_style) + bool exist_change_in_font = is_font_changed(m_style_manager); + const GuiCfg::Translations &tr = m_gui_cfg->translations; + if (exist_change_in_font || !exist_stored_style) ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr.font); else ImGuiWrapper::text(tr.font); @@ -2516,8 +2565,9 @@ void GLGizmoEmboss::draw_style_edit() { ImGui::SameLine(); if (draw_bold_button()) exist_change = true; - - if (is_font_changed) { + + EmbossStyle &style = m_style_manager.get_style(); + if (exist_change_in_font) { ImGui::SameLine(ImGui::GetStyle().FramePadding.x); if (draw_button(IconType::undo)) { const EmbossStyle *stored_style = m_style_manager.get_stored_style(); @@ -2539,7 +2589,6 @@ void GLGizmoEmboss::draw_style_edit() { } bool use_inch = wxGetApp().app_config->get("use_inches") == "1"; - const std::string revert_text_size = _u8L("Revert text size."); FontProp &font_prop = style.prop; const GLVolume* gl_vol = m_parent.get_selection().get_first_volume(); @@ -2554,63 +2603,7 @@ void GLGizmoEmboss::draw_style_edit() { if (!is_approx(norm_sq, 1.)) height_scale = sqrt(norm_sq); - bool use_correction = use_inch || height_scale.has_value(); - const char *format = ((use_inch) ? "%.2f in" : "%.1f mm"); - float *size_ptr = nullptr; - float size_value; - const float * def_size_ptr = nullptr; - float def_value; - if (use_inch) { - // calc value in inch - size_value = ObjectManipulation::mm_to_in * font_prop.size_in_mm; - if (exist_stored_style) { - def_value = ObjectManipulation::mm_to_in * (*def_size_ptr); - def_size_ptr = &def_value; - } - size_ptr = &size_value; - } - if (height_scale.has_value()) { - // use inch - if (size_ptr == nullptr) { - size_value = font_prop.size_in_mm; - size_ptr = &size_value; - } - size_value *= *height_scale; - if (def_size_ptr == nullptr) { - def_size_ptr = &m_style_manager.get_stored_style()->prop.size_in_mm; - } - } - - if (!use_correction){ - size_ptr = &font_prop.size_in_mm; - if (exist_stored_style) - def_size_ptr = &m_style_manager.get_stored_style()->prop.size_in_mm; - } - - assert(size_ptr != nullptr); - assert(exist_stored_style == (def_size_ptr != nullptr)); - if (rev_input(tr.size, *size_ptr, def_size_ptr, revert_text_size, 0.1f, 1.f, format)) { - if (use_correction) { - font_prop.size_in_mm = *size_ptr; - if (use_inch) font_prop.size_in_mm *= ObjectManipulation::in_to_mm; - if (height_scale.has_value()) font_prop.size_in_mm /= *height_scale; - } - // size can't be zero or negative - Limits::apply(font_prop.size_in_mm, limits.size_in_mm); - - // only different value need process - if (!is_approx(font_prop.size_in_mm, m_volume->text_configuration->style.prop.size_in_mm)) { - // store font size into path - if (style.type == WxFontUtils::get_actual_type()) { - if (wx_font_opt.has_value()) { - wxFont wx_font = *wx_font_opt; - wx_font.SetPointSize(static_cast(font_prop.size_in_mm)); - m_style_manager.set_wx_font(wx_font); - } - } - process(); - } - } + draw_height(height_scale, use_inch); #ifdef SHOW_WX_WEIGHT_INPUT if (wx_font.has_value()) { @@ -2642,30 +2635,53 @@ void GLGizmoEmboss::draw_style_edit() { double depth_sq = depth_world.squaredNorm(); std::optional depth_scale; if (!is_approx(depth_sq, 1.)) depth_scale = sqrt(depth_sq); - - const std::string revert_emboss_depth = _u8L("Revert embossed depth."); - const float *def_depth = exist_stored_style ? - &m_style_manager.get_stored_style()->prop.emboss : nullptr; - bool is_depth_changed = false; - if (use_inch) { - float depthj_in_inch = ObjectManipulation::mm_to_in * font_prop.emboss; - float def_depth_inch = exist_stored_style ? ObjectManipulation::mm_to_in * (*def_depth) : 0.f; - if (def_depth != nullptr) def_depth = &def_depth_inch; - if (rev_input(tr.depth, depthj_in_inch, def_depth, revert_emboss_depth, 0.1f, 0.25, "%.3f in")) { - font_prop.emboss = ObjectManipulation::in_to_mm * depthj_in_inch; - is_depth_changed = true; - } - } else { - if (rev_input(tr.depth, font_prop.emboss, def_depth, revert_emboss_depth, 0.1f, 0.25, "%.2f mm")) - is_depth_changed = true; - } - - if (is_depth_changed) { - Limits::apply(font_prop.emboss, limits.emboss); - process(); - } + draw_depth(depth_scale, use_inch); } +void GLGizmoEmboss::draw_height(std::optional scale, bool use_inch) +{ + float &value = m_style_manager.get_style().prop.size_in_mm; + const EmbossStyle* stored_style = m_style_manager.get_stored_style(); + const float *stored = ((stored_style)? &stored_style->prop.size_in_mm : nullptr); + const char *size_format = ((use_inch) ? "%.2f in" : "%.1f mm"); + const std::string revert_text_size = _u8L("Revert text size."); + const std::string& name = m_gui_cfg->translations.size; + if(rev_input_mm(name, value, stored, revert_text_size, 0.1f, 1.f, size_format, use_inch, scale)){ + // size can't be zero or negative + Limits::apply(value, limits.size_in_mm); + // only different value need process + if (!is_approx(value, m_volume->text_configuration->style.prop.size_in_mm)) { + // store font size into path + EmbossStyle &style = m_style_manager.get_style(); + if (style.type == WxFontUtils::get_actual_type()) { + const std::optional &wx_font_opt = m_style_manager.get_wx_font(); + if (wx_font_opt.has_value()) { + wxFont wx_font = *wx_font_opt; + wx_font.SetPointSize(static_cast(value)); + m_style_manager.set_wx_font(wx_font); + } + } + process(); + } + } +} + +void GLGizmoEmboss::draw_depth(std::optional scale, bool use_inch) +{ + float &value = m_style_manager.get_style().prop.emboss; + const EmbossStyle* stored_style = m_style_manager.get_stored_style(); + const float *stored = ((stored_style)? &stored_style->prop.emboss : nullptr); + const std::string revert_emboss_depth = _u8L("Revert embossed depth."); + const char *size_format = ((use_inch) ? "%.3f in" : "%.2f mm"); + const std::string name = m_gui_cfg->translations.depth; + if (rev_input_mm(name, value, stored, revert_emboss_depth, 0.1f, 1.f, size_format, use_inch, scale)) { + // size can't be zero or negative + Limits::apply(value, limits.emboss); + process(); + } +} + + bool GLGizmoEmboss::rev_slider(const std::string &name, std::optional& value, const std::optional *default_value, @@ -2741,7 +2757,7 @@ void GLGizmoEmboss::do_translate(const Vec3d &relative_move) selection.setup_cache(); selection.translate(relative_move, TransformationType::Local); - std::string snapshot_name; // empty meand no store undo / redo + std::string snapshot_name; // empty mean no store undo / redo // NOTE: it use L instead of _L macro because prefix _ is appended inside // function do_move // snapshot_name = L("Set surface distance"); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index 067690527b..2e6656ff71 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -111,6 +111,9 @@ private: void draw_font_preview(FaceName &face, bool is_visible); void draw_font_list(); void draw_style_edit(); + void draw_height(std::optional scale, bool use_inch); + void draw_depth(std::optional scale, bool use_inch); + bool draw_italic_button(); bool draw_bold_button(); void draw_advanced(); @@ -121,6 +124,10 @@ private: void do_translate(const Vec3d& relative_move); void do_rotate(float relative_z_angle); + bool rev_input_mm(const std::string &name, float &value, const float *default_value, + const std::string &undo_tooltip, float step, float step_fast, const char *format, + bool use_inch = false, std::optional scale = {}); + /// /// Reversible input float with option to restor default value /// TODO: make more general, static and move to ImGuiWrapper From d9e7195b61e71f045d8b2dc7f2ee1a591f302d5d Mon Sep 17 00:00:00 2001 From: David Kocik Date: Thu, 8 Dec 2022 10:15:23 +0100 Subject: [PATCH 33/45] Build volume as parameter in config wizard custom printer --- src/slic3r/GUI/ConfigWizard.cpp | 6206 ++++++++++++----------- src/slic3r/GUI/ConfigWizard_private.hpp | 9 + 2 files changed, 3143 insertions(+), 3072 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 490e8e54ab..71f59c7572 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1,3072 +1,3134 @@ -// FIXME: extract absolute units -> em - -#include "ConfigWizard_private.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _MSW_DARK_MODE -#include -#endif // _MSW_DARK_MODE - -#include "libslic3r/Platform.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Config.hpp" -#include "libslic3r/libslic3r.h" -#include "libslic3r/Model.hpp" -#include "libslic3r/Color.hpp" -#include "GUI.hpp" -#include "GUI_App.hpp" -#include "GUI_Utils.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "Field.hpp" -#include "DesktopIntegrationDialog.hpp" -#include "slic3r/Config/Snapshot.hpp" -#include "slic3r/Utils/PresetUpdater.hpp" -#include "format.hpp" -#include "MsgDialog.hpp" -#include "UnsavedChangesDialog.hpp" - -#if defined(__linux__) && defined(__WXGTK3__) -#define wxLinux_gtk3 true -#else -#define wxLinux_gtk3 false -#endif //defined(__linux__) && defined(__WXGTK3__) - -namespace Slic3r { -namespace GUI { - - -using Config::Snapshot; -using Config::SnapshotDB; - - -// Configuration data structures extensions needed for the wizard - -bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle) -{ - this->preset_bundle = std::make_unique(); - this->is_in_resources = ais_in_resources; - this->is_prusa_bundle = ais_prusa_bundle; - - std::string path_string = source_path.string(); - // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air. - auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle( - path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable); - UNUSED(config_substitutions); - // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed. - assert(config_substitutions.empty()); - auto first_vendor = preset_bundle->vendors.begin(); - if (first_vendor == preset_bundle->vendors.end()) { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string; - return false; - } - if (presets_loaded == 0) { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string; - return false; - } - - BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded; - this->vendor_profile = &first_vendor->second; - return true; -} - -Bundle::Bundle(Bundle &&other) - : preset_bundle(std::move(other.preset_bundle)) - , vendor_profile(other.vendor_profile) - , is_in_resources(other.is_in_resources) - , is_prusa_bundle(other.is_prusa_bundle) -{ - other.vendor_profile = nullptr; -} - -BundleMap BundleMap::load() -{ - BundleMap res; - - const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred(); - const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); - - auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); - auto prusa_bundle_rsrc = false; - if (! boost::filesystem::exists(prusa_bundle_path)) { - prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); - prusa_bundle_rsrc = true; - } - { - Bundle prusa_bundle; - if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true)) - res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); - } - - // Load the other bundles in the datadir/vendor directory - // and then additionally from resources/profiles. - bool is_in_resources = false; - for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) { - for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) { - if (Slic3r::is_ini_file(dir_entry)) { - std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part - - // Don't load this bundle if we've already loaded it. - if (res.find(id) != res.end()) { continue; } - - Bundle bundle; - if (bundle.load(dir_entry.path(), is_in_resources)) - res.emplace(std::move(id), std::move(bundle)); - } - } - - is_in_resources = true; - } - - return res; -} - -Bundle& BundleMap::prusa_bundle() -{ - auto it = find(PresetBundle::PRUSA_BUNDLE); - if (it == end()) { - throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); - } - - return it->second; -} - -const Bundle& BundleMap::prusa_bundle() const -{ - return const_cast(this)->prusa_bundle(); -} - - -// Printer model picker GUI control - -struct PrinterPickerEvent : public wxEvent -{ - std::string vendor_id; - std::string model_id; - std::string variant_name; - bool enable; - - PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) - : wxEvent(winid, eventType) - , vendor_id(std::move(vendor_id)) - , model_id(std::move(model_id)) - , variant_name(std::move(variant_name)) - , enable(enable) - {} - - virtual wxEvent *Clone() const - { - return new PrinterPickerEvent(*this); - } -}; - -wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent); - -const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png"; - -PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter) - : wxPanel(parent) - , vendor_id(vendor.id) - , width(0) -{ - wxGetApp().UpdateDarkUI(this); - const auto &models = vendor.models; - - auto *sizer = new wxBoxSizer(wxVERTICAL); - - const auto font_title = GetFont().MakeBold().Scaled(1.3f); - const auto font_name = GetFont().MakeBold(); - const auto font_alt_nozzle = GetFont().Scaled(0.9f); - - // wxGrid appends widgets by rows, but we need to construct them in columns. - // These vectors are used to hold the elements so that they can be appended in the right order. - std::vector titles; - std::vector bitmaps; - std::vector variants_panels; - - int max_row_width = 0; - int current_row_width = 0; - - bool is_variants = false; - - for (const auto &model : models) { - if (! filter(model)) { continue; } - - wxBitmap bitmap; - int bitmap_width = 0; - auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool { - if (wxFileExists(bitmap_file)) { - bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); - bitmap_width = bitmap.GetWidth(); - return true; - } - return false; - }; - if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") - % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png") - % vendor.id - % model.id; - load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); - } - } - auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - title->SetFont(font_name); - const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width); - title->Wrap(wrap_width); - - current_row_width += wrap_width; - if (titles.size() % max_cols == max_cols - 1) { - max_row_width = std::max(max_row_width, current_row_width); - current_row_width = 0; - } - - titles.push_back(title); - - auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); - bitmaps.push_back(bitmap_widget); - - auto *variants_panel = new wxPanel(this); - wxGetApp().UpdateDarkUI(variants_panel); - auto *variants_sizer = new wxBoxSizer(wxVERTICAL); - variants_panel->SetSizer(variants_sizer); - const auto model_id = model.id; - - for (size_t i = 0; i < model.variants.size(); i++) { - const auto &variant = model.variants[i]; - - const auto label = model.technology == ptFFF - ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str()) - : from_u8(model.name); - - if (i == 1) { - auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:")); - alt_label->SetFont(font_alt_nozzle); - variants_sizer->Add(alt_label, 0, wxBOTTOM, 3); - is_variants = true; - } - - auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name); - i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox); - - const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name); - cbox->SetValue(enabled); - - variants_sizer->Add(cbox, 0, wxBOTTOM, 3); - - cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) { - on_checkbox(cbox, event.IsChecked()); - }); - } - - variants_panels.push_back(variants_panel); - } - - width = std::max(max_row_width, current_row_width); - - const size_t cols = std::min(max_cols, titles.size()); - - auto *printer_grid = new wxFlexGridSizer(cols, 0, 20); - printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL); - - if (titles.size() > 0) { - const size_t odd_items = titles.size() % cols; - - for (size_t i = 0; i < titles.size() - odd_items; i += cols) { - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); } - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); } - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); } - - // Add separator space to multiliners - if (titles.size() > cols) { - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); } - } - } - if (odd_items > 0) { - const size_t rem = titles.size() - odd_items; - - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); } - for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); } - for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); } - } - } - - auto *title_sizer = new wxBoxSizer(wxHORIZONTAL); - if (! title.IsEmpty()) { - auto *title_widget = new wxStaticText(this, wxID_ANY, title); - title_widget->SetFont(font_title); - title_sizer->Add(title_widget); - } - title_sizer->AddStretchSpacer(); - - if (titles.size() > 1 || is_variants) { - // It only makes sense to add the All / None buttons if there's multiple printers - // All Standard button is added when there are more variants for at least one printer - auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard")); - auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); - auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); - if (is_variants) - sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); }); - sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); }); - sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); }); - if (is_variants) - title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING); - title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING); - title_sizer->Add(sel_none); - - wxGetApp().UpdateDarkUI(sel_all_std); - wxGetApp().UpdateDarkUI(sel_all); - wxGetApp().UpdateDarkUI(sel_none); - - // fill button indexes used later for buttons rescaling - if (is_variants) - m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() }; - else { - sel_all_std->Destroy(); - m_button_indexes = { sel_all->GetId(), sel_none->GetId() }; - } - } - - sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING); - sizer->Add(printer_grid); - - SetSizer(sizer); -} - -PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig) - : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; }) -{} - -void PrinterPicker::select_all(bool select, bool alternates) -{ - for (const auto &cb : cboxes) { - if (cb->GetValue() != select) { - cb->SetValue(select); - on_checkbox(cb, select); - } - } - - if (! select) { alternates = false; } - - for (const auto &cb : cboxes_alt) { - if (cb->GetValue() != alternates) { - cb->SetValue(alternates); - on_checkbox(cb, alternates); - } - } -} - -void PrinterPicker::select_one(size_t i, bool select) -{ - if (i < cboxes.size() && cboxes[i]->GetValue() != select) { - cboxes[i]->SetValue(select); - on_checkbox(cboxes[i], select); - } -} - -bool PrinterPicker::any_selected() const -{ - for (const auto &cb : cboxes) { - if (cb->GetValue()) { return true; } - } - - for (const auto &cb : cboxes_alt) { - if (cb->GetValue()) { return true; } - } - - return false; -} - -std::set PrinterPicker::get_selected_models() const -{ - std::set ret_set; - - for (const auto& cb : cboxes) - if (cb->GetValue()) - ret_set.emplace(cb->model); - - for (const auto& cb : cboxes_alt) - if (cb->GetValue()) - ret_set.emplace(cb->model); - - return ret_set; -} - -void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) -{ - PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); - AddPendingEvent(evt); -} - - -// Wizard page base - -ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent) - : wxPanel(parent->p->hscroll) - , parent(parent) - , shortname(std::move(shortname)) - , indent(indent) -{ - wxGetApp().UpdateDarkUI(this); - - auto *sizer = new wxBoxSizer(wxVERTICAL); - - auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - const auto font = GetFont().MakeBold().Scaled(1.5); - text->SetFont(font); - sizer->Add(text, 0, wxALIGN_LEFT, 0); - sizer->AddSpacer(10); - - content = new wxBoxSizer(wxVERTICAL); - sizer->Add(content, 1, wxEXPAND); - - SetSizer(sizer); - - // There is strange layout on Linux with GTK3, - // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861 - // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages - if (!wxLinux_gtk3) - this->Hide(); - - Bind(wxEVT_SIZE, [this](wxSizeEvent &event) { - this->Layout(); - event.Skip(); - }); -} - -ConfigWizardPage::~ConfigWizardPage() {} - -wxStaticText* ConfigWizardPage::append_text(wxString text) -{ - auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - widget->Wrap(WRAP_WIDTH); - widget->SetMinSize(wxSize(WRAP_WIDTH, -1)); - append(widget); - return widget; -} - -void ConfigWizardPage::append_spacer(int space) -{ - // FIXME: scaling - content->AddSpacer(space); -} - -// Wizard pages - -PageWelcome::PageWelcome(ConfigWizard *parent) - : ConfigWizardPage(parent, from_u8((boost::format( -#ifdef __APPLE__ - _utf8(L("Welcome to the %s Configuration Assistant")) -#else - _utf8(L("Welcome to the %s Configuration Wizard")) -#endif - ) % SLIC3R_APP_NAME).str()), _L("Welcome")) - , welcome_text(append_text(from_u8((boost::format( - _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print."))) - % SLIC3R_APP_NAME - % _utf8(ConfigWizard::name())).str()) - )) - , cbox_reset(append( - new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)")) - )) - , cbox_integrate(append( - new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system).")) - )) -{ - welcome_text->Hide(); - cbox_reset->Hide(); - cbox_integrate->Hide(); -} - -void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) -{ - const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY; - welcome_text->Show(data_empty); - cbox_reset->Show(!data_empty); -#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - if (!DesktopIntegrationDialog::is_integrated()) - cbox_integrate->Show(true); - else - cbox_integrate->Hide(); -#else - cbox_integrate->Hide(); -#endif -} - - -PagePrinters::PagePrinters(ConfigWizard *parent, - wxString title, - wxString shortname, - const VendorProfile &vendor, - unsigned indent, - Technology technology) - : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent) - , technology(technology) - , install(false) // only used for 3rd party vendors -{ - enum { - COL_SIZE = 200, - }; - - AppConfig *appconfig = &this->wizard_p()->appconfig_new; - - const auto families = vendor.families(); - for (const auto &family : families) { - const auto filter = [&](const VendorProfile::PrinterModel &model) { - return ((model.technology == ptFFF && technology & T_FFF) - || (model.technology == ptSLA && technology & T_SLA)) - && model.family == family; - }; - - if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) { - continue; - } - - const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str()); - auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter); - - picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) { - appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); - wizard_p()->on_printer_pick(this, evt); - }); - - append(new StaticLine(this)); - - append(picker); - printer_pickers.push_back(picker); - has_printers = true; - } - -} - -void PagePrinters::select_all(bool select, bool alternates) -{ - for (auto picker : printer_pickers) { - picker->select_all(select, alternates); - } -} - -int PagePrinters::get_width() const -{ - return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0, - [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); }); -} - -bool PagePrinters::any_selected() const -{ - for (const auto *picker : printer_pickers) { - if (picker->any_selected()) { return true; } - } - - return false; -} - -std::set PagePrinters::get_selected_models() -{ - std::set ret_set; - - for (const auto *picker : printer_pickers) - { - std::set tmp_models = picker->get_selected_models(); - ret_set.insert(tmp_models.begin(), tmp_models.end()); - } - - return ret_set; -} - -void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) -{ - 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) { - printer_pickers[0]->select_one(0, true); - } -} - - -const std::string PageMaterials::EMPTY; - -PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) - : ConfigWizardPage(parent, std::move(title), std::move(shortname)) - , materials(materials) - , list_printer(new StringList(this, wxLB_MULTIPLE)) - , list_type(new StringList(this)) - , list_vendor(new StringList(this)) - , list_profile(new PresetList(this)) -{ - append_spacer(VERTICAL_SPACING); - - const int em = parent->em_unit(); - const int list_h = 30*em; - - - list_printer->SetMinSize(wxSize(23*em, list_h)); - list_type->SetMinSize(wxSize(13*em, list_h)); - list_vendor->SetMinSize(wxSize(13*em, list_h)); - list_profile->SetMinSize(wxSize(23*em, list_h)); - - - - grid = new wxFlexGridSizer(4, em/2, em); - grid->AddGrowableCol(3, 1); - grid->AddGrowableRow(1, 1); - - grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:"))); - grid->Add(new wxStaticText(this, wxID_ANY, list1name)); - grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:"))); - grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:"))); - - grid->Add(list_printer, 0, wxEXPAND); - grid->Add(list_type, 0, wxEXPAND); - grid->Add(list_vendor, 0, wxEXPAND); - grid->Add(list_profile, 1, wxEXPAND); - - auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL); - auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); - auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); - btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2); - btn_sizer->Add(sel_none); - - wxGetApp().UpdateDarkUI(list_printer); - wxGetApp().UpdateDarkUI(list_type); - wxGetApp().UpdateDarkUI(list_vendor); - wxGetApp().UpdateDarkUI(sel_all); - wxGetApp().UpdateDarkUI(sel_none); - - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(btn_sizer, 0, wxALIGN_RIGHT); - - append(grid, 1, wxEXPAND); - - append_spacer(VERTICAL_SPACING); - - html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, - wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); - append(html_window, 0, wxEXPAND); - - list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt()); - }); - list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection()); - }); - list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection()); - }); - - list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); }); - list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); }); - - sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); }); - sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); }); - /* - Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();}); - - list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); }); - list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); }); - list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); }); - */ - reload_presets(); - set_compatible_printers_html_window(std::vector(), false); -} -void PageMaterials::on_paint() -{ -} -void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt) -{ - const wxClientDC dc(list_profile); - const wxPoint pos = evt.GetLogicalPosition(dc); - int item = list_profile->HitTest(pos); - on_material_hovered(item); -} -void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt) -{} -void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt) -{ - on_material_hovered(-1); -} -void PageMaterials::reload_presets() -{ - clear(); - - list_printer->append(_L("(All)"), &EMPTY); - //list_printer->SetLabelMarkup("bald"); - for (const Preset* printer : materials->printers) { - list_printer->append(printer->name, &printer->name); - } - sort_list_data(list_printer, true, false); - if (list_printer->GetCount() > 0) { - list_printer->SetSelection(0); - sel_printers_prev.Clear(); - sel_type_prev = wxNOT_FOUND; - sel_vendor_prev = wxNOT_FOUND; - update_lists(0, 0, 0); - } - - presets_loaded = true; -} - -void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers) -{ - const auto bgr_clr = -#if defined(__APPLE__) - html_window->GetParent()->GetBackgroundColour(); -#else -#if defined(_WIN32) - wxGetApp().get_window_default_clr(); -#else - wxSystemSettings::GetColour(wxSYS_COLOUR_MENU); -#endif -#endif - const auto text_clr = wxGetApp().get_label_clr_default(); - const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); - const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); - wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); - wxString text; - if (all_printers) { - wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); - text = wxString::Format( - "" - "" - "" - "" - "" - "%s

%s" - "
" - "
" - "" - "" - , bgr_clr_str - , text_clr_str - , first_line - , second_line - ); - } else { - 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( - "" - "" - "" - "" - "" - "%s

%s" - "" - "" - , bgr_clr_str - , text_clr_str - , first_line - , second_line); - for (size_t i = 0; i < printer_names.size(); ++i) - { - text += wxString::Format("", boost::nowide::widen(printer_names[i])); - if (i % 3 == 2) { - text += wxString::Format( - "" - ""); - } - } - text += wxString::Format( - "" - "
%s
" - "
" - "
" - "" - "" - ); - } - - wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this)); - const int fs = font.GetPointSize(); - int size[] = { fs,fs,fs,fs,fs,fs,fs }; - html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size); - html_window->SetPage(text); -} - -void PageMaterials::clear_compatible_printers_label() -{ - set_compatible_printers_html_window(std::vector(), false); -} - -void PageMaterials::on_material_hovered(int sel_material) -{ - -} - -void PageMaterials::on_material_highlighted(int sel_material) -{ - if (sel_material == last_hovered_item) - return; - if (sel_material == -1) { - clear_compatible_printers_label(); - return; - } - last_hovered_item = sel_material; - std::vector tabs; - tabs.push_back(std::string()); - tabs.push_back(std::string()); - tabs.push_back(std::string()); - //selected material string - std::string material_name = list_profile->get_data(sel_material); - // get material preset - const std::vector matching_materials = materials->get_presets_by_alias(material_name); - if (matching_materials.empty()) - { - clear_compatible_printers_label(); - return; - } - //find matching printers - std::vector names; - for (const Preset* printer : materials->printers) { - for (const Preset* material : matching_materials) { - if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { - names.push_back(printer->name); - break; - } - } - } - set_compatible_printers_html_window(names, names.size() == materials->printers.size()); -} - -void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/) -{ - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; - - wxArrayInt sel_printers; - int sel_printers_count = list_printer->GetSelections(sel_printers); - - // Does our wxWidgets version support operator== for wxArrayInt ? - // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614 -#if wxCHECK_VERSION(3, 1, 1) - if (sel_printers != sel_printers_prev) { -#else - auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) { - if (arr_first.GetCount() != arr_second.GetCount()) - return false; - for (size_t i = 0; i < arr_first.GetCount(); i++) - if (arr_first[i] != arr_second[i]) - return false; - return true; - }; - if (!are_equal(sel_printers, sel_printers_prev)) { -#endif - - // Refresh type list - list_type->Clear(); - list_type->append(_L("(All)"), &EMPTY); - if (sel_printers_count > 0) { - // If all is selected with other printers - // unselect "all" or all printers depending on last value - if (sel_printers[0] == 0 && sel_printers_count > 1) { - if (last_selected_printer == 0) { - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(0); - } else { - list_printer->SetSelection(0, false); - sel_printers_count = list_printer->GetSelections(sel_printers); - } - } - if (sel_printers[0] != 0) { - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - } else { - //clear selection except "ALL" - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(0); - sel_printers_count = list_printer->GetSelections(sel_printers); - - materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - sort_list_data(list_type, true, true); - } - - sel_printers_prev = sel_printers; - sel_type = 0; - sel_type_prev = wxNOT_FOUND; - list_type->SetSelection(sel_type); - list_profile->Clear(); - } - - if (sel_type != sel_type_prev) { - // Refresh vendor list - - // XXX: The vendor list is created with quadratic complexity here, - // but the number of vendors is going to be very small this shouldn't be a problem. - - list_vendor->Clear(); - list_vendor->append(_L("(All)"), &EMPTY); - if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) { - const std::string& type = list_type->get_data(sel_type); - // find printer preset - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) { - const std::string& vendor = this->materials->get_vendor(p); - if (list_vendor->find(vendor) == wxNOT_FOUND) { - list_vendor->append(vendor, &vendor); - } - }); - } - sort_list_data(list_vendor, true, false); - } - - sel_type_prev = sel_type; - sel_vendor = 0; - sel_vendor_prev = wxNOT_FOUND; - list_vendor->SetSelection(sel_vendor); - list_profile->Clear(); - } - - if (sel_vendor != sel_vendor_prev) { - // Refresh material list - - list_profile->Clear(); - clear_compatible_printers_label(); - if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) { - const std::string& type = list_type->get_data(sel_type); - const std::string& vendor = list_vendor->get_data(sel_vendor); - // finst printer preset - std::vector to_list; - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - - materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) { - const std::string& section = materials->appconfig_section(); - bool checked = wizard_p()->appconfig_new.has(section, p->name); - bool was_checked = false; - - int cur_i = list_profile->find(p->alias); - if (cur_i == wxNOT_FOUND) { - cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias); - to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked); - } - else { - was_checked = list_profile->IsChecked(cur_i); - to_list[cur_i].checked = checked || was_checked; - } - list_profile->Check(cur_i, checked || was_checked); - - /* Update preset selection in config. - * If one preset from aliases bundle is selected, - * than mark all presets with this aliases as selected - * */ - if (checked && !was_checked) - wizard_p()->update_presets_in_config(section, p->alias, true); - else if (!checked && was_checked) - wizard_p()->appconfig_new.set(section, p->name, "1"); - }); - } - sort_list_data(list_profile, to_list); - } - - sel_vendor_prev = sel_vendor; - } - wxGetApp().UpdateDarkUI(list_profile); -} - -void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering) -{ -// get data from list -// sort data -// first should be -// then prusa profiles -// then the rest -// in alphabetical order - - std::vector> prusa_profiles; - std::vector> other_profiles; - for (int i = 0 ; i < list->size(); ++i) { - const std::string& data = list->get_data(i); - if (data == EMPTY) // do not sort item - continue; - if (!material_type_ordering && data.find("Prusa") != std::string::npos) - prusa_profiles.push_back(data); - else - other_profiles.push_back(data); - } - if(material_type_ordering) { - - const ConfigOptionDef* def = print_config_def.get("filament_type"); - std::vectorenum_values = def->enum_values; - size_t end_of_sorted = 0; - for (size_t vals = 0; vals < enum_values.size(); vals++) { - for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++) - { - // find instead compare because PET vs PETG - if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) { - //swap - if(profs != end_of_sorted) { - std::reference_wrapper aux = other_profiles[end_of_sorted]; - other_profiles[end_of_sorted] = other_profiles[profs]; - other_profiles[profs] = aux; - } - end_of_sorted++; - break; - } - } - } - } else { - std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { - return a.get() < b.get(); - }); - std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { - return a.get() < b.get(); - }); - } - - list->Clear(); - if (add_All_item) - list->append(_L("(All)"), &EMPTY); - for (const auto& item : prusa_profiles) - list->append(item, &const_cast(item.get())); - for (const auto& item : other_profiles) - list->append(item, &const_cast(item.get())); -} - -void PageMaterials::sort_list_data(PresetList* list, const std::vector& data) -{ - // sort data - // then prusa profiles - // then the rest - // in alphabetical order - std::vector prusa_profiles; - std::vector other_profiles; - //for (int i = 0; i < data.size(); ++i) { - for (const auto& item : data) { - const std::string& name = item.name; - if (name.find("Prusa") != std::string::npos) - prusa_profiles.emplace_back(item); - else - other_profiles.emplace_back(item); - } - std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { - return a.name.get() < b.name.get(); - }); - std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { - return a.name.get() < b.name.get(); - }); - list->Clear(); - for (size_t i = 0; i < prusa_profiles.size(); ++i) { - list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast(prusa_profiles[i].name.get())); - list->Check(i, prusa_profiles[i].checked); - } - for (size_t i = 0; i < other_profiles.size(); ++i) { - list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast(other_profiles[i].name.get())); - list->Check(i + prusa_profiles.size(), other_profiles[i].checked); - } -} - -void PageMaterials::select_material(int i) -{ - const bool checked = list_profile->IsChecked(i); - - const std::string& alias_key = list_profile->get_data(i); - wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked); -} - -void PageMaterials::select_all(bool select) -{ - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; - - for (unsigned i = 0; i < list_profile->GetCount(); i++) { - const bool current = list_profile->IsChecked(i); - if (current != select) { - list_profile->Check(i, select); - select_material(i); - } - } -} - -void PageMaterials::clear() -{ - list_printer->Clear(); - list_type->Clear(); - list_vendor->Clear(); - list_profile->Clear(); - sel_printers_prev.Clear(); - sel_type_prev = wxNOT_FOUND; - sel_vendor_prev = wxNOT_FOUND; - presets_loaded = false; -} - -void PageMaterials::on_activate() -{ - if (! presets_loaded) { - wizard_p()->update_materials(materials->technology); - reload_presets(); - } - first_paint = true; -} - - -const char *PageCustom::default_profile_name = "My Settings"; - -PageCustom::PageCustom(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer")) -{ - cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile")); - tc_profile_name = new wxTextCtrl(this, wxID_ANY, default_profile_name); - auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:")); - - wxGetApp().UpdateDarkUI(tc_profile_name); - - tc_profile_name->Enable(false); - tc_profile_name->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &evt) { - if (tc_profile_name->GetValue().IsEmpty()) { - if (profile_name_prev.IsEmpty()) { tc_profile_name->SetValue(default_profile_name); } - else { tc_profile_name->SetValue(profile_name_prev); } - } else { - profile_name_prev = tc_profile_name->GetValue(); - } - evt.Skip(); - }); - - cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { - tc_profile_name->Enable(custom_wanted()); - wizard_p()->on_custom_setup(custom_wanted()); - - }); - - append(cb_custom); - append(label); - append(tc_profile_name); -} - -PageUpdate::PageUpdate(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates")) - , version_check(true) - , preset_update(true) -{ - const AppConfig *app_config = wxGetApp().app_config; - auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates")); - box_slic3r->SetValue(app_config->get("notify_release") != "none"); - append(box_slic3r); - append_text(wxString::Format(_L( - "If enabled, %s checks for new application versions online. When a new version becomes available, " - "a notification is displayed at the next application startup (never during program usage). " - "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME)); - - append_spacer(VERTICAL_SPACING); - - auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically")); - box_presets->SetValue(app_config->get("preset_update") == "1"); - append(box_presets); - append_text(wxString::Format(_L( - "If enabled, %s downloads updates of built-in system presets in the background." - "These updates are downloaded into a separate temporary location." - "When a new preset version becomes available it is offered at application startup."), SLIC3R_APP_NAME)); - const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings."); - auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold); - label_bold->SetFont(boldfont); - label_bold->Wrap(WRAP_WIDTH); - append(label_bold); - append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")); - - box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); }); - box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); -} - -PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk")) - , full_pathnames(false) -{ - auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files")); - box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1"); - append(box_pathnames); - append_text(_L( - "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" - "If not enabled, the Reload from disk command will ask to select each file using an open file dialog." - )); - - box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); }); -} - -#ifdef _WIN32 -PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Files association"), _L("Files association")) -{ - cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer")); - cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer")); -// cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer")); - - append(cb_3mf); - append(cb_stl); -// append(cb_gcode); -} -#endif // _WIN32 - -PageMode::PageMode(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("View mode"), _L("View mode")) -{ - append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n" - "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. " - "The other two offer progressively more sophisticated fine-tuning, " - "they are suitable for advanced and expert users, respectively.")); - - radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode")); - radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode")); - radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode")); - - std::string mode { "simple" }; - wxGetApp().app_config->get("", "view_mode", mode); - - if (mode == "advanced") { radio_advanced->SetValue(true); } - else if (mode == "expert") { radio_expert->SetValue(true); } - else { radio_simple->SetValue(true); } - - append(radio_simple); - append(radio_advanced); - append(radio_expert); - - append_text("\n" + _L("The size of the object can be specified in inches")); - check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); - check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); - append(check_inch); - - on_activate(); -} - -void PageMode::serialize_mode(AppConfig *app_config) const -{ - std::string mode = ""; - - if (radio_simple->GetValue()) { mode = "simple"; } - if (radio_advanced->GetValue()) { mode = "advanced"; } - if (radio_expert->GetValue()) { mode = "expert"; } - - app_config->set("view_mode", mode); - app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); -} - -PageVendors::PageVendors(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors")) -{ - const AppConfig &appconfig = this->wizard_p()->appconfig_new; - - append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":"); - - auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - for (const auto &pair : wizard_p()->bundles) { - const VendorProfile *vendor = pair.second.vendor_profile; - if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - - auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); - cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { - wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked()); - }); - - const auto &vendors = appconfig.vendors(); - const bool enabled = vendors.find(pair.first) != vendors.end(); - if (enabled) { - cbox->SetValue(true); - - auto pages = wizard_p()->pages_3rdparty.find(vendor->id); - wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created"); - - for (PagePrinters* page : { pages->second.first, pages->second.second }) - if (page) page->install = true; - } - - append(cbox); - } -} - -PageFirmware::PageFirmware(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1) - , gcode_opt(*print_config_def.get("gcode_flavor")) - , gcode_picker(nullptr) -{ - append_text(_L("Choose the type of firmware used by your printer.")); - append_text(_(gcode_opt.tooltip)); - - wxArrayString choices; - choices.Alloc(gcode_opt.enum_labels.size()); - for (const auto &label : gcode_opt.enum_labels) { - choices.Add(label); - } - - gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices); - wxGetApp().UpdateDarkUI(gcode_picker); - const auto &enum_values = gcode_opt.enum_values; - auto needle = enum_values.cend(); - if (gcode_opt.default_value) { - needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize()); - } - if (needle != enum_values.cend()) { - gcode_picker->SetSelection(needle - enum_values.cbegin()); - } else { - gcode_picker->SetSelection(0); - } - - append(gcode_picker); -} - -void PageFirmware::apply_custom_config(DynamicPrintConfig &config) -{ - auto sel = gcode_picker->GetSelection(); - if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) { - auto *opt = new ConfigOptionEnum(static_cast(sel)); - config.set_key_value("gcode_flavor", opt); - } -} - -PageBedShape::PageBedShape(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1) - , shape_panel(new BedShapePanel(this)) -{ - append_text(_L("Set the shape of your printer's bed.")); - - shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), - *wizard_p()->custom_config->option("bed_custom_texture"), - *wizard_p()->custom_config->option("bed_custom_model")); - - append(shape_panel); -} - -void PageBedShape::apply_custom_config(DynamicPrintConfig &config) -{ - const std::vector& points = shape_panel->get_shape(); - const std::string& custom_texture = shape_panel->get_custom_texture(); - const std::string& custom_model = shape_panel->get_custom_model(); - config.set_key_value("bed_shape", new ConfigOptionPoints(points)); - config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); - config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); -} - -static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) -{ - e.Skip(); - wxString str = ctrl->GetValue(); - - const char dec_sep = is_decimal_separator_point() ? '.' : ','; - const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; - // Replace the first incorrect separator in decimal number. - bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; - - double val = 0.0; - if (!str.ToDouble(&val)) { - if (val == 0.0) - val = def_value; - ctrl->SetValue(double_to_string(val)); - show_error(nullptr, _L("Invalid numeric input.")); - ctrl->SetFocus(); - } - else if (was_replaced) - ctrl->SetValue(double_to_string(val)); -} - -class DiamTextCtrl : public wxTextCtrl -{ -public: - DiamTextCtrl(wxWindow* parent) - { -#ifdef _WIN32 - long style = wxBORDER_SIMPLE; -#else - long style = 0; -#endif - Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style); - wxGetApp().UpdateDarkUI(this); - } - ~DiamTextCtrl() {} -}; - -PageDiameters::PageDiameters(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) - , diam_nozzle(new DiamTextCtrl(this)) - , diam_filam (new DiamTextCtrl(this)) -{ - auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value(); - wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); - diam_nozzle->SetValue(value); - - auto *default_filam = print_config_def.get("filament_diameter")->get_default_value(); - value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); - diam_filam->SetValue(value); - - diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId()); - diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId()); - - append_text(_L("Enter the diameter of your printer's hot end nozzle.")); - - auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5); - auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:")); - auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm")); - sizer_nozzle->AddGrowableCol(0, 1); - sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - sizer_nozzle->Add(diam_nozzle); - sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_nozzle); - - append_spacer(VERTICAL_SPACING); - - append_text(_L("Enter the diameter of your filament.")); - append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.")); - - auto *sizer_filam = new wxFlexGridSizer(3, 5, 5); - auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:")); - auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); - sizer_filam->AddGrowableCol(0, 1); - sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(diam_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_filam); -} - -void PageDiameters::apply_custom_config(DynamicPrintConfig &config) -{ - double val = 0.0; - diam_nozzle->GetValue().ToDouble(&val); - auto *opt_nozzle = new ConfigOptionFloats(1, val); - config.set_key_value("nozzle_diameter", opt_nozzle); - - val = 0.0; - diam_filam->GetValue().ToDouble(&val); - auto * opt_filam = new ConfigOptionFloats(1, val); - config.set_key_value("filament_diameter", opt_filam); - - auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { - char buf[64]; // locales don't matter here (sprintf/atof) - sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4); - config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); - }; - - set_extrusion_width("support_material_extrusion_width", 0.35); - set_extrusion_width("top_infill_extrusion_width", 0.40); - set_extrusion_width("first_layer_extrusion_width", 0.42); - - set_extrusion_width("extrusion_width", 0.45); - set_extrusion_width("perimeter_extrusion_width", 0.45); - set_extrusion_width("external_perimeter_extrusion_width", 0.45); - set_extrusion_width("infill_extrusion_width", 0.45); - set_extrusion_width("solid_infill_extrusion_width", 0.45); -} - -class SpinCtrlDouble: public wxSpinCtrlDouble -{ -public: - SpinCtrlDouble(wxWindow* parent) - { -#ifdef _WIN32 - long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE; -#else - long style = wxSP_ARROW_KEYS; -#endif - Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style); -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(this->GetText()); -#endif - this->Refresh(); - } - ~SpinCtrlDouble() {} -}; - -PageTemperatures::PageTemperatures(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1) - , spin_extr(new SpinCtrlDouble(this)) - , spin_bed (new SpinCtrlDouble(this)) -{ - spin_extr->SetIncrement(5.0); - const auto &def_extr = *print_config_def.get("temperature"); - spin_extr->SetRange(def_extr.min, def_extr.max); - auto *default_extr = def_extr.get_default_value(); - spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); - - spin_bed->SetIncrement(5.0); - const auto &def_bed = *print_config_def.get("bed_temperature"); - spin_bed->SetRange(def_bed.min, def_bed.max); - auto *default_bed = def_bed.get_default_value(); - spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0); - - append_text(_L("Enter the temperature needed for extruding your filament.")); - append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")); - - auto *sizer_extr = new wxFlexGridSizer(3, 5, 5); - auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:")); - auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C")); - sizer_extr->AddGrowableCol(0, 1); - sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL); - sizer_extr->Add(spin_extr); - sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_extr); - - append_spacer(VERTICAL_SPACING); - - append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")); - append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")); - - auto *sizer_bed = new wxFlexGridSizer(3, 5, 5); - auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:")); - auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C")); - sizer_bed->AddGrowableCol(0, 1); - sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL); - sizer_bed->Add(spin_bed); - sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_bed); -} - -void PageTemperatures::apply_custom_config(DynamicPrintConfig &config) -{ - auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue()); - config.set_key_value("temperature", opt_extr); - auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue()); - config.set_key_value("first_layer_temperature", opt_extr1st); - auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue()); - config.set_key_value("bed_temperature", opt_bed); - auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue()); - config.set_key_value("first_layer_bed_temperature", opt_bed1st); -} - - -// Index - -ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) - : wxPanel(parent) - , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192)) - , bullet_black(ScalableBitmap(parent, "bullet_black.png")) - , bullet_blue(ScalableBitmap(parent, "bullet_blue.png")) - , bullet_white(ScalableBitmap(parent, "bullet_white.png")) - , item_active(NO_ITEM) - , item_hover(NO_ITEM) - , last_page((size_t)-1) -{ -#ifndef __WXOSX__ - SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX -#endif //__WXOSX__ - SetMinSize(bg.GetSize()); - - const wxSize size = GetTextExtent("m"); - em_w = size.x; - em_h = size.y; - - Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); - Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); }); - Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this); - - Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) { - if (item_hover != -1) { - item_hover = -1; - Refresh(); - } - evt.Skip(); - }); - - Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) { - if (item_hover >= 0) { go_to(item_hover); } - }); -} - -wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent); - -void ConfigWizardIndex::add_page(ConfigWizardPage *page) -{ - last_page = items.size(); - items.emplace_back(Item { page->shortname, page->indent, page }); - Refresh(); -} - -void ConfigWizardIndex::add_label(wxString label, unsigned indent) -{ - items.emplace_back(Item { std::move(label), indent, nullptr }); - Refresh(); -} - -ConfigWizardPage* ConfigWizardIndex::active_page() const -{ - if (item_active >= items.size()) { return nullptr; } - - return items[item_active].page; -} - -void ConfigWizardIndex::go_prev() -{ - // Search for a preceiding item that is a page (not a label, ie. page != nullptr) - - if (item_active == NO_ITEM) { return; } - - for (size_t i = item_active; i > 0; i--) { - if (items[i - 1].page != nullptr) { - go_to(i - 1); - return; - } - } -} - -void ConfigWizardIndex::go_next() -{ - // Search for a next item that is a page (not a label, ie. page != nullptr) - - if (item_active == NO_ITEM) { return; } - - for (size_t i = item_active + 1; i < items.size(); i++) { - if (items[i].page != nullptr) { - go_to(i); - return; - } - } -} - -// This one actually performs the go-to op -void ConfigWizardIndex::go_to(size_t i) -{ - if (i != item_active - && i < items.size() - && items[i].page != nullptr) { - auto *new_active = items[i].page; - auto *former_active = active_page(); - if (former_active != nullptr) { - former_active->Hide(); - } - - item_active = i; - new_active->Show(); - - wxCommandEvent evt(EVT_INDEX_PAGE, GetId()); - AddPendingEvent(evt); - - Refresh(); - - new_active->on_activate(); - } -} - -void ConfigWizardIndex::go_to(const ConfigWizardPage *page) -{ - if (page == nullptr) { return; } - - for (size_t i = 0; i < items.size(); i++) { - if (items[i].page == page) { - go_to(i); - return; - } - } -} - -void ConfigWizardIndex::clear() -{ - auto *former_active = active_page(); - if (former_active != nullptr) { former_active->Hide(); } - - items.clear(); - item_active = NO_ITEM; -} - -void ConfigWizardIndex::on_paint(wxPaintEvent & evt) -{ - const auto size = GetClientSize(); - if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; } - - wxPaintDC dc(this); - - const auto bullet_w = bullet_black.GetWidth(); - const auto bullet_h = bullet_black.GetHeight(); - const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0; - const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0; - const int yinc = item_height(); - - int index_width = 0; - - unsigned y = 0; - for (size_t i = 0; i < items.size(); i++) { - const Item& item = items[i]; - unsigned x = em_w/2 + item.indent * em_w; - - if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { - dc.DrawBitmap(bullet_blue.get_bitmap(), x, y + yoff_icon, false); - } - else if (i < item_active) { dc.DrawBitmap(bullet_black.get_bitmap(), x, y + yoff_icon, false); } - else if (i > item_active) { dc.DrawBitmap(bullet_white.get_bitmap(), x, y + yoff_icon, false); } - - x += + bullet_w + em_w/2; - const auto text_size = dc.GetTextExtent(item.label); - dc.SetTextForeground(wxGetApp().get_label_clr_default()); - dc.DrawText(item.label, x, y + yoff_text); - - y += yinc; - index_width = std::max(index_width, (int)x + text_size.x); - } - - //draw logo - if (int y = size.y - bg.GetHeight(); y>=0) { - dc.DrawBitmap(bg.get_bitmap(), 0, y, false); - index_width = std::max(index_width, bg.GetWidth() + em_w / 2); - } - - if (GetMinSize().x < index_width) { - CallAfter([this, index_width]() { - SetMinSize(wxSize(index_width, GetMinSize().y)); - Refresh(); - }); - } -} - -void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt) -{ - const wxClientDC dc(this); - const wxPoint pos = evt.GetLogicalPosition(dc); - - const ssize_t item_hover_new = pos.y / item_height(); - - if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) { - item_hover = item_hover_new; - Refresh(); - } - - evt.Skip(); -} - -void ConfigWizardIndex::msw_rescale() -{ - const wxSize size = GetTextExtent("m"); - em_w = size.x; - em_h = size.y; - - SetMinSize(bg.GetSize()); - - Refresh(); -} - - -// Materials - -const std::string Materials::UNKNOWN = "(Unknown)"; - -void Materials::push(const Preset *preset) -{ - presets.emplace_back(preset); - types.insert(technology & T_FFF - ? Materials::get_filament_type(preset) - : Materials::get_material_type(preset)); -} - -void Materials::add_printer(const Preset* preset) -{ - printers.insert(preset); -} - -void Materials::clear() -{ - presets.clear(); - types.clear(); - printers.clear(); - compatibility_counter.clear(); -} - -const std::string& Materials::appconfig_section() const -{ - return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; -} - -const std::string& Materials::get_type(const Preset *preset) const -{ - return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset); -} - -const std::string& Materials::get_vendor(const Preset *preset) const -{ - return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset); -} - -const std::string& Materials::get_filament_type(const Preset *preset) -{ - const auto *opt = preset->config.opt("filament_type"); - if (opt != nullptr && opt->values.size() > 0) { - return opt->values[0]; - } else { - return UNKNOWN; - } -} - -const std::string& Materials::get_filament_vendor(const Preset *preset) -{ - const auto *opt = preset->config.opt("filament_vendor"); - return opt != nullptr ? opt->value : UNKNOWN; -} - -const std::string& Materials::get_material_type(const Preset *preset) -{ - const auto *opt = preset->config.opt("material_type"); - if (opt != nullptr) { - return opt->value; - } else { - return UNKNOWN; - } -} - -const std::string& Materials::get_material_vendor(const Preset *preset) -{ - const auto *opt = preset->config.opt("material_vendor"); - return opt != nullptr ? opt->value : UNKNOWN; -} - -// priv - -static const std::unordered_map> legacy_preset_map {{ - { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, - { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") }, - { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, - { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") }, - { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, - { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, -}}; - -void ConfigWizard::priv::load_pages() -{ - wxWindowUpdateLocker freeze_guard(q); - (void)freeze_guard; - - const ConfigWizardPage *former_active = index->active_page(); - - index->clear(); - - index->add_page(page_welcome); - - // Printers - if (!only_sla_mode) - index->add_page(page_fff); - index->add_page(page_msla); - if (!only_sla_mode) { - index->add_page(page_vendors); - for (const auto &pages : pages_3rdparty) { - for ( PagePrinters* page : { pages.second.first, pages.second.second }) - if (page && page->install) - index->add_page(page); - } - - index->add_page(page_custom); - if (page_custom->custom_wanted()) { - index->add_page(page_firmware); - index->add_page(page_bed); - index->add_page(page_diams); - index->add_page(page_temps); - } - - // Filaments & Materials - if (any_fff_selected) { index->add_page(page_filaments); } - } - if (any_sla_selected) { index->add_page(page_sla_materials); } - - // there should to be selected at least one printer - btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected); - - index->add_page(page_update); - index->add_page(page_reload_from_disk); -#ifdef _WIN32 - index->add_page(page_files_association); -#endif // _WIN32 - index->add_page(page_mode); - - index->go_to(former_active); // Will restore the active item/page if possible - - q->Layout(); -// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig - q->Refresh(); -} - -void ConfigWizard::priv::init_dialog_size() -{ - // Clamp the Wizard size based on screen dimensions - - const auto idx = wxDisplay::GetFromWindow(q); - wxDisplay display(idx != wxNOT_FOUND ? idx : 0u); - - const auto disp_rect = display.GetClientArea(); - wxRect window_rect( - disp_rect.x + disp_rect.width / 20, - disp_rect.y + disp_rect.height / 20, - 9*disp_rect.width / 10, - 9*disp_rect.height / 10); - - 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; - } - - q->SetSize(window_rect); -} - -void ConfigWizard::priv::load_vendors() -{ - bundles = BundleMap::load(); - - // Load up the set of vendors / models / variants the user has had enabled up till now - AppConfig *app_config = wxGetApp().app_config; - if (! app_config->legacy_datadir()) { - appconfig_new.set_vendors(*app_config); - } else { - // In case of legacy datadir, try to guess the preference based on the printer preset files that are present - const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; - for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir)) - if (Slic3r::is_ini_file(dir_entry)) { - auto needle = legacy_preset_map.find(dir_entry.path().filename().string()); - if (needle == legacy_preset_map.end()) { continue; } - - const auto &model = needle->second.first; - const auto &variant = needle->second.second; - appconfig_new.set_variant("PrusaResearch", model, variant, true); - } - } - - // Initialize the is_visible flag in printer Presets - for (auto &pair : bundles) { - pair.second.preset_bundle->load_installed_printers(appconfig_new); - } - - // Copy installed filaments and SLA material names from app_config to appconfig_new - // while resolving current names of profiles, which were renamed in the meantime. - for (PrinterTechnology technology : { ptFFF, ptSLA }) { - const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; - std::map section_new; - if (app_config->has_section(section_name)) { - const std::map §ion_old = app_config->get_section(section_name); - for (const auto& material_name_and_installed : section_old) - if (material_name_and_installed.second == "1") { - // Material is installed. Resolve it in bundles. - size_t num_found = 0; - const std::string &material_name = material_name_and_installed.first; - for (auto &bundle : bundles) { - const PresetCollection &materials = bundle.second.preset_bundle->materials(technology); - const Preset *preset = materials.find_preset(material_name); - if (preset == nullptr) { - // Not found. Maybe the material preset is there, bu it was was renamed? - const std::string *new_name = materials.get_preset_name_renamed(material_name); - if (new_name != nullptr) - preset = materials.find_preset(*new_name); - } - if (preset != nullptr) { - // Materal preset was found, mark it as installed. - section_new[preset->name] = "1"; - ++ num_found; - } - } - if (num_found == 0) - BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name; - else if (num_found > 1) - BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found; - } - } - appconfig_new.set_section(section_name, section_new); - }; -} - -void ConfigWizard::priv::add_page(ConfigWizardPage *page) -{ - const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0; - hscroll_sizer->Add(page, proportion, wxEXPAND); - all_pages.push_back(page); -} - -void ConfigWizard::priv::enable_next(bool enable) -{ - btn_next->Enable(enable); - btn_finish->Enable(enable); -} - -void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page) -{ - switch (start_page) { - case ConfigWizard::SP_PRINTERS: - index->go_to(page_fff); - btn_next->SetFocus(); - break; - case ConfigWizard::SP_FILAMENTS: - index->go_to(page_filaments); - btn_finish->SetFocus(); - break; - case ConfigWizard::SP_MATERIALS: - index->go_to(page_sla_materials); - btn_finish->SetFocus(); - break; - default: - index->go_to(page_welcome); - btn_next->SetFocus(); - break; - } -} - -void ConfigWizard::priv::create_3rdparty_pages() -{ - for (const auto &pair : bundles) { - const VendorProfile *vendor = pair.second.vendor_profile; - if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - - bool is_fff_technology = false; - bool is_sla_technology = false; - - for (auto& model: vendor->models) - { - if (!is_fff_technology && model.technology == ptFFF) - is_fff_technology = true; - if (!is_sla_technology && model.technology == ptSLA) - is_sla_technology = true; - } - - PagePrinters* pageFFF = nullptr; - PagePrinters* pageSLA = nullptr; - - if (is_fff_technology) { - pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF); - add_page(pageFFF); - } - - if (is_sla_technology) { - pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA); - add_page(pageSLA); - } - - pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}}); - } -} - -void ConfigWizard::priv::set_run_reason(RunReason run_reason) -{ - this->run_reason = run_reason; - for (auto &page : all_pages) { - page->set_run_reason(run_reason); - } -} - -void ConfigWizard::priv::update_materials(Technology technology) -{ - if (any_fff_selected && (technology & T_FFF)) { - filaments.clear(); - aliases_fff.clear(); - // Iterate filaments in all bundles - for (const auto &pair : bundles) { - for (const auto &filament : pair.second.preset_bundle->filaments) { - // Check if filament is already added - if (filaments.containts(&filament)) - continue; - // Iterate printers in all bundles - for (const auto &printer : pair.second.preset_bundle->printers) { - if (!printer.is_visible || printer.printer_technology() != ptFFF) - continue; - // Filter out inapplicable printers - if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) { - if (!filaments.containts(&filament)) { - filaments.push(&filament); - if (!filament.alias.empty()) - aliases_fff[filament.alias].insert(filament.name); - } - filaments.add_printer(&printer); - } - } - - } - } - // count compatible printers - for (const auto& preset : filaments.presets) { - - const auto filter = [preset](const std::pair element) { - return preset->alias == element.first; - }; - if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) { - continue; - } - std::vector idx_with_same_alias; - for (size_t i = 0; i < filaments.presets.size(); ++i) { - if (preset->alias == filaments.presets[i]->alias) - idx_with_same_alias.push_back(i); - } - size_t counter = 0; - for (const auto& printer : filaments.printers) { - if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) - continue; - bool compatible = false; - // Test otrher materials with same alias - for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { - const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]); - const Preset& prntr = *printer; - if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { - compatible = true; - break; - } - } - if (compatible) - counter++; - } - filaments.compatibility_counter.emplace_back(preset->alias, counter); - } - } - - if (any_sla_selected && (technology & T_SLA)) { - sla_materials.clear(); - aliases_sla.clear(); - - // Iterate SLA materials in all bundles - for (const auto &pair : bundles) { - for (const auto &material : pair.second.preset_bundle->sla_materials) { - // Check if material is already added - if (sla_materials.containts(&material)) - continue; - // Iterate printers in all bundles - // For now, we only allow the profiles to be compatible with another profiles inside the same bundle. - for (const auto& printer : pair.second.preset_bundle->printers) { - if(!printer.is_visible || printer.printer_technology() != ptSLA) - continue; - // Filter out inapplicable printers - if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) { - // Check if material is already added - if(!sla_materials.containts(&material)) { - sla_materials.push(&material); - if (!material.alias.empty()) - aliases_sla[material.alias].insert(material.name); - } - sla_materials.add_printer(&printer); - } - } - } - } - // count compatible printers - for (const auto& preset : sla_materials.presets) { - - const auto filter = [preset](const std::pair element) { - return preset->alias == element.first; - }; - if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) { - continue; - } - std::vector idx_with_same_alias; - for (size_t i = 0; i < sla_materials.presets.size(); ++i) { - if(preset->alias == sla_materials.presets[i]->alias) - idx_with_same_alias.push_back(i); - } - size_t counter = 0; - for (const auto& printer : sla_materials.printers) { - if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA) - continue; - bool compatible = false; - // Test otrher materials with same alias - for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { - const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]); - const Preset& prntr = *printer; - if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { - compatible = true; - break; - } - } - if (compatible) - counter++; - } - sla_materials.compatibility_counter.emplace_back(preset->alias, counter); - } - } -} - -void ConfigWizard::priv::on_custom_setup(const bool custom_wanted) -{ - custom_printer_selected = custom_wanted; - load_pages(); -} - -void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt) -{ - if (check_sla_selected() != any_sla_selected || - check_fff_selected() != any_fff_selected) { - any_fff_selected = check_fff_selected(); - any_sla_selected = check_sla_selected(); - - load_pages(); - } - - // Update the is_visible flag on relevant printer profiles - for (auto &pair : bundles) { - if (pair.first != evt.vendor_id) { continue; } - - for (auto &preset : pair.second.preset_bundle->printers) { - if (preset.config.opt_string("printer_model") == evt.model_id - && preset.config.opt_string("printer_variant") == evt.variant_name) { - preset.is_visible = evt.enable; - } - } - - // When a printer model is picked, but there is no material installed compatible with this printer model, - // install default materials for selected printer model silently. - check_and_install_missing_materials(page->technology, evt.model_id); - } - - if (page->technology & T_FFF) { - page_filaments->clear(); - } else if (page->technology & T_SLA) { - page_sla_materials->clear(); - } -} - -void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology) -{ - PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials; - for (const std::string& material : printer_model.default_materials) - appconfig_new.set(page_materials->materials->appconfig_section(), material, "1"); -} - -void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set &printer_models) -{ - PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials; - const std::string &appconfig_section = page_materials->materials->appconfig_section(); - - // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page. - // Filament is selected on same page for all printers of same technology. - /* - auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology) - { - const std::string vendor_id = page_printers->get_vendor_id(); - for (auto& pair : bundles) - if (pair.first == vendor_id) - for (const VendorProfile::PrinterModel *printer_model : printer_models) - for (const std::string &material : printer_model->default_materials) - appconfig_new.set(appconfig_section, material, "1"); - }; - - PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla; - select_default_materials_for_printer_page(page_printers, technology); - - for (const auto& printer : pages_3rdparty) - { - page_printers = technology & T_FFF ? printer.second.first : printer.second.second; - if (page_printers) - select_default_materials_for_printer_page(page_printers, technology); - } - */ - - // Iterate printer_models and select default materials. If none available -> msg to user. - std::vector models_without_default; - for (const VendorProfile::PrinterModel* printer_model : printer_models) { - if (printer_model->default_materials.empty()) { - models_without_default.emplace_back(printer_model); - } else { - for (const std::string& material : printer_model->default_materials) - appconfig_new.set(appconfig_section, material, "1"); - } - } - - if (!models_without_default.empty()) { - std::string printer_names = "\n\n"; - for (const VendorProfile::PrinterModel* printer_model : models_without_default) { - printer_names += printer_model->name + "\n"; - } - printer_names += "\n\n"; - std::string message = (technology & T_FFF ? - GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) : - GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names)); - MessageDialog msg(q, message, _L("Notice"), wxOK); - msg.ShowModal(); - } - - update_materials(technology); - ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets(); -} - -void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install) -{ - auto it = pages_3rdparty.find(vendor->id); - wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile"); - - for (PagePrinters* page : { it->second.first, it->second.second }) - if (page) { - if (page->install && !install) - page->select_all(false); - page->install = install; - // if some 3rd vendor is selected, select first printer for them - if (install) - page->printer_pickers[0]->select_one(0, true); - page->Layout(); - } - - load_pages(); -} - -bool ConfigWizard::priv::on_bnt_finish() -{ - wxBusyCursor wait; - /* When Filaments or Sla Materials pages are activated, - * materials for this pages are automaticaly updated and presets are reloaded. - * - * But, if _Finish_ button was clicked without activation of those pages - * (for example, just some printers were added/deleted), - * than last changes wouldn't be updated for filaments/materials. - * SO, do that before close of Wizard - */ - update_materials(T_ANY); - if (any_fff_selected) - page_filaments->reload_presets(); - if (any_sla_selected) - page_sla_materials->reload_presets(); - - // theres no need to check that filament is selected if we have only custom printer - if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true; - // check, that there is selected at least one filament/material - return check_and_install_missing_materials(T_ANY); -} - -// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed -// for each Printer preset of each Printer Model installed. -// -// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently. -// Otherwise the user is quieried whether to install the missing default materials or not. -// -// Return true if the tested Printer Models already had materials installed. -// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these -// respective Printer Models or not. -bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id) -{ - // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle, - // which is compatible with it. - const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion) - { - const std::map &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map(); - std::set printer_models_without_material; - for (const auto &pair : bundles) { - const PresetCollection &materials = pair.second.preset_bundle->materials(technology); - for (const auto &printer : pair.second.preset_bundle->printers) { - if (printer.is_visible && printer.printer_technology() == technology) { - const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer); - assert(printer_model != nullptr); - if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) && - printer_models_without_material.find(printer_model) == printer_models_without_material.end()) { - bool has_material = false; - for (const auto& preset : appconfig_presets) { - if (preset.second == "1") { - const Preset *material = materials.find_preset(preset.first, false); - if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) { - has_material = true; - break; - } - } - } - if (! has_material) - printer_models_without_material.insert(printer_model); - } - } - } - } - assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id); - return printer_models_without_material; - }; - - const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology) - { - //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO); - MessageDialog msg(q, message, _L("Notice"), wxYES_NO); - if (msg.ShowModal() == wxID_YES) - select_default_materials_for_printer_models(technology, printer_models); - }; - - const auto printer_model_list = [](const std::set &printer_models) -> wxString { - wxString out; - for (const VendorProfile::PrinterModel *printer_model : printer_models) { - wxString name = from_u8(printer_model->name); - out += "\t\t"; - out += name; - out += "\n"; - } - return out; - }; - - if (any_fff_selected && (technology & T_FFF)) { - std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS); - if (! printer_models_without_material.empty()) { - if (only_for_model_id.empty()) - ask_and_select_default_materials( - _L("The following FFF printer models have no filament selected:") + - "\n\n\t" + - printer_model_list(printer_models_without_material) + - "\n\n\t" + - _L("Do you want to select default filaments for these FFF printer models?"), - printer_models_without_material, - T_FFF); - else - select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF); - return false; - } - } - - if (any_sla_selected && (technology & T_SLA)) { - std::set printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS); - if (! printer_models_without_material.empty()) { - if (only_for_model_id.empty()) - ask_and_select_default_materials( - _L("The following SLA printer models have no materials selected:") + - "\n\n\t" + - printer_model_list(printer_models_without_material) + - "\n\n\t" + - _L("Do you want to select default SLA materials for these printer models?"), - printer_models_without_material, - T_SLA); - else - select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA); - return false; - } - } - - return true; -} - -static std::set get_new_added_presets(const std::map& old_data, const std::map& new_data) -{ - auto get_aliases = [](const std::map& data) { - std::set old_aliases; - for (auto item : data) { - const std::string& name = item.first; - size_t pos = name.find("@"); - old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1)); - } - return old_aliases; - }; - - std::set old_aliases = get_aliases(old_data); - std::set new_aliases = get_aliases(new_data); - std::set diff; - std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin())); - - return diff; -} - -static std::string get_first_added_preset(const std::map& old_data, const std::map& new_data) -{ - std::set diff = get_new_added_presets(old_data, new_data); - if (diff.empty()) - return std::string(); - return *diff.begin(); -} - -bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes) -{ - wxString header, caption = _L("Configuration is edited in ConfigWizard"); - const auto enabled_vendors = appconfig_new.vendors(); - const auto enabled_vendors_old = app_config->vendors(); - - bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model()); - PrinterTechnology preferred_pt = ptAny; - auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { - const auto config = enabled_vendors.find(bundle_name); - PrinterTechnology pt = ptAny; - if (config != enabled_vendors.end()) { - for (const auto& model : bundle.vendor_profile->models) { - if (const auto model_it = config->second.find(model.id); - model_it != config->second.end() && model_it->second.size() > 0) { - pt = model.technology; - const auto config_old = enabled_vendors_old.find(bundle_name); - if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) { - // if preferred printer model has SLA printer technology it's important to check the model for multi-part state - if (pt == ptSLA && suppress_sla_printer) - continue; - return pt; - } - - if (const auto model_it_old = config_old->second.find(model.id); - model_it_old == config_old->second.end() || model_it_old->second != model_it->second) { - // if preferred printer model has SLA printer technology it's important to check the model for multi-part state - if (pt == ptSLA && suppress_sla_printer) - continue; - return pt; - } - } - } - } - return pt; - }; - // Prusa printers are considered first, then 3rd party. - if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle()); - preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) { - for (const auto& bundle : bundles) { - if (bundle.second.is_prusa_bundle) { continue; } - if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny) - continue; - else if (preferred_pt == ptAny) - preferred_pt = pt; - if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer))) - break; - } - } - - if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption)) - return false; - - bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); - if (check_unsaved_preset_changes) - header = _L("All user presets will be deleted."); - int act_btns = ActionButtons::KEEP; - if (!check_unsaved_preset_changes) - act_btns |= ActionButtons::SAVE; - - // Install bundles from resources if needed: - std::vector install_bundles; - for (const auto &pair : bundles) { - if (! pair.second.is_in_resources) { continue; } - - if (pair.second.is_prusa_bundle) { - // Always install Prusa bundle, because it has a lot of filaments/materials - // likely to be referenced by other profiles. - install_bundles.emplace_back(pair.first); - continue; - } - - const auto vendor = enabled_vendors.find(pair.first); - if (vendor == enabled_vendors.end()) { continue; } - - size_t size_sum = 0; - for (const auto &model : vendor->second) { size_sum += model.second.size(); } - - if (size_sum > 0) { - // This vendor needs to be installed - install_bundles.emplace_back(pair.first); - } - } - if (!check_unsaved_preset_changes) - if ((check_unsaved_preset_changes = install_bundles.size() > 0)) - 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 - if (page_welcome->integrate_desktop()) - DesktopIntegrationDialog::perform_desktop_integration(); -#endif - - // Decide whether to create snapshot based on run_reason and the reset profile checkbox - bool snapshot = true; - Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE; - switch (run_reason) { - case ConfigWizard::RR_DATA_EMPTY: - snapshot = false; - break; - case ConfigWizard::RR_DATA_LEGACY: - snapshot = true; - break; - case ConfigWizard::RR_DATA_INCOMPAT: - // In this case snapshot has already been taken by - // PresetUpdater with the appropriate reason - snapshot = false; - break; - case ConfigWizard::RR_USER: - snapshot = page_welcome->reset_user_profile(); - snapshot_reason = Snapshot::SNAPSHOT_USER; - break; - } - - 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 && - !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - - if (install_bundles.size() > 0) { - // Install bundles from resources. - // Don't create snapshot - we've already done that above if applicable. - if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) - return false; - } else { - BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; - } - - if (page_welcome->reset_user_profile()) { - BOOST_LOG_TRIVIAL(info) << "Resetting user profiles..."; - preset_bundle->reset(true); - } - - std::string preferred_model; - std::string preferred_variant; - auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { - const auto config = enabled_vendors.find(bundle_name); - if (config == enabled_vendors.end()) - return std::string(); - for (const auto& model : bundle.vendor_profile->models) { - if (const auto model_it = config->second.find(model.id); - model_it != config->second.end() && model_it->second.size() > 0 && - preferred_pt == model.technology) { - variant = *model_it->second.begin(); - const auto config_old = enabled_vendors_old.find(bundle_name); - if (config_old == enabled_vendors_old.end()) - return model.id; - const auto model_it_old = config_old->second.find(model.id); - if (model_it_old == config_old->second.end()) - return model.id; - else if (model_it_old->second != model_it->second) { - for (const auto& var : model_it->second) - if (model_it_old->second.find(var) == model_it_old->second.end()) { - variant = var; - return model.id; - } - } - } - } - if (!variant.empty()) - variant.clear(); - return std::string(); - }; - // Prusa printers are considered first, then 3rd party. - if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant); - preferred_model.empty()) { - for (const auto& bundle : bundles) { - if (bundle.second.is_prusa_bundle) { continue; } - if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant); - !preferred_model.empty()) - break; - } - } - - // if unsaved changes was not cheched till this moment - if (!check_unsaved_preset_changes) { - if ((check_unsaved_preset_changes = !preferred_model.empty())) { - header = _L("A new Printer was installed and it will be activated."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) { - header = _L("Some Printers were uninstalled."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - } - - std::string first_added_filament, first_added_sla_material; - auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) { - if (appconfig_new.has_section(section_name)) { - // get first of new added preset names - const std::map& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map(); - first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name)); - } - }; - get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament); - get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material); - - // if unsaved changes was not cheched till this moment - if (!check_unsaved_preset_changes) { - if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) { - header = !first_added_filament.empty() ? - _L("A new filament was installed and it will be activated.") : - _L("A new SLA material was installed and it will be activated."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - else { - auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) { - return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map()) != appconfig_new.get_section(section_name); - }; - bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS); - bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS); - if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) { - header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - } - } - - // apply materials in app_config - for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS}) - app_config->set_section(section_name, appconfig_new.get_section(section_name)); - - app_config->set_vendors(appconfig_new); - - app_config->set("notify_release", page_update->version_check ? "all" : "none"); - app_config->set("preset_update", page_update->preset_update ? "1" : "0"); - app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); - -#ifdef _WIN32 - app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); - app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); -// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); - - if (wxGetApp().is_editor()) { - if (page_files_association->associate_3mf()) - wxGetApp().associate_3mf_files(); - if (page_files_association->associate_stl()) - wxGetApp().associate_stl_files(); - } -// else { -// if (page_files_association->associate_gcode()) -// wxGetApp().associate_gcode_files(); -// } -#endif // _WIN32 - - page_mode->serialize_mode(app_config); - - if (check_unsaved_preset_changes) - preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, - {preferred_model, preferred_variant, first_added_filament, first_added_sla_material}); - - 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)) - return false; - - page_firmware->apply_custom_config(*custom_config); - page_bed->apply_custom_config(*custom_config); - page_diams->apply_custom_config(*custom_config); - page_temps->apply_custom_config(*custom_config); - - copy_bed_model_and_texture_if_needed(*custom_config); - - const std::string profile_name = page_custom->profile_name(); - preset_bundle->load_config_from_wizard(profile_name, *custom_config); - } - - // Update the selections from the compatibilty. - preset_bundle->export_selections(*app_config); - - return true; -} -void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add) -{ - const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla; - - auto update = [this, add](const std::string& s, const std::string& key) { - assert(! s.empty()); - if (add) - appconfig_new.set(s, key, "1"); - else - appconfig_new.erase(s, key); - }; - - // add or delete presets had a same alias - auto it = aliases.find(alias_key); - if (it != aliases.end()) - for (const std::string& name : it->second) - update(section, name); -} - -bool ConfigWizard::priv::check_fff_selected() -{ - bool ret = page_fff->any_selected(); - for (const auto& printer: pages_3rdparty) - if (printer.second.first) // FFF page - ret |= printer.second.first->any_selected(); - return ret; -} - -bool ConfigWizard::priv::check_sla_selected() -{ - bool ret = page_msla->any_selected(); - for (const auto& printer: pages_3rdparty) - if (printer.second.second) // SLA page - ret |= printer.second.second->any_selected(); - return ret; -} - - -// Public - -ConfigWizard::ConfigWizard(wxWindow *parent) - : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) - , p(new priv(this)) -{ - this->SetFont(wxGetApp().normal_font()); - - p->load_vendors(); - p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ - "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", - })); - - p->index = new ConfigWizardIndex(this); - - auto *vsizer = new wxBoxSizer(wxVERTICAL); - auto *topsizer = new wxBoxSizer(wxHORIZONTAL); - auto* hline = new StaticLine(this); - p->btnsizer = new wxBoxSizer(wxHORIZONTAL); - - // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling. - // Later, we compare that to the size of the current screen and set minimum width based on that (see below). - p->hscroll = new wxScrolledWindow(this); - p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL); - p->hscroll->SetSizer(p->hscroll_sizer); - - topsizer->Add(p->index, 0, wxEXPAND); - topsizer->AddSpacer(INDEX_MARGIN); - topsizer->Add(p->hscroll, 1, wxEXPAND); - - p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers")); - p->btnsizer->Add(p->btn_sel_all); - - p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back")); - p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >")); - p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish")); - p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel")); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac - p->btnsizer->AddStretchSpacer(); - p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING); - - wxGetApp().UpdateDarkUI(p->btn_sel_all); - wxGetApp().UpdateDarkUI(p->btn_prev); - wxGetApp().UpdateDarkUI(p->btn_next); - wxGetApp().UpdateDarkUI(p->btn_finish); - wxGetApp().UpdateDarkUI(p->btn_cancel); - - const auto prusa_it = p->bundles.find("PrusaResearch"); - wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found"); - const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile; - - 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->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; - } - - 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->only_sla_mode && 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_sla_materials = new PageMaterials(this, &p->sla_materials, - _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") )); - - - p->add_page(p->page_update = new PageUpdate(this)); - p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); -#ifdef _WIN32 - p->add_page(p->page_files_association = new PageFilesAssociation(this)); -#endif // _WIN32 - p->add_page(p->page_mode = new PageMode(this)); - p->add_page(p->page_firmware = new PageFirmware(this)); - p->add_page(p->page_bed = new PageBedShape(this)); - p->add_page(p->page_diams = new PageDiameters(this)); - p->add_page(p->page_temps = new PageTemperatures(this)); - - p->load_pages(); - p->index->go_to(size_t{0}); - - vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); - vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING); - vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); - - SetSizer(vsizer); - SetSizerAndFit(vsizer); - - // We can now enable scrolling on hscroll - p->hscroll->SetScrollRate(30, 30); - - on_window_geometry(this, [this]() { - p->init_dialog_size(); - }); - - p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); }); - - p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) - { - // check, that there is selected at least one filament/material - ConfigWizardPage* active_page = this->p->index->active_page(); - if (// Leaving the filaments or SLA materials page and - (active_page == p->page_filaments || active_page == p->page_sla_materials) && - // some Printer models had no filament or SLA material selected. - ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology)) - // In that case don't leave the page and the function above queried the user whether to install default materials. - return; - this->p->index->go_next(); - }); - - p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) - { - if (p->on_bnt_finish()) - this->EndModal(wxID_OK); - }); - - p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { - p->any_sla_selected = true; - p->load_pages(); - p->page_fff->select_all(true, false); - p->page_msla->select_all(true, false); - p->index->go_to(p->page_mode); - }); - - p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) { - const bool is_last = p->index->active_is_last(); - p->btn_next->Show(! is_last); - if (is_last) - p->btn_finish->SetFocus(); - - Layout(); - }); - - if (wxLinux_gtk3) - this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) { - ConfigWizardPage* active_page = p->index->active_page(); - if (!active_page) - return; - for (auto page : p->all_pages) - if (page != active_page) - page->Hide(); - // update best size for the dialog after hiding of the non-active pages - vsizer->SetSizeHints(this); - // set initial dialog size - p->init_dialog_size(); - }); -} - -ConfigWizard::~ConfigWizard() {} - -bool ConfigWizard::run(RunReason reason, StartPage start_page) -{ - BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page; - - GUI_App &app = wxGetApp(); - - p->set_run_reason(reason); - p->set_start_page(start_page); - - if (ShowModal() == wxID_OK) { - bool apply_keeped_changes = false; - if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes)) - return false; - - if (apply_keeped_changes) - app.apply_keeped_preset_modifications(); - - app.app_config->set_legacy_datadir(false); - app.update_mode(); - app.obj_manipul()->update_ui_from_settings(); - BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; - return true; - } else { - BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled"; - return false; - } -} - -const wxString& ConfigWizard::name(const bool from_menu/* = false*/) -{ - // A different naming convention is used for the Wizard on Windows & GTK vs. OSX. - // Note: Don't call _() macro here. - // This function just return the current name according to the OS. - // Translation is implemented inside GUI_App::add_config_menu() -#if __APPLE__ - static const wxString config_wizard_name = L("Configuration Assistant"); - static const wxString config_wizard_name_menu = L("Configuration &Assistant"); -#else - static const wxString config_wizard_name = L("Configuration Wizard"); - static const wxString config_wizard_name_menu = L("Configuration &Wizard"); -#endif - return from_menu ? config_wizard_name_menu : config_wizard_name; -} - -void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect) -{ - p->index->msw_rescale(); - - const int em = em_unit(); - - msw_buttons_rescale(this, em, { wxID_APPLY, - wxID_CANCEL, - p->btn_sel_all->GetId(), - p->btn_next->GetId(), - p->btn_prev->GetId() }); - - for (auto printer_picker: p->page_fff->printer_pickers) - msw_buttons_rescale(this, em, printer_picker->get_button_indexes()); - - p->init_dialog_size(); - - Refresh(); -} - -void ConfigWizard::on_sys_color_changed() -{ - wxGetApp().UpdateDlgDarkUI(this); - Refresh(); -} - -} -} +// FIXME: extract absolute units -> em + +#include "ConfigWizard_private.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSW_DARK_MODE +#include +#endif // _MSW_DARK_MODE + +#include "libslic3r/Platform.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Model.hpp" +#include "libslic3r/Color.hpp" +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "GUI_Utils.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "Field.hpp" +#include "DesktopIntegrationDialog.hpp" +#include "slic3r/Config/Snapshot.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" +#include "format.hpp" +#include "MsgDialog.hpp" +#include "UnsavedChangesDialog.hpp" + +#if defined(__linux__) && defined(__WXGTK3__) +#define wxLinux_gtk3 true +#else +#define wxLinux_gtk3 false +#endif //defined(__linux__) && defined(__WXGTK3__) + +namespace Slic3r { +namespace GUI { + + +using Config::Snapshot; +using Config::SnapshotDB; + + +// Configuration data structures extensions needed for the wizard + +bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle) +{ + this->preset_bundle = std::make_unique(); + this->is_in_resources = ais_in_resources; + this->is_prusa_bundle = ais_prusa_bundle; + + std::string path_string = source_path.string(); + // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air. + auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle( + path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable); + UNUSED(config_substitutions); + // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed. + assert(config_substitutions.empty()); + auto first_vendor = preset_bundle->vendors.begin(); + if (first_vendor == preset_bundle->vendors.end()) { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string; + return false; + } + if (presets_loaded == 0) { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string; + return false; + } + + BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded; + this->vendor_profile = &first_vendor->second; + return true; +} + +Bundle::Bundle(Bundle &&other) + : preset_bundle(std::move(other.preset_bundle)) + , vendor_profile(other.vendor_profile) + , is_in_resources(other.is_in_resources) + , is_prusa_bundle(other.is_prusa_bundle) +{ + other.vendor_profile = nullptr; +} + +BundleMap BundleMap::load() +{ + BundleMap res; + + const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred(); + const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); + + auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + auto prusa_bundle_rsrc = false; + if (! boost::filesystem::exists(prusa_bundle_path)) { + prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + prusa_bundle_rsrc = true; + } + { + Bundle prusa_bundle; + if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true)) + res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); + } + + // Load the other bundles in the datadir/vendor directory + // and then additionally from resources/profiles. + bool is_in_resources = false; + for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) { + for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) { + if (Slic3r::is_ini_file(dir_entry)) { + std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part + + // Don't load this bundle if we've already loaded it. + if (res.find(id) != res.end()) { continue; } + + Bundle bundle; + if (bundle.load(dir_entry.path(), is_in_resources)) + res.emplace(std::move(id), std::move(bundle)); + } + } + + is_in_resources = true; + } + + return res; +} + +Bundle& BundleMap::prusa_bundle() +{ + auto it = find(PresetBundle::PRUSA_BUNDLE); + if (it == end()) { + throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); + } + + return it->second; +} + +const Bundle& BundleMap::prusa_bundle() const +{ + return const_cast(this)->prusa_bundle(); +} + + +// Printer model picker GUI control + +struct PrinterPickerEvent : public wxEvent +{ + std::string vendor_id; + std::string model_id; + std::string variant_name; + bool enable; + + PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) + : wxEvent(winid, eventType) + , vendor_id(std::move(vendor_id)) + , model_id(std::move(model_id)) + , variant_name(std::move(variant_name)) + , enable(enable) + {} + + virtual wxEvent *Clone() const + { + return new PrinterPickerEvent(*this); + } +}; + +wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent); + +const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png"; + +PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter) + : wxPanel(parent) + , vendor_id(vendor.id) + , width(0) +{ + wxGetApp().UpdateDarkUI(this); + const auto &models = vendor.models; + + auto *sizer = new wxBoxSizer(wxVERTICAL); + + const auto font_title = GetFont().MakeBold().Scaled(1.3f); + const auto font_name = GetFont().MakeBold(); + const auto font_alt_nozzle = GetFont().Scaled(0.9f); + + // wxGrid appends widgets by rows, but we need to construct them in columns. + // These vectors are used to hold the elements so that they can be appended in the right order. + std::vector titles; + std::vector bitmaps; + std::vector variants_panels; + + int max_row_width = 0; + int current_row_width = 0; + + bool is_variants = false; + + for (const auto &model : models) { + if (! filter(model)) { continue; } + + wxBitmap bitmap; + int bitmap_width = 0; + auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool { + if (wxFileExists(bitmap_file)) { + bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); + bitmap_width = bitmap.GetWidth(); + return true; + } + return false; + }; + if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { + if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { + BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") + % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png") + % vendor.id + % model.id; + load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); + } + } + auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + title->SetFont(font_name); + const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width); + title->Wrap(wrap_width); + + current_row_width += wrap_width; + if (titles.size() % max_cols == max_cols - 1) { + max_row_width = std::max(max_row_width, current_row_width); + current_row_width = 0; + } + + titles.push_back(title); + + auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); + bitmaps.push_back(bitmap_widget); + + auto *variants_panel = new wxPanel(this); + wxGetApp().UpdateDarkUI(variants_panel); + auto *variants_sizer = new wxBoxSizer(wxVERTICAL); + variants_panel->SetSizer(variants_sizer); + const auto model_id = model.id; + + for (size_t i = 0; i < model.variants.size(); i++) { + const auto &variant = model.variants[i]; + + const auto label = model.technology == ptFFF + ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str()) + : from_u8(model.name); + + if (i == 1) { + auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:")); + alt_label->SetFont(font_alt_nozzle); + variants_sizer->Add(alt_label, 0, wxBOTTOM, 3); + is_variants = true; + } + + auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name); + i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox); + + const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name); + cbox->SetValue(enabled); + + variants_sizer->Add(cbox, 0, wxBOTTOM, 3); + + cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) { + on_checkbox(cbox, event.IsChecked()); + }); + } + + variants_panels.push_back(variants_panel); + } + + width = std::max(max_row_width, current_row_width); + + const size_t cols = std::min(max_cols, titles.size()); + + auto *printer_grid = new wxFlexGridSizer(cols, 0, 20); + printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL); + + if (titles.size() > 0) { + const size_t odd_items = titles.size() % cols; + + for (size_t i = 0; i < titles.size() - odd_items; i += cols) { + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); } + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); } + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); } + + // Add separator space to multiliners + if (titles.size() > cols) { + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); } + } + } + if (odd_items > 0) { + const size_t rem = titles.size() - odd_items; + + for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); } + for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } + for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); } + for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } + for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); } + } + } + + auto *title_sizer = new wxBoxSizer(wxHORIZONTAL); + if (! title.IsEmpty()) { + auto *title_widget = new wxStaticText(this, wxID_ANY, title); + title_widget->SetFont(font_title); + title_sizer->Add(title_widget); + } + title_sizer->AddStretchSpacer(); + + if (titles.size() > 1 || is_variants) { + // It only makes sense to add the All / None buttons if there's multiple printers + // All Standard button is added when there are more variants for at least one printer + auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard")); + auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); + auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); + if (is_variants) + sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); }); + sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); }); + sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); }); + if (is_variants) + title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING); + title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING); + title_sizer->Add(sel_none); + + wxGetApp().UpdateDarkUI(sel_all_std); + wxGetApp().UpdateDarkUI(sel_all); + wxGetApp().UpdateDarkUI(sel_none); + + // fill button indexes used later for buttons rescaling + if (is_variants) + m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() }; + else { + sel_all_std->Destroy(); + m_button_indexes = { sel_all->GetId(), sel_none->GetId() }; + } + } + + sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING); + sizer->Add(printer_grid); + + SetSizer(sizer); +} + +PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig) + : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; }) +{} + +void PrinterPicker::select_all(bool select, bool alternates) +{ + for (const auto &cb : cboxes) { + if (cb->GetValue() != select) { + cb->SetValue(select); + on_checkbox(cb, select); + } + } + + if (! select) { alternates = false; } + + for (const auto &cb : cboxes_alt) { + if (cb->GetValue() != alternates) { + cb->SetValue(alternates); + on_checkbox(cb, alternates); + } + } +} + +void PrinterPicker::select_one(size_t i, bool select) +{ + if (i < cboxes.size() && cboxes[i]->GetValue() != select) { + cboxes[i]->SetValue(select); + on_checkbox(cboxes[i], select); + } +} + +bool PrinterPicker::any_selected() const +{ + for (const auto &cb : cboxes) { + if (cb->GetValue()) { return true; } + } + + for (const auto &cb : cboxes_alt) { + if (cb->GetValue()) { return true; } + } + + return false; +} + +std::set PrinterPicker::get_selected_models() const +{ + std::set ret_set; + + for (const auto& cb : cboxes) + if (cb->GetValue()) + ret_set.emplace(cb->model); + + for (const auto& cb : cboxes_alt) + if (cb->GetValue()) + ret_set.emplace(cb->model); + + return ret_set; +} + +void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) +{ + PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); + AddPendingEvent(evt); +} + + +// Wizard page base + +ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent) + : wxPanel(parent->p->hscroll) + , parent(parent) + , shortname(std::move(shortname)) + , indent(indent) +{ + wxGetApp().UpdateDarkUI(this); + + auto *sizer = new wxBoxSizer(wxVERTICAL); + + auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + const auto font = GetFont().MakeBold().Scaled(1.5); + text->SetFont(font); + sizer->Add(text, 0, wxALIGN_LEFT, 0); + sizer->AddSpacer(10); + + content = new wxBoxSizer(wxVERTICAL); + sizer->Add(content, 1, wxEXPAND); + + SetSizer(sizer); + + // There is strange layout on Linux with GTK3, + // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861 + // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages + if (!wxLinux_gtk3) + this->Hide(); + + Bind(wxEVT_SIZE, [this](wxSizeEvent &event) { + this->Layout(); + event.Skip(); + }); +} + +ConfigWizardPage::~ConfigWizardPage() {} + +wxStaticText* ConfigWizardPage::append_text(wxString text) +{ + auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + widget->Wrap(WRAP_WIDTH); + widget->SetMinSize(wxSize(WRAP_WIDTH, -1)); + append(widget); + return widget; +} + +void ConfigWizardPage::append_spacer(int space) +{ + // FIXME: scaling + content->AddSpacer(space); +} + +// Wizard pages + +PageWelcome::PageWelcome(ConfigWizard *parent) + : ConfigWizardPage(parent, from_u8((boost::format( +#ifdef __APPLE__ + _utf8(L("Welcome to the %s Configuration Assistant")) +#else + _utf8(L("Welcome to the %s Configuration Wizard")) +#endif + ) % SLIC3R_APP_NAME).str()), _L("Welcome")) + , welcome_text(append_text(from_u8((boost::format( + _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print."))) + % SLIC3R_APP_NAME + % _utf8(ConfigWizard::name())).str()) + )) + , cbox_reset(append( + new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)")) + )) + , cbox_integrate(append( + new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system).")) + )) +{ + welcome_text->Hide(); + cbox_reset->Hide(); + cbox_integrate->Hide(); +} + +void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) +{ + const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY; + welcome_text->Show(data_empty); + cbox_reset->Show(!data_empty); +#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + if (!DesktopIntegrationDialog::is_integrated()) + cbox_integrate->Show(true); + else + cbox_integrate->Hide(); +#else + cbox_integrate->Hide(); +#endif +} + + +PagePrinters::PagePrinters(ConfigWizard *parent, + wxString title, + wxString shortname, + const VendorProfile &vendor, + unsigned indent, + Technology technology) + : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent) + , technology(technology) + , install(false) // only used for 3rd party vendors +{ + enum { + COL_SIZE = 200, + }; + + AppConfig *appconfig = &this->wizard_p()->appconfig_new; + + const auto families = vendor.families(); + for (const auto &family : families) { + const auto filter = [&](const VendorProfile::PrinterModel &model) { + return ((model.technology == ptFFF && technology & T_FFF) + || (model.technology == ptSLA && technology & T_SLA)) + && model.family == family; + }; + + if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) { + continue; + } + + const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str()); + auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter); + + picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) { + appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); + wizard_p()->on_printer_pick(this, evt); + }); + + append(new StaticLine(this)); + + append(picker); + printer_pickers.push_back(picker); + has_printers = true; + } + +} + +void PagePrinters::select_all(bool select, bool alternates) +{ + for (auto picker : printer_pickers) { + picker->select_all(select, alternates); + } +} + +int PagePrinters::get_width() const +{ + return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0, + [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); }); +} + +bool PagePrinters::any_selected() const +{ + for (const auto *picker : printer_pickers) { + if (picker->any_selected()) { return true; } + } + + return false; +} + +std::set PagePrinters::get_selected_models() +{ + std::set ret_set; + + for (const auto *picker : printer_pickers) + { + std::set tmp_models = picker->get_selected_models(); + ret_set.insert(tmp_models.begin(), tmp_models.end()); + } + + return ret_set; +} + +void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) +{ + 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) { + printer_pickers[0]->select_one(0, true); + } +} + + +const std::string PageMaterials::EMPTY; + +PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) + : ConfigWizardPage(parent, std::move(title), std::move(shortname)) + , materials(materials) + , list_printer(new StringList(this, wxLB_MULTIPLE)) + , list_type(new StringList(this)) + , list_vendor(new StringList(this)) + , list_profile(new PresetList(this)) +{ + append_spacer(VERTICAL_SPACING); + + const int em = parent->em_unit(); + const int list_h = 30*em; + + + list_printer->SetMinSize(wxSize(23*em, list_h)); + list_type->SetMinSize(wxSize(13*em, list_h)); + list_vendor->SetMinSize(wxSize(13*em, list_h)); + list_profile->SetMinSize(wxSize(23*em, list_h)); + + + + grid = new wxFlexGridSizer(4, em/2, em); + grid->AddGrowableCol(3, 1); + grid->AddGrowableRow(1, 1); + + grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:"))); + grid->Add(new wxStaticText(this, wxID_ANY, list1name)); + grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:"))); + grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:"))); + + grid->Add(list_printer, 0, wxEXPAND); + grid->Add(list_type, 0, wxEXPAND); + grid->Add(list_vendor, 0, wxEXPAND); + grid->Add(list_profile, 1, wxEXPAND); + + auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL); + auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); + auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); + btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2); + btn_sizer->Add(sel_none); + + wxGetApp().UpdateDarkUI(list_printer); + wxGetApp().UpdateDarkUI(list_type); + wxGetApp().UpdateDarkUI(list_vendor); + wxGetApp().UpdateDarkUI(sel_all); + wxGetApp().UpdateDarkUI(sel_none); + + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(btn_sizer, 0, wxALIGN_RIGHT); + + append(grid, 1, wxEXPAND); + + append_spacer(VERTICAL_SPACING); + + html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, + wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); + append(html_window, 0, wxEXPAND); + + list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { + update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt()); + }); + list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { + update_lists(list_type->GetSelection(), list_vendor->GetSelection()); + }); + list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { + update_lists(list_type->GetSelection(), list_vendor->GetSelection()); + }); + + list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); }); + list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); }); + + sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); }); + sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); }); + /* + Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();}); + + list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); }); + list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); }); + list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); }); + */ + reload_presets(); + set_compatible_printers_html_window(std::vector(), false); +} +void PageMaterials::on_paint() +{ +} +void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt) +{ + const wxClientDC dc(list_profile); + const wxPoint pos = evt.GetLogicalPosition(dc); + int item = list_profile->HitTest(pos); + on_material_hovered(item); +} +void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt) +{} +void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt) +{ + on_material_hovered(-1); +} +void PageMaterials::reload_presets() +{ + clear(); + + list_printer->append(_L("(All)"), &EMPTY); + //list_printer->SetLabelMarkup("bald"); + for (const Preset* printer : materials->printers) { + list_printer->append(printer->name, &printer->name); + } + sort_list_data(list_printer, true, false); + if (list_printer->GetCount() > 0) { + list_printer->SetSelection(0); + sel_printers_prev.Clear(); + sel_type_prev = wxNOT_FOUND; + sel_vendor_prev = wxNOT_FOUND; + update_lists(0, 0, 0); + } + + presets_loaded = true; +} + +void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers) +{ + const auto bgr_clr = +#if defined(__APPLE__) + html_window->GetParent()->GetBackgroundColour(); +#else +#if defined(_WIN32) + wxGetApp().get_window_default_clr(); +#else + wxSystemSettings::GetColour(wxSYS_COLOUR_MENU); +#endif +#endif + const auto text_clr = wxGetApp().get_label_clr_default(); + const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); + const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); + wxString text; + if (all_printers) { + wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); + text = wxString::Format( + "" + "" + "" + "" + "" + "%s

%s" + "
" + "
" + "" + "" + , bgr_clr_str + , text_clr_str + , first_line + , second_line + ); + } else { + 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( + "" + "" + "" + "" + "" + "%s

%s" + "" + "" + , bgr_clr_str + , text_clr_str + , first_line + , second_line); + for (size_t i = 0; i < printer_names.size(); ++i) + { + text += wxString::Format("", boost::nowide::widen(printer_names[i])); + if (i % 3 == 2) { + text += wxString::Format( + "" + ""); + } + } + text += wxString::Format( + "" + "
%s
" + "
" + "
" + "" + "" + ); + } + + wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this)); + const int fs = font.GetPointSize(); + int size[] = { fs,fs,fs,fs,fs,fs,fs }; + html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html_window->SetPage(text); +} + +void PageMaterials::clear_compatible_printers_label() +{ + set_compatible_printers_html_window(std::vector(), false); +} + +void PageMaterials::on_material_hovered(int sel_material) +{ + +} + +void PageMaterials::on_material_highlighted(int sel_material) +{ + if (sel_material == last_hovered_item) + return; + if (sel_material == -1) { + clear_compatible_printers_label(); + return; + } + last_hovered_item = sel_material; + std::vector tabs; + tabs.push_back(std::string()); + tabs.push_back(std::string()); + tabs.push_back(std::string()); + //selected material string + std::string material_name = list_profile->get_data(sel_material); + // get material preset + const std::vector matching_materials = materials->get_presets_by_alias(material_name); + if (matching_materials.empty()) + { + clear_compatible_printers_label(); + return; + } + //find matching printers + std::vector names; + for (const Preset* printer : materials->printers) { + for (const Preset* material : matching_materials) { + if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { + names.push_back(printer->name); + break; + } + } + } + set_compatible_printers_html_window(names, names.size() == materials->printers.size()); +} + +void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/) +{ + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + + wxArrayInt sel_printers; + int sel_printers_count = list_printer->GetSelections(sel_printers); + + // Does our wxWidgets version support operator== for wxArrayInt ? + // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614 +#if wxCHECK_VERSION(3, 1, 1) + if (sel_printers != sel_printers_prev) { +#else + auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) { + if (arr_first.GetCount() != arr_second.GetCount()) + return false; + for (size_t i = 0; i < arr_first.GetCount(); i++) + if (arr_first[i] != arr_second[i]) + return false; + return true; + }; + if (!are_equal(sel_printers, sel_printers_prev)) { +#endif + + // Refresh type list + list_type->Clear(); + list_type->append(_L("(All)"), &EMPTY); + if (sel_printers_count > 0) { + // If all is selected with other printers + // unselect "all" or all printers depending on last value + if (sel_printers[0] == 0 && sel_printers_count > 1) { + if (last_selected_printer == 0) { + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(0); + } else { + list_printer->SetSelection(0, false); + sel_printers_count = list_printer->GetSelections(sel_printers); + } + } + if (sel_printers[0] != 0) { + for (int i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + } else { + //clear selection except "ALL" + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(0); + sel_printers_count = list_printer->GetSelections(sel_printers); + + materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + sort_list_data(list_type, true, true); + } + + sel_printers_prev = sel_printers; + sel_type = 0; + sel_type_prev = wxNOT_FOUND; + list_type->SetSelection(sel_type); + list_profile->Clear(); + } + + if (sel_type != sel_type_prev) { + // Refresh vendor list + + // XXX: The vendor list is created with quadratic complexity here, + // but the number of vendors is going to be very small this shouldn't be a problem. + + list_vendor->Clear(); + list_vendor->append(_L("(All)"), &EMPTY); + if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) { + const std::string& type = list_type->get_data(sel_type); + // find printer preset + for (int i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) { + const std::string& vendor = this->materials->get_vendor(p); + if (list_vendor->find(vendor) == wxNOT_FOUND) { + list_vendor->append(vendor, &vendor); + } + }); + } + sort_list_data(list_vendor, true, false); + } + + sel_type_prev = sel_type; + sel_vendor = 0; + sel_vendor_prev = wxNOT_FOUND; + list_vendor->SetSelection(sel_vendor); + list_profile->Clear(); + } + + if (sel_vendor != sel_vendor_prev) { + // Refresh material list + + list_profile->Clear(); + clear_compatible_printers_label(); + if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) { + const std::string& type = list_type->get_data(sel_type); + const std::string& vendor = list_vendor->get_data(sel_vendor); + // finst printer preset + std::vector to_list; + for (int i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + + materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) { + const std::string& section = materials->appconfig_section(); + bool checked = wizard_p()->appconfig_new.has(section, p->name); + bool was_checked = false; + + int cur_i = list_profile->find(p->alias); + if (cur_i == wxNOT_FOUND) { + cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias); + to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked); + } + else { + was_checked = list_profile->IsChecked(cur_i); + to_list[cur_i].checked = checked || was_checked; + } + list_profile->Check(cur_i, checked || was_checked); + + /* Update preset selection in config. + * If one preset from aliases bundle is selected, + * than mark all presets with this aliases as selected + * */ + if (checked && !was_checked) + wizard_p()->update_presets_in_config(section, p->alias, true); + else if (!checked && was_checked) + wizard_p()->appconfig_new.set(section, p->name, "1"); + }); + } + sort_list_data(list_profile, to_list); + } + + sel_vendor_prev = sel_vendor; + } + wxGetApp().UpdateDarkUI(list_profile); +} + +void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering) +{ +// get data from list +// sort data +// first should be +// then prusa profiles +// then the rest +// in alphabetical order + + std::vector> prusa_profiles; + std::vector> other_profiles; + for (int i = 0 ; i < list->size(); ++i) { + const std::string& data = list->get_data(i); + if (data == EMPTY) // do not sort item + continue; + if (!material_type_ordering && data.find("Prusa") != std::string::npos) + prusa_profiles.push_back(data); + else + other_profiles.push_back(data); + } + if(material_type_ordering) { + + const ConfigOptionDef* def = print_config_def.get("filament_type"); + std::vectorenum_values = def->enum_values; + size_t end_of_sorted = 0; + for (size_t vals = 0; vals < enum_values.size(); vals++) { + for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++) + { + // find instead compare because PET vs PETG + if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) { + //swap + if(profs != end_of_sorted) { + std::reference_wrapper aux = other_profiles[end_of_sorted]; + other_profiles[end_of_sorted] = other_profiles[profs]; + other_profiles[profs] = aux; + } + end_of_sorted++; + break; + } + } + } + } else { + std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() < b.get(); + }); + std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() < b.get(); + }); + } + + list->Clear(); + if (add_All_item) + list->append(_L("(All)"), &EMPTY); + for (const auto& item : prusa_profiles) + list->append(item, &const_cast(item.get())); + for (const auto& item : other_profiles) + list->append(item, &const_cast(item.get())); +} + +void PageMaterials::sort_list_data(PresetList* list, const std::vector& data) +{ + // sort data + // then prusa profiles + // then the rest + // in alphabetical order + std::vector prusa_profiles; + std::vector other_profiles; + //for (int i = 0; i < data.size(); ++i) { + for (const auto& item : data) { + const std::string& name = item.name; + if (name.find("Prusa") != std::string::npos) + prusa_profiles.emplace_back(item); + else + other_profiles.emplace_back(item); + } + std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { + return a.name.get() < b.name.get(); + }); + std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { + return a.name.get() < b.name.get(); + }); + list->Clear(); + for (size_t i = 0; i < prusa_profiles.size(); ++i) { + list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast(prusa_profiles[i].name.get())); + list->Check(i, prusa_profiles[i].checked); + } + for (size_t i = 0; i < other_profiles.size(); ++i) { + list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast(other_profiles[i].name.get())); + list->Check(i + prusa_profiles.size(), other_profiles[i].checked); + } +} + +void PageMaterials::select_material(int i) +{ + const bool checked = list_profile->IsChecked(i); + + const std::string& alias_key = list_profile->get_data(i); + wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked); +} + +void PageMaterials::select_all(bool select) +{ + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + + for (unsigned i = 0; i < list_profile->GetCount(); i++) { + const bool current = list_profile->IsChecked(i); + if (current != select) { + list_profile->Check(i, select); + select_material(i); + } + } +} + +void PageMaterials::clear() +{ + list_printer->Clear(); + list_type->Clear(); + list_vendor->Clear(); + list_profile->Clear(); + sel_printers_prev.Clear(); + sel_type_prev = wxNOT_FOUND; + sel_vendor_prev = wxNOT_FOUND; + presets_loaded = false; +} + +void PageMaterials::on_activate() +{ + if (! presets_loaded) { + wizard_p()->update_materials(materials->technology); + reload_presets(); + } + first_paint = true; +} + + +const char *PageCustom::default_profile_name = "My Settings"; + +PageCustom::PageCustom(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer")) +{ + cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile")); + tc_profile_name = new wxTextCtrl(this, wxID_ANY, default_profile_name); + auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:")); + + wxGetApp().UpdateDarkUI(tc_profile_name); + + tc_profile_name->Enable(false); + tc_profile_name->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &evt) { + if (tc_profile_name->GetValue().IsEmpty()) { + if (profile_name_prev.IsEmpty()) { tc_profile_name->SetValue(default_profile_name); } + else { tc_profile_name->SetValue(profile_name_prev); } + } else { + profile_name_prev = tc_profile_name->GetValue(); + } + evt.Skip(); + }); + + cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { + tc_profile_name->Enable(custom_wanted()); + wizard_p()->on_custom_setup(custom_wanted()); + + }); + + append(cb_custom); + append(label); + append(tc_profile_name); +} + +PageUpdate::PageUpdate(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates")) + , version_check(true) + , preset_update(true) +{ + const AppConfig *app_config = wxGetApp().app_config; + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates")); + box_slic3r->SetValue(app_config->get("notify_release") != "none"); + append(box_slic3r); + append_text(wxString::Format(_L( + "If enabled, %s checks for new application versions online. When a new version becomes available, " + "a notification is displayed at the next application startup (never during program usage). " + "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME)); + + append_spacer(VERTICAL_SPACING); + + auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically")); + box_presets->SetValue(app_config->get("preset_update") == "1"); + append(box_presets); + append_text(wxString::Format(_L( + "If enabled, %s downloads updates of built-in system presets in the background." + "These updates are downloaded into a separate temporary location." + "When a new preset version becomes available it is offered at application startup."), SLIC3R_APP_NAME)); + const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings."); + auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold); + label_bold->SetFont(boldfont); + label_bold->Wrap(WRAP_WIDTH); + append(label_bold); + append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")); + + box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); }); + box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); +} + +PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk")) + , full_pathnames(false) +{ + auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files")); + box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1"); + append(box_pathnames); + append_text(_L( + "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" + "If not enabled, the Reload from disk command will ask to select each file using an open file dialog." + )); + + box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); }); +} + +#ifdef _WIN32 +PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Files association"), _L("Files association")) +{ + cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer")); + cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer")); +// cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer")); + + append(cb_3mf); + append(cb_stl); +// append(cb_gcode); +} +#endif // _WIN32 + +PageMode::PageMode(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("View mode"), _L("View mode")) +{ + append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n" + "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. " + "The other two offer progressively more sophisticated fine-tuning, " + "they are suitable for advanced and expert users, respectively.")); + + radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode")); + radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode")); + radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode")); + + std::string mode { "simple" }; + wxGetApp().app_config->get("", "view_mode", mode); + + if (mode == "advanced") { radio_advanced->SetValue(true); } + else if (mode == "expert") { radio_expert->SetValue(true); } + else { radio_simple->SetValue(true); } + + append(radio_simple); + append(radio_advanced); + append(radio_expert); + + append_text("\n" + _L("The size of the object can be specified in inches")); + check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); + check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); + append(check_inch); + + on_activate(); +} + +void PageMode::serialize_mode(AppConfig *app_config) const +{ + std::string mode = ""; + + if (radio_simple->GetValue()) { mode = "simple"; } + if (radio_advanced->GetValue()) { mode = "advanced"; } + if (radio_expert->GetValue()) { mode = "expert"; } + + app_config->set("view_mode", mode); + app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); +} + +PageVendors::PageVendors(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors")) +{ + const AppConfig &appconfig = this->wizard_p()->appconfig_new; + + append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":"); + + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + for (const auto &pair : wizard_p()->bundles) { + const VendorProfile *vendor = pair.second.vendor_profile; + if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } + + auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); + cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { + wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked()); + }); + + const auto &vendors = appconfig.vendors(); + const bool enabled = vendors.find(pair.first) != vendors.end(); + if (enabled) { + cbox->SetValue(true); + + auto pages = wizard_p()->pages_3rdparty.find(vendor->id); + wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created"); + + for (PagePrinters* page : { pages->second.first, pages->second.second }) + if (page) page->install = true; + } + + append(cbox); + } +} + +PageFirmware::PageFirmware(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1) + , gcode_opt(*print_config_def.get("gcode_flavor")) + , gcode_picker(nullptr) +{ + append_text(_L("Choose the type of firmware used by your printer.")); + append_text(_(gcode_opt.tooltip)); + + wxArrayString choices; + choices.Alloc(gcode_opt.enum_labels.size()); + for (const auto &label : gcode_opt.enum_labels) { + choices.Add(label); + } + + gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices); + wxGetApp().UpdateDarkUI(gcode_picker); + const auto &enum_values = gcode_opt.enum_values; + auto needle = enum_values.cend(); + if (gcode_opt.default_value) { + needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize()); + } + if (needle != enum_values.cend()) { + gcode_picker->SetSelection(needle - enum_values.cbegin()); + } else { + gcode_picker->SetSelection(0); + } + + append(gcode_picker); +} + +void PageFirmware::apply_custom_config(DynamicPrintConfig &config) +{ + auto sel = gcode_picker->GetSelection(); + if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) { + auto *opt = new ConfigOptionEnum(static_cast(sel)); + config.set_key_value("gcode_flavor", opt); + } +} + +static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) +{ + e.Skip(); + wxString str = ctrl->GetValue(); + + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; + + double val = 0.0; + if (!str.ToDouble(&val)) { + if (val == 0.0) + val = def_value; + ctrl->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + // On Windows, this SetFocus creates an invisible marker. + //ctrl->SetFocus(); + } + else if (was_replaced) + ctrl->SetValue(double_to_string(val)); +} + +class DiamTextCtrl : public wxTextCtrl +{ +public: + DiamTextCtrl(wxWindow* parent) + { +#ifdef _WIN32 + long style = wxBORDER_SIMPLE; +#else + long style = 0; +#endif + Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style); + wxGetApp().UpdateDarkUI(this); + } + ~DiamTextCtrl() {} +}; + +PageBedShape::PageBedShape(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1) + , shape_panel(new BedShapePanel(this)) +{ + append_text(_L("Set the shape of your printer's bed.")); + + shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), + *wizard_p()->custom_config->option("bed_custom_texture"), + *wizard_p()->custom_config->option("bed_custom_model")); + + append(shape_panel); +} + +void PageBedShape::apply_custom_config(DynamicPrintConfig &config) +{ + const std::vector& points = shape_panel->get_shape(); + const std::string& custom_texture = shape_panel->get_custom_texture(); + const std::string& custom_model = shape_panel->get_custom_model(); + config.set_key_value("bed_shape", new ConfigOptionPoints(points)); + config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); + config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); +} + +PageBuildVolume::PageBuildVolume(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Build Volume"), _L("Build Volume"), 1) + , build_volume(new DiamTextCtrl(this)) +{ + append_text(_L("Set verctical size of your printer.")); + + wxString value = "200"; + build_volume->SetValue(value); + + build_volume->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { + double def_value = 200.0; + double max_value = 1200.0; + e.Skip(); + wxString str = build_volume->GetValue(); + + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; + + double val = 0.0; + if (!str.ToDouble(&val)) { + val = def_value; + build_volume->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + //build_volume->SetFocus(); + } else if (val < 0.0) { + val = def_value; + build_volume->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + //build_volume->SetFocus(); + } else if (val > max_value) { + val = max_value; + build_volume->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + //build_volume->SetFocus(); + } else if (was_replaced) + build_volume->SetValue(double_to_string(val)); + }, build_volume->GetId()); + + auto* sizer_volume = new wxFlexGridSizer(3, 5, 5); + auto* text_volume = new wxStaticText(this, wxID_ANY, _L("Max print height:")); + auto* unit_volume = new wxStaticText(this, wxID_ANY, _L("mm")); + sizer_volume->AddGrowableCol(0, 1); + sizer_volume->Add(text_volume, 0, wxALIGN_CENTRE_VERTICAL); + sizer_volume->Add(build_volume); + sizer_volume->Add(unit_volume, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_volume); +} + +void PageBuildVolume::apply_custom_config(DynamicPrintConfig& config) +{ + double val = 0.0; + build_volume->GetValue().ToDouble(&val); + auto* opt_volume = new ConfigOptionFloat(val); + config.set_key_value("max_print_height", opt_volume); +} + +PageDiameters::PageDiameters(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) + , diam_nozzle(new DiamTextCtrl(this)) + , diam_filam (new DiamTextCtrl(this)) +{ + auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value(); + wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + diam_nozzle->SetValue(value); + + auto *default_filam = print_config_def.get("filament_diameter")->get_default_value(); + value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + diam_filam->SetValue(value); + + diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId()); + diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId()); + + append_text(_L("Enter the diameter of your printer's hot end nozzle.")); + + auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5); + auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:")); + auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm")); + sizer_nozzle->AddGrowableCol(0, 1); + sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); + sizer_nozzle->Add(diam_nozzle); + sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_nozzle); + + append_spacer(VERTICAL_SPACING); + + append_text(_L("Enter the diameter of your filament.")); + append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.")); + + auto *sizer_filam = new wxFlexGridSizer(3, 5, 5); + auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:")); + auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); + sizer_filam->AddGrowableCol(0, 1); + sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); + sizer_filam->Add(diam_filam, 0, wxALIGN_CENTRE_VERTICAL); + sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_filam); +} + +void PageDiameters::apply_custom_config(DynamicPrintConfig &config) +{ + double val = 0.0; + diam_nozzle->GetValue().ToDouble(&val); + auto *opt_nozzle = new ConfigOptionFloats(1, val); + config.set_key_value("nozzle_diameter", opt_nozzle); + + val = 0.0; + diam_filam->GetValue().ToDouble(&val); + auto * opt_filam = new ConfigOptionFloats(1, val); + config.set_key_value("filament_diameter", opt_filam); + + auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { + char buf[64]; // locales don't matter here (sprintf/atof) + sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4); + config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); + }; + + set_extrusion_width("support_material_extrusion_width", 0.35); + set_extrusion_width("top_infill_extrusion_width", 0.40); + set_extrusion_width("first_layer_extrusion_width", 0.42); + + set_extrusion_width("extrusion_width", 0.45); + set_extrusion_width("perimeter_extrusion_width", 0.45); + set_extrusion_width("external_perimeter_extrusion_width", 0.45); + set_extrusion_width("infill_extrusion_width", 0.45); + set_extrusion_width("solid_infill_extrusion_width", 0.45); +} + +class SpinCtrlDouble: public wxSpinCtrlDouble +{ +public: + SpinCtrlDouble(wxWindow* parent) + { +#ifdef _WIN32 + long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE; +#else + long style = wxSP_ARROW_KEYS; +#endif + Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style); +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(this->GetText()); +#endif + this->Refresh(); + } + ~SpinCtrlDouble() {} +}; + +PageTemperatures::PageTemperatures(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1) + , spin_extr(new SpinCtrlDouble(this)) + , spin_bed (new SpinCtrlDouble(this)) +{ + spin_extr->SetIncrement(5.0); + const auto &def_extr = *print_config_def.get("temperature"); + spin_extr->SetRange(def_extr.min, def_extr.max); + auto *default_extr = def_extr.get_default_value(); + spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); + + spin_bed->SetIncrement(5.0); + const auto &def_bed = *print_config_def.get("bed_temperature"); + spin_bed->SetRange(def_bed.min, def_bed.max); + auto *default_bed = def_bed.get_default_value(); + spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0); + + append_text(_L("Enter the temperature needed for extruding your filament.")); + append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")); + + auto *sizer_extr = new wxFlexGridSizer(3, 5, 5); + auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:")); + auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C")); + sizer_extr->AddGrowableCol(0, 1); + sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL); + sizer_extr->Add(spin_extr); + sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_extr); + + append_spacer(VERTICAL_SPACING); + + append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")); + append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")); + + auto *sizer_bed = new wxFlexGridSizer(3, 5, 5); + auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:")); + auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C")); + sizer_bed->AddGrowableCol(0, 1); + sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL); + sizer_bed->Add(spin_bed); + sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_bed); +} + +void PageTemperatures::apply_custom_config(DynamicPrintConfig &config) +{ + auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue()); + config.set_key_value("temperature", opt_extr); + auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue()); + config.set_key_value("first_layer_temperature", opt_extr1st); + auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue()); + config.set_key_value("bed_temperature", opt_bed); + auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue()); + config.set_key_value("first_layer_bed_temperature", opt_bed1st); +} + + +// Index + +ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) + : wxPanel(parent) + , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192)) + , bullet_black(ScalableBitmap(parent, "bullet_black.png")) + , bullet_blue(ScalableBitmap(parent, "bullet_blue.png")) + , bullet_white(ScalableBitmap(parent, "bullet_white.png")) + , item_active(NO_ITEM) + , item_hover(NO_ITEM) + , last_page((size_t)-1) +{ +#ifndef __WXOSX__ + SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX +#endif //__WXOSX__ + SetMinSize(bg.GetSize()); + + const wxSize size = GetTextExtent("m"); + em_w = size.x; + em_h = size.y; + + Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); + Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); }); + Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this); + + Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) { + if (item_hover != -1) { + item_hover = -1; + Refresh(); + } + evt.Skip(); + }); + + Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) { + if (item_hover >= 0) { go_to(item_hover); } + }); +} + +wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent); + +void ConfigWizardIndex::add_page(ConfigWizardPage *page) +{ + last_page = items.size(); + items.emplace_back(Item { page->shortname, page->indent, page }); + Refresh(); +} + +void ConfigWizardIndex::add_label(wxString label, unsigned indent) +{ + items.emplace_back(Item { std::move(label), indent, nullptr }); + Refresh(); +} + +ConfigWizardPage* ConfigWizardIndex::active_page() const +{ + if (item_active >= items.size()) { return nullptr; } + + return items[item_active].page; +} + +void ConfigWizardIndex::go_prev() +{ + // Search for a preceiding item that is a page (not a label, ie. page != nullptr) + + if (item_active == NO_ITEM) { return; } + + for (size_t i = item_active; i > 0; i--) { + if (items[i - 1].page != nullptr) { + go_to(i - 1); + return; + } + } +} + +void ConfigWizardIndex::go_next() +{ + // Search for a next item that is a page (not a label, ie. page != nullptr) + + if (item_active == NO_ITEM) { return; } + + for (size_t i = item_active + 1; i < items.size(); i++) { + if (items[i].page != nullptr) { + go_to(i); + return; + } + } +} + +// This one actually performs the go-to op +void ConfigWizardIndex::go_to(size_t i) +{ + if (i != item_active + && i < items.size() + && items[i].page != nullptr) { + auto *new_active = items[i].page; + auto *former_active = active_page(); + if (former_active != nullptr) { + former_active->Hide(); + } + + item_active = i; + new_active->Show(); + + wxCommandEvent evt(EVT_INDEX_PAGE, GetId()); + AddPendingEvent(evt); + + Refresh(); + + new_active->on_activate(); + } +} + +void ConfigWizardIndex::go_to(const ConfigWizardPage *page) +{ + if (page == nullptr) { return; } + + for (size_t i = 0; i < items.size(); i++) { + if (items[i].page == page) { + go_to(i); + return; + } + } +} + +void ConfigWizardIndex::clear() +{ + auto *former_active = active_page(); + if (former_active != nullptr) { former_active->Hide(); } + + items.clear(); + item_active = NO_ITEM; +} + +void ConfigWizardIndex::on_paint(wxPaintEvent & evt) +{ + const auto size = GetClientSize(); + if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; } + + wxPaintDC dc(this); + + const auto bullet_w = bullet_black.GetWidth(); + const auto bullet_h = bullet_black.GetHeight(); + const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0; + const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0; + const int yinc = item_height(); + + int index_width = 0; + + unsigned y = 0; + for (size_t i = 0; i < items.size(); i++) { + const Item& item = items[i]; + unsigned x = em_w/2 + item.indent * em_w; + + if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { + dc.DrawBitmap(bullet_blue.get_bitmap(), x, y + yoff_icon, false); + } + else if (i < item_active) { dc.DrawBitmap(bullet_black.get_bitmap(), x, y + yoff_icon, false); } + else if (i > item_active) { dc.DrawBitmap(bullet_white.get_bitmap(), x, y + yoff_icon, false); } + + x += + bullet_w + em_w/2; + const auto text_size = dc.GetTextExtent(item.label); + dc.SetTextForeground(wxGetApp().get_label_clr_default()); + dc.DrawText(item.label, x, y + yoff_text); + + y += yinc; + index_width = std::max(index_width, (int)x + text_size.x); + } + + //draw logo + if (int y = size.y - bg.GetHeight(); y>=0) { + dc.DrawBitmap(bg.get_bitmap(), 0, y, false); + index_width = std::max(index_width, bg.GetWidth() + em_w / 2); + } + + if (GetMinSize().x < index_width) { + CallAfter([this, index_width]() { + SetMinSize(wxSize(index_width, GetMinSize().y)); + Refresh(); + }); + } +} + +void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt) +{ + const wxClientDC dc(this); + const wxPoint pos = evt.GetLogicalPosition(dc); + + const ssize_t item_hover_new = pos.y / item_height(); + + if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) { + item_hover = item_hover_new; + Refresh(); + } + + evt.Skip(); +} + +void ConfigWizardIndex::msw_rescale() +{ + const wxSize size = GetTextExtent("m"); + em_w = size.x; + em_h = size.y; + + SetMinSize(bg.GetSize()); + + Refresh(); +} + + +// Materials + +const std::string Materials::UNKNOWN = "(Unknown)"; + +void Materials::push(const Preset *preset) +{ + presets.emplace_back(preset); + types.insert(technology & T_FFF + ? Materials::get_filament_type(preset) + : Materials::get_material_type(preset)); +} + +void Materials::add_printer(const Preset* preset) +{ + printers.insert(preset); +} + +void Materials::clear() +{ + presets.clear(); + types.clear(); + printers.clear(); + compatibility_counter.clear(); +} + +const std::string& Materials::appconfig_section() const +{ + return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; +} + +const std::string& Materials::get_type(const Preset *preset) const +{ + return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset); +} + +const std::string& Materials::get_vendor(const Preset *preset) const +{ + return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset); +} + +const std::string& Materials::get_filament_type(const Preset *preset) +{ + const auto *opt = preset->config.opt("filament_type"); + if (opt != nullptr && opt->values.size() > 0) { + return opt->values[0]; + } else { + return UNKNOWN; + } +} + +const std::string& Materials::get_filament_vendor(const Preset *preset) +{ + const auto *opt = preset->config.opt("filament_vendor"); + return opt != nullptr ? opt->value : UNKNOWN; +} + +const std::string& Materials::get_material_type(const Preset *preset) +{ + const auto *opt = preset->config.opt("material_type"); + if (opt != nullptr) { + return opt->value; + } else { + return UNKNOWN; + } +} + +const std::string& Materials::get_material_vendor(const Preset *preset) +{ + const auto *opt = preset->config.opt("material_vendor"); + return opt != nullptr ? opt->value : UNKNOWN; +} + +// priv + +static const std::unordered_map> legacy_preset_map {{ + { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, + { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") }, + { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, + { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") }, + { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, + { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, +}}; + +void ConfigWizard::priv::load_pages() +{ + wxWindowUpdateLocker freeze_guard(q); + (void)freeze_guard; + + const ConfigWizardPage *former_active = index->active_page(); + + index->clear(); + + index->add_page(page_welcome); + + // Printers + if (!only_sla_mode) + index->add_page(page_fff); + index->add_page(page_msla); + 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_bvolume); + index->add_page(page_diams); + index->add_page(page_temps); + } + + // Filaments & Materials + 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 + btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected); + + index->add_page(page_update); + index->add_page(page_reload_from_disk); +#ifdef _WIN32 + index->add_page(page_files_association); +#endif // _WIN32 + index->add_page(page_mode); + + index->go_to(former_active); // Will restore the active item/page if possible + + q->Layout(); +// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig + q->Refresh(); +} + +void ConfigWizard::priv::init_dialog_size() +{ + // Clamp the Wizard size based on screen dimensions + + const auto idx = wxDisplay::GetFromWindow(q); + wxDisplay display(idx != wxNOT_FOUND ? idx : 0u); + + const auto disp_rect = display.GetClientArea(); + wxRect window_rect( + disp_rect.x + disp_rect.width / 20, + disp_rect.y + disp_rect.height / 20, + 9*disp_rect.width / 10, + 9*disp_rect.height / 10); + + 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; + } + + q->SetSize(window_rect); +} + +void ConfigWizard::priv::load_vendors() +{ + bundles = BundleMap::load(); + + // Load up the set of vendors / models / variants the user has had enabled up till now + AppConfig *app_config = wxGetApp().app_config; + if (! app_config->legacy_datadir()) { + appconfig_new.set_vendors(*app_config); + } else { + // In case of legacy datadir, try to guess the preference based on the printer preset files that are present + const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; + for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir)) + if (Slic3r::is_ini_file(dir_entry)) { + auto needle = legacy_preset_map.find(dir_entry.path().filename().string()); + if (needle == legacy_preset_map.end()) { continue; } + + const auto &model = needle->second.first; + const auto &variant = needle->second.second; + appconfig_new.set_variant("PrusaResearch", model, variant, true); + } + } + + // Initialize the is_visible flag in printer Presets + for (auto &pair : bundles) { + pair.second.preset_bundle->load_installed_printers(appconfig_new); + } + + // Copy installed filaments and SLA material names from app_config to appconfig_new + // while resolving current names of profiles, which were renamed in the meantime. + for (PrinterTechnology technology : { ptFFF, ptSLA }) { + const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; + std::map section_new; + if (app_config->has_section(section_name)) { + const std::map §ion_old = app_config->get_section(section_name); + for (const auto& material_name_and_installed : section_old) + if (material_name_and_installed.second == "1") { + // Material is installed. Resolve it in bundles. + size_t num_found = 0; + const std::string &material_name = material_name_and_installed.first; + for (auto &bundle : bundles) { + const PresetCollection &materials = bundle.second.preset_bundle->materials(technology); + const Preset *preset = materials.find_preset(material_name); + if (preset == nullptr) { + // Not found. Maybe the material preset is there, bu it was was renamed? + const std::string *new_name = materials.get_preset_name_renamed(material_name); + if (new_name != nullptr) + preset = materials.find_preset(*new_name); + } + if (preset != nullptr) { + // Materal preset was found, mark it as installed. + section_new[preset->name] = "1"; + ++ num_found; + } + } + if (num_found == 0) + BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name; + else if (num_found > 1) + BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found; + } + } + appconfig_new.set_section(section_name, section_new); + }; +} + +void ConfigWizard::priv::add_page(ConfigWizardPage *page) +{ + const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0; + hscroll_sizer->Add(page, proportion, wxEXPAND); + all_pages.push_back(page); +} + +void ConfigWizard::priv::enable_next(bool enable) +{ + btn_next->Enable(enable); + btn_finish->Enable(enable); +} + +void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page) +{ + switch (start_page) { + case ConfigWizard::SP_PRINTERS: + index->go_to(page_fff); + btn_next->SetFocus(); + break; + case ConfigWizard::SP_FILAMENTS: + index->go_to(page_filaments); + btn_finish->SetFocus(); + break; + case ConfigWizard::SP_MATERIALS: + index->go_to(page_sla_materials); + btn_finish->SetFocus(); + break; + default: + index->go_to(page_welcome); + btn_next->SetFocus(); + break; + } +} + +void ConfigWizard::priv::create_3rdparty_pages() +{ + for (const auto &pair : bundles) { + const VendorProfile *vendor = pair.second.vendor_profile; + if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } + + bool is_fff_technology = false; + bool is_sla_technology = false; + + for (auto& model: vendor->models) + { + if (!is_fff_technology && model.technology == ptFFF) + is_fff_technology = true; + if (!is_sla_technology && model.technology == ptSLA) + is_sla_technology = true; + } + + PagePrinters* pageFFF = nullptr; + PagePrinters* pageSLA = nullptr; + + if (is_fff_technology) { + pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF); + add_page(pageFFF); + } + + if (is_sla_technology) { + pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA); + add_page(pageSLA); + } + + pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}}); + } +} + +void ConfigWizard::priv::set_run_reason(RunReason run_reason) +{ + this->run_reason = run_reason; + for (auto &page : all_pages) { + page->set_run_reason(run_reason); + } +} + +void ConfigWizard::priv::update_materials(Technology technology) +{ + if (any_fff_selected && (technology & T_FFF)) { + filaments.clear(); + aliases_fff.clear(); + // Iterate filaments in all bundles + for (const auto &pair : bundles) { + for (const auto &filament : pair.second.preset_bundle->filaments) { + // Check if filament is already added + if (filaments.containts(&filament)) + continue; + // Iterate printers in all bundles + for (const auto &printer : pair.second.preset_bundle->printers) { + if (!printer.is_visible || printer.printer_technology() != ptFFF) + continue; + // Filter out inapplicable printers + if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) { + if (!filaments.containts(&filament)) { + filaments.push(&filament); + if (!filament.alias.empty()) + aliases_fff[filament.alias].insert(filament.name); + } + filaments.add_printer(&printer); + } + } + + } + } + // count compatible printers + for (const auto& preset : filaments.presets) { + + const auto filter = [preset](const std::pair element) { + return preset->alias == element.first; + }; + if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) { + continue; + } + std::vector idx_with_same_alias; + for (size_t i = 0; i < filaments.presets.size(); ++i) { + if (preset->alias == filaments.presets[i]->alias) + idx_with_same_alias.push_back(i); + } + size_t counter = 0; + for (const auto& printer : filaments.printers) { + if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) + continue; + bool compatible = false; + // Test otrher materials with same alias + for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { + const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]); + const Preset& prntr = *printer; + if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { + compatible = true; + break; + } + } + if (compatible) + counter++; + } + filaments.compatibility_counter.emplace_back(preset->alias, counter); + } + } + + if (any_sla_selected && (technology & T_SLA)) { + sla_materials.clear(); + aliases_sla.clear(); + + // Iterate SLA materials in all bundles + for (const auto &pair : bundles) { + for (const auto &material : pair.second.preset_bundle->sla_materials) { + // Check if material is already added + if (sla_materials.containts(&material)) + continue; + // Iterate printers in all bundles + // For now, we only allow the profiles to be compatible with another profiles inside the same bundle. + for (const auto& printer : pair.second.preset_bundle->printers) { + if(!printer.is_visible || printer.printer_technology() != ptSLA) + continue; + // Filter out inapplicable printers + if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) { + // Check if material is already added + if(!sla_materials.containts(&material)) { + sla_materials.push(&material); + if (!material.alias.empty()) + aliases_sla[material.alias].insert(material.name); + } + sla_materials.add_printer(&printer); + } + } + } + } + // count compatible printers + for (const auto& preset : sla_materials.presets) { + + const auto filter = [preset](const std::pair element) { + return preset->alias == element.first; + }; + if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) { + continue; + } + std::vector idx_with_same_alias; + for (size_t i = 0; i < sla_materials.presets.size(); ++i) { + if(preset->alias == sla_materials.presets[i]->alias) + idx_with_same_alias.push_back(i); + } + size_t counter = 0; + for (const auto& printer : sla_materials.printers) { + if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA) + continue; + bool compatible = false; + // Test otrher materials with same alias + for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { + const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]); + const Preset& prntr = *printer; + if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { + compatible = true; + break; + } + } + if (compatible) + counter++; + } + sla_materials.compatibility_counter.emplace_back(preset->alias, counter); + } + } +} + +void ConfigWizard::priv::on_custom_setup(const bool custom_wanted) +{ + custom_printer_selected = custom_wanted; + load_pages(); +} + +void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt) +{ + if (check_sla_selected() != any_sla_selected || + check_fff_selected() != any_fff_selected) { + any_fff_selected = check_fff_selected(); + any_sla_selected = check_sla_selected(); + + load_pages(); + } + + // Update the is_visible flag on relevant printer profiles + for (auto &pair : bundles) { + if (pair.first != evt.vendor_id) { continue; } + + for (auto &preset : pair.second.preset_bundle->printers) { + if (preset.config.opt_string("printer_model") == evt.model_id + && preset.config.opt_string("printer_variant") == evt.variant_name) { + preset.is_visible = evt.enable; + } + } + + // When a printer model is picked, but there is no material installed compatible with this printer model, + // install default materials for selected printer model silently. + check_and_install_missing_materials(page->technology, evt.model_id); + } + + if (page->technology & T_FFF) { + page_filaments->clear(); + } else if (page->technology & T_SLA) { + page_sla_materials->clear(); + } +} + +void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology) +{ + PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials; + for (const std::string& material : printer_model.default_materials) + appconfig_new.set(page_materials->materials->appconfig_section(), material, "1"); +} + +void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set &printer_models) +{ + PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials; + const std::string &appconfig_section = page_materials->materials->appconfig_section(); + + // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page. + // Filament is selected on same page for all printers of same technology. + /* + auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology) + { + const std::string vendor_id = page_printers->get_vendor_id(); + for (auto& pair : bundles) + if (pair.first == vendor_id) + for (const VendorProfile::PrinterModel *printer_model : printer_models) + for (const std::string &material : printer_model->default_materials) + appconfig_new.set(appconfig_section, material, "1"); + }; + + PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla; + select_default_materials_for_printer_page(page_printers, technology); + + for (const auto& printer : pages_3rdparty) + { + page_printers = technology & T_FFF ? printer.second.first : printer.second.second; + if (page_printers) + select_default_materials_for_printer_page(page_printers, technology); + } + */ + + // Iterate printer_models and select default materials. If none available -> msg to user. + std::vector models_without_default; + for (const VendorProfile::PrinterModel* printer_model : printer_models) { + if (printer_model->default_materials.empty()) { + models_without_default.emplace_back(printer_model); + } else { + for (const std::string& material : printer_model->default_materials) + appconfig_new.set(appconfig_section, material, "1"); + } + } + + if (!models_without_default.empty()) { + std::string printer_names = "\n\n"; + for (const VendorProfile::PrinterModel* printer_model : models_without_default) { + printer_names += printer_model->name + "\n"; + } + printer_names += "\n\n"; + std::string message = (technology & T_FFF ? + GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) : + GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names)); + MessageDialog msg(q, message, _L("Notice"), wxOK); + msg.ShowModal(); + } + + update_materials(technology); + ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets(); +} + +void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install) +{ + auto it = pages_3rdparty.find(vendor->id); + wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile"); + + for (PagePrinters* page : { it->second.first, it->second.second }) + if (page) { + if (page->install && !install) + page->select_all(false); + page->install = install; + // if some 3rd vendor is selected, select first printer for them + if (install) + page->printer_pickers[0]->select_one(0, true); + page->Layout(); + } + + load_pages(); +} + +bool ConfigWizard::priv::on_bnt_finish() +{ + wxBusyCursor wait; + /* When Filaments or Sla Materials pages are activated, + * materials for this pages are automaticaly updated and presets are reloaded. + * + * But, if _Finish_ button was clicked without activation of those pages + * (for example, just some printers were added/deleted), + * than last changes wouldn't be updated for filaments/materials. + * SO, do that before close of Wizard + */ + update_materials(T_ANY); + if (any_fff_selected) + page_filaments->reload_presets(); + if (any_sla_selected) + page_sla_materials->reload_presets(); + + // theres no need to check that filament is selected if we have only custom printer + if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true; + // check, that there is selected at least one filament/material + return check_and_install_missing_materials(T_ANY); +} + +// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed +// for each Printer preset of each Printer Model installed. +// +// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently. +// Otherwise the user is quieried whether to install the missing default materials or not. +// +// Return true if the tested Printer Models already had materials installed. +// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these +// respective Printer Models or not. +bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id) +{ + // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle, + // which is compatible with it. + const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion) + { + const std::map &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map(); + std::set printer_models_without_material; + for (const auto &pair : bundles) { + const PresetCollection &materials = pair.second.preset_bundle->materials(technology); + for (const auto &printer : pair.second.preset_bundle->printers) { + if (printer.is_visible && printer.printer_technology() == technology) { + const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer); + assert(printer_model != nullptr); + if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) && + printer_models_without_material.find(printer_model) == printer_models_without_material.end()) { + bool has_material = false; + for (const auto& preset : appconfig_presets) { + if (preset.second == "1") { + const Preset *material = materials.find_preset(preset.first, false); + if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) { + has_material = true; + break; + } + } + } + if (! has_material) + printer_models_without_material.insert(printer_model); + } + } + } + } + assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id); + return printer_models_without_material; + }; + + const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology) + { + //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO); + MessageDialog msg(q, message, _L("Notice"), wxYES_NO); + if (msg.ShowModal() == wxID_YES) + select_default_materials_for_printer_models(technology, printer_models); + }; + + const auto printer_model_list = [](const std::set &printer_models) -> wxString { + wxString out; + for (const VendorProfile::PrinterModel *printer_model : printer_models) { + wxString name = from_u8(printer_model->name); + out += "\t\t"; + out += name; + out += "\n"; + } + return out; + }; + + if (any_fff_selected && (technology & T_FFF)) { + std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS); + if (! printer_models_without_material.empty()) { + if (only_for_model_id.empty()) + ask_and_select_default_materials( + _L("The following FFF printer models have no filament selected:") + + "\n\n\t" + + printer_model_list(printer_models_without_material) + + "\n\n\t" + + _L("Do you want to select default filaments for these FFF printer models?"), + printer_models_without_material, + T_FFF); + else + select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF); + return false; + } + } + + if (any_sla_selected && (technology & T_SLA)) { + std::set printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS); + if (! printer_models_without_material.empty()) { + if (only_for_model_id.empty()) + ask_and_select_default_materials( + _L("The following SLA printer models have no materials selected:") + + "\n\n\t" + + printer_model_list(printer_models_without_material) + + "\n\n\t" + + _L("Do you want to select default SLA materials for these printer models?"), + printer_models_without_material, + T_SLA); + else + select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA); + return false; + } + } + + return true; +} + +static std::set get_new_added_presets(const std::map& old_data, const std::map& new_data) +{ + auto get_aliases = [](const std::map& data) { + std::set old_aliases; + for (auto item : data) { + const std::string& name = item.first; + size_t pos = name.find("@"); + old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1)); + } + return old_aliases; + }; + + std::set old_aliases = get_aliases(old_data); + std::set new_aliases = get_aliases(new_data); + std::set diff; + std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin())); + + return diff; +} + +static std::string get_first_added_preset(const std::map& old_data, const std::map& new_data) +{ + std::set diff = get_new_added_presets(old_data, new_data); + if (diff.empty()) + return std::string(); + return *diff.begin(); +} + +bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes) +{ + wxString header, caption = _L("Configuration is edited in ConfigWizard"); + const auto enabled_vendors = appconfig_new.vendors(); + const auto enabled_vendors_old = app_config->vendors(); + + bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model()); + PrinterTechnology preferred_pt = ptAny; + auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { + const auto config = enabled_vendors.find(bundle_name); + PrinterTechnology pt = ptAny; + if (config != enabled_vendors.end()) { + for (const auto& model : bundle.vendor_profile->models) { + if (const auto model_it = config->second.find(model.id); + model_it != config->second.end() && model_it->second.size() > 0) { + pt = model.technology; + const auto config_old = enabled_vendors_old.find(bundle_name); + if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) { + // if preferred printer model has SLA printer technology it's important to check the model for multi-part state + if (pt == ptSLA && suppress_sla_printer) + continue; + return pt; + } + + if (const auto model_it_old = config_old->second.find(model.id); + model_it_old == config_old->second.end() || model_it_old->second != model_it->second) { + // if preferred printer model has SLA printer technology it's important to check the model for multi-part state + if (pt == ptSLA && suppress_sla_printer) + continue; + return pt; + } + } + } + } + return pt; + }; + // Prusa printers are considered first, then 3rd party. + if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle()); + preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) { + for (const auto& bundle : bundles) { + if (bundle.second.is_prusa_bundle) { continue; } + if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny) + continue; + else if (preferred_pt == ptAny) + preferred_pt = pt; + if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer))) + break; + } + } + + if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption)) + return false; + + bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); + if (check_unsaved_preset_changes) + header = _L("All user presets will be deleted."); + int act_btns = ActionButtons::KEEP; + if (!check_unsaved_preset_changes) + act_btns |= ActionButtons::SAVE; + + // Install bundles from resources if needed: + std::vector install_bundles; + for (const auto &pair : bundles) { + if (! pair.second.is_in_resources) { continue; } + + if (pair.second.is_prusa_bundle) { + // Always install Prusa bundle, because it has a lot of filaments/materials + // likely to be referenced by other profiles. + install_bundles.emplace_back(pair.first); + continue; + } + + const auto vendor = enabled_vendors.find(pair.first); + if (vendor == enabled_vendors.end()) { continue; } + + size_t size_sum = 0; + for (const auto &model : vendor->second) { size_sum += model.second.size(); } + + if (size_sum > 0) { + // This vendor needs to be installed + install_bundles.emplace_back(pair.first); + } + } + if (!check_unsaved_preset_changes) + if ((check_unsaved_preset_changes = install_bundles.size() > 0)) + 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 + if (page_welcome->integrate_desktop()) + DesktopIntegrationDialog::perform_desktop_integration(); +#endif + + // Decide whether to create snapshot based on run_reason and the reset profile checkbox + bool snapshot = true; + Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE; + switch (run_reason) { + case ConfigWizard::RR_DATA_EMPTY: + snapshot = false; + break; + case ConfigWizard::RR_DATA_LEGACY: + snapshot = true; + break; + case ConfigWizard::RR_DATA_INCOMPAT: + // In this case snapshot has already been taken by + // PresetUpdater with the appropriate reason + snapshot = false; + break; + case ConfigWizard::RR_USER: + snapshot = page_welcome->reset_user_profile(); + snapshot_reason = Snapshot::SNAPSHOT_USER; + break; + } + + 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 && + !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + + if (install_bundles.size() > 0) { + // Install bundles from resources. + // Don't create snapshot - we've already done that above if applicable. + if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) + return false; + } else { + BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; + } + + if (page_welcome->reset_user_profile()) { + BOOST_LOG_TRIVIAL(info) << "Resetting user profiles..."; + preset_bundle->reset(true); + } + + std::string preferred_model; + std::string preferred_variant; + auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { + const auto config = enabled_vendors.find(bundle_name); + if (config == enabled_vendors.end()) + return std::string(); + for (const auto& model : bundle.vendor_profile->models) { + if (const auto model_it = config->second.find(model.id); + model_it != config->second.end() && model_it->second.size() > 0 && + preferred_pt == model.technology) { + variant = *model_it->second.begin(); + const auto config_old = enabled_vendors_old.find(bundle_name); + if (config_old == enabled_vendors_old.end()) + return model.id; + const auto model_it_old = config_old->second.find(model.id); + if (model_it_old == config_old->second.end()) + return model.id; + else if (model_it_old->second != model_it->second) { + for (const auto& var : model_it->second) + if (model_it_old->second.find(var) == model_it_old->second.end()) { + variant = var; + return model.id; + } + } + } + } + if (!variant.empty()) + variant.clear(); + return std::string(); + }; + // Prusa printers are considered first, then 3rd party. + if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant); + preferred_model.empty()) { + for (const auto& bundle : bundles) { + if (bundle.second.is_prusa_bundle) { continue; } + if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant); + !preferred_model.empty()) + break; + } + } + + // if unsaved changes was not cheched till this moment + if (!check_unsaved_preset_changes) { + if ((check_unsaved_preset_changes = !preferred_model.empty())) { + header = _L("A new Printer was installed and it will be activated."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) { + header = _L("Some Printers were uninstalled."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + } + + std::string first_added_filament, first_added_sla_material; + auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) { + if (appconfig_new.has_section(section_name)) { + // get first of new added preset names + const std::map& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map(); + first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name)); + } + }; + get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament); + get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material); + + // if unsaved changes was not cheched till this moment + if (!check_unsaved_preset_changes) { + if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) { + header = !first_added_filament.empty() ? + _L("A new filament was installed and it will be activated.") : + _L("A new SLA material was installed and it will be activated."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + else { + auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) { + return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map()) != appconfig_new.get_section(section_name); + }; + bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS); + bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS); + if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) { + header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + } + } + + // apply materials in app_config + for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS}) + app_config->set_section(section_name, appconfig_new.get_section(section_name)); + + app_config->set_vendors(appconfig_new); + + app_config->set("notify_release", page_update->version_check ? "all" : "none"); + app_config->set("preset_update", page_update->preset_update ? "1" : "0"); + app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); + +#ifdef _WIN32 + app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); + app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); +// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); + + if (wxGetApp().is_editor()) { + if (page_files_association->associate_3mf()) + wxGetApp().associate_3mf_files(); + if (page_files_association->associate_stl()) + wxGetApp().associate_stl_files(); + } +// else { +// if (page_files_association->associate_gcode()) +// wxGetApp().associate_gcode_files(); +// } +#endif // _WIN32 + + page_mode->serialize_mode(app_config); + + if (check_unsaved_preset_changes) + preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, + {preferred_model, preferred_variant, first_added_filament, first_added_sla_material}); + + 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)) + return false; + + page_firmware->apply_custom_config(*custom_config); + page_bed->apply_custom_config(*custom_config); + page_bvolume->apply_custom_config(*custom_config); + page_diams->apply_custom_config(*custom_config); + page_temps->apply_custom_config(*custom_config); + + copy_bed_model_and_texture_if_needed(*custom_config); + + const std::string profile_name = page_custom->profile_name(); + preset_bundle->load_config_from_wizard(profile_name, *custom_config); + } + + // Update the selections from the compatibilty. + preset_bundle->export_selections(*app_config); + + return true; +} +void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add) +{ + const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla; + + auto update = [this, add](const std::string& s, const std::string& key) { + assert(! s.empty()); + if (add) + appconfig_new.set(s, key, "1"); + else + appconfig_new.erase(s, key); + }; + + // add or delete presets had a same alias + auto it = aliases.find(alias_key); + if (it != aliases.end()) + for (const std::string& name : it->second) + update(section, name); +} + +bool ConfigWizard::priv::check_fff_selected() +{ + bool ret = page_fff->any_selected(); + for (const auto& printer: pages_3rdparty) + if (printer.second.first) // FFF page + ret |= printer.second.first->any_selected(); + return ret; +} + +bool ConfigWizard::priv::check_sla_selected() +{ + bool ret = page_msla->any_selected(); + for (const auto& printer: pages_3rdparty) + if (printer.second.second) // SLA page + ret |= printer.second.second->any_selected(); + return ret; +} + + +// Public + +ConfigWizard::ConfigWizard(wxWindow *parent) + : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + , p(new priv(this)) +{ + this->SetFont(wxGetApp().normal_font()); + + p->load_vendors(); + p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ + "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", + })); + + p->index = new ConfigWizardIndex(this); + + auto *vsizer = new wxBoxSizer(wxVERTICAL); + auto *topsizer = new wxBoxSizer(wxHORIZONTAL); + auto* hline = new StaticLine(this); + p->btnsizer = new wxBoxSizer(wxHORIZONTAL); + + // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling. + // Later, we compare that to the size of the current screen and set minimum width based on that (see below). + p->hscroll = new wxScrolledWindow(this); + p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL); + p->hscroll->SetSizer(p->hscroll_sizer); + + topsizer->Add(p->index, 0, wxEXPAND); + topsizer->AddSpacer(INDEX_MARGIN); + topsizer->Add(p->hscroll, 1, wxEXPAND); + + p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers")); + p->btnsizer->Add(p->btn_sel_all); + + p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back")); + p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >")); + p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish")); + p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel")); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac + p->btnsizer->AddStretchSpacer(); + p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING); + + wxGetApp().UpdateDarkUI(p->btn_sel_all); + wxGetApp().UpdateDarkUI(p->btn_prev); + wxGetApp().UpdateDarkUI(p->btn_next); + wxGetApp().UpdateDarkUI(p->btn_finish); + wxGetApp().UpdateDarkUI(p->btn_cancel); + + const auto prusa_it = p->bundles.find("PrusaResearch"); + wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found"); + const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile; + + 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->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; + } + + 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->only_sla_mode && 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_sla_materials = new PageMaterials(this, &p->sla_materials, + _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") )); + + + p->add_page(p->page_update = new PageUpdate(this)); + p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); +#ifdef _WIN32 + p->add_page(p->page_files_association = new PageFilesAssociation(this)); +#endif // _WIN32 + p->add_page(p->page_mode = new PageMode(this)); + p->add_page(p->page_firmware = new PageFirmware(this)); + p->add_page(p->page_bed = new PageBedShape(this)); + p->add_page(p->page_bvolume = new PageBuildVolume(this)); + p->add_page(p->page_diams = new PageDiameters(this)); + p->add_page(p->page_temps = new PageTemperatures(this)); + + p->load_pages(); + p->index->go_to(size_t{0}); + + vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); + vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING); + vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); + + SetSizer(vsizer); + SetSizerAndFit(vsizer); + + // We can now enable scrolling on hscroll + p->hscroll->SetScrollRate(30, 30); + + on_window_geometry(this, [this]() { + p->init_dialog_size(); + }); + + p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); }); + + p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) + { + // check, that there is selected at least one filament/material + ConfigWizardPage* active_page = this->p->index->active_page(); + if (// Leaving the filaments or SLA materials page and + (active_page == p->page_filaments || active_page == p->page_sla_materials) && + // some Printer models had no filament or SLA material selected. + ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology)) + // In that case don't leave the page and the function above queried the user whether to install default materials. + return; + this->p->index->go_next(); + }); + + p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) + { + if (p->on_bnt_finish()) + this->EndModal(wxID_OK); + }); + + p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { + p->any_sla_selected = true; + p->load_pages(); + p->page_fff->select_all(true, false); + p->page_msla->select_all(true, false); + p->index->go_to(p->page_mode); + }); + + p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) { + const bool is_last = p->index->active_is_last(); + p->btn_next->Show(! is_last); + if (is_last) + p->btn_finish->SetFocus(); + + Layout(); + }); + + if (wxLinux_gtk3) + this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) { + ConfigWizardPage* active_page = p->index->active_page(); + if (!active_page) + return; + for (auto page : p->all_pages) + if (page != active_page) + page->Hide(); + // update best size for the dialog after hiding of the non-active pages + vsizer->SetSizeHints(this); + // set initial dialog size + p->init_dialog_size(); + }); +} + +ConfigWizard::~ConfigWizard() {} + +bool ConfigWizard::run(RunReason reason, StartPage start_page) +{ + BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page; + + GUI_App &app = wxGetApp(); + + p->set_run_reason(reason); + p->set_start_page(start_page); + + if (ShowModal() == wxID_OK) { + bool apply_keeped_changes = false; + if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes)) + return false; + + if (apply_keeped_changes) + app.apply_keeped_preset_modifications(); + + app.app_config->set_legacy_datadir(false); + app.update_mode(); + app.obj_manipul()->update_ui_from_settings(); + BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; + return true; + } else { + BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled"; + return false; + } +} + +const wxString& ConfigWizard::name(const bool from_menu/* = false*/) +{ + // A different naming convention is used for the Wizard on Windows & GTK vs. OSX. + // Note: Don't call _() macro here. + // This function just return the current name according to the OS. + // Translation is implemented inside GUI_App::add_config_menu() +#if __APPLE__ + static const wxString config_wizard_name = L("Configuration Assistant"); + static const wxString config_wizard_name_menu = L("Configuration &Assistant"); +#else + static const wxString config_wizard_name = L("Configuration Wizard"); + static const wxString config_wizard_name_menu = L("Configuration &Wizard"); +#endif + return from_menu ? config_wizard_name_menu : config_wizard_name; +} + +void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect) +{ + p->index->msw_rescale(); + + const int em = em_unit(); + + msw_buttons_rescale(this, em, { wxID_APPLY, + wxID_CANCEL, + p->btn_sel_all->GetId(), + p->btn_next->GetId(), + p->btn_prev->GetId() }); + + for (auto printer_picker: p->page_fff->printer_pickers) + msw_buttons_rescale(this, em, printer_picker->get_button_indexes()); + + p->init_dialog_size(); + + Refresh(); +} + +void ConfigWizard::on_sys_color_changed() +{ + wxGetApp().UpdateDlgDarkUI(this); + Refresh(); +} + +} +} diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index d757eed634..dc705cab94 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -450,6 +450,14 @@ struct PageBedShape: ConfigWizardPage virtual void apply_custom_config(DynamicPrintConfig &config); }; +struct PageBuildVolume : ConfigWizardPage +{ + wxTextCtrl* build_volume; + + PageBuildVolume(ConfigWizard* parent); + virtual void apply_custom_config(DynamicPrintConfig& config); +}; + struct PageDiameters: ConfigWizardPage { wxTextCtrl *diam_nozzle; @@ -584,6 +592,7 @@ struct ConfigWizard::priv PageBedShape *page_bed = nullptr; PageDiameters *page_diams = nullptr; PageTemperatures *page_temps = nullptr; + PageBuildVolume* page_bvolume = nullptr; // Pointers to all pages (regardless or whether currently part of the ConfigWizardIndex) std::vector all_pages; From 767a556443e3f4bc6b4c06491f25192926d1d4e8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 8 Dec 2022 11:20:05 +0100 Subject: [PATCH 34/45] MenuFactory: SLA specific: Fixed adding of the "Edit text" menu item --- src/slic3r/GUI/GUI_Factories.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 0ae1b8870a..3a51ac4254 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -970,16 +970,19 @@ void MenuFactory::append_menu_item_edit_text(wxMenu *menu) wxString name = _L("Edit text"); auto can_edit_text = []() { - const auto& sel = plater()->get_selection(); - if (sel.volumes_count() != 1) return false; - auto cid = sel.get_volume(*sel.get_volume_idxs().begin()); - const ModelVolume* vol = plater()->canvas3D()->get_model() - ->objects[cid->object_idx()]->volumes[cid->volume_idx()]; - return vol->text_configuration.has_value(); + if (plater() != nullptr) { + const Selection& sel = plater()->get_selection(); + if (sel.volumes_count() == 1) { + const GLVolume* gl_vol = sel.get_first_volume(); + const ModelVolume* vol = plater()->model().objects[gl_vol->object_idx()]->volumes[gl_vol->volume_idx()]; + return vol->text_configuration.has_value(); + } + } + return false; }; - if (menu == &m_object_menu) { - auto menu_item_id = menu->FindItem(name); + if (menu != &m_text_part_menu) { + const int menu_item_id = menu->FindItem(name); if (menu_item_id != wxNOT_FOUND) menu->Destroy(menu_item_id); if (!can_edit_text()) From 7f6f63db0fae37cb83dc5bbfab991f935c81ad7d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 6 Dec 2022 14:43:15 +0100 Subject: [PATCH 35/45] Fixed asserts in connecting islands and in perimeter overhangs due to self intersections in clipped clipping rectangle. --- src/libslic3r/Layer.cpp | 15 ++++++++------ src/libslic3r/PerimeterGenerator.cpp | 29 +++++++++++++++++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 2fa0c5c3ca..35b4a331ab 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -317,13 +317,16 @@ void Layer::build_up_down_graph(Layer& below, Layer& above) coord_t* end = srcs + 4; std::sort(begin, end); end = std::unique(begin, end); - assert(begin + 2 == end); - if (begin + 1 == end) + if (begin + 1 == end) { + // Self intersection may happen on source contour. Just copy the Z value. pt.z() = *begin; - else if (begin + 2 <= end) { - // store a -1 based negative index into the "intersections" vector here. - m_intersections.emplace_back(srcs[0], srcs[1]); - pt.z() = -coord_t(m_intersections.size()); + } else { + assert(begin + 2 == end); + if (begin + 2 <= end) { + // store a -1 based negative index into the "intersections" vector here. + m_intersections.emplace_back(srcs[0], srcs[1]); + pt.z() = -coord_t(m_intersections.size()); + } } } const std::vector>& intersections() const { return m_intersections; } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 490fd54bfe..875870fa8e 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -397,22 +397,37 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con ClipperLib_Z::Clipper clipper; clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) { + // The clipping contour may be simplified by clipping it with a bounding box of "subject" path. + // The clipping function used may produce self intersections outside of the "subject" bounding box. Such self intersections are + // harmless to the result of the clipping operation, + // Both ends of each edge belong to the same source: Either they are from subject or from clipping path. + assert(e1bot.z() >= 0 && e1top.z() >= 0); + assert(e2bot.z() >= 0 && e2top.z() >= 0); + assert((e1bot.z() == 0) == (e1top.z() == 0)); + assert((e2bot.z() == 0) == (e2top.z() == 0)); + + // Start & end points of the clipped polyline (extrusion path with a non-zero width). ClipperLib_Z::IntPoint start = e1bot; ClipperLib_Z::IntPoint end = e1top; - if (start.z() <= 0 && end.z() <= 0) { start = e2bot; end = e2top; } - assert(start.z() > 0 && end.z() > 0); + if (start.z() <= 0 && end.z() <= 0) { + // Self intersection on the source contour. + assert(start.z() == 0 && end.z() == 0); + pt.z() = 0; + } else { + // Interpolate extrusion line width. + assert(start.z() > 0 && end.z() > 0); - // Interpolate extrusion line width. - double length_sqr = (end - start).cast().squaredNorm(); - double dist_sqr = (pt - start).cast().squaredNorm(); - double t = std::sqrt(dist_sqr / length_sqr); + double length_sqr = (end - start).cast().squaredNorm(); + double dist_sqr = (pt - start).cast().squaredNorm(); + double t = std::sqrt(dist_sqr / length_sqr); - pt.z() = start.z() + coord_t((end.z() - start.z()) * t); + pt.z() = start.z() + coord_t((end.z() - start.z()) * t); + } }); clipper.AddPath(subject, ClipperLib_Z::ptSubject, false); From dee9fb797cd83d39c9caf0f62a42118624e21f42 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 8 Dec 2022 13:05:35 +0100 Subject: [PATCH 36/45] Follow-up to 8858651bf46dce2ac0b3435ab9b46a4053cf7c3b Fixed missing support interface layers in G-code after Refactoring of Layers: Reworked G-code export... --- src/libslic3r/GCode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index dc587f3880..e413ce991b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2327,7 +2327,7 @@ void GCode::process_layer_single_object( interface_extruder = dontcare_extruder; } bool extrude_support = has_support && support_extruder == extruder_id; - bool extrude_interface = interface_extruder && interface_extruder == extruder_id; + bool extrude_interface = has_interface && interface_extruder == extruder_id; if (extrude_support || extrude_interface) { init_layer_delayed(); m_layer = layer_to_print.support_layer; From f188e0ef8848bf4a47588b6c94028c6848e04559 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 8 Dec 2022 13:17:15 +0100 Subject: [PATCH 37/45] Draw circle on the origin --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 103 +++++++++++++++++++++--- 1 file changed, 91 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index e9e0abe11b..daaef32255 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -359,8 +359,62 @@ static ModelVolume *get_selected_volume(const Selection &selection); /// Offset in screan coordinate static Vec2d calc_mouse_to_center_text_offset(const Vec2d &mouse, const ModelVolume &mv); +/// +/// Access to one selected volume +/// +/// Containe what is selected +/// Slected when only one volume otherwise nullptr +static const GLVolume *get_gl_volume(const Selection &selection); + +/// +/// Get transformation to world +/// - use fix after store to 3mf when exists +/// +/// +/// To identify MovelVolume with fix transformation +/// +static Transform3d world_matrix(const GLVolume *gl_volume, const Model *model); +static Transform3d world_matrix(const Selection &selection); + } // namespace priv +const GLVolume *priv::get_gl_volume(const Selection &selection) { + const auto &list = selection.get_volume_idxs(); + if (list.size() != 1) + return nullptr; + unsigned int volume_idx = *list.begin(); + return selection.get_volume(volume_idx); +} + +Transform3d priv::world_matrix(const GLVolume *gl_volume, const Model *model) +{ + if (!gl_volume) + return Transform3d::Identity(); + Transform3d res = gl_volume->world_matrix(); + + if (!model) + return res; + ModelVolume* mv = get_model_volume(gl_volume, model->objects); + if (!mv) + return res; + + const std::optional &tc = mv->text_configuration; + if (!tc.has_value()) + return res; + + const std::optional &fix = tc->fix_3mf_tr; + if (!fix.has_value()) + return res; + + return res * (*fix); +} + +Transform3d priv::world_matrix(const Selection &selection) +{ + const GLVolume *gl_volume = get_gl_volume(selection); + return world_matrix(gl_volume, selection.get_model()); +} + Vec2d priv::calc_mouse_to_center_text_offset(const Vec2d& mouse, const ModelVolume& mv) { const Transform3d &volume_tr = mv.get_matrix(); const Camera &camera = wxGetApp().plater()->get_camera(); @@ -645,6 +699,20 @@ static void draw_mouse_offset(const std::optional &offset) } #endif // SHOW_OFFSET_DURING_DRAGGING +static void draw_origin_ball(const GLCanvas3D& canvas) { + auto draw_list = ImGui::GetOverlayDrawList(); + const Selection &selection = canvas.get_selection(); + Transform3d to_world = priv::world_matrix(selection); + Vec3d volume_zero = to_world * Vec3d::Zero(); + + const Camera &camera = wxGetApp().plater()->get_camera(); + Point screen_coor = CameraUtils::project(camera, volume_zero); + ImVec2 center(screen_coor.x(), screen_coor.y()); + float radius = 10.f; + ImU32 color = ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT); + draw_list->AddCircleFilled(center, radius, color); +} + void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) { if (!m_gui_cfg.has_value()) initialize(); @@ -660,6 +728,8 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) const ImVec2 &min_window_size = get_minimal_window_size(); ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, min_window_size); + draw_origin_ball(m_parent); + #ifdef SHOW_FINE_POSITION draw_fine_position(m_parent.get_selection(), m_parent.get_canvas_size(), min_window_size); #endif // SHOW_FINE_POSITION @@ -1149,16 +1219,26 @@ void GLGizmoEmboss::discard_and_close() { // * Volume containing 3mf fix transformation - needs work around } -bool use_camera_dir(const Camera &camera, GLCanvas3D &canvas) { +namespace priv { + +/// +/// Apply camera direction for emboss direction +/// +/// Define view vector +/// Containe Selected Model to modify +/// True when apply change otherwise false +static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas); +} + +bool priv::apply_camera_dir(const Camera &camera, GLCanvas3D &canvas) { const Vec3d &cam_dir = camera.get_dir_forward(); Selection &sel = canvas.get_selection(); if (sel.is_empty()) return false; - assert(sel.get_volume_idxs().size() == 1); - GLVolume *vol = sel.get_volume(*sel.get_volume_idxs().begin()); - + // camera direction transformed into volume coordinate system - Vec3d cam_dir_tr = vol->world_matrix().inverse().linear() * cam_dir; + Transform3d to_world = priv::world_matrix(sel); + Vec3d cam_dir_tr = to_world.inverse().linear() * cam_dir; cam_dir_tr.normalize(); Vec3d emboss_dir(0., 0., -1.); @@ -1166,6 +1246,9 @@ bool use_camera_dir(const Camera &camera, GLCanvas3D &canvas) { // check wether cam_dir is already used if (is_approx(cam_dir_tr, emboss_dir)) return false; + assert(sel.get_volume_idxs().size() == 1); + GLVolume *vol = sel.get_volume(*sel.get_volume_idxs().begin()); + Transform3d vol_rot; Transform3d vol_tr = vol->get_volume_transformation().get_matrix(); // check whether cam_dir is opposit to emboss dir @@ -1298,7 +1381,7 @@ void GLGizmoEmboss::draw_window() assert(priv::get_selected_volume(m_parent.get_selection()) == m_volume); const Camera& cam = wxGetApp().plater()->get_camera(); bool use_surface = m_style_manager.get_style().prop.use_surface; - if (use_camera_dir(cam, m_parent) && use_surface) + if (priv::apply_camera_dir(cam, m_parent) && use_surface) process(); } else if (ImGui::IsItemHovered()) { ImGui::SetTooltip("%s", _u8L("Use camera direction for text orientation").c_str()); @@ -2591,12 +2674,8 @@ void GLGizmoEmboss::draw_style_edit() { bool use_inch = wxGetApp().app_config->get("use_inches") == "1"; FontProp &font_prop = style.prop; - const GLVolume* gl_vol = m_parent.get_selection().get_first_volume(); - Transform3d to_world = gl_vol->world_matrix(); - // Use fix of .3mf loaded tranformation when exist - if (m_volume->text_configuration->fix_3mf_tr.has_value()) - to_world = to_world * (*m_volume->text_configuration->fix_3mf_tr); - + // IMPROVE: calc scale only when neccessary not each frame + Transform3d to_world = priv::world_matrix(m_parent.get_selection()); Vec3d up_world = to_world.linear() * Vec3d(0., 1., 0.); double norm_sq = up_world.squaredNorm(); std::optional height_scale; From f44e57d335d38e5c9008fb5693996b31878b6123 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 8 Dec 2022 14:00:53 +0100 Subject: [PATCH 38/45] Fix for: Not printable text volume after update is set as printable (#SPE-1384, https://dev.prusa3d.com/browse/SPE-1384) --- src/slic3r/GUI/3DScene.cpp | 2 ++ src/slic3r/GUI/Jobs/EmbossJob.cpp | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 27a67001ec..b004938021 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -448,6 +448,8 @@ int GLVolumeCollection::load_object_volume( this->volumes.emplace_back(new GLVolume()); GLVolume& v = *this->volumes.back(); v.set_color(color_from_model_volume(*model_volume)); + // apply printable value from the instance + v.printable = instance->printable; #if ENABLE_SMOOTH_NORMALS v.model.init_from(*mesh, true); v.mesh_raycaster = std::make_unique(mesh); diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 0e7db94e87..a987e5b77c 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -527,10 +527,6 @@ void UpdateJob::update_volume(ModelVolume *volume, obj_list->update_name_in_list(object_idx, volume_idx); } - // update printable state on canvas - if (volume->type() == ModelVolumeType::MODEL_PART) - canvas->update_instance_printable_state_for_object((size_t) object_idx); - // Move object on bed if (GLGizmoEmboss::is_text_object(volume)) volume->get_object()->ensure_on_bed(); From 4b3fc0f821026214640d7cc15d28b83e3b958220 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 8 Dec 2022 14:10:17 +0100 Subject: [PATCH 39/45] fix: ../src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp:2675:15: warning: unused variable 'font_prop' [-Wunused-variable] ../src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp:3463:13: warning: 'Slic3r::Transform3d priv::create_transformation_on_bed(const Vec2d&, const Slic3r::GUI::Camera&, const std::vector >&, double)' defined but not used [-Wunused-function] --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 45 +++---------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index daaef32255..9c8a6acfbd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -204,19 +204,6 @@ static void find_closest_volume(const Selection &selection, /// Define params of text /// Screen coordinat, where to create new object laying on bed static void start_create_object_job(DataBase &emboss_data, const Vec2d &coor); - -/// -/// Create transformation for new created emboss object by mouse position -/// -/// Define where to add object -/// Actual camera view -/// Define shape of bed for its center and check that coor is on bed center -/// Emboss size / 2 -/// Transformation for create text on bed -static Transform3d create_transformation_on_bed(const Vec2d &screen_coor, - const Camera &camera, - const std::vector &bed_shape, - double z); } // namespace priv bool priv::is_valid(ModelVolumeType volume_type){ @@ -698,7 +685,7 @@ static void draw_mouse_offset(const std::optional &offset) draw_list->AddLine(p1, p2, color, thickness); } #endif // SHOW_OFFSET_DURING_DRAGGING - +namespace priv { static void draw_origin_ball(const GLCanvas3D& canvas) { auto draw_list = ImGui::GetOverlayDrawList(); const Selection &selection = canvas.get_selection(); @@ -713,6 +700,8 @@ static void draw_origin_ball(const GLCanvas3D& canvas) { draw_list->AddCircleFilled(center, radius, color); } +} // namespace priv + void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) { if (!m_gui_cfg.has_value()) initialize(); @@ -728,7 +717,7 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) const ImVec2 &min_window_size = get_minimal_window_size(); ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, min_window_size); - draw_origin_ball(m_parent); + priv::draw_origin_ball(m_parent); #ifdef SHOW_FINE_POSITION draw_fine_position(m_parent.get_selection(), m_parent.get_canvas_size(), min_window_size); @@ -2672,8 +2661,7 @@ void GLGizmoEmboss::draw_style_edit() { } bool use_inch = wxGetApp().app_config->get("use_inches") == "1"; - FontProp &font_prop = style.prop; - + // IMPROVE: calc scale only when neccessary not each frame Transform3d to_world = priv::world_matrix(m_parent.get_selection()); Vec3d up_world = to_world.linear() * Vec3d(0., 1., 0.); @@ -3459,29 +3447,6 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager& st return Slic3r::GUI::Emboss::DataBase{style_manager.get_font_file_with_cache(), create_configuration(), create_volume_name()}; } - -Transform3d priv::create_transformation_on_bed(const Vec2d &screen_coor, const Camera &camera, const std::vector &bed_shape, double z) -{ - // Create new object - // calculate X,Y offset position for lay on platter in place of - // mouse click - Vec2d bed_coor = CameraUtils::get_z0_position(camera, screen_coor); - - // check point is on build plate: - Points bed_shape_; - bed_shape_.reserve(bed_shape.size()); - for (const Vec2d &p : bed_shape) bed_shape_.emplace_back(p.cast()); - Slic3r::Polygon bed(bed_shape_); - if (!bed.contains(bed_coor.cast())) - // mouse pose is out of build plate so create object in center of plate - bed_coor = bed.centroid().cast(); - - Vec3d offset(bed_coor.x(), bed_coor.y(), z); - // offset -= m_result.center(); - Transform3d::TranslationType tt(offset.x(), offset.y(), offset.z()); - return Transform3d(tt); -} - void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor) { // start creation of new object From f81382f604d58d1ac9b73584622996de15248360 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 19 May 2022 16:59:05 +0200 Subject: [PATCH 40/45] Generic sphere shape is now created by recursive division of an icosahedron --- src/libslic3r/TriangleMesh.cpp | 151 ++++++++++++++++++-------- tests/fff_print/test_trianglemesh.cpp | 14 ++- 2 files changed, 117 insertions(+), 48 deletions(-) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index d5fe83c265..82dc254ca0 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1013,58 +1013,121 @@ indexed_triangle_set its_make_pyramid(float base, float height) // Generates mesh for a sphere centered about the origin, using the generated angle // to determine the granularity. // Default angle is 1 degree. -//FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html indexed_triangle_set its_make_sphere(double radius, double fa) { - int sectorCount = int(ceil(2. * M_PI / fa)); - int stackCount = int(ceil(M_PI / fa)); - float sectorStep = float(2. * M_PI / sectorCount); - float stackStep = float(M_PI / stackCount); - + // First build an icosahedron (taken from http://www.songho.ca/opengl/gl_sphere.html) indexed_triangle_set mesh; + + const float PI = 3.1415926f; + const float H_ANGLE = PI / 180 * 72; // 72 degree = 360 / 5 + const float V_ANGLE = atanf(1.0f / 2); // elevation = 26.565 degree + auto& vertices = mesh.vertices; - vertices.reserve((stackCount - 1) * sectorCount + 2); - for (int i = 0; i <= stackCount; ++ i) { - // from pi/2 to -pi/2 - double stackAngle = 0.5 * M_PI - stackStep * i; - double xy = radius * cos(stackAngle); - double z = radius * sin(stackAngle); - if (i == 0 || i == stackCount) - vertices.emplace_back(Vec3f(float(xy), 0.f, float(z))); - else - for (int j = 0; j < sectorCount; ++ j) { - // from 0 to 2pi - double sectorAngle = sectorStep * j; - vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast()); - } - } + auto& indices = mesh.indices; + vertices.resize(12); + indices.reserve(20); - auto& facets = mesh.indices; - facets.reserve(2 * (stackCount - 1) * sectorCount); - for (int i = 0; i < stackCount; ++ i) { - // Beginning of current stack. - int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); - int k1_first = k1; - // Beginning of next stack. - int k2 = (i == 0) ? 1 : (k1 + sectorCount); - int k2_first = k2; - for (int j = 0; j < sectorCount; ++ j) { - // 2 triangles per sector excluding first and last stacks - int k1_next = k1; - int k2_next = k2; - if (i != 0) { - k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); - facets.emplace_back(k1, k2, k1_next); + float z, xy; + float hAngle1 = -PI / 2 - H_ANGLE / 2; + + vertices[0] = stl_vertex(0, 0, radius); // the first top vertex at (0, 0, r) + + for (int i = 1; i <= 5; ++i) { + z = radius * sinf(V_ANGLE); + xy = radius * cosf(V_ANGLE); + vertices[i] = stl_vertex(xy * cosf(hAngle1), xy * sinf(hAngle1), z); + vertices[i+5] = stl_vertex(xy * cosf(hAngle1 + H_ANGLE / 2), xy * sinf(hAngle1 + H_ANGLE / 2), -z); + hAngle1 += H_ANGLE; + + indices.emplace_back(stl_triangle_vertex_indices(i, i < 5 ? i+1 : 1, 0)); + indices.emplace_back(stl_triangle_vertex_indices(i, i+5, i < 5 ? i+1 : 1)); + indices.emplace_back(stl_triangle_vertex_indices(i+5, i+6 < 11 ? i+6 : 6, i+6 < 11 ? i+1 : 1)); + indices.emplace_back(stl_triangle_vertex_indices(i+5, 11, i+6 < 11 ? i+6 : 6)); + } + vertices[11] = stl_vertex(0, 0, -radius); // the last bottom vertex at (0, 0, -r) + + + // We have a beautiful icosahedron. Now subdivide the triangles. + std::vector neighbors = its_face_neighbors(mesh); // This is cheap, the mesh is small. + + const double side_len_limit = radius * fa; + const double side_len = (vertices[1] - vertices[0]).norm(); + const int iterations = std::ceil(std::log2(side_len / side_len_limit)); + + indices.reserve(indices.size() * std::pow(4, iterations)); + vertices.reserve(vertices.size() * std::pow(2, iterations)); + + struct DividedEdge { + int neighbor = -1; + int middle_vertex_idx; + std::pair children_idxs; + }; + + for (int iter=0; iter> divided_triangles(indices.size()); + std::vector new_neighbors(4*indices.size()); + + size_t orig_indices_size = indices.size(); + for (int i=0; i, 3> edge_children = { std::make_pair(i,last_triangle_idx + 2), + std::make_pair(last_triangle_idx + 2,last_triangle_idx + 3), + std::make_pair(last_triangle_idx + 3,i) }; + + std::array middle_vertices_idxs; + std::array, 3> new_neighbors_per_edge; + + for (int n=0; n<3; ++n) { // for all three edges + const int edge_neighbor = neighbors[i][n]; + + if (divided_triangles[edge_neighbor][0].neighbor == -1) { + // This n-th edge is not yet divided. Divide it now. + vertices.emplace_back(0.5 * (vertices[indices[i][n]] + vertices[indices[i][n == 2 ? 0 : n+1]])); + vertices.back() *= radius / vertices.back().norm(); + middle_vertices_idxs[n] = vertices.size()-1; + + // Save information about what we did. + int j = -1; + while (divided_triangles[i][++j].neighbor != -1); + + divided_triangles[i][j] = { edge_neighbor, int(vertices.size()-1), edge_children[n] }; + new_neighbors_per_edge[n] = std::make_pair(-1,-1); + } else { + // This edge is already divided. Get the index of the middle point. + int j = -1; + while (divided_triangles[edge_neighbor][++j].neighbor != i); + middle_vertices_idxs[n] = divided_triangles[edge_neighbor][j].middle_vertex_idx; + new_neighbors_per_edge[n] = divided_triangles[edge_neighbor][j].children_idxs; + std::swap(new_neighbors_per_edge[n].first, new_neighbors_per_edge[n].second); + + // We have saved the middle-point. We are looking for edges leading to/from it. + int idx = -1; while (indices[new_neighbors_per_edge[n].first][++idx] != middle_vertices_idxs[n]); + new_neighbors[new_neighbors_per_edge[n].first][idx] = edge_children[n].first; + new_neighbors[new_neighbors_per_edge[n].second][idx] = edge_children[n].second; + } } - if (i + 1 != stackCount) { - k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); - facets.emplace_back(k1_next, k2, k2_next); - } - k1 = k1_next; - k2 = k2_next; + + // Add three new triangles, reindex the old one. + const int last_index = indices.size() - 1; + indices.emplace_back(stl_triangle_vertex_indices(middle_vertices_idxs[0], middle_vertices_idxs[1], middle_vertices_idxs[2])); + new_neighbors[indices.size()-1] = Vec3i(last_index+2, last_index+3, i); + + indices.emplace_back(stl_triangle_vertex_indices(middle_vertices_idxs[0], indices[i][1], middle_vertices_idxs[1])); + new_neighbors[indices.size()-1] = Vec3i(new_neighbors_per_edge[0].second, new_neighbors_per_edge[1].first, last_index+1); + + indices.emplace_back(stl_triangle_vertex_indices(middle_vertices_idxs[2], middle_vertices_idxs[1], indices[i][2])); + new_neighbors[indices.size()-1] = Vec3i(last_index+1, new_neighbors_per_edge[1].second, new_neighbors_per_edge[2].first); + + indices[i][1] = middle_vertices_idxs[0]; + indices[i][2] = middle_vertices_idxs[2]; + new_neighbors[i] = Vec3i(new_neighbors_per_edge[0].first, last_index+1, new_neighbors_per_edge[2].second); + } + neighbors = std::move(new_neighbors); } - return mesh; } diff --git a/tests/fff_print/test_trianglemesh.cpp b/tests/fff_print/test_trianglemesh.cpp index 6faaf1584c..eff39ed351 100644 --- a/tests/fff_print/test_trianglemesh.cpp +++ b/tests/fff_print/test_trianglemesh.cpp @@ -220,10 +220,16 @@ SCENARIO( "make_xxx functions produce meshes.") { GIVEN("make_sphere() function") { WHEN("make_sphere() is called with arguments 10, PI / 3") { TriangleMesh sph = make_sphere(10, PI / 243.0); - THEN("Resulting mesh has one point at 0,0,-10 and one at 0,0,10") { - const std::vector &verts = sph.its.vertices; - REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, 10.f)); } ) == 1); - REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, -10.f)); } ) == 1); + THEN( "Edge length is smaller than limit but not smaller than half of it") { + double len = (sph.its.vertices[sph.its.indices[0][0]] - sph.its.vertices[sph.its.indices[0][1]]).norm(); + double limit = 10*PI/243.; + REQUIRE(len <= limit); + REQUIRE(len >= limit/2.); + } + THEN( "Vertices are about the correct distance from the origin") { + bool all_vertices_ok = std::all_of(sph.its.vertices.begin(), sph.its.vertices.end(), + [](const stl_vertex& pt) { return is_approx(pt.squaredNorm(), 100.f); }); + REQUIRE(all_vertices_ok); } THEN( "The mesh volume is approximately 4/3 * pi * 10^3") { REQUIRE(abs(sph.volume() - (4.0/3.0 * M_PI * std::pow(10,3))) < 1); // 1% tolerance? From 4bc0d1dee2567a5649cfbb209ddd401825f22434 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 8 Dec 2022 15:20:40 +0100 Subject: [PATCH 41/45] Disallow float window --- src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 9c8a6acfbd..f631831893 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -55,6 +55,7 @@ #define SHOW_FINE_POSITION // draw convex hull around volume #define SHOW_WX_WEIGHT_INPUT #define DRAW_PLACE_TO_ADD_TEXT // Interactive draw of window position +#define ALLOW_FLOAT_WINDOW #endif // ALLOW_DEBUG_MODE using namespace Slic3r; @@ -1355,6 +1356,7 @@ void GLGizmoEmboss::draw_window() ImGui::Image(atlas.TexID, ImVec2(atlas.TexWidth, atlas.TexHeight)); #endif // SHOW_IMGUI_ATLAS +#ifdef ALLOW_FLOAT_WINDOW ImGui::SameLine(); if (ImGui::Checkbox("##allow_float_window", &m_allow_float_window)) { if (m_allow_float_window) @@ -1364,6 +1366,7 @@ void GLGizmoEmboss::draw_window() _u8L("Fix settings possition"): _u8L("Allow floating window near text")).c_str()); } +#endif // ALLOW_FLOAT_WINDOW ImGui::SameLine(); if (ImGui::Button("use")) { From 7edc2acfc063883d818d3ff360ada5ae63f4a31c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 9 Dec 2022 11:11:30 +0100 Subject: [PATCH 42/45] Cut: Connectors mode: Implemented forward/downward view to the cut plane + updated cut ikons --- resources/icons/cut_.svg | 48 +++++++++++++--------------- resources/icons/cut_connectors.svg | 40 ++++++++++------------- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 41 ++++++++++++++++++++---- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 ++ 4 files changed, 77 insertions(+), 54 deletions(-) diff --git a/resources/icons/cut_.svg b/resources/icons/cut_.svg index a7f462bb9c..0919e39522 100644 --- a/resources/icons/cut_.svg +++ b/resources/icons/cut_.svg @@ -1,28 +1,26 @@ - - - - - - - - - - - - - - + + + + + + + + diff --git a/resources/icons/cut_connectors.svg b/resources/icons/cut_connectors.svg index 8cd03aa06b..504df0a419 100644 --- a/resources/icons/cut_connectors.svg +++ b/resources/icons/cut_connectors.svg @@ -1,26 +1,20 @@ - - - - - - - + + + + + + diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 3cd661ba3f..9b3def4dd8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -359,10 +359,19 @@ void GLGizmoCut3D::put_connectors_on_cut_plane(const Vec3d& cp_normal, double cp } } +// returns true if the camera (forward) is pointing in the negative direction of the cut normal +bool GLGizmoCut3D::is_looking_forward() const +{ + const Camera& camera = wxGetApp().plater()->get_camera(); + const double dot = camera.get_dir_forward().dot(m_cut_normal); + return dot < 0.05; +} + void GLGizmoCut3D::update_clipper() { BoundingBoxf3 box = bounding_box(); + // update cut_normal Vec3d beg, end = beg = m_plane_center; beg[Z] = box.center().z() - m_radius; end[Z] = box.center().z() + m_radius; @@ -370,12 +379,26 @@ void GLGizmoCut3D::update_clipper() rotate_vec3d_around_plane_center(beg); rotate_vec3d_around_plane_center(end); - double dist = (m_plane_center - beg).norm(); + // calculate normal for cut plane + Vec3d normal = m_cut_normal = end - beg; + m_cut_normal.normalize(); + + if (!is_looking_forward()) { + end = beg = m_plane_center; + beg[Z] = box.center().z() + m_radius; + end[Z] = box.center().z() - m_radius; + + rotate_vec3d_around_plane_center(beg); + rotate_vec3d_around_plane_center(end); + + // recalculate normal for clipping plane, if camera is looking downward to cut plane + normal = end - beg; + if (normal == Vec3d::Zero()) + return; + } // calculate normal and offset for clipping plane - Vec3d normal = end - beg; - if (normal == Vec3d::Zero()) - return; + double dist = (m_plane_center - beg).norm(); dist = std::clamp(dist, 0.0001, normal.norm()); normal.normalize(); const double offset = normal.dot(beg) + dist; @@ -1372,7 +1395,7 @@ void GLGizmoCut3D::render_clipper_cut() void GLGizmoCut3D::on_render() { - if (update_bb() || force_update_clipper_on_render) { + if (update_bb() || force_update_clipper_on_render || m_connectors_editing) { update_clipper_on_render(); m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4); } @@ -1914,9 +1937,15 @@ void GLGizmoCut3D::render_connectors() const Camera& camera = wxGetApp().plater()->get_camera(); if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prizm) { - pos -= height * normal; + if (is_looking_forward()) + pos -= height * normal; + else + pos += height * normal; height *= 2; } + else if (!is_looking_forward()) + pos += 0.05 * normal; + const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * scale_transform(Vec3f(connector.radius, connector.radius, height).cast()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index e9412357d7..8fea38849e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -73,6 +73,7 @@ class GLGizmoCut3D : public GLGizmoBase GLModel m_angle_arc; Vec3d m_old_center; + Vec3d m_cut_normal; struct InvalidConnectorsStatistics { @@ -160,6 +161,7 @@ public: bool is_in_editing_mode() const override { return m_connectors_editing; } bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); } + bool is_looking_forward() const; /// /// Drag of plane From b9c54e900a1f26574e005ec28e6a99965d1dbd3b Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Fri, 9 Dec 2022 15:03:00 +0100 Subject: [PATCH 43/45] added SERMOONV1 and ENDER5 S1 thumbnails per request from https://github.com/prusa3d/PrusaSlicer-settings/issues/175 --- .../profiles/Creality/ENDER5S1_thumbnail.png | Bin 0 -> 36850 bytes .../profiles/Creality/SERMOONV1_thumbnail.png | Bin 0 -> 24117 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/profiles/Creality/ENDER5S1_thumbnail.png create mode 100644 resources/profiles/Creality/SERMOONV1_thumbnail.png diff --git a/resources/profiles/Creality/ENDER5S1_thumbnail.png b/resources/profiles/Creality/ENDER5S1_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..410c4f3d4c347805ceb136f79103dc8b06039b51 GIT binary patch literal 36850 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%0A2aWH> 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 Date: Fri, 9 Dec 2022 18:25:19 +0100 Subject: [PATCH 44/45] Cut: Extended detection if connector is outside of cut contour It respects to the whole connector contour now --- src/libslic3r/TriangleMesh.cpp | 4 ++- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 47 ++++++++++++++++++++++++++-- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 1 + 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 82dc254ca0..f4e27982b2 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -977,8 +977,10 @@ indexed_triangle_set its_make_cone(double r, double h, double fa) vertices.emplace_back(Vec3f(0., 0., h)); size_t i = 0; + const auto vec = Eigen::Vector2f(0, float(r)); for (double angle=0; angle<2*PI; angle+=fa) { - vertices.emplace_back(r*std::cos(angle), r*std::sin(angle), 0.); + Vec2f p = Eigen::Rotation2Df(angle) * vec; + vertices.emplace_back(Vec3f(p(0), p(1), 0.f)); if (angle > 0.) { facets.emplace_back(0, i+2, i+1); facets.emplace_back(1, i+1, i+2); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9b3def4dd8..21a8285d7c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -30,6 +30,7 @@ static const ColorRGBA SELECTED_PLAG_COLOR = ColorRGBA::GRAY(); static const ColorRGBA SELECTED_DOWEL_COLOR = ColorRGBA::DARK_GRAY(); static const ColorRGBA CONNECTOR_DEF_COLOR = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); static const ColorRGBA CONNECTOR_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); +static const ColorRGBA HOVERED_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 1.0f); const unsigned int AngleResolution = 64; const unsigned int ScaleStepsCount = 72; @@ -1849,7 +1850,7 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c return translation_transform(offset) * scale_transform(Vec3d::Ones() - border_scale) * vol_matrix; } -bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) +bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) { // check if connector pos is out of clipping plane if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos)) { @@ -1857,16 +1858,54 @@ bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& co return true; } + // check if connector bottom contour is out of clipping plane const CutConnector& cur_connector = connectors[idx]; + const CutConnectorShape shape = CutConnectorShape(cur_connector.attribs.shape); + const int sectorCount = shape == CutConnectorShape::Triangle ? 3 : + shape == CutConnectorShape::Square ? 4 : + shape == CutConnectorShape::Circle ? 60: // supposably, 60 points are enough for conflict detection + shape == CutConnectorShape::Hexagon ? 6 : 1 ; + + indexed_triangle_set mesh; + auto& vertices = mesh.vertices; + vertices.reserve(sectorCount + 1); + + float fa = 2 * PI / sectorCount; + auto vec = Eigen::Vector2f(0, cur_connector.radius); + for (float angle = 0; angle < 2.f * PI; angle += fa) { + Vec2f p = Eigen::Rotation2Df(angle) * vec; + vertices.emplace_back(Vec3f(p(0), p(1), 0.f)); + } + its_transform(mesh, translation_transform(cur_pos) * m_rotation_m); + + for (auto vertex : vertices) { + if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(vertex.cast())) { + m_info_stats.outside_cut_contour++; + return true; + } + } + + return false; +} + +bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) +{ + if (is_outside_of_cut_contour(idx, connectors, cur_pos)) + return true; + + const CutConnector& cur_connector = connectors[idx]; + const Transform3d matrix = translation_transform(cur_pos) * m_rotation_m * scale_transform(Vec3f(cur_connector.radius, cur_connector.radius, cur_connector.height).cast()); const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix); + // check if connector's bounding box is inside the object's bounding box if (!bounding_box().contains(cur_tbb)) { m_info_stats.outside_bb++; return true; } + // check if connectors are overlapping for (size_t i = 0; i < connectors.size(); ++i) { if (i == idx) continue; @@ -1920,7 +1959,8 @@ void GLGizmoCut3D::render_connectors() Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); // First decide about the color of the point. - if (is_conflict_for_connector(i, connectors, pos)) { + const bool conflict_connector = is_conflict_for_connector(i, connectors, pos); + if (conflict_connector) { m_has_invalid_connector = true; render_color = CONNECTOR_ERR_COLOR; } @@ -1930,7 +1970,8 @@ void GLGizmoCut3D::render_connectors() if (!m_connectors_editing) render_color = CONNECTOR_ERR_COLOR; else if (size_t(m_hover_id - m_connectors_group_id) == i) - render_color = connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; + render_color = conflict_connector ? HOVERED_ERR_COLOR : + connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; else if (m_selected[i]) render_color = connector.attribs.type == CutConnectorType::Dowel ? SELECTED_DOWEL_COLOR : SELECTED_PLAG_COLOR; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 8fea38849e..15d53c9de1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -241,6 +241,7 @@ private: bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; bool render_connect_type_radio_button(CutConnectorType type); Transform3d get_volume_transformation(const ModelVolume* volume) const; + bool is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); void render_connectors(); From 2bcb62d447f0e0db39de26d53525442a23f5f215 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 12 Dec 2022 08:27:19 +0100 Subject: [PATCH 45/45] Do not generate volumes' raycaster for preview --- src/slic3r/GUI/3DScene.cpp | 12 +++++++----- src/slic3r/GUI/3DScene.hpp | 2 ++ src/slic3r/GUI/GCodeViewer.cpp | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index b004938021..c858d5559c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -437,9 +437,9 @@ std::vector GLVolumeCollection::load_object( int GLVolumeCollection::load_object_volume( const ModelObject* model_object, - int obj_idx, - int volume_idx, - int instance_idx) + int obj_idx, + int volume_idx, + int instance_idx) { const ModelVolume *model_volume = model_object->volumes[volume_idx]; const int extruder_id = model_volume->extruder_id(); @@ -452,10 +452,12 @@ int GLVolumeCollection::load_object_volume( v.printable = instance->printable; #if ENABLE_SMOOTH_NORMALS v.model.init_from(*mesh, true); - v.mesh_raycaster = std::make_unique(mesh); + if (m_use_raycasters) + v.mesh_raycaster = std::make_unique(mesh); #else v.model.init_from(*mesh); - v.mesh_raycaster = std::make_unique(mesh); + if (m_use_raycasters) + v.mesh_raycaster = std::make_unique(mesh); #endif // ENABLE_SMOOTH_NORMALS v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); if (model_volume->is_model_part()) { diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 2eb161480b..674c3ce824 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -397,6 +397,7 @@ private: Slope m_slope; bool m_show_sinking_contours{ false }; bool m_show_non_manifold_edges{ true }; + bool m_use_raycasters{ true }; public: GLVolumePtrs volumes; @@ -445,6 +446,7 @@ public: bool empty() const { return volumes.empty(); } void set_range(double low, double high) { for (GLVolume* vol : this->volumes) vol->set_range(low, high); } + void set_use_raycasters(bool value) { m_use_raycasters = value; } void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index b0e11422db..097757ff94 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -643,7 +643,7 @@ const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY(); GCodeViewer::GCodeViewer() { m_extrusions.reset_role_visibility_flags(); - + m_shells.volumes.set_use_raycasters(false); // m_sequential_view.skip_invisible_moves = true; }